@bivy/bivy 0.17.3-staging.27 → 0.17.3-staging.28

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/bin/bivy.mjs CHANGED
@@ -2432,6 +2432,7 @@ async function cmdApp(args = []) {
2432
2432
  bivy app remove <app-id> [--session <id>]
2433
2433
  bivy app shot [app-id] [--widths 390,1280] [--themes light,dark] [--path /page] [--session <id>]
2434
2434
  bivy app present [view] [--path /page] [--note "…"] [--session <id>]
2435
+ bivy app share [app-id] [--view <view>] [--session <id>]
2435
2436
  bivy app run [--name <name>] [--restart-on-change] [--session <id>] -- <command> [args…]
2436
2437
 
2437
2438
  Manifest: {"version":1,"name":"My app","views":[
@@ -2458,10 +2459,16 @@ run: bivy config set sessions.appScreenshots true
2458
2459
  the chat with the app at phone width (and before/after), and it opens the live
2459
2460
  preview. Use it when you finish a visible change, or when the user asks to see
2460
2461
  it. [view] is an app or view name or ID (default: the one opened last).
2462
+ "share" mints a reusable link to one web view and prints its URL and expiry
2463
+ (24 hours, or until Revoke access in Apps, the app is removed or the machine
2464
+ restarts). [app-id] is an app ID or name, --view a view ID or name (default: the
2465
+ one opened last). Anyone who has the link can use the app, including a live
2466
+ server's backend, until it expires or is revoked: only post it where everyone
2467
+ who can read it may use the app.
2461
2468
  Web previews require operator setup; see docs/apps.md.`);
2462
2469
  return;
2463
2470
  }
2464
- if (!["publish", "list", "remove", "shot", "run", "present"].includes(action)) throw new Error("Unknown app command. Run bivy app --help.");
2471
+ if (!["publish", "list", "remove", "shot", "run", "present", "share"].includes(action)) throw new Error("Unknown app command. Run bivy app --help.");
2465
2472
  // `run -- <command> [args…]`: publish a one-window desktop app without a manifest file.
2466
2473
  let runManifest;
2467
2474
  if (action === "run") {
@@ -2476,6 +2483,15 @@ Web previews require operator setup; see docs/apps.md.`);
2476
2483
  runManifest = { version: 1, name: name.slice(0, 100), views: [{ kind: "display", name: "Window", command, args: commandArgs, ...(restartIndex >= 0 ? { restartOnChange: true } : {}) }] };
2477
2484
  }
2478
2485
  const shot = {};
2486
+ if (action === "share") {
2487
+ const i = rest.indexOf("--view");
2488
+ if (i >= 0) {
2489
+ const value = rest[i + 1];
2490
+ if (!value || value.startsWith("--")) throw new Error("--view requires a view ID or name.");
2491
+ shot.view = value;
2492
+ rest.splice(i, 2);
2493
+ }
2494
+ }
2479
2495
  if (action === "shot" || action === "present") {
2480
2496
  for (const flag of action === "shot" ? ["--widths", "--themes", "--path"] : ["--path", "--note"]) {
2481
2497
  const i = rest.indexOf(flag);
@@ -2494,10 +2510,11 @@ Web previews require operator setup; see docs/apps.md.`);
2494
2510
  const sessionId = resolveAttachSessionId({ sessionFlag: sessionIndex >= 0 ? rest[sessionIndex + 1] : undefined, env: process.env });
2495
2511
  if (!sessionId) throw new Error("Set --session <id> or run inside a Bivy agent session.");
2496
2512
  const positional = rest.filter((_, i) => i !== sessionIndex && (sessionIndex < 0 || i !== sessionIndex + 1));
2497
- if (positional.some((a) => a.startsWith("-")) || (action === "shot" || action === "present" ? positional.length > 1 : positional.length !== (action === "list" || action === "run" ? 0 : 1))) throw new Error("Invalid arguments. Run bivy app --help.");
2513
+ if (positional.some((a) => a.startsWith("-")) || (action === "shot" || action === "present" || action === "share" ? positional.length > 1 : positional.length !== (action === "list" || action === "run" ? 0 : 1))) throw new Error("Invalid arguments. Run bivy app --help.");
2498
2514
  const body = { sessionId, ...shot };
2499
2515
  if (action === "shot" && positional[0]) body.appId = positional[0];
2500
2516
  if (action === "present" && positional[0]) body.target = positional[0];
2517
+ if (action === "share" && positional[0]) body.appId = positional[0];
2501
2518
  if (action === "publish") {
2502
2519
  const file = path.resolve(positional[0]);
2503
2520
  if (fs.statSync(file).size > 64 * 1024) throw new Error("App manifest exceeds 64 KiB.");
@@ -2519,6 +2536,12 @@ Web previews require operator setup; see docs/apps.md.`);
2519
2536
  });
2520
2537
  const result = await response.json();
2521
2538
  if (!response.ok || result.error) throw new Error(result.error || `App command failed (${response.status}).`);
2539
+ if (action === "share") {
2540
+ const { type: _type, requestId: _requestId, ...link } = result;
2541
+ console.log(JSON.stringify({ ...link, expires: new Date(link.expiresAt).toISOString() }, null, 2));
2542
+ console.error("Anyone with this link can use the app, including its live backend, until it expires or you revoke it (Apps → Revoke access).");
2543
+ return;
2544
+ }
2522
2545
  console.log(JSON.stringify(result, null, 2));
2523
2546
  }
2524
2547
 
@@ -231,8 +231,11 @@ export class AppService {
231
231
  return { ok: true };
232
232
  }
233
233
  /** A view by app or view ID or name; by default the one opened last, else the newest app's first. */
234
+ webViews(sessionId) {
235
+ return this.registry.list(sessionId).flatMap((app) => app.views.filter((view) => view.kind === "web").map((view) => this.registry.getView(view.id))).filter(Boolean);
236
+ }
234
237
  pickView(sessionId, target) {
235
- const views = this.registry.list(sessionId).flatMap((app) => app.views.filter((view) => view.kind === "web").map((view) => this.registry.getView(view.id))).filter(Boolean);
238
+ const views = this.webViews(sessionId);
236
239
  if (!views.length)
237
240
  throw new Error("This session has no web views to present. Publish one with bivy app publish, or preview a detected server.");
238
241
  if (target) {
@@ -507,6 +510,25 @@ export class AppService {
507
510
  throw new Error("Bivy's preview service is unavailable on this connection.");
508
511
  return this.gateway.share(viewId);
509
512
  }
513
+ /** `bivy app share`: mints a share link for a web view picked by app and/or
514
+ * view ID or name, like `present` (default: the one opened last). */
515
+ shareView(sessionId, input) {
516
+ let entry;
517
+ if (input.view) {
518
+ const wantedApp = input.app?.trim().toLowerCase();
519
+ const scope = this.webViews(sessionId).filter((candidate) => !wantedApp || candidate.app.id === wantedApp || candidate.app.name.toLowerCase() === wantedApp);
520
+ if (wantedApp && !scope.length)
521
+ throw new Error(`No app called "${input.app}" with web views in this session. Run bivy app list to see them.`);
522
+ const wanted = input.view.trim().toLowerCase();
523
+ const found = scope.find((candidate) => candidate.view.id === wanted) ?? scope.find((candidate) => candidate.view.name.toLowerCase() === wanted);
524
+ if (!found)
525
+ throw new Error(`No web view called "${input.view}"${input.app ? ` in "${input.app}"` : ""}. Run bivy app list to see them.`);
526
+ entry = found;
527
+ }
528
+ else
529
+ entry = this.pickView(sessionId, input.app);
530
+ return { ...this.share(sessionId, entry.app.id, entry.view.id), appId: entry.app.id, viewId: entry.view.id, app: entry.app.name, view: entry.view.name };
531
+ }
510
532
  clearNotes(sessionId, appId, viewId) {
511
533
  delete this.registry.requireView(sessionId, appId, viewId).notes;
512
534
  return { ok: true };
@@ -29,7 +29,10 @@ export function createAppCommands(service, workspaceFor, published = () => { })
29
29
  "apps.annotate": (msg) => service.annotate(String(msg.sessionId), msg),
30
30
  "apps.clearNotes": (msg) => service.clearNotes(String(msg.sessionId), String(msg.appId), String(msg.viewId)),
31
31
  "apps.logs": (msg) => service.logs(String(msg.sessionId), String(msg.appId), String(msg.viewId)),
32
- "apps.share": (msg) => service.share(String(msg.sessionId), String(msg.appId), String(msg.viewId)),
32
+ // The app UI passes exact IDs; `bivy app share` passes an app and/or view by ID or name.
33
+ "apps.share": (msg) => typeof msg.viewId === "string" && typeof msg.appId === "string"
34
+ ? service.share(String(msg.sessionId), msg.appId, msg.viewId)
35
+ : service.shareView(String(msg.sessionId), { app: typeof msg.appId === "string" ? msg.appId : undefined, view: typeof msg.view === "string" ? msg.view : undefined }),
33
36
  "apps.revoke": (msg) => service.revoke(String(msg.sessionId), String(msg.appId), String(msg.viewId)),
34
37
  "apps.remove": (msg) => { service.remove(String(msg.sessionId), String(msg.appId)); return { ok: true }; },
35
38
  };
@@ -26,7 +26,7 @@ export const CLIENT_COMMAND_SCHEMAS = {
26
26
  signals: Type.Optional(Type.Record(Type.String(), Type.Boolean())) }),
27
27
  "apps.clearNotes": Type.Object({ ...request, ...session, appId: Type.String(), viewId: Type.String() }),
28
28
  "apps.logs": Type.Object({ ...request, ...session, appId: Type.String(), viewId: Type.String() }),
29
- "apps.share": Type.Object({ ...request, ...session, appId: Type.String(), viewId: Type.String() }),
29
+ "apps.share": Type.Object({ ...request, ...session, appId: Type.Optional(Type.String({ maxLength: 200 })), viewId: Type.Optional(Type.String()), view: Type.Optional(Type.String({ maxLength: 200 })) }),
30
30
  "apps.revoke": Type.Object({ ...request, ...session, appId: Type.String(), viewId: Type.String() }),
31
31
  "apps.remove": Type.Object({ ...request, ...session, appId: Type.String() }),
32
32
  "credentials.native.preview": Type.Object({ ...request, label: Type.Optional(Type.String({ maxLength: 100 })) }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.17.3-staging.27",
3
+ "version": "0.17.3-staging.28",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",