@drawcall/market 0.1.25 → 0.1.27
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/cli-client.d.ts.map +1 -1
- package/dist/cli-client.js +1 -1
- package/dist/cli-client.js.map +1 -1
- package/dist/cli.js +15 -15
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +0 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -2
- package/dist/client.js.map +1 -1
- package/dist/commands/install.d.ts +8 -4
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +24 -59
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/preview.d.ts +0 -2
- package/dist/commands/preview.d.ts.map +1 -1
- package/dist/commands/preview.js +4 -6
- package/dist/commands/preview.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -3
- package/dist/config.js.map +1 -1
- package/dist/install.js +6 -8
- package/dist/install.js.map +1 -1
- package/dist/resolve.d.ts +4 -3
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +38 -19
- package/dist/resolve.js.map +1 -1
- package/dist/skill.d.ts +1 -1
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +16 -12
- package/dist/skill.js.map +1 -1
- package/package.json +1 -1
- package/src/cli-client.ts +1 -2
- package/src/cli.ts +26 -32
- package/src/client.ts +1 -3
- package/src/commands/install.ts +30 -77
- package/src/commands/preview.ts +5 -10
- package/src/config.ts +9 -3
- package/src/install.ts +6 -7
- package/src/resolve.ts +46 -21
- package/src/skill.ts +16 -12
- package/tests/install-command.test.ts +13 -46
package/dist/resolve.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Dependency resolver for market assets.
|
|
3
3
|
*
|
|
4
4
|
* 1. Collects the target assets and all transitive asset dependencies.
|
|
5
|
-
* 2. For each asset,
|
|
6
|
-
* every dependent.
|
|
7
|
-
*
|
|
5
|
+
* 2. For each asset, takes the latest published version and verifies it
|
|
6
|
+
* satisfies ALL constraints from every dependent. (Only the latest
|
|
7
|
+
* version is resolved; older versions are not considered.)
|
|
8
|
+
* 3. Intersects npm dependency ranges across all resolved assets and checks
|
|
8
9
|
* that they are compatible.
|
|
9
10
|
*/
|
|
10
11
|
import * as semver from 'semver';
|
|
@@ -29,18 +30,22 @@ export async function resolve(client, requests, opts = {}) {
|
|
|
29
30
|
throw new ResolutionError(`Asset "${assetName}" not found.`);
|
|
30
31
|
}
|
|
31
32
|
if (!opts.includeUnapproved && !meta.approved) {
|
|
32
|
-
throw new ResolutionError(`Asset "${assetName}" has no
|
|
33
|
+
throw new ResolutionError(`Asset "${assetName}" has no approved versions.`);
|
|
33
34
|
}
|
|
35
|
+
// The market publishes immutable versions and resolution targets the
|
|
36
|
+
// latest published version of each asset. A range that excludes the
|
|
37
|
+
// latest version is therefore unsatisfiable (older versions are not
|
|
38
|
+
// resolved), which this message makes explicit.
|
|
34
39
|
const assetConstraints = constraints.get(assetName) ?? [];
|
|
35
40
|
if (!assetConstraints.every((c) => semver.satisfies(meta.latestVersion, c.range))) {
|
|
36
41
|
const constraintDesc = assetConstraints
|
|
37
42
|
.map((c) => ` ${c.range} (from ${c.from})`)
|
|
38
43
|
.join('\n');
|
|
39
|
-
throw new ResolutionError(`
|
|
40
|
-
`
|
|
44
|
+
throw new ResolutionError(`The latest${opts.includeUnapproved ? '' : ' approved'} version of "${assetName}" ` +
|
|
45
|
+
`(${meta.latestVersion}) does not satisfy all constraints:\n${constraintDesc}`);
|
|
41
46
|
}
|
|
42
|
-
const npmDeps =
|
|
43
|
-
const assetDeps =
|
|
47
|
+
const npmDeps = parseDependencies(meta.npmDependencies, assetName, 'npm');
|
|
48
|
+
const assetDeps = parseDependencies(meta.assetDependencies, assetName, 'asset');
|
|
44
49
|
resolved.set(assetName, {
|
|
45
50
|
name: assetName,
|
|
46
51
|
version: meta.latestVersion,
|
|
@@ -71,6 +76,18 @@ export async function resolve(client, requests, opts = {}) {
|
|
|
71
76
|
npmDependencies: mergedNpm,
|
|
72
77
|
};
|
|
73
78
|
}
|
|
79
|
+
function parseDependencies(value, assetName, kind) {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(value || '{}');
|
|
82
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
83
|
+
return parsed;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// fall through to a clear error below
|
|
88
|
+
}
|
|
89
|
+
throw new ResolutionError(`Asset "${assetName}" has malformed ${kind} dependency metadata.`);
|
|
90
|
+
}
|
|
74
91
|
function addConstraint(constraints, name, range, from) {
|
|
75
92
|
const existing = constraints.get(name) ?? [];
|
|
76
93
|
existing.push({ range, from });
|
|
@@ -93,7 +110,8 @@ async function fetchMeta(client, cache, name, includeUnapproved) {
|
|
|
93
110
|
/**
|
|
94
111
|
* Merge npm dependency ranges from all resolved assets.
|
|
95
112
|
* For each package, check that all declared ranges are compatible
|
|
96
|
-
* (using semver.intersects)
|
|
113
|
+
* (using semver.intersects), then return the single declared range that is a
|
|
114
|
+
* subset of every other — the true intersection.
|
|
97
115
|
*/
|
|
98
116
|
function mergeNpmDependencies(assets) {
|
|
99
117
|
// Collect all ranges per package
|
|
@@ -117,17 +135,18 @@ function mergeNpmDependencies(assets) {
|
|
|
117
135
|
}
|
|
118
136
|
}
|
|
119
137
|
}
|
|
120
|
-
// Use the
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
138
|
+
// Use the single declared range that is contained in every other declared
|
|
139
|
+
// range — the true intersection of all constraints. Picking by "highest
|
|
140
|
+
// minimum version" is unsound: a range with a higher minimum can also have
|
|
141
|
+
// a higher (or open) upper bound, silently dropping another dependent's
|
|
142
|
+
// upper bound. If no single range represents the intersection, it cannot be
|
|
143
|
+
// expressed as one package.json range, so fail loudly.
|
|
144
|
+
const narrowest = ranges.find((candidate) => ranges.every((other) => semver.subset(candidate.range, other.range)));
|
|
145
|
+
if (!narrowest) {
|
|
146
|
+
throw new ResolutionError(`npm dependency ranges for "${pkg}" overlap but cannot be combined into a single range:\n` +
|
|
147
|
+
ranges.map((r) => ` ${r.range} (from ${r.from})`).join('\n'));
|
|
129
148
|
}
|
|
130
|
-
merged[pkg] = narrowest;
|
|
149
|
+
merged[pkg] = narrowest.range;
|
|
131
150
|
}
|
|
132
151
|
return merged;
|
|
133
152
|
}
|
package/dist/resolve.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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"}
|
package/dist/skill.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const marketSkill = "---\nname: market\ndescription: Find, preview, install, generate, and publish Drawcall Market assets from a coding agent.\n---\n\n# Drawcall Market\n\nUse the `market` CLI. Keep commands short and read the summary
|
|
1
|
+
export declare const marketSkill = "---\nname: market\ndescription: Find, preview, install, generate, and publish Drawcall Market assets from a coding agent.\n---\n\n# Drawcall Market\n\nUse the `market` CLI. Keep commands short and read the summary lines.\n\n## Quick Start\n\n```sh\nmarket search \"wooden chair\" --type model --limit 3\nmarket install wooden-chair --cwd \"$PWD\"\nmarket preview wooden-chair --out /tmp/wooden-chair.png\n```\n\n## Workflow\n\n1. Search first unless the user already gave an exact asset name. `search` requires `--type`; use `model` unless the user names another supported type: `humanoid-model`, `texture`, `humanoid-animation`, `template`, `sound-effect`, `background-music`, or `environment`.\n2. Use `--limit 1` for lookup, `--limit 3` for choice. Search caps at 5. Add `--verbose` only when the one-line descriptions are not enough.\n3. `install` takes one or more exact asset names (optionally `name@range`); it does not search or generate. Find names with `search` first. No `--type` is needed \u2014 asset names are unique.\n4. `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.\n5. Use `--unapproved` only when the user asks for unapproved/private/admin assets. Do not install unapproved assets without explicit acceptance.\n6. `generate --type <type> \"<prompt>\"` creates a new asset; it requires login and a type that supports generation. No asset type currently supports generation.\n7. Upload only when publishing is requested: `market upload <name> <zip> \"<description>\" --type <type>`.\n8. 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.\n\n## Output\n\nCommands print concise, line-oriented summaries:\n\n```text\nResults: 2/8 query=\"wooden chair\" type=model approval=approved\n- wooden-chair@1.0.0 | model | approved | Low-poly wooden chair\nInstalled:\n- wooden-chair@1.0.0 (asset)\n files:\n public/model\n \u2514\u2500 wooden-chair.glb\n- three@^0.178.0 (npm)\nSaved preview for wooden-chair@1.0.0: /tmp/wooden-chair.png\n```\n\nIf search returns no results, try one broader noun phrase. If a command returns `Error: Not logged in...`, ask before running `market login`.\n";
|
|
2
2
|
//# sourceMappingURL=skill.d.ts.map
|
package/dist/skill.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,s6EA6CvB,CAAA"}
|
package/dist/skill.js
CHANGED
|
@@ -5,36 +5,40 @@ description: Find, preview, install, generate, and publish Drawcall Market asset
|
|
|
5
5
|
|
|
6
6
|
# Drawcall Market
|
|
7
7
|
|
|
8
|
-
Use the \`market\` CLI. Keep commands short and read the summary
|
|
8
|
+
Use the \`market\` CLI. Keep commands short and read the summary lines.
|
|
9
9
|
|
|
10
10
|
## Quick Start
|
|
11
11
|
|
|
12
12
|
\`\`\`sh
|
|
13
13
|
market search "wooden chair" --type model --limit 3
|
|
14
|
-
market install wooden-chair --
|
|
14
|
+
market install wooden-chair --cwd "$PWD"
|
|
15
15
|
market preview wooden-chair --out /tmp/wooden-chair.png
|
|
16
16
|
\`\`\`
|
|
17
17
|
|
|
18
18
|
## Workflow
|
|
19
19
|
|
|
20
|
-
1. Search first unless the user gave an exact asset name.
|
|
21
|
-
2.
|
|
22
|
-
3.
|
|
23
|
-
4.
|
|
20
|
+
1. Search first unless the user already gave an exact asset name. \`search\` requires \`--type\`; use \`model\` unless the user names another supported type: \`humanoid-model\`, \`texture\`, \`humanoid-animation\`, \`template\`, \`sound-effect\`, \`background-music\`, or \`environment\`.
|
|
21
|
+
2. Use \`--limit 1\` for lookup, \`--limit 3\` for choice. Search caps at 5. Add \`--verbose\` only when the one-line descriptions are not enough.
|
|
22
|
+
3. \`install\` takes one or more exact asset names (optionally \`name@range\`); it does not search or generate. Find names with \`search\` first. No \`--type\` is needed — asset names are unique.
|
|
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
|
-
6.
|
|
26
|
-
7.
|
|
27
|
-
8.
|
|
28
|
-
9. 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.
|
|
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>\`.
|
|
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.
|
|
29
28
|
|
|
30
29
|
## Output
|
|
31
30
|
|
|
32
|
-
Commands print concise summaries:
|
|
31
|
+
Commands print concise, line-oriented summaries:
|
|
33
32
|
|
|
34
33
|
\`\`\`text
|
|
35
34
|
Results: 2/8 query="wooden chair" type=model approval=approved
|
|
36
35
|
- wooden-chair@1.0.0 | model | approved | Low-poly wooden chair
|
|
37
|
-
Installed
|
|
36
|
+
Installed:
|
|
37
|
+
- wooden-chair@1.0.0 (asset)
|
|
38
|
+
files:
|
|
39
|
+
public/model
|
|
40
|
+
└─ wooden-chair.glb
|
|
41
|
+
- three@^0.178.0 (npm)
|
|
38
42
|
Saved preview for wooden-chair@1.0.0: /tmp/wooden-chair.png
|
|
39
43
|
\`\`\`
|
|
40
44
|
|
package/dist/skill.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill.js","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG
|
|
1
|
+
{"version":3,"file":"skill.js","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6C1B,CAAA"}
|
package/package.json
CHANGED
package/src/cli-client.ts
CHANGED
|
@@ -25,8 +25,7 @@ export interface CliClient {
|
|
|
25
25
|
|
|
26
26
|
export async function getCliClient(opts: CliClientOptions = {}): Promise<CliClient> {
|
|
27
27
|
const config = await loadConfig()
|
|
28
|
-
const authToken =
|
|
29
|
-
process.env.DRAWCALL_AUTH_TOKEN ?? process.env.MARKET_API_KEY ?? config?.authToken
|
|
28
|
+
const authToken = process.env.DRAWCALL_AUTH_TOKEN ?? config?.authToken
|
|
30
29
|
const baseUrl = opts.baseUrl ?? process.env.MARKET_API_URL ?? config?.baseUrl ?? DEFAULT_BASE_URL
|
|
31
30
|
|
|
32
31
|
if (opts.requireAuth && !authToken) {
|
package/src/cli.ts
CHANGED
|
@@ -56,7 +56,10 @@ program
|
|
|
56
56
|
const client = createMarketClient({ baseUrl, authToken: token })
|
|
57
57
|
const profile = await client.user.getProfile()
|
|
58
58
|
if (!profile) throw new Error('The Market API did not accept the auth token.')
|
|
59
|
-
|
|
59
|
+
// Only pin baseUrl in config when the user explicitly chose one. Persisting
|
|
60
|
+
// the default would freeze logged-in users to a host the CLI can no longer
|
|
61
|
+
// change in a future release.
|
|
62
|
+
await saveConfig(opts.api ? { authToken: token, baseUrl: opts.api } : { authToken: token })
|
|
60
63
|
console.log(loginResult(profile.email, getConfigPath()))
|
|
61
64
|
})
|
|
62
65
|
|
|
@@ -69,25 +72,18 @@ program
|
|
|
69
72
|
|
|
70
73
|
program
|
|
71
74
|
.command('install')
|
|
72
|
-
.description('Install by name
|
|
73
|
-
.argument('<assets...>', '
|
|
74
|
-
.addOption(typeOption)
|
|
75
|
+
.description('Install assets by name')
|
|
76
|
+
.argument('<assets...>', 'Asset names, optionally with @range')
|
|
75
77
|
.addOption(apiOption)
|
|
76
78
|
.option('--unapproved', 'Include unapproved versions', false)
|
|
77
79
|
.option('--cwd <dir>', 'Project directory')
|
|
78
|
-
.action(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
baseUrl: opts.api,
|
|
86
|
-
unapproved: opts.unapproved,
|
|
87
|
-
cwd: opts.cwd,
|
|
88
|
-
})
|
|
89
|
-
},
|
|
90
|
-
)
|
|
80
|
+
.action(async (args: string[], opts: { api?: string; unapproved: boolean; cwd?: string }) => {
|
|
81
|
+
await installCommand(args, {
|
|
82
|
+
baseUrl: opts.api,
|
|
83
|
+
unapproved: opts.unapproved,
|
|
84
|
+
cwd: opts.cwd,
|
|
85
|
+
})
|
|
86
|
+
})
|
|
91
87
|
|
|
92
88
|
program
|
|
93
89
|
.command('search')
|
|
@@ -109,12 +105,7 @@ program
|
|
|
109
105
|
verbose: boolean
|
|
110
106
|
},
|
|
111
107
|
) => {
|
|
112
|
-
|
|
113
|
-
console.error(
|
|
114
|
-
errorResult(`Search requires --type. Available types: ${ASSET_TYPES.join(', ')}`),
|
|
115
|
-
)
|
|
116
|
-
process.exit(1)
|
|
117
|
-
}
|
|
108
|
+
requireType(opts.type, 'Search')
|
|
118
109
|
await searchCommand(query, {
|
|
119
110
|
type: opts.type,
|
|
120
111
|
baseUrl: opts.api,
|
|
@@ -142,12 +133,7 @@ program
|
|
|
142
133
|
description: string,
|
|
143
134
|
opts: { type?: AssetType; api?: string; version?: string; cwd?: string },
|
|
144
135
|
) => {
|
|
145
|
-
|
|
146
|
-
console.error(
|
|
147
|
-
errorResult(`Upload requires --type. Available types: ${ASSET_TYPES.join(', ')}`),
|
|
148
|
-
)
|
|
149
|
-
process.exit(1)
|
|
150
|
-
}
|
|
136
|
+
requireType(opts.type, 'Upload')
|
|
151
137
|
await uploadCommand(name, zipFilter, description, {
|
|
152
138
|
type: opts.type,
|
|
153
139
|
version: opts.version,
|
|
@@ -162,7 +148,6 @@ program
|
|
|
162
148
|
.description('Save preview image')
|
|
163
149
|
.argument('<name>', 'Asset name')
|
|
164
150
|
.argument('[version]', 'Semver version')
|
|
165
|
-
.addOption(typeOption)
|
|
166
151
|
.addOption(apiOption)
|
|
167
152
|
.option('--unapproved', 'Include unapproved versions', false)
|
|
168
153
|
.option('--out <file>', 'Output image path')
|
|
@@ -170,10 +155,9 @@ program
|
|
|
170
155
|
async (
|
|
171
156
|
name: string,
|
|
172
157
|
version: string | undefined,
|
|
173
|
-
opts: {
|
|
158
|
+
opts: { api?: string; unapproved: boolean; out?: string },
|
|
174
159
|
) => {
|
|
175
160
|
await previewCommand(name, version, {
|
|
176
|
-
type: opts.type,
|
|
177
161
|
baseUrl: opts.api,
|
|
178
162
|
unapproved: opts.unapproved,
|
|
179
163
|
out: opts.out,
|
|
@@ -189,6 +173,7 @@ program
|
|
|
189
173
|
.addOption(apiOption)
|
|
190
174
|
.option('--cwd <dir>', 'Project directory')
|
|
191
175
|
.action(async (description: string, opts: { type?: AssetType; api?: string; cwd?: string }) => {
|
|
176
|
+
requireType(opts.type, 'Generate')
|
|
192
177
|
await generateCommand(description, {
|
|
193
178
|
type: opts.type,
|
|
194
179
|
baseUrl: opts.api,
|
|
@@ -207,6 +192,15 @@ program.parseAsync().catch((err) => {
|
|
|
207
192
|
process.exit(1)
|
|
208
193
|
})
|
|
209
194
|
|
|
195
|
+
function requireType(type: AssetType | undefined, command: string): asserts type is AssetType {
|
|
196
|
+
if (!type) {
|
|
197
|
+
console.error(
|
|
198
|
+
errorResult(`${command} requires --type. Available types: ${ASSET_TYPES.join(', ')}`),
|
|
199
|
+
)
|
|
200
|
+
process.exit(1)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
210
204
|
function parseSearchLimit(value: string): number {
|
|
211
205
|
const parsed = Number.parseInt(value, 10)
|
|
212
206
|
if (!Number.isInteger(parsed) || parsed < 1) {
|
package/src/client.ts
CHANGED
|
@@ -11,7 +11,6 @@ export interface MarketClientOptions {
|
|
|
11
11
|
baseUrl?: string
|
|
12
12
|
fetch?: typeof globalThis.fetch
|
|
13
13
|
authToken?: string
|
|
14
|
-
apiKey?: string
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
function buildHeaders(authToken?: string): Record<string, string> | undefined {
|
|
@@ -20,11 +19,10 @@ function buildHeaders(authToken?: string): Record<string, string> | undefined {
|
|
|
20
19
|
|
|
21
20
|
export function createMarketClient(opts: MarketClientOptions = {}): MarketClient {
|
|
22
21
|
const baseUrl = opts.baseUrl || DEFAULT_BASE_URL
|
|
23
|
-
const authToken = opts.authToken ?? opts.apiKey
|
|
24
22
|
const link = new RPCLink({
|
|
25
23
|
url: new URL('/api/rpc', baseUrl).href,
|
|
26
24
|
fetch: opts.fetch,
|
|
27
|
-
headers: buildHeaders(authToken),
|
|
25
|
+
headers: buildHeaders(opts.authToken),
|
|
28
26
|
})
|
|
29
27
|
return createORPCClient<MarketClient>(link)
|
|
30
28
|
}
|
package/src/commands/install.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import ora
|
|
1
|
+
import ora from 'ora'
|
|
2
2
|
import { resolve } from '../resolve.js'
|
|
3
3
|
import { install as runInstall } from '../install.js'
|
|
4
|
-
import {
|
|
5
|
-
import { assetNameSchema, type AssetType } from '../schemas.js'
|
|
4
|
+
import { assetNameSchema } from '../schemas.js'
|
|
6
5
|
import { getCliClient } from '../cli-client.js'
|
|
7
|
-
import {
|
|
6
|
+
import { installResult } from '../output.js'
|
|
8
7
|
import type { MarketClient } from '../client.js'
|
|
9
8
|
|
|
10
9
|
export interface InstallCommandOptions {
|
|
11
|
-
type?: AssetType
|
|
12
10
|
unapproved?: boolean
|
|
13
11
|
cwd?: string
|
|
14
12
|
baseUrl?: string
|
|
@@ -20,8 +18,8 @@ interface AssetRequest {
|
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
export async function installCommand(args: string[], opts: InstallCommandOptions): Promise<void> {
|
|
23
|
-
const { client
|
|
24
|
-
const
|
|
21
|
+
const { client } = await getCliClient({ baseUrl: opts.baseUrl })
|
|
22
|
+
const includeUnapproved = opts.unapproved ?? false
|
|
25
23
|
|
|
26
24
|
const spinner = ora({
|
|
27
25
|
isEnabled: Boolean(process.stderr.isTTY),
|
|
@@ -31,15 +29,11 @@ export async function installCommand(args: string[], opts: InstallCommandOptions
|
|
|
31
29
|
const requests: AssetRequest[] = []
|
|
32
30
|
for (const arg of args) {
|
|
33
31
|
spinner.text = `Resolving "${truncate(arg, 40)}"`
|
|
34
|
-
requests.push(
|
|
35
|
-
await resolveArg(client, arg, opts.type, spinner, canGenerate, opts.unapproved ?? false),
|
|
36
|
-
)
|
|
32
|
+
requests.push(await resolveArg(client, arg, includeUnapproved))
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
spinner.text = 'Resolving dependency tree'
|
|
40
|
-
const resolution = await resolve(client.asset, requests, {
|
|
41
|
-
includeUnapproved: opts.unapproved ?? false,
|
|
42
|
-
})
|
|
36
|
+
const resolution = await resolve(client.asset, requests, { includeUnapproved })
|
|
43
37
|
|
|
44
38
|
const result = await runInstall(client, resolution, {
|
|
45
39
|
cwd: opts.cwd,
|
|
@@ -56,82 +50,41 @@ export async function installCommand(args: string[], opts: InstallCommandOptions
|
|
|
56
50
|
}
|
|
57
51
|
}
|
|
58
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a single `name` or `name@range` argument to an exact asset request.
|
|
55
|
+
* Asset names are globally unique, so the name fully determines the asset — no
|
|
56
|
+
* `--type` is needed. install never fuzzy-searches or generates: it points the
|
|
57
|
+
* caller at `market search` / `market generate` instead, which keeps behavior
|
|
58
|
+
* predictable for agents.
|
|
59
|
+
*/
|
|
59
60
|
export async function resolveArg(
|
|
60
61
|
client: MarketClient,
|
|
61
62
|
arg: string,
|
|
62
|
-
type: AssetType | undefined,
|
|
63
|
-
spinner: Ora,
|
|
64
|
-
canGenerate: boolean,
|
|
65
63
|
includeUnapproved: boolean,
|
|
66
64
|
): Promise<AssetRequest> {
|
|
67
65
|
const parsed = parseNameAndRange(arg)
|
|
68
|
-
if (parsed) {
|
|
69
|
-
|
|
70
|
-
name
|
|
71
|
-
|
|
72
|
-
includeUnapproved,
|
|
73
|
-
})
|
|
74
|
-
if (existing) return parsed
|
|
75
|
-
|
|
76
|
-
if (!includeUnapproved) {
|
|
77
|
-
const hidden = await client.asset.exact({
|
|
78
|
-
name: parsed.name,
|
|
79
|
-
type,
|
|
80
|
-
includeUnapproved: true,
|
|
81
|
-
})
|
|
82
|
-
if (hidden) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
`Asset "${parsed.name}" exists but has no approved versions. Re-run with \`--unapproved\` if you have access.`,
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (arg.includes('@')) {
|
|
90
|
-
return parsed
|
|
91
|
-
}
|
|
66
|
+
if (!parsed) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Invalid asset name "${arg}". Use \`market search --type <type> <query>\` to find assets.`,
|
|
69
|
+
)
|
|
92
70
|
}
|
|
93
71
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
query: arg,
|
|
97
|
-
type,
|
|
98
|
-
includeUnapproved,
|
|
99
|
-
limit: 1,
|
|
100
|
-
page: 1,
|
|
101
|
-
sort: 'relevance',
|
|
102
|
-
})
|
|
103
|
-
if (results.items.length > 0) {
|
|
104
|
-
const hit = results.items[0]
|
|
105
|
-
if (process.stderr.isTTY) {
|
|
106
|
-
spinner.info(`Matched "${compact(arg, 48)}" to ${hit.name} (${hit.type})`)
|
|
107
|
-
spinner.start()
|
|
108
|
-
}
|
|
109
|
-
return { name: hit.name, range: '*' }
|
|
110
|
-
}
|
|
72
|
+
const existing = await client.asset.exact({ name: parsed.name, includeUnapproved })
|
|
73
|
+
if (existing) return parsed
|
|
111
74
|
|
|
112
|
-
if (!
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
75
|
+
if (!includeUnapproved) {
|
|
76
|
+
const hidden = await client.asset.exact({ name: parsed.name, includeUnapproved: true })
|
|
77
|
+
if (hidden) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Asset "${parsed.name}" exists but has no approved versions. Re-run with \`--unapproved\` if you have access.`,
|
|
80
|
+
)
|
|
81
|
+
}
|
|
116
82
|
}
|
|
117
83
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
{ description: arg, type },
|
|
122
|
-
{
|
|
123
|
-
onProgress: (msg) => {
|
|
124
|
-
spinner.text = msg
|
|
125
|
-
},
|
|
126
|
-
},
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Asset "${parsed.name}" not found. Use \`market search\` to find assets or ` +
|
|
86
|
+
`\`market generate --type <type>\` to create one.`,
|
|
127
87
|
)
|
|
128
|
-
if (process.stderr.isTTY) {
|
|
129
|
-
spinner.info(
|
|
130
|
-
`${generatedInstallResult(generated.assetName, generated.version)} for "${compact(arg, 48)}"`,
|
|
131
|
-
)
|
|
132
|
-
spinner.start()
|
|
133
|
-
}
|
|
134
|
-
return { name: generated.assetName, range: generated.version }
|
|
135
88
|
}
|
|
136
89
|
|
|
137
90
|
function parseNameAndRange(arg: string): AssetRequest | null {
|
package/src/commands/preview.ts
CHANGED
|
@@ -3,10 +3,9 @@ import * as path from 'path'
|
|
|
3
3
|
import ora from 'ora'
|
|
4
4
|
import { getCliClient } from '../cli-client.js'
|
|
5
5
|
import { previewResult } from '../output.js'
|
|
6
|
-
import { assetNameSchema, semverSchema
|
|
6
|
+
import { assetNameSchema, semverSchema } from '../schemas.js'
|
|
7
7
|
|
|
8
8
|
export interface PreviewCommandOptions {
|
|
9
|
-
type?: AssetType
|
|
10
9
|
unapproved?: boolean
|
|
11
10
|
out?: string
|
|
12
11
|
baseUrl?: string
|
|
@@ -19,13 +18,10 @@ export async function previewCommand(
|
|
|
19
18
|
): Promise<void> {
|
|
20
19
|
const parsedName = assetNameSchema.parse(name)
|
|
21
20
|
const parsedVersion = version ? semverSchema.parse(version) : undefined
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
) {
|
|
27
|
-
throw new Error(`Preview images are not supported for ${opts.type}`)
|
|
28
|
-
}
|
|
21
|
+
// The asset name is globally unique, so the server resolves the type. Whether
|
|
22
|
+
// that type supports preview images is also a server concern: the API checks
|
|
23
|
+
// for a provider `downloadPreviewImage` capability and returns a clear,
|
|
24
|
+
// type-specific error rather than us mirroring a list that can drift.
|
|
29
25
|
|
|
30
26
|
const spinner = ora({
|
|
31
27
|
text: `Resolving preview for ${parsedName}`,
|
|
@@ -40,7 +36,6 @@ export async function previewCommand(
|
|
|
40
36
|
(
|
|
41
37
|
await client.asset.exact({
|
|
42
38
|
name: parsedName,
|
|
43
|
-
type: opts.type,
|
|
44
39
|
includeUnapproved: opts.unapproved ?? false,
|
|
45
40
|
})
|
|
46
41
|
)?.latestVersion
|
package/src/config.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { z } from 'zod'
|
|
|
5
5
|
|
|
6
6
|
const rawConfigSchema = z.object({
|
|
7
7
|
authToken: z.string().min(1).optional(),
|
|
8
|
-
apiKey: z.string().min(1).optional(),
|
|
9
8
|
baseUrl: z.string().url().optional(),
|
|
10
9
|
})
|
|
11
10
|
|
|
@@ -31,8 +30,9 @@ export async function loadConfig(): Promise<Config | null> {
|
|
|
31
30
|
const raw = await fs.readFile(configPath(), 'utf-8')
|
|
32
31
|
const parsed = rawConfigSchema.safeParse(JSON.parse(raw))
|
|
33
32
|
if (!parsed.success) return null
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
return parsed.data.authToken
|
|
34
|
+
? { authToken: parsed.data.authToken, baseUrl: parsed.data.baseUrl }
|
|
35
|
+
: null
|
|
36
36
|
} catch {
|
|
37
37
|
return null
|
|
38
38
|
}
|
|
@@ -41,8 +41,14 @@ export async function loadConfig(): Promise<Config | null> {
|
|
|
41
41
|
export async function saveConfig(config: Config): Promise<void> {
|
|
42
42
|
const dir = configDir()
|
|
43
43
|
await fs.mkdir(dir, { recursive: true })
|
|
44
|
+
// `mode` on writeFile/mkdir only applies when the path is created, so chmod
|
|
45
|
+
// explicitly to protect a pre-existing (possibly world-readable) token file
|
|
46
|
+
// and its directory. A chmod failure means we could not secure the token, so
|
|
47
|
+
// let it surface rather than silently leaving it exposed.
|
|
48
|
+
await fs.chmod(dir, 0o700)
|
|
44
49
|
const file = configPath()
|
|
45
50
|
await fs.writeFile(file, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 })
|
|
51
|
+
await fs.chmod(file, 0o600)
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export async function clearConfig(): Promise<boolean> {
|
package/src/install.ts
CHANGED
|
@@ -147,19 +147,18 @@ async function installNpmDeps(
|
|
|
147
147
|
if (Object.keys(deps).length === 0) return
|
|
148
148
|
|
|
149
149
|
const pkgPath = path.join(projectRoot, 'package.json')
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
150
|
+
// Only synthesize a package.json when none exists. A malformed existing file
|
|
151
|
+
// is a real error that must surface — never silently overwrite it.
|
|
152
|
+
const pkg: PackageJson = (await isFile(pkgPath))
|
|
153
|
+
? (JSON.parse(await fs.readFile(pkgPath, 'utf-8')) as PackageJson)
|
|
154
|
+
: { name: 'my-project', private: true, dependencies: {} }
|
|
156
155
|
|
|
157
156
|
pkg.dependencies = { ...pkg.dependencies, ...deps }
|
|
158
157
|
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
159
158
|
|
|
160
159
|
log(`Installing npm dependencies: ${Object.keys(deps).join(', ')}`)
|
|
161
160
|
|
|
162
|
-
const pm = await detectPackageManager(projectRoot)
|
|
161
|
+
const pm = await detectPackageManager(projectRoot)
|
|
163
162
|
const pmName = pm?.name ?? 'npm'
|
|
164
163
|
|
|
165
164
|
log(`Using ${pmName}...`)
|