@magicpixelart/cli 0.5.9 → 0.5.11

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/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.11] — 2026-08-26
11
+
12
+ ### Changed
13
+
14
+ - **Sprite counting shows a running total** (`Counting sprites in your
15
+ game… 1,247`) while walking the project, and watch reuses that first
16
+ scan so startup does not walk the tree twice.
17
+
18
+ ## [0.5.10] — 2026-08-26
19
+
20
+ ### Changed
21
+
22
+ - **Watch header shows the working-set size**, not only the last successful
23
+ pull. Connecting 2,000+ game sprites no longer prints `Sprites: 469` when
24
+ `state.json` hasn't caught up yet (`2,147 in your game · 469 last pulled
25
+ from MagicPixel`).
26
+ - **Watch prints live status** while a tick is in flight (`Checking
27
+ MagicPixel…`, `Comparing N sprites…`, download progress, `Checking your
28
+ game files…`) instead of sitting silent on the header until the tick
29
+ finishes. Idle ticks say `Waiting for edits…`.
30
+
10
31
  ## [0.5.9] — 2026-08-26
11
32
 
12
33
  ### Security
@@ -1,8 +1,9 @@
1
1
  import kleur from 'kleur';
2
+ import ora from 'ora';
2
3
  import { loadConfig, saveConfig } from '../config.js';
3
4
  import { assertSafeGlob } from '../util/security.js';
4
5
  import { detectProjectKind, isEngineKind } from '../util/framework.js';
5
- import { indexGamePngs, matchConnectGlobs, GAME_INDEX_CAP_HINT, connectCapMessage } from '../util/gameScan.js';
6
+ import { indexGamePngs, matchConnectGlobs, GAME_INDEX_CAP_HINT, connectCapMessage, countingSpritesText } from '../util/gameScan.js';
6
7
  import { cmd } from '../util/invoke.js';
7
8
  import { runPush } from './push.js';
8
9
  /**
@@ -26,7 +27,11 @@ export async function connectCommand(glob) {
26
27
  console.log(kleur.yellow(' This folder is not a Unity/Godot/GameMaker project — nothing to ingest from disk.'));
27
28
  return;
28
29
  }
29
- const index = await indexGamePngs(kind, process.cwd(), config.outDir);
30
+ const spinner = ora({ text: countingSpritesText(0), spinner: 'dots' }).start();
31
+ const index = await indexGamePngs(kind, process.cwd(), config.outDir, {
32
+ onProgress: (n) => { spinner.text = countingSpritesText(n); },
33
+ });
34
+ spinner.stop();
30
35
  if (index.capped) {
31
36
  console.log(kleur.yellow(`! ${GAME_INDEX_CAP_HINT}`));
32
37
  }
@@ -19,7 +19,7 @@ import { assetDiskPathFromKey, walkOutDirPngs } from '../util/paths.js';
19
19
  import { applyDiskFingerprints, indexSyncedBySourceRel, planPush, resolveSyncedKey } from '../util/pushPlan.js';
20
20
  import { cmd } from '../util/invoke.js';
21
21
  import { detectProjectKind, isEngineKind } from '../util/framework.js';
22
- import { indexGamePngs, matchConnectGlobs, GAME_INDEX_CAP_HINT, connectCapMessage } from '../util/gameScan.js';
22
+ import { indexGamePngs, matchConnectGlobs, GAME_INDEX_CAP_HINT, connectCapMessage, countingSpritesText } from '../util/gameScan.js';
23
23
  import { collectSourceRelMap, remapAbsToCloudKeys } from '../util/syncPath.js';
24
24
  import { assertSafeIoPath } from '../util/security.js';
25
25
  /** Parallel hashing / path checks — enough to saturate disk, low enough to stay polite. */
@@ -42,11 +42,13 @@ export async function runPush(opts = {}) {
42
42
  export async function runPushWith(config, state, opts = {}) {
43
43
  const quiet = opts.quiet === true;
44
44
  const empty = { created: 0, updated: 0, unchanged: 0, conflict: 0, error: 0, imported: 0 };
45
- const spinner = quiet ? null : ora('Scanning local sprites…').start();
45
+ const spinner = quiet ? null : ora(countingSpritesText(0)).start();
46
46
  const kind = await detectProjectKind();
47
47
  const index = opts.gameIndex
48
48
  ?? (isEngineKind(kind)
49
- ? await indexGamePngs(kind, process.cwd(), config.outDir)
49
+ ? await indexGamePngs(kind, process.cwd(), config.outDir, {
50
+ onProgress: spinner ? (n) => { spinner.text = countingSpritesText(n); } : undefined,
51
+ })
50
52
  : { files: [], capped: false });
51
53
  const matched = matchConnectGlobs(index, config.connect ?? []);
52
54
  const sourceByKey = collectSourceRelMap(matched.entries, state.synced);
@@ -1,7 +1,8 @@
1
1
  import kleur from 'kleur';
2
+ import ora from 'ora';
2
3
  import { loadConfig } from '../config.js';
3
4
  import { detectProjectKind, isEngineKind } from '../util/framework.js';
4
- import { indexGamePngs, searchGameIndex, GAME_INDEX_CAP_HINT } from '../util/gameScan.js';
5
+ import { indexGamePngs, searchGameIndex, GAME_INDEX_CAP_HINT, countingSpritesText } from '../util/gameScan.js';
5
6
  import { cmd } from '../util/invoke.js';
6
7
  const PRINT_CAP = 50;
7
8
  /**
@@ -25,7 +26,11 @@ export async function searchCommand(query) {
25
26
  catch {
26
27
  // Config is optional for search — skip MagicPixel outDir when missing.
27
28
  }
28
- const index = await indexGamePngs(kind, process.cwd(), outDir);
29
+ const spinner = ora({ text: countingSpritesText(0), spinner: 'dots' }).start();
30
+ const index = await indexGamePngs(kind, process.cwd(), outDir, {
31
+ onProgress: (n) => { spinner.text = countingSpritesText(n); },
32
+ });
33
+ spinner.stop();
29
34
  if (index.capped) {
30
35
  console.log(kleur.yellow(`! ${GAME_INDEX_CAP_HINT}`));
31
36
  }
@@ -12,7 +12,7 @@ import { createLimit } from '../util/limit.js';
12
12
  import { emitTypedIndex, ensureAgentsDoc, scanDiskAssets } from '../util/emitIndex.js';
13
13
  import { assertPathInsideRoot, assertSafeIoPath } from '../util/security.js';
14
14
  import { detectProjectKind, isEngineKind } from '../util/framework.js';
15
- import { indexGamePngs, matchConnectGlobs, GAME_INDEX_CAP_HINT, connectCapMessage } from '../util/gameScan.js';
15
+ import { indexGamePngs, matchConnectGlobs, GAME_INDEX_CAP_HINT, connectCapMessage, countingSpritesText } from '../util/gameScan.js';
16
16
  import { collectSourceRelMap, isPathInside, syncDiskPathFromKey } from '../util/syncPath.js';
17
17
  import { DEFAULT_UNITY_PPU, writeMissingUnityMetas } from '../util/unityMeta.js';
18
18
  import { filterUnityManifest } from '../util/unityFilter.js';
@@ -23,6 +23,7 @@ import { maxIsoTimestamp } from '../util/iso.js';
23
23
  import { formatBytes } from '../util/format.js';
24
24
  import { computePreviousKeyOrphans } from '../util/previousKeyOrphans.js';
25
25
  import { cmd } from '../util/invoke.js';
26
+ import { formatWatchSpriteLine } from '../util/watchCopy.js';
26
27
  import { runPush } from './push.js';
27
28
  export async function syncCommand(opts) {
28
29
  // Sweep any leaked `<file>.<pid>.<hex>.tmp` files left behind by a prior
@@ -50,20 +51,33 @@ async function watchLoop(opts) {
50
51
  // Commander already validated the numeric value (2–3600); the `?? 2`
51
52
  // covers the bare boolean `-w` form.
52
53
  const intervalSec = typeof opts.watch === 'string' ? parseInt(opts.watch, 10) : 2;
53
- // Header — print once on start. Project/asset count is best-effort; if the
54
- // first manifest fetch fails we still want the loop to come up and retry.
55
- let headerCount = null;
56
- try {
57
- const state = await loadState();
58
- headerCount = state.assets ? Object.keys(state.assets).length : null;
59
- }
60
- catch {
61
- // ignore — header is cosmetic
62
- }
54
+ // Header — print once on start. Counts are best-effort; if the first
55
+ // manifest fetch fails we still want the loop to come up and retry.
63
56
  console.log(kleur.bold('👀 MagicPixel watching for changes…'));
64
57
  console.log(` Edit at: ${kleur.cyan('https://magicpixel.art')}`);
65
- if (headerCount !== null)
66
- console.log(kleur.dim(` Sprites: ${headerCount}`));
58
+ let countSpinner = null;
59
+ if (!opts.quiet) {
60
+ try {
61
+ const config = await loadConfig();
62
+ const kind = await detectProjectKind();
63
+ if (isEngineKind(kind) && (config.connect?.length ?? 0) > 0) {
64
+ countSpinner = ora({ text: countingSpritesText(0), spinner: 'dots' }).start();
65
+ }
66
+ }
67
+ catch {
68
+ /* header is cosmetic */
69
+ }
70
+ }
71
+ const header = await loadWatchHeader(countSpinner ? (n) => { countSpinner.text = countingSpritesText(n); } : undefined);
72
+ if (countSpinner) {
73
+ if (header.line)
74
+ countSpinner.succeed(header.line.trim());
75
+ else
76
+ countSpinner.stop();
77
+ }
78
+ else if (header.line && !opts.quiet) {
79
+ console.log(kleur.dim(header.line));
80
+ }
67
81
  console.log(kleur.dim(` Polling: every ${intervalSec}s (slows when idle) · Stop: Ctrl+C`));
68
82
  console.log();
69
83
  let stopping = false;
@@ -106,12 +120,12 @@ async function watchLoop(opts) {
106
120
  wakeStop();
107
121
  process.stdout.write('\x1b[2K\r');
108
122
  if (inFlight) {
109
- console.log(kleur.dim(`[watch] finishing current sync… (${signal} again to force quit)`));
123
+ console.log(kleur.dim(`Still finishing this check… (${signal} again to stop now)`));
110
124
  process.once(signal, () => process.exit(signal === 'SIGINT' ? 130 : 143));
111
125
  }
112
126
  else {
113
127
  if (!opts.quiet)
114
- console.log(kleur.dim('[watch] stopped.'));
128
+ console.log(kleur.dim('Stopped watching.'));
115
129
  // Preserve any exit code already set by a prior failed tick.
116
130
  process.exit(process.exitCode ?? 0);
117
131
  }
@@ -122,8 +136,14 @@ async function watchLoop(opts) {
122
136
  if (inFlight || stopping)
123
137
  return;
124
138
  inFlight = true;
139
+ const onStatus = (msg) => {
140
+ if (opts.quiet)
141
+ return;
142
+ process.stdout.write(`\r\x1b[2K${kleur.dim(`${timestamp()} ${msg}`)}`);
143
+ };
125
144
  try {
126
- const r = await runOnce({ ...opts, watch: false }, { watchMode: true });
145
+ onStatus('Checking MagicPixel…');
146
+ const r = await runOnce({ ...opts, watch: false }, { watchMode: true, onStatus, gameIndex: header.gameIndex });
127
147
  // Reset backoff on any successful tick. Resume message fires once when
128
148
  // we recover from an auth pause — `getApiKey()` re-reads
129
149
  // .magicpixel/credentials on every call, so a `magicpixel login` in
@@ -165,7 +185,7 @@ async function watchLoop(opts) {
165
185
  printChanges(r, /* indent */ ' ');
166
186
  }
167
187
  else {
168
- process.stdout.write(`\r\x1b[2K${kleur.dim(`${timestamp()} no changes (${r.unchanged} unchanged)`)}`);
188
+ process.stdout.write(`\r\x1b[2K${kleur.dim(`${timestamp()} Waiting for edits… (${r.unchanged} up to date)`)}`);
169
189
  }
170
190
  }
171
191
  catch (e) {
@@ -222,6 +242,7 @@ async function watchLoop(opts) {
222
242
  }
223
243
  }
224
244
  finally {
245
+ header.gameIndex = undefined;
225
246
  inFlight = false;
226
247
  }
227
248
  };
@@ -248,7 +269,7 @@ async function watchLoop(opts) {
248
269
  await tick();
249
270
  }
250
271
  if (!opts.quiet)
251
- console.log(kleur.dim('[watch] stopped.'));
272
+ console.log(kleur.dim('Stopped watching.'));
252
273
  // Preserve any non-zero exit code set by a failed tick — don't mask a
253
274
  // download failure with a clean exit just because the user pressed Ctrl+C.
254
275
  process.exit(process.exitCode ?? 0);
@@ -309,12 +330,35 @@ export function classifyTickError(err, state) {
309
330
  }
310
331
  return { kind: 'other', nextBackoffSec: Math.min(state.backoffSec * 2, 60) };
311
332
  }
333
+ async function loadWatchHeader(onProgress) {
334
+ try {
335
+ const config = await loadConfig();
336
+ const state = await loadState();
337
+ const lastPulled = state.assets ? Object.keys(state.assets).length : 0;
338
+ let workingSet = 0;
339
+ let gameIndex;
340
+ const kind = await detectProjectKind();
341
+ if (isEngineKind(kind) && (config.connect?.length ?? 0) > 0) {
342
+ gameIndex = await indexGamePngs(kind, process.cwd(), config.outDir, { onProgress });
343
+ workingSet = matchConnectGlobs(gameIndex, config.connect).total;
344
+ }
345
+ else if (state.synced) {
346
+ workingSet = Object.keys(state.synced).length;
347
+ }
348
+ return { line: formatWatchSpriteLine({ workingSet, lastPulled }), gameIndex };
349
+ }
350
+ catch {
351
+ return { line: null };
352
+ }
353
+ }
312
354
  async function runOnce(opts, runOpts = {}) {
313
355
  const config = await loadConfig();
314
356
  const state = await loadState();
315
357
  const startedAt = new Date().toISOString();
316
358
  const concurrency = Math.max(1, Math.min(opts.concurrency ?? 6, 16));
317
359
  const verbose = !opts.quiet && !runOpts.watchMode;
360
+ const live = !opts.quiet;
361
+ const onStatus = runOpts.onStatus;
318
362
  const shouldPrune = opts.prune !== false; // commander: --no-prune sets false
319
363
  if (verbose && config.endpoint) {
320
364
  console.log(kleur.yellow(`! using custom endpoint: ${config.endpoint}`));
@@ -324,6 +368,8 @@ async function runOnce(opts, runOpts = {}) {
324
368
  const spinner = verbose
325
369
  ? ora(since ? `Fetching manifest since ${humanTime(since)}…` : 'Fetching manifest…').start()
326
370
  : null;
371
+ if (!spinner)
372
+ onStatus?.(since ? 'Checking MagicPixel for edits…' : 'Fetching your sprites from MagicPixel…');
327
373
  let manifest;
328
374
  try {
329
375
  manifest = await fetchAllManifest(config, since);
@@ -359,10 +405,14 @@ async function runOnce(opts, runOpts = {}) {
359
405
  // Unity projects sync only the artboards flagged "Sync to Unity" in the
360
406
  // editor (parity with the in-app sync button). `unitySyncAll: true` in
361
407
  // magicpixel.json opts back into everything.
408
+ onStatus?.('Looking through your game sprites…');
362
409
  const projectKind = await detectProjectKind();
363
- const gameIndex = isEngineKind(projectKind)
364
- ? await indexGamePngs(projectKind, process.cwd(), config.outDir)
365
- : { files: [], capped: false };
410
+ const gameIndex = runOpts.gameIndex
411
+ ?? (isEngineKind(projectKind)
412
+ ? await indexGamePngs(projectKind, process.cwd(), config.outDir, {
413
+ onProgress: (n) => onStatus?.(`Looking through your game sprites… ${n.toLocaleString('en-US')}`),
414
+ })
415
+ : { files: [], capped: false });
366
416
  const connected = matchConnectGlobs(gameIndex, config.connect ?? []);
367
417
  const sourceByKey = collectSourceRelMap(connected.entries, state.synced);
368
418
  if (verbose && gameIndex.capped) {
@@ -424,7 +474,7 @@ async function runOnce(opts, runOpts = {}) {
424
474
  bytesSaved: 0,
425
475
  renamed: [],
426
476
  };
427
- await maybePushLocalSprites(config.push, verbose, gameIndex);
477
+ await maybePushLocalSprites(config.push, verbose, gameIndex, live && !!runOpts.watchMode);
428
478
  return empty;
429
479
  }
430
480
  const skipped = manifest.length - filtered.entries.length;
@@ -460,6 +510,9 @@ async function runOnce(opts, runOpts = {}) {
460
510
  // the dominant wall-clock cost of an incremental sync. We cache each result
461
511
  // so the download loop can reuse it for `If-None-Match` ETags instead of
462
512
  // re-hashing the same file moments later.
513
+ onStatus?.(manifest.length > 0
514
+ ? `Comparing ${manifest.length.toLocaleString('en-US')} sprite${manifest.length === 1 ? '' : 's'} with files on disk…`
515
+ : 'Checking MagicPixel…');
463
516
  const diffLimit = createLimit(concurrency);
464
517
  const shaByEntryId = new Map();
465
518
  const toDownload = [];
@@ -679,7 +732,8 @@ async function runOnce(opts, runOpts = {}) {
679
732
  bytesSaved,
680
733
  };
681
734
  let progress = null;
682
- if (verbose && toDownload.length > 0) {
735
+ if (live && toDownload.length > 0) {
736
+ onStatus?.(`Pulling ${toDownload.length.toLocaleString('en-US')} updated sprite${toDownload.length === 1 ? '' : 's'}…`);
683
737
  progress = ora({ text: progressText(0, toDownload.length, 0), spinner: 'dots' }).start();
684
738
  }
685
739
  const run = createLimit(concurrency);
@@ -771,10 +825,15 @@ async function runOnce(opts, runOpts = {}) {
771
825
  })));
772
826
  const downloaded = result.added.length + result.modified.length;
773
827
  if (progress) {
774
- if (result.failed === 0)
828
+ if (runOpts.watchMode) {
829
+ progress.stop();
830
+ }
831
+ else if (result.failed === 0) {
775
832
  progress.succeed(`Downloaded ${downloaded} (${formatBytes(result.bytesIn)})`);
776
- else
833
+ }
834
+ else {
777
835
  progress.warn(`Downloaded ${downloaded}, failed ${result.failed} (${formatBytes(result.bytesIn)})`);
836
+ }
778
837
  }
779
838
  // Unity: write pixel-art-correct `.meta` sidecars so sprites import with
780
839
  // point filtering / no compression instead of Unity's blurry defaults —
@@ -982,21 +1041,22 @@ async function runOnce(opts, runOpts = {}) {
982
1041
  if (renamed.length > 0)
983
1042
  printRenames(renamed, { withHints: true });
984
1043
  }
985
- await maybePushLocalSprites(config.push, verbose, gameIndex);
1044
+ onStatus?.('Checking your game files…');
1045
+ await maybePushLocalSprites(config.push, verbose, gameIndex, live && !!runOpts.watchMode);
986
1046
  if (result.failed)
987
1047
  process.exitCode = 1;
988
1048
  return result;
989
1049
  }
990
- async function maybePushLocalSprites(pushEnabled, verbose, gameIndex) {
1050
+ async function maybePushLocalSprites(pushEnabled, verbose, gameIndex, announceErrors = verbose) {
991
1051
  if (pushEnabled === false)
992
1052
  return null;
993
1053
  try {
994
1054
  return await runPush({ quiet: !verbose, gameIndex });
995
1055
  }
996
1056
  catch (e) {
997
- if (verbose) {
1057
+ if (announceErrors) {
998
1058
  console.log();
999
- console.log(kleur.yellow(`! could not push working-set sprites into MagicPixel: ${e.message}`));
1059
+ console.log(kleur.yellow(`! could not update MagicPixel from your game files: ${e.message}`));
1000
1060
  console.log(kleur.dim(` Fix: run \`${cmd('push')}\` once the connection is healthy.`));
1001
1061
  }
1002
1062
  return null;
@@ -94,8 +94,30 @@ function toEntry(abs, cwdAbs, assetRoot) {
94
94
  return null;
95
95
  return { abs, sourceRel, adoptRel, key: adopted.path.join('/') };
96
96
  }
97
+ /** Spinner / status copy while walking the game tree. */
98
+ export function countingSpritesText(found) {
99
+ return found > 0
100
+ ? `Counting sprites in your game… ${found.toLocaleString('en-US')}`
101
+ : 'Counting sprites in your game…';
102
+ }
103
+ function bindScanProgress(onProgress) {
104
+ if (!onProgress)
105
+ return () => { };
106
+ let lastAt = 0;
107
+ let lastN = 0;
108
+ return (n) => {
109
+ if (n === lastN)
110
+ return;
111
+ const now = Date.now();
112
+ if (lastN === 0 || now - lastAt >= 80) {
113
+ lastAt = now;
114
+ lastN = n;
115
+ onProgress(n);
116
+ }
117
+ };
118
+ }
97
119
  /** Walk the engine tree and return index entries. Does not copy files. */
98
- export async function indexGamePngs(kind, cwd = process.cwd(), outDir = '') {
120
+ export async function indexGamePngs(kind, cwd = process.cwd(), outDir = '', opts = {}) {
99
121
  const empty = { files: [], capped: false };
100
122
  if (!isEngineKind(kind))
101
123
  return empty;
@@ -107,7 +129,10 @@ export async function indexGamePngs(kind, cwd = process.cwd(), outDir = '') {
107
129
  const assetRoot = namedRoot ? resolveChildDir(cwd, namedRoot) : null;
108
130
  const found = [];
109
131
  const io = createLimit(SCAN_WALK_CONCURRENCY);
110
- await walkPngs(scanRoot, scanRoot, destNorm, found, MAX_GAME_INDEX + 1, io);
132
+ const report = bindScanProgress(opts.onProgress);
133
+ await walkPngs(scanRoot, scanRoot, destNorm, found, MAX_GAME_INDEX + 1, io, report);
134
+ if (found.length > 0)
135
+ opts.onProgress?.(found.length);
111
136
  found.sort((a, b) => a.localeCompare(b));
112
137
  const capped = found.length > MAX_GAME_INDEX;
113
138
  if (capped)
@@ -136,7 +161,7 @@ export function searchGameIndex(index, query) {
136
161
  return [];
137
162
  return index.files.filter((e) => e.sourceRel.toLowerCase().includes(q) || e.key.includes(q) || e.adoptRel.toLowerCase().includes(q));
138
163
  }
139
- async function walkPngs(dir, root, destNorm, out, limit, io) {
164
+ async function walkPngs(dir, root, destNorm, out, limit, io, onFound) {
140
165
  if (out.length >= limit)
141
166
  return;
142
167
  let entries;
@@ -169,9 +194,10 @@ async function walkPngs(dir, root, destNorm, out, limit, io) {
169
194
  }
170
195
  else if (ent.isFile() && ent.name.toLowerCase().endsWith('.png')) {
171
196
  out.push(full);
197
+ onFound?.(out.length);
172
198
  }
173
199
  }
174
200
  if (subdirs.length === 0 || out.length >= limit)
175
201
  return;
176
- await Promise.all(subdirs.map((d) => io(() => walkPngs(d, root, destNorm, out, limit, io))));
202
+ await Promise.all(subdirs.map((d) => io(() => walkPngs(d, root, destNorm, out, limit, io, onFound))));
177
203
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Watch-mode header copy. `state.assets` is only the last *successful* pull,
3
+ * so a connected working set of 2k sprites used to show as "Sprites: 469"
4
+ * while catch-up was still pending. Show both numbers when they differ.
5
+ */
6
+ function fmt(n) {
7
+ return n.toLocaleString('en-US');
8
+ }
9
+ /** One header line, or null when we have nothing useful to print. */
10
+ export function formatWatchSpriteLine(c) {
11
+ const set = c.workingSet > 0 ? c.workingSet : 0;
12
+ const pulled = c.lastPulled > 0 ? c.lastPulled : 0;
13
+ if (set === 0 && pulled === 0)
14
+ return null;
15
+ if (set > 0 && pulled > 0 && set !== pulled) {
16
+ return ` Sprites: ${fmt(set)} in your game · ${fmt(pulled)} last pulled from MagicPixel`;
17
+ }
18
+ if (set > 0)
19
+ return ` Sprites: ${fmt(set)} in your working set`;
20
+ return ` Sprites: ${fmt(pulled)}`;
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magicpixelart/cli",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
4
4
  "description": "Sync MagicPixel pixel-art assets to your local project.",
5
5
  "type": "module",
6
6
  "bin": {