@drawcall/market 0.9.3 → 0.10.0
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/README.md +11 -12
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -1
- package/dist/auth.js.map +1 -1
- package/dist/cli-client.d.ts +6 -0
- package/dist/cli-client.d.ts.map +1 -1
- package/dist/cli-client.js +7 -2
- package/dist/cli-client.js.map +1 -1
- package/dist/commands/agent.js +1 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/install.d.ts +2 -2
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +24 -6
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/reference-images.d.ts.map +1 -1
- package/dist/commands/reference-images.js +2 -1
- package/dist/commands/reference-images.js.map +1 -1
- package/dist/commands/upload.js +1 -1
- package/dist/commands/upload.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/install-entry.d.ts +1 -1
- package/dist/install-entry.d.ts.map +1 -1
- package/dist/install.d.ts +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/installation-id.d.ts +4 -0
- package/dist/installation-id.d.ts.map +1 -0
- package/dist/installation-id.js +39 -0
- package/dist/installation-id.js.map +1 -0
- package/dist/mcp/agent.d.ts.map +1 -1
- package/dist/mcp/agent.js +2 -13
- package/dist/mcp/agent.js.map +1 -1
- package/dist/mcp/asset.d.ts.map +1 -1
- package/dist/mcp/asset.js +73 -29
- package/dist/mcp/asset.js.map +1 -1
- package/dist/mcp/generation.d.ts.map +1 -1
- package/dist/mcp/generation.js +31 -3
- package/dist/mcp/generation.js.map +1 -1
- package/dist/output.js +1 -1
- package/dist/output.js.map +1 -1
- package/dist/pack.d.ts +1 -1
- package/dist/pack.js +1 -1
- package/dist/skill.generated.d.ts +2 -2
- package/dist/skill.generated.d.ts.map +1 -1
- package/dist/skill.generated.js +2 -2
- package/dist/skill.generated.js.map +1 -1
- package/dist/v1/client.d.ts +5 -0
- package/dist/v1/client.d.ts.map +1 -1
- package/dist/v1/client.js +10 -1
- package/dist/v1/client.js.map +1 -1
- package/dist/v1/contract.d.ts +22 -0
- package/dist/v1/contract.d.ts.map +1 -1
- package/dist/v1/contract.js +15 -0
- package/dist/v1/contract.js.map +1 -1
- package/package.json +1 -1
- package/skills/market/SKILL.md +10 -10
- package/src/auth.ts +2 -1
- package/src/cli-client.ts +15 -2
- package/src/commands/agent.ts +3 -1
- package/src/commands/install.ts +34 -6
- package/src/commands/reference-images.ts +2 -1
- package/src/commands/upload.ts +1 -1
- package/src/config.ts +3 -3
- package/src/install-entry.ts +1 -1
- package/src/install.ts +1 -1
- package/src/installation-id.ts +44 -0
- package/src/mcp/agent.ts +3 -19
- package/src/mcp/asset.ts +86 -31
- package/src/mcp/generation.ts +32 -3
- package/src/output.ts +1 -1
- package/src/pack.ts +1 -1
- package/src/skill.generated.ts +2 -2
- package/src/v1/client.ts +12 -1
- package/src/v1/contract.ts +17 -0
package/skills/market/SKILL.md
CHANGED
|
@@ -7,21 +7,21 @@ description: Find, inspect, generate, and use Drawcall Market assets from a codi
|
|
|
7
7
|
|
|
8
8
|
## CLI transport
|
|
9
9
|
|
|
10
|
-
Run commands as `npx
|
|
10
|
+
Run commands as `npx drawcall market <command>`; do not assume a global `market` binary or switch to MCP after a CLI failure. Use `npx drawcall market skill` when these instructions are not already available.
|
|
11
11
|
|
|
12
12
|
Discover the current vocabulary and choose an asset:
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
npx
|
|
16
|
-
npx
|
|
17
|
-
npx
|
|
18
|
-
npx
|
|
15
|
+
npx drawcall market types
|
|
16
|
+
npx drawcall market search "<query>" --type <type> --limit 3
|
|
17
|
+
npx drawcall market preview <name@version> --out /tmp/<name>.png
|
|
18
|
+
npx drawcall market urls <name@version>
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
`urls` prints one base URL, file subpaths, and a preview URL without changing the project. Install exact refs together when the project needs local files:
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
|
-
npx
|
|
24
|
+
npx drawcall market install <name@range...> --cwd "$PWD"
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
Install records `assetDependencies` in the nearest `package.json`. `list --cwd "$PWD"` reads that inventory without network access. Run `sync --cwd "$PWD"` after moving installed files so aliases continue to resolve. Use `--force` only with permission to overwrite changed files.
|
|
@@ -29,16 +29,16 @@ Install records `assetDependencies` in the nearest `package.json`. `list --cwd "
|
|
|
29
29
|
Generate and install one asset with:
|
|
30
30
|
|
|
31
31
|
```sh
|
|
32
|
-
npx
|
|
32
|
+
npx drawcall market generate --type <type> "<description>"
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
If it returns a continuation, run the printed `generate install <jobId>` command. The Market agent can source a coherent set of assets from a broader goal:
|
|
36
36
|
|
|
37
37
|
```sh
|
|
38
|
-
npx
|
|
38
|
+
npx drawcall market agent "<goal>"
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Publish only when asked. `pack <source> --out <zip>` creates a Market archive and `upload <name> <source> "<description>" --type <type>` publishes it. Use command help for dependency and visibility options. If authentication is required, ask before running `npx
|
|
41
|
+
Publish only when asked. `pack <source> --out <zip>` creates a Market archive and `upload <name> <source> "<description>" --type <type>` publishes it. Use command help for dependency and visibility options. If authentication is required, ask before running `npx drawcall auth login`.
|
|
42
42
|
|
|
43
43
|
## Choose assets
|
|
44
44
|
|
|
@@ -56,7 +56,7 @@ Reference images must be direct fetchable image URLs or image data, never webpag
|
|
|
56
56
|
|
|
57
57
|
A generated humanoid model is one expressive, animation-ready biped: one head, two arms, two legs, open empty hands, and no companions or scene. Describe identity, theme, silhouette, clothing, colors, and materials. Express topology outside the shared skeleton as costume or body-surface design so the result remains riggable.
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
Treat visibility as a publication decision. Choose public only when the user explicitly approves publication; choose private when the asset should remain owner-only. Private generation requires the account's `market:private` entitlement.
|
|
60
60
|
|
|
61
61
|
## Failures
|
|
62
62
|
|
package/src/auth.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { logout } from './commands/logout.js'
|
|
|
9
9
|
import { clearConfig, saveConfig } from './config.js'
|
|
10
10
|
import { loginResult } from './output.js'
|
|
11
11
|
import { createClient } from './v1/client.js'
|
|
12
|
+
import { getCliSource } from './installation-id.js'
|
|
12
13
|
|
|
13
14
|
const DEFAULT_BASE_URL = 'https://market.drawcall.ai'
|
|
14
15
|
|
|
@@ -24,7 +25,7 @@ export function createLoginCommand(): Command {
|
|
|
24
25
|
.action(async (options: { api?: string }) => {
|
|
25
26
|
const baseUrl = options.api ?? DEFAULT_BASE_URL
|
|
26
27
|
const token = await runDeviceLogin()
|
|
27
|
-
const client = createClient({ baseUrl, authToken: token })
|
|
28
|
+
const client = createClient({ baseUrl, authToken: token, source: await getCliSource() })
|
|
28
29
|
const profile = await client.user.getProfile()
|
|
29
30
|
if (!profile) throw new Error('The Market API did not accept the auth token.')
|
|
30
31
|
|
package/src/cli-client.ts
CHANGED
|
@@ -2,12 +2,13 @@ import type { MarketClient } from './client.js'
|
|
|
2
2
|
import { getAuthToken } from '@drawcall/auth'
|
|
3
3
|
import { createClient as createV1Client } from './v1/client.js'
|
|
4
4
|
import { loadConfig } from './config.js'
|
|
5
|
+
import { getCliSource } from './installation-id.js'
|
|
5
6
|
|
|
6
7
|
const DEFAULT_BASE_URL = 'https://market.drawcall.ai'
|
|
7
8
|
|
|
8
9
|
export class NotLoggedInError extends Error {
|
|
9
10
|
constructor() {
|
|
10
|
-
super('Not logged in. Run `
|
|
11
|
+
super('Not logged in. Run `npx drawcall auth login` first.')
|
|
11
12
|
this.name = 'NotLoggedInError'
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -25,6 +26,14 @@ export interface CliClient {
|
|
|
25
26
|
authToken: string | undefined
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
export interface AuthenticatedCliClient extends CliClient {
|
|
30
|
+
authToken: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getCliClient(
|
|
34
|
+
opts: CliClientOptions & { requireAuth: true },
|
|
35
|
+
): Promise<AuthenticatedCliClient>
|
|
36
|
+
export function getCliClient(opts?: CliClientOptions): Promise<CliClient>
|
|
28
37
|
export async function getCliClient(opts: CliClientOptions = {}): Promise<CliClient> {
|
|
29
38
|
const authToken = await getAuthToken()
|
|
30
39
|
const config = await loadConfig()
|
|
@@ -37,7 +46,11 @@ export async function getCliClient(opts: CliClientOptions = {}): Promise<CliClie
|
|
|
37
46
|
return {
|
|
38
47
|
// The v1 client has the same call shapes as the legacy `MarketClient`, so every CLI command
|
|
39
48
|
// and library consumer of `getCliClient` speaks /api/v1 without a signature change.
|
|
40
|
-
client: createV1Client({
|
|
49
|
+
client: createV1Client({
|
|
50
|
+
baseUrl,
|
|
51
|
+
authToken,
|
|
52
|
+
source: await getCliSource(),
|
|
53
|
+
}),
|
|
41
54
|
baseUrl,
|
|
42
55
|
authToken,
|
|
43
56
|
}
|
package/src/commands/agent.ts
CHANGED
|
@@ -43,7 +43,9 @@ function printResult(result: AgentResult, asJson: boolean): void {
|
|
|
43
43
|
const note = asset.notes ? ` — ${asset.notes}` : ''
|
|
44
44
|
console.log(`- ${assetVersionRef(asset.name, asset.version)}${note}`)
|
|
45
45
|
}
|
|
46
|
-
console.log(
|
|
46
|
+
console.log(
|
|
47
|
+
`Install: npx drawcall market install ${assets.map((asset) => asset.name).join(' ')}`,
|
|
48
|
+
)
|
|
47
49
|
}
|
|
48
50
|
if (notes) {
|
|
49
51
|
console.log(`Notes: ${notes}`)
|
package/src/commands/install.ts
CHANGED
|
@@ -2,7 +2,12 @@ import * as fs from 'fs/promises'
|
|
|
2
2
|
import * as path from 'path'
|
|
3
3
|
import ora from 'ora'
|
|
4
4
|
import { resolve } from '../resolve.js'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
findInstallRoot,
|
|
7
|
+
install as runInstall,
|
|
8
|
+
type InstallResult,
|
|
9
|
+
type InstallRootRequest,
|
|
10
|
+
} from '../install.js'
|
|
6
11
|
import { packageJsonAssetDependencies } from '../package-json.js'
|
|
7
12
|
import { assetDependencyRange, assetNameSchema, type AssetDependencies } from '../schemas.js'
|
|
8
13
|
import { getCliClient } from '../cli-client.js'
|
|
@@ -57,18 +62,41 @@ export async function installCommand(args: string[], opts: InstallCommandOptions
|
|
|
57
62
|
|
|
58
63
|
spinner.stop()
|
|
59
64
|
console.log(installResult(result, metadata))
|
|
65
|
+
await reportCompletedInstall(client, result)
|
|
60
66
|
} catch (err) {
|
|
61
67
|
spinner.stop()
|
|
62
68
|
throw err
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
async function reportCompletedInstall(client: MarketClient, result: InstallResult): Promise<void> {
|
|
73
|
+
try {
|
|
74
|
+
await client.installation.complete({
|
|
75
|
+
assetCount: result.assets.length,
|
|
76
|
+
assetTypes: [...new Set(result.assets.map((asset) => asset.type))],
|
|
77
|
+
fileCount: result.assets.reduce((count, asset) => count + asset.files.length, 0),
|
|
78
|
+
npmDependencyCount: Object.keys(result.npmDependencies).length,
|
|
79
|
+
redirectedFileCount: result.assets.reduce(
|
|
80
|
+
(count, asset) => count + asset.redirectedFiles.length,
|
|
81
|
+
0,
|
|
82
|
+
),
|
|
83
|
+
skillDependencyCount: Object.keys(result.skillDependencies).length,
|
|
84
|
+
warningCount: result.warnings.length,
|
|
85
|
+
})
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
88
|
+
console.warn(
|
|
89
|
+
`Assets installed, but the Market could not record the completed install: ${message}`,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
66
94
|
/**
|
|
67
95
|
* Resolve a single `name` or `name@range` argument to an exact asset request.
|
|
68
96
|
* Asset names are globally unique, so the name fully determines the asset — no
|
|
69
97
|
* `--type` is needed. install never fuzzy-searches or generates: it points the
|
|
70
|
-
* caller at `market search` / `market generate`
|
|
71
|
-
* predictable for agents.
|
|
98
|
+
* caller at `npx drawcall market search` / `npx drawcall market generate`
|
|
99
|
+
* instead, which keeps behavior predictable for agents.
|
|
72
100
|
*/
|
|
73
101
|
export async function resolveArg(
|
|
74
102
|
client: MarketClient,
|
|
@@ -79,7 +107,7 @@ export async function resolveArg(
|
|
|
79
107
|
const parsed = parseNameAndRange(arg)
|
|
80
108
|
if (!parsed) {
|
|
81
109
|
throw new Error(
|
|
82
|
-
`Invalid asset name "${arg}". Use \`market search --type <type> <query>\` to find assets.`,
|
|
110
|
+
`Invalid asset name "${arg}". Use \`npx drawcall market search --type <type> <query>\` to find assets.`,
|
|
83
111
|
)
|
|
84
112
|
}
|
|
85
113
|
|
|
@@ -108,8 +136,8 @@ export async function resolveArg(
|
|
|
108
136
|
}
|
|
109
137
|
|
|
110
138
|
throw new Error(
|
|
111
|
-
`Asset "${parsed.name}" not found. Use \`market search\` to find assets or ` +
|
|
112
|
-
`\`market generate --type <type>\` to create one.`,
|
|
139
|
+
`Asset "${parsed.name}" not found. Use \`npx drawcall market search\` to find assets or ` +
|
|
140
|
+
`\`npx drawcall market generate --type <type>\` to create one.`,
|
|
113
141
|
)
|
|
114
142
|
}
|
|
115
143
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
1
2
|
import { readFile } from 'node:fs/promises'
|
|
2
3
|
import { extname } from 'node:path'
|
|
3
4
|
|
|
@@ -12,7 +13,7 @@ export function loadReferenceImages(refs: string[]): Promise<string[]> {
|
|
|
12
13
|
async function loadReferenceImage(ref: string): Promise<string> {
|
|
13
14
|
if (ref.startsWith('http://') || ref.startsWith('https://')) return ref
|
|
14
15
|
const data = await readFile(ref)
|
|
15
|
-
return `data:${mimeFor(ref)};base64,${data.toString('base64')}`
|
|
16
|
+
return `data:${mimeFor(ref)};base64,${Buffer.from(data).toString('base64')}`
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function mimeFor(path: string): string {
|
package/src/commands/upload.ts
CHANGED
|
@@ -74,7 +74,7 @@ export async function uploadCommand(
|
|
|
74
74
|
for (const line of packWarnings(packed)) console.warn(line)
|
|
75
75
|
const profile = await client.user.getProfile()
|
|
76
76
|
if (!profile) {
|
|
77
|
-
throw new Error('Not logged in. Run `
|
|
77
|
+
throw new Error('Not logged in. Run `npx drawcall auth login` first.')
|
|
78
78
|
}
|
|
79
79
|
const [existing] = await lookupAssets(client.asset, [{ name: parsedName }], {
|
|
80
80
|
includeUnapproved: true,
|
package/src/config.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface Config {
|
|
|
13
13
|
baseUrl?: string
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
function
|
|
16
|
+
export function getConfigDir(): string {
|
|
17
17
|
if (process.platform === 'win32' && process.env.APPDATA) {
|
|
18
18
|
return path.join(process.env.APPDATA, 'drawcall-market')
|
|
19
19
|
}
|
|
@@ -22,7 +22,7 @@ function configDir(): string {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function configPath(): string {
|
|
25
|
-
return path.join(
|
|
25
|
+
return path.join(getConfigDir(), 'config.json')
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export async function loadConfig(): Promise<Config | null> {
|
|
@@ -35,7 +35,7 @@ export async function loadConfig(): Promise<Config | null> {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export async function saveConfig(config: Config): Promise<void> {
|
|
38
|
-
const dir =
|
|
38
|
+
const dir = getConfigDir()
|
|
39
39
|
await fs.mkdir(dir, { recursive: true })
|
|
40
40
|
// `mode` on writeFile/mkdir only applies when the path is created, so chmod
|
|
41
41
|
// explicitly to protect a pre-existing (possibly world-readable) token file
|
package/src/install-entry.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { MarketClient, MarketClientOptions } from './client.js'
|
|
2
2
|
export { getCliClient, NotLoggedInError } from './cli-client.js'
|
|
3
|
-
export type { CliClient, CliClientOptions } from './cli-client.js'
|
|
3
|
+
export type { AuthenticatedCliClient, CliClient, CliClientOptions } from './cli-client.js'
|
|
4
4
|
|
|
5
5
|
export { resolve, ResolutionError } from './resolve.js'
|
|
6
6
|
export type { ResolvedAsset, ResolveResult } from './resolve.js'
|
package/src/install.ts
CHANGED
|
@@ -40,7 +40,7 @@ export interface InstallOptions {
|
|
|
40
40
|
rootRequests?: InstallRootRequest[]
|
|
41
41
|
/** Asset-type install policy from the Market API. */
|
|
42
42
|
installMetadata?: Record<string, AssetInstallMetadata>
|
|
43
|
-
/** Capability key of a shared private asset (`market install <asset> --key <key>`). */
|
|
43
|
+
/** Capability key of a shared private asset (`npx drawcall market install <asset> --key <key>`). */
|
|
44
44
|
key?: string
|
|
45
45
|
/** Log progress */
|
|
46
46
|
onProgress?: (message: string) => void
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as fs from 'fs/promises'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import { getConfigDir } from './config.js'
|
|
5
|
+
import type { MarketV1ClientOptions } from './v1/client.js'
|
|
6
|
+
|
|
7
|
+
const installationIdSchema = z.string().uuid()
|
|
8
|
+
|
|
9
|
+
function installationIdPath(): string {
|
|
10
|
+
return path.join(getConfigDir(), 'installation-id')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function getInstallationId(): Promise<string> {
|
|
14
|
+
try {
|
|
15
|
+
return installationIdSchema.parse((await fs.readFile(installationIdPath(), 'utf-8')).trim())
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (!isMissingFile(error)) throw error
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const dir = getConfigDir()
|
|
21
|
+
const installationId = crypto.randomUUID()
|
|
22
|
+
await fs.mkdir(dir, { recursive: true })
|
|
23
|
+
await fs.chmod(dir, 0o700)
|
|
24
|
+
await fs.writeFile(installationIdPath(), `${installationId}\n`, {
|
|
25
|
+
encoding: 'utf-8',
|
|
26
|
+
flag: 'wx',
|
|
27
|
+
mode: 0o600,
|
|
28
|
+
})
|
|
29
|
+
return installationId
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function getCliSource(): Promise<NonNullable<MarketV1ClientOptions['source']>> {
|
|
33
|
+
const analyticsDisabled =
|
|
34
|
+
process.env.DO_NOT_TRACK === '1' || process.env.MARKET_TELEMETRY_DISABLED === '1'
|
|
35
|
+
return {
|
|
36
|
+
surface: 'cli',
|
|
37
|
+
distinctId: analyticsDisabled ? undefined : await getInstallationId(),
|
|
38
|
+
analyticsDisabled,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isMissingFile(error: unknown): boolean {
|
|
43
|
+
return error instanceof Error && 'code' in error && error.code === 'ENOENT'
|
|
44
|
+
}
|
package/src/mcp/agent.ts
CHANGED
|
@@ -1,31 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { agentStatusSchema } from '../schemas.js'
|
|
2
2
|
import type { MarketV1Client } from '../v1/client.js'
|
|
3
3
|
import { result } from './result.js'
|
|
4
4
|
import type { MarketMcpServer } from './types.js'
|
|
5
5
|
|
|
6
6
|
export function registerAgentTools(server: MarketMcpServer, client: MarketV1Client): void {
|
|
7
|
-
server.registerTool(
|
|
8
|
-
'start_market_agent_run',
|
|
9
|
-
{
|
|
10
|
-
title: 'Start Market agent run',
|
|
11
|
-
description:
|
|
12
|
-
'Start a Market agent run that plans, finds, judges, and generates a coherent set of assets for a goal.',
|
|
13
|
-
inputSchema: agentStartSchema,
|
|
14
|
-
annotations: {
|
|
15
|
-
readOnlyHint: false,
|
|
16
|
-
destructiveHint: false,
|
|
17
|
-
openWorldHint: true,
|
|
18
|
-
idempotentHint: false,
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
async (input) => result({ run: await client.asset.agent.start(input) }),
|
|
22
|
-
)
|
|
23
|
-
|
|
24
7
|
server.registerTool(
|
|
25
8
|
'get_market_agent_run',
|
|
26
9
|
{
|
|
27
10
|
title: 'Get Market agent run',
|
|
28
|
-
description:
|
|
11
|
+
description:
|
|
12
|
+
'Get the current state and result of an existing Market agent run. MCP cannot start agent runs until the Market API supports explicit output visibility.',
|
|
29
13
|
inputSchema: agentStatusSchema,
|
|
30
14
|
annotations: {
|
|
31
15
|
readOnlyHint: true,
|
package/src/mcp/asset.ts
CHANGED
|
@@ -2,15 +2,23 @@ import { z } from 'zod'
|
|
|
2
2
|
import { resolveAsset } from '../refs.js'
|
|
3
3
|
import { assetTypeSchema } from '../schemas.js'
|
|
4
4
|
import type { MarketV1Client } from '../v1/client.js'
|
|
5
|
+
import type { AssetRefEntry } from '../v1/index.js'
|
|
5
6
|
import { base64, result } from './result.js'
|
|
6
7
|
import type { MarketMcpServer } from './types.js'
|
|
7
8
|
|
|
9
|
+
const includeUnapprovedSchema = z
|
|
10
|
+
.boolean()
|
|
11
|
+
.default(false)
|
|
12
|
+
.describe(
|
|
13
|
+
'Include unapproved assets only when the user explicitly asks to inspect unmoderated content and accepts that it has not passed Market review.',
|
|
14
|
+
)
|
|
15
|
+
|
|
8
16
|
const searchMarketSchema = z
|
|
9
17
|
.object({
|
|
10
18
|
query: z.string().min(1).max(200),
|
|
11
19
|
type: assetTypeSchema,
|
|
12
20
|
limit: z.coerce.number().int().min(1).max(5).default(3),
|
|
13
|
-
includeUnapproved:
|
|
21
|
+
includeUnapproved: includeUnapprovedSchema,
|
|
14
22
|
})
|
|
15
23
|
.strict()
|
|
16
24
|
|
|
@@ -18,8 +26,7 @@ const assetRefSchema = z.string().min(1).describe('Asset name or exact name@vers
|
|
|
18
26
|
const assetInput = z
|
|
19
27
|
.object({
|
|
20
28
|
asset: assetRefSchema,
|
|
21
|
-
includeUnapproved:
|
|
22
|
-
key: z.string().min(1).max(128).optional(),
|
|
29
|
+
includeUnapproved: includeUnapprovedSchema,
|
|
23
30
|
})
|
|
24
31
|
.strict()
|
|
25
32
|
|
|
@@ -70,16 +77,20 @@ export function registerAssetTools(
|
|
|
70
77
|
throw new Error('Market search unexpectedly returned an exact asset result')
|
|
71
78
|
}
|
|
72
79
|
return result({
|
|
73
|
-
assets: response.items.map((asset) =>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
assets: response.items.map((asset) => {
|
|
81
|
+
const previewUrl = safePublicUrl(asset.previewUrl, asset)
|
|
82
|
+
return {
|
|
83
|
+
asset: `${asset.name}@${asset.latestVersion}`,
|
|
84
|
+
name: asset.name,
|
|
85
|
+
version: asset.latestVersion,
|
|
86
|
+
type: asset.type,
|
|
87
|
+
description: asset.description,
|
|
88
|
+
approved: asset.approved,
|
|
89
|
+
access: asset.access,
|
|
90
|
+
hasPreview: Boolean(asset.previewUrl),
|
|
91
|
+
...(previewUrl ? { previewUrl } : {}),
|
|
92
|
+
}
|
|
93
|
+
}),
|
|
83
94
|
total: response.total,
|
|
84
95
|
})
|
|
85
96
|
},
|
|
@@ -90,7 +101,7 @@ export function registerAssetTools(
|
|
|
90
101
|
{
|
|
91
102
|
title: 'Get Market asset',
|
|
92
103
|
description:
|
|
93
|
-
'Get one exact Drawcall Market asset manifest with its version history, dependencies,
|
|
104
|
+
'Get one exact Drawcall Market asset manifest with its version history, dependencies, and file metadata. Capability-bearing URLs and owner identifiers are never returned.',
|
|
94
105
|
inputSchema: assetInput,
|
|
95
106
|
annotations: {
|
|
96
107
|
readOnlyHint: true,
|
|
@@ -99,12 +110,9 @@ export function registerAssetTools(
|
|
|
99
110
|
idempotentHint: true,
|
|
100
111
|
},
|
|
101
112
|
},
|
|
102
|
-
async ({ asset, includeUnapproved
|
|
113
|
+
async ({ asset, includeUnapproved }) =>
|
|
103
114
|
result({
|
|
104
|
-
asset: await resolveAsset(client.asset, asset, {
|
|
105
|
-
includeUnapproved,
|
|
106
|
-
...(key ? { key } : {}),
|
|
107
|
-
}),
|
|
115
|
+
asset: mcpAsset(await resolveAsset(client.asset, asset, { includeUnapproved })),
|
|
108
116
|
}),
|
|
109
117
|
)
|
|
110
118
|
|
|
@@ -112,7 +120,8 @@ export function registerAssetTools(
|
|
|
112
120
|
'get_market_asset_preview',
|
|
113
121
|
{
|
|
114
122
|
title: 'Get Market asset preview',
|
|
115
|
-
description:
|
|
123
|
+
description:
|
|
124
|
+
'Return the preview image for one exact Drawcall Market asset. The server fetches restricted previews without returning their capability-bearing URL.',
|
|
116
125
|
inputSchema: assetInput,
|
|
117
126
|
annotations: {
|
|
118
127
|
readOnlyHint: true,
|
|
@@ -121,22 +130,17 @@ export function registerAssetTools(
|
|
|
121
130
|
idempotentHint: true,
|
|
122
131
|
},
|
|
123
132
|
},
|
|
124
|
-
async ({ asset: ref, includeUnapproved
|
|
125
|
-
const asset = await resolveAsset(client.asset, ref, {
|
|
126
|
-
includeUnapproved,
|
|
127
|
-
...(key ? { key } : {}),
|
|
128
|
-
})
|
|
133
|
+
async ({ asset: ref, includeUnapproved }) => {
|
|
134
|
+
const asset = await resolveAsset(client.asset, ref, { includeUnapproved })
|
|
129
135
|
if (!asset.previewUrl) throw new Error(`Asset "${ref}" has no preview image`)
|
|
130
136
|
|
|
131
|
-
const response = await fetcher
|
|
137
|
+
const response = await fetchPreview(fetcher, asset.previewUrl, ref)
|
|
132
138
|
if (!response.ok) {
|
|
133
|
-
throw new Error(
|
|
134
|
-
`Could not read preview ${asset.previewUrl}: ${response.status} ${response.statusText}`,
|
|
135
|
-
)
|
|
139
|
+
throw new Error(`Could not read preview for "${ref}": ${response.status}`)
|
|
136
140
|
}
|
|
137
141
|
const mimeType = response.headers.get('content-type')?.split(';', 1)[0]
|
|
138
142
|
if (!mimeType?.startsWith('image/')) {
|
|
139
|
-
throw new Error(`Preview ${
|
|
143
|
+
throw new Error(`Preview for "${ref}" returned ${mimeType ?? 'no media type'}`)
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
return {
|
|
@@ -149,10 +153,61 @@ export function registerAssetTools(
|
|
|
149
153
|
],
|
|
150
154
|
structuredContent: {
|
|
151
155
|
asset: `${asset.name}@${asset.version}`,
|
|
152
|
-
url: asset.previewUrl,
|
|
153
156
|
mediaType: mimeType,
|
|
154
157
|
},
|
|
155
158
|
}
|
|
156
159
|
},
|
|
157
160
|
)
|
|
158
161
|
}
|
|
162
|
+
|
|
163
|
+
function mcpAsset(asset: AssetRefEntry): Record<string, unknown> {
|
|
164
|
+
const publicAsset = asset.access === 'public' && asset.approved
|
|
165
|
+
const previewUrl = safePublicUrl(asset.previewUrl, asset)
|
|
166
|
+
const zipUrl = safePublicUrl(asset.zipUrl, asset)
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
name: asset.name,
|
|
170
|
+
type: asset.type,
|
|
171
|
+
version: asset.version,
|
|
172
|
+
description: asset.description,
|
|
173
|
+
approved: asset.approved,
|
|
174
|
+
access: asset.access,
|
|
175
|
+
npmDependencies: asset.npmDependencies,
|
|
176
|
+
assetDependencies: asset.assetDependencies,
|
|
177
|
+
skillDependencies: asset.skillDependencies,
|
|
178
|
+
files: asset.files.map(({ url, ...file }) => {
|
|
179
|
+
const publicUrl = safePublicUrl(url, asset)
|
|
180
|
+
return publicUrl ? { ...file, url: publicUrl } : file
|
|
181
|
+
}),
|
|
182
|
+
hasPreview: Boolean(asset.previewUrl),
|
|
183
|
+
versions: asset.versions,
|
|
184
|
+
...(previewUrl ? { previewUrl } : {}),
|
|
185
|
+
...(zipUrl ? { zipUrl } : {}),
|
|
186
|
+
...(!publicAsset ? { restrictedUrlsOmitted: true } : {}),
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function fetchPreview(
|
|
191
|
+
fetcher: typeof globalThis.fetch,
|
|
192
|
+
url: string,
|
|
193
|
+
ref: string,
|
|
194
|
+
): Promise<Response> {
|
|
195
|
+
try {
|
|
196
|
+
return await fetcher(url)
|
|
197
|
+
} catch {
|
|
198
|
+
throw new Error(`Could not read preview for "${ref}"`)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function safePublicUrl(
|
|
203
|
+
url: string | null,
|
|
204
|
+
asset: Pick<AssetRefEntry, 'access' | 'approved'>,
|
|
205
|
+
): string | undefined {
|
|
206
|
+
if (!url || asset.access !== 'public' || !asset.approved) return undefined
|
|
207
|
+
try {
|
|
208
|
+
const parsed = new URL(url)
|
|
209
|
+
return parsed.searchParams.has('key') ? undefined : parsed.href
|
|
210
|
+
} catch {
|
|
211
|
+
return undefined
|
|
212
|
+
}
|
|
213
|
+
}
|
package/src/mcp/generation.ts
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
1
2
|
import { assetTypeSchema, generateAssetSchema, generateStatusInputSchema } from '../schemas.js'
|
|
2
3
|
import type { MarketV1Client } from '../v1/client.js'
|
|
3
4
|
import { result } from './result.js'
|
|
4
5
|
import type { MarketMcpServer } from './types.js'
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const generationBaseSchema = generateAssetSchema
|
|
8
|
+
.omit({ access: true, type: true })
|
|
9
|
+
.extend({ type: assetTypeSchema })
|
|
10
|
+
|
|
11
|
+
const generateMarketAssetSchema = z.discriminatedUnion('access', [
|
|
12
|
+
generationBaseSchema
|
|
13
|
+
.extend({
|
|
14
|
+
access: z
|
|
15
|
+
.literal('public')
|
|
16
|
+
.describe('Publish the generated asset to the public Market after its approval flow.'),
|
|
17
|
+
confirmPublicPublication: z
|
|
18
|
+
.literal(true)
|
|
19
|
+
.describe('Must be true only after the user explicitly approves public publication.'),
|
|
20
|
+
})
|
|
21
|
+
.strict(),
|
|
22
|
+
generationBaseSchema
|
|
23
|
+
.extend({
|
|
24
|
+
access: z
|
|
25
|
+
.literal('private')
|
|
26
|
+
.describe('Keep the generated asset private to the authenticated owner.'),
|
|
27
|
+
})
|
|
28
|
+
.strict(),
|
|
29
|
+
])
|
|
7
30
|
|
|
8
31
|
export function registerGenerationTools(server: MarketMcpServer, client: MarketV1Client): void {
|
|
9
32
|
server.registerTool(
|
|
@@ -11,7 +34,7 @@ export function registerGenerationTools(server: MarketMcpServer, client: MarketV
|
|
|
11
34
|
{
|
|
12
35
|
title: 'Generate Market asset',
|
|
13
36
|
description:
|
|
14
|
-
'
|
|
37
|
+
'Create one external Drawcall Market asset. Visibility is required; public publication is allowed only after explicit user consent. A slow provider returns a jobId to pass to get_market_asset_generation.',
|
|
15
38
|
inputSchema: generateMarketAssetSchema,
|
|
16
39
|
annotations: {
|
|
17
40
|
readOnlyHint: false,
|
|
@@ -20,7 +43,13 @@ export function registerGenerationTools(server: MarketMcpServer, client: MarketV
|
|
|
20
43
|
idempotentHint: false,
|
|
21
44
|
},
|
|
22
45
|
},
|
|
23
|
-
async (input) =>
|
|
46
|
+
async (input) => {
|
|
47
|
+
if (input.access === 'public') {
|
|
48
|
+
const { confirmPublicPublication: _confirmed, ...generation } = input
|
|
49
|
+
return result({ generation: await client.asset.generate(generation) })
|
|
50
|
+
}
|
|
51
|
+
return result({ generation: await client.asset.generate(input) })
|
|
52
|
+
},
|
|
24
53
|
)
|
|
25
54
|
|
|
26
55
|
server.registerTool(
|
package/src/output.ts
CHANGED
|
@@ -189,7 +189,7 @@ export function generationRunningResult(jobId: string): string {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
function generationInstallCommand(jobId: string): string {
|
|
192
|
-
return `npx
|
|
192
|
+
return `npx drawcall market generate install ${jobId} --cwd "$PWD"`
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
export function uploadResult(name: string, version: string): string {
|
package/src/pack.ts
CHANGED
|
@@ -616,7 +616,7 @@ export interface AssetDependencySync {
|
|
|
616
616
|
|
|
617
617
|
/**
|
|
618
618
|
* Update the aliases in package.json `assetDependencies` to match where each dependency's files
|
|
619
|
-
* currently live — the standalone form of what pack does before omitting (`market sync`). Renamed
|
|
619
|
+
* currently live — the standalone form of what pack does before omitting (`npx drawcall market sync`). Renamed
|
|
620
620
|
* files gain an alias, files back at their canonical path lose theirs, and dependencies whose
|
|
621
621
|
* manifest cannot be fetched are reported as skipped rather than blindly rewritten.
|
|
622
622
|
*/
|