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.
- package/dev/client/web/index.html +6 -1
- package/docs/routec/cloud_deplyments.md +3 -0
- package/docs/routec/config_hierachy.md +5 -0
- package/docs/routec/host_setup.md +30 -14
- package/docs/routec/npmjs_publish.md +81 -6
- package/host-service/config.mjs +7 -0
- package/host-service/package.json +1 -1
- package/host-service/server.mjs +2 -0
- package/host-service/setup.mjs +56 -69
- package/package.json +1 -1
|
@@ -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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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,
|
|
216
|
+
Only after the gate passes, Owner publishes from the staging package.
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
npm
|
|
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
|
-
|
|
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.
|
package/host-service/config.mjs
CHANGED
|
@@ -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
|
),
|
package/host-service/server.mjs
CHANGED
|
@@ -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,
|
package/host-service/setup.mjs
CHANGED
|
@@ -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
|
-
|
|
176
|
-
|
|
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 (
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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 >=
|
|
427
|
-
(value >=
|
|
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
|
-
: (
|
|
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
|
-
|
|
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`);
|