oysterun 0.1.0 → 0.1.1-beta.1

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.
@@ -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(
@@ -329,11 +329,14 @@ CPU usage
329
329
  Memory used
330
330
  Disk used
331
331
  Backend RSS
332
+ APNs deliveries
332
333
  ```
333
334
 
334
335
  Chart layout is two cards per row on desktop/tablet and one card per row on
335
336
  small screens. Each chart shows y-axis min/max ticks and x-axis first/last
336
337
  bucket labels. Percent charts use a fixed 0% to 100% y-axis.
338
+ The APNs deliveries chart belongs in the first Operational Charts block. The
339
+ second APNs Deliveries block is for the latest sanitized delivery table only.
337
340
 
338
341
  Each chart card has its own range controls:
339
342
 
@@ -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
 
@@ -440,11 +441,15 @@ CPU usage
440
441
  Memory used
441
442
  Disk used
442
443
  Backend RSS
444
+ APNs deliveries
443
445
  ```
444
446
 
445
447
  Dashboard rendering uses two chart cards per row on desktop/tablet, one per row
446
448
  on small screens, y-axis min/max ticks, and x-axis first/last bucket labels.
447
449
  Percent charts use a fixed 0% to 100% scale.
450
+ The APNs deliveries chart is rendered in the first Operational Charts block;
451
+ the APNs Deliveries section below it is reserved for the latest sanitized
452
+ delivery table.
448
453
 
449
454
  Every chart card exposes independent `1h`, `1d`, `1w`, and `1m` range controls.
450
455
  The `1m` range maps to the 30-day retention window.
@@ -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
 
@@ -213,24 +213,99 @@ web login / sessions page works
213
213
  phone QR/manual login works
214
214
  ```
215
215
 
216
- Only after the gate passes, publish:
216
+ Only after the gate passes, Owner publishes from the staging package.
217
217
 
218
- ```bash
219
- npm publish
220
- ```
218
+ Do not use the deprecated `npm publish --otp <code>` flow as the normal
219
+ Oysterun publish path. npm now requires Owner to complete the account auth/2FA
220
+ flow directly, so the maintainer should provide the exact command block and let
221
+ Owner run the final `npm publish` command in an authenticated terminal.
221
222
 
222
- For beta:
223
+ Owner publish script:
223
224
 
224
225
  ```bash
226
+ cd /Users/wanghsuanchung/Projects/OysterunOwnerManualWork
227
+ npm run package:npm
228
+
229
+ cd /Users/wanghsuanchung/Projects/OysterunOwnerManualWork/dist/npm/oysterun
230
+ npm pack --dry-run
225
231
  npm publish --tag beta
226
232
  ```
227
233
 
228
- For stable:
234
+ For stable, replace the last command with:
229
235
 
230
236
  ```bash
231
237
  npm publish --tag latest
232
238
  ```
233
239
 
240
+ ## Dist Tag Verification And Beta Practice
241
+
242
+ After publishing, verify registry tags explicitly. The publish success line:
243
+
244
+ ```text
245
+ + oysterun@0.1.0
246
+ ```
247
+
248
+ only proves that the version was published. It does not prove which dist-tag was
249
+ assigned.
250
+
251
+ Verification commands:
252
+
253
+ ```bash
254
+ npm dist-tag ls oysterun
255
+ npm view oysterun dist-tags --json
256
+ npm view oysterun@beta version
257
+ npm view oysterun@latest version
258
+ ```
259
+
260
+ Install-channel smoke:
261
+
262
+ ```bash
263
+ npm install -g oysterun@beta
264
+ oysterun --help
265
+ ```
266
+
267
+ Current first publish result, 2026-06-06:
268
+
269
+ ```text
270
+ beta: 0.1.0
271
+ latest: 0.1.0
272
+ ```
273
+
274
+ This is acceptable for the first public bootstrap package because there was no
275
+ previous stable version to preserve. It means both commands install the same
276
+ package:
277
+
278
+ ```bash
279
+ npm install -g oysterun@beta
280
+ npm install -g oysterun
281
+ ```
282
+
283
+ Best practice for future beta releases:
284
+
285
+ ```text
286
+ Use prerelease semver for beta channel:
287
+ 0.1.1-beta.0
288
+ 0.1.1-beta.1
289
+
290
+ Publish those with:
291
+ npm publish --tag beta
292
+
293
+ Keep latest for stable releases only:
294
+ 0.1.1
295
+ npm publish --tag latest
296
+ ```
297
+
298
+ If a future beta publish accidentally moves `latest` away from the last stable
299
+ release, restore `latest` explicitly:
300
+
301
+ ```bash
302
+ npm dist-tag add oysterun@<last-stable-version> latest
303
+ ```
304
+
305
+ Do not use a semver-looking string as a dist-tag. `beta`, `next`, and `canary`
306
+ are valid channel names; tags like `v1.4` are not appropriate because npm
307
+ dist-tags share the same namespace as versions.
308
+
234
309
  ## Manual Product Smoke
235
310
 
236
311
  After install/setup, maintainers may run a manual Cloud notification smoke.
@@ -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.0",
3
+ "version": "0.1.1-beta.1",
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);
@@ -417,14 +442,14 @@ function isPrivateLanAddress(address) {
417
442
  if (value === null) return false;
418
443
  const ten = ipToNumber("10.0.0.0");
419
444
  const tenEnd = ipToNumber("10.255.255.255");
420
- const172 = ipToNumber("172.16.0.0");
421
- const172End = ipToNumber("172.31.255.255");
422
- const192 = ipToNumber("192.168.0.0");
423
- const192End = ipToNumber("192.168.255.255");
445
+ const private172Start = ipToNumber("172.16.0.0");
446
+ const private172End = ipToNumber("172.31.255.255");
447
+ const private192Start = ipToNumber("192.168.0.0");
448
+ const private192End = ipToNumber("192.168.255.255");
424
449
  return (
425
450
  (value >= ten && value <= tenEnd) ||
426
- (value >= const172 && value <= const172End) ||
427
- (value >= const192 && value <= const192End)
451
+ (value >= private172Start && value <= private172End) ||
452
+ (value >= private192Start && value <= private192End)
428
453
  );
429
454
  }
430
455
 
@@ -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.0",
3
+ "version": "0.1.1-beta.1",
4
4
  "type": "module",
5
5
  "description": "Oysterun Host installer and local service",
6
6
  "bin": {