pixelkiln 0.2.0 → 0.3.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/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import path16 from "path";
5
- import { existsSync as existsSync14 } from "fs";
6
- import { readFile as readFile13 } from "fs/promises";
4
+ import path18 from "path";
5
+ import { existsSync as existsSync15 } from "fs";
6
+ import { readFile as readFile14 } from "fs/promises";
7
7
 
8
8
  // src/env.ts
9
9
  import { readFileSync, existsSync } from "fs";
@@ -154,11 +154,11 @@ var PixelLabClient = class {
154
154
  * common than the failure mode of retrying (a duplicate object), and a
155
155
  * duplicate is visible and free to delete whereas a silent gap is neither.
156
156
  */
157
- async request(path17, init, attempt = 0) {
157
+ async request(path19, init, attempt = 0) {
158
158
  const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
159
159
  let res;
160
160
  try {
161
- res = await fetch(`${BASE}${path17}`, {
161
+ res = await fetch(`${BASE}${path19}`, {
162
162
  ...init,
163
163
  signal: init?.signal ?? AbortSignal.timeout(this.timeoutMs),
164
164
  headers: {
@@ -170,24 +170,24 @@ var PixelLabClient = class {
170
170
  } catch (err) {
171
171
  if (attempt < MAX_RETRIES) {
172
172
  await sleep(backoffMs(attempt));
173
- return this.request(path17, init, attempt + 1);
173
+ return this.request(path19, init, attempt + 1);
174
174
  }
175
175
  throw err;
176
176
  }
177
177
  if (!res.ok && shouldRetry(res.status) && attempt < MAX_RETRIES) {
178
178
  const waitMs = retryAfterMs(res.headers.get("retry-after")) ?? backoffMs(attempt);
179
179
  await sleep(waitMs);
180
- return this.request(path17, init, attempt + 1);
180
+ return this.request(path19, init, attempt + 1);
181
181
  }
182
182
  const text = await res.text();
183
183
  if (!res.ok) {
184
- throw new PixelLabError(`${init?.method ?? "GET"} ${path17} \u2192 ${res.status}`, res.status, text);
184
+ throw new PixelLabError(`${init?.method ?? "GET"} ${path19} \u2192 ${res.status}`, res.status, text);
185
185
  }
186
186
  if (!text) return {};
187
187
  try {
188
188
  return JSON.parse(text);
189
189
  } catch {
190
- throw new Error(`${init?.method ?? "GET"} ${path17} returned invalid JSON`);
190
+ throw new Error(`${init?.method ?? "GET"} ${path19} returned invalid JSON`);
191
191
  }
192
192
  }
193
193
  async balance() {
@@ -1353,8 +1353,8 @@ import { readFile } from "fs/promises";
1353
1353
  function sha256(data) {
1354
1354
  return createHash("sha256").update(data).digest("hex");
1355
1355
  }
1356
- async function sha256File(path17) {
1357
- return sha256(await readFile(path17));
1356
+ async function sha256File(path19) {
1357
+ return sha256(await readFile(path19));
1358
1358
  }
1359
1359
  function specHash(spec, styleImageHashes) {
1360
1360
  return sha256(
@@ -2715,10 +2715,10 @@ function isSha256Hash(value) {
2715
2715
  function parseCache(value) {
2716
2716
  return HashCacheSchema.parse(value);
2717
2717
  }
2718
- async function loadCache(path17) {
2719
- if (!existsSync8(path17)) return { version: 1, hashes: {} };
2718
+ async function loadCache(path19) {
2719
+ if (!existsSync8(path19)) return { version: 1, hashes: {} };
2720
2720
  try {
2721
- const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile5(path17, "utf8")));
2721
+ const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile5(path19, "utf8")));
2722
2722
  if (!parsed.success) return { version: 1, hashes: {} };
2723
2723
  return {
2724
2724
  version: 1,
@@ -2730,18 +2730,18 @@ async function loadCache(path17) {
2730
2730
  return { version: 1, hashes: {} };
2731
2731
  }
2732
2732
  }
2733
- async function saveCache(path17, cache) {
2733
+ async function saveCache(path19, cache) {
2734
2734
  const sorted = {};
2735
2735
  for (const key of Object.keys(cache.hashes).sort()) {
2736
2736
  const hash = cache.hashes[key];
2737
2737
  if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
2738
2738
  sorted[key] = hash;
2739
2739
  }
2740
- await mkdir3(pathModule.dirname(pathModule.resolve(path17)), { recursive: true });
2741
- const tmp = `${path17}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
2740
+ await mkdir3(pathModule.dirname(pathModule.resolve(path19)), { recursive: true });
2741
+ const tmp = `${path19}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
2742
2742
  try {
2743
2743
  await writeFile3(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
2744
- await rename3(tmp, path17);
2744
+ await rename3(tmp, path19);
2745
2745
  } finally {
2746
2746
  await rm3(tmp, { force: true });
2747
2747
  }
@@ -3382,6 +3382,7 @@ async function writeManifestFile(target, manifest) {
3382
3382
  // src/pipeline/salvage.ts
3383
3383
  import { readFile as readFile8 } from "fs/promises";
3384
3384
  import { existsSync as existsSync11 } from "fs";
3385
+ import path12 from "path";
3385
3386
  async function loadClaims(lockPaths) {
3386
3387
  const claimed = /* @__PURE__ */ new Set();
3387
3388
  for (const p of lockPaths) {
@@ -3429,6 +3430,26 @@ function matchStyleByPattern(prompt, manifest) {
3429
3430
  }
3430
3431
  return null;
3431
3432
  }
3433
+ async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
3434
+ const own = path12.resolve(ownManifestPath);
3435
+ const siblingManifestPaths = [
3436
+ .../* @__PURE__ */ new Set([
3437
+ ...workspaceManifestPaths,
3438
+ ...claimPaths.map((c) => path12.join(path12.dirname(path12.resolve(c)), "pixelkiln.manifest.json"))
3439
+ ])
3440
+ ];
3441
+ const siblings = [];
3442
+ for (const siblingManifestPath of siblingManifestPaths) {
3443
+ if (path12.resolve(siblingManifestPath) === own) continue;
3444
+ if (!existsSync11(siblingManifestPath)) continue;
3445
+ try {
3446
+ const { manifest } = await loadManifest(siblingManifestPath);
3447
+ siblings.push({ label: path12.basename(path12.dirname(siblingManifestPath)), manifest });
3448
+ } catch {
3449
+ }
3450
+ }
3451
+ return siblings;
3452
+ }
3432
3453
  function groupOrphansByStyle(orphans, manifest, siblings = []) {
3433
3454
  const matched = /* @__PURE__ */ new Map();
3434
3455
  const elsewhere = /* @__PURE__ */ new Map();
@@ -3538,10 +3559,237 @@ async function applyTags(provider, decisions, existing, opts = {}) {
3538
3559
  return { tagged, failed };
3539
3560
  }
3540
3561
 
3541
- // src/pipeline/audit.ts
3542
- import { readFile as readFile9 } from "fs/promises";
3562
+ // src/workspace.ts
3563
+ import { mkdir as mkdir4, readFile as readFile9, rename as rename4, rm as rm4, writeFile as writeFile6 } from "fs/promises";
3543
3564
  import { existsSync as existsSync12 } from "fs";
3544
- import path12 from "path";
3565
+ import path13 from "path";
3566
+ import { z as z4 } from "zod";
3567
+ var WorkspaceProjectSchema = z4.object({
3568
+ id: z4.string().min(1),
3569
+ /** Manifest path, relative to the catalog file's own directory. */
3570
+ manifest: z4.string().min(1),
3571
+ /** Lockfile path, relative to the catalog file's own directory. */
3572
+ lock: z4.string().min(1),
3573
+ provider: z4.string().min(1).default("pixellab"),
3574
+ /** Free-form label for a shared account, e.g. distinguishing sandboxes. */
3575
+ account: z4.string().optional()
3576
+ }).strict();
3577
+ var WorkspaceSchema = z4.object({
3578
+ version: z4.literal(1),
3579
+ projects: z4.array(WorkspaceProjectSchema).default([])
3580
+ }).strict();
3581
+ function parseWorkspace(raw) {
3582
+ const parsed = WorkspaceSchema.safeParse(raw);
3583
+ if (parsed.success) return parsed.data;
3584
+ throw new Error(
3585
+ `Workspace catalog is not valid v1:
3586
+ ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
3587
+ );
3588
+ }
3589
+ async function loadWorkspace(workspacePath) {
3590
+ if (!existsSync12(workspacePath)) return { version: 1, projects: [] };
3591
+ let raw;
3592
+ try {
3593
+ raw = JSON.parse(await readFile9(workspacePath, "utf8"));
3594
+ } catch (err) {
3595
+ throw new Error(
3596
+ `Workspace catalog at ${workspacePath} is malformed:
3597
+ ${err instanceof Error ? err.message : String(err)}`
3598
+ );
3599
+ }
3600
+ return parseWorkspace(raw);
3601
+ }
3602
+ async function saveWorkspace(workspacePath, ws) {
3603
+ const sorted = {
3604
+ version: 1,
3605
+ projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
3606
+ };
3607
+ await mkdir4(path13.dirname(path13.resolve(workspacePath)), { recursive: true });
3608
+ const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
3609
+ try {
3610
+ await writeFile6(tmp, JSON.stringify(sorted, null, 2) + "\n");
3611
+ await rename4(tmp, workspacePath);
3612
+ } finally {
3613
+ await rm4(tmp, { force: true });
3614
+ }
3615
+ }
3616
+ function toPortablePath(dir, absolute) {
3617
+ return path13.relative(dir, absolute).split(path13.sep).join("/");
3618
+ }
3619
+ function resolveProject(dir, project) {
3620
+ return {
3621
+ manifestPath: path13.resolve(dir, project.manifest.split("/").join(path13.sep)),
3622
+ lockPath: path13.resolve(dir, project.lock.split("/").join(path13.sep))
3623
+ };
3624
+ }
3625
+ function validateWorkspace(ws, dir) {
3626
+ const diagnostics = [];
3627
+ const idCounts = /* @__PURE__ */ new Map();
3628
+ const lockOwners = /* @__PURE__ */ new Map();
3629
+ const manifestOwners = /* @__PURE__ */ new Map();
3630
+ for (const project of ws.projects) {
3631
+ idCounts.set(project.id, (idCounts.get(project.id) ?? 0) + 1);
3632
+ const { manifestPath, lockPath } = resolveProject(dir, project);
3633
+ lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
3634
+ manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
3635
+ if (path13.isAbsolute(project.manifest) || path13.isAbsolute(project.lock)) {
3636
+ diagnostics.push({
3637
+ id: "absolute-path",
3638
+ level: "warning",
3639
+ message: `project "${project.id}" stores an absolute path \u2014 the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
3640
+ });
3641
+ }
3642
+ if (!existsSync12(manifestPath)) {
3643
+ diagnostics.push({
3644
+ id: "missing-manifest",
3645
+ level: "error",
3646
+ message: `project "${project.id}" manifest not found: ${manifestPath}`
3647
+ });
3648
+ }
3649
+ if (!existsSync12(lockPath)) {
3650
+ diagnostics.push({
3651
+ id: "missing-lock",
3652
+ level: "error",
3653
+ message: `project "${project.id}" lockfile not found: ${lockPath}`
3654
+ });
3655
+ }
3656
+ }
3657
+ for (const [id, count] of idCounts) {
3658
+ if (count > 1) {
3659
+ diagnostics.push({
3660
+ id: "duplicate-id",
3661
+ level: "error",
3662
+ message: `project id "${id}" is registered ${count} times`
3663
+ });
3664
+ }
3665
+ }
3666
+ for (const [lockPath, ids] of lockOwners) {
3667
+ if (ids.length > 1) {
3668
+ diagnostics.push({
3669
+ id: "duplicate-lock",
3670
+ level: "error",
3671
+ message: `${ids.join(", ")} all register the same lockfile: ${lockPath}`
3672
+ });
3673
+ }
3674
+ }
3675
+ for (const [manifestPath, ids] of manifestOwners) {
3676
+ if (ids.length > 1) {
3677
+ diagnostics.push({
3678
+ id: "duplicate-manifest",
3679
+ level: "warning",
3680
+ message: `${ids.join(", ")} share manifest ${manifestPath} \u2014 expected only when they are variant lockfiles beside one manifest`
3681
+ });
3682
+ }
3683
+ }
3684
+ const providers = new Set(ws.projects.map((p) => p.provider));
3685
+ if (providers.size > 1) {
3686
+ diagnostics.push({
3687
+ id: "mixed-provider",
3688
+ level: "warning",
3689
+ message: `registered projects use different providers: ${[...providers].sort().join(", ")} \u2014 spend totals are kept separate per unit, but confirm this is intentional`
3690
+ });
3691
+ }
3692
+ return diagnostics;
3693
+ }
3694
+
3695
+ // src/pipeline/workspace.ts
3696
+ async function workspaceClaims(ws, dir) {
3697
+ const lockPaths = [];
3698
+ const byProject = {};
3699
+ const claimed = /* @__PURE__ */ new Set();
3700
+ for (const project of ws.projects) {
3701
+ const { lockPath } = resolveProject(dir, project);
3702
+ lockPaths.push(lockPath);
3703
+ let projectClaims;
3704
+ try {
3705
+ projectClaims = await loadClaims([lockPath]);
3706
+ } catch (err) {
3707
+ throw new Error(
3708
+ `Project "${project.id}" lockfile is unreadable: ${err instanceof Error ? err.message : String(err)}`
3709
+ );
3710
+ }
3711
+ byProject[project.id] = projectClaims.size;
3712
+ for (const id of projectClaims) claimed.add(id);
3713
+ }
3714
+ return { claimed, byProject, lockPaths };
3715
+ }
3716
+ function emptyStateCounts() {
3717
+ return {
3718
+ ok: 0,
3719
+ missing: 0,
3720
+ untracked: 0,
3721
+ stale: 0,
3722
+ orphaned: 0,
3723
+ "in-flight": 0,
3724
+ recoverable: 0,
3725
+ failed: 0
3726
+ };
3727
+ }
3728
+ async function workspaceStatus(ws, dir) {
3729
+ const diagnostics = validateWorkspace(ws, dir);
3730
+ const provider = PixelLabProvider.forOffline();
3731
+ const projects = [];
3732
+ const totalsByState = emptyStateCounts();
3733
+ const totalsSpend = { generations: 0, usd: 0, free: 0 };
3734
+ for (const project of ws.projects) {
3735
+ const { manifestPath, lockPath } = resolveProject(dir, project);
3736
+ const base = {
3737
+ id: project.id,
3738
+ provider: project.provider,
3739
+ account: project.account ?? null,
3740
+ manifest: manifestPath,
3741
+ lock: lockPath
3742
+ };
3743
+ try {
3744
+ const loaded = await loadManifest(manifestPath);
3745
+ const specs = await resolveSpecs(loaded, { provider });
3746
+ const lock = await loadLock(lockPath);
3747
+ normalizeLockOutputPaths(lock, specs);
3748
+ const plan = await buildPlan(specs, lock);
3749
+ const byState = summarize(plan);
3750
+ const spend = spendByUnit(lock);
3751
+ for (const state of Object.keys(byState)) {
3752
+ totalsByState[state] += byState[state];
3753
+ }
3754
+ for (const unit of Object.keys(spend)) {
3755
+ totalsSpend[unit] += spend[unit];
3756
+ }
3757
+ projects.push({
3758
+ ...base,
3759
+ entries: Object.keys(lock.entries).length,
3760
+ byState,
3761
+ spendByUnit: spend,
3762
+ error: null
3763
+ });
3764
+ } catch (err) {
3765
+ projects.push({
3766
+ ...base,
3767
+ entries: 0,
3768
+ byState: emptyStateCounts(),
3769
+ spendByUnit: { generations: 0, usd: 0, free: 0 },
3770
+ error: err instanceof Error ? err.message : String(err)
3771
+ });
3772
+ }
3773
+ }
3774
+ let claims = 0;
3775
+ try {
3776
+ claims = (await workspaceClaims(ws, dir)).claimed.size;
3777
+ } catch {
3778
+ }
3779
+ return {
3780
+ version: 1,
3781
+ safe: !diagnostics.some((d) => d.level === "error") && projects.every((p) => !p.error),
3782
+ dir,
3783
+ projects,
3784
+ totals: { byState: totalsByState, spendByUnit: totalsSpend, claims },
3785
+ diagnostics
3786
+ };
3787
+ }
3788
+
3789
+ // src/pipeline/audit.ts
3790
+ import { readFile as readFile10 } from "fs/promises";
3791
+ import { existsSync as existsSync13 } from "fs";
3792
+ import path14 from "path";
3545
3793
  function colorDistance(a, b) {
3546
3794
  const rmean = (a.r + b.r) / 2;
3547
3795
  const dr = a.r - b.r;
@@ -3586,12 +3834,12 @@ async function auditStyle(loaded, specs, styleId, lock) {
3586
3834
  const unreadable = [];
3587
3835
  for (const spec of mine) {
3588
3836
  for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
3589
- if (!existsSync12(output.absolutePath)) {
3837
+ if (!existsSync13(output.absolutePath)) {
3590
3838
  missing.push(output.id);
3591
3839
  continue;
3592
3840
  }
3593
3841
  try {
3594
- const png = decodePng(await readFile9(output.absolutePath));
3842
+ const png = decodePng(await readFile10(output.absolutePath));
3595
3843
  const palette = extractPalette(png, 12);
3596
3844
  assets.push({
3597
3845
  assetId: spec.assetId,
@@ -3615,10 +3863,10 @@ async function auditStyle(loaded, specs, styleId, lock) {
3615
3863
  let referenceFromStyleImages = false;
3616
3864
  const refPalettes = [];
3617
3865
  for (const img of style.styleImages) {
3618
- const abs = path12.resolve(loaded.root, img.path);
3619
- if (!existsSync12(abs)) continue;
3866
+ const abs = path14.resolve(loaded.root, img.path);
3867
+ if (!existsSync13(abs)) continue;
3620
3868
  try {
3621
- refPalettes.push(extractPalette(decodePng(await readFile9(abs)), 12));
3869
+ refPalettes.push(extractPalette(decodePng(await readFile10(abs)), 12));
3622
3870
  } catch {
3623
3871
  }
3624
3872
  }
@@ -3694,16 +3942,16 @@ function hex(c) {
3694
3942
  }
3695
3943
 
3696
3944
  // src/pipeline/cache-health.ts
3697
- import { existsSync as existsSync13 } from "fs";
3698
- import { readFile as readFile10, readdir as readdir2, rm as rm4 } from "fs/promises";
3699
- import path13 from "path";
3945
+ import { existsSync as existsSync14 } from "fs";
3946
+ import { readFile as readFile11, readdir as readdir2, rm as rm5 } from "fs/promises";
3947
+ import path15 from "path";
3700
3948
  var PNG_SIGNATURE2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
3701
3949
  async function inspectCaches(lock, lockPath, options = {}) {
3702
- if (options.prune && !existsSync13(lockPath)) {
3703
- throw new Error(`Refusing to prune without an existing lockfile at ${path13.resolve(lockPath)}`);
3950
+ if (options.prune && !existsSync14(lockPath)) {
3951
+ throw new Error(`Refusing to prune without an existing lockfile at ${path15.resolve(lockPath)}`);
3704
3952
  }
3705
- const contentDir = path13.resolve(path13.dirname(lockPath), ".pixelkiln", "cache");
3706
- const remotePath = path13.resolve(cachePathFor(lockPath));
3953
+ const contentDir = path15.resolve(path15.dirname(lockPath), ".pixelkiln", "cache");
3954
+ const remotePath = path15.resolve(cachePathFor(lockPath));
3707
3955
  const referenced = new Set(
3708
3956
  Object.values(lock.entries).flatMap((entry) => entry.outputs.map((output) => output.sha256))
3709
3957
  );
@@ -3717,7 +3965,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
3717
3965
  ]);
3718
3966
  for (const name of names) {
3719
3967
  try {
3720
- await rm4(path13.join(contentDir, name), { force: true });
3968
+ await rm5(path15.join(contentDir, name), { force: true });
3721
3969
  removed.contentFiles++;
3722
3970
  } catch {
3723
3971
  }
@@ -3726,7 +3974,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
3726
3974
  await saveCache(remotePath, { version: 1, hashes: {} });
3727
3975
  removed.resetRemoteHashCache = true;
3728
3976
  } else if (remoteHashes.invalidIds.length) {
3729
- const cache = parseCache(JSON.parse(await readFile10(remotePath, "utf8")));
3977
+ const cache = parseCache(JSON.parse(await readFile11(remotePath, "utf8")));
3730
3978
  for (const id of remoteHashes.invalidIds) delete cache.hashes[id];
3731
3979
  removed.remoteHashEntries = remoteHashes.invalidIds.length;
3732
3980
  await saveCache(remotePath, cache);
@@ -3744,7 +3992,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
3744
3992
  async function inspectContentCache(contentDir, referenced) {
3745
3993
  const report = {
3746
3994
  path: contentDir,
3747
- exists: existsSync13(contentDir),
3995
+ exists: existsSync14(contentDir),
3748
3996
  files: 0,
3749
3997
  bytes: 0,
3750
3998
  valid: 0,
@@ -3765,11 +4013,11 @@ async function inspectContentCache(contentDir, referenced) {
3765
4013
  continue;
3766
4014
  }
3767
4015
  report.files++;
3768
- const file = path13.join(contentDir, entry.name);
4016
+ const file = path15.join(contentDir, entry.name);
3769
4017
  const expected = entry.name.endsWith(".png") ? entry.name.slice(0, -4) : "";
3770
4018
  let bytes;
3771
4019
  try {
3772
- bytes = await readFile10(file);
4020
+ bytes = await readFile11(file);
3773
4021
  report.bytes += bytes.length;
3774
4022
  } catch (err) {
3775
4023
  report.invalid.push({
@@ -3811,7 +4059,7 @@ async function inspectContentCache(contentDir, referenced) {
3811
4059
  async function inspectRemoteHashCache(remotePath) {
3812
4060
  const report = {
3813
4061
  path: remotePath,
3814
- exists: existsSync13(remotePath),
4062
+ exists: existsSync14(remotePath),
3815
4063
  entries: 0,
3816
4064
  valid: 0,
3817
4065
  invalidIds: [],
@@ -3820,7 +4068,7 @@ async function inspectRemoteHashCache(remotePath) {
3820
4068
  if (!report.exists) return report;
3821
4069
  let cache;
3822
4070
  try {
3823
- cache = parseCache(JSON.parse(await readFile10(remotePath, "utf8")));
4071
+ cache = parseCache(JSON.parse(await readFile11(remotePath, "utf8")));
3824
4072
  } catch (err) {
3825
4073
  report.error = err instanceof Error ? err.message : String(err);
3826
4074
  return report;
@@ -4091,8 +4339,8 @@ function isRecord(value) {
4091
4339
  }
4092
4340
 
4093
4341
  // src/pick/salvage-server.ts
4094
- import { mkdir as mkdir4, writeFile as writeFile6, readFile as readFile11 } from "fs/promises";
4095
- import path14 from "path";
4342
+ import { mkdir as mkdir5, writeFile as writeFile7, readFile as readFile12 } from "fs/promises";
4343
+ import path16 from "path";
4096
4344
 
4097
4345
  // src/pick/salvage-sheet.ts
4098
4346
  var escapeHtml2 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
@@ -4257,7 +4505,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
4257
4505
  });
4258
4506
  const html = renderSalvageSheet(orphans, {
4259
4507
  styleId: ctx.styleId,
4260
- importDir: path14.relative(process.cwd(), ctx.importDir) || "."
4508
+ importDir: path16.relative(process.cwd(), ctx.importDir) || "."
4261
4509
  });
4262
4510
  const byId = new Map(orphans.map((o) => [o.id, o]));
4263
4511
  const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
@@ -4286,10 +4534,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
4286
4534
  if (!buf.subarray(0, 8).equals(PNG_SIGNATURE3)) throw new Error("not a PNG");
4287
4535
  decodePng(buf);
4288
4536
  const assetId = idFromPrompt(orphan.prompt, taken);
4289
- const rel = path14.join("_salvaged", `${assetId}.png`);
4290
- const outFile = path14.resolve(ctx.importDir, rel);
4291
- await mkdir4(path14.dirname(outFile), { recursive: true });
4292
- await writeFile6(outFile, buf);
4537
+ const rel = path16.join("_salvaged", `${assetId}.png`);
4538
+ const outFile = path16.resolve(ctx.importDir, rel);
4539
+ await mkdir5(path16.dirname(outFile), { recursive: true });
4540
+ await writeFile7(outFile, buf);
4293
4541
  ctx.manifest.assets[assetId] = {
4294
4542
  prompt: orphan.prompt,
4295
4543
  promptByStyle: {},
@@ -4315,7 +4563,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
4315
4563
  error: null,
4316
4564
  sourceUrl: orphan.previewUrl,
4317
4565
  outputs: [{
4318
- path: portableOutputPath(outFile, path14.dirname(ctx.manifestPath)),
4566
+ path: portableOutputPath(outFile, path16.dirname(ctx.manifestPath)),
4319
4567
  sha256: sha256(buf)
4320
4568
  }],
4321
4569
  submittedAt: orphan.createdAt,
@@ -4339,9 +4587,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
4339
4587
  }
4340
4588
  }
4341
4589
  await applyTags(provider, decisions, existingTags, { onProgress: log2 });
4342
- const raw = JSON.parse(await readFile11(ctx.manifestPath, "utf8"));
4590
+ const raw = JSON.parse(await readFile12(ctx.manifestPath, "utf8"));
4343
4591
  for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
4344
- await writeFile6(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
4592
+ await writeFile7(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
4345
4593
  await saveLock(ctx.lockPath, ctx.lock);
4346
4594
  return result;
4347
4595
  }
@@ -4349,9 +4597,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
4349
4597
  }
4350
4598
 
4351
4599
  // src/artifacts.ts
4352
- import path15 from "path";
4600
+ import path17 from "path";
4353
4601
  import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
4354
- import { mkdir as mkdir5, readFile as readFile12, rename as rename4, rm as rm5, writeFile as writeFile7 } from "fs/promises";
4602
+ import { mkdir as mkdir6, readFile as readFile13, rename as rename5, rm as rm6, writeFile as writeFile8 } from "fs/promises";
4355
4603
  var activeTransactions = /* @__PURE__ */ new Set();
4356
4604
  function message(error) {
4357
4605
  return error instanceof Error ? error.message : String(error);
@@ -4360,7 +4608,7 @@ function digest(data) {
4360
4608
  return createHash2("sha256").update(data).digest("hex");
4361
4609
  }
4362
4610
  function portableRelative(from, to) {
4363
- return path15.relative(from, path15.resolve(to)).split(path15.sep).join("/") || ".";
4611
+ return path17.relative(from, path17.resolve(to)).split(path17.sep).join("/") || ".";
4364
4612
  }
4365
4613
  function canonical(value) {
4366
4614
  if (value === null || typeof value === "string" || typeof value === "boolean") return value;
@@ -4411,7 +4659,7 @@ function parseArtifactManifest(absolute, data) {
4411
4659
  }
4412
4660
  async function readOptional(file) {
4413
4661
  try {
4414
- return await readFile12(file);
4662
+ return await readFile13(file);
4415
4663
  } catch (error) {
4416
4664
  if (isCode(error, "ENOENT")) return null;
4417
4665
  throw error;
@@ -4430,8 +4678,8 @@ function processIsAlive(pid) {
4430
4678
  }
4431
4679
  }
4432
4680
  function validTemporaryPath(candidate, destination, type) {
4433
- return path15.dirname(candidate) === path15.dirname(destination) && path15.basename(candidate).startsWith(
4434
- `.${path15.basename(destination)}.pixelkiln-${type}-`
4681
+ return path17.dirname(candidate) === path17.dirname(destination) && path17.basename(candidate).startsWith(
4682
+ `.${path17.basename(destination)}.pixelkiln-${type}-`
4435
4683
  );
4436
4684
  }
4437
4685
  function parseTransaction(journal, data) {
@@ -4447,14 +4695,14 @@ function parseTransaction(journal, data) {
4447
4695
  return raw;
4448
4696
  }
4449
4697
  async function removeTransactionFiles(journal) {
4450
- await rm5(journal, { force: true });
4451
- await rm5(transactionMarker(journal), { force: true });
4698
+ await rm6(journal, { force: true });
4699
+ await rm6(transactionMarker(journal), { force: true });
4452
4700
  }
4453
4701
  async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
4454
- const journal = path15.resolve(recoveryFile);
4702
+ const journal = path17.resolve(recoveryFile);
4455
4703
  const bytes = await readOptional(journal);
4456
4704
  if (!bytes) {
4457
- await rm5(transactionMarker(journal), { force: true });
4705
+ await rm6(transactionMarker(journal), { force: true });
4458
4706
  return;
4459
4707
  }
4460
4708
  if (activeTransactions.has(journal)) {
@@ -4467,7 +4715,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
4467
4715
  );
4468
4716
  }
4469
4717
  for (const entry of transaction.entries) {
4470
- if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(path15.resolve(entry.destination)) || !validTemporaryPath(entry.stage, entry.destination, "stage") || entry.backup !== void 0 && !validTemporaryPath(entry.backup, entry.destination, "backup")) {
4718
+ if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(path17.resolve(entry.destination)) || !validTemporaryPath(entry.stage, entry.destination, "stage") || entry.backup !== void 0 && !validTemporaryPath(entry.backup, entry.destination, "backup")) {
4471
4719
  throw new Error(
4472
4720
  `Refusing unsafe artifact recovery from ${journal}; its destinations or temporary paths do not match the current bundle.`
4473
4721
  );
@@ -4493,7 +4741,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
4493
4741
  }
4494
4742
  await remove2(entry.destination, errors);
4495
4743
  try {
4496
- await rename4(entry.backup, entry.destination);
4744
+ await rename5(entry.backup, entry.destination);
4497
4745
  } catch (error) {
4498
4746
  errors.push(
4499
4747
  `could not restore ${entry.destination}; previous file remains at ${entry.backup}: ${message(error)}`
@@ -4513,7 +4761,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
4513
4761
  await removeTransactionFiles(journal);
4514
4762
  }
4515
4763
  function createArtifactBundleManifest(manifestPath, outputs, provenance) {
4516
- const root = path15.dirname(path15.resolve(manifestPath));
4764
+ const root = path17.dirname(path17.resolve(manifestPath));
4517
4765
  const sources = provenance.sources.map((source) => ({
4518
4766
  ...source,
4519
4767
  path: portableRelative(root, source.path)
@@ -4543,11 +4791,11 @@ function withArtifactManifest(manifestPath, outputs, provenance) {
4543
4791
  ];
4544
4792
  }
4545
4793
  async function writeManagedArtifactBundle(manifestPath, outputs, provenance, options = {}) {
4546
- const absoluteManifest = path15.resolve(manifestPath);
4794
+ const absoluteManifest = path17.resolve(manifestPath);
4547
4795
  const recoveryFile = `${absoluteManifest}.transaction`;
4548
4796
  const allowedDestinations = /* @__PURE__ */ new Set([
4549
4797
  absoluteManifest,
4550
- ...outputs.map((output) => path15.resolve(output.path))
4798
+ ...outputs.map((output) => path17.resolve(output.path))
4551
4799
  ]);
4552
4800
  await recoverArtifactTransaction(recoveryFile, allowedDestinations);
4553
4801
  const existingManifestBytes = await readOptional(absoluteManifest);
@@ -4565,13 +4813,13 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
4565
4813
  }
4566
4814
  }
4567
4815
  if (!options.force) {
4568
- const root = path15.dirname(absoluteManifest);
4816
+ const root = path17.dirname(absoluteManifest);
4569
4817
  const recorded = new Map(
4570
- previous?.outputs.map((output) => [path15.resolve(root, output.path), output.sha256]) ?? []
4818
+ previous?.outputs.map((output) => [path17.resolve(root, output.path), output.sha256]) ?? []
4571
4819
  );
4572
4820
  const conflicts = [];
4573
4821
  for (const output of outputs) {
4574
- const destination = path15.resolve(output.path);
4822
+ const destination = path17.resolve(output.path);
4575
4823
  const current = await readOptional(destination);
4576
4824
  if (!current || digest(current) === digest(output.data)) continue;
4577
4825
  const expected = recorded.get(destination);
@@ -4596,7 +4844,7 @@ function isCode(error, code) {
4596
4844
  }
4597
4845
  async function remove2(file, errors) {
4598
4846
  try {
4599
- await rm5(file, { force: true });
4847
+ await rm6(file, { force: true });
4600
4848
  } catch (error) {
4601
4849
  errors?.push(`could not remove ${file}: ${message(error)}`);
4602
4850
  }
@@ -4609,7 +4857,7 @@ async function rollback(prepared) {
4609
4857
  for (const artifact of [...prepared].reverse()) {
4610
4858
  if (!artifact.backup || !artifact.backedUp) continue;
4611
4859
  try {
4612
- await rename4(artifact.backup, artifact.destination);
4860
+ await rename5(artifact.backup, artifact.destination);
4613
4861
  artifact.backedUp = false;
4614
4862
  } catch (error) {
4615
4863
  errors.push(
@@ -4626,7 +4874,7 @@ async function writeArtifactBundle(files, options = {}) {
4626
4874
  if (!files.length) throw new Error("An artifact bundle must contain at least one file.");
4627
4875
  const normalized = files.map((file) => {
4628
4876
  if (!file.path.trim()) throw new Error("Artifact paths cannot be empty.");
4629
- return { destination: path15.resolve(file.path), data: Buffer.from(file.data) };
4877
+ return { destination: path17.resolve(file.path), data: Buffer.from(file.data) };
4630
4878
  });
4631
4879
  const destinations = /* @__PURE__ */ new Set();
4632
4880
  for (const artifact of normalized) {
@@ -4642,7 +4890,7 @@ async function writeArtifactBundle(files, options = {}) {
4642
4890
  const unchanged = [];
4643
4891
  for (const artifact of normalized) {
4644
4892
  try {
4645
- const current = await readFile12(artifact.destination);
4893
+ const current = await readFile13(artifact.destination);
4646
4894
  if (current.equals(artifact.data)) {
4647
4895
  unchanged.push(artifact.destination);
4648
4896
  } else {
@@ -4656,21 +4904,21 @@ async function writeArtifactBundle(files, options = {}) {
4656
4904
  if (!changed.length) return { changed: [], unchanged };
4657
4905
  const token = randomUUID2();
4658
4906
  for (const [index, artifact] of changed.entries()) {
4659
- const basename = path15.basename(artifact.destination);
4660
- artifact.stage = path15.join(
4661
- path15.dirname(artifact.destination),
4907
+ const basename = path17.basename(artifact.destination);
4908
+ artifact.stage = path17.join(
4909
+ path17.dirname(artifact.destination),
4662
4910
  `.${basename}.pixelkiln-stage-${token}-${index}`
4663
4911
  );
4664
4912
  if (artifact.existed) {
4665
- artifact.backup = path15.join(
4666
- path15.dirname(artifact.destination),
4913
+ artifact.backup = path17.join(
4914
+ path17.dirname(artifact.destination),
4667
4915
  `.${basename}.pixelkiln-backup-${token}-${index}`
4668
4916
  );
4669
4917
  }
4670
4918
  }
4671
- const journal = options.recoveryFile ? path15.resolve(options.recoveryFile) : null;
4919
+ const journal = options.recoveryFile ? path17.resolve(options.recoveryFile) : null;
4672
4920
  if (journal) {
4673
- await mkdir5(path15.dirname(journal), { recursive: true });
4921
+ await mkdir6(path17.dirname(journal), { recursive: true });
4674
4922
  const transaction = {
4675
4923
  format: "pixelkiln-artifact-transaction",
4676
4924
  version: 1,
@@ -4684,7 +4932,7 @@ async function writeArtifactBundle(files, options = {}) {
4684
4932
  }))
4685
4933
  };
4686
4934
  try {
4687
- await writeFile7(journal, JSON.stringify(transaction, null, 2) + "\n", { flag: "wx" });
4935
+ await writeFile8(journal, JSON.stringify(transaction, null, 2) + "\n", { flag: "wx" });
4688
4936
  activeTransactions.add(journal);
4689
4937
  } catch (error) {
4690
4938
  throw new Error(
@@ -4695,9 +4943,9 @@ async function writeArtifactBundle(files, options = {}) {
4695
4943
  }
4696
4944
  try {
4697
4945
  for (const [index, artifact] of changed.entries()) {
4698
- await mkdir5(path15.dirname(artifact.destination), { recursive: true });
4946
+ await mkdir6(path17.dirname(artifact.destination), { recursive: true });
4699
4947
  await options.beforeStage?.(artifact.destination, index);
4700
- await writeFile7(artifact.stage, artifact.data, { flag: "wx" });
4948
+ await writeFile8(artifact.stage, artifact.data, { flag: "wx" });
4701
4949
  }
4702
4950
  } catch (error) {
4703
4951
  const cleanupErrors2 = await rollback(changed);
@@ -4710,17 +4958,17 @@ async function writeArtifactBundle(files, options = {}) {
4710
4958
  try {
4711
4959
  for (const artifact of changed) {
4712
4960
  if (!artifact.existed) continue;
4713
- await rename4(artifact.destination, artifact.backup);
4961
+ await rename5(artifact.destination, artifact.backup);
4714
4962
  artifact.backedUp = true;
4715
4963
  }
4716
4964
  for (const [index, artifact] of changed.entries()) {
4717
4965
  await options.beforePromote?.(artifact.destination, index);
4718
- await rename4(artifact.stage, artifact.destination);
4966
+ await rename5(artifact.stage, artifact.destination);
4719
4967
  artifact.stage = void 0;
4720
4968
  artifact.promoted = true;
4721
4969
  }
4722
4970
  if (journal) {
4723
- await writeFile7(transactionMarker(journal), `${token}
4971
+ await writeFile8(transactionMarker(journal), `${token}
4724
4972
  `, { flag: "wx" });
4725
4973
  durableCommit = true;
4726
4974
  await options.afterCommit?.();
@@ -4765,11 +5013,11 @@ async function writeArtifactBundle(files, options = {}) {
4765
5013
  // src/cli.ts
4766
5014
  var log = (msg = "") => console.log(msg);
4767
5015
  async function provenanceFile(id, file) {
4768
- const absolute = path16.resolve(file);
5016
+ const absolute = path18.resolve(file);
4769
5017
  return {
4770
5018
  id,
4771
5019
  path: absolute,
4772
- sha256: existsSync14(absolute) ? await sha256File(absolute) : null,
5020
+ sha256: existsSync15(absolute) ? await sha256File(absolute) : null,
4773
5021
  included: true
4774
5022
  };
4775
5023
  }
@@ -4793,7 +5041,10 @@ var VALUE_FLAGS = [
4793
5041
  "--max-distance",
4794
5042
  "--min-transparency",
4795
5043
  "--max-colors",
4796
- "--sigma"
5044
+ "--sigma",
5045
+ "--workspace",
5046
+ "--provider",
5047
+ "--account"
4797
5048
  ];
4798
5049
  var BOOL_FLAGS = [
4799
5050
  "--force",
@@ -4832,18 +5083,43 @@ var COMMANDS = [
4832
5083
  "tag",
4833
5084
  "balance",
4834
5085
  "status",
5086
+ "workspace",
4835
5087
  "help",
4836
5088
  "--help",
4837
5089
  "-h",
4838
5090
  "--version",
4839
5091
  "-v"
4840
5092
  ];
5093
+ var WORKSPACE_SUBCOMMANDS = ["add", "remove", "list", "status", "claims"];
4841
5094
  function parseArgs(argv) {
4842
5095
  const [command = "help"] = argv;
4843
5096
  if (!COMMANDS.includes(command)) {
4844
5097
  throw new Error(`Unknown command "${command}". Run \`pixelkiln help\` for the list.`);
4845
5098
  }
4846
- const rest = argv.slice(1);
5099
+ let rest = argv.slice(1);
5100
+ let subcommand;
5101
+ let target;
5102
+ if (command === "workspace") {
5103
+ subcommand = rest[0];
5104
+ if (subcommand === void 0 || subcommand.startsWith("-")) {
5105
+ throw new Error(`workspace needs a subcommand: ${WORKSPACE_SUBCOMMANDS.join(", ")}`);
5106
+ }
5107
+ if (!WORKSPACE_SUBCOMMANDS.includes(subcommand)) {
5108
+ throw new Error(
5109
+ `Unknown workspace subcommand "${subcommand}". Known: ${WORKSPACE_SUBCOMMANDS.join(", ")}`
5110
+ );
5111
+ }
5112
+ rest = rest.slice(1);
5113
+ if (subcommand === "add" || subcommand === "remove") {
5114
+ target = rest[0];
5115
+ if (target === void 0 || target.startsWith("-")) {
5116
+ throw new Error(
5117
+ subcommand === "add" ? "workspace add needs a manifest path." : "workspace remove needs a project id or manifest path."
5118
+ );
5119
+ }
5120
+ rest = rest.slice(1);
5121
+ }
5122
+ }
4847
5123
  for (let i = 0; i < rest.length; i++) {
4848
5124
  const token = rest[i];
4849
5125
  if (!token.startsWith("-")) {
@@ -4920,7 +5196,8 @@ function parseArgs(argv) {
4920
5196
  return {
4921
5197
  command,
4922
5198
  manifest,
4923
- lock: get("--lock") ?? path16.join(path16.dirname(path16.resolve(manifest)), "pixelkiln.lock.json"),
5199
+ lock: get("--lock") ?? path18.join(path18.dirname(path18.resolve(manifest)), "pixelkiln.lock.json"),
5200
+ explicitLock: get("--lock"),
4924
5201
  styles: list("--style"),
4925
5202
  assets: list("--only"),
4926
5203
  force: rest.includes("--force"),
@@ -4949,7 +5226,12 @@ function parseArgs(argv) {
4949
5226
  maxDistance: numberOption("--max-distance", { min: 0 }),
4950
5227
  minTransparency: numberOption("--min-transparency", { min: 0, max: 1 }),
4951
5228
  maxColors: numberOption("--max-colors", { min: 1, integer: true }),
4952
- sigma: numberOption("--sigma", { min: Number.EPSILON })
5229
+ sigma: numberOption("--sigma", { min: Number.EPSILON }),
5230
+ subcommand,
5231
+ workspace: get("--workspace"),
5232
+ target,
5233
+ provider: get("--provider"),
5234
+ account: get("--account")
4953
5235
  };
4954
5236
  }
4955
5237
  var HELP = `pixelkiln \u2014 manifest-driven pixel art generation (PixelLab)
@@ -4982,6 +5264,8 @@ Commands
4982
5264
  tag Push manifest tags to the objects upstream (free).
4983
5265
  balance Show the provider's remaining balance.
4984
5266
  status Summarise the lockfile.
5267
+ workspace Register sibling projects and derive account-wide claims/status.
5268
+ add/remove/list/status/claims. Offline.
4985
5269
 
4986
5270
  Options
4987
5271
  --columns <n> pack/export: sprites or tiles per row (default: near-square)
@@ -5009,6 +5293,8 @@ Options
5009
5293
  --no-open Do not auto-open the browser during pick
5010
5294
  --tag Also push tags upstream after fetch
5011
5295
  --claims a.json,b Other projects' lockfiles (salvage; required if account is shared)
5296
+ --workspace <path> Workspace catalog (default: pixelkiln.workspace.json). Also
5297
+ derives salvage's claim set instead of repeated --claims.
5012
5298
  --from <dir> Source tree for init
5013
5299
  --write-prompts adopt: recover prompts into the manifest
5014
5300
 
@@ -5021,6 +5307,9 @@ Examples
5021
5307
  pixelkiln pack --inputs sprites.json --out dist/sheet # no manifest needed
5022
5308
  pixelkiln mount --style ground
5023
5309
  pixelkiln export --style ground --only terrain --format tiled
5310
+ pixelkiln workspace add ../other-game/pixelkiln.manifest.json
5311
+ pixelkiln workspace status --json
5312
+ pixelkiln salvage --workspace pixelkiln.workspace.json
5024
5313
  `;
5025
5314
  function printPlan(plan) {
5026
5315
  const counts = summarize(plan);
@@ -5061,6 +5350,23 @@ async function confirm(question, auto) {
5061
5350
  process.stdin.pause();
5062
5351
  return answer === "y" || answer === "yes";
5063
5352
  }
5353
+ async function requireCompleteWorkspaceClaims(workspacePath) {
5354
+ if (!existsSync15(workspacePath)) {
5355
+ throw new Error(`Workspace catalog not found: ${workspacePath}`);
5356
+ }
5357
+ const dir = path18.dirname(path18.resolve(workspacePath));
5358
+ const ws = await loadWorkspace(workspacePath);
5359
+ const diagnostics = validateWorkspace(ws, dir);
5360
+ const errors = diagnostics.filter((d) => d.level === "error");
5361
+ if (errors.length) {
5362
+ throw new Error(
5363
+ `Workspace catalog at ${workspacePath} is not safe to derive a claim set from:
5364
+ ` + errors.map((d) => ` ${d.id}: ${d.message}`).join("\n")
5365
+ );
5366
+ }
5367
+ const claims = await workspaceClaims(ws, dir);
5368
+ return { ws, dir, diagnostics, claims };
5369
+ }
5064
5370
  async function main() {
5065
5371
  const args = parseArgs(process.argv.slice(2));
5066
5372
  if (args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -5069,13 +5375,13 @@ async function main() {
5069
5375
  }
5070
5376
  if (args.command === "--version" || args.command === "-v") {
5071
5377
  const pkg = JSON.parse(
5072
- await readFile13(new URL("../package.json", import.meta.url), "utf8")
5378
+ await readFile14(new URL("../package.json", import.meta.url), "utf8")
5073
5379
  );
5074
5380
  log(`${pkg.name} ${pkg.version}`);
5075
5381
  return;
5076
5382
  }
5077
5383
  if (args.command === "balance") {
5078
- loadEnvFiles(path16.dirname(path16.resolve(args.manifest)));
5384
+ loadEnvFiles(path18.dirname(path18.resolve(args.manifest)));
5079
5385
  loadEnvFiles(process.cwd());
5080
5386
  const p = PixelLabProvider.fromEnv();
5081
5387
  const b = await p.balance();
@@ -5086,31 +5392,31 @@ async function main() {
5086
5392
  }
5087
5393
  if (args.command === "init") {
5088
5394
  if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
5089
- const root = path16.resolve(args.from);
5090
- if (!existsSync14(root)) throw new Error(`No directory at ${root}`);
5395
+ const root = path18.resolve(args.from);
5396
+ if (!existsSync15(root)) throw new Error(`No directory at ${root}`);
5091
5397
  const generator = args.generator ?? "map";
5092
5398
  if (generator !== "1dir" && generator !== "map") {
5093
5399
  throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
5094
5400
  }
5095
- const target = path16.resolve(args.out ?? "pixelkiln.manifest.json");
5401
+ const target = path18.resolve(args.out ?? "pixelkiln.manifest.json");
5096
5402
  const { assets, skipped } = await scanAssets(root, { exclude: args.exclude });
5097
5403
  if (!assets.length) throw new Error(`No PNGs found under ${root}`);
5098
5404
  const manifest = buildManifest(
5099
- args.name ?? path16.basename(path16.dirname(target)),
5405
+ args.name ?? path18.basename(path18.dirname(target)),
5100
5406
  args.styles[0] ?? "base",
5101
5407
  generator,
5102
- path16.relative(path16.dirname(target), root) || ".",
5408
+ path18.relative(path18.dirname(target), root) || ".",
5103
5409
  assets
5104
5410
  );
5105
5411
  await writeManifestFile(target, manifest);
5106
- log(` scanned ${assets.length} PNG(s) under ${path16.relative(process.cwd(), root)}`);
5412
+ log(` scanned ${assets.length} PNG(s) under ${path18.relative(process.cwd(), root)}`);
5107
5413
  if (skipped.length) log(` skipped ${skipped.length} unreadable file(s)`);
5108
- log(` wrote ${path16.relative(process.cwd(), target)}`);
5414
+ log(` wrote ${path18.relative(process.cwd(), target)}`);
5109
5415
  log(`
5110
5416
  Prompts are intentionally empty. To recover the real ones from your`);
5111
5417
  log(` PixelLab account instead of inventing them:`);
5112
5418
  log(`
5113
- pixelkiln adopt --manifest ${path16.relative(process.cwd(), target)} --write-prompts
5419
+ pixelkiln adopt --manifest ${path18.relative(process.cwd(), target)} --write-prompts
5114
5420
  `);
5115
5421
  return;
5116
5422
  }
@@ -5158,10 +5464,10 @@ async function main() {
5158
5464
  if (args.primaryOnly || args.outputRoles.length) {
5159
5465
  throw new Error("--primary-only and --output-role require manifest-driven pack");
5160
5466
  }
5161
- const raw = JSON.parse(await readFile13(path16.resolve(args.inputs), "utf8"));
5467
+ const raw = JSON.parse(await readFile14(path18.resolve(args.inputs), "utf8"));
5162
5468
  const inputs = resolvePackInputs(raw, args.inputs);
5163
5469
  const { png, atlas, skipped, sources } = packSprites(inputs, { columns: args.columns });
5164
- const base = path16.resolve(args.out.replace(/\.png$/, ""));
5470
+ const base = path18.resolve(args.out.replace(/\.png$/, ""));
5165
5471
  const outputs = [
5166
5472
  { path: `${base}.png`, data: png },
5167
5473
  { path: `${base}.json`, data: JSON.stringify(atlas, null, 2) + "\n" }
@@ -5174,18 +5480,143 @@ async function main() {
5174
5480
  log(
5175
5481
  ` ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s) \u2014 ${(png.length / 1024).toFixed(1)} KB`
5176
5482
  );
5177
- log(` ${path16.relative(process.cwd(), base)}.png + .json + .pixelkiln.json`);
5483
+ log(` ${path18.relative(process.cwd(), base)}.png + .json + .pixelkiln.json`);
5178
5484
  for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
5179
5485
  return;
5180
5486
  }
5181
- if (!existsSync14(path16.resolve(args.manifest))) {
5487
+ if (args.command === "workspace") {
5488
+ const workspacePath = path18.resolve(args.workspace ?? "pixelkiln.workspace.json");
5489
+ const dir = path18.dirname(workspacePath);
5490
+ if (args.subcommand === "add") {
5491
+ const manifestPath = path18.resolve(args.target);
5492
+ const loadedTarget = await loadManifest(manifestPath);
5493
+ const lockPath = args.explicitLock ? path18.resolve(args.explicitLock) : path18.join(path18.dirname(manifestPath), "pixelkiln.lock.json");
5494
+ const ws = await loadWorkspace(workspacePath);
5495
+ const id = args.name ?? loadedTarget.manifest.name;
5496
+ if (ws.projects.some((p) => p.id === id)) {
5497
+ throw new Error(
5498
+ `Project id "${id}" is already registered in ${workspacePath}. Pass --name for a different id.`
5499
+ );
5500
+ }
5501
+ const lockOwner = ws.projects.find((p) => resolveProject(dir, p).lockPath === lockPath);
5502
+ if (lockOwner) {
5503
+ throw new Error(`Lockfile ${lockPath} is already registered under project id "${lockOwner.id}".`);
5504
+ }
5505
+ const project = {
5506
+ id,
5507
+ manifest: toPortablePath(dir, manifestPath),
5508
+ lock: toPortablePath(dir, lockPath),
5509
+ provider: args.provider ?? "pixellab",
5510
+ ...args.account ? { account: args.account } : {}
5511
+ };
5512
+ await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
5513
+ log(` registered "${id}" in ${path18.relative(process.cwd(), workspacePath)}`);
5514
+ log(` manifest: ${project.manifest}`);
5515
+ log(` lock: ${project.lock}`);
5516
+ if (!existsSync15(lockPath)) {
5517
+ log(
5518
+ ` warning: no lockfile there yet \u2014 this project contributes no claims until one is generated`
5519
+ );
5520
+ }
5521
+ return;
5522
+ }
5523
+ if (args.subcommand === "remove") {
5524
+ const ws = await loadWorkspace(workspacePath);
5525
+ const resolvedTarget = path18.resolve(args.target);
5526
+ const match = ws.projects.find(
5527
+ (p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
5528
+ );
5529
+ if (!match) {
5530
+ throw new Error(`No registered project matches "${args.target}" (checked id and manifest path).`);
5531
+ }
5532
+ await saveWorkspace(workspacePath, {
5533
+ version: 1,
5534
+ projects: ws.projects.filter((p) => p !== match)
5535
+ });
5536
+ log(` removed "${match.id}" from ${path18.relative(process.cwd(), workspacePath)}`);
5537
+ return;
5538
+ }
5539
+ if (args.subcommand === "list") {
5540
+ if (!existsSync15(workspacePath)) {
5541
+ throw new Error(`Workspace catalog not found: ${workspacePath}`);
5542
+ }
5543
+ const ws = await loadWorkspace(workspacePath);
5544
+ const diagnostics = validateWorkspace(ws, dir);
5545
+ if (args.json) {
5546
+ log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
5547
+ } else if (!ws.projects.length) {
5548
+ log(` no projects registered in ${path18.relative(process.cwd(), workspacePath)}`);
5549
+ } else {
5550
+ log(` ${ws.projects.length} project(s) in ${path18.relative(process.cwd(), workspacePath)}:`);
5551
+ for (const p of ws.projects) {
5552
+ log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
5553
+ }
5554
+ for (const d of diagnostics) log(` ${d.level === "error" ? "ERROR" : "WARN "} ${d.id.padEnd(18)} ${d.message}`);
5555
+ }
5556
+ if (args.check && diagnostics.some((d) => d.level === "error")) process.exitCode = 1;
5557
+ return;
5558
+ }
5559
+ if (args.subcommand === "status") {
5560
+ if (!existsSync15(workspacePath)) {
5561
+ throw new Error(`Workspace catalog not found: ${workspacePath}`);
5562
+ }
5563
+ const ws = await loadWorkspace(workspacePath);
5564
+ const report = await workspaceStatus(ws, dir);
5565
+ if (args.json) {
5566
+ log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
5567
+ } else {
5568
+ log(` workspace: ${path18.relative(process.cwd(), workspacePath)}`);
5569
+ for (const p of report.projects) {
5570
+ if (p.error) {
5571
+ log(`
5572
+ ${p.id} \u2014 ERROR: ${p.error}`);
5573
+ continue;
5574
+ }
5575
+ log(`
5576
+ ${p.id} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
5577
+ log(` ${p.entries} lock entries`);
5578
+ for (const [state, n] of Object.entries(p.byState)) if (n) log(` ${state.padEnd(12)} ${n}`);
5579
+ for (const unit of ["generations", "usd", "free"]) {
5580
+ if (p.spendByUnit[unit]) log(` spend: ${formatCost(unit, p.spendByUnit[unit])}`);
5581
+ }
5582
+ }
5583
+ log(`
5584
+ totals:`);
5585
+ for (const [state, n] of Object.entries(report.totals.byState)) if (n) log(` ${state.padEnd(12)} ${n}`);
5586
+ for (const unit of ["generations", "usd", "free"]) {
5587
+ if (report.totals.spendByUnit[unit]) log(` spend: ${formatCost(unit, report.totals.spendByUnit[unit])}`);
5588
+ }
5589
+ log(` claims: ${report.totals.claims}`);
5590
+ for (const d of report.diagnostics) log(` ${d.level === "error" ? "ERROR" : "WARN "} ${d.id.padEnd(18)} ${d.message}`);
5591
+ }
5592
+ if (args.check && !report.safe) process.exitCode = 1;
5593
+ return;
5594
+ }
5595
+ if (args.subcommand === "claims") {
5596
+ const { claims, diagnostics } = await requireCompleteWorkspaceClaims(workspacePath);
5597
+ if (args.json) {
5598
+ log(JSON.stringify({
5599
+ version: 1,
5600
+ claimed: [...claims.claimed].sort(),
5601
+ byProject: claims.byProject,
5602
+ lockPaths: claims.lockPaths
5603
+ }, null, 2));
5604
+ } else {
5605
+ log(` ${claims.claimed.size} claimed id(s) across ${claims.lockPaths.length} lockfile(s):`);
5606
+ for (const [id, n] of Object.entries(claims.byProject).sort()) log(` ${id.padEnd(24)} ${n}`);
5607
+ for (const d of diagnostics) log(` WARN ${d.id.padEnd(18)} ${d.message}`);
5608
+ }
5609
+ return;
5610
+ }
5611
+ }
5612
+ if (!existsSync15(path18.resolve(args.manifest))) {
5182
5613
  throw new Error(
5183
- `No manifest at ${path16.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
5614
+ `No manifest at ${path18.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
5184
5615
  );
5185
5616
  }
5186
- const manifestDir = path16.dirname(path16.resolve(args.manifest));
5617
+ const manifestDir = path18.dirname(path18.resolve(args.manifest));
5187
5618
  const envFiles = [...loadEnvFiles(manifestDir)];
5188
- if (path16.resolve(process.cwd()) !== manifestDir) envFiles.push(...loadEnvFiles(process.cwd()));
5619
+ if (path18.resolve(process.cwd()) !== manifestDir) envFiles.push(...loadEnvFiles(process.cwd()));
5189
5620
  const loaded = await loadManifest(args.manifest);
5190
5621
  const estimator = PixelLabProvider.forOffline();
5191
5622
  const specs = await resolveSpecs(loaded, {
@@ -5260,7 +5691,7 @@ async function main() {
5260
5691
  let intact = true;
5261
5692
  for (const output of entry.outputs) {
5262
5693
  const file = resolveOutputPath(output.path, item.spec.root);
5263
- if (!existsSync14(file) || await sha256File(file) !== output.sha256) {
5694
+ if (!existsSync15(file) || await sha256File(file) !== output.sha256) {
5264
5695
  intact = false;
5265
5696
  break;
5266
5697
  }
@@ -5318,7 +5749,7 @@ async function main() {
5318
5749
  if (args.primaryOnly && args.outputRoles.length) {
5319
5750
  throw new Error("pack accepts either --primary-only or --output-role, not both");
5320
5751
  }
5321
- const manifestDir2 = path16.dirname(path16.resolve(args.manifest));
5752
+ const manifestDir2 = path18.dirname(path18.resolve(args.manifest));
5322
5753
  const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
5323
5754
  for (const styleId of styleIds) {
5324
5755
  const { png, atlas, skipped, sources } = packStyle(lock, styleId, manifestDir2, {
@@ -5327,7 +5758,7 @@ async function main() {
5327
5758
  primaryOnly: args.primaryOnly
5328
5759
  });
5329
5760
  const style = loaded.manifest.styles[styleId];
5330
- const base = args.out ? path16.resolve(args.out.replace(/\.png$/, "")) : path16.resolve(manifestDir2, style.outDir, `${styleId}-sheet`);
5761
+ const base = args.out ? path18.resolve(args.out.replace(/\.png$/, "")) : path18.resolve(manifestDir2, style.outDir, `${styleId}-sheet`);
5331
5762
  const outputs = [
5332
5763
  { path: `${base}.png`, data: png },
5333
5764
  { path: `${base}.json`, data: JSON.stringify(atlas, null, 2) + "\n" }
@@ -5350,13 +5781,13 @@ async function main() {
5350
5781
  log(
5351
5782
  ` ${styleId} \u2014 ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s)`
5352
5783
  );
5353
- log(` ${path16.relative(process.cwd(), base)}.png + .json + .pixelkiln.json`);
5784
+ log(` ${path18.relative(process.cwd(), base)}.png + .json + .pixelkiln.json`);
5354
5785
  for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
5355
5786
  }
5356
5787
  return;
5357
5788
  }
5358
5789
  if (args.command === "mount") {
5359
- const manifestDir2 = path16.dirname(path16.resolve(args.manifest));
5790
+ const manifestDir2 = path18.dirname(path18.resolve(args.manifest));
5360
5791
  const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
5361
5792
  for (const styleId of styleIds) {
5362
5793
  const style = loaded.manifest.styles[styleId];
@@ -5386,7 +5817,7 @@ async function main() {
5386
5817
  sources,
5387
5818
  outputRoles
5388
5819
  );
5389
- const out = path16.resolve(manifestDir2, style.mount.out);
5820
+ const out = path18.resolve(manifestDir2, style.mount.out);
5390
5821
  const metadata = out.replace(/\.png$/, "") + ".json";
5391
5822
  const companion = out.replace(/\.png$/, "") + ".pixelkiln.json";
5392
5823
  const outputs = [
@@ -5399,7 +5830,7 @@ async function main() {
5399
5830
  await provenanceFile("$manifest", args.manifest),
5400
5831
  await provenanceFile("$lock", args.lock),
5401
5832
  ...artifactSources.filter(
5402
- (source) => source.id !== "$base" || path16.resolve(source.path) !== out
5833
+ (source) => source.id !== "$base" || path18.resolve(source.path) !== out
5403
5834
  )
5404
5835
  ],
5405
5836
  options: {
@@ -5412,7 +5843,7 @@ async function main() {
5412
5843
  log(
5413
5844
  ` ${styleId} \u2014 ${atlas.frames.length} cell(s) into ${atlas.sheet.width}x${atlas.sheet.height}` + (overBase ? ` over ${style.mount.base}` : " (new sheet)")
5414
5845
  );
5415
- log(` ${path16.relative(process.cwd(), out)} + atlas/provenance JSON`);
5846
+ log(` ${path18.relative(process.cwd(), out)} + atlas/provenance JSON`);
5416
5847
  for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
5417
5848
  }
5418
5849
  return;
@@ -5468,7 +5899,7 @@ async function main() {
5468
5899
  }
5469
5900
  if (args.command === "export") {
5470
5901
  const format = args.format ?? "generic";
5471
- const manifestDir2 = path16.dirname(path16.resolve(args.manifest));
5902
+ const manifestDir2 = path18.dirname(path18.resolve(args.manifest));
5472
5903
  const selected = specs.filter((spec) => {
5473
5904
  if (spec.generator !== "tiles") return false;
5474
5905
  if (args.styles.length && !args.styles.includes(spec.styleId)) return false;
@@ -5484,12 +5915,12 @@ async function main() {
5484
5915
  for (const spec of selected) {
5485
5916
  const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
5486
5917
  const style = loaded.manifest.styles[spec.styleId];
5487
- const defaultBase = path16.resolve(manifestDir2, style.outDir, `${spec.assetId}-tileset`);
5488
- const base = args.out ? path16.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
5918
+ const defaultBase = path18.resolve(manifestDir2, style.outDir, `${spec.assetId}-tileset`);
5919
+ const base = args.out ? path18.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
5489
5920
  const result = exportTileset(entry, spec, {
5490
5921
  format,
5491
5922
  manifestDir: manifestDir2,
5492
- imageName: path16.basename(`${base}.png`),
5923
+ imageName: path18.basename(`${base}.png`),
5493
5924
  columns: args.columns
5494
5925
  });
5495
5926
  const outputs = [
@@ -5507,7 +5938,7 @@ async function main() {
5507
5938
  asset: spec.assetId,
5508
5939
  columns: args.columns ?? null,
5509
5940
  format,
5510
- image: path16.basename(`${base}.png`),
5941
+ image: path18.basename(`${base}.png`),
5511
5942
  providerRules: result.generic.providerRules,
5512
5943
  style: spec.styleId,
5513
5944
  tileType: spec.tileType ?? null
@@ -5517,7 +5948,7 @@ async function main() {
5517
5948
  ` ${spec.styleId}/${spec.assetId} \u2014 ${result.generic.tiles.length} tile(s), ${result.generic.sheet.width}x${result.generic.sheet.height} (${format})`
5518
5949
  );
5519
5950
  log(
5520
- ` ${path16.relative(process.cwd(), base)}.png + ${path16.basename(base)}${result.extension} + .pixelkiln.json`
5951
+ ` ${path18.relative(process.cwd(), base)}.png + ${path18.basename(base)}${result.extension} + .pixelkiln.json`
5521
5952
  );
5522
5953
  }
5523
5954
  return;
@@ -5557,10 +5988,10 @@ async function main() {
5557
5988
  tagged ${n} object(s) upstream`);
5558
5989
  }
5559
5990
  if (args.writePrompts) {
5560
- const { filled, stillEmpty } = await writePromptsBack(path16.resolve(args.manifest), lock, {
5991
+ const { filled, stillEmpty } = await writePromptsBack(path18.resolve(args.manifest), lock, {
5561
5992
  onProgress: log
5562
5993
  });
5563
- log(` recovered ${filled} prompt(s) into ${path16.relative(process.cwd(), args.manifest)}`);
5994
+ log(` recovered ${filled} prompt(s) into ${path18.relative(process.cwd(), args.manifest)}`);
5564
5995
  const reloaded = await loadManifest(args.manifest);
5565
5996
  const rebased = await resolveSpecs(reloaded, {
5566
5997
  styles: args.styles,
@@ -5589,19 +6020,39 @@ async function main() {
5589
6020
  if (args.command === "salvage") {
5590
6021
  const jsonMode = args.dryRun && args.json;
5591
6022
  const diag = jsonMode ? (msg = "") => console.error(msg) : log;
5592
- const ownLock = path16.resolve(args.lock);
6023
+ const ownLock = path18.resolve(args.lock);
6024
+ let workspaceProjects = [];
6025
+ let workspaceDir = "";
6026
+ if (args.workspace) {
6027
+ const workspacePath = path18.resolve(args.workspace);
6028
+ workspaceDir = path18.dirname(workspacePath);
6029
+ const complete = await requireCompleteWorkspaceClaims(workspacePath);
6030
+ workspaceProjects = complete.ws.projects;
6031
+ for (const d of complete.diagnostics) diag(` WARN ${d.id}: ${d.message}`);
6032
+ }
6033
+ const workspaceLockPaths = workspaceProjects.map((p) => resolveProject(workspaceDir, p).lockPath);
5593
6034
  const lockPaths = [
5594
- ...existsSync14(ownLock) ? [ownLock] : [],
5595
- ...args.claims.map((c) => path16.resolve(c))
6035
+ .../* @__PURE__ */ new Set([
6036
+ ...workspaceLockPaths,
6037
+ ...existsSync15(ownLock) ? [ownLock] : [],
6038
+ ...args.claims.map((c) => path18.resolve(c))
6039
+ ])
5596
6040
  ];
5597
6041
  diag(` claim set (${lockPaths.length} lockfile(s)):`);
5598
- for (const p of lockPaths) diag(` ${path16.relative(process.cwd(), p)}`);
5599
- if (!args.claims.length) {
6042
+ for (const p of lockPaths) diag(` ${path18.relative(process.cwd(), p)}`);
6043
+ if (!args.claims.length && !args.workspace) {
5600
6044
  diag(
5601
6045
  `
5602
6046
  Only this project's lockfile was consulted. If the account is shared,
5603
- pass every other project's lockfile via --claims a.json,b.json or you
5604
- will see their assets listed as unclaimed.`
6047
+ pass every other project's lockfile via --claims a.json,b.json, or
6048
+ register every project in a workspace catalog and pass --workspace.`
6049
+ );
6050
+ } else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path18.resolve(args.manifest))) {
6051
+ diag(
6052
+ `
6053
+ This project's manifest is not registered in the workspace catalog. Its own
6054
+ lockfile is still included above, so this run's claim set is complete \u2014 but
6055
+ \`pixelkiln workspace add ${args.manifest}\` would keep it aggregated too.`
5605
6056
  );
5606
6057
  }
5607
6058
  const claimed = await loadClaims(lockPaths);
@@ -5613,16 +6064,11 @@ async function main() {
5613
6064
  nothing to triage`);
5614
6065
  return;
5615
6066
  }
5616
- const siblings = [];
5617
- for (const claimPath of args.claims.map((c) => path16.resolve(c))) {
5618
- const siblingManifestPath = path16.join(path16.dirname(claimPath), "pixelkiln.manifest.json");
5619
- if (!existsSync14(siblingManifestPath)) continue;
5620
- try {
5621
- const siblingLoaded = await loadManifest(siblingManifestPath);
5622
- siblings.push({ label: path16.basename(path16.dirname(siblingManifestPath)), manifest: siblingLoaded.manifest });
5623
- } catch {
5624
- }
5625
- }
6067
+ const siblings = await loadSiblingManifests(
6068
+ args.manifest,
6069
+ workspaceProjects.map((p) => resolveProject(workspaceDir, p).manifestPath),
6070
+ args.claims
6071
+ );
5626
6072
  const { matched, unmatched, elsewhere } = groupOrphansByStyle(orphans, loaded.manifest, siblings);
5627
6073
  const multiStyle = Object.keys(loaded.manifest.styles).length > 1;
5628
6074
  if (multiStyle) {
@@ -5685,7 +6131,7 @@ async function main() {
5685
6131
  manifestPath: loaded.path,
5686
6132
  manifest: loaded.manifest,
5687
6133
  styleId,
5688
- importDir: path16.resolve(loaded.root, style.outDir),
6134
+ importDir: path18.resolve(loaded.root, style.outDir),
5689
6135
  lock,
5690
6136
  lockPath: args.lock
5691
6137
  },