@drawcall/market 0.1.27 → 0.1.29
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/dist/asset-implementation.d.ts +1 -0
- package/dist/asset-implementation.d.ts.map +1 -1
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/upload.d.ts +18 -0
- package/dist/commands/upload.d.ts.map +1 -1
- package/dist/commands/upload.js +67 -3
- package/dist/commands/upload.js.map +1 -1
- package/dist/contract.d.ts +3 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +11 -2
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +63 -11
- package/dist/install.js.map +1 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +6 -0
- package/dist/output.js.map +1 -1
- package/dist/resolve.d.ts +2 -0
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +28 -1
- package/dist/resolve.js.map +1 -1
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +5 -0
- package/dist/schemas.js.map +1 -1
- package/dist/skill.d.ts +1 -1
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +4 -1
- package/dist/skill.js.map +1 -1
- package/package.json +1 -1
- package/src/asset-implementation.ts +1 -0
- package/src/cli.ts +18 -1
- package/src/commands/upload.ts +79 -3
- package/src/contract.ts +2 -0
- package/src/index.ts +1 -0
- package/src/install.ts +89 -12
- package/src/output.ts +9 -0
- package/src/resolve.ts +36 -2
- package/src/schemas.ts +6 -0
- package/src/skill.ts +4 -1
- package/tests/install-layout.test.ts +60 -2
- package/tests/output.test.ts +18 -0
- package/tests/resolve-skills.test.ts +87 -0
- package/tests/upload-deps.test.ts +44 -0
package/src/schemas.ts
CHANGED
|
@@ -39,6 +39,11 @@ export const npmDependenciesSchema = z.record(z.string(), z.string()).default({}
|
|
|
39
39
|
|
|
40
40
|
export const assetDependenciesSchema = z.record(z.string(), z.string()).default({})
|
|
41
41
|
|
|
42
|
+
// Maps a skill label to a `skills add` source: a remote ref (owner/repo,
|
|
43
|
+
// git/HTTP URL, tree/<branch>/<subpath>) or a local path to a skill directory
|
|
44
|
+
// shipped inside the asset.
|
|
45
|
+
export const skillDependenciesSchema = z.record(z.string(), z.string()).default({})
|
|
46
|
+
|
|
42
47
|
export const updateProfileSchema = z.object({
|
|
43
48
|
name: z.string().min(1).max(100).optional(),
|
|
44
49
|
image: z.string().url().optional(),
|
|
@@ -66,6 +71,7 @@ export const uploadZipSchema = z.object({
|
|
|
66
71
|
description: z.string().max(1000).optional(),
|
|
67
72
|
npmDependencies: npmDependenciesSchema,
|
|
68
73
|
assetDependencies: assetDependenciesSchema,
|
|
74
|
+
skillDependencies: skillDependenciesSchema,
|
|
69
75
|
tags: z.array(z.string()).default([]),
|
|
70
76
|
})
|
|
71
77
|
|
package/src/skill.ts
CHANGED
|
@@ -23,7 +23,7 @@ market preview wooden-chair --out /tmp/wooden-chair.png
|
|
|
23
23
|
4. \`preview <name>\` saves the preview image; no \`--type\` is needed. Not every type has previews (e.g. \`humanoid-animation\`, \`template\`, \`sound-effect\`, \`background-music\`); the CLI reports when one is unavailable.
|
|
24
24
|
5. Use \`--unapproved\` only when the user asks for unapproved/private/admin assets. Do not install unapproved assets without explicit acceptance.
|
|
25
25
|
6. \`generate --type <type> "<prompt>"\` creates a new asset; it requires login and a type that supports generation. No asset type currently supports generation.
|
|
26
|
-
7. Upload only when publishing is requested: \`market upload <name> <zip> "<description>" --type <type>\`.
|
|
26
|
+
7. Upload only when publishing is requested: \`market upload <name> <zip> "<description>" --type <type>\`. Declare dependencies with repeatable flags: \`--npm name@range\`, \`--asset name@range\`, \`--skill label=source\`. A skill source is a \`skills add\` argument: a whole repo (\`owner/repo\` or a git URL), a single skill via the full URL form \`https://github.com/owner/repo/tree/<branch>/<subpath>\` (the \`tree/<branch>/<subpath>\` shorthand needs the full URL, not \`owner/repo\`), or a local path to a skill directory inside the zip. Example: \`market upload my-scene scene.zip "A scene" --type model --npm three@^0.178.0 --skill web-design=https://github.com/vercel-labs/agent-skills/tree/main/skills/web-design-guidelines\`.
|
|
27
27
|
8. Installed \`environment\` assets contain \`public/environment/<name>.hdr\` for Three.js IBL lighting and \`public/environment/<name>-background.webp\` for the visible equirectangular background. Use \`market preview\` to fetch the preview image separately.
|
|
28
28
|
|
|
29
29
|
## Output
|
|
@@ -39,8 +39,11 @@ Installed:
|
|
|
39
39
|
public/model
|
|
40
40
|
└─ wooden-chair.glb
|
|
41
41
|
- three@^0.178.0 (npm)
|
|
42
|
+
- web-design ← https://github.com/vercel-labs/agent-skills/tree/main/skills/web-design-guidelines (skill)
|
|
42
43
|
Saved preview for wooden-chair@1.0.0: /tmp/wooden-chair.png
|
|
43
44
|
\`\`\`
|
|
44
45
|
|
|
46
|
+
Assets may also declare \`skill\` dependencies, installed for you via the \`skills\` CLI (\`skills add\`) during \`install\`. Sources are either a GitHub/git ref or a local path to a skill directory shipped inside the asset. This requires \`npx\` to be available.
|
|
47
|
+
|
|
45
48
|
If search returns no results, try one broader noun phrase. If a command returns \`Error: Not logged in...\`, ask before running \`market login\`.
|
|
46
49
|
`
|
|
@@ -30,9 +30,11 @@ test('install writes zip files into the package root', async () => {
|
|
|
30
30
|
version: '1.0.0',
|
|
31
31
|
npmDependencies: {},
|
|
32
32
|
assetDependencies: {},
|
|
33
|
+
skillDependencies: {},
|
|
33
34
|
},
|
|
34
35
|
],
|
|
35
36
|
npmDependencies: {},
|
|
37
|
+
skillDependencies: {},
|
|
36
38
|
},
|
|
37
39
|
{ cwd },
|
|
38
40
|
)
|
|
@@ -46,6 +48,7 @@ test('install writes zip files into the package root', async () => {
|
|
|
46
48
|
},
|
|
47
49
|
],
|
|
48
50
|
npmDependencies: {},
|
|
51
|
+
skillDependencies: {},
|
|
49
52
|
})
|
|
50
53
|
assert.equal(
|
|
51
54
|
await fs.readFile(path.join(appRoot, 'public', 'humanoid-animation', 'idle-loop.glb'), 'utf-8'),
|
|
@@ -75,9 +78,11 @@ test('install rejects zip paths that escape through parent segments', async () =
|
|
|
75
78
|
version: '1.0.0',
|
|
76
79
|
npmDependencies: {},
|
|
77
80
|
assetDependencies: {},
|
|
81
|
+
skillDependencies: {},
|
|
78
82
|
},
|
|
79
83
|
],
|
|
80
84
|
npmDependencies: {},
|
|
85
|
+
skillDependencies: {},
|
|
81
86
|
},
|
|
82
87
|
{ cwd: tempDir },
|
|
83
88
|
),
|
|
@@ -85,7 +90,60 @@ test('install rejects zip paths that escape through parent segments', async () =
|
|
|
85
90
|
)
|
|
86
91
|
})
|
|
87
92
|
|
|
88
|
-
test('
|
|
93
|
+
test('install runs the skills runner per dependency, resolving local paths against the root', async () => {
|
|
94
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'market-skills-'))
|
|
95
|
+
const appRoot = path.join(tempDir, 'app')
|
|
96
|
+
const cwd = path.join(appRoot, 'src')
|
|
97
|
+
await fs.mkdir(cwd, { recursive: true })
|
|
98
|
+
await fs.writeFile(path.join(appRoot, 'package.json'), '{}\n')
|
|
99
|
+
|
|
100
|
+
const calls: Array<{ source: string; cwd: string }> = []
|
|
101
|
+
|
|
102
|
+
const result = await install(
|
|
103
|
+
clientWithZip({
|
|
104
|
+
'public/skills/local-skill/SKILL.md': textEncoder.encode('---\nname: local-skill\n---\n'),
|
|
105
|
+
}),
|
|
106
|
+
{
|
|
107
|
+
assets: [
|
|
108
|
+
{
|
|
109
|
+
name: 'with-skills',
|
|
110
|
+
version: '1.0.0',
|
|
111
|
+
npmDependencies: {},
|
|
112
|
+
assetDependencies: {},
|
|
113
|
+
skillDependencies: {},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
npmDependencies: {},
|
|
117
|
+
skillDependencies: {
|
|
118
|
+
'web-design-guidelines': 'vercel-labs/agent-skills/tree/main/skills/web-design-guidelines',
|
|
119
|
+
'local-skill': './public/skills/local-skill',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
cwd,
|
|
124
|
+
runSkillAdd: async (source, runnerCwd) => {
|
|
125
|
+
calls.push({ source, cwd: runnerCwd })
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// Every runner call uses the install root (the dir holding package.json).
|
|
131
|
+
assert.deepEqual(new Set(calls.map((c) => c.cwd)), new Set([appRoot]))
|
|
132
|
+
|
|
133
|
+
const bySource = new Map(calls.map((c) => [c.source, c]))
|
|
134
|
+
// Remote refs are passed through untouched.
|
|
135
|
+
assert.ok(bySource.has('vercel-labs/agent-skills/tree/main/skills/web-design-guidelines'))
|
|
136
|
+
// Local paths are resolved against the install root, not the cwd.
|
|
137
|
+
assert.ok(bySource.has(path.join(appRoot, 'public', 'skills', 'local-skill')))
|
|
138
|
+
assert.equal(calls.length, 2)
|
|
139
|
+
|
|
140
|
+
assert.deepEqual(result.skillDependencies, {
|
|
141
|
+
'web-design-guidelines': 'vercel-labs/agent-skills/tree/main/skills/web-design-guidelines',
|
|
142
|
+
'local-skill': './public/skills/local-skill',
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('findInstallRoot uses the nearest package.json, not the monorepo root', async () => {
|
|
89
147
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'market-root-'))
|
|
90
148
|
const repoRoot = path.join(tempDir, 'repo')
|
|
91
149
|
const appRoot = path.join(repoRoot, 'packages', 'app')
|
|
@@ -95,7 +153,7 @@ test('findInstallRoot ignores public directories and uses the highest package.js
|
|
|
95
153
|
await fs.writeFile(path.join(repoRoot, 'package.json'), '{}\n')
|
|
96
154
|
await fs.writeFile(path.join(appRoot, 'package.json'), '{}\n')
|
|
97
155
|
|
|
98
|
-
assert.equal(await findInstallRoot(cwd),
|
|
156
|
+
assert.equal(await findInstallRoot(cwd), appRoot)
|
|
99
157
|
})
|
|
100
158
|
|
|
101
159
|
test('findInstallRoot falls back to cwd when no package.json exists', async () => {
|
package/tests/output.test.ts
CHANGED
|
@@ -18,6 +18,7 @@ test('installResult prints asset and npm lists with a file tree', () => {
|
|
|
18
18
|
},
|
|
19
19
|
],
|
|
20
20
|
npmDependencies: { '@react-three/drei': 'latest' },
|
|
21
|
+
skillDependencies: {},
|
|
21
22
|
}),
|
|
22
23
|
`Installed:
|
|
23
24
|
- wooden-chair@1.2.0 (asset)
|
|
@@ -35,3 +36,20 @@ test('installResult prints asset and npm lists with a file tree', () => {
|
|
|
35
36
|
- @react-three/drei@latest (npm)`,
|
|
36
37
|
)
|
|
37
38
|
})
|
|
39
|
+
|
|
40
|
+
test('installResult lists installed skills with their source', () => {
|
|
41
|
+
assert.equal(
|
|
42
|
+
installResult({
|
|
43
|
+
assets: [{ name: 'wooden-chair', version: '1.2.0', files: [] }],
|
|
44
|
+
npmDependencies: {},
|
|
45
|
+
skillDependencies: {
|
|
46
|
+
'web-design-guidelines': 'vercel-labs/agent-skills/tree/main/skills/web-design-guidelines',
|
|
47
|
+
'local-skill': '/abs/public/skills/local-skill',
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
`Installed:
|
|
51
|
+
- wooden-chair@1.2.0 (asset)
|
|
52
|
+
- local-skill ← /abs/public/skills/local-skill (skill)
|
|
53
|
+
- web-design-guidelines ← vercel-labs/agent-skills/tree/main/skills/web-design-guidelines (skill)`,
|
|
54
|
+
)
|
|
55
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
import { resolve, ResolutionError } from '../src/resolve.js'
|
|
4
|
+
|
|
5
|
+
interface FakeAsset {
|
|
6
|
+
latestVersion: string
|
|
7
|
+
approved?: boolean
|
|
8
|
+
npmDependencies?: Record<string, string>
|
|
9
|
+
assetDependencies?: Record<string, string>
|
|
10
|
+
skillDependencies?: Record<string, string>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function clientWith(assets: Record<string, FakeAsset>) {
|
|
14
|
+
return {
|
|
15
|
+
exact: async ({ name }: { name: string }) => {
|
|
16
|
+
const asset = assets[name]
|
|
17
|
+
if (!asset) return null
|
|
18
|
+
return {
|
|
19
|
+
name,
|
|
20
|
+
latestVersion: asset.latestVersion,
|
|
21
|
+
approved: asset.approved ?? true,
|
|
22
|
+
npmDependencies: JSON.stringify(asset.npmDependencies ?? {}),
|
|
23
|
+
assetDependencies: JSON.stringify(asset.assetDependencies ?? {}),
|
|
24
|
+
skillDependencies: JSON.stringify(asset.skillDependencies ?? {}),
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
} as never
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('resolve collects skill dependencies across transitive assets', async () => {
|
|
31
|
+
const client = clientWith({
|
|
32
|
+
root: {
|
|
33
|
+
latestVersion: '1.0.0',
|
|
34
|
+
assetDependencies: { dep: '*' },
|
|
35
|
+
skillDependencies: { 'web-design-guidelines': 'vercel-labs/agent-skills' },
|
|
36
|
+
},
|
|
37
|
+
dep: {
|
|
38
|
+
latestVersion: '2.0.0',
|
|
39
|
+
skillDependencies: { 'local-skill': './public/skills/local-skill' },
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const result = await resolve(client, [{ name: 'root', range: '*' }])
|
|
44
|
+
|
|
45
|
+
assert.deepEqual(result.skillDependencies, {
|
|
46
|
+
'web-design-guidelines': 'vercel-labs/agent-skills',
|
|
47
|
+
'local-skill': './public/skills/local-skill',
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('resolve unions identical skill sources without complaint', async () => {
|
|
52
|
+
const client = clientWith({
|
|
53
|
+
root: {
|
|
54
|
+
latestVersion: '1.0.0',
|
|
55
|
+
assetDependencies: { dep: '*' },
|
|
56
|
+
skillDependencies: { shared: 'vercel-labs/agent-skills' },
|
|
57
|
+
},
|
|
58
|
+
dep: {
|
|
59
|
+
latestVersion: '1.0.0',
|
|
60
|
+
skillDependencies: { shared: 'vercel-labs/agent-skills' },
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const result = await resolve(client, [{ name: 'root', range: '*' }])
|
|
65
|
+
|
|
66
|
+
assert.deepEqual(result.skillDependencies, { shared: 'vercel-labs/agent-skills' })
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('resolve rejects the same skill label with conflicting sources', async () => {
|
|
70
|
+
const client = clientWith({
|
|
71
|
+
root: {
|
|
72
|
+
latestVersion: '1.0.0',
|
|
73
|
+
assetDependencies: { dep: '*' },
|
|
74
|
+
skillDependencies: { shared: 'vercel-labs/agent-skills' },
|
|
75
|
+
},
|
|
76
|
+
dep: {
|
|
77
|
+
latestVersion: '1.0.0',
|
|
78
|
+
skillDependencies: { shared: 'other-org/other-skills' },
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await assert.rejects(
|
|
83
|
+
resolve(client, [{ name: 'root', range: '*' }]),
|
|
84
|
+
(err: unknown) =>
|
|
85
|
+
err instanceof ResolutionError && /skill dependency conflict for "shared"/u.test(err.message),
|
|
86
|
+
)
|
|
87
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
import { parseSkillDeps, parseVersionedDeps } from '../src/commands/upload.js'
|
|
4
|
+
|
|
5
|
+
test('parseVersionedDeps parses name@range and defaults a missing range to *', () => {
|
|
6
|
+
assert.deepEqual(parseVersionedDeps(['three@^0.178.0', 'gsap'], 'npm'), {
|
|
7
|
+
three: '^0.178.0',
|
|
8
|
+
gsap: '*',
|
|
9
|
+
})
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('parseVersionedDeps keeps the scope marker on scoped npm packages', () => {
|
|
13
|
+
assert.deepEqual(parseVersionedDeps(['@react-three/drei@^9.0.0', '@types/node'], 'npm'), {
|
|
14
|
+
'@react-three/drei': '^9.0.0',
|
|
15
|
+
'@types/node': '*',
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('parseVersionedDeps rejects duplicates', () => {
|
|
20
|
+
assert.throws(() => parseVersionedDeps(['three@^1', 'three@^2'], 'npm'), /Duplicate npm dependency/u)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('parseSkillDeps splits label=source on the first = only', () => {
|
|
24
|
+
assert.deepEqual(
|
|
25
|
+
parseSkillDeps([
|
|
26
|
+
'web-design=vercel-labs/agent-skills/tree/main/skills/web-design-guidelines',
|
|
27
|
+
'local=./public/skills/local',
|
|
28
|
+
]),
|
|
29
|
+
{
|
|
30
|
+
'web-design': 'vercel-labs/agent-skills/tree/main/skills/web-design-guidelines',
|
|
31
|
+
local: './public/skills/local',
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('parseSkillDeps rejects specs without a usable label=source', () => {
|
|
37
|
+
assert.throws(() => parseSkillDeps(['no-separator']), /Use label=source/u)
|
|
38
|
+
assert.throws(() => parseSkillDeps(['=missing-label']), /Use label=source/u)
|
|
39
|
+
assert.throws(() => parseSkillDeps(['missing-source=']), /Use label=source/u)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('parseSkillDeps rejects duplicate labels', () => {
|
|
43
|
+
assert.throws(() => parseSkillDeps(['a=one', 'a=two']), /Duplicate skill dependency "a"/u)
|
|
44
|
+
})
|