@shipvo/sim 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +8 -3
  2. package/server.mjs +3 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipvo/sim",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "The Shipvo simulator: runs a Shipvo app's web build in a phone-sized headless browser and exposes it to agents (accessibility tree, tap/type/scroll, simulated device, mock backend). Used by @shipvo/mcp and `shipvo sim`.",
@@ -20,10 +20,15 @@
20
20
  },
21
21
  "keywords": [
22
22
  "shipvo",
23
- "simulator",
23
+ "react-native",
24
24
  "react-native-web",
25
+ "mobile-simulator",
26
+ "headless",
25
27
  "playwright",
26
- "ai-agents"
28
+ "mcp",
29
+ "ai-agents",
30
+ "mobile-testing",
31
+ "e2e-testing"
27
32
  ],
28
33
  "homepage": "https://github.com/shipvo/shipvo#readme",
29
34
  "repository": {
package/server.mjs CHANGED
@@ -25,7 +25,7 @@ let CFG = {}; try { CFG = JSON.parse(readFileSync(join(process.cwd(), "shipvo.js
25
25
  const PORT = Number(arg("port", 4545)), DIR = arg("dir", "/tmp/pn-ui"), DIST = arg("dist", join(process.cwd(), "sim-dist"));
26
26
  const DEFAULT_APP = arg("app", CFG.appId || "tide"), APP_NAME = CFG.name || "Tide & Tackle";
27
27
  // Published releases: open_app("tide@r13") loads the web artifact that `shipvo publish` stored next to the phone bundle.
28
- const CONTROL = (process.env.PN_CONTROL || process.env.CONTENT_FROM || CFG.controlPlane || "https://purenative-control.purenative.workers.dev").replace(/\/$/, "");
28
+ const CONTROL = (process.env.PN_CONTROL || process.env.CONTENT_FROM || CFG.controlPlane || "https://api.shipvo.dev").replace(/\/$/, "");
29
29
  async function resolveApp(spec) {
30
30
  const m = /^([a-z0-9-]+)(?:@(?:r?(\d+)|(latest)))?$/.exec(String(spec || DEFAULT_APP)); if (!m) throw new Error(`bad app spec "${spec}": use "${DEFAULT_APP}", "${DEFAULT_APP}@r13" or "${DEFAULT_APP}@latest"`);
31
31
  const [, app, rel] = m;
@@ -53,7 +53,7 @@ async function newSession(id, opts = {}) {
53
53
  const page = await ctx.newPage();
54
54
  await page.goto(target.url, { waitUntil: "networkidle" });
55
55
  await page.waitForTimeout(300);
56
- sessions.set(id, { ctx, page, app: target.app, release: target.release });
56
+ sessions.set(id, { ctx, page, app: target.app, release: target.release, startedAt: new Date().toISOString() });
57
57
  }
58
58
 
59
59
  async function screen(page) {
@@ -191,6 +191,7 @@ const server = http.createServer(async (req, res) => {
191
191
  // agent API
192
192
  if (req.method !== "POST") return send(404, { error: "not found" });
193
193
  let raw = ""; for await (const ch of req) raw += ch; const body = raw ? JSON.parse(raw) : {};
194
+ if (url.pathname === "/sessions") return send(200, { ok: true, sessions: [...sessions.entries()].map(([id, s]) => ({ session: id, app: s.app, release: s.release, started_at: s.startedAt ?? null, url: s.page.url() })) });
194
195
  if (url.pathname === "/session") { await newSession(body.session, body); const s = sessions.get(body.session); log(body.session, "start", true, { app: s.app, release: s.release }); return send(200, { ok: true, app: s.app, release: s.release, screen: await screen(s.page) }); }
195
196
  if (url.pathname === "/act") {
196
197
  try { const r = await act(body.session, body); log(body.session, body.action, true, body.ref ? { ref: body.ref } : {}); return send(200, { ok: true, ...r }); }