simvyn 2.0.0 → 2.1.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.
@@ -38,8 +38,8 @@
38
38
  }
39
39
  </style>
40
40
  <title>simvyn</title>
41
- <script type="module" crossorigin src="/assets/index-SgS9nwZ7.js"></script>
42
- <link rel="stylesheet" crossorigin href="/assets/index-Pv0mHRGr.css">
41
+ <script type="module" crossorigin src="/assets/index-jHtyqXZH.js"></script>
42
+ <link rel="stylesheet" crossorigin href="/assets/index-B6SC-KfX.css">
43
43
  </head>
44
44
  <body>
45
45
  <div id="root"></div>
package/dist/index.js CHANGED
@@ -1488,9 +1488,64 @@ var init_storage = __esm({
1488
1488
  }
1489
1489
  });
1490
1490
 
1491
+ // ../core/src/favourites.ts
1492
+ async function getFavourites() {
1493
+ return await storage.read(KEY) ?? [];
1494
+ }
1495
+ async function addFavourite(id) {
1496
+ const favs = await getFavourites();
1497
+ if (favs.includes(id)) return;
1498
+ favs.push(id);
1499
+ favs.sort();
1500
+ await storage.write(KEY, favs);
1501
+ }
1502
+ async function removeFavourite(id) {
1503
+ const favs = await getFavourites();
1504
+ const idx = favs.indexOf(id);
1505
+ if (idx === -1) return;
1506
+ favs.splice(idx, 1);
1507
+ await storage.write(KEY, favs);
1508
+ }
1509
+ async function cleanupStaleFavourites(currentDeviceIds, missCountMap, threshold = 3) {
1510
+ const favs = await getFavourites();
1511
+ const currentSet = new Set(currentDeviceIds);
1512
+ const toRemove = [];
1513
+ for (const id of favs) {
1514
+ if (currentSet.has(id)) {
1515
+ missCountMap.delete(id);
1516
+ } else {
1517
+ const count = (missCountMap.get(id) ?? 0) + 1;
1518
+ missCountMap.set(id, count);
1519
+ if (count > threshold) {
1520
+ toRemove.push(id);
1521
+ }
1522
+ }
1523
+ }
1524
+ if (toRemove.length > 0) {
1525
+ const cleaned = favs.filter((id) => !toRemove.includes(id));
1526
+ await storage.write(KEY, cleaned);
1527
+ for (const id of toRemove) {
1528
+ missCountMap.delete(id);
1529
+ }
1530
+ return cleaned;
1531
+ }
1532
+ return favs;
1533
+ }
1534
+ var storage, KEY;
1535
+ var init_favourites = __esm({
1536
+ "../core/src/favourites.ts"() {
1537
+ "use strict";
1538
+ init_storage();
1539
+ storage = createModuleStorage("favourites");
1540
+ KEY = "favourites";
1541
+ }
1542
+ });
1543
+
1491
1544
  // ../core/src/index.ts
1492
1545
  var src_exports = {};
1493
1546
  __export(src_exports, {
1547
+ addFavourite: () => addFavourite,
1548
+ cleanupStaleFavourites: () => cleanupStaleFavourites,
1494
1549
  createAndroidAdapter: () => createAndroidAdapter,
1495
1550
  createAvailableAdapters: () => createAvailableAdapters,
1496
1551
  createDeviceManager: () => createDeviceManager,
@@ -1498,12 +1553,14 @@ __export(src_exports, {
1498
1553
  createModuleStorage: () => createModuleStorage,
1499
1554
  createProcessManager: () => createProcessManager,
1500
1555
  getDevicectlStatus: () => getDevicectlStatus,
1556
+ getFavourites: () => getFavourites,
1501
1557
  getSimvynDir: () => getSimvynDir,
1502
1558
  hasBinary: () => hasBinary,
1503
1559
  isAndroidPhysical: () => isAndroidPhysical,
1504
1560
  isLinux: () => isLinux,
1505
1561
  isMacOS: () => isMacOS,
1506
1562
  isPhysicalDevice: () => isPhysicalDevice,
1563
+ removeFavourite: () => removeFavourite,
1507
1564
  setVerbose: () => setVerbose,
1508
1565
  stripPhysicalPrefix: () => stripPhysicalPrefix,
1509
1566
  verboseExec: () => verboseExec,
@@ -1517,6 +1574,7 @@ var init_src = __esm({
1517
1574
  init_platform();
1518
1575
  init_process_manager();
1519
1576
  init_storage();
1577
+ init_favourites();
1520
1578
  init_verbose_exec();
1521
1579
  }
1522
1580
  });
@@ -3685,16 +3743,16 @@ function getStarterCollections() {
3685
3743
  // ../modules/collections/routes.ts
3686
3744
  var activeRuns = /* @__PURE__ */ new Map();
3687
3745
  async function collectionsRoutes(fastify) {
3688
- const storage3 = createModuleStorage("collections");
3746
+ const storage4 = createModuleStorage("collections");
3689
3747
  async function readCollections() {
3690
- const stored = await storage3.read("collections");
3748
+ const stored = await storage4.read("collections");
3691
3749
  if (stored && stored.length > 0) return stored;
3692
3750
  const starters = getStarterCollections();
3693
3751
  await writeCollections(starters);
3694
3752
  return starters;
3695
3753
  }
3696
3754
  async function writeCollections(collections) {
3697
- await storage3.write("collections", collections);
3755
+ await storage4.write("collections", collections);
3698
3756
  }
3699
3757
  fastify.get("/actions", async () => {
3700
3758
  const descriptors = getActionDescriptors();
@@ -3859,8 +3917,8 @@ var collectionsModule = {
3859
3917
  const cmd = program2.command("collections").description("Manage device action collections");
3860
3918
  cmd.command("list").description("List all saved collections").action(async () => {
3861
3919
  const { createModuleStorage: createModuleStorage2 } = await Promise.resolve().then(() => (init_src(), src_exports));
3862
- const storage3 = createModuleStorage2("collections");
3863
- const collections = await storage3.read(
3920
+ const storage4 = createModuleStorage2("collections");
3921
+ const collections = await storage4.read(
3864
3922
  "collections"
3865
3923
  ) ?? [];
3866
3924
  if (collections.length === 0) {
@@ -3877,8 +3935,8 @@ var collectionsModule = {
3877
3935
  });
3878
3936
  cmd.command("show <id>").description("Show a collection's details").action(async (id) => {
3879
3937
  const { createModuleStorage: createModuleStorage2 } = await Promise.resolve().then(() => (init_src(), src_exports));
3880
- const storage3 = createModuleStorage2("collections");
3881
- const collections = await storage3.read("collections") ?? [];
3938
+ const storage4 = createModuleStorage2("collections");
3939
+ const collections = await storage4.read("collections") ?? [];
3882
3940
  const collection = collections.find((c) => c.id.startsWith(id));
3883
3941
  if (!collection) {
3884
3942
  console.error(`Collection not found: ${id}`);
@@ -3896,8 +3954,8 @@ var collectionsModule = {
3896
3954
  });
3897
3955
  cmd.command("create <name>").description("Create a new empty collection").option("-d, --description <desc>", "Collection description").action(async (name, opts) => {
3898
3956
  const { createModuleStorage: createModuleStorage2 } = await Promise.resolve().then(() => (init_src(), src_exports));
3899
- const storage3 = createModuleStorage2("collections");
3900
- const collections = await storage3.read("collections") ?? [];
3957
+ const storage4 = createModuleStorage2("collections");
3958
+ const collections = await storage4.read("collections") ?? [];
3901
3959
  const collection = {
3902
3960
  id: crypto.randomUUID(),
3903
3961
  name,
@@ -3908,13 +3966,13 @@ var collectionsModule = {
3908
3966
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3909
3967
  };
3910
3968
  collections.push(collection);
3911
- await storage3.write("collections", collections);
3969
+ await storage4.write("collections", collections);
3912
3970
  console.log(`Created collection: ${collection.id}`);
3913
3971
  });
3914
3972
  cmd.command("delete <id>").description("Delete a collection").action(async (id) => {
3915
3973
  const { createModuleStorage: createModuleStorage2 } = await Promise.resolve().then(() => (init_src(), src_exports));
3916
- const storage3 = createModuleStorage2("collections");
3917
- const collections = await storage3.read("collections") ?? [];
3974
+ const storage4 = createModuleStorage2("collections");
3975
+ const collections = await storage4.read("collections") ?? [];
3918
3976
  const idx = collections.findIndex((c) => c.id.startsWith(id));
3919
3977
  if (idx === -1) {
3920
3978
  console.error(`Collection not found: ${id}`);
@@ -3922,13 +3980,13 @@ var collectionsModule = {
3922
3980
  }
3923
3981
  const removed = collections[idx];
3924
3982
  collections.splice(idx, 1);
3925
- await storage3.write("collections", collections);
3983
+ await storage4.write("collections", collections);
3926
3984
  console.log(`Deleted collection: ${removed.name}`);
3927
3985
  });
3928
3986
  cmd.command("duplicate <id>").description("Duplicate a collection").option("-n, --name <newName>", "Name for the duplicate").action(async (id, opts) => {
3929
3987
  const { createModuleStorage: createModuleStorage2 } = await Promise.resolve().then(() => (init_src(), src_exports));
3930
- const storage3 = createModuleStorage2("collections");
3931
- const collections = await storage3.read("collections") ?? [];
3988
+ const storage4 = createModuleStorage2("collections");
3989
+ const collections = await storage4.read("collections") ?? [];
3932
3990
  const original = collections.find((c) => c.id.startsWith(id));
3933
3991
  if (!original) {
3934
3992
  console.error(`Collection not found: ${id}`);
@@ -3944,19 +4002,19 @@ var collectionsModule = {
3944
4002
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3945
4003
  };
3946
4004
  collections.push(duplicate);
3947
- await storage3.write("collections", collections);
4005
+ await storage4.write("collections", collections);
3948
4006
  console.log(`Duplicated collection: ${duplicate.id}`);
3949
4007
  });
3950
4008
  cmd.command("apply <name-or-id>").description("Execute a collection on one or more devices").argument("<devices...>", "Device IDs to execute on").action(async (nameOrId, devices) => {
3951
4009
  const { createAvailableAdapters: createAvailableAdapters2, createDeviceManager: createDeviceManager2 } = await Promise.resolve().then(() => (init_src(), src_exports));
3952
4010
  const { createModuleStorage: createModuleStorage2 } = await Promise.resolve().then(() => (init_src(), src_exports));
3953
4011
  const { runCollection: runCollection2 } = await Promise.resolve().then(() => (init_execution_engine(), execution_engine_exports));
3954
- const storage3 = createModuleStorage2("collections");
4012
+ const storage4 = createModuleStorage2("collections");
3955
4013
  const adapters = await createAvailableAdapters2();
3956
4014
  const dm = createDeviceManager2(adapters);
3957
4015
  try {
3958
4016
  await dm.refresh();
3959
- const collections = await storage3.read("collections") ?? [];
4017
+ const collections = await storage4.read("collections") ?? [];
3960
4018
  const collection = collections.find(
3961
4019
  (c) => c.id.startsWith(nameOrId) || c.name.toLowerCase() === nameOrId.toLowerCase()
3962
4020
  );
@@ -4716,7 +4774,7 @@ init_src();
4716
4774
  import { randomUUID } from "crypto";
4717
4775
  var MAX_HISTORY = 50;
4718
4776
  async function deepLinkRoutes(fastify) {
4719
- const storage3 = createModuleStorage("deep-links");
4777
+ const storage4 = createModuleStorage("deep-links");
4720
4778
  fastify.post("/open", async (req, reply) => {
4721
4779
  const { deviceId, url } = req.body;
4722
4780
  const device = fastify.deviceManager.devices.find((d) => d.id === deviceId);
@@ -4728,7 +4786,7 @@ async function deepLinkRoutes(fastify) {
4728
4786
  return reply.status(400).send({ error: "Deep links not supported for this platform" });
4729
4787
  try {
4730
4788
  await adapter.openUrl(device.id, url);
4731
- const history = await storage3.read("history") ?? [];
4789
+ const history = await storage4.read("history") ?? [];
4732
4790
  history.unshift({
4733
4791
  url,
4734
4792
  deviceId: device.id,
@@ -4736,21 +4794,21 @@ async function deepLinkRoutes(fastify) {
4736
4794
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4737
4795
  });
4738
4796
  if (history.length > MAX_HISTORY) history.length = MAX_HISTORY;
4739
- await storage3.write("history", history);
4797
+ await storage4.write("history", history);
4740
4798
  return { success: true, url, deviceId: device.id };
4741
4799
  } catch (err) {
4742
4800
  return reply.status(500).send({ error: err.message });
4743
4801
  }
4744
4802
  });
4745
4803
  fastify.get("/favorites", async () => {
4746
- const favorites = await storage3.read("favorites") ?? [];
4804
+ const favorites = await storage4.read("favorites") ?? [];
4747
4805
  return { favorites };
4748
4806
  });
4749
4807
  fastify.post(
4750
4808
  "/favorites",
4751
4809
  async (req) => {
4752
4810
  const { url, label, bundleId } = req.body;
4753
- const favorites = await storage3.read("favorites") ?? [];
4811
+ const favorites = await storage4.read("favorites") ?? [];
4754
4812
  const favorite = {
4755
4813
  id: randomUUID(),
4756
4814
  url,
@@ -4759,21 +4817,21 @@ async function deepLinkRoutes(fastify) {
4759
4817
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4760
4818
  };
4761
4819
  favorites.push(favorite);
4762
- await storage3.write("favorites", favorites);
4820
+ await storage4.write("favorites", favorites);
4763
4821
  return favorite;
4764
4822
  }
4765
4823
  );
4766
4824
  fastify.delete("/favorites/:id", async (req, reply) => {
4767
4825
  const { id } = req.params;
4768
- const favorites = await storage3.read("favorites") ?? [];
4826
+ const favorites = await storage4.read("favorites") ?? [];
4769
4827
  const idx = favorites.findIndex((f) => f.id === id);
4770
4828
  if (idx === -1) return reply.status(404).send({ error: "Favorite not found" });
4771
4829
  favorites.splice(idx, 1);
4772
- await storage3.write("favorites", favorites);
4830
+ await storage4.write("favorites", favorites);
4773
4831
  return { success: true };
4774
4832
  });
4775
4833
  fastify.get("/history", async () => {
4776
- const history = await storage3.read("history") ?? [];
4834
+ const history = await storage4.read("history") ?? [];
4777
4835
  return { history };
4778
4836
  });
4779
4837
  }
@@ -4820,6 +4878,7 @@ var deepLinksModule = {
4820
4878
  var manifest_default6 = deepLinksModule;
4821
4879
 
4822
4880
  // ../modules/device-management/routes.ts
4881
+ init_src();
4823
4882
  async function deviceRoutes(fastify) {
4824
4883
  fastify.get("/list", async () => {
4825
4884
  return { devices: fastify.deviceManager.devices };
@@ -5040,6 +5099,17 @@ async function deviceRoutes(fastify) {
5040
5099
  return reply.status(500).send({ error: err.message });
5041
5100
  }
5042
5101
  });
5102
+ fastify.get("/favourites", async () => {
5103
+ return { favourites: await getFavourites() };
5104
+ });
5105
+ fastify.post("/favourites", async (req) => {
5106
+ await addFavourite(req.body.deviceId);
5107
+ return { success: true };
5108
+ });
5109
+ fastify.delete("/favourites", async (req) => {
5110
+ await removeFavourite(req.body.deviceId);
5111
+ return { success: true };
5112
+ });
5043
5113
  fastify.post("/refresh", async () => {
5044
5114
  const devices = await fastify.deviceManager.refresh();
5045
5115
  return { devices };
@@ -7807,7 +7877,7 @@ import { tmpdir as tmpdir7 } from "os";
7807
7877
  import { join as join14 } from "path";
7808
7878
  import { promisify as promisify9 } from "util";
7809
7879
  var execFileAsync9 = promisify9(execFile9);
7810
- var storage = createModuleStorage("push");
7880
+ var storage2 = createModuleStorage("push");
7811
7881
  async function pushRoutes(fastify) {
7812
7882
  fastify.post(
7813
7883
  "/send",
@@ -7828,7 +7898,7 @@ async function pushRoutes(fastify) {
7828
7898
  try {
7829
7899
  await writeFile4(tmpFile, JSON.stringify(payload), "utf-8");
7830
7900
  await execFileAsync9("xcrun", ["simctl", "push", deviceId, bundleId, tmpFile]);
7831
- const history = await storage.read("history") ?? [];
7901
+ const history = await storage2.read("history") ?? [];
7832
7902
  history.unshift({
7833
7903
  bundleId,
7834
7904
  payload,
@@ -7837,7 +7907,7 @@ async function pushRoutes(fastify) {
7837
7907
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7838
7908
  });
7839
7909
  if (history.length > 50) history.length = 50;
7840
- await storage.write("history", history);
7910
+ await storage2.write("history", history);
7841
7911
  return { success: true };
7842
7912
  } catch (err) {
7843
7913
  return reply.status(500).send({ error: err.message });
@@ -7852,7 +7922,7 @@ async function pushRoutes(fastify) {
7852
7922
  return { templates: pushTemplates2 };
7853
7923
  });
7854
7924
  fastify.get("/payloads", async () => {
7855
- const payloads = await storage.read("payloads") ?? [];
7925
+ const payloads = await storage2.read("payloads") ?? [];
7856
7926
  return { payloads };
7857
7927
  });
7858
7928
  fastify.post(
@@ -7862,7 +7932,7 @@ async function pushRoutes(fastify) {
7862
7932
  if (!name || !payload) {
7863
7933
  return reply.status(400).send({ error: "name and payload are required" });
7864
7934
  }
7865
- const payloads = await storage.read("payloads") ?? [];
7935
+ const payloads = await storage2.read("payloads") ?? [];
7866
7936
  const created = {
7867
7937
  id: randomUUID2(),
7868
7938
  name,
@@ -7871,21 +7941,21 @@ async function pushRoutes(fastify) {
7871
7941
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
7872
7942
  };
7873
7943
  payloads.push(created);
7874
- await storage.write("payloads", payloads);
7944
+ await storage2.write("payloads", payloads);
7875
7945
  return created;
7876
7946
  }
7877
7947
  );
7878
7948
  fastify.delete("/payloads/:id", async (req, reply) => {
7879
7949
  const { id } = req.params;
7880
- const payloads = await storage.read("payloads") ?? [];
7950
+ const payloads = await storage2.read("payloads") ?? [];
7881
7951
  const idx = payloads.findIndex((p) => p.id === id);
7882
7952
  if (idx === -1) return reply.status(404).send({ error: "Payload not found" });
7883
7953
  payloads.splice(idx, 1);
7884
- await storage.write("payloads", payloads);
7954
+ await storage2.write("payloads", payloads);
7885
7955
  return { success: true };
7886
7956
  });
7887
7957
  fastify.get("/history", async () => {
7888
- const history = await storage.read("history") ?? [];
7958
+ const history = await storage2.read("history") ?? [];
7889
7959
  return { history };
7890
7960
  });
7891
7961
  }
@@ -8011,7 +8081,7 @@ function getActiveRecordings() {
8011
8081
  }
8012
8082
 
8013
8083
  // ../modules/screenshot/routes.ts
8014
- var storage2 = createModuleStorage("screenshot");
8084
+ var storage3 = createModuleStorage("screenshot");
8015
8085
  var capturesDir = join15(getSimvynDir(), "screenshot", "captures");
8016
8086
  var recordingsDir = join15(getSimvynDir(), "screenshot", "recordings");
8017
8087
  async function ensureDirs() {
@@ -8019,9 +8089,9 @@ async function ensureDirs() {
8019
8089
  await mkdir4(recordingsDir, { recursive: true });
8020
8090
  }
8021
8091
  async function appendHistory(entry) {
8022
- const history = await storage2.read("history") ?? [];
8092
+ const history = await storage3.read("history") ?? [];
8023
8093
  history.push(entry);
8024
- await storage2.write("history", history);
8094
+ await storage3.write("history", history);
8025
8095
  }
8026
8096
  async function screenshotRoutes(fastify) {
8027
8097
  await ensureDirs();
@@ -8112,12 +8182,12 @@ async function screenshotRoutes(fastify) {
8112
8182
  }
8113
8183
  });
8114
8184
  fastify.get("/history", async () => {
8115
- const history = await storage2.read("history") ?? [];
8185
+ const history = await storage3.read("history") ?? [];
8116
8186
  return history;
8117
8187
  });
8118
8188
  fastify.delete("/history/:filename", async (req, reply) => {
8119
8189
  const { filename } = req.params;
8120
- const history = await storage2.read("history") ?? [];
8190
+ const history = await storage3.read("history") ?? [];
8121
8191
  const idx = history.findIndex((e) => e.filename === filename);
8122
8192
  if (idx === -1) return reply.status(404).send({ error: "Entry not found" });
8123
8193
  const entry = history[idx];
@@ -8126,11 +8196,11 @@ async function screenshotRoutes(fastify) {
8126
8196
  } catch {
8127
8197
  }
8128
8198
  history.splice(idx, 1);
8129
- await storage2.write("history", history);
8199
+ await storage3.write("history", history);
8130
8200
  return { deleted: true };
8131
8201
  });
8132
8202
  fastify.delete("/history", async () => {
8133
- const history = await storage2.read("history") ?? [];
8203
+ const history = await storage3.read("history") ?? [];
8134
8204
  const count = history.length;
8135
8205
  for (const entry of history) {
8136
8206
  try {
@@ -8138,7 +8208,7 @@ async function screenshotRoutes(fastify) {
8138
8208
  } catch {
8139
8209
  }
8140
8210
  }
8141
- await storage2.write("history", []);
8211
+ await storage3.write("history", []);
8142
8212
  return { cleared: true, count };
8143
8213
  });
8144
8214
  fastify.get("/download/:filename", async (req, reply) => {
@@ -8482,6 +8552,45 @@ function registerDeviceCommand(program2) {
8482
8552
  process.exit(1);
8483
8553
  }
8484
8554
  });
8555
+ device.command("favourite <id>").description("Add a device to favourites").action(async (id) => {
8556
+ try {
8557
+ const { device: dev } = await findDevice(id);
8558
+ await addFavourite(dev.id);
8559
+ console.log(`\u2713 Added ${dev.name} to favourites`);
8560
+ } catch (err) {
8561
+ console.error(`Failed to favourite device: ${err.message}`);
8562
+ process.exit(1);
8563
+ }
8564
+ });
8565
+ device.command("unfavourite <id>").description("Remove a device from favourites").action(async (id) => {
8566
+ try {
8567
+ const { device: dev } = await findDevice(id);
8568
+ await removeFavourite(dev.id);
8569
+ console.log(`\u2713 Removed ${dev.name} from favourites`);
8570
+ } catch (err) {
8571
+ console.error(`Failed to unfavourite device: ${err.message}`);
8572
+ process.exit(1);
8573
+ }
8574
+ });
8575
+ device.command("favourites").description("List favourite devices").action(async () => {
8576
+ try {
8577
+ const favIds = await getFavourites();
8578
+ if (favIds.length === 0) {
8579
+ console.log("No favourite devices.");
8580
+ return;
8581
+ }
8582
+ const { devices } = await getAllDevices();
8583
+ const favDevices = favIds.map((id) => devices.find((d) => d.id === id)).filter((d) => d !== void 0);
8584
+ if (favDevices.length === 0) {
8585
+ console.log("No favourite devices.");
8586
+ return;
8587
+ }
8588
+ printTable2(favDevices);
8589
+ } catch (err) {
8590
+ console.error(`Failed to list favourites: ${err.message}`);
8591
+ process.exit(1);
8592
+ }
8593
+ });
8485
8594
  }
8486
8595
 
8487
8596
  // src/commands/start.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simvyn",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "Universal mobile device devtool — control iOS Simulators, Android Emulators, and real devices from a single dashboard and CLI",
6
6
  "main": "./dist/index.js",