@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 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((resolve4, reject) => {
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) => resolve4(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((resolve4) => {
1129
+ await new Promise((resolve5) => {
1124
1130
  const finish = () => {
1125
1131
  exited = true;
1126
- resolve4();
1132
+ resolve5();
1127
1133
  };
1128
1134
  child.once("exit", finish);
1129
1135
  child.stdin.end();
1130
- setTimeout(resolve4, SHUTDOWN_GRACE_MS).unref?.();
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: toCommands(commands?.commands),
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 toCommands(items) {
1551
- return (items ?? []).filter((item) => {
1552
- return typeof item.name === "string" && item.name.trim().length > 0;
1553
- }).map((item) => ({
1554
- name: item.name,
1555
- description: item.description,
1556
- source: toCommandSource(item.source)
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 = join(directory, `${binary}${extension}`);
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 ?? join(homedir(), ".pi", "agent", "sessions");
1655
- return isAbsolute(base) ? base : join(process.cwd(), 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 join2, resolve, sep } from "node:path";
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 = resolve(id);
1799
- const directory = resolve(this.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 = join2(this.directory, bucket.name);
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 = join2(bucketPath, entry.name);
1818
- const info = await stat(path).catch(() => void 0);
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 stat(path).catch(() => void 0);
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 readFile(path, "utf8").catch(() => void 0);
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 readdir(path, { withFileTypes: true });
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 readFile2 } from "node:fs/promises";
3357
- import { join as join3 } from "node:path";
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 readFile2(join3(this.config.uiDir, "webview.manifest.json"), "utf8").catch(
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 resolve2, sep as sep2 } from "node:path";
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) => resolve2(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 = resolve2(path);
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 join4, relative, sep as sep3 } from "node:path";
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 : join4(cwd, 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: basename(target), bytes: bytes.length };
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 = basename(value.replace(/\\/g, "/")).trim();
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 = join4(directory, name);
3702
+ let candidate = join5(directory, name);
3534
3703
  let suffix = 1;
3535
3704
  while (existsSync(candidate)) {
3536
- candidate = join4(directory, `${stem}-${suffix}${extension}`);
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 readdir2, stat as stat2 } from "node:fs/promises";
3552
- import { homedir as homedir2 } from "node:os";
3553
- import { basename as basename2, dirname, isAbsolute as isAbsolute4, join as join5, resolve as resolve3 } from "node:path";
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 = resolve3(requested && requested.length > 0 ? requested : options.defaultPath);
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 readdir2(start, { withFileTypes: true });
3577
- const directories = entries.filter((entry) => entry.isDirectory()).filter((entry) => !shouldSkip(entry.name)).map((entry) => ({ name: entry.name, path: join5(start, entry.name) })).sort((left, right) => left.name.localeCompare(right.name));
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 stat2(join5(path, ".git"));
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 ?? [], homedir2(), options.defaultPath]) {
3770
+ for (const candidate of [...options.roots ?? [], homedir3(), options.defaultPath]) {
3602
3771
  if (!candidate || !isAbsolute4(candidate)) {
3603
3772
  continue;
3604
3773
  }
3605
- const path = resolve3(candidate);
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: basename2(path) || path, path });
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 readdir3 } from "node:fs/promises";
3618
- import { join as join6, relative as relative2 } from "node:path";
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((resolve4) => {
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) => resolve4(error ? void 0 : 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 readdir3(directory, { withFileTypes: true });
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 = join6(directory, entry.name);
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 basename3, dirname as dirname2, isAbsolute as isAbsolute5, join as join7 } from "node:path";
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 = join7(moduleDir, "ui");
3932
- const monorepoUiDir = join7(moduleDir, "..", "..", "..", "ui-angular", "dist");
3933
- const defaultUiDir = existsSync2(join7(bundledUiDir, "index.html")) ? bundledUiDir : monorepoUiDir;
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 : join7(process.cwd(), configuredUiDir) : defaultUiDir;
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: basename3(workspacePath) || workspacePath },
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(join7(uiDir, FRONTEND_MANIFEST_FILE), "utf8"))
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 = join7(directory, "package.json");
4156
+ const manifest = join8(directory, "package.json");
3988
4157
  if (existsSync2(manifest) && declaresWorkspaces(manifest)) {
3989
4158
  return directory;
3990
4159
  }