@supanadit/morse-web 0.3.0 → 0.4.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 +235 -66
- package/dist/server.mjs.map +4 -4
- package/dist/ui/index.html +1 -1
- package/dist/ui/main-SZ5UAVYL.js +90 -0
- package/dist/ui/webview.manifest.json +2 -2
- package/package.json +1 -1
- package/dist/ui/main-QNDG2ZHO.js +0 -88
package/dist/server.mjs
CHANGED
|
@@ -32,6 +32,7 @@ var CLIENT_MESSAGE_TYPES = [
|
|
|
32
32
|
"session/compact",
|
|
33
33
|
"history/load",
|
|
34
34
|
"session/list",
|
|
35
|
+
"commands/refresh",
|
|
35
36
|
"project/list",
|
|
36
37
|
"project/open",
|
|
37
38
|
"model/set",
|
|
@@ -104,6 +105,11 @@ function frontendIdentity(manifest) {
|
|
|
104
105
|
// ../server/src/app.module.ts
|
|
105
106
|
import { Module } from "@nestjs/common";
|
|
106
107
|
|
|
108
|
+
// ../adapter-pi-rpc/dist/pi-rpc-agent.js
|
|
109
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
110
|
+
import { homedir } from "node:os";
|
|
111
|
+
import { basename, join, resolve } from "node:path";
|
|
112
|
+
|
|
107
113
|
// ../core/dist/domain.js
|
|
108
114
|
var THINKING_LEVELS = [
|
|
109
115
|
"off",
|
|
@@ -1094,14 +1100,14 @@ var PiRpcClient = class {
|
|
|
1094
1100
|
throw this.failed ?? new AgentUnavailableError("The pi agent is not running.");
|
|
1095
1101
|
}
|
|
1096
1102
|
const id = `morse-${this.nextId++}`;
|
|
1097
|
-
return new Promise((
|
|
1103
|
+
return new Promise((resolve5, reject) => {
|
|
1098
1104
|
const timer = setTimeout(() => {
|
|
1099
1105
|
this.pending.delete(id);
|
|
1100
1106
|
reject(new AgentProtocolError(`pi did not answer "${command.type}" within ${timeoutMs} ms.`));
|
|
1101
1107
|
}, timeoutMs);
|
|
1102
1108
|
this.pending.set(id, {
|
|
1103
1109
|
command: command.type,
|
|
1104
|
-
resolve: (data) =>
|
|
1110
|
+
resolve: (data) => resolve5(data),
|
|
1105
1111
|
reject,
|
|
1106
1112
|
timer
|
|
1107
1113
|
});
|
|
@@ -1120,14 +1126,14 @@ var PiRpcClient = class {
|
|
|
1120
1126
|
return;
|
|
1121
1127
|
}
|
|
1122
1128
|
let exited = false;
|
|
1123
|
-
await new Promise((
|
|
1129
|
+
await new Promise((resolve5) => {
|
|
1124
1130
|
const finish = () => {
|
|
1125
1131
|
exited = true;
|
|
1126
|
-
|
|
1132
|
+
resolve5();
|
|
1127
1133
|
};
|
|
1128
1134
|
child.once("exit", finish);
|
|
1129
1135
|
child.stdin.end();
|
|
1130
|
-
setTimeout(
|
|
1136
|
+
setTimeout(resolve5, SHUTDOWN_GRACE_MS).unref?.();
|
|
1131
1137
|
});
|
|
1132
1138
|
if (!exited) {
|
|
1133
1139
|
child.kill();
|
|
@@ -1401,13 +1407,33 @@ var PiRpcAgent = class {
|
|
|
1401
1407
|
thinkingLevel: thinkingLevel ?? this.sessionState.thinkingLevel,
|
|
1402
1408
|
availableModels,
|
|
1403
1409
|
availableThinkingLevels: availableThinkingLevels.length > 0 ? availableThinkingLevels : [...THINKING_LEVELS],
|
|
1404
|
-
availableCommands:
|
|
1410
|
+
availableCommands: await buildCommandList(commands?.commands, this.commandContext()),
|
|
1405
1411
|
streaming: state.isStreaming === true
|
|
1406
1412
|
};
|
|
1407
1413
|
this.ready = true;
|
|
1408
1414
|
await this.refreshStats();
|
|
1409
1415
|
this.emit({ type: "agent/ready", at: this.now(), state: this.snapshotState() });
|
|
1410
1416
|
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Re-reads the resources behind `get_commands` and reports the result the same
|
|
1419
|
+
* way a fresh spawn would. The palette calls it the moment it opens, so a new
|
|
1420
|
+
* or edited template is there by the time the reader finishes typing.
|
|
1421
|
+
*/
|
|
1422
|
+
async refreshCommands() {
|
|
1423
|
+
if (this.disposed || !this.ready) {
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
const commands = await this.client.request({ type: "get_commands" }).catch(() => void 0);
|
|
1427
|
+
this.sessionState = {
|
|
1428
|
+
...this.sessionState,
|
|
1429
|
+
availableCommands: await buildCommandList(commands?.commands, this.commandContext())
|
|
1430
|
+
};
|
|
1431
|
+
this.emit({ type: "agent/state", at: this.now(), state: this.snapshotState() });
|
|
1432
|
+
}
|
|
1433
|
+
/** Where a command list is rebuilt from: the session's workspace and env. */
|
|
1434
|
+
commandContext() {
|
|
1435
|
+
return { cwd: this.options.workspace.cwd, env: this.options.env };
|
|
1436
|
+
}
|
|
1411
1437
|
/**
|
|
1412
1438
|
* Pulls the cumulative token/cost/context totals pi keeps for the session, so
|
|
1413
1439
|
* the footer can show the same numbers the TUI does. Best-effort: an older pi
|
|
@@ -1547,14 +1573,141 @@ function dedupeThinkingLevels(levels) {
|
|
|
1547
1573
|
function toCommandSource(value) {
|
|
1548
1574
|
return value === "prompt" || value === "skill" ? value : "extension";
|
|
1549
1575
|
}
|
|
1550
|
-
function
|
|
1551
|
-
|
|
1552
|
-
return
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1576
|
+
async function readTemplateFile(path) {
|
|
1577
|
+
if (path === void 0 || path.length === 0) {
|
|
1578
|
+
return void 0;
|
|
1579
|
+
}
|
|
1580
|
+
try {
|
|
1581
|
+
return await readFile(path, "utf8");
|
|
1582
|
+
} catch {
|
|
1583
|
+
return void 0;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
async function buildCommandList(items, context) {
|
|
1587
|
+
const commands = [];
|
|
1588
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1589
|
+
let projectTrusted = false;
|
|
1590
|
+
for (const item of items ?? []) {
|
|
1591
|
+
if (typeof item.name !== "string" || item.name.trim().length === 0) {
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
const source = toCommandSource(item.source);
|
|
1595
|
+
if (source !== "prompt") {
|
|
1596
|
+
commands.push({ name: item.name, description: item.description, source });
|
|
1597
|
+
seen.add(item.name);
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
if (item.sourceInfo?.scope === "project") {
|
|
1601
|
+
projectTrusted = true;
|
|
1602
|
+
}
|
|
1603
|
+
const template = await readTemplateFile(item.sourceInfo?.path);
|
|
1604
|
+
if (template === void 0) {
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
commands.push({
|
|
1608
|
+
name: item.name,
|
|
1609
|
+
// Prefer the freshly parsed description so an edited frontmatter reflects
|
|
1610
|
+
// too; pi's cached description is the fallback.
|
|
1611
|
+
description: promptDescription(template) ?? item.description,
|
|
1612
|
+
source,
|
|
1613
|
+
template
|
|
1614
|
+
});
|
|
1615
|
+
seen.add(item.name);
|
|
1616
|
+
}
|
|
1617
|
+
for (const dir of await promptDirs(projectTrusted, context)) {
|
|
1618
|
+
for (const file of await listPromptFiles(dir)) {
|
|
1619
|
+
if (seen.has(file.name)) {
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
const template = await readTemplateFile(file.path);
|
|
1623
|
+
if (template === void 0) {
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
commands.push({
|
|
1627
|
+
name: file.name,
|
|
1628
|
+
description: promptDescription(template),
|
|
1629
|
+
source: "prompt",
|
|
1630
|
+
template
|
|
1631
|
+
});
|
|
1632
|
+
seen.add(file.name);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
return commands;
|
|
1636
|
+
}
|
|
1637
|
+
async function promptDirs(projectTrusted, context) {
|
|
1638
|
+
const dirs = [join(agentDir(context.env), "prompts")];
|
|
1639
|
+
if (projectTrusted || await isProjectTrusted(context.cwd, context.env)) {
|
|
1640
|
+
dirs.push(resolve(context.cwd, ".pi", "prompts"));
|
|
1641
|
+
}
|
|
1642
|
+
return dirs;
|
|
1643
|
+
}
|
|
1644
|
+
function agentDir(env) {
|
|
1645
|
+
const configured = env?.PI_CODING_AGENT_DIR?.trim();
|
|
1646
|
+
return configured && configured.length > 0 ? expandHome(configured) : join(homedir(), ".pi", "agent");
|
|
1647
|
+
}
|
|
1648
|
+
function expandHome(path) {
|
|
1649
|
+
if (path === "~") {
|
|
1650
|
+
return homedir();
|
|
1651
|
+
}
|
|
1652
|
+
if (path.startsWith("~/") || path.startsWith("~\\")) {
|
|
1653
|
+
return join(homedir(), path.slice(2));
|
|
1654
|
+
}
|
|
1655
|
+
return path;
|
|
1656
|
+
}
|
|
1657
|
+
async function isProjectTrusted(cwd, env) {
|
|
1658
|
+
try {
|
|
1659
|
+
const raw = await readFile(join(agentDir(env), "trust.json"), "utf8");
|
|
1660
|
+
const decisions = JSON.parse(raw);
|
|
1661
|
+
return decisions[cwd] === true || decisions[resolve(cwd)] === true;
|
|
1662
|
+
} catch {
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
async function listPromptFiles(dir) {
|
|
1667
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1668
|
+
const files = [];
|
|
1669
|
+
for (const entry of entries) {
|
|
1670
|
+
if (!entry.name.endsWith(".md")) {
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
const path = join(dir, entry.name);
|
|
1674
|
+
let isFile = entry.isFile();
|
|
1675
|
+
if (entry.isSymbolicLink()) {
|
|
1676
|
+
try {
|
|
1677
|
+
isFile = (await stat(path)).isFile();
|
|
1678
|
+
} catch {
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
if (isFile) {
|
|
1683
|
+
files.push({ name: basename(entry.name, ".md"), path });
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
return files;
|
|
1687
|
+
}
|
|
1688
|
+
function promptDescription(raw) {
|
|
1689
|
+
let body = raw;
|
|
1690
|
+
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(raw);
|
|
1691
|
+
if (frontmatter !== null) {
|
|
1692
|
+
const described = /^description:[ \t]*(.*)$/m.exec(frontmatter[1]);
|
|
1693
|
+
if (described !== null) {
|
|
1694
|
+
const value = unquote(described[1].trim());
|
|
1695
|
+
if (value.length > 0) {
|
|
1696
|
+
return value;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
body = raw.slice(frontmatter[0].length);
|
|
1700
|
+
}
|
|
1701
|
+
const firstLine = body.split("\n").find((line) => line.trim().length > 0);
|
|
1702
|
+
if (firstLine === void 0) {
|
|
1703
|
+
return void 0;
|
|
1704
|
+
}
|
|
1705
|
+
const trimmed = firstLine.trim();
|
|
1706
|
+
return trimmed.length > 60 ? `${trimmed.slice(0, 60)}...` : trimmed;
|
|
1707
|
+
}
|
|
1708
|
+
function unquote(value) {
|
|
1709
|
+
const match = /^(['"])([\s\S]*)\1$/.exec(value);
|
|
1710
|
+
return match === null ? value : match[2];
|
|
1558
1711
|
}
|
|
1559
1712
|
function toNoticeLevel(value) {
|
|
1560
1713
|
if (value === "error") {
|
|
@@ -1590,8 +1743,8 @@ ${mentions}` : mentions;
|
|
|
1590
1743
|
|
|
1591
1744
|
// ../adapter-pi-rpc/dist/internal/resolve-pi.js
|
|
1592
1745
|
import { accessSync, constants } from "node:fs";
|
|
1593
|
-
import { homedir } from "node:os";
|
|
1594
|
-
import { delimiter, isAbsolute, join } from "node:path";
|
|
1746
|
+
import { homedir as homedir2 } from "node:os";
|
|
1747
|
+
import { delimiter, isAbsolute, join as join2 } from "node:path";
|
|
1595
1748
|
var PI_INSTALL_COMMAND = "npm install -g @earendil-works/pi-coding-agent";
|
|
1596
1749
|
function buildRpcArgs(options) {
|
|
1597
1750
|
const args = ["--mode", "rpc"];
|
|
@@ -1640,7 +1793,7 @@ function findOnPath(binary, env = process.env) {
|
|
|
1640
1793
|
continue;
|
|
1641
1794
|
}
|
|
1642
1795
|
for (const extension of extensions) {
|
|
1643
|
-
const candidate =
|
|
1796
|
+
const candidate = join2(directory, `${binary}${extension}`);
|
|
1644
1797
|
if (isExecutableEntry(candidate)) {
|
|
1645
1798
|
return candidate;
|
|
1646
1799
|
}
|
|
@@ -1651,8 +1804,8 @@ function findOnPath(binary, env = process.env) {
|
|
|
1651
1804
|
function resolveSessionDir(configured, env = process.env) {
|
|
1652
1805
|
const fromEnv = env.PI_CODING_AGENT_SESSION_DIR;
|
|
1653
1806
|
const value = configured ?? (fromEnv && fromEnv.length > 0 ? fromEnv : void 0);
|
|
1654
|
-
const base = value ??
|
|
1655
|
-
return isAbsolute(base) ? base :
|
|
1807
|
+
const base = value ?? join2(homedir2(), ".pi", "agent", "sessions");
|
|
1808
|
+
return isAbsolute(base) ? base : join2(process.cwd(), base);
|
|
1656
1809
|
}
|
|
1657
1810
|
function isExecutableEntry(path) {
|
|
1658
1811
|
try {
|
|
@@ -1719,8 +1872,8 @@ var PiRpcAgentFactory = class {
|
|
|
1719
1872
|
};
|
|
1720
1873
|
|
|
1721
1874
|
// ../adapter-pi-rpc/dist/pi-rpc-session-catalog.js
|
|
1722
|
-
import { open, readdir, readFile, stat, unlink } from "node:fs/promises";
|
|
1723
|
-
import { join as
|
|
1875
|
+
import { open, readdir as readdir2, readFile as readFile2, stat as stat2, unlink } from "node:fs/promises";
|
|
1876
|
+
import { join as join3, resolve as resolve2, sep } from "node:path";
|
|
1724
1877
|
var TYPE_PREFIX = '{"type":"';
|
|
1725
1878
|
function typeAt(raw, start, end) {
|
|
1726
1879
|
if (!raw.startsWith(TYPE_PREFIX, start)) {
|
|
@@ -1795,8 +1948,8 @@ var PiRpcSessionCatalog = class {
|
|
|
1795
1948
|
* a delete of an arbitrary file.
|
|
1796
1949
|
*/
|
|
1797
1950
|
async remove(id) {
|
|
1798
|
-
const path =
|
|
1799
|
-
const directory =
|
|
1951
|
+
const path = resolve2(id);
|
|
1952
|
+
const directory = resolve2(this.directory);
|
|
1800
1953
|
if (!path.startsWith(`${directory}${sep}`) || !path.endsWith(".jsonl")) {
|
|
1801
1954
|
throw new Error(`Refusing to delete a session outside ${directory}`);
|
|
1802
1955
|
}
|
|
@@ -1809,13 +1962,13 @@ var PiRpcSessionCatalog = class {
|
|
|
1809
1962
|
if (!bucket.isDirectory()) {
|
|
1810
1963
|
continue;
|
|
1811
1964
|
}
|
|
1812
|
-
const bucketPath =
|
|
1965
|
+
const bucketPath = join3(this.directory, bucket.name);
|
|
1813
1966
|
for (const entry of await safeReadDir(bucketPath)) {
|
|
1814
1967
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
|
|
1815
1968
|
continue;
|
|
1816
1969
|
}
|
|
1817
|
-
const path =
|
|
1818
|
-
const info = await
|
|
1970
|
+
const path = join3(bucketPath, entry.name);
|
|
1971
|
+
const info = await stat2(path).catch(() => void 0);
|
|
1819
1972
|
files.push({ path, size: info?.size ?? 0, updatedAt: info?.mtimeMs ?? 0 });
|
|
1820
1973
|
}
|
|
1821
1974
|
}
|
|
@@ -1826,11 +1979,11 @@ var PiRpcSessionCatalog = class {
|
|
|
1826
1979
|
async function readSessionSummary(path, updatedAt, previous) {
|
|
1827
1980
|
let resumable = previous !== void 0 && previous.scanned > 0 && updatedAt >= previous.updatedAt;
|
|
1828
1981
|
if (resumable && previous !== void 0) {
|
|
1829
|
-
const info = await
|
|
1982
|
+
const info = await stat2(path).catch(() => void 0);
|
|
1830
1983
|
resumable = info !== void 0 && info.size > previous.scanned;
|
|
1831
1984
|
}
|
|
1832
1985
|
const from = resumable && previous !== void 0 ? previous.scanned : 0;
|
|
1833
|
-
const raw = resumable ? await readFrom(path, from).catch(() => void 0) : await
|
|
1986
|
+
const raw = resumable ? await readFrom(path, from).catch(() => void 0) : await readFile2(path, "utf8").catch(() => void 0);
|
|
1834
1987
|
if (raw === void 0) {
|
|
1835
1988
|
return void 0;
|
|
1836
1989
|
}
|
|
@@ -1951,7 +2104,7 @@ function titleFromText(text) {
|
|
|
1951
2104
|
}
|
|
1952
2105
|
async function safeReadDir(path) {
|
|
1953
2106
|
try {
|
|
1954
|
-
return await
|
|
2107
|
+
return await readdir2(path, { withFileTypes: true });
|
|
1955
2108
|
} catch {
|
|
1956
2109
|
return [];
|
|
1957
2110
|
}
|
|
@@ -2401,7 +2554,8 @@ function toCommandOption(command) {
|
|
|
2401
2554
|
return {
|
|
2402
2555
|
name: command.name,
|
|
2403
2556
|
description: command.description,
|
|
2404
|
-
source: command.source
|
|
2557
|
+
source: command.source,
|
|
2558
|
+
template: command.template
|
|
2405
2559
|
};
|
|
2406
2560
|
}
|
|
2407
2561
|
function toThinkingLevel2(level) {
|
|
@@ -2591,6 +2745,9 @@ var HostSessionController = class {
|
|
|
2591
2745
|
case "session/list":
|
|
2592
2746
|
await this.publishSessions();
|
|
2593
2747
|
return;
|
|
2748
|
+
case "commands/refresh":
|
|
2749
|
+
await this.refreshCommands();
|
|
2750
|
+
return;
|
|
2594
2751
|
case "project/list":
|
|
2595
2752
|
await this.publishProjects();
|
|
2596
2753
|
return;
|
|
@@ -3013,6 +3170,18 @@ var HostSessionController = class {
|
|
|
3013
3170
|
}
|
|
3014
3171
|
await this.guard(() => gateway.respondToInteraction(response));
|
|
3015
3172
|
}
|
|
3173
|
+
/**
|
|
3174
|
+
* The palette asked for a fresh command list. The agent re-reads the template
|
|
3175
|
+
* files and reports back through `agent/state`, so the composer sees a template
|
|
3176
|
+
* added, changed or deleted since the session started — without a pi reload.
|
|
3177
|
+
*/
|
|
3178
|
+
async refreshCommands() {
|
|
3179
|
+
const gateway = this.options.services.registry.active();
|
|
3180
|
+
if (gateway?.refreshCommands === void 0) {
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3183
|
+
await this.guard(() => gateway.refreshCommands());
|
|
3184
|
+
}
|
|
3016
3185
|
async runHostCommand(command, args, requestId) {
|
|
3017
3186
|
const handler = this.options.onHostCommand;
|
|
3018
3187
|
if (!handler) {
|
|
@@ -3353,8 +3522,8 @@ function hintOnly(hint) {
|
|
|
3353
3522
|
|
|
3354
3523
|
// ../server/src/internal/http/health.controller.ts
|
|
3355
3524
|
import { Controller, Get } from "@nestjs/common";
|
|
3356
|
-
import { readFile as
|
|
3357
|
-
import { join as
|
|
3525
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
3526
|
+
import { join as join4 } from "node:path";
|
|
3358
3527
|
import { Inject } from "@nestjs/common";
|
|
3359
3528
|
|
|
3360
3529
|
// ../server/src/app/tokens.ts
|
|
@@ -3379,7 +3548,7 @@ var HealthController = class {
|
|
|
3379
3548
|
};
|
|
3380
3549
|
}
|
|
3381
3550
|
async manifest() {
|
|
3382
|
-
const raw = await
|
|
3551
|
+
const raw = await readFile3(join4(this.config.uiDir, "webview.manifest.json"), "utf8").catch(
|
|
3383
3552
|
() => void 0
|
|
3384
3553
|
);
|
|
3385
3554
|
return parseFrontendManifest(raw) ?? { name: "unknown", protocolVersion: PROTOCOL_VERSION };
|
|
@@ -3430,10 +3599,10 @@ function withDetail(message, detail) {
|
|
|
3430
3599
|
}
|
|
3431
3600
|
|
|
3432
3601
|
// ../server/src/internal/projects/project-policy.ts
|
|
3433
|
-
import { isAbsolute as isAbsolute2, resolve as
|
|
3602
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
3434
3603
|
var ServerProjectPolicy = class {
|
|
3435
3604
|
constructor(roots) {
|
|
3436
|
-
this.roots = roots.filter((root) => root.trim().length > 0).map((root) =>
|
|
3605
|
+
this.roots = roots.filter((root) => root.trim().length > 0).map((root) => resolve3(root));
|
|
3437
3606
|
}
|
|
3438
3607
|
get allowList() {
|
|
3439
3608
|
return [...this.roots];
|
|
@@ -3445,7 +3614,7 @@ var ServerProjectPolicy = class {
|
|
|
3445
3614
|
if (this.roots.length === 0) {
|
|
3446
3615
|
return true;
|
|
3447
3616
|
}
|
|
3448
|
-
const target =
|
|
3617
|
+
const target = resolve3(path);
|
|
3449
3618
|
return this.roots.some(
|
|
3450
3619
|
(root) => target === root || target.startsWith(`${root}${sep2}`)
|
|
3451
3620
|
);
|
|
@@ -3491,7 +3660,7 @@ import { Inject as Inject3, Injectable as Injectable2 } from "@nestjs/common";
|
|
|
3491
3660
|
// ../server/src/internal/uploads/upload-store.ts
|
|
3492
3661
|
import { existsSync } from "node:fs";
|
|
3493
3662
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3494
|
-
import { basename, isAbsolute as isAbsolute3, join as
|
|
3663
|
+
import { basename as basename2, isAbsolute as isAbsolute3, join as join5, relative, sep as sep3 } from "node:path";
|
|
3495
3664
|
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
3496
3665
|
var DEFAULT_UPLOAD_DIR = ".morse/uploads";
|
|
3497
3666
|
var UploadError = class extends Error {
|
|
@@ -3511,15 +3680,15 @@ async function saveUpload(cwd, request, uploadDir = DEFAULT_UPLOAD_DIR) {
|
|
|
3511
3680
|
`That file is ${formatMb(bytes.length)} MB; the limit is ${formatMb(MAX_UPLOAD_BYTES)} MB.`
|
|
3512
3681
|
);
|
|
3513
3682
|
}
|
|
3514
|
-
const directory = isAbsolute3(uploadDir) ? uploadDir :
|
|
3683
|
+
const directory = isAbsolute3(uploadDir) ? uploadDir : join5(cwd, uploadDir);
|
|
3515
3684
|
await mkdir(directory, { recursive: true });
|
|
3516
3685
|
const target = uniquePath(directory, name);
|
|
3517
3686
|
await writeFile(target, bytes);
|
|
3518
|
-
return { path: mentionPath(cwd, target), name:
|
|
3687
|
+
return { path: mentionPath(cwd, target), name: basename2(target), bytes: bytes.length };
|
|
3519
3688
|
}
|
|
3520
3689
|
function safeFileName(raw) {
|
|
3521
3690
|
const value = typeof raw === "string" ? raw : "";
|
|
3522
|
-
const base =
|
|
3691
|
+
const base = basename2(value.replace(/\\/g, "/")).trim();
|
|
3523
3692
|
const cleaned = base.replace(/[\u0000-\u001f<>:"|?*]/g, "_").replace(/^\.+/, "").trim();
|
|
3524
3693
|
if (cleaned.length === 0) {
|
|
3525
3694
|
throw new UploadError("That upload had no usable file name.");
|
|
@@ -3530,10 +3699,10 @@ function uniquePath(directory, name) {
|
|
|
3530
3699
|
const dot = name.lastIndexOf(".");
|
|
3531
3700
|
const stem = dot > 0 ? name.slice(0, dot) : name;
|
|
3532
3701
|
const extension = dot > 0 ? name.slice(dot) : "";
|
|
3533
|
-
let candidate =
|
|
3702
|
+
let candidate = join5(directory, name);
|
|
3534
3703
|
let suffix = 1;
|
|
3535
3704
|
while (existsSync(candidate)) {
|
|
3536
|
-
candidate =
|
|
3705
|
+
candidate = join5(directory, `${stem}-${suffix}${extension}`);
|
|
3537
3706
|
suffix += 1;
|
|
3538
3707
|
}
|
|
3539
3708
|
return candidate;
|
|
@@ -3548,9 +3717,9 @@ function formatMb(bytes) {
|
|
|
3548
3717
|
}
|
|
3549
3718
|
|
|
3550
3719
|
// ../server/src/internal/workspace/directory-browser.ts
|
|
3551
|
-
import { readdir as
|
|
3552
|
-
import { homedir as
|
|
3553
|
-
import { basename as
|
|
3720
|
+
import { readdir as readdir3, stat as stat3 } from "node:fs/promises";
|
|
3721
|
+
import { homedir as homedir3 } from "node:os";
|
|
3722
|
+
import { basename as basename3, dirname, isAbsolute as isAbsolute4, join as join6, resolve as resolve4 } from "node:path";
|
|
3554
3723
|
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
3555
3724
|
"node_modules",
|
|
3556
3725
|
"dist",
|
|
@@ -3569,12 +3738,12 @@ var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
3569
3738
|
]);
|
|
3570
3739
|
async function browseDirectory(path, options) {
|
|
3571
3740
|
const requested = path?.trim();
|
|
3572
|
-
const start =
|
|
3741
|
+
const start = resolve4(requested && requested.length > 0 ? requested : options.defaultPath);
|
|
3573
3742
|
if (requested && !isAbsolute4(requested)) {
|
|
3574
3743
|
throw new Error(`A folder path must be absolute: ${requested}`);
|
|
3575
3744
|
}
|
|
3576
|
-
const entries = await
|
|
3577
|
-
const directories = entries.filter((entry) => entry.isDirectory()).filter((entry) => !shouldSkip(entry.name)).map((entry) => ({ name: entry.name, path:
|
|
3745
|
+
const entries = await readdir3(start, { withFileTypes: true });
|
|
3746
|
+
const directories = entries.filter((entry) => entry.isDirectory()).filter((entry) => !shouldSkip(entry.name)).map((entry) => ({ name: entry.name, path: join6(start, entry.name) })).sort((left, right) => left.name.localeCompare(right.name));
|
|
3578
3747
|
const parent = dirname(start);
|
|
3579
3748
|
return {
|
|
3580
3749
|
path: start,
|
|
@@ -3589,7 +3758,7 @@ function shouldSkip(name) {
|
|
|
3589
3758
|
}
|
|
3590
3759
|
async function isGitRepo(path) {
|
|
3591
3760
|
try {
|
|
3592
|
-
await
|
|
3761
|
+
await stat3(join6(path, ".git"));
|
|
3593
3762
|
return true;
|
|
3594
3763
|
} catch {
|
|
3595
3764
|
return false;
|
|
@@ -3598,24 +3767,24 @@ async function isGitRepo(path) {
|
|
|
3598
3767
|
function shortcutRoots(options) {
|
|
3599
3768
|
const seen = /* @__PURE__ */ new Set();
|
|
3600
3769
|
const roots = [];
|
|
3601
|
-
for (const candidate of [...options.roots ?? [],
|
|
3770
|
+
for (const candidate of [...options.roots ?? [], homedir3(), options.defaultPath]) {
|
|
3602
3771
|
if (!candidate || !isAbsolute4(candidate)) {
|
|
3603
3772
|
continue;
|
|
3604
3773
|
}
|
|
3605
|
-
const path =
|
|
3774
|
+
const path = resolve4(candidate);
|
|
3606
3775
|
if (seen.has(path)) {
|
|
3607
3776
|
continue;
|
|
3608
3777
|
}
|
|
3609
3778
|
seen.add(path);
|
|
3610
|
-
roots.push({ name:
|
|
3779
|
+
roots.push({ name: basename3(path) || path, path });
|
|
3611
3780
|
}
|
|
3612
3781
|
return roots;
|
|
3613
3782
|
}
|
|
3614
3783
|
|
|
3615
3784
|
// ../server/src/internal/workspace/workspace-index.ts
|
|
3616
3785
|
import { execFile } from "node:child_process";
|
|
3617
|
-
import { readdir as
|
|
3618
|
-
import { join as
|
|
3786
|
+
import { readdir as readdir4 } from "node:fs/promises";
|
|
3787
|
+
import { join as join7, relative as relative2 } from "node:path";
|
|
3619
3788
|
var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set([
|
|
3620
3789
|
"node_modules",
|
|
3621
3790
|
"dist",
|
|
@@ -3699,12 +3868,12 @@ async function gitFileList(cwd) {
|
|
|
3699
3868
|
}
|
|
3700
3869
|
}
|
|
3701
3870
|
function runGit(args, cwd) {
|
|
3702
|
-
return new Promise((
|
|
3871
|
+
return new Promise((resolve5) => {
|
|
3703
3872
|
execFile(
|
|
3704
3873
|
"git",
|
|
3705
3874
|
args,
|
|
3706
3875
|
{ cwd, timeout: 5e3, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
|
|
3707
|
-
(error, stdout) =>
|
|
3876
|
+
(error, stdout) => resolve5(error ? void 0 : stdout)
|
|
3708
3877
|
);
|
|
3709
3878
|
});
|
|
3710
3879
|
}
|
|
@@ -3718,7 +3887,7 @@ async function walk(root, directory, files) {
|
|
|
3718
3887
|
}
|
|
3719
3888
|
let entries;
|
|
3720
3889
|
try {
|
|
3721
|
-
entries = await
|
|
3890
|
+
entries = await readdir4(directory, { withFileTypes: true });
|
|
3722
3891
|
} catch {
|
|
3723
3892
|
return;
|
|
3724
3893
|
}
|
|
@@ -3726,7 +3895,7 @@ async function walk(root, directory, files) {
|
|
|
3726
3895
|
if (files.length >= MAX_INDEXED_FILES) {
|
|
3727
3896
|
return;
|
|
3728
3897
|
}
|
|
3729
|
-
const full =
|
|
3898
|
+
const full = join7(directory, entry.name);
|
|
3730
3899
|
if (entry.isDirectory()) {
|
|
3731
3900
|
if (SKIPPED_DIRECTORIES2.has(entry.name) || entry.name.startsWith(".")) {
|
|
3732
3901
|
continue;
|
|
@@ -3921,24 +4090,24 @@ MorseGateway = __decorateClass([
|
|
|
3921
4090
|
], MorseGateway);
|
|
3922
4091
|
|
|
3923
4092
|
// ../server/src/app/config.ts
|
|
3924
|
-
import { basename as
|
|
4093
|
+
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute5, join as join8 } from "node:path";
|
|
3925
4094
|
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
3926
4095
|
import { fileURLToPath } from "node:url";
|
|
3927
4096
|
function loadConfig(env = process.env) {
|
|
3928
4097
|
const workspacePath = resolveWorkspacePath(env);
|
|
3929
4098
|
const configuredUiDir = env.MORSE_UI_DIR;
|
|
3930
4099
|
const moduleDir = dirname2(fileURLToPath(import.meta.url));
|
|
3931
|
-
const bundledUiDir =
|
|
3932
|
-
const monorepoUiDir =
|
|
3933
|
-
const defaultUiDir = existsSync2(
|
|
4100
|
+
const bundledUiDir = join8(moduleDir, "ui");
|
|
4101
|
+
const monorepoUiDir = join8(moduleDir, "..", "..", "..", "ui-angular", "dist");
|
|
4102
|
+
const defaultUiDir = existsSync2(join8(bundledUiDir, "index.html")) ? bundledUiDir : monorepoUiDir;
|
|
3934
4103
|
const defaultHost = "127.0.0.1";
|
|
3935
|
-
const uiDir = configuredUiDir ? isAbsolute5(configuredUiDir) ? configuredUiDir :
|
|
4104
|
+
const uiDir = configuredUiDir ? isAbsolute5(configuredUiDir) ? configuredUiDir : join8(process.cwd(), configuredUiDir) : defaultUiDir;
|
|
3936
4105
|
return {
|
|
3937
4106
|
host: env.MORSE_HOST ?? defaultHost,
|
|
3938
4107
|
port: parsePositiveInt(env.MORSE_PORT, 4399),
|
|
3939
4108
|
uiDir,
|
|
3940
4109
|
frontend: readFrontendIdentity(uiDir),
|
|
3941
|
-
workspace: { cwd: workspacePath, name:
|
|
4110
|
+
workspace: { cwd: workspacePath, name: basename4(workspacePath) || workspacePath },
|
|
3942
4111
|
piPath: env.MORSE_PI_PATH,
|
|
3943
4112
|
nodeEntryPath: env.MORSE_PI_ENTRY,
|
|
3944
4113
|
sessionDir: env.MORSE_SESSION_DIR,
|
|
@@ -3953,7 +4122,7 @@ function loadConfig(env = process.env) {
|
|
|
3953
4122
|
function readFrontendIdentity(uiDir) {
|
|
3954
4123
|
try {
|
|
3955
4124
|
return frontendIdentity(
|
|
3956
|
-
parseFrontendManifest(readFileSync(
|
|
4125
|
+
parseFrontendManifest(readFileSync(join8(uiDir, FRONTEND_MANIFEST_FILE), "utf8"))
|
|
3957
4126
|
);
|
|
3958
4127
|
} catch {
|
|
3959
4128
|
return void 0;
|
|
@@ -3984,7 +4153,7 @@ function resolveWorkspacePath(env) {
|
|
|
3984
4153
|
const start = process.cwd();
|
|
3985
4154
|
let directory = start;
|
|
3986
4155
|
for (; ; ) {
|
|
3987
|
-
const manifest =
|
|
4156
|
+
const manifest = join8(directory, "package.json");
|
|
3988
4157
|
if (existsSync2(manifest) && declaresWorkspaces(manifest)) {
|
|
3989
4158
|
return directory;
|
|
3990
4159
|
}
|