@drawcall/market 0.1.4 → 0.1.6
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 +88 -0
- package/dist/asset-implementation.d.ts.map +1 -0
- package/dist/asset-implementation.js +2 -0
- package/dist/asset-implementation.js.map +1 -0
- package/dist/cli-client.d.ts +2 -2
- package/dist/cli-client.d.ts.map +1 -1
- package/dist/cli-client.js +4 -4
- package/dist/cli-client.js.map +1 -1
- package/dist/cli.js +104 -22
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +1 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -12
- package/dist/client.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +9 -4
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +33 -27
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/logout.js +3 -3
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/preview.d.ts +9 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +50 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/search.d.ts +4 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +21 -11
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/upload.d.ts +9 -0
- package/dist/commands/upload.d.ts.map +1 -0
- package/dist/commands/upload.js +220 -0
- package/dist/commands/upload.js.map +1 -0
- package/dist/config.d.ts +4 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -4
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -10
- package/dist/constants.js.map +1 -1
- package/dist/contract.d.ts +37 -134
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +10 -48
- package/dist/contract.js.map +1 -1
- package/dist/generate.d.ts +0 -2
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +5 -22
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +5 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +14 -11
- package/dist/install.js.map +1 -1
- package/dist/output.d.ts +26 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +52 -0
- package/dist/output.js.map +1 -0
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +15 -26
- package/dist/resolve.js.map +1 -1
- package/dist/schemas.d.ts +31 -39
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +23 -25
- package/dist/schemas.js.map +1 -1
- package/package.json +13 -4
- package/src/asset-implementation.ts +114 -0
- package/src/cli-client.ts +7 -6
- package/src/cli.ts +144 -29
- package/src/client.ts +5 -15
- package/src/commands/generate.ts +9 -6
- package/src/commands/install.ts +41 -30
- package/src/commands/logout.ts +3 -3
- package/src/commands/preview.ts +69 -0
- package/src/commands/search.ts +28 -12
- package/src/commands/upload.ts +262 -0
- package/src/config.ts +11 -5
- package/src/constants.ts +2 -10
- package/src/contract.ts +22 -120
- package/src/generate.ts +5 -29
- package/src/index.ts +23 -14
- package/src/install.ts +24 -14
- package/src/output.ts +76 -0
- package/src/resolve.ts +22 -38
- package/src/schemas.ts +26 -27
- package/tsconfig.json +2 -1
- package/dist/commands/login.d.ts +0 -10
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js +0 -92
- package/dist/commands/login.js.map +0 -1
- package/dist/internal-contract.d.ts +0 -19
- package/dist/internal-contract.d.ts.map +0 -1
- package/dist/internal-contract.js +0 -19
- package/dist/internal-contract.js.map +0 -1
- package/src/commands/login.ts +0 -113
- package/src/internal-contract.ts +0 -26
package/src/cli.ts
CHANGED
|
@@ -2,56 +2,66 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command, Option } from 'commander'
|
|
4
4
|
import chalk from 'chalk'
|
|
5
|
+
import open from 'open'
|
|
6
|
+
import * as oauthClient from 'openid-client'
|
|
5
7
|
import { ASSET_TYPES, type AssetType } from './schemas.js'
|
|
6
8
|
import { installCommand } from './commands/install.js'
|
|
7
9
|
import { searchCommand } from './commands/search.js'
|
|
8
10
|
import { generateCommand } from './commands/generate.js'
|
|
9
|
-
import {
|
|
11
|
+
import { previewCommand } from './commands/preview.js'
|
|
12
|
+
import { uploadCommand } from './commands/upload.js'
|
|
10
13
|
import { logout } from './commands/logout.js'
|
|
11
|
-
import {
|
|
14
|
+
import { createMarketClient } from './client.js'
|
|
15
|
+
import { saveConfig, getConfigPath } from './config.js'
|
|
16
|
+
import { errorResult, loginResult } from './output.js'
|
|
12
17
|
|
|
13
18
|
const program = new Command()
|
|
14
19
|
|
|
15
20
|
const DEFAULT_BASE_URL = 'https://api.market.drawcall.ai'
|
|
21
|
+
const AUTH_ISSUER_URL = 'https://auth.drawcall.ai/api/auth'
|
|
22
|
+
const DEVICE_CLIENT_ID = 'market-cli'
|
|
16
23
|
|
|
17
|
-
const typeOption = new Option('--type <type>', '
|
|
18
|
-
|
|
19
|
-
const apiOption = new Option('--api <url>', 'API base URL').default(
|
|
24
|
+
const typeOption = new Option('--type <type>', 'Asset type').choices([...ASSET_TYPES])
|
|
25
|
+
const apiOption = new Option('--api <url>', 'API URL').default(
|
|
20
26
|
process.env.MARKET_API_URL,
|
|
21
27
|
'from MARKET_API_URL / config / default',
|
|
22
28
|
)
|
|
23
29
|
|
|
24
|
-
program.name('market').description('
|
|
30
|
+
program.name('market').description('Find and install Drawcall Market assets').version('0.1.0')
|
|
25
31
|
|
|
26
32
|
program
|
|
27
33
|
.command('login')
|
|
28
|
-
.description('
|
|
34
|
+
.description('Sign in')
|
|
29
35
|
.addOption(apiOption)
|
|
30
36
|
.action(async (opts: { api?: string }) => {
|
|
31
|
-
|
|
37
|
+
const baseUrl = opts.api ?? DEFAULT_BASE_URL
|
|
38
|
+
const token = await runDeviceLogin()
|
|
39
|
+
const client = createMarketClient({ baseUrl, authToken: token })
|
|
40
|
+
const profile = await client.user.getProfile()
|
|
41
|
+
if (!profile) throw new Error('The Market API did not accept the auth token.')
|
|
42
|
+
await saveConfig({ authToken: token, baseUrl })
|
|
43
|
+
console.log(loginResult(profile.email, getConfigPath()))
|
|
32
44
|
})
|
|
33
45
|
|
|
34
46
|
program
|
|
35
47
|
.command('logout')
|
|
36
|
-
.description('
|
|
48
|
+
.description('Sign out')
|
|
37
49
|
.action(async () => {
|
|
38
50
|
await logout()
|
|
39
51
|
})
|
|
40
52
|
|
|
41
53
|
program
|
|
42
54
|
.command('install')
|
|
43
|
-
.description(
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
.argument('<assets...>', 'Asset names or natural-language descriptions')
|
|
55
|
+
.description('Install by name or prompt')
|
|
56
|
+
.argument('<assets...>', 'Names or prompts')
|
|
47
57
|
.addOption(typeOption)
|
|
48
58
|
.addOption(apiOption)
|
|
49
59
|
.option('--unapproved', 'Include unapproved versions', false)
|
|
50
|
-
.option('--cwd <dir>', 'Project directory'
|
|
60
|
+
.option('--cwd <dir>', 'Project directory')
|
|
51
61
|
.action(
|
|
52
62
|
async (
|
|
53
63
|
args: string[],
|
|
54
|
-
opts: { type?: AssetType; api?: string; unapproved: boolean; cwd
|
|
64
|
+
opts: { type?: AssetType; api?: string; unapproved: boolean; cwd?: string },
|
|
55
65
|
) => {
|
|
56
66
|
await installCommand(args, {
|
|
57
67
|
type: opts.type,
|
|
@@ -64,22 +74,98 @@ program
|
|
|
64
74
|
|
|
65
75
|
program
|
|
66
76
|
.command('search')
|
|
67
|
-
.description('
|
|
68
|
-
.argument('<query>', '
|
|
77
|
+
.description('Find assets')
|
|
78
|
+
.argument('<query>', 'Search query')
|
|
69
79
|
.addOption(typeOption)
|
|
70
80
|
.addOption(apiOption)
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
.option('--unapproved', 'Include unapproved versions', false)
|
|
82
|
+
.option('--limit <n>', 'Max results, 1-5', parseSearchLimit, 5)
|
|
83
|
+
.option('--verbose', 'Show longer descriptions', false)
|
|
84
|
+
.action(
|
|
85
|
+
async (
|
|
86
|
+
query: string,
|
|
87
|
+
opts: {
|
|
88
|
+
type?: AssetType
|
|
89
|
+
api?: string
|
|
90
|
+
unapproved: boolean
|
|
91
|
+
limit: number
|
|
92
|
+
verbose: boolean
|
|
93
|
+
},
|
|
94
|
+
) => {
|
|
95
|
+
if (!opts.type) {
|
|
96
|
+
console.error(
|
|
97
|
+
errorResult(`Search requires --type. Available types: ${ASSET_TYPES.join(', ')}`),
|
|
98
|
+
)
|
|
99
|
+
process.exit(1)
|
|
100
|
+
}
|
|
101
|
+
await searchCommand(query, {
|
|
102
|
+
type: opts.type,
|
|
103
|
+
baseUrl: opts.api,
|
|
104
|
+
unapproved: opts.unapproved,
|
|
105
|
+
limit: opts.limit,
|
|
106
|
+
verbose: opts.verbose,
|
|
107
|
+
})
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
program
|
|
112
|
+
.command('upload')
|
|
113
|
+
.description('Publish one model')
|
|
114
|
+
.argument('<name>', 'Asset name')
|
|
115
|
+
.argument('<file-filter>', '.glb/.gltf path or glob')
|
|
116
|
+
.argument('<description>', 'Short description')
|
|
117
|
+
.addOption(typeOption)
|
|
118
|
+
.addOption(apiOption)
|
|
119
|
+
.option('--version <version>', 'Explicit semver version')
|
|
120
|
+
.option('--cwd <dir>', 'Project directory')
|
|
121
|
+
.action(
|
|
122
|
+
async (
|
|
123
|
+
name: string,
|
|
124
|
+
fileFilter: string,
|
|
125
|
+
description: string,
|
|
126
|
+
opts: { type?: AssetType; api?: string; version?: string; cwd?: string },
|
|
127
|
+
) => {
|
|
128
|
+
await uploadCommand(name, fileFilter, description, {
|
|
129
|
+
type: opts.type,
|
|
130
|
+
version: opts.version,
|
|
131
|
+
baseUrl: opts.api,
|
|
132
|
+
cwd: opts.cwd,
|
|
133
|
+
})
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
program
|
|
138
|
+
.command('preview')
|
|
139
|
+
.description('Save preview image')
|
|
140
|
+
.argument('<name>', 'Asset name')
|
|
141
|
+
.argument('[version]', 'Semver version')
|
|
142
|
+
.addOption(typeOption)
|
|
143
|
+
.addOption(apiOption)
|
|
144
|
+
.option('--unapproved', 'Include unapproved versions', false)
|
|
145
|
+
.option('--out <file>', 'Output PNG path')
|
|
146
|
+
.action(
|
|
147
|
+
async (
|
|
148
|
+
name: string,
|
|
149
|
+
version: string | undefined,
|
|
150
|
+
opts: { type?: AssetType; api?: string; unapproved: boolean; out?: string },
|
|
151
|
+
) => {
|
|
152
|
+
await previewCommand(name, version, {
|
|
153
|
+
type: opts.type,
|
|
154
|
+
baseUrl: opts.api,
|
|
155
|
+
unapproved: opts.unapproved,
|
|
156
|
+
out: opts.out,
|
|
157
|
+
})
|
|
158
|
+
},
|
|
159
|
+
)
|
|
74
160
|
|
|
75
161
|
program
|
|
76
162
|
.command('generate')
|
|
77
|
-
.description('Generate
|
|
78
|
-
.argument('<description>', '
|
|
163
|
+
.description('Generate and install')
|
|
164
|
+
.argument('<description>', 'Asset prompt')
|
|
79
165
|
.addOption(typeOption)
|
|
80
166
|
.addOption(apiOption)
|
|
81
|
-
.option('--cwd <dir>', 'Project directory'
|
|
82
|
-
.action(async (description: string, opts: { type?: AssetType; api?: string; cwd
|
|
167
|
+
.option('--cwd <dir>', 'Project directory')
|
|
168
|
+
.action(async (description: string, opts: { type?: AssetType; api?: string; cwd?: string }) => {
|
|
83
169
|
await generateCommand(description, {
|
|
84
170
|
type: opts.type,
|
|
85
171
|
baseUrl: opts.api,
|
|
@@ -87,11 +173,40 @@ program
|
|
|
87
173
|
})
|
|
88
174
|
})
|
|
89
175
|
|
|
176
|
+
if (process.argv.length <= 2) {
|
|
177
|
+
program.outputHelp()
|
|
178
|
+
process.exit(0)
|
|
179
|
+
}
|
|
180
|
+
|
|
90
181
|
program.parseAsync().catch((err) => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (err instanceof NotLoggedInError) {
|
|
94
|
-
console.error(chalk.red(err.message))
|
|
95
|
-
}
|
|
182
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
183
|
+
console.error(errorResult(message))
|
|
96
184
|
process.exit(1)
|
|
97
185
|
})
|
|
186
|
+
|
|
187
|
+
function parseSearchLimit(value: string): number {
|
|
188
|
+
const parsed = Number.parseInt(value, 10)
|
|
189
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
190
|
+
throw new Error('--limit must be a positive integer')
|
|
191
|
+
}
|
|
192
|
+
return Math.min(parsed, 5)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function runDeviceLogin(): Promise<string> {
|
|
196
|
+
const config = await oauthClient.discovery(
|
|
197
|
+
new URL(AUTH_ISSUER_URL),
|
|
198
|
+
DEVICE_CLIENT_ID,
|
|
199
|
+
undefined,
|
|
200
|
+
oauthClient.None(),
|
|
201
|
+
)
|
|
202
|
+
const code = await oauthClient.initiateDeviceAuthorization(config, { scope: 'market' })
|
|
203
|
+
const verificationUri = code.verification_uri_complete ?? code.verification_uri
|
|
204
|
+
|
|
205
|
+
console.log(`Open ${chalk.cyan(verificationUri)}`)
|
|
206
|
+
console.log(`Code: ${chalk.bold(code.user_code)}`)
|
|
207
|
+
await open(verificationUri).catch(() => undefined)
|
|
208
|
+
|
|
209
|
+
const tokens = await oauthClient.pollDeviceAuthorizationGrant(config, code)
|
|
210
|
+
if (!tokens.access_token) throw new Error('Device login completed without an access token.')
|
|
211
|
+
return tokens.access_token
|
|
212
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -2,39 +2,29 @@ import { createORPCClient } from '@orpc/client'
|
|
|
2
2
|
import { RPCLink } from '@orpc/client/fetch'
|
|
3
3
|
import type { ContractRouterClient } from '@orpc/contract'
|
|
4
4
|
import type { AppContract } from './contract.js'
|
|
5
|
-
import type { InternalContract } from './internal-contract.js'
|
|
6
5
|
|
|
7
6
|
export type MarketClient = ContractRouterClient<AppContract>
|
|
8
|
-
export type InternalClient = ContractRouterClient<InternalContract>
|
|
9
7
|
|
|
10
8
|
const DEFAULT_BASE_URL = 'https://api.market.drawcall.ai'
|
|
11
9
|
|
|
12
10
|
export interface MarketClientOptions {
|
|
13
11
|
baseUrl?: string
|
|
14
12
|
fetch?: typeof globalThis.fetch
|
|
13
|
+
authToken?: string
|
|
15
14
|
apiKey?: string
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
function buildHeaders(
|
|
19
|
-
return
|
|
17
|
+
function buildHeaders(authToken?: string): Record<string, string> | undefined {
|
|
18
|
+
return authToken ? { authorization: `Bearer ${authToken}` } : undefined
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
export function createMarketClient(opts: MarketClientOptions = {}): MarketClient {
|
|
23
22
|
const baseUrl = opts.baseUrl || DEFAULT_BASE_URL
|
|
23
|
+
const authToken = opts.authToken ?? opts.apiKey
|
|
24
24
|
const link = new RPCLink({
|
|
25
25
|
url: new URL('/api/rpc', baseUrl).href,
|
|
26
26
|
fetch: opts.fetch,
|
|
27
|
-
headers: buildHeaders(
|
|
27
|
+
headers: buildHeaders(authToken),
|
|
28
28
|
})
|
|
29
29
|
return createORPCClient<MarketClient>(link)
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
export function createInternalClient(opts: MarketClientOptions = {}): InternalClient {
|
|
33
|
-
const baseUrl = opts.baseUrl || DEFAULT_BASE_URL
|
|
34
|
-
const link = new RPCLink({
|
|
35
|
-
url: new URL('/api/internal-rpc', baseUrl).href,
|
|
36
|
-
fetch: opts.fetch,
|
|
37
|
-
headers: buildHeaders(opts.apiKey),
|
|
38
|
-
})
|
|
39
|
-
return createORPCClient<InternalClient>(link)
|
|
40
|
-
}
|
package/src/commands/generate.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
1
|
import ora from 'ora'
|
|
3
2
|
import { getCliClient } from '../cli-client.js'
|
|
4
3
|
import { generateAndWait } from '../generate.js'
|
|
5
4
|
import { resolve } from '../resolve.js'
|
|
6
5
|
import { install as runInstall } from '../install.js'
|
|
6
|
+
import { generatedInstallResult } from '../output.js'
|
|
7
7
|
import type { AssetType } from '../schemas.js'
|
|
8
8
|
|
|
9
9
|
export interface GenerateCommandOptions {
|
|
@@ -19,7 +19,11 @@ export async function generateCommand(
|
|
|
19
19
|
): Promise<void> {
|
|
20
20
|
const { client } = await getCliClient({ baseUrl: opts.baseUrl, requireAuth: true })
|
|
21
21
|
|
|
22
|
-
const spinner = ora(
|
|
22
|
+
const spinner = ora({
|
|
23
|
+
text: `Generating "${description}"`,
|
|
24
|
+
isEnabled: Boolean(process.stderr.isTTY),
|
|
25
|
+
isSilent: !process.stderr.isTTY,
|
|
26
|
+
}).start()
|
|
23
27
|
try {
|
|
24
28
|
const generated = await generateAndWait(
|
|
25
29
|
client,
|
|
@@ -45,11 +49,10 @@ export async function generateCommand(
|
|
|
45
49
|
},
|
|
46
50
|
})
|
|
47
51
|
|
|
48
|
-
spinner.
|
|
49
|
-
|
|
50
|
-
)
|
|
52
|
+
spinner.stop()
|
|
53
|
+
console.log(generatedInstallResult(generated.assetName, generated.version))
|
|
51
54
|
} catch (err) {
|
|
52
|
-
spinner.
|
|
55
|
+
spinner.stop()
|
|
53
56
|
throw err
|
|
54
57
|
}
|
|
55
58
|
}
|
package/src/commands/install.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
1
|
import ora, { type Ora } from 'ora'
|
|
3
2
|
import { resolve } from '../resolve.js'
|
|
4
3
|
import { install as runInstall } from '../install.js'
|
|
5
4
|
import { generateAndWait } from '../generate.js'
|
|
6
5
|
import { assetNameSchema, type AssetType } from '../schemas.js'
|
|
7
6
|
import { getCliClient } from '../cli-client.js'
|
|
7
|
+
import { compact, generatedInstallResult, installResult } from '../output.js'
|
|
8
8
|
import type { MarketClient } from '../client.js'
|
|
9
9
|
|
|
10
10
|
export interface InstallCommandOptions {
|
|
@@ -20,14 +20,20 @@ interface AssetRequest {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export async function installCommand(args: string[], opts: InstallCommandOptions): Promise<void> {
|
|
23
|
-
const { client } = await getCliClient({ baseUrl: opts.baseUrl
|
|
23
|
+
const { client, authToken } = await getCliClient({ baseUrl: opts.baseUrl })
|
|
24
|
+
const canGenerate = Boolean(authToken)
|
|
24
25
|
|
|
25
|
-
const spinner = ora(
|
|
26
|
+
const spinner = ora({
|
|
27
|
+
isEnabled: Boolean(process.stderr.isTTY),
|
|
28
|
+
isSilent: !process.stderr.isTTY,
|
|
29
|
+
}).start()
|
|
26
30
|
try {
|
|
27
31
|
const requests: AssetRequest[] = []
|
|
28
32
|
for (const arg of args) {
|
|
29
33
|
spinner.text = `Resolving "${truncate(arg, 40)}"`
|
|
30
|
-
requests.push(
|
|
34
|
+
requests.push(
|
|
35
|
+
await resolveArg(client, arg, opts.type, spinner, canGenerate, opts.unapproved ?? false),
|
|
36
|
+
)
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
spinner.text = 'Resolving dependency tree'
|
|
@@ -42,50 +48,57 @@ export async function installCommand(args: string[], opts: InstallCommandOptions
|
|
|
42
48
|
},
|
|
43
49
|
})
|
|
44
50
|
|
|
45
|
-
spinner.
|
|
46
|
-
|
|
47
|
-
resolution.assets.map((a) => chalk.cyan(a.name) + chalk.dim('@' + a.version)).join(', '),
|
|
48
|
-
)
|
|
51
|
+
spinner.stop()
|
|
52
|
+
console.log(installResult(resolution.assets))
|
|
49
53
|
} catch (err) {
|
|
50
|
-
spinner.
|
|
54
|
+
spinner.stop()
|
|
51
55
|
throw err
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
/**
|
|
56
|
-
* Fallthrough: exact-name → top-1 search hit → auto-generate.
|
|
57
|
-
*/
|
|
58
59
|
async function resolveArg(
|
|
59
60
|
client: MarketClient,
|
|
60
61
|
arg: string,
|
|
61
62
|
type: AssetType | undefined,
|
|
62
63
|
spinner: Ora,
|
|
64
|
+
canGenerate: boolean,
|
|
65
|
+
includeUnapproved: boolean,
|
|
63
66
|
): Promise<AssetRequest> {
|
|
64
|
-
// 1. Try parsing as asset-name[@range] and looking up exact
|
|
65
67
|
const parsed = parseNameAndRange(arg)
|
|
66
68
|
if (parsed) {
|
|
67
|
-
const existing = await client.asset.
|
|
69
|
+
const existing = await client.asset.exact({
|
|
70
|
+
name: parsed.name,
|
|
71
|
+
type,
|
|
72
|
+
includeUnapproved,
|
|
73
|
+
})
|
|
68
74
|
if (existing) return parsed
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
// 2. Semantic search — top-1
|
|
72
77
|
spinner.text = `Searching "${truncate(arg, 40)}"`
|
|
73
|
-
const results = await client.asset.
|
|
74
|
-
|
|
78
|
+
const results = await client.asset.search({
|
|
79
|
+
query: arg,
|
|
75
80
|
type,
|
|
81
|
+
includeUnapproved,
|
|
76
82
|
limit: 1,
|
|
77
83
|
page: 1,
|
|
78
84
|
sort: 'relevance',
|
|
79
85
|
})
|
|
80
86
|
if (results.items.length > 0) {
|
|
81
87
|
const hit = results.items[0]
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
if (process.stderr.isTTY) {
|
|
89
|
+
spinner.info(`Matched "${compact(arg, 48)}" to ${hit.name} (${hit.type})`)
|
|
90
|
+
spinner.start()
|
|
91
|
+
}
|
|
84
92
|
return { name: hit.name, range: '*' }
|
|
85
93
|
}
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
if (!canGenerate) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`No matches for "${arg}". Run \`market login\` to enable auto-generation of missing assets.`,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
spinner.text = `No matches; generating "${truncate(arg, 40)}"`
|
|
89
102
|
const generated = await generateAndWait(
|
|
90
103
|
client,
|
|
91
104
|
{ description: arg, type },
|
|
@@ -95,17 +108,15 @@ async function resolveArg(
|
|
|
95
108
|
},
|
|
96
109
|
},
|
|
97
110
|
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
if (process.stderr.isTTY) {
|
|
112
|
+
spinner.info(
|
|
113
|
+
`${generatedInstallResult(generated.assetName, generated.version)} for "${compact(arg, 48)}"`,
|
|
114
|
+
)
|
|
115
|
+
spinner.start()
|
|
116
|
+
}
|
|
102
117
|
return { name: generated.assetName, range: generated.version }
|
|
103
118
|
}
|
|
104
119
|
|
|
105
|
-
/**
|
|
106
|
-
* Returns { name, range } if the arg parses as `<asset-name>[@<range>]`.
|
|
107
|
-
* Returns null if the name portion fails the asset-name schema (e.g. has spaces).
|
|
108
|
-
*/
|
|
109
120
|
function parseNameAndRange(arg: string): AssetRequest | null {
|
|
110
121
|
const atIdx = arg.indexOf('@')
|
|
111
122
|
const name = atIdx >= 0 ? arg.slice(0, atIdx) : arg
|
|
@@ -114,5 +125,5 @@ function parseNameAndRange(arg: string): AssetRequest | null {
|
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
function truncate(s: string, max: number): string {
|
|
117
|
-
return s.length > max ? s.slice(0, max -
|
|
128
|
+
return s.length > max ? s.slice(0, max - 3) + '...' : s
|
|
118
129
|
}
|
package/src/commands/logout.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
1
|
import { clearConfig, getConfigPath } from '../config.js'
|
|
2
|
+
import { logoutResult } from '../output.js'
|
|
3
3
|
|
|
4
4
|
export async function logout(): Promise<void> {
|
|
5
5
|
const existed = await clearConfig()
|
|
6
6
|
if (existed) {
|
|
7
|
-
console.log(
|
|
7
|
+
console.log(logoutResult(getConfigPath()))
|
|
8
8
|
} else {
|
|
9
|
-
console.log(
|
|
9
|
+
console.log(logoutResult())
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as fs from 'fs/promises'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import ora from 'ora'
|
|
4
|
+
import { getCliClient } from '../cli-client.js'
|
|
5
|
+
import { previewResult } from '../output.js'
|
|
6
|
+
import { assetNameSchema, semverSchema, type AssetType } from '../schemas.js'
|
|
7
|
+
|
|
8
|
+
export interface PreviewCommandOptions {
|
|
9
|
+
type?: AssetType
|
|
10
|
+
unapproved?: boolean
|
|
11
|
+
out?: string
|
|
12
|
+
baseUrl?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function previewCommand(
|
|
16
|
+
name: string,
|
|
17
|
+
version: string | undefined,
|
|
18
|
+
opts: PreviewCommandOptions,
|
|
19
|
+
): Promise<void> {
|
|
20
|
+
const parsedName = assetNameSchema.parse(name)
|
|
21
|
+
const parsedVersion = version ? semverSchema.parse(version) : undefined
|
|
22
|
+
const spinner = ora({
|
|
23
|
+
text: `Resolving preview for ${parsedName}`,
|
|
24
|
+
isEnabled: Boolean(process.stderr.isTTY),
|
|
25
|
+
isSilent: !process.stderr.isTTY,
|
|
26
|
+
}).start()
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const { client } = await getCliClient({ baseUrl: opts.baseUrl })
|
|
30
|
+
const resolvedVersion =
|
|
31
|
+
parsedVersion ??
|
|
32
|
+
(
|
|
33
|
+
await client.asset.exact({
|
|
34
|
+
name: parsedName,
|
|
35
|
+
type: opts.type ?? 'model',
|
|
36
|
+
includeUnapproved: opts.unapproved ?? false,
|
|
37
|
+
})
|
|
38
|
+
)?.latestVersion
|
|
39
|
+
|
|
40
|
+
if (!resolvedVersion) {
|
|
41
|
+
throw new Error(`Asset "${parsedName}" not found`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
spinner.text = `Downloading preview for ${parsedName}@${resolvedVersion}`
|
|
45
|
+
const preview = await client.asset.downloadPreviewImage({
|
|
46
|
+
name: parsedName,
|
|
47
|
+
version: resolvedVersion,
|
|
48
|
+
})
|
|
49
|
+
const bytes = new Uint8Array(await preview.arrayBuffer())
|
|
50
|
+
const out = path.resolve(
|
|
51
|
+
opts.out ?? `${parsedName}-${resolvedVersion}-preview${previewExtension(preview.type)}`,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
await fs.mkdir(path.dirname(out), { recursive: true })
|
|
55
|
+
await fs.writeFile(out, bytes)
|
|
56
|
+
|
|
57
|
+
spinner.stop()
|
|
58
|
+
console.log(previewResult(parsedName, resolvedVersion, out))
|
|
59
|
+
} catch (err) {
|
|
60
|
+
spinner.stop()
|
|
61
|
+
throw err
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function previewExtension(contentType: string): string {
|
|
66
|
+
if (contentType === 'image/webp') return '.webp'
|
|
67
|
+
if (contentType === 'image/jpeg') return '.jpg'
|
|
68
|
+
return '.png'
|
|
69
|
+
}
|
package/src/commands/search.ts
CHANGED
|
@@ -1,41 +1,57 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
1
|
import ora from 'ora'
|
|
3
2
|
import { getCliClient } from '../cli-client.js'
|
|
3
|
+
import { assetSearchResultLine, searchSummary } from '../output.js'
|
|
4
4
|
import type { AssetType } from '../schemas.js'
|
|
5
5
|
|
|
6
|
-
const SEARCH_LIMIT =
|
|
6
|
+
const SEARCH_LIMIT = 5
|
|
7
7
|
|
|
8
8
|
export interface SearchCommandOptions {
|
|
9
|
-
type
|
|
9
|
+
type: AssetType
|
|
10
|
+
unapproved?: boolean
|
|
10
11
|
baseUrl?: string
|
|
12
|
+
limit?: number
|
|
13
|
+
verbose?: boolean
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export async function searchCommand(query: string, opts: SearchCommandOptions): Promise<void> {
|
|
14
|
-
const { client } = await getCliClient({ baseUrl: opts.baseUrl
|
|
17
|
+
const { client } = await getCliClient({ baseUrl: opts.baseUrl })
|
|
15
18
|
|
|
16
|
-
const spinner = ora(
|
|
19
|
+
const spinner = ora({
|
|
20
|
+
text: `Searching "${query}"`,
|
|
21
|
+
isEnabled: Boolean(process.stderr.isTTY),
|
|
22
|
+
isSilent: !process.stderr.isTTY,
|
|
23
|
+
}).start()
|
|
17
24
|
let results
|
|
18
25
|
try {
|
|
19
|
-
results = await client.asset.
|
|
20
|
-
|
|
26
|
+
results = await client.asset.search({
|
|
27
|
+
query,
|
|
21
28
|
type: opts.type,
|
|
22
|
-
|
|
29
|
+
includeUnapproved: opts.unapproved ?? false,
|
|
30
|
+
limit: opts.limit ?? SEARCH_LIMIT,
|
|
23
31
|
page: 1,
|
|
24
32
|
sort: 'relevance',
|
|
25
33
|
})
|
|
26
34
|
} catch (err) {
|
|
27
|
-
spinner.
|
|
35
|
+
spinner.stop()
|
|
28
36
|
throw err
|
|
29
37
|
}
|
|
30
38
|
spinner.stop()
|
|
31
39
|
|
|
40
|
+
console.log(
|
|
41
|
+
searchSummary({
|
|
42
|
+
query,
|
|
43
|
+
type: opts.type,
|
|
44
|
+
includeUnapproved: opts.unapproved ?? false,
|
|
45
|
+
count: results.items.length,
|
|
46
|
+
total: results.total,
|
|
47
|
+
}),
|
|
48
|
+
)
|
|
49
|
+
|
|
32
50
|
if (results.items.length === 0) {
|
|
33
|
-
console.log(chalk.dim('No matches.'))
|
|
34
51
|
return
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
for (const item of results.items) {
|
|
38
|
-
|
|
39
|
-
console.log(` ${chalk.cyan(item.name)} ${chalk.dim(`(${item.type})`)}${desc}`)
|
|
55
|
+
console.log(assetSearchResultLine(item, { verbose: opts.verbose ?? false }))
|
|
40
56
|
}
|
|
41
57
|
}
|