@drawcall/market 0.2.3 → 0.4.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.
Files changed (56) hide show
  1. package/README.md +8 -8
  2. package/dist/client.d.ts +4 -9
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +1 -16
  5. package/dist/client.js.map +1 -1
  6. package/dist/commands/upload.d.ts.map +1 -1
  7. package/dist/commands/upload.js +4 -1
  8. package/dist/commands/upload.js.map +1 -1
  9. package/dist/contract.d.ts +0 -170
  10. package/dist/contract.d.ts.map +1 -1
  11. package/dist/contract.js +1 -46
  12. package/dist/contract.js.map +1 -1
  13. package/dist/index.d.ts +3 -5
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +4 -7
  16. package/dist/index.js.map +1 -1
  17. package/dist/install-entry.d.ts +0 -1
  18. package/dist/install-entry.d.ts.map +1 -1
  19. package/dist/install-entry.js +0 -1
  20. package/dist/install-entry.js.map +1 -1
  21. package/dist/install.d.ts +2 -1
  22. package/dist/install.d.ts.map +1 -1
  23. package/dist/install.js +138 -34
  24. package/dist/install.js.map +1 -1
  25. package/dist/output.d.ts +6 -0
  26. package/dist/output.d.ts.map +1 -1
  27. package/dist/output.js +17 -0
  28. package/dist/output.js.map +1 -1
  29. package/dist/pack.d.ts +49 -9
  30. package/dist/pack.d.ts.map +1 -1
  31. package/dist/pack.js +77 -77
  32. package/dist/pack.js.map +1 -1
  33. package/dist/project-walk.d.ts +18 -0
  34. package/dist/project-walk.d.ts.map +1 -0
  35. package/dist/project-walk.js +83 -0
  36. package/dist/project-walk.js.map +1 -0
  37. package/dist/schemas.d.ts +9 -8
  38. package/dist/schemas.d.ts.map +1 -1
  39. package/dist/schemas.js +3 -2
  40. package/dist/schemas.js.map +1 -1
  41. package/dist/v1/contract.d.ts +4 -4
  42. package/dist/v1/contract.d.ts.map +1 -1
  43. package/dist/v1/contract.js +6 -4
  44. package/dist/v1/contract.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/client.ts +8 -28
  47. package/src/commands/upload.ts +3 -1
  48. package/src/contract.ts +0 -80
  49. package/src/index.ts +6 -10
  50. package/src/install-entry.ts +0 -1
  51. package/src/install.ts +199 -42
  52. package/src/output.ts +22 -0
  53. package/src/pack.ts +118 -94
  54. package/src/project-walk.ts +95 -0
  55. package/src/schemas.ts +11 -8
  56. package/src/v1/contract.ts +8 -6
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/v1/contract.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/v1/contract.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawcall/market",
3
- "version": "0.2.3",
3
+ "version": "0.4.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/drawcall-ai/market",
package/src/client.ts CHANGED
@@ -1,32 +1,12 @@
1
- import { createORPCClient } from '@orpc/client'
2
- import { RPCLink } from '@orpc/client/fetch'
3
- import type { ContractRouterClient } from '@orpc/contract'
4
- import type { AppContract } from './contract.js'
5
- import { createClient as createV1Client } from './v1/client.js'
1
+ import {
2
+ createClient as createV1Client,
3
+ type MarketV1Client,
4
+ type MarketV1ClientOptions,
5
+ } from './v1/client.js'
6
6
 
7
- export type MarketClient = ContractRouterClient<AppContract>
8
-
9
- const DEFAULT_BASE_URL = 'https://market.drawcall.ai'
10
-
11
- export interface MarketClientOptions {
12
- baseUrl?: string
13
- fetch?: typeof globalThis.fetch
14
- authToken?: string
15
- }
16
-
17
- function buildHeaders(authToken?: string): Record<string, string> | undefined {
18
- return authToken ? { authorization: `Bearer ${authToken}` } : undefined
19
- }
20
-
21
- export function createMarketClient(opts: MarketClientOptions = {}): MarketClient {
22
- const baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL
23
- const link = new RPCLink({
24
- url: new URL('/api/rpc', baseUrl).href,
25
- fetch: opts.fetch,
26
- headers: buildHeaders(opts.authToken),
27
- })
28
- return createORPCClient<MarketClient>(link)
29
- }
7
+ /** The Market client — the v1 REST client; the RPC wire format is gone. */
8
+ export type MarketClient = MarketV1Client
9
+ export type MarketClientOptions = MarketV1ClientOptions
30
10
 
31
11
  /**
32
12
  * A version's zip as bytes, fetched from the content-plane URL the v1 manifest mints. The stream
@@ -8,7 +8,7 @@ import {
8
8
  packPolicyFromInstallMetadata,
9
9
  parsePackDependencies,
10
10
  } from '../pack.js'
11
- import { unchangedUploadResult, uploadResult } from '../output.js'
11
+ import { packWarnings, unchangedUploadResult, uploadResult } from '../output.js'
12
12
  import {
13
13
  assetDescriptionSchema,
14
14
  assetNameSchema,
@@ -69,6 +69,8 @@ export async function uploadCommand(
69
69
  fetchFileManifest: fileManifestForRange(client.asset),
70
70
  })
71
71
  const { zip, npmDependencies, assetDependencies, skillDependencies } = packed
72
+ // Loud fail-open: what pack could not verify ships in the zip — say so before publishing it.
73
+ for (const line of packWarnings(packed)) console.warn(line)
72
74
  const profile = await client.user.getProfile()
73
75
  if (!profile) {
74
76
  throw new Error('Not logged in. Run `market login` first.')
package/src/contract.ts CHANGED
@@ -1,23 +1,3 @@
1
- import { oc } from '@orpc/contract'
2
- import { z } from 'zod'
3
- import type { AssetFileManifest } from './schemas.js'
4
- import {
5
- MAX_UPLOAD_ZIP_SIZE_BYTES,
6
- agentRunStatusSchema,
7
- agentStartSchema,
8
- agentStatusSchema,
9
- assetFilesSchema,
10
- exactAssetSchema,
11
- fileManifestSchema,
12
- generateAssetSchema,
13
- generateJobStatusSchema,
14
- generateResponseSchema,
15
- generateStatusInputSchema,
16
- listAssetsSchema,
17
- updateProfileSchema,
18
- uploadZipSchema,
19
- } from './schemas.js'
20
-
21
1
  export interface AssetVersion {
22
2
  id: string
23
3
  assetId: string
@@ -128,63 +108,3 @@ export interface AssetInstallMetadata {
128
108
  readAssetDependenciesFromPackageJson: boolean
129
109
  omitUnchangedInstalledFilesOnUpload: boolean
130
110
  }
131
-
132
- export const contract = {
133
- asset: {
134
- search: oc.input(listAssetsSchema).output(z.custom<PaginatedList<AssetSearchResult>>()),
135
-
136
- exact: oc.input(exactAssetSchema).output(z.custom<AssetExactResult | null>()),
137
-
138
- uploadZip: oc
139
- .input(
140
- uploadZipSchema
141
- .extend({ zip: z.instanceof(File) })
142
- .refine((input) => input.zip.size < MAX_UPLOAD_ZIP_SIZE_BYTES, {
143
- message: 'Upload zip must be smaller than 1 GB',
144
- path: ['zip'],
145
- }),
146
- )
147
- .output(z.custom<AssetVersion>()),
148
-
149
- // The derived file index of a version — every stored file's path, size and md5 etag plus the
150
- // fetch-ready data-plane URLs, so installs fetch files in parallel through the same edge cache
151
- // browsers warm.
152
- files: oc.input(assetFilesSchema).output(z.custom<AssetFilesResult>()),
153
-
154
- // The content hashes of a version's files, keyed by install path. Computed once from the stored
155
- // source zip and cached, so a caller (e.g. `pack`) can tell whether a local file is an unchanged
156
- // copy of an installed asset without keeping any hashes itself. Private assets are owner-only,
157
- // same as download.
158
- fileManifest: oc.input(fileManifestSchema).output(z.custom<AssetFileManifest>()),
159
-
160
- // Generation answers immediately: `completed` for fast providers, or `pending` with an
161
- // unguessable Market Workflow id for slow providers. The Workflow polls and finalizes the
162
- // upstream job even when the caller disconnects; `generateStatus` only reads its state.
163
- generate: oc.input(generateAssetSchema).output(generateResponseSchema),
164
-
165
- generateStatus: oc.input(generateStatusInputSchema).output(generateJobStatusSchema),
166
-
167
- installMetadata: oc.output(z.custom<Record<string, AssetInstallMetadata>>()),
168
-
169
- // The agent plans, searches, judges and (where supported) generates a set of
170
- // assets from a goal. `start` kicks off an async run; `status` long-polls it
171
- // (blocks until the run settles or a heartbeat window elapses). The
172
- // `agentAndWait` client helper drives both.
173
- agent: {
174
- start: oc.input(agentStartSchema).output(z.object({ runId: z.string() })),
175
- status: oc.input(agentStatusSchema).output(agentRunStatusSchema),
176
- },
177
- },
178
-
179
- user: {
180
- getProfile: oc.output(z.custom<User | null>()),
181
-
182
- updateProfile: oc.input(updateProfileSchema).output(z.custom<User>()),
183
-
184
- getAuthToken: oc.output(z.custom<{ prefix: string; createdAt: Date } | null>()),
185
-
186
- createAuthToken: oc.output(z.object({ token: z.string(), prefix: z.string() })),
187
- },
188
- }
189
-
190
- export type AppContract = typeof contract
package/src/index.ts CHANGED
@@ -1,15 +1,10 @@
1
- // The v1 REST surface (`/api/v1`): routed contract + typed client. Prefer this; the legacy RPC
2
- // client below is deprecated and retires together with `/api/rpc`.
1
+ // The v1 REST surface (`/api/v1`): routed contract + typed client.
3
2
  export * as v1 from './v1/index.js'
4
3
 
5
- // Client
6
- export { createMarketClient, downloadAssetZipBytes } from './client.js'
4
+ // Client (an alias of the v1 client)
5
+ export { downloadAssetZipBytes } from './client.js'
7
6
  export type { MarketClient, MarketClientOptions } from './client.js'
8
7
 
9
- // Contracts
10
- export { contract } from './contract.js'
11
- export type { AppContract } from './contract.js'
12
-
13
8
  // Contract output types
14
9
  export type {
15
10
  Asset,
@@ -74,7 +69,7 @@ export {
74
69
  skillDependenciesSchema,
75
70
  uploadZipSchema,
76
71
  exactAssetSchema,
77
- fileManifestSchema,
72
+ assetVersionsSchema,
78
73
  generateAssetSchema,
79
74
  updateProfileSchema,
80
75
  listAssetsSchema,
@@ -93,7 +88,8 @@ export type {
93
88
  AssetAccess,
94
89
  AssetDependencies,
95
90
  AssetDependencyValue,
96
- AssetFileManifest,
91
+ AssetVersionListing,
92
+ AssetVersionsResult,
97
93
  AgentResultAsset,
98
94
  AgentResult,
99
95
  AgentRunStatus,
@@ -1,4 +1,3 @@
1
- export { createMarketClient } from './client.js'
2
1
  export type { MarketClient, MarketClientOptions } from './client.js'
3
2
  export { getCliClient, NotLoggedInError } from './cli-client.js'
4
3
  export type { CliClient, CliClientOptions } from './cli-client.js'
package/src/install.ts CHANGED
@@ -11,15 +11,17 @@
11
11
  */
12
12
 
13
13
  import { execFile } from 'child_process'
14
- import { createHash } from 'crypto'
15
14
  import * as fs from 'fs/promises'
16
15
  import * as path from 'path'
17
16
  import { detectPackageManager, installDependencies } from 'nypm'
18
17
  import type { MarketClient } from './client.js'
19
18
  import type { AssetFilesResult, AssetInstallMetadata } from './contract.js'
20
19
  import { withProjectInstallLock } from './install-lock.js'
20
+ import { md5, resolveDependencyLayout } from './pack.js'
21
+ import { findInstallRoot, walkWithGitignore } from './project-walk.js'
21
22
  import { packageJsonAssetDependencies, parsePackageJson, type PackageJson } from './package-json.js'
22
23
  import type { ResolveResult } from './resolve.js'
24
+ export { findInstallRoot }
23
25
  import { assetDependencyAlias, assetDependencyRange, assetDependencyValue } from './schemas.js'
24
26
 
25
27
  export interface InstallOptions {
@@ -72,6 +74,8 @@ interface DownloadResult {
72
74
  assets: InstalledAsset[]
73
75
  wrotePackageJson: boolean
74
76
  warnings: string[]
77
+ /** Renames adopted during this install (per asset: canonical path → adopted project path). */
78
+ adoptedAliases: Record<string, Record<string, string>>
75
79
  }
76
80
 
77
81
  interface DownloadOptions {
@@ -80,6 +84,14 @@ interface DownloadOptions {
80
84
  key?: string
81
85
  /** File aliases per asset name: canonical install path → the project's current path for it. */
82
86
  aliases: Record<string, Record<string, string>>
87
+ /** md5 of every project file, computed at most once per install (rename adoption). */
88
+ projectHashes: () => Promise<Map<string, string>>
89
+ /**
90
+ * Etags across an asset's published versions — the "recognized bytes" set for content-aware
91
+ * updates. `null` when the server cannot answer: fail-open in the conservative direction
92
+ * (never overwrite on uncertainty).
93
+ */
94
+ knownHashes: (name: string) => Promise<Set<string> | null>
83
95
  }
84
96
 
85
97
  export async function install(
@@ -97,6 +109,8 @@ export async function install(
97
109
  log,
98
110
  key: opts.key,
99
111
  aliases,
112
+ projectHashes: memoizedProjectHashes(installRoot),
113
+ knownHashes: memoizedKnownHashes(client),
100
114
  })
101
115
  const integration = await withProjectInstallLock(
102
116
  installRoot,
@@ -105,6 +119,7 @@ export async function install(
105
119
  rootRequests: opts.rootRequests ?? [],
106
120
  installMetadata: metadata,
107
121
  packageManagerNeeded: download.wrotePackageJson,
122
+ adoptedAliases: download.adoptedAliases,
108
123
  })
109
124
  if (packageJsonUpdate.packageManagerNeeded) {
110
125
  await (opts.runPackageManagerInstall ?? runPackageManagerInstall)(installRoot, log)
@@ -137,19 +152,6 @@ export async function install(
137
152
  }
138
153
  }
139
154
 
140
- export async function findInstallRoot(cwd: string = process.cwd()): Promise<string> {
141
- const start = path.resolve(cwd)
142
-
143
- for (let dir = start; ; dir = path.dirname(dir)) {
144
- if (await isFile(path.join(dir, 'package.json'))) {
145
- return dir
146
- }
147
- if (path.dirname(dir) === dir) {
148
- return start
149
- }
150
- }
151
- }
152
-
153
155
  /** Max asset downloads in flight at once. Assets write disjoint file trees, so downloading them
154
156
  * concurrently is safe and turns a ~40-asset install from serial round-trips into a few batched
155
157
  * waves. Kept modest so one install does not itself overwhelm the worker's per-request memory. */
@@ -175,6 +177,11 @@ async function downloadAssets(
175
177
  // the package manager to bring its npm deps.
176
178
  wrotePackageJson: perAsset.some((r) => r.wrotePackageJson),
177
179
  warnings: perAsset.flatMap((r) => r.warnings),
180
+ adoptedAliases: Object.fromEntries(
181
+ perAsset
182
+ .filter((r) => Object.keys(r.adopted).length > 0)
183
+ .map((r) => [r.asset.name, r.adopted]),
184
+ ),
178
185
  }
179
186
  }
180
187
 
@@ -183,53 +190,78 @@ async function downloadOneAsset(
183
190
  asset: ResolveResult['assets'][number],
184
191
  projectRoot: string,
185
192
  opts: DownloadOptions,
186
- ): Promise<{ asset: InstalledAsset; wrotePackageJson: boolean; warnings: string[] }> {
193
+ ): Promise<{
194
+ asset: InstalledAsset
195
+ wrotePackageJson: boolean
196
+ warnings: string[]
197
+ adopted: Record<string, string>
198
+ }> {
187
199
  opts.log(`Downloading ${asset.name}@${asset.version}...`)
188
200
 
189
201
  // Only the network fetches are retried — downloads are the sole transient step; file writes are
190
202
  // deterministic local work and a failure there is a real error, not worth re-attempting.
191
- const files = await fetchAssetEntries(client, asset, opts)
192
- const alias = opts.aliases[asset.name] ?? {}
193
- const installedFiles: string[] = []
194
- const warnings: string[] = []
195
- let wrotePackageJson = false
196
-
197
- for (const [relativePath, content] of files) {
203
+ const entries = await fetchAssetEntries(client, asset, opts)
204
+ const planned = entries.flatMap((entry) => {
198
205
  const normalizedPath = safeRelativePath(
199
- relativePath,
200
- () => `Zip contains an unsafe path: ${relativePath}`,
206
+ entry.path,
207
+ () => `Zip contains an unsafe path: ${entry.path}`,
201
208
  )
202
209
  if (
203
210
  normalizedPath === '.' ||
204
211
  (normalizedPath === 'README.md' && asset.type !== 'template') ||
205
212
  normalizedPath.endsWith('/')
206
213
  ) {
207
- continue
214
+ return []
208
215
  }
216
+ return [{ ...entry, normalizedPath }]
217
+ })
218
+
219
+ // Alias targets come from metadata; reject traversal before any path is stat'd or resolved —
220
+ // adoption must never quietly route around unsafe metadata.
221
+ const declaredAlias = opts.aliases[asset.name] ?? {}
222
+ for (const [canonical, target] of Object.entries(declaredAlias)) {
223
+ safeRelativePath(
224
+ target,
225
+ () => `Alias for ${canonical} in ${asset.name} is an unsafe path: ${target}`,
226
+ )
227
+ }
228
+ const adopted = await adoptRenamedFiles(planned, asset.name, declaredAlias, projectRoot, opts)
229
+ const alias = { ...declaredAlias, ...adopted }
230
+
231
+ const installedFiles: string[] = []
232
+ const warnings: string[] = []
233
+ let wrotePackageJson = false
209
234
 
210
- // An alias redirects the file to where this project keeps it (a rename recorded by pack or a
211
- // dependent asset). Alias targets come from metadata, so they pass the same traversal guard.
212
- const aliased = alias[normalizedPath]
235
+ for (const entry of planned) {
236
+ // An alias redirects the file to where this project keeps it (a rename recorded by pack, a
237
+ // dependent asset, or adoption above). Alias targets pass the same traversal guard.
238
+ const aliased = alias[entry.normalizedPath]
213
239
  const targetPath =
214
240
  aliased === undefined
215
- ? normalizedPath
241
+ ? entry.normalizedPath
216
242
  : safeRelativePath(
217
243
  aliased,
218
- () => `Alias for ${normalizedPath} in ${asset.name} is an unsafe path: ${aliased}`,
244
+ () =>
245
+ `Alias for ${entry.normalizedPath} in ${asset.name} is an unsafe path: ${aliased}`,
219
246
  )
220
247
 
221
248
  const filePath = path.join(projectRoot, targetPath)
222
249
  const existing = await maybeReadFile(filePath)
223
- if (existing && !bytesEqual(existing, content) && !opts.force) {
224
- warnings.push(
225
- `Skipped ${targetPath} from ${asset.name}@${asset.version}; file already exists. Re-run with --force to overwrite.`,
226
- )
227
- continue
250
+ if (existing && !bytesEqual(existing, entry.bytes)) {
251
+ // Recognized bytes — a published version of this same dependency file — are replaceable
252
+ // without --force; unrecognized bytes are the user's and keep the skip-and-warn behavior.
253
+ const known = opts.force ? null : await opts.knownHashes(asset.name)
254
+ if (!opts.force && !known?.has(md5(existing))) {
255
+ warnings.push(
256
+ `Skipped ${targetPath} from ${asset.name}@${asset.version}; file already exists. Re-run with --force to overwrite.`,
257
+ )
258
+ continue
259
+ }
228
260
  }
229
261
 
230
- if (!existing || !bytesEqual(existing, content)) {
262
+ if (!existing || !bytesEqual(existing, entry.bytes)) {
231
263
  await fs.mkdir(path.dirname(filePath), { recursive: true })
232
- await fs.writeFile(filePath, content)
264
+ await fs.writeFile(filePath, entry.bytes)
233
265
  if (targetPath === 'package.json') {
234
266
  wrotePackageJson = true
235
267
  }
@@ -249,6 +281,103 @@ async function downloadOneAsset(
249
281
  },
250
282
  wrotePackageJson,
251
283
  warnings,
284
+ adopted,
285
+ }
286
+ }
287
+
288
+ interface PlannedEntry {
289
+ normalizedPath: string
290
+ etag: string
291
+ bytes: Uint8Array
292
+ }
293
+
294
+ /**
295
+ * Install-time adoption of unsynced renames: when a file's write target is absent but identical
296
+ * bytes already live elsewhere in the project, that location is used instead of creating a
297
+ * duplicate — an implicit `sync` before writing. Uses the same layout resolution as pack/sync
298
+ * (recorded alias → canonical → first match), so the selection rule stays identical everywhere.
299
+ * The project is only hashed when some target is actually absent; a reinstall with every file in
300
+ * place never scans.
301
+ */
302
+ async function adoptRenamedFiles(
303
+ planned: PlannedEntry[],
304
+ name: string,
305
+ declaredAlias: Record<string, string>,
306
+ projectRoot: string,
307
+ opts: DownloadOptions,
308
+ ): Promise<Record<string, string>> {
309
+ const provisionalTargets = planned.map(
310
+ (entry) => declaredAlias[entry.normalizedPath] ?? entry.normalizedPath,
311
+ )
312
+ if (!(await anyPathMissing(projectRoot, provisionalTargets))) return {}
313
+
314
+ const layout = resolveDependencyLayout(
315
+ { [name]: assetDependencyValue('*', declaredAlias) },
316
+ {
317
+ files: planned.map((entry) => ({
318
+ name,
319
+ canonicalPath: entry.normalizedPath,
320
+ hash: entry.etag,
321
+ })),
322
+ fetched: new Set([name]),
323
+ },
324
+ await opts.projectHashes(),
325
+ )
326
+ const resolved = assetDependencyAlias(layout.assetDependencies[name])
327
+ return Object.fromEntries(
328
+ Object.entries(resolved).filter(([canonical, target]) => declaredAlias[canonical] !== target),
329
+ )
330
+ }
331
+
332
+ async function anyPathMissing(projectRoot: string, targets: string[]): Promise<boolean> {
333
+ for (const target of targets) {
334
+ if (!(await isFile(path.join(projectRoot, target)))) return true
335
+ }
336
+ return false
337
+ }
338
+
339
+ /** md5 of every project file (gitignored trees excluded), computed at most once per install. */
340
+ function memoizedProjectHashes(projectRoot: string): () => Promise<Map<string, string>> {
341
+ let cached: Promise<Map<string, string>> | undefined
342
+ return () =>
343
+ (cached ??= (async () => {
344
+ const walk = await walkWithGitignore(projectRoot)
345
+ const hashes = new Map<string, string>()
346
+ for (const relative of walk.kept) {
347
+ hashes.set(
348
+ relative,
349
+ md5(new Uint8Array(await fs.readFile(path.join(projectRoot, relative)))),
350
+ )
351
+ }
352
+ return hashes
353
+ })())
354
+ }
355
+
356
+ /** Newest published versions contributing to the recognized-bytes set (same bound as pack). */
357
+ const KNOWN_HASH_VERSION_CAP = 20
358
+
359
+ function memoizedKnownHashes(client: MarketClient): (name: string) => Promise<Set<string> | null> {
360
+ const cache = new Map<string, Promise<Set<string> | null>>()
361
+ return (name) => {
362
+ const hit = cache.get(name)
363
+ if (hit) return hit
364
+ const result = (async () => {
365
+ try {
366
+ const { versions } = await client.asset.versions({ name })
367
+ const recent = versions.map((entry) => entry.version).slice(-KNOWN_HASH_VERSION_CAP)
368
+ const etags = await Promise.all(
369
+ recent.map(async (version) =>
370
+ (await client.asset.files({ name, version })).files.map((file) => file.etag),
371
+ ),
372
+ )
373
+ return new Set(etags.flat())
374
+ } catch {
375
+ // Offline or not visible — the conservative fail-open: the caller keeps skip-and-warn.
376
+ return null
377
+ }
378
+ })()
379
+ cache.set(name, result)
380
+ return result
252
381
  }
253
382
  }
254
383
 
@@ -304,7 +433,7 @@ async function fetchAssetEntries(
304
433
  client: MarketClient,
305
434
  asset: ResolveResult['assets'][number],
306
435
  opts: DownloadOptions,
307
- ): Promise<Array<[string, Uint8Array]>> {
436
+ ): Promise<Array<{ path: string; etag: string; bytes: Uint8Array }>> {
308
437
  const index = await client.asset.files({
309
438
  name: asset.name,
310
439
  version: asset.version,
@@ -317,7 +446,7 @@ async function fetchAssetEntries(
317
446
  attempts: DOWNLOAD_ATTEMPTS,
318
447
  onRetry: (msg) => opts.log(`Downloading ${label} ${file.path}: ${msg}`),
319
448
  })
320
- return [file.path, bytes] as [string, Uint8Array]
449
+ return { path: file.path, etag: file.etag, bytes }
321
450
  })
322
451
  }
323
452
 
@@ -329,9 +458,9 @@ async function fetchVerifiedFile(file: AssetFilesResult['files'][number]): Promi
329
458
  const bytes = new Uint8Array(await res.arrayBuffer())
330
459
  // A digest mismatch is a corrupt transfer; thrown without a status so withRetry treats it as
331
460
  // transient and re-fetches.
332
- const md5 = createHash('md5').update(bytes).digest('hex')
333
- if (md5 !== file.etag) {
334
- throw new Error(`md5 mismatch for ${file.path}: expected ${file.etag}, got ${md5}`)
461
+ const digest = md5(bytes)
462
+ if (digest !== file.etag) {
463
+ throw new Error(`md5 mismatch for ${file.path}: expected ${file.etag}, got ${digest}`)
335
464
  }
336
465
  return bytes
337
466
  }
@@ -424,6 +553,7 @@ async function updatePackageJson(
424
553
  rootRequests: InstallRootRequest[]
425
554
  installMetadata: Record<string, AssetInstallMetadata>
426
555
  packageManagerNeeded: boolean
556
+ adoptedAliases: Record<string, Record<string, string>>
427
557
  },
428
558
  ): Promise<{ packageManagerNeeded: boolean }> {
429
559
  const pkgPath = path.join(projectRoot, 'package.json')
@@ -448,6 +578,9 @@ async function updatePackageJson(
448
578
  if (mergeAssetDependencyRanges(pkg, assetDependencies)) {
449
579
  changed = true
450
580
  }
581
+ if (mergeAdoptedAliases(pkg, opts.adoptedAliases)) {
582
+ changed = true
583
+ }
451
584
 
452
585
  if (changed) {
453
586
  await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
@@ -456,6 +589,30 @@ async function updatePackageJson(
456
589
  return { packageManagerNeeded }
457
590
  }
458
591
 
592
+ /** Record renames adopted during this install on the entries package.json already declares —
593
+ * adoption is thereby persisted, not just applied. Undeclared (transitive) deps stay out. */
594
+ function mergeAdoptedAliases(
595
+ pkg: PackageJson,
596
+ adopted: Record<string, Record<string, string>>,
597
+ ): boolean {
598
+ const current = pkg.assetDependencies
599
+ if (!current) return false
600
+
601
+ let changed = false
602
+ for (const [name, alias] of Object.entries(adopted)) {
603
+ const existing = current[name]
604
+ if (existing === undefined) continue
605
+ const next = assetDependencyValue(assetDependencyRange(existing), {
606
+ ...assetDependencyAlias(existing),
607
+ ...alias,
608
+ })
609
+ if (JSON.stringify(existing) === JSON.stringify(next)) continue
610
+ current[name] = next
611
+ changed = true
612
+ }
613
+ return changed
614
+ }
615
+
459
616
  async function runPackageManagerInstall(projectRoot: string, log: (msg: string) => void) {
460
617
  log('Installing npm dependencies...')
461
618
  const pm = await detectPackageManager(projectRoot)
package/src/output.ts CHANGED
@@ -219,12 +219,34 @@ export function packResult(out: string, packed: PackedAsset): string {
219
219
  if (packed.omittedUnchangedInstalledFiles) {
220
220
  lines.push('Omitted unchanged installed dependency files.')
221
221
  }
222
+ lines.push(...packWarnings(packed))
222
223
  lines.push(...dependencyLines('npm dependencies', packed.npmDependencies))
223
224
  lines.push(...dependencyLines('asset dependencies', packed.assetDependencies))
224
225
  lines.push(...dependencyLines('skill dependencies', packed.skillDependencies))
225
226
  return lines.join('\n')
226
227
  }
227
228
 
229
+ /**
230
+ * What pack could not check — the silent-fail-open cases with real consequences: an unfetchable
231
+ * dependency ships its files inside the zip, and a missing dependency file means a local edit or
232
+ * deletion the packer should know about.
233
+ */
234
+ export function packWarnings(packed: PackedAsset): string[] {
235
+ const lines: string[] = []
236
+ if (packed.skippedDependencies.length > 0) {
237
+ lines.push(
238
+ `Dependency file indexes unavailable (their files ship in the zip): ${[...packed.skippedDependencies].sort().join(', ')}`,
239
+ )
240
+ }
241
+ if (packed.missingDependencyFiles.length > 0) {
242
+ lines.push('Dependency files not found (locally modified or deleted):')
243
+ lines.push(
244
+ ...packed.missingDependencyFiles.map((file) => `- ${file.name}: ${file.path}`).sort(),
245
+ )
246
+ }
247
+ return lines
248
+ }
249
+
228
250
  export function previewResult(name: string, version: string, out: string): string {
229
251
  return `Saved preview for ${assetVersionRef(name, version)}: ${out}`
230
252
  }