@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.
Files changed (49) hide show
  1. package/dist/asset-implementation.d.ts +1 -0
  2. package/dist/asset-implementation.d.ts.map +1 -1
  3. package/dist/cli.js +8 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/upload.d.ts +18 -0
  6. package/dist/commands/upload.d.ts.map +1 -1
  7. package/dist/commands/upload.js +67 -3
  8. package/dist/commands/upload.js.map +1 -1
  9. package/dist/contract.d.ts +3 -0
  10. package/dist/contract.d.ts.map +1 -1
  11. package/dist/contract.js.map +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/install.d.ts +11 -2
  17. package/dist/install.d.ts.map +1 -1
  18. package/dist/install.js +63 -11
  19. package/dist/install.js.map +1 -1
  20. package/dist/output.d.ts.map +1 -1
  21. package/dist/output.js +6 -0
  22. package/dist/output.js.map +1 -1
  23. package/dist/resolve.d.ts +2 -0
  24. package/dist/resolve.d.ts.map +1 -1
  25. package/dist/resolve.js +28 -1
  26. package/dist/resolve.js.map +1 -1
  27. package/dist/schemas.d.ts +2 -0
  28. package/dist/schemas.d.ts.map +1 -1
  29. package/dist/schemas.js +5 -0
  30. package/dist/schemas.js.map +1 -1
  31. package/dist/skill.d.ts +1 -1
  32. package/dist/skill.d.ts.map +1 -1
  33. package/dist/skill.js +4 -1
  34. package/dist/skill.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/asset-implementation.ts +1 -0
  37. package/src/cli.ts +18 -1
  38. package/src/commands/upload.ts +79 -3
  39. package/src/contract.ts +2 -0
  40. package/src/index.ts +1 -0
  41. package/src/install.ts +89 -12
  42. package/src/output.ts +9 -0
  43. package/src/resolve.ts +36 -2
  44. package/src/schemas.ts +6 -0
  45. package/src/skill.ts +4 -1
  46. package/tests/install-layout.test.ts +60 -2
  47. package/tests/output.test.ts +18 -0
  48. package/tests/resolve-skills.test.ts +87 -0
  49. 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('findInstallRoot ignores public directories and uses the highest package.json', async () => {
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), repoRoot)
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 () => {
@@ -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
+ })