oysterun 0.1.1-beta.0 → 0.1.1-beta.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.
package/bin/oysterun.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, readFileSync } from "node:fs";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { dirname, join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
@@ -17,6 +17,8 @@ const releaseServiceScript = join(
17
17
  "tool_scripts",
18
18
  "oysterun_release_service.sh"
19
19
  );
20
+ const packageJsonPath = join(repoRoot, "package.json");
21
+ const hostPackageJsonPath = join(repoRoot, "host-service", "package.json");
20
22
 
21
23
  const serviceCommands = new Map([
22
24
  ["service:install", "install"],
@@ -88,11 +90,30 @@ function inspectConfig() {
88
90
  };
89
91
  }
90
92
 
93
+ function readPackageVersion() {
94
+ for (const path of [packageJsonPath, hostPackageJsonPath]) {
95
+ try {
96
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
97
+ if (typeof parsed.version === "string" && parsed.version.trim()) {
98
+ return parsed.version.trim();
99
+ }
100
+ } catch {
101
+ // Keep version reporting best-effort so diagnostics never trigger setup.
102
+ }
103
+ }
104
+ return "unknown";
105
+ }
106
+
107
+ function printVersion() {
108
+ console.log(`oysterun ${readPackageVersion()}`);
109
+ }
110
+
91
111
  function printHelp() {
92
112
  console.log(`Oysterun Host
93
113
 
94
114
  Usage:
95
115
  oysterun
116
+ oysterun --version
96
117
  oysterun setup [options]
97
118
  oysterun show-qr
98
119
  oysterun service:start
@@ -166,6 +187,11 @@ switch (command) {
166
187
  case "-h":
167
188
  printHelp();
168
189
  break;
190
+ case "version":
191
+ case "--version":
192
+ case "-v":
193
+ printVersion();
194
+ break;
169
195
  default:
170
196
  if (serviceCommands.has(command)) {
171
197
  runService(serviceCommands.get(command), rest);
@@ -11275,6 +11275,7 @@ Waiting for Claude login output…</pre
11275
11275
  iosPush: null,
11276
11276
  platform: "",
11277
11277
  folderAccessSettingsSupported: false,
11278
+ debugHostPreferencesFullDiskAccessBlockEnabled: false,
11278
11279
  folderAccessSettingsUri: "",
11279
11280
  defaultProvider: HOST_DEFAULT_SESSION_DEFAULTS.defaultProvider,
11280
11281
  effectiveDefaultProvider:
@@ -26492,6 +26493,8 @@ Waiting for Claude login output…</pre
26492
26493
  platform: typeof data.platform === "string" ? data.platform : "",
26493
26494
  folderAccessSettingsSupported:
26494
26495
  data.folder_access_settings_supported === true,
26496
+ debugHostPreferencesFullDiskAccessBlockEnabled:
26497
+ data.debug_host_preferences_full_disk_access_block_enabled === true,
26495
26498
  folderAccessSettingsUri:
26496
26499
  typeof data.folder_access_settings_uri === "string"
26497
26500
  ? data.folder_access_settings_uri
@@ -28144,7 +28147,9 @@ Waiting for Claude login output…</pre
28144
28147
  const publicUrlDisabled = disabled;
28145
28148
  const folderAccessVisible =
28146
28149
  hostPreferencesState.platform === "darwin" &&
28147
- hostPreferencesState.folderAccessSettingsSupported === true;
28150
+ hostPreferencesState.folderAccessSettingsSupported === true &&
28151
+ hostPreferencesState.debugHostPreferencesFullDiskAccessBlockEnabled ===
28152
+ true;
28148
28153
  renderHostNotificationPreferences({ disabled });
28149
28154
  setVisible("host-folder-access-card", folderAccessVisible);
28150
28155
  $("host-public-base-url")?.toggleAttribute(
@@ -193,6 +193,7 @@ Current implemented allowlist:
193
193
  | `debug_routec_facade_transcript_rotation_enabled` | `true` | Enables bounded rotation for facade transcript logs. |
194
194
  | `debug_routec_facade_transcript_max_bytes` | `262144` | Bounded facade transcript max file size. |
195
195
  | `debug_routec_facade_transcript_max_files` | `3` | Bounded facade transcript max file count. |
196
+ | `debug_host_preferences_full_disk_access_block_enabled` | `false` | Shows the Host Preferences macOS Folder Access recovery block for debug/manual diagnosis. Hidden in normal product setup. |
196
197
 
197
198
  Behavior:
198
199
 
@@ -36,19 +36,29 @@ oysterun
36
36
  ```
37
37
 
38
38
  `oysterun` enters setup automatically when required Host config is missing.
39
- `npm run setup` and `oysterun setup` both run the first-run wizard:
39
+ `npm run setup` and `oysterun setup` both run the first-run wizard. Interactive
40
+ setup questions use a step-based terminal format such as `[1/7] Host name` and
41
+ a stable `?` prompt prefix, so explanatory copy and questions are visually
42
+ separate.
40
43
 
41
44
  ```text
45
+ [1/7] Host name
42
46
  1. Ask for Host display name.
47
+
48
+ [2/7] Host port
43
49
  2. Ask for Host port.
44
50
  - default production port is 8802.
45
51
  - if occupied, offer the next available port.
52
+
53
+ [3/7] iPhone connection URL
46
54
  3. Ask how the iPhone will connect to this Mac.
47
55
  - prefer detected Tailscale/VPN or LAN URL.
48
56
  - localhost is Mac-browser-only; for iPhone on the same Wi-Fi/LAN, use the
49
57
  Mac LAN IP address.
50
58
  - if not on the same reachable network, use Tailscale, Cloudflare Tunnel,
51
59
  VPN, or another reachable tunnel/direct URL.
60
+
61
+ [4/7] Host password
52
62
  4. Ask for Host password.
53
63
  - password input must be hidden.
54
64
  - first setup must confirm password by entering it twice.
@@ -57,30 +67,36 @@ oysterun
57
67
  6. Create dashboard credential hash.
58
68
  7. Register Stage 1 direct-IP Host with Cloud when Cloud direct mode is enabled.
59
69
  8. Write Host Cloud identity to cloud_identity.json, not config.json.
60
- 9. Ask whether the user has downloaded the Oysterun phone app.
61
- - yes: continue to the Host login QR step.
62
- - no: show the phone app download link and QR.
70
+
71
+ [5/7] Phone app
72
+ 9. Explain: "You need the Oysterun phone app to scan the login QR."
73
+ Ask: "Show phone app download link and QR code?"
74
+ - yes: show the phone app download link and QR.
75
+ - no: continue to the Host login QR step.
63
76
  - first version download URL: `https://oysterun.com`.
64
77
  - later production URL should be the App Store link.
78
+
79
+ [6/7] Login QR
65
80
  10. Create a 15-minute one-time QR/bootstrap login token.
66
81
  11. Show the QR code in terminal.
67
- 12. Ask about macOS Full Disk Access with a clear explanation:
68
- Oysterun needs it to browse, preview, and let agents work with protected
69
- folders such as Desktop, Documents, Downloads, external disks, or folders
70
- added later.
71
- If skipped, Oysterun can still start, but some file access may fail until
72
- permission is granted.
73
- It can be enabled later in Host Preferences > macOS Folder Access.
74
- 13. Install the launchd service.
75
- 14. Ask whether to start Host now, default yes.
76
- 15. yes:
82
+
83
+ [7/7] Start Host
84
+ 12. Install the launchd service.
85
+ 13. Ask whether to start Host now, default yes.
86
+ 14. yes:
77
87
  launchd start
78
88
  health check
79
89
  display Web URL
90
+ on interactive macOS setup, open `<Host URL>/app/sessions` in the browser
91
+ after health check succeeds
80
92
  no:
81
93
  print `oysterun` as the start command.
82
94
  ```
83
95
 
96
+ Setup must not ask for macOS Full Disk Access during first-run installation.
97
+ macOS folder access recovery UI is a Host Preferences debug block, hidden by
98
+ default and shown only when `config.debug.json` explicitly enables it.
99
+
84
100
  Setup must run before the server starts. The server reads the config generated
85
101
  by setup.
86
102
 
@@ -474,6 +474,7 @@ const DEFAULT_CONFIG = Object.freeze({
474
474
  debug_routec_facade_transcript_rotation_enabled: true,
475
475
  debug_routec_facade_transcript_max_bytes: 262144,
476
476
  debug_routec_facade_transcript_max_files: 3,
477
+ debug_host_preferences_full_disk_access_block_enabled: false,
477
478
  debug_cloud_backend_stage: PRODUCT_CLOUD_BACKEND_STAGE,
478
479
  routec_matrix_storage_cache_enabled: true,
479
480
  transcript_retention_days: null,
@@ -494,6 +495,7 @@ const DEBUG_CONFIG_KEYS = Object.freeze([
494
495
  "debug_routec_facade_transcript_rotation_enabled",
495
496
  "debug_routec_facade_transcript_max_bytes",
496
497
  "debug_routec_facade_transcript_max_files",
498
+ "debug_host_preferences_full_disk_access_block_enabled",
497
499
  "debug_cloud_backend_stage",
498
500
  ]);
499
501
  const DEBUG_CONFIG_KEY_SET = new Set(DEBUG_CONFIG_KEYS);
@@ -1582,6 +1584,11 @@ function normalizeConfig(config) {
1582
1584
  migratedCredentialConfig.debug_routec_facade_transcript_max_files,
1583
1585
  DEFAULT_CONFIG.debug_routec_facade_transcript_max_files
1584
1586
  ),
1587
+ debug_host_preferences_full_disk_access_block_enabled:
1588
+ normalizeBooleanWithDefault(
1589
+ migratedCredentialConfig.debug_host_preferences_full_disk_access_block_enabled,
1590
+ DEFAULT_CONFIG.debug_host_preferences_full_disk_access_block_enabled
1591
+ ),
1585
1592
  debug_cloud_backend_stage: normalizeCloudBackendStage(
1586
1593
  migratedCredentialConfig.debug_cloud_backend_stage
1587
1594
  ),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oysterun-host-service",
3
- "version": "0.1.1-beta.0",
3
+ "version": "0.1.1-beta.2",
4
4
  "type": "module",
5
5
  "description": "Oysterun host service — runs on Mac mini, manages Claude Code sessions",
6
6
  "scripts": {
@@ -4889,6 +4889,8 @@ function buildHostPreferencesPayload() {
4889
4889
  connection_mode: CONNECTION_MODE,
4890
4890
  platform: process.platform,
4891
4891
  folder_access_settings_supported: supportsMacFolderAccessPermissions(),
4892
+ debug_host_preferences_full_disk_access_block_enabled:
4893
+ config.debug_host_preferences_full_disk_access_block_enabled === true,
4892
4894
  folder_access_settings_uri: supportsMacFolderAccessPermissions()
4893
4895
  ? FULL_DISK_ACCESS_SETTINGS_URI
4894
4896
  : null,
@@ -40,22 +40,20 @@ import {
40
40
  createHostLoginBootstrapToken,
41
41
  } from "./host-login-bootstrap-tokens.mjs";
42
42
  import { detectProviderCommands } from "./provider-command-detection.mjs";
43
- import {
44
- FULL_DISK_ACCESS_SETTINGS_URI,
45
- openFullDiskAccessSettings,
46
- supportsMacFolderAccessPermissions,
47
- } from "./macos-permissions.mjs";
48
- import { execFileSync } from "child_process";
43
+ import { execFile, execFileSync } from "child_process";
49
44
  import { createInterface } from "readline";
50
45
  import { basename, dirname, join, resolve } from "path";
51
46
  import { fileURLToPath } from "url";
52
47
  import { createServer } from "net";
53
48
  import { networkInterfaces } from "os";
49
+ import { promisify } from "util";
54
50
 
55
51
  const __dirname = dirname(fileURLToPath(import.meta.url));
56
52
  const repoRoot = dirname(__dirname);
57
53
  const releaseServiceScript = join(repoRoot, "tool_scripts", "oysterun_release_service.sh");
58
54
  const OYSTERUN_PHONE_APP_DOWNLOAD_URL = "https://oysterun.com";
55
+ const execFileAsync = promisify(execFile);
56
+ const SETUP_TOTAL_STEPS = 7;
59
57
 
60
58
  function printDetectedProviderStatus(detectedCommands) {
61
59
  console.log(" Detected providers:");
@@ -128,9 +126,20 @@ function prompt(question) {
128
126
  });
129
127
  }
130
128
 
129
+ function formatSetupQuestion(question) {
130
+ const normalized = String(question || "").trim();
131
+ if (normalized.startsWith("?")) return normalized;
132
+ return `? ${normalized}`;
133
+ }
134
+
135
+ function printSetupStep(stepNumber, title) {
136
+ if (!isInteractiveSetup()) return;
137
+ console.log(`\n[${stepNumber}/${SETUP_TOTAL_STEPS}] ${title}`);
138
+ }
139
+
131
140
  async function promptWithDefault(question, defaultValue) {
132
141
  const suffix = defaultValue ? ` [${defaultValue}]` : "";
133
- const answer = await prompt(`${question}${suffix}: `);
142
+ const answer = await prompt(`${formatSetupQuestion(question)}${suffix}: `);
134
143
  return answer || defaultValue || "";
135
144
  }
136
145
 
@@ -146,7 +155,7 @@ async function promptRequiredValue(question, defaultValue = "") {
146
155
 
147
156
  async function promptRequiredPassword(question) {
148
157
  while (true) {
149
- const answer = await promptHidden(`${question}: `);
158
+ const answer = await promptHidden(`${formatSetupQuestion(question)}: `);
150
159
  if (answer.trim()) {
151
160
  return answer;
152
161
  }
@@ -156,13 +165,13 @@ async function promptRequiredPassword(question) {
156
165
 
157
166
  async function promptYesNoDefaultYes(question) {
158
167
  if (!process.stdin.isTTY || !process.stdout.isTTY) return true;
159
- const answer = (await prompt(`${question} (Y/n) `)).trim().toLowerCase();
168
+ const answer = (await prompt(`${formatSetupQuestion(question)} (Y/n) `)).trim().toLowerCase();
160
169
  return !(answer === "n" || answer === "no");
161
170
  }
162
171
 
163
172
  async function promptYesNoDefaultNo(question) {
164
173
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
165
- const answer = (await prompt(`${question} (y/N) `)).trim().toLowerCase();
174
+ const answer = (await prompt(`${formatSetupQuestion(question)} (y/N) `)).trim().toLowerCase();
166
175
  return answer === "y" || answer === "yes";
167
176
  }
168
177
 
@@ -172,17 +181,19 @@ function isInteractiveSetup() {
172
181
 
173
182
  async function maybeShowPhoneAppDownloadStep() {
174
183
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;
175
- const hasPhoneApp = await promptYesNoDefaultYes(
176
- "Have you downloaded the Oysterun phone app?"
184
+ printSetupStep(5, "Phone app");
185
+ console.log(" You need the Oysterun phone app to scan the login QR.");
186
+ const showDownload = await promptYesNoDefaultNo(
187
+ "Show phone app download link and QR code?"
177
188
  );
178
- if (hasPhoneApp) return;
189
+ if (!showDownload) return;
179
190
 
180
191
  console.log("\n Download the Oysterun phone app first:");
181
192
  console.log(` ${OYSTERUN_PHONE_APP_DOWNLOAD_URL}`);
182
193
  console.log("\n App download QR:\n");
183
194
  qrcode.generate(OYSTERUN_PHONE_APP_DOWNLOAD_URL, { small: true });
184
195
  console.log("\n This link will point to the App Store when the app is published.");
185
- await prompt(" Press Enter after installing the phone app to continue to login QR...");
196
+ await prompt("? Press Enter after installing the phone app to continue to login QR...");
186
197
  }
187
198
 
188
199
  async function promptHidden(question) {
@@ -264,6 +275,18 @@ function inferReleaseServiceStack(opts = {}) {
264
275
  return "production";
265
276
  }
266
277
 
278
+ async function maybeOpenSetupBrowser(url) {
279
+ if (!isInteractiveSetup() || process.platform !== "darwin" || !url) return;
280
+ try {
281
+ console.log(` Opening ${url} in your browser...`);
282
+ await execFileAsync("open", [url]);
283
+ console.log(" Browser opened.");
284
+ } catch (err) {
285
+ console.log(` Could not open the browser automatically: ${err.message}`);
286
+ console.log(` Open manually: ${url}`);
287
+ }
288
+ }
289
+
267
290
  async function maybeInstallOrStartReleaseService(opts, { hostUrl }) {
268
291
  if (!opts.installService && !opts.startService && !opts.noStartService) {
269
292
  return false;
@@ -272,6 +295,7 @@ async function maybeInstallOrStartReleaseService(opts, { hostUrl }) {
272
295
  throw new Error("release service install/start requires macOS launchd");
273
296
  }
274
297
 
298
+ printSetupStep(7, "Start Host");
275
299
  const shouldStart = opts.noStartService
276
300
  ? false
277
301
  : opts.startService
@@ -285,6 +309,7 @@ async function maybeInstallOrStartReleaseService(opts, { hostUrl }) {
285
309
  );
286
310
  runReleaseService("install", [], opts);
287
311
  console.log(`\n ✓ Oysterun Host is running at ${hostUrl}`);
312
+ await maybeOpenSetupBrowser(`${hostUrl}/app/sessions`);
288
313
  } else {
289
314
  console.log(
290
315
  ` Installing LaunchAgent without starting Oysterun Host (${inferReleaseServiceStack(opts)})...`
@@ -373,7 +398,7 @@ async function promptHostPort({ defaultPort, optsPort }) {
373
398
  }
374
399
 
375
400
  while (true) {
376
- console.log("\n Host port");
401
+ printSetupStep(2, "Host port");
377
402
  console.log(" Press Enter unless this port is already used.");
378
403
  const portInput = await promptWithDefault("Host port", String(defaultPort));
379
404
  const port = parsePortInput(portInput);
@@ -502,6 +527,7 @@ async function promptDirectHostUrl({ port, existingUrl, optsUrl }) {
502
527
  }
503
528
 
504
529
  const candidates = detectHostUrlCandidates(port, existingUrl);
530
+ printSetupStep(3, "iPhone connection URL");
505
531
  console.log("\n How will your iPhone connect to this Mac?");
506
532
  console.log("\n We detected:");
507
533
  candidates.forEach((candidate, index) => {
@@ -532,7 +558,7 @@ async function promptDirectHostUrl({ port, existingUrl, optsUrl }) {
532
558
  console.log(` Press Enter only if you are setting up Mac-browser access for now.`);
533
559
  while (true) {
534
560
  const answer = await prompt(
535
- `Host URL for iPhone login (or Enter for http://localhost:${port}): `
561
+ `${formatSetupQuestion(`Host URL for iPhone login (or Enter for http://localhost:${port})`)}: `
536
562
  );
537
563
  const trimmed = answer.trim();
538
564
  if (!trimmed) return `http://localhost:${port}`;
@@ -545,7 +571,7 @@ async function promptDirectHostUrl({ port, existingUrl, optsUrl }) {
545
571
  const selectedDefault = defaultIndex >= 0 ? defaultIndex + 1 : 1;
546
572
  while (true) {
547
573
  const answer = await prompt(
548
- `Choose Host URL [${selectedDefault}] or paste a different URL: `
574
+ `${formatSetupQuestion(`Choose Host URL [${selectedDefault}] or paste a different URL`)}: `
549
575
  );
550
576
  const trimmed = answer.trim();
551
577
  if (!trimmed) return candidates[selectedDefault - 1].url;
@@ -563,47 +589,6 @@ async function promptDirectHostUrl({ port, existingUrl, optsUrl }) {
563
589
  }
564
590
  }
565
591
 
566
- async function maybeOfferMacFolderAccessPermissionDuringSetup() {
567
- if (!supportsMacFolderAccessPermissions()) return;
568
- if (!process.stdin.isTTY || !process.stdout.isTTY) return;
569
- console.log();
570
- console.log(
571
- "Oysterun agents can only work with files macOS allows this Host to read."
572
- );
573
- console.log(
574
- "Enable Full Disk Access if you want agents to browse, preview, read, or write files in Desktop, Documents, Downloads, external disks, or project folders you add later."
575
- );
576
- console.log(
577
- "You can skip now. Chat still works, but file browsing or agent file tasks may fail until permission is granted."
578
- );
579
- console.log(
580
- "You can enable this later in Host Preferences > macOS Folder Access."
581
- );
582
- const answer = await prompt(
583
- "Open System Settings > Privacy & Security > Full Disk Access now? (y/N) "
584
- );
585
- if (answer.trim().toLowerCase() !== "y") {
586
- console.log(" Skipped Full Disk Access setup.");
587
- console.log(
588
- " You can enable it later from Host Preferences > macOS Folder Access."
589
- );
590
- return;
591
- }
592
- try {
593
- await openFullDiskAccessSettings();
594
- console.log(" Opened System Settings to Full Disk Access.");
595
- console.log(` If it does not appear automatically, open: ${FULL_DISK_ACCESS_SETTINGS_URI}`);
596
- } catch (err) {
597
- console.log(` Could not open System Settings automatically: ${err.message}`);
598
- console.log(
599
- " Open System Settings > Privacy & Security > Full Disk Access, then enable Oysterun."
600
- );
601
- console.log(
602
- " You can also retry later from Host Preferences > macOS Folder Access."
603
- );
604
- }
605
- }
606
-
607
592
  function clearCloudRegistration() {
608
593
  return {
609
594
  connection_mode: "direct",
@@ -651,7 +636,8 @@ async function configureDirectMode(opts, config) {
651
636
  }
652
637
  const displayNameRaw = opts.displayName !== undefined
653
638
  ? opts.displayName
654
- : (console.log("\n This name appears in the iPhone app Host list."),
639
+ : (printSetupStep(1, "Host name"),
640
+ console.log(" This name appears in the iPhone app Host list."),
655
641
  await promptWithDefault("Name this Host", currentDisplayName));
656
642
  const displayName = displayNameRaw.trim() || null;
657
643
  const currentPort = String(
@@ -705,7 +691,6 @@ async function configureDirectMode(opts, config) {
705
691
  console.log(` Local login username: ${dashboardUser}`);
706
692
  printDetectedProviderStatus(detectedCommands);
707
693
  console.log(` Config saved to ${getConfigPath()}`);
708
- await maybeOfferMacFolderAccessPermissionDuringSetup();
709
694
  const serviceHandled = await maybeInstallOrStartReleaseService(opts, {
710
695
  hostUrl: publicBaseUrl || `http://localhost:${port}`,
711
696
  });
@@ -728,7 +713,8 @@ async function configureCloudDirectMode(opts, config, backendUrl, backendStage)
728
713
  }
729
714
  const displayNameDefault = config.display_name || directSetupDefaults.display_name;
730
715
  if (isInteractiveSetup()) {
731
- console.log("\n This name appears in the iPhone app Host list.");
716
+ printSetupStep(1, "Host name");
717
+ console.log(" This name appears in the iPhone app Host list.");
732
718
  }
733
719
  const displayName = (
734
720
  opts.displayName !== undefined
@@ -757,6 +743,7 @@ async function configureCloudDirectMode(opts, config, backendUrl, backendStage)
757
743
  throw new Error("Direct Host URL required");
758
744
  }
759
745
 
746
+ printSetupStep(4, "Host password");
760
747
  const hasExistingPasswordHash = typeof config.dashboard_password_hash === "string" && config.dashboard_password_hash.trim().length > 0;
761
748
  let dashboardPasswordHash = config.dashboard_password_hash || null;
762
749
  const hostPassword = opts.hostPassword !== undefined
@@ -847,12 +834,12 @@ async function configureCloudDirectMode(opts, config, backendUrl, backendStage)
847
834
  console.log(` Local login user: admin`);
848
835
  printDetectedProviderStatus(detectedCommands);
849
836
  await maybeShowPhoneAppDownloadStep();
837
+ printSetupStep(6, "Login QR");
850
838
  console.log("\n Direct Host connection QR:\n");
851
839
  qrcode.generate(JSON.stringify(qrPayload.compact), { small: true });
852
840
  console.log(`\n QR expires at: ${qrPayload.verbose.expires_at}`);
853
841
  console.log(`\n Config saved to ${getConfigPath()}`);
854
842
  console.log(` Cloud identity saved to ${getCloudIdentityPath()}`);
855
- await maybeOfferMacFolderAccessPermissionDuringSetup();
856
843
  const serviceHandled = await maybeInstallOrStartReleaseService(opts, {
857
844
  hostUrl: directHostUrl,
858
845
  });
@@ -975,12 +962,12 @@ async function configureCloudMode(opts, config, backendUrl, backendStage) {
975
962
  printDetectedProviderStatus(detectedCommands);
976
963
 
977
964
  await maybeShowPhoneAppDownloadStep();
965
+ printSetupStep(6, "Login QR");
978
966
  console.log("\n Scan this QR code with the Oysterun app to claim your farm:\n");
979
967
  qrcode.generate(data.onboarding_url, { small: true });
980
968
 
981
969
  console.log(`\n Config saved to ${getConfigPath()}`);
982
970
  console.log(` Cloud identity saved to ${getCloudIdentityPath()}`);
983
- await maybeOfferMacFolderAccessPermissionDuringSetup();
984
971
  console.log(`\n Next steps:`);
985
972
  console.log(` 1. Scan the QR code above with the Oysterun app`);
986
973
  console.log(` 2. Run: oysterun`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oysterun",
3
- "version": "0.1.1-beta.0",
3
+ "version": "0.1.1-beta.2",
4
4
  "type": "module",
5
5
  "description": "Oysterun Host installer and local service",
6
6
  "bin": {