@supanadit/morse-web 0.6.0 → 0.8.0
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/dist/server.mjs +240 -22
- package/dist/server.mjs.map +4 -4
- package/dist/ui/index.html +2 -2
- package/dist/ui/main-BSQ3ZMOL.js +98 -0
- package/dist/ui/styles-CNPQRZ4U.css +1 -0
- package/dist/ui/webview.manifest.json +3 -3
- package/package.json +1 -1
- package/dist/ui/main-6EMJ4WR3.js +0 -90
- package/dist/ui/styles-74L3DQUP.css +0 -1
package/dist/server.mjs
CHANGED
|
@@ -75,7 +75,7 @@ function hasKnownType(value, allowed) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// ../protocol/dist/version.js
|
|
78
|
-
var PROTOCOL_VERSION =
|
|
78
|
+
var PROTOCOL_VERSION = 19;
|
|
79
79
|
var FRONTEND_MANIFEST_FILE = "webview.manifest.json";
|
|
80
80
|
var DEFAULT_WS_PATH = "/ws";
|
|
81
81
|
|
|
@@ -1108,14 +1108,14 @@ var PiRpcClient = class {
|
|
|
1108
1108
|
throw this.failed ?? new AgentUnavailableError("The pi agent is not running.");
|
|
1109
1109
|
}
|
|
1110
1110
|
const id = `morse-${this.nextId++}`;
|
|
1111
|
-
return new Promise((
|
|
1111
|
+
return new Promise((resolve6, reject) => {
|
|
1112
1112
|
const timer = setTimeout(() => {
|
|
1113
1113
|
this.pending.delete(id);
|
|
1114
1114
|
reject(new AgentProtocolError(`pi did not answer "${command.type}" within ${timeoutMs} ms.`));
|
|
1115
1115
|
}, timeoutMs);
|
|
1116
1116
|
this.pending.set(id, {
|
|
1117
1117
|
command: command.type,
|
|
1118
|
-
resolve: (data) =>
|
|
1118
|
+
resolve: (data) => resolve6(data),
|
|
1119
1119
|
reject,
|
|
1120
1120
|
timer
|
|
1121
1121
|
});
|
|
@@ -1134,14 +1134,14 @@ var PiRpcClient = class {
|
|
|
1134
1134
|
return;
|
|
1135
1135
|
}
|
|
1136
1136
|
let exited = false;
|
|
1137
|
-
await new Promise((
|
|
1137
|
+
await new Promise((resolve6) => {
|
|
1138
1138
|
const finish = () => {
|
|
1139
1139
|
exited = true;
|
|
1140
|
-
|
|
1140
|
+
resolve6();
|
|
1141
1141
|
};
|
|
1142
1142
|
child.once("exit", finish);
|
|
1143
1143
|
child.stdin.end();
|
|
1144
|
-
setTimeout(
|
|
1144
|
+
setTimeout(resolve6, SHUTDOWN_GRACE_MS).unref?.();
|
|
1145
1145
|
});
|
|
1146
1146
|
if (!exited) {
|
|
1147
1147
|
child.kill();
|
|
@@ -2612,6 +2612,7 @@ function noticeMessage(level, text, at) {
|
|
|
2612
2612
|
|
|
2613
2613
|
// ../host-runtime/dist/session-controller.js
|
|
2614
2614
|
var HISTORY_PAGE_SIZE = 50;
|
|
2615
|
+
var UNTITLED_SESSION = "New session";
|
|
2615
2616
|
var HostSessionController = class {
|
|
2616
2617
|
options;
|
|
2617
2618
|
summaries = /* @__PURE__ */ new Map();
|
|
@@ -3322,7 +3323,7 @@ var HostSessionController = class {
|
|
|
3322
3323
|
}
|
|
3323
3324
|
sessions.push({
|
|
3324
3325
|
id: key,
|
|
3325
|
-
title: state?.sessionTitle ?? this.derivedTitle(key) ??
|
|
3326
|
+
title: state?.sessionTitle ?? this.derivedTitle(key) ?? UNTITLED_SESSION,
|
|
3326
3327
|
cwd,
|
|
3327
3328
|
updatedAt: Date.now(),
|
|
3328
3329
|
messageCount: 0
|
|
@@ -3409,7 +3410,12 @@ var HostSessionController = class {
|
|
|
3409
3410
|
};
|
|
3410
3411
|
const state = this.activeKey === void 0 ? void 0 : this.options.services.registry.stateOf(this.activeKey);
|
|
3411
3412
|
if (state) {
|
|
3412
|
-
|
|
3413
|
+
const view = toSessionViewState(state, meta);
|
|
3414
|
+
if (view.sessionTitle === void 0 && this.activeKey !== void 0) {
|
|
3415
|
+
const catalog = this.summaries.get(this.activeKey)?.title;
|
|
3416
|
+
view.sessionTitle = catalog !== void 0 && catalog !== UNTITLED_SESSION ? catalog : this.derivedTitle(this.activeKey);
|
|
3417
|
+
}
|
|
3418
|
+
return view;
|
|
3413
3419
|
}
|
|
3414
3420
|
return draftSessionViewState(this.draftWorkspace ?? this.options.services.registry.defaultWorkspace, meta, this.draftCatalog, this.draftModel, this.draftThinking);
|
|
3415
3421
|
}
|
|
@@ -3798,10 +3804,190 @@ function shortcutRoots(options) {
|
|
|
3798
3804
|
return roots;
|
|
3799
3805
|
}
|
|
3800
3806
|
|
|
3801
|
-
// ../server/src/internal/workspace/
|
|
3807
|
+
// ../server/src/internal/workspace/file-store.ts
|
|
3808
|
+
import { open as open2, stat as stat4 } from "node:fs/promises";
|
|
3809
|
+
import { isAbsolute as isAbsolute5, relative as relative2, resolve as resolve5 } from "node:path";
|
|
3810
|
+
var MAX_PREVIEW_BYTES = 512 * 1024;
|
|
3811
|
+
var SNIFF_BYTES = 8 * 1024;
|
|
3812
|
+
async function readWorkspaceFile(cwd, requested) {
|
|
3813
|
+
const target = resolveWithin(cwd, requested);
|
|
3814
|
+
const info = await stat4(target).catch(() => void 0);
|
|
3815
|
+
if (info === void 0) {
|
|
3816
|
+
throw new UnsupportedByHostError(`No such file: ${requested}`);
|
|
3817
|
+
}
|
|
3818
|
+
if (info.isDirectory()) {
|
|
3819
|
+
throw new UnsupportedByHostError(`${requested} is a directory, not a file.`);
|
|
3820
|
+
}
|
|
3821
|
+
const handle = await open2(target, "r");
|
|
3822
|
+
try {
|
|
3823
|
+
const length = Math.min(info.size, MAX_PREVIEW_BYTES);
|
|
3824
|
+
const buffer = Buffer.alloc(length);
|
|
3825
|
+
const { bytesRead } = await handle.read(buffer, 0, length, 0);
|
|
3826
|
+
const slice = buffer.subarray(0, bytesRead);
|
|
3827
|
+
const binary = slice.subarray(0, Math.min(bytesRead, SNIFF_BYTES)).includes(0);
|
|
3828
|
+
return {
|
|
3829
|
+
path: toPosix(relative2(cwd, target)),
|
|
3830
|
+
content: binary ? "" : slice.toString("utf8"),
|
|
3831
|
+
size: info.size,
|
|
3832
|
+
truncated: info.size > bytesRead,
|
|
3833
|
+
binary
|
|
3834
|
+
};
|
|
3835
|
+
} finally {
|
|
3836
|
+
await handle.close();
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
function resolveWithin(cwd, requested) {
|
|
3840
|
+
if (isAbsolute5(requested)) {
|
|
3841
|
+
throw new UnsupportedByHostError("A preview only opens files inside the project.");
|
|
3842
|
+
}
|
|
3843
|
+
const target = resolve5(cwd, requested);
|
|
3844
|
+
const rel = relative2(cwd, target);
|
|
3845
|
+
if (rel.length === 0 || rel.startsWith("..") || isAbsolute5(rel)) {
|
|
3846
|
+
throw new UnsupportedByHostError("A preview only opens files inside the project.");
|
|
3847
|
+
}
|
|
3848
|
+
return target;
|
|
3849
|
+
}
|
|
3850
|
+
function toPosix(path) {
|
|
3851
|
+
return path.split("\\").join("/");
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
// ../server/src/internal/workspace/git-log.ts
|
|
3802
3855
|
import { execFile } from "node:child_process";
|
|
3856
|
+
import { relative as relative3 } from "node:path";
|
|
3857
|
+
var DEFAULT_MAX_COMMITS = 250;
|
|
3858
|
+
var HARD_MAX_COMMITS = 500;
|
|
3859
|
+
var FIELD = "";
|
|
3860
|
+
var RECORD = "";
|
|
3861
|
+
var PRETTY_FORMAT = ["%H", "%h", "%P", "%D", "%an", "%aI", "%s"].join(FIELD) + RECORD;
|
|
3862
|
+
async function readGitLog(cwd, requestedMax, requestedSkip) {
|
|
3863
|
+
const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
3864
|
+
if (inside?.trim() !== "true") {
|
|
3865
|
+
return { isRepo: false, commits: [] };
|
|
3866
|
+
}
|
|
3867
|
+
const max = clampMax(requestedMax);
|
|
3868
|
+
const skip = clampSkip(requestedSkip);
|
|
3869
|
+
const [root, branch, raw] = await Promise.all([
|
|
3870
|
+
gitText(["rev-parse", "--show-toplevel"], cwd),
|
|
3871
|
+
gitText(["rev-parse", "--abbrev-ref", "HEAD"], cwd),
|
|
3872
|
+
gitText(
|
|
3873
|
+
[
|
|
3874
|
+
"log",
|
|
3875
|
+
"--all",
|
|
3876
|
+
"--date-order",
|
|
3877
|
+
`--skip=${skip}`,
|
|
3878
|
+
`--max-count=${max}`,
|
|
3879
|
+
`--pretty=format:${PRETTY_FORMAT}`
|
|
3880
|
+
],
|
|
3881
|
+
cwd
|
|
3882
|
+
)
|
|
3883
|
+
]);
|
|
3884
|
+
return {
|
|
3885
|
+
isRepo: true,
|
|
3886
|
+
root: root?.trim() || cwd,
|
|
3887
|
+
branch: branch?.trim() || void 0,
|
|
3888
|
+
commits: parseCommits(raw ?? "")
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
function clampMax(requested) {
|
|
3892
|
+
if (requested === void 0 || !Number.isFinite(requested)) {
|
|
3893
|
+
return DEFAULT_MAX_COMMITS;
|
|
3894
|
+
}
|
|
3895
|
+
return Math.max(1, Math.min(HARD_MAX_COMMITS, Math.floor(requested)));
|
|
3896
|
+
}
|
|
3897
|
+
async function readGitStatus(cwd) {
|
|
3898
|
+
const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
3899
|
+
if (inside?.trim() !== "true") {
|
|
3900
|
+
return { isRepo: false, files: [] };
|
|
3901
|
+
}
|
|
3902
|
+
const [prefix, raw] = await Promise.all([
|
|
3903
|
+
gitText(["rev-parse", "--show-prefix"], cwd),
|
|
3904
|
+
gitText(["status", "--porcelain=v1", "-z", "--untracked-files=all"], cwd)
|
|
3905
|
+
]);
|
|
3906
|
+
return { isRepo: true, files: parseStatus(raw ?? "", prefix?.trim() ?? "") };
|
|
3907
|
+
}
|
|
3908
|
+
function parseStatus(raw, root) {
|
|
3909
|
+
const records = raw.split("\0");
|
|
3910
|
+
const files = [];
|
|
3911
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
3912
|
+
const record = records[index];
|
|
3913
|
+
if (!record || record.length < 4) {
|
|
3914
|
+
continue;
|
|
3915
|
+
}
|
|
3916
|
+
const status = record.slice(0, 2);
|
|
3917
|
+
if (status[0] === "R" || status[0] === "C") {
|
|
3918
|
+
index += 1;
|
|
3919
|
+
}
|
|
3920
|
+
const full = record.slice(3);
|
|
3921
|
+
if (root.length > 0 && !full.startsWith(root)) {
|
|
3922
|
+
continue;
|
|
3923
|
+
}
|
|
3924
|
+
const path = full.slice(root.length);
|
|
3925
|
+
if (path.length > 0) {
|
|
3926
|
+
files.push({ path: toPosix2(path), status });
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
return files;
|
|
3930
|
+
}
|
|
3931
|
+
async function readGitDiff(cwd, requested) {
|
|
3932
|
+
const rel = toPosix2(relative3(cwd, resolveWithin(cwd, requested)));
|
|
3933
|
+
const diff = await gitText(
|
|
3934
|
+
["diff", "--no-color", "--no-ext-diff", "-U3", "HEAD", "--", rel],
|
|
3935
|
+
cwd
|
|
3936
|
+
);
|
|
3937
|
+
return { path: rel, diff: diff ?? "" };
|
|
3938
|
+
}
|
|
3939
|
+
function toPosix2(path) {
|
|
3940
|
+
return path.split("\\").join("/");
|
|
3941
|
+
}
|
|
3942
|
+
function clampSkip(requested) {
|
|
3943
|
+
if (requested === void 0 || !Number.isFinite(requested)) {
|
|
3944
|
+
return 0;
|
|
3945
|
+
}
|
|
3946
|
+
return Math.max(0, Math.floor(requested));
|
|
3947
|
+
}
|
|
3948
|
+
function parseCommits(raw) {
|
|
3949
|
+
const commits = [];
|
|
3950
|
+
for (const record of raw.split(RECORD)) {
|
|
3951
|
+
const line = record.replace(/^\n+/, "");
|
|
3952
|
+
if (line.trim().length === 0) {
|
|
3953
|
+
continue;
|
|
3954
|
+
}
|
|
3955
|
+
const fields = line.split(FIELD);
|
|
3956
|
+
const [hash, shortHash, parents, refs, author, date, subject] = fields;
|
|
3957
|
+
if (!hash || !shortHash || !date) {
|
|
3958
|
+
continue;
|
|
3959
|
+
}
|
|
3960
|
+
commits.push({
|
|
3961
|
+
hash,
|
|
3962
|
+
shortHash,
|
|
3963
|
+
parents: (parents ?? "").split(" ").filter((parent) => parent.length > 0),
|
|
3964
|
+
refs: parseRefs(refs ?? ""),
|
|
3965
|
+
author: author ?? "",
|
|
3966
|
+
date,
|
|
3967
|
+
subject: subject ?? ""
|
|
3968
|
+
});
|
|
3969
|
+
}
|
|
3970
|
+
return commits;
|
|
3971
|
+
}
|
|
3972
|
+
function parseRefs(raw) {
|
|
3973
|
+
const refs = raw.split(",").map((ref) => ref.trim()).filter((ref) => ref.length > 0);
|
|
3974
|
+
return refs;
|
|
3975
|
+
}
|
|
3976
|
+
function gitText(args, cwd) {
|
|
3977
|
+
return new Promise((resolve6) => {
|
|
3978
|
+
execFile(
|
|
3979
|
+
"git",
|
|
3980
|
+
args,
|
|
3981
|
+
{ cwd, timeout: 5e3, maxBuffer: 16 * 1024 * 1024, windowsHide: true },
|
|
3982
|
+
(error, stdout) => resolve6(error ? void 0 : stdout)
|
|
3983
|
+
);
|
|
3984
|
+
});
|
|
3985
|
+
}
|
|
3986
|
+
|
|
3987
|
+
// ../server/src/internal/workspace/workspace-index.ts
|
|
3988
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
3803
3989
|
import { readdir as readdir4 } from "node:fs/promises";
|
|
3804
|
-
import { join as join7, relative as
|
|
3990
|
+
import { join as join7, relative as relative4 } from "node:path";
|
|
3805
3991
|
var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set([
|
|
3806
3992
|
"node_modules",
|
|
3807
3993
|
"dist",
|
|
@@ -3822,15 +4008,15 @@ var MAX_INDEXED_FILES = 5e3;
|
|
|
3822
4008
|
var INDEX_TTL_MS = 6e4;
|
|
3823
4009
|
var indexes = /* @__PURE__ */ new Map();
|
|
3824
4010
|
var inFlight = /* @__PURE__ */ new Map();
|
|
3825
|
-
async function workspaceFiles(cwd, logger) {
|
|
3826
|
-
const { files, source } = await cachedIndex(cwd);
|
|
4011
|
+
async function workspaceFiles(cwd, logger, options = {}) {
|
|
4012
|
+
const { files, source } = await cachedIndex(cwd, options.fresh === true);
|
|
3827
4013
|
logger.info(`File picker: ${files.length} entries via ${source} in ${cwd}`);
|
|
3828
4014
|
return files;
|
|
3829
4015
|
}
|
|
3830
|
-
async function cachedIndex(cwd) {
|
|
4016
|
+
async function cachedIndex(cwd, fresh) {
|
|
3831
4017
|
const now = Date.now();
|
|
3832
4018
|
const cached = indexes.get(cwd);
|
|
3833
|
-
if (cached && now - cached.at < INDEX_TTL_MS) {
|
|
4019
|
+
if (!fresh && cached && now - cached.at < INDEX_TTL_MS) {
|
|
3834
4020
|
return cached;
|
|
3835
4021
|
}
|
|
3836
4022
|
const pending = inFlight.get(cwd);
|
|
@@ -3885,12 +4071,12 @@ async function gitFileList(cwd) {
|
|
|
3885
4071
|
}
|
|
3886
4072
|
}
|
|
3887
4073
|
function runGit(args, cwd) {
|
|
3888
|
-
return new Promise((
|
|
3889
|
-
|
|
4074
|
+
return new Promise((resolve6) => {
|
|
4075
|
+
execFile2(
|
|
3890
4076
|
"git",
|
|
3891
4077
|
args,
|
|
3892
4078
|
{ cwd, timeout: 5e3, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
|
|
3893
|
-
(error, stdout) =>
|
|
4079
|
+
(error, stdout) => resolve6(error ? void 0 : stdout)
|
|
3894
4080
|
);
|
|
3895
4081
|
});
|
|
3896
4082
|
}
|
|
@@ -3921,11 +4107,11 @@ async function walk(root, directory, files) {
|
|
|
3921
4107
|
continue;
|
|
3922
4108
|
}
|
|
3923
4109
|
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
3924
|
-
files.push(
|
|
4110
|
+
files.push(toPosix3(relative4(root, full)));
|
|
3925
4111
|
}
|
|
3926
4112
|
}
|
|
3927
4113
|
}
|
|
3928
|
-
function
|
|
4114
|
+
function toPosix3(path) {
|
|
3929
4115
|
return path.split("\\").join("/");
|
|
3930
4116
|
}
|
|
3931
4117
|
|
|
@@ -3952,6 +4138,12 @@ var MorseSessionFactory = class {
|
|
|
3952
4138
|
// The server can walk the active session's directory, so the composer's
|
|
3953
4139
|
// `@` picker works here too — the same list VS Code offers for a workspace.
|
|
3954
4140
|
filePicker: true,
|
|
4141
|
+
// A browser has no editor of its own, so this host reads files for the
|
|
4142
|
+
// frontend's Explorer and preview tabs. VS Code keeps its native ones.
|
|
4143
|
+
filePreview: true,
|
|
4144
|
+
// The same reasoning for history: VS Code has Source Control, so the
|
|
4145
|
+
// browser host is the one that answers `gitLog` for its own git panel.
|
|
4146
|
+
gitPanel: true,
|
|
3955
4147
|
// A browser cannot hand a dragged file's path to pi, so the host takes the
|
|
3956
4148
|
// bytes and writes them next to the session; the frontend then `@mentions`
|
|
3957
4149
|
// the path it gets back.
|
|
@@ -4015,15 +4207,41 @@ var MorseSessionFactory = class {
|
|
|
4015
4207
|
}
|
|
4016
4208
|
case "listFiles": {
|
|
4017
4209
|
const cwd = this.requireWritableCwd(context);
|
|
4018
|
-
const files = await workspaceFiles(cwd, this.logger);
|
|
4210
|
+
const files = await workspaceFiles(cwd, this.logger, { fresh: args?.fresh === true });
|
|
4019
4211
|
return { files };
|
|
4020
4212
|
}
|
|
4213
|
+
case "readFile": {
|
|
4214
|
+
const cwd = this.requireWritableCwd(context);
|
|
4215
|
+
const path = typeof args?.path === "string" ? args.path : "";
|
|
4216
|
+
if (path.length === 0) {
|
|
4217
|
+
throw new UnsupportedByHostError('readFile needs a "path" argument.');
|
|
4218
|
+
}
|
|
4219
|
+
return readWorkspaceFile(cwd, path);
|
|
4220
|
+
}
|
|
4021
4221
|
case "uploadFile": {
|
|
4022
4222
|
const cwd = this.requireWritableCwd(context);
|
|
4023
4223
|
const saved = await saveUpload(cwd, args ?? {}, this.config.uploadDir);
|
|
4024
4224
|
this.logger.info(`Upload stored: ${saved.path} (${saved.bytes} bytes)`);
|
|
4025
4225
|
return saved;
|
|
4026
4226
|
}
|
|
4227
|
+
case "gitLog": {
|
|
4228
|
+
const cwd = this.requireWritableCwd(context);
|
|
4229
|
+
const max = typeof args?.max === "number" ? args.max : void 0;
|
|
4230
|
+
const skip = typeof args?.skip === "number" ? args.skip : void 0;
|
|
4231
|
+
return readGitLog(cwd, max, skip);
|
|
4232
|
+
}
|
|
4233
|
+
case "gitStatus": {
|
|
4234
|
+
const cwd = this.requireWritableCwd(context);
|
|
4235
|
+
return readGitStatus(cwd);
|
|
4236
|
+
}
|
|
4237
|
+
case "gitDiff": {
|
|
4238
|
+
const cwd = this.requireWritableCwd(context);
|
|
4239
|
+
const path = typeof args?.path === "string" ? args.path : "";
|
|
4240
|
+
if (path.length === 0) {
|
|
4241
|
+
throw new UnsupportedByHostError('gitDiff needs a "path" argument.');
|
|
4242
|
+
}
|
|
4243
|
+
return readGitDiff(cwd, path);
|
|
4244
|
+
}
|
|
4027
4245
|
default:
|
|
4028
4246
|
throw new UnsupportedByHostError(`This host does not support "${command}".`);
|
|
4029
4247
|
}
|
|
@@ -4107,7 +4325,7 @@ MorseGateway = __decorateClass([
|
|
|
4107
4325
|
], MorseGateway);
|
|
4108
4326
|
|
|
4109
4327
|
// ../server/src/app/config.ts
|
|
4110
|
-
import { basename as basename4, dirname as dirname2, isAbsolute as
|
|
4328
|
+
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute6, join as join8 } from "node:path";
|
|
4111
4329
|
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
4112
4330
|
import { fileURLToPath } from "node:url";
|
|
4113
4331
|
function loadConfig(env = process.env) {
|
|
@@ -4118,7 +4336,7 @@ function loadConfig(env = process.env) {
|
|
|
4118
4336
|
const monorepoUiDir = join8(moduleDir, "..", "..", "..", "ui-angular", "dist");
|
|
4119
4337
|
const defaultUiDir = existsSync2(join8(bundledUiDir, "index.html")) ? bundledUiDir : monorepoUiDir;
|
|
4120
4338
|
const defaultHost = "127.0.0.1";
|
|
4121
|
-
const uiDir = configuredUiDir ?
|
|
4339
|
+
const uiDir = configuredUiDir ? isAbsolute6(configuredUiDir) ? configuredUiDir : join8(process.cwd(), configuredUiDir) : defaultUiDir;
|
|
4122
4340
|
return {
|
|
4123
4341
|
host: env.MORSE_HOST ?? defaultHost,
|
|
4124
4342
|
port: parsePositiveInt(env.MORSE_PORT, 4399),
|