@bytespell/shella 0.1.11 → 0.1.14
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/bin/cli.js +1209 -304
- package/dist/bin/cli.js.map +1 -1
- package/dist/index.js +1209 -304
- package/dist/index.js.map +1 -1
- package/dist/public/assets/{_baseUniq-xMBLm_vj.js → _baseUniq-DFrDAdNH.js} +1 -1
- package/{public/assets/arc-CCivY36l.js → dist/public/assets/arc-Dn-EBh2M.js} +1 -1
- package/{public/assets/architectureDiagram-VXUJARFQ-Cx1ftnZs.js → dist/public/assets/architectureDiagram-VXUJARFQ-cLPhT_VH.js} +1 -1
- package/{public/assets/blockDiagram-VD42YOAC-Dd5J4pAa.js → dist/public/assets/blockDiagram-VD42YOAC-D9YWCJd4.js} +1 -1
- package/dist/public/assets/{c4Diagram-YG6GDRKO-C4yoJFk1.js → c4Diagram-YG6GDRKO-7iBiFNxv.js} +1 -1
- package/dist/public/assets/channel-Bc28uGr5.js +1 -0
- package/dist/public/assets/{chunk-4BX2VUAB-CiImuKHD.js → chunk-4BX2VUAB-DHospLZi.js} +1 -1
- package/dist/public/assets/{chunk-55IACEB6-V6l1i-_P.js → chunk-55IACEB6-DYi5BOlX.js} +1 -1
- package/{public/assets/chunk-B4BG7PRW-DU8Wql9S.js → dist/public/assets/chunk-B4BG7PRW-B9aL4JLb.js} +1 -1
- package/{public/assets/chunk-DI55MBZ5-B_Jw-lhj.js → dist/public/assets/chunk-DI55MBZ5-ljO7G9dq.js} +1 -1
- package/dist/public/assets/{chunk-FMBD7UC4-C1LlA37K.js → chunk-FMBD7UC4-Cwe-EPLG.js} +1 -1
- package/dist/public/assets/{chunk-QN33PNHL-CcgyTGxJ.js → chunk-QN33PNHL-DEnaVKU1.js} +1 -1
- package/dist/public/assets/{chunk-QZHKN3VN-C2H0lOPM.js → chunk-QZHKN3VN-CWDdzKRs.js} +1 -1
- package/dist/public/assets/{chunk-TZMSLE5B-Cp4Dpzkh.js → chunk-TZMSLE5B-Bpz-P3mQ.js} +1 -1
- package/dist/public/assets/classDiagram-2ON5EDUG-DJRc91IB.js +1 -0
- package/dist/public/assets/classDiagram-v2-WZHVMYZB-DJRc91IB.js +1 -0
- package/dist/public/assets/clone-CkWk6F_f.js +1 -0
- package/dist/public/assets/{code-block-IT6T5CEO-DOOuyfxG.js → code-block-IT6T5CEO-Cd4wcD52.js} +1 -1
- package/dist/public/assets/{cose-bilkent-S5V4N54A-DefjjRca.js → cose-bilkent-S5V4N54A-BWl9wqzp.js} +1 -1
- package/dist/public/assets/{dagre-6UL2VRFP-hHpxpOKX.js → dagre-6UL2VRFP-DGDxxVd8.js} +1 -1
- package/dist/public/assets/{diagram-PSM6KHXK-ZgZ2X0Q-.js → diagram-PSM6KHXK-iQjjIACd.js} +1 -1
- package/dist/public/assets/{diagram-QEK2KX5R-gCeAqKj1.js → diagram-QEK2KX5R-D-rDIvx4.js} +1 -1
- package/dist/public/assets/{diagram-S2PKOQOG-9IyVqMFa.js → diagram-S2PKOQOG-BV8jEZTk.js} +1 -1
- package/{public/assets/erDiagram-Q2GNP2WA-Dzm33cik.js → dist/public/assets/erDiagram-Q2GNP2WA-XJ0647Zi.js} +1 -1
- package/{public/assets/flowDiagram-NV44I4VS-BamNhi0g.js → dist/public/assets/flowDiagram-NV44I4VS-DSH4uel9.js} +1 -1
- package/dist/public/assets/{ganttDiagram-JELNMOA3-CrECl9wX.js → ganttDiagram-JELNMOA3-Cz4yT-vm.js} +1 -1
- package/dist/public/assets/{gitGraphDiagram-NY62KEGX-C7_E64uK.js → gitGraphDiagram-NY62KEGX-Y2DLg6xz.js} +1 -1
- package/dist/public/assets/{graph-CtTAhETf.js → graph-z0wljCJ_.js} +1 -1
- package/dist/public/assets/index-CCsulXgB.css +1 -0
- package/dist/public/assets/index-DplAQDze.js +1781 -0
- package/dist/public/assets/index-iDg75qrw.js +23 -0
- package/dist/public/assets/{infoDiagram-WHAUD3N6-CLOougXO.js → infoDiagram-WHAUD3N6-G5NGCtYh.js} +1 -1
- package/dist/public/assets/{journeyDiagram-XKPGCS4Q-g0dISD-I.js → journeyDiagram-XKPGCS4Q-BQ7qAaof.js} +1 -1
- package/{public/assets/kanban-definition-3W4ZIXB7-DFIVrLyR.js → dist/public/assets/kanban-definition-3W4ZIXB7-DnMJJT5X.js} +1 -1
- package/dist/public/assets/{layout-DolajoeL.js → layout-CbJei3Fo.js} +1 -1
- package/dist/public/assets/{linear-B5Hf7uIN.js → linear-3YD7PkfQ.js} +1 -1
- package/dist/public/assets/{mermaid.core-DqP3HtOk.js → mermaid.core-DjfX8Ewh.js} +5 -5
- package/dist/public/assets/{min-UFvm8GLY.js → min-Chhh-zX-.js} +1 -1
- package/dist/public/assets/{mindmap-definition-VGOIOE7T-CnSTRcIt.js → mindmap-definition-VGOIOE7T-CLM5K3ub.js} +1 -1
- package/dist/public/assets/{pieDiagram-ADFJNKIX-CuMH9Po8.js → pieDiagram-ADFJNKIX-BFiT_I_m.js} +1 -1
- package/{public/assets/quadrantDiagram-AYHSOK5B-6i0SX3Xo.js → dist/public/assets/quadrantDiagram-AYHSOK5B-Be7QQurL.js} +1 -1
- package/dist/public/assets/{requirementDiagram-UZGBJVZJ-ChwO2HU-.js → requirementDiagram-UZGBJVZJ-5NrHk2Xy.js} +1 -1
- package/{public/assets/sankeyDiagram-TZEHDZUN-DG-8crEL.js → dist/public/assets/sankeyDiagram-TZEHDZUN-DQ8Siqqh.js} +1 -1
- package/dist/public/assets/{sequenceDiagram-WL72ISMW-BQH_OfEp.js → sequenceDiagram-WL72ISMW-Bj_sOA9D.js} +1 -1
- package/{public/assets/stateDiagram-FKZM4ZOC-CBOkbVQn.js → dist/public/assets/stateDiagram-FKZM4ZOC-DncnQ-99.js} +1 -1
- package/dist/public/assets/stateDiagram-v2-4FDKWEC3-BdQ5RGa6.js +1 -0
- package/{public/assets/timeline-definition-IT6M3QCI-CkNd8y6W.js → dist/public/assets/timeline-definition-IT6M3QCI-C9uFv_GY.js} +1 -1
- package/dist/public/assets/{treemap-KMMF4GRG-D6z_eudj.js → treemap-KMMF4GRG-CTzKLdTW.js} +1 -1
- package/{public/assets/xychartDiagram-PRI3JC2R-B5wk4XRt.js → dist/public/assets/xychartDiagram-PRI3JC2R-BP2VkXwS.js} +1 -1
- package/dist/public/index.html +15 -2
- package/package.json +6 -2
- package/public/assets/{_baseUniq-xMBLm_vj.js → _baseUniq-DFrDAdNH.js} +1 -1
- package/{dist/public/assets/arc-CCivY36l.js → public/assets/arc-Dn-EBh2M.js} +1 -1
- package/{dist/public/assets/architectureDiagram-VXUJARFQ-Cx1ftnZs.js → public/assets/architectureDiagram-VXUJARFQ-cLPhT_VH.js} +1 -1
- package/{dist/public/assets/blockDiagram-VD42YOAC-Dd5J4pAa.js → public/assets/blockDiagram-VD42YOAC-D9YWCJd4.js} +1 -1
- package/public/assets/{c4Diagram-YG6GDRKO-C4yoJFk1.js → c4Diagram-YG6GDRKO-7iBiFNxv.js} +1 -1
- package/public/assets/channel-Bc28uGr5.js +1 -0
- package/public/assets/{chunk-4BX2VUAB-CiImuKHD.js → chunk-4BX2VUAB-DHospLZi.js} +1 -1
- package/public/assets/{chunk-55IACEB6-V6l1i-_P.js → chunk-55IACEB6-DYi5BOlX.js} +1 -1
- package/{dist/public/assets/chunk-B4BG7PRW-DU8Wql9S.js → public/assets/chunk-B4BG7PRW-B9aL4JLb.js} +1 -1
- package/{dist/public/assets/chunk-DI55MBZ5-B_Jw-lhj.js → public/assets/chunk-DI55MBZ5-ljO7G9dq.js} +1 -1
- package/public/assets/{chunk-FMBD7UC4-C1LlA37K.js → chunk-FMBD7UC4-Cwe-EPLG.js} +1 -1
- package/public/assets/{chunk-QN33PNHL-CcgyTGxJ.js → chunk-QN33PNHL-DEnaVKU1.js} +1 -1
- package/public/assets/{chunk-QZHKN3VN-C2H0lOPM.js → chunk-QZHKN3VN-CWDdzKRs.js} +1 -1
- package/public/assets/{chunk-TZMSLE5B-Cp4Dpzkh.js → chunk-TZMSLE5B-Bpz-P3mQ.js} +1 -1
- package/public/assets/classDiagram-2ON5EDUG-DJRc91IB.js +1 -0
- package/public/assets/classDiagram-v2-WZHVMYZB-DJRc91IB.js +1 -0
- package/public/assets/clone-CkWk6F_f.js +1 -0
- package/public/assets/{code-block-IT6T5CEO-DOOuyfxG.js → code-block-IT6T5CEO-Cd4wcD52.js} +1 -1
- package/public/assets/{cose-bilkent-S5V4N54A-DefjjRca.js → cose-bilkent-S5V4N54A-BWl9wqzp.js} +1 -1
- package/public/assets/{dagre-6UL2VRFP-hHpxpOKX.js → dagre-6UL2VRFP-DGDxxVd8.js} +1 -1
- package/public/assets/{diagram-PSM6KHXK-ZgZ2X0Q-.js → diagram-PSM6KHXK-iQjjIACd.js} +1 -1
- package/public/assets/{diagram-QEK2KX5R-gCeAqKj1.js → diagram-QEK2KX5R-D-rDIvx4.js} +1 -1
- package/public/assets/{diagram-S2PKOQOG-9IyVqMFa.js → diagram-S2PKOQOG-BV8jEZTk.js} +1 -1
- package/{dist/public/assets/erDiagram-Q2GNP2WA-Dzm33cik.js → public/assets/erDiagram-Q2GNP2WA-XJ0647Zi.js} +1 -1
- package/{dist/public/assets/flowDiagram-NV44I4VS-BamNhi0g.js → public/assets/flowDiagram-NV44I4VS-DSH4uel9.js} +1 -1
- package/public/assets/{ganttDiagram-JELNMOA3-CrECl9wX.js → ganttDiagram-JELNMOA3-Cz4yT-vm.js} +1 -1
- package/public/assets/{gitGraphDiagram-NY62KEGX-C7_E64uK.js → gitGraphDiagram-NY62KEGX-Y2DLg6xz.js} +1 -1
- package/public/assets/{graph-CtTAhETf.js → graph-z0wljCJ_.js} +1 -1
- package/public/assets/index-CCsulXgB.css +1 -0
- package/public/assets/index-DplAQDze.js +1781 -0
- package/public/assets/index-iDg75qrw.js +23 -0
- package/public/assets/{infoDiagram-WHAUD3N6-CLOougXO.js → infoDiagram-WHAUD3N6-G5NGCtYh.js} +1 -1
- package/public/assets/{journeyDiagram-XKPGCS4Q-g0dISD-I.js → journeyDiagram-XKPGCS4Q-BQ7qAaof.js} +1 -1
- package/{dist/public/assets/kanban-definition-3W4ZIXB7-DFIVrLyR.js → public/assets/kanban-definition-3W4ZIXB7-DnMJJT5X.js} +1 -1
- package/public/assets/{layout-DolajoeL.js → layout-CbJei3Fo.js} +1 -1
- package/public/assets/{linear-B5Hf7uIN.js → linear-3YD7PkfQ.js} +1 -1
- package/public/assets/{mermaid.core-DqP3HtOk.js → mermaid.core-DjfX8Ewh.js} +5 -5
- package/public/assets/{min-UFvm8GLY.js → min-Chhh-zX-.js} +1 -1
- package/public/assets/{mindmap-definition-VGOIOE7T-CnSTRcIt.js → mindmap-definition-VGOIOE7T-CLM5K3ub.js} +1 -1
- package/public/assets/{pieDiagram-ADFJNKIX-CuMH9Po8.js → pieDiagram-ADFJNKIX-BFiT_I_m.js} +1 -1
- package/{dist/public/assets/quadrantDiagram-AYHSOK5B-6i0SX3Xo.js → public/assets/quadrantDiagram-AYHSOK5B-Be7QQurL.js} +1 -1
- package/public/assets/{requirementDiagram-UZGBJVZJ-ChwO2HU-.js → requirementDiagram-UZGBJVZJ-5NrHk2Xy.js} +1 -1
- package/{dist/public/assets/sankeyDiagram-TZEHDZUN-DG-8crEL.js → public/assets/sankeyDiagram-TZEHDZUN-DQ8Siqqh.js} +1 -1
- package/public/assets/{sequenceDiagram-WL72ISMW-BQH_OfEp.js → sequenceDiagram-WL72ISMW-Bj_sOA9D.js} +1 -1
- package/{dist/public/assets/stateDiagram-FKZM4ZOC-CBOkbVQn.js → public/assets/stateDiagram-FKZM4ZOC-DncnQ-99.js} +1 -1
- package/public/assets/stateDiagram-v2-4FDKWEC3-BdQ5RGa6.js +1 -0
- package/{dist/public/assets/timeline-definition-IT6M3QCI-CkNd8y6W.js → public/assets/timeline-definition-IT6M3QCI-C9uFv_GY.js} +1 -1
- package/public/assets/{treemap-KMMF4GRG-D6z_eudj.js → treemap-KMMF4GRG-CTzKLdTW.js} +1 -1
- package/{dist/public/assets/xychartDiagram-PRI3JC2R-B5wk4XRt.js → public/assets/xychartDiagram-PRI3JC2R-BP2VkXwS.js} +1 -1
- package/public/index.html +15 -2
- package/dist/public/assets/channel-CgkYY8Ci.js +0 -1
- package/dist/public/assets/classDiagram-2ON5EDUG-BuYVNwmc.js +0 -1
- package/dist/public/assets/classDiagram-v2-WZHVMYZB-BuYVNwmc.js +0 -1
- package/dist/public/assets/clone-mkrd6lC9.js +0 -1
- package/dist/public/assets/index-Czr2q0NP.css +0 -1
- package/dist/public/assets/index-kEIJA0pC.js +0 -1781
- package/dist/public/assets/stateDiagram-v2-4FDKWEC3-BqHz04Bh.js +0 -1
- package/public/assets/channel-CgkYY8Ci.js +0 -1
- package/public/assets/classDiagram-2ON5EDUG-BuYVNwmc.js +0 -1
- package/public/assets/classDiagram-v2-WZHVMYZB-BuYVNwmc.js +0 -1
- package/public/assets/clone-mkrd6lC9.js +0 -1
- package/public/assets/index-Czr2q0NP.css +0 -1
- package/public/assets/index-kEIJA0pC.js +0 -1781
- package/public/assets/stateDiagram-v2-4FDKWEC3-BqHz04Bh.js +0 -1
package/dist/index.js
CHANGED
|
@@ -8,13 +8,12 @@ var __export = (target, all) => {
|
|
|
8
8
|
import express from "express";
|
|
9
9
|
import { createServer } from "http";
|
|
10
10
|
import { WebSocketServer } from "ws";
|
|
11
|
-
import
|
|
12
|
-
import { fileURLToPath } from "url";
|
|
11
|
+
import path10 from "path";
|
|
12
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13
13
|
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
|
14
14
|
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
|
15
|
-
import {
|
|
15
|
+
import { muxManager as muxManager3 } from "@bytespell/amux/streams/manager";
|
|
16
16
|
import { setVerbose, debug } from "@bytespell/amux/lib/logger";
|
|
17
|
-
import "@bytespell/amux/db";
|
|
18
17
|
|
|
19
18
|
// src/db/index.ts
|
|
20
19
|
import Database from "better-sqlite3";
|
|
@@ -56,11 +55,37 @@ function ensureDir(dir) {
|
|
|
56
55
|
var schema_exports = {};
|
|
57
56
|
__export(schema_exports, {
|
|
58
57
|
panes: () => panes,
|
|
58
|
+
streamConfigs: () => streamConfigs,
|
|
59
|
+
streams: () => streams,
|
|
59
60
|
uiState: () => uiState,
|
|
60
61
|
windows: () => windows
|
|
61
62
|
});
|
|
62
63
|
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
63
64
|
import { sql } from "drizzle-orm";
|
|
65
|
+
var streamConfigs = sqliteTable("stream_configs", {
|
|
66
|
+
id: text("id").primaryKey(),
|
|
67
|
+
name: text("name").notNull(),
|
|
68
|
+
/** Driver type for routing (e.g., 'acp', 'pty', or custom plugin types) */
|
|
69
|
+
driverType: text("driver_type").notNull().default("acp"),
|
|
70
|
+
command: text("command").notNull(),
|
|
71
|
+
args: text("args", { mode: "json" }).$type().default([]),
|
|
72
|
+
env: text("env", { mode: "json" }).$type().default({}),
|
|
73
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
74
|
+
});
|
|
75
|
+
var streams = sqliteTable("streams", {
|
|
76
|
+
id: text("id").primaryKey(),
|
|
77
|
+
directory: text("directory").notNull(),
|
|
78
|
+
streamConfigId: text("stream_config_id").notNull().references(() => streamConfigs.id),
|
|
79
|
+
/** Stream type determined by the driver at creation time. Built-in: 'acp', 'pty' */
|
|
80
|
+
streamType: text("stream_type").notNull(),
|
|
81
|
+
/** Generic driver state (e.g., ACP's session ID for resumption) */
|
|
82
|
+
driverState: text("driver_state", { mode: "json" }).$type(),
|
|
83
|
+
/** Title from stream (via session_info_update for ACP) */
|
|
84
|
+
title: text("title"),
|
|
85
|
+
model: text("model"),
|
|
86
|
+
mode: text("mode"),
|
|
87
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
88
|
+
});
|
|
64
89
|
var windows = sqliteTable("windows", {
|
|
65
90
|
id: text("id").primaryKey(),
|
|
66
91
|
title: text("title").notNull(),
|
|
@@ -74,8 +99,12 @@ var windows = sqliteTable("windows", {
|
|
|
74
99
|
var panes = sqliteTable("panes", {
|
|
75
100
|
id: text("id").primaryKey(),
|
|
76
101
|
windowId: text("window_id").notNull().references(() => windows.id, { onDelete: "cascade" }),
|
|
77
|
-
/**
|
|
102
|
+
/** Static content (browser URL, etc.). Null when no content. */
|
|
78
103
|
content: text("content", { mode: "json" }).$type(),
|
|
104
|
+
/** Live event stream (amux stream). Null when no stream. */
|
|
105
|
+
eventStream: text("event_stream", { mode: "json" }).$type(),
|
|
106
|
+
/** Renderer ID (e.g., 'builtin:terminal', 'project:custom-viewer'). Null = use default. */
|
|
107
|
+
rendererId: text("renderer_id"),
|
|
79
108
|
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
|
|
80
109
|
});
|
|
81
110
|
var uiState = sqliteTable("ui_state", {
|
|
@@ -90,6 +119,30 @@ sqlite.pragma("journal_mode = WAL");
|
|
|
90
119
|
sqlite.pragma("foreign_keys = ON");
|
|
91
120
|
var db = drizzle(sqlite, { schema: schema_exports });
|
|
92
121
|
sqlite.exec(`
|
|
122
|
+
-- Stream tables (from amux)
|
|
123
|
+
CREATE TABLE IF NOT EXISTS stream_configs (
|
|
124
|
+
id TEXT PRIMARY KEY,
|
|
125
|
+
name TEXT NOT NULL,
|
|
126
|
+
driver_type TEXT NOT NULL DEFAULT 'acp',
|
|
127
|
+
command TEXT NOT NULL,
|
|
128
|
+
args TEXT DEFAULT '[]',
|
|
129
|
+
env TEXT DEFAULT '{}',
|
|
130
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
CREATE TABLE IF NOT EXISTS streams (
|
|
134
|
+
id TEXT PRIMARY KEY,
|
|
135
|
+
directory TEXT NOT NULL,
|
|
136
|
+
stream_config_id TEXT NOT NULL REFERENCES stream_configs(id),
|
|
137
|
+
stream_type TEXT NOT NULL,
|
|
138
|
+
driver_state TEXT,
|
|
139
|
+
title TEXT,
|
|
140
|
+
model TEXT,
|
|
141
|
+
mode TEXT,
|
|
142
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
-- UI tables
|
|
93
146
|
CREATE TABLE IF NOT EXISTS windows (
|
|
94
147
|
id TEXT PRIMARY KEY,
|
|
95
148
|
title TEXT NOT NULL,
|
|
@@ -104,6 +157,8 @@ sqlite.exec(`
|
|
|
104
157
|
id TEXT PRIMARY KEY,
|
|
105
158
|
window_id TEXT NOT NULL REFERENCES windows(id) ON DELETE CASCADE,
|
|
106
159
|
content TEXT,
|
|
160
|
+
event_stream TEXT,
|
|
161
|
+
renderer_id TEXT,
|
|
107
162
|
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
108
163
|
);
|
|
109
164
|
|
|
@@ -113,157 +168,686 @@ sqlite.exec(`
|
|
|
113
168
|
);
|
|
114
169
|
`);
|
|
115
170
|
try {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
sqlite.exec(`
|
|
121
|
-
|
|
122
|
-
|
|
171
|
+
const configInfo = sqlite.prepare(`PRAGMA table_info(stream_configs)`).all();
|
|
172
|
+
const hasDriverType = configInfo.some((col) => col.name === "driver_type");
|
|
173
|
+
if (!hasDriverType) {
|
|
174
|
+
console.log("[db] Adding driver_type column to stream_configs...");
|
|
175
|
+
sqlite.exec(`ALTER TABLE stream_configs ADD COLUMN driver_type TEXT NOT NULL DEFAULT 'acp'`);
|
|
176
|
+
sqlite.exec(`UPDATE stream_configs SET driver_type = 'pty' WHERE command = '__shell__'`);
|
|
177
|
+
sqlite.exec(`UPDATE stream_configs SET driver_type = 'mock' WHERE command = '__mock__'`);
|
|
178
|
+
console.log("[db] driver_type migration complete");
|
|
179
|
+
}
|
|
180
|
+
} catch (e) {
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const tableInfo = sqlite.prepare(`PRAGMA table_info(panes)`).all();
|
|
184
|
+
const hasProcessColumn = tableInfo.some((col) => col.name === "process");
|
|
185
|
+
const hasEventStreamColumn = tableInfo.some((col) => col.name === "event_stream");
|
|
186
|
+
if (hasProcessColumn && !hasEventStreamColumn) {
|
|
187
|
+
console.log("[db] Migrating panes.process to panes.event_stream...");
|
|
188
|
+
sqlite.exec(`ALTER TABLE panes ADD COLUMN event_stream TEXT`);
|
|
189
|
+
const panesWithProcess = sqlite.prepare(`SELECT id, process FROM panes WHERE process IS NOT NULL`).all();
|
|
190
|
+
for (const pane of panesWithProcess) {
|
|
191
|
+
try {
|
|
192
|
+
const process2 = JSON.parse(pane.process);
|
|
193
|
+
if (process2.type === "session" && process2.sessionId) {
|
|
194
|
+
const eventStream = JSON.stringify({ type: "acp", streamId: process2.sessionId });
|
|
195
|
+
sqlite.prepare(`UPDATE panes SET event_stream = ? WHERE id = ?`).run(eventStream, pane.id);
|
|
196
|
+
}
|
|
197
|
+
} catch (e) {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
console.log("[db] Migration complete, migrated", panesWithProcess.length, "panes");
|
|
201
|
+
}
|
|
202
|
+
} catch (e) {
|
|
203
|
+
}
|
|
123
204
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
205
|
+
// src/lib/configDiscovery.ts
|
|
206
|
+
import { execSync } from "child_process";
|
|
207
|
+
import { randomUUID } from "crypto";
|
|
208
|
+
function commandExists(cmd) {
|
|
209
|
+
try {
|
|
210
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
211
|
+
return true;
|
|
212
|
+
} catch {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
var claudeDiscoverer = {
|
|
217
|
+
name: "claude",
|
|
218
|
+
discover() {
|
|
219
|
+
if (!commandExists("claude")) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
id: randomUUID(),
|
|
224
|
+
name: "claude",
|
|
225
|
+
driverType: "acp",
|
|
226
|
+
command: "npx",
|
|
227
|
+
args: ["@zed-industries/claude-code-acp"],
|
|
228
|
+
env: {},
|
|
229
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
var codexDiscoverer = {
|
|
234
|
+
name: "codex",
|
|
235
|
+
discover() {
|
|
236
|
+
if (!commandExists("codex")) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
id: randomUUID(),
|
|
241
|
+
name: "codex",
|
|
242
|
+
driverType: "acp",
|
|
243
|
+
command: "npx",
|
|
244
|
+
args: ["@zed-industries/codex-acp"],
|
|
245
|
+
env: {},
|
|
246
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var geminiDiscoverer = {
|
|
251
|
+
name: "gemini",
|
|
252
|
+
discover() {
|
|
253
|
+
if (!commandExists("gemini")) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
id: randomUUID(),
|
|
258
|
+
name: "gemini",
|
|
259
|
+
driverType: "acp",
|
|
260
|
+
command: "gemini",
|
|
261
|
+
args: ["--experimental-acp"],
|
|
262
|
+
env: {},
|
|
263
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
var openCodeDiscoverer = {
|
|
268
|
+
name: "opencode",
|
|
269
|
+
discover() {
|
|
270
|
+
if (!commandExists("opencode")) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
id: randomUUID(),
|
|
275
|
+
name: "opencode",
|
|
276
|
+
driverType: "acp",
|
|
277
|
+
command: "opencode",
|
|
278
|
+
args: ["acp"],
|
|
279
|
+
env: {},
|
|
280
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var shellDiscoverer = {
|
|
285
|
+
name: "Shell",
|
|
286
|
+
discover() {
|
|
287
|
+
return {
|
|
288
|
+
id: "shell",
|
|
289
|
+
name: "shell",
|
|
290
|
+
driverType: "pty",
|
|
291
|
+
command: "",
|
|
292
|
+
// PTY driver ignores command, spawns user's $SHELL
|
|
293
|
+
args: [],
|
|
294
|
+
env: {},
|
|
295
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
var builtInDiscoverers = [
|
|
300
|
+
claudeDiscoverer,
|
|
301
|
+
codexDiscoverer,
|
|
302
|
+
geminiDiscoverer,
|
|
303
|
+
openCodeDiscoverer,
|
|
304
|
+
shellDiscoverer
|
|
305
|
+
];
|
|
306
|
+
function discoverAndRegisterConfigs(discoverers) {
|
|
307
|
+
const existing = db.select().from(streamConfigs).all();
|
|
308
|
+
let added = 0;
|
|
309
|
+
for (const discoverer of discoverers) {
|
|
310
|
+
const config = discoverer.discover();
|
|
311
|
+
if (!config) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const alreadyExists = existing.some(
|
|
315
|
+
(e) => e.command === config.command && JSON.stringify(e.args) === JSON.stringify(config.args)
|
|
316
|
+
);
|
|
317
|
+
if (!alreadyExists) {
|
|
318
|
+
db.insert(streamConfigs).values({
|
|
319
|
+
id: config.id,
|
|
320
|
+
name: config.name,
|
|
321
|
+
driverType: config.driverType,
|
|
322
|
+
command: config.command,
|
|
323
|
+
args: config.args,
|
|
324
|
+
env: config.env,
|
|
325
|
+
createdAt: config.createdAt
|
|
326
|
+
}).run();
|
|
327
|
+
console.log(`[config] Discovered: ${config.name}`);
|
|
328
|
+
added++;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (added > 0) {
|
|
332
|
+
console.log(`[config] ${added} new stream config(s) registered`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
131
335
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
336
|
+
// src/lib/driverWatcher.ts
|
|
337
|
+
import chokidar from "chokidar";
|
|
338
|
+
import * as path3 from "path";
|
|
339
|
+
import * as os2 from "os";
|
|
340
|
+
import * as fs3 from "fs/promises";
|
|
341
|
+
import { EventEmitter } from "events";
|
|
342
|
+
|
|
343
|
+
// src/lib/driverCompiler.ts
|
|
344
|
+
import { build } from "esbuild";
|
|
345
|
+
import * as fs2 from "fs/promises";
|
|
346
|
+
import * as path2 from "path";
|
|
347
|
+
import { fileURLToPath } from "url";
|
|
348
|
+
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
349
|
+
async function compileDriver(pluginDir) {
|
|
350
|
+
const serverPath = path2.join(pluginDir, "server.ts");
|
|
351
|
+
try {
|
|
352
|
+
await fs2.access(serverPath);
|
|
353
|
+
} catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
const metaPath = path2.join(pluginDir, "meta.json");
|
|
357
|
+
let meta;
|
|
358
|
+
try {
|
|
359
|
+
const metaContent = await fs2.readFile(metaPath, "utf-8");
|
|
360
|
+
meta = JSON.parse(metaContent);
|
|
361
|
+
} catch (err) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`Failed to read meta.json for ${pluginDir}: ${err instanceof Error ? err.message : String(err)}`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
if (!meta.id || typeof meta.id !== "string") {
|
|
367
|
+
throw new Error(`Plugin in ${pluginDir}: meta.json requires "id" field`);
|
|
368
|
+
}
|
|
369
|
+
if (!meta.name || typeof meta.name !== "string") {
|
|
370
|
+
throw new Error(`Plugin in ${pluginDir}: meta.json requires "name" field`);
|
|
371
|
+
}
|
|
372
|
+
const outDir = path2.join(pluginDir, ".shella");
|
|
373
|
+
const outFile = path2.join(outDir, "server.mjs");
|
|
374
|
+
await fs2.mkdir(outDir, { recursive: true });
|
|
375
|
+
const pkgPath = path2.join(pluginDir, "package.json");
|
|
376
|
+
let hasPackageJson = false;
|
|
377
|
+
try {
|
|
378
|
+
await fs2.access(pkgPath);
|
|
379
|
+
hasPackageJson = true;
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
const shellaRoot = path2.resolve(__dirname, "../..");
|
|
383
|
+
const sdkPath = path2.resolve(shellaRoot, "../sdk/dist");
|
|
384
|
+
await build({
|
|
385
|
+
entryPoints: [serverPath],
|
|
386
|
+
bundle: true,
|
|
387
|
+
write: true,
|
|
388
|
+
format: "esm",
|
|
389
|
+
platform: "node",
|
|
390
|
+
target: "node20",
|
|
391
|
+
outfile: outFile,
|
|
392
|
+
alias: {
|
|
393
|
+
"@bytespell/shella-sdk": path2.join(sdkPath, "index.js"),
|
|
394
|
+
"@bytespell/shella-sdk/server": path2.join(sdkPath, "server.js")
|
|
395
|
+
},
|
|
396
|
+
// If plugin has package.json, look for node_modules
|
|
397
|
+
nodePaths: hasPackageJson ? [path2.join(pluginDir, "node_modules")] : [],
|
|
398
|
+
minify: false
|
|
399
|
+
});
|
|
400
|
+
return {
|
|
401
|
+
id: meta.id,
|
|
402
|
+
name: meta.name,
|
|
403
|
+
filePath: outFile,
|
|
404
|
+
pluginDir
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/lib/driverWatcher.ts
|
|
409
|
+
import { muxManager } from "@bytespell/amux/streams/manager";
|
|
410
|
+
import { eq } from "drizzle-orm";
|
|
411
|
+
var DriverEventEmitter = class extends EventEmitter {
|
|
412
|
+
emit(event, data) {
|
|
413
|
+
return super.emit(event, data);
|
|
414
|
+
}
|
|
415
|
+
on(event, listener) {
|
|
416
|
+
return super.on(event, listener);
|
|
417
|
+
}
|
|
418
|
+
off(event, listener) {
|
|
419
|
+
return super.off(event, listener);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
var loadedDrivers = /* @__PURE__ */ new Map();
|
|
423
|
+
async function loadDriver(compiled) {
|
|
424
|
+
const entry = loadedDrivers.get(compiled.pluginDir);
|
|
425
|
+
const version = (entry?.version ?? 0) + 1;
|
|
426
|
+
const url = `file://${compiled.filePath}?v=${version}`;
|
|
427
|
+
const module = await import(
|
|
428
|
+
/* @vite-ignore */
|
|
429
|
+
url
|
|
430
|
+
);
|
|
431
|
+
const driver = module.default;
|
|
432
|
+
if (!driver || typeof driver.start !== "function") {
|
|
433
|
+
throw new Error(`Plugin driver must export default with start() method`);
|
|
434
|
+
}
|
|
435
|
+
driver.type = compiled.id;
|
|
436
|
+
loadedDrivers.set(compiled.pluginDir, { id: compiled.id, version });
|
|
437
|
+
return driver;
|
|
438
|
+
}
|
|
439
|
+
function ensureStreamConfig(compiled) {
|
|
440
|
+
const existing = db.select().from(streamConfigs).where(eq(streamConfigs.id, compiled.id)).get();
|
|
441
|
+
if (!existing) {
|
|
442
|
+
db.insert(streamConfigs).values({
|
|
443
|
+
id: compiled.id,
|
|
444
|
+
name: compiled.name,
|
|
445
|
+
driverType: compiled.id,
|
|
446
|
+
// Driver type matches plugin id
|
|
447
|
+
command: "",
|
|
448
|
+
// Plugin drivers don't spawn processes
|
|
449
|
+
args: [],
|
|
450
|
+
env: {},
|
|
451
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
452
|
+
}).run();
|
|
453
|
+
} else if (existing.name !== compiled.name) {
|
|
454
|
+
db.update(streamConfigs).set({ name: compiled.name }).where(eq(streamConfigs.id, compiled.id)).run();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function removeStreamConfig(id) {
|
|
458
|
+
db.delete(streamConfigs).where(eq(streamConfigs.id, id)).run();
|
|
459
|
+
}
|
|
460
|
+
function watchDrivers(emitter) {
|
|
461
|
+
const userPath = path3.join(os2.homedir(), ".shella", "renderers");
|
|
462
|
+
const pendingCompiles = /* @__PURE__ */ new Map();
|
|
463
|
+
const watcher = chokidar.watch(userPath, {
|
|
464
|
+
ignoreInitial: false,
|
|
465
|
+
persistent: true,
|
|
466
|
+
awaitWriteFinish: {
|
|
467
|
+
stabilityThreshold: 150,
|
|
468
|
+
pollInterval: 50
|
|
469
|
+
},
|
|
470
|
+
depth: 3
|
|
471
|
+
// Watch inside plugin folders
|
|
472
|
+
});
|
|
473
|
+
const getPluginDir = (filePath) => {
|
|
474
|
+
const relative3 = path3.relative(userPath, filePath);
|
|
475
|
+
const parts = relative3.split(path3.sep);
|
|
476
|
+
if (parts.length >= 1 && parts[0]) {
|
|
477
|
+
return path3.join(userPath, parts[0]);
|
|
478
|
+
}
|
|
479
|
+
return null;
|
|
480
|
+
};
|
|
481
|
+
const isServerFile = (filePath) => {
|
|
482
|
+
const relative3 = path3.relative(userPath, filePath);
|
|
483
|
+
const parts = relative3.split(path3.sep);
|
|
484
|
+
return parts.length >= 2 && (parts[1] === "server.ts" || parts[1]?.startsWith("server/") || filePath.includes("/server/"));
|
|
485
|
+
};
|
|
486
|
+
const scheduleCompile = (pluginDir) => {
|
|
487
|
+
if (pendingCompiles.has(pluginDir)) {
|
|
488
|
+
clearTimeout(pendingCompiles.get(pluginDir));
|
|
489
|
+
}
|
|
490
|
+
pendingCompiles.set(
|
|
491
|
+
pluginDir,
|
|
492
|
+
setTimeout(async () => {
|
|
493
|
+
pendingCompiles.delete(pluginDir);
|
|
494
|
+
await handleDriverChange(pluginDir);
|
|
495
|
+
}, 150)
|
|
496
|
+
);
|
|
497
|
+
};
|
|
498
|
+
const handleDriverChange = async (pluginDir) => {
|
|
499
|
+
try {
|
|
500
|
+
const compiled = await compileDriver(pluginDir);
|
|
501
|
+
if (!compiled) return;
|
|
502
|
+
const existing = loadedDrivers.get(pluginDir);
|
|
503
|
+
if (existing) {
|
|
504
|
+
muxManager.unregisterDriver(existing.id);
|
|
505
|
+
}
|
|
506
|
+
const driver = await loadDriver(compiled);
|
|
507
|
+
muxManager.registerDriver(driver);
|
|
508
|
+
ensureStreamConfig(compiled);
|
|
509
|
+
if (!existing) {
|
|
510
|
+
console.log(`[drivers] Added: ${compiled.id} (${compiled.name})`);
|
|
511
|
+
emitter.emit("driver:added", { id: compiled.id, name: compiled.name });
|
|
512
|
+
} else {
|
|
513
|
+
console.log(`[drivers] Updated: ${compiled.id} (${compiled.name})`);
|
|
514
|
+
emitter.emit("driver:updated", { id: compiled.id, name: compiled.name });
|
|
515
|
+
}
|
|
516
|
+
} catch (err) {
|
|
517
|
+
console.error(`[drivers] Error compiling ${pluginDir}:`, err);
|
|
518
|
+
emitter.emit("driver:error", {
|
|
519
|
+
pluginDir,
|
|
520
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
watcher.on("add", (filePath) => {
|
|
525
|
+
if (isServerFile(filePath)) {
|
|
526
|
+
const pluginDir = getPluginDir(filePath);
|
|
527
|
+
if (pluginDir) scheduleCompile(pluginDir);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
watcher.on("change", (filePath) => {
|
|
531
|
+
if (isServerFile(filePath)) {
|
|
532
|
+
const pluginDir = getPluginDir(filePath);
|
|
533
|
+
if (pluginDir) scheduleCompile(pluginDir);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
watcher.on("unlink", (filePath) => {
|
|
537
|
+
if (filePath.endsWith("server.ts")) {
|
|
538
|
+
const pluginDir = getPluginDir(filePath);
|
|
539
|
+
if (pluginDir) {
|
|
540
|
+
const entry = loadedDrivers.get(pluginDir);
|
|
541
|
+
if (entry) {
|
|
542
|
+
muxManager.unregisterDriver(entry.id);
|
|
543
|
+
removeStreamConfig(entry.id);
|
|
544
|
+
loadedDrivers.delete(pluginDir);
|
|
545
|
+
console.log(`[drivers] Removed: ${entry.id}`);
|
|
546
|
+
emitter.emit("driver:removed", { id: entry.id });
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
watcher.on("error", (err) => {
|
|
552
|
+
console.error("[drivers] Watcher error:", err);
|
|
553
|
+
});
|
|
554
|
+
return () => {
|
|
555
|
+
for (const timeout of pendingCompiles.values()) {
|
|
556
|
+
clearTimeout(timeout);
|
|
557
|
+
}
|
|
558
|
+
pendingCompiles.clear();
|
|
559
|
+
watcher.close();
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
async function scanDrivers() {
|
|
563
|
+
const userPath = path3.join(os2.homedir(), ".shella", "renderers");
|
|
564
|
+
try {
|
|
565
|
+
const entries = await fs3.readdir(userPath, { withFileTypes: true });
|
|
566
|
+
for (const entry of entries) {
|
|
567
|
+
if (!entry.isDirectory()) continue;
|
|
568
|
+
const pluginDir = path3.join(userPath, entry.name);
|
|
569
|
+
try {
|
|
570
|
+
const compiled = await compileDriver(pluginDir);
|
|
571
|
+
if (compiled) {
|
|
572
|
+
const driver = await loadDriver(compiled);
|
|
573
|
+
muxManager.registerDriver(driver);
|
|
574
|
+
ensureStreamConfig(compiled);
|
|
575
|
+
console.log(`[drivers] Loaded: ${compiled.id} (${compiled.name})`);
|
|
576
|
+
}
|
|
577
|
+
} catch (err) {
|
|
578
|
+
console.error(`[drivers] Error loading ${pluginDir}:`, err);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
} catch {
|
|
582
|
+
}
|
|
583
|
+
}
|
|
143
584
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
585
|
+
// src/amuxBridge.ts
|
|
586
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
587
|
+
import path5 from "path";
|
|
588
|
+
import fs5 from "fs";
|
|
589
|
+
import { eq as eq2, inArray } from "drizzle-orm";
|
|
147
590
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
591
|
+
// src/lib/files.ts
|
|
592
|
+
import * as fs4 from "fs/promises";
|
|
593
|
+
import * as path4 from "path";
|
|
594
|
+
import { glob } from "glob";
|
|
595
|
+
async function listFilesForAutocomplete(workingDir, partialPath, limit) {
|
|
596
|
+
const normalized = partialPath.startsWith("./") ? partialPath.slice(2) : partialPath;
|
|
597
|
+
if (normalized.includes("/")) {
|
|
598
|
+
return listFilesPrefix(workingDir, normalized, limit);
|
|
599
|
+
}
|
|
600
|
+
return listFilesFuzzy(workingDir, normalized, limit);
|
|
601
|
+
}
|
|
602
|
+
async function listFilesPrefix(workingDir, partialPath, limit) {
|
|
603
|
+
const lastSlash = partialPath.lastIndexOf("/");
|
|
604
|
+
const dirPart = lastSlash >= 0 ? partialPath.slice(0, lastSlash) : "";
|
|
605
|
+
const prefix = lastSlash >= 0 ? partialPath.slice(lastSlash + 1) : partialPath;
|
|
606
|
+
const targetDir = path4.resolve(workingDir, dirPart);
|
|
607
|
+
const normalizedWorkingDir = path4.resolve(workingDir);
|
|
608
|
+
const normalizedTargetDir = path4.resolve(targetDir);
|
|
609
|
+
if (!normalizedTargetDir.startsWith(normalizedWorkingDir)) {
|
|
610
|
+
return [];
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
const entries = await fs4.readdir(targetDir, { withFileTypes: true });
|
|
614
|
+
const matching = entries.filter((e) => !e.name.startsWith(".")).filter((e) => prefix === "" || e.name.toLowerCase().startsWith(prefix.toLowerCase())).slice(0, limit * 2).map((e) => ({
|
|
615
|
+
name: e.name,
|
|
616
|
+
path: dirPart ? `${dirPart}/${e.name}` : e.name,
|
|
617
|
+
isDirectory: e.isDirectory()
|
|
618
|
+
}));
|
|
619
|
+
matching.sort((a, b) => {
|
|
620
|
+
if (a.isDirectory !== b.isDirectory) {
|
|
621
|
+
return a.isDirectory ? -1 : 1;
|
|
622
|
+
}
|
|
623
|
+
return a.name.localeCompare(b.name);
|
|
624
|
+
});
|
|
625
|
+
return matching.slice(0, limit);
|
|
626
|
+
} catch {
|
|
627
|
+
return [];
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async function listFilesFuzzy(workingDir, query, limit) {
|
|
631
|
+
if (!query.trim()) return [];
|
|
632
|
+
try {
|
|
633
|
+
const pattern = `{*${query}*,**/*${query}*}`;
|
|
634
|
+
const matches = await glob(pattern, {
|
|
635
|
+
cwd: workingDir,
|
|
636
|
+
nodir: false,
|
|
637
|
+
dot: false,
|
|
638
|
+
// Skip hidden files
|
|
639
|
+
ignore: ["**/node_modules/**", "**/.git/**"],
|
|
640
|
+
maxDepth: 10
|
|
641
|
+
});
|
|
642
|
+
const results = [];
|
|
643
|
+
for (const match of matches.slice(0, limit * 2)) {
|
|
644
|
+
try {
|
|
645
|
+
const fullPath = path4.join(workingDir, match);
|
|
646
|
+
const stat2 = await fs4.stat(fullPath);
|
|
647
|
+
results.push({
|
|
648
|
+
name: path4.basename(match),
|
|
649
|
+
path: match,
|
|
650
|
+
isDirectory: stat2.isDirectory()
|
|
651
|
+
});
|
|
652
|
+
} catch {
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
results.sort((a, b) => {
|
|
656
|
+
if (a.isDirectory !== b.isDirectory) {
|
|
657
|
+
return a.isDirectory ? -1 : 1;
|
|
658
|
+
}
|
|
659
|
+
const aDepth = a.path.split("/").length;
|
|
660
|
+
const bDepth = b.path.split("/").length;
|
|
661
|
+
if (aDepth !== bDepth) {
|
|
662
|
+
return aDepth - bDepth;
|
|
663
|
+
}
|
|
664
|
+
return a.path.localeCompare(b.path);
|
|
665
|
+
});
|
|
666
|
+
return results.slice(0, limit);
|
|
667
|
+
} catch {
|
|
668
|
+
return [];
|
|
152
669
|
}
|
|
153
|
-
} catch {
|
|
154
670
|
}
|
|
155
671
|
|
|
156
672
|
// src/amuxBridge.ts
|
|
157
|
-
import {
|
|
158
|
-
import {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
673
|
+
import { muxManager as muxManager2 } from "@bytespell/amux/streams/manager";
|
|
674
|
+
import { isAcpEvent } from "@bytespell/amux/types";
|
|
675
|
+
function getStreamStorageDir(streamId) {
|
|
676
|
+
return path5.join(getDataDir(), "streams", streamId);
|
|
677
|
+
}
|
|
678
|
+
function generateTitleFromMessage(message) {
|
|
679
|
+
const cleaned = message.replace(/@[\w./~-]+/g, "").replace(/\s+/g, " ").trim();
|
|
680
|
+
const firstLine = cleaned.split("\n")[0] || cleaned;
|
|
681
|
+
if (firstLine.length <= 50) return firstLine;
|
|
682
|
+
const truncated = firstLine.slice(0, 50);
|
|
683
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
684
|
+
return lastSpace > 20 ? truncated.slice(0, lastSpace) + "..." : truncated + "...";
|
|
685
|
+
}
|
|
164
686
|
function createAmuxBridge() {
|
|
165
|
-
|
|
166
|
-
async
|
|
687
|
+
const bridge = {
|
|
688
|
+
async getStreams(ids) {
|
|
167
689
|
if (ids.length === 0) return [];
|
|
168
|
-
return
|
|
690
|
+
return db.select().from(streams).where(inArray(streams.id, ids)).all();
|
|
169
691
|
},
|
|
170
|
-
async
|
|
171
|
-
return
|
|
692
|
+
async getStream(id) {
|
|
693
|
+
return db.select().from(streams).where(eq2(streams.id, id)).get() ?? null;
|
|
172
694
|
},
|
|
173
|
-
async
|
|
174
|
-
let configId = params.
|
|
695
|
+
async createStream(params) {
|
|
696
|
+
let configId = params.streamConfigId;
|
|
175
697
|
if (!configId) {
|
|
176
|
-
const lastUsedRow =
|
|
698
|
+
const lastUsedRow = db.select().from(uiState).where(eq2(uiState.key, "last_used_stream_config_id")).get();
|
|
177
699
|
if (lastUsedRow?.value) {
|
|
178
|
-
const config2 =
|
|
700
|
+
const config2 = db.select().from(streamConfigs).where(eq2(streamConfigs.id, lastUsedRow.value)).get();
|
|
179
701
|
if (config2) configId = config2.id;
|
|
180
702
|
}
|
|
181
703
|
if (!configId) {
|
|
182
|
-
const firstConfig =
|
|
704
|
+
const firstConfig = db.select().from(streamConfigs).get();
|
|
183
705
|
if (!firstConfig) {
|
|
184
|
-
throw new Error("No
|
|
706
|
+
throw new Error("No stream configs available");
|
|
185
707
|
}
|
|
186
708
|
configId = firstConfig.id;
|
|
187
709
|
}
|
|
188
710
|
} else {
|
|
189
|
-
|
|
711
|
+
db.insert(uiState).values({ key: "last_used_stream_config_id", value: configId }).onConflictDoUpdate({ target: uiState.key, set: { value: configId } }).run();
|
|
190
712
|
}
|
|
191
|
-
const id = params.id ??
|
|
713
|
+
const id = params.id ?? randomUUID2();
|
|
192
714
|
const now = /* @__PURE__ */ new Date();
|
|
193
|
-
const config =
|
|
194
|
-
const
|
|
195
|
-
|
|
715
|
+
const config = db.select().from(streamConfigs).where(eq2(streamConfigs.id, configId)).get();
|
|
716
|
+
const streamType = config?.driverType === "pty" ? "pty" : "acp";
|
|
717
|
+
const initialTitle = streamType === "pty" ? params.directory.split("/").pop() || null : null;
|
|
718
|
+
db.insert(streams).values({
|
|
196
719
|
id,
|
|
197
720
|
directory: params.directory,
|
|
198
|
-
|
|
199
|
-
|
|
721
|
+
streamConfigId: configId,
|
|
722
|
+
streamType,
|
|
723
|
+
driverState: null,
|
|
200
724
|
title: initialTitle,
|
|
201
725
|
model: null,
|
|
202
726
|
mode: null,
|
|
203
727
|
createdAt: now
|
|
204
728
|
}).run();
|
|
205
|
-
return
|
|
729
|
+
return db.select().from(streams).where(eq2(streams.id, id)).get();
|
|
206
730
|
},
|
|
207
|
-
async
|
|
208
|
-
await
|
|
209
|
-
|
|
210
|
-
|
|
731
|
+
async deleteStream(id) {
|
|
732
|
+
await muxManager2.stopForStream(id);
|
|
733
|
+
const storageDir = getStreamStorageDir(id);
|
|
734
|
+
try {
|
|
735
|
+
if (fs5.existsSync(storageDir)) {
|
|
736
|
+
fs5.rmSync(storageDir, { recursive: true });
|
|
737
|
+
}
|
|
738
|
+
} catch {
|
|
739
|
+
}
|
|
740
|
+
db.delete(streams).where(eq2(streams.id, id)).run();
|
|
211
741
|
},
|
|
212
|
-
async
|
|
213
|
-
return
|
|
742
|
+
async getStreamConfigs() {
|
|
743
|
+
return db.select().from(streamConfigs).all();
|
|
214
744
|
},
|
|
215
|
-
async
|
|
216
|
-
|
|
745
|
+
async startStream(streamId) {
|
|
746
|
+
const dbStream = db.select().from(streams).where(eq2(streams.id, streamId)).get();
|
|
747
|
+
if (!dbStream) throw new Error(`Stream ${streamId} not found`);
|
|
748
|
+
const dbConfig = db.select().from(streamConfigs).where(eq2(streamConfigs.id, dbStream.streamConfigId)).get();
|
|
749
|
+
if (!dbConfig) throw new Error(`Stream config ${dbStream.streamConfigId} not found`);
|
|
750
|
+
const storageDir = getStreamStorageDir(streamId);
|
|
751
|
+
const emit = (event) => {
|
|
752
|
+
if (isAcpEvent(event) && event.sessionUpdate === "session_info_update") {
|
|
753
|
+
const title = event.title;
|
|
754
|
+
if (title !== void 0) {
|
|
755
|
+
db.update(streams).set({ title }).where(eq2(streams.id, streamId)).run();
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
const handle = await muxManager2.startStream({
|
|
760
|
+
streamId,
|
|
761
|
+
driverType: dbConfig.driverType,
|
|
762
|
+
config: {
|
|
763
|
+
id: dbConfig.id,
|
|
764
|
+
name: dbConfig.name,
|
|
765
|
+
command: dbConfig.command,
|
|
766
|
+
args: dbConfig.args ?? [],
|
|
767
|
+
env: dbConfig.env ?? {}
|
|
768
|
+
},
|
|
769
|
+
cwd: dbStream.directory,
|
|
770
|
+
restoredState: dbStream.driverState,
|
|
771
|
+
emit,
|
|
772
|
+
storageDir
|
|
773
|
+
});
|
|
774
|
+
const driverState = muxManager2.getDriverState(streamId);
|
|
775
|
+
if (driverState !== void 0) {
|
|
776
|
+
db.update(streams).set({ driverState }).where(eq2(streams.id, streamId)).run();
|
|
777
|
+
}
|
|
778
|
+
const replayData = muxManager2.getReplayData(streamId);
|
|
779
|
+
const replayEvents = Array.isArray(replayData) ? replayData : [];
|
|
780
|
+
const scrollback = typeof replayData === "string" ? replayData : muxManager2.getTerminalScrollback(streamId);
|
|
781
|
+
return {
|
|
782
|
+
acpSessionId: handle.stream.acpSessionId,
|
|
783
|
+
replayEvents,
|
|
784
|
+
scrollback,
|
|
785
|
+
models: handle.stream.models,
|
|
786
|
+
modes: handle.stream.modes
|
|
787
|
+
};
|
|
217
788
|
},
|
|
218
|
-
async
|
|
219
|
-
|
|
789
|
+
async stopStream(streamId) {
|
|
790
|
+
const driverState = muxManager2.getDriverState(streamId);
|
|
791
|
+
if (driverState !== void 0) {
|
|
792
|
+
db.update(streams).set({ driverState }).where(eq2(streams.id, streamId)).run();
|
|
793
|
+
}
|
|
794
|
+
await muxManager2.stopForStream(streamId);
|
|
220
795
|
},
|
|
221
|
-
async
|
|
222
|
-
|
|
796
|
+
async input(streamId, message) {
|
|
797
|
+
const stream = db.select().from(streams).where(eq2(streams.id, streamId)).get();
|
|
798
|
+
if (!stream) throw new Error(`Stream ${streamId} not found`);
|
|
799
|
+
if (!stream.title) {
|
|
800
|
+
const title = generateTitleFromMessage(message);
|
|
801
|
+
if (title) {
|
|
802
|
+
db.update(streams).set({ title }).where(eq2(streams.id, streamId)).run();
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
await muxManager2.input(streamId, message);
|
|
223
806
|
},
|
|
224
|
-
async cancel(
|
|
225
|
-
await
|
|
807
|
+
async cancel(streamId) {
|
|
808
|
+
await muxManager2.cancel(streamId);
|
|
226
809
|
},
|
|
227
|
-
async setMode(
|
|
228
|
-
await
|
|
810
|
+
async setMode(streamId, modeId) {
|
|
811
|
+
await muxManager2.setMode(streamId, modeId);
|
|
229
812
|
},
|
|
230
|
-
async setModel(
|
|
231
|
-
await
|
|
813
|
+
async setModel(streamId, modelId) {
|
|
814
|
+
await muxManager2.setModel(streamId, modelId);
|
|
232
815
|
},
|
|
233
|
-
respondPermission(
|
|
234
|
-
|
|
816
|
+
respondPermission(streamId, requestId, optionId) {
|
|
817
|
+
muxManager2.respondPermission(streamId, requestId, optionId);
|
|
235
818
|
},
|
|
236
|
-
|
|
819
|
+
subscribeToStream(streamId, callback) {
|
|
237
820
|
const handler = (event) => {
|
|
238
|
-
if (event.
|
|
239
|
-
callback(event
|
|
821
|
+
if (event.streamId === streamId) {
|
|
822
|
+
callback(event);
|
|
240
823
|
}
|
|
241
824
|
};
|
|
242
|
-
|
|
243
|
-
return () =>
|
|
825
|
+
muxManager2.on("update", handler);
|
|
826
|
+
return () => muxManager2.off("update", handler);
|
|
244
827
|
},
|
|
245
|
-
async listFilesForAutocomplete(
|
|
246
|
-
const
|
|
247
|
-
if (!
|
|
248
|
-
return listFilesForAutocomplete(
|
|
828
|
+
async listFilesForAutocomplete(streamId, partialPath, limit) {
|
|
829
|
+
const stream = db.select().from(streams).where(eq2(streams.id, streamId)).get();
|
|
830
|
+
if (!stream) throw new Error(`Stream ${streamId} not found`);
|
|
831
|
+
return listFilesForAutocomplete(stream.directory, partialPath, limit);
|
|
249
832
|
},
|
|
250
833
|
// Terminal operations
|
|
251
|
-
terminalWrite(
|
|
252
|
-
|
|
834
|
+
terminalWrite(streamId, data) {
|
|
835
|
+
muxManager2.terminalWrite(streamId, data);
|
|
253
836
|
},
|
|
254
|
-
terminalResize(
|
|
255
|
-
|
|
837
|
+
terminalResize(streamId, cols, rows) {
|
|
838
|
+
muxManager2.terminalResize(streamId, cols, rows);
|
|
256
839
|
},
|
|
257
|
-
getTerminalScrollback(
|
|
258
|
-
return
|
|
840
|
+
getTerminalScrollback(streamId) {
|
|
841
|
+
return muxManager2.getTerminalScrollback(streamId);
|
|
259
842
|
},
|
|
260
|
-
|
|
261
|
-
return
|
|
843
|
+
isTerminalStream(streamId) {
|
|
844
|
+
return muxManager2.isTerminalStream(streamId);
|
|
262
845
|
},
|
|
263
|
-
|
|
264
|
-
|
|
846
|
+
updateStreamTitle(streamId, title) {
|
|
847
|
+
db.update(streams).set({ title }).where(eq2(streams.id, streamId)).run();
|
|
265
848
|
}
|
|
266
849
|
};
|
|
850
|
+
return bridge;
|
|
267
851
|
}
|
|
268
852
|
|
|
269
853
|
// src/trpc/trpc.ts
|
|
@@ -274,8 +858,8 @@ var publicProcedure = t.procedure;
|
|
|
274
858
|
|
|
275
859
|
// src/trpc/windows.ts
|
|
276
860
|
import { z } from "zod";
|
|
277
|
-
import { randomUUID as
|
|
278
|
-
import { eq as
|
|
861
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
862
|
+
import { eq as eq3 } from "drizzle-orm";
|
|
279
863
|
|
|
280
864
|
// client/lib/layout.ts
|
|
281
865
|
function createLeaf(paneId) {
|
|
@@ -290,13 +874,13 @@ function getAllPaneIds(node) {
|
|
|
290
874
|
}
|
|
291
875
|
return [...getAllPaneIds(node.first), ...getAllPaneIds(node.second)];
|
|
292
876
|
}
|
|
293
|
-
function findPanePath(node, paneId,
|
|
877
|
+
function findPanePath(node, paneId, path11 = []) {
|
|
294
878
|
if (node.type === "leaf") {
|
|
295
|
-
return node.paneId === paneId ?
|
|
879
|
+
return node.paneId === paneId ? path11 : null;
|
|
296
880
|
}
|
|
297
|
-
const firstPath = findPanePath(node.first, paneId, [...
|
|
881
|
+
const firstPath = findPanePath(node.first, paneId, [...path11, "first"]);
|
|
298
882
|
if (firstPath) return firstPath;
|
|
299
|
-
return findPanePath(node.second, paneId, [...
|
|
883
|
+
return findPanePath(node.second, paneId, [...path11, "second"]);
|
|
300
884
|
}
|
|
301
885
|
function replacePaneWithNode(root, paneId, newNode) {
|
|
302
886
|
if (root.type === "leaf") {
|
|
@@ -333,15 +917,15 @@ function removePane(root, paneId) {
|
|
|
333
917
|
}
|
|
334
918
|
return root;
|
|
335
919
|
}
|
|
336
|
-
function updateRatio(root,
|
|
337
|
-
if (
|
|
920
|
+
function updateRatio(root, path11, newRatio) {
|
|
921
|
+
if (path11.length === 0) {
|
|
338
922
|
if (root.type === "branch") {
|
|
339
923
|
return { ...root, ratio: Math.max(10, Math.min(90, newRatio)) };
|
|
340
924
|
}
|
|
341
925
|
return root;
|
|
342
926
|
}
|
|
343
927
|
if (root.type === "leaf") return root;
|
|
344
|
-
const [next, ...rest] =
|
|
928
|
+
const [next, ...rest] = path11;
|
|
345
929
|
if (next === "first") {
|
|
346
930
|
return { ...root, first: updateRatio(root.first, rest, newRatio) };
|
|
347
931
|
} else {
|
|
@@ -349,11 +933,11 @@ function updateRatio(root, path3, newRatio) {
|
|
|
349
933
|
}
|
|
350
934
|
}
|
|
351
935
|
function getAdjacentPane(root, currentPaneId, direction) {
|
|
352
|
-
const
|
|
353
|
-
if (!
|
|
936
|
+
const path11 = findPanePath(root, currentPaneId);
|
|
937
|
+
if (!path11) return null;
|
|
354
938
|
let node = root;
|
|
355
939
|
const pathNodes = [];
|
|
356
|
-
for (const step of
|
|
940
|
+
for (const step of path11) {
|
|
357
941
|
if (node.type === "branch") {
|
|
358
942
|
pathNodes.push({ node, childSide: step });
|
|
359
943
|
node = step === "first" ? node.first : node.second;
|
|
@@ -399,71 +983,76 @@ function generateTitle(existingCount, directory) {
|
|
|
399
983
|
}
|
|
400
984
|
var windowsRouter = router({
|
|
401
985
|
/**
|
|
402
|
-
* List all windows with their panes and
|
|
986
|
+
* List all windows with their panes and streams.
|
|
403
987
|
*/
|
|
404
988
|
list: publicProcedure.query(async () => {
|
|
405
989
|
const allWindows = db.select().from(windows).orderBy(windows.createdAt).all();
|
|
406
990
|
const allPanes = db.select().from(panes).all();
|
|
407
|
-
const activeRow = db.select().from(uiState).where(
|
|
408
|
-
const
|
|
409
|
-
allPanes.map((p) => p.
|
|
991
|
+
const activeRow = db.select().from(uiState).where(eq3(uiState.key, "active_window_id")).get();
|
|
992
|
+
const streamIds = [...new Set(
|
|
993
|
+
allPanes.map((p) => p.eventStream?.streamId ?? null).filter((id) => id !== null)
|
|
410
994
|
)];
|
|
411
|
-
const
|
|
412
|
-
const
|
|
413
|
-
const
|
|
995
|
+
const streamsData = await amux.getStreams(streamIds);
|
|
996
|
+
const streamMap = new Map(streamsData.map((s) => [s.id, s]));
|
|
997
|
+
const streamConfigs2 = await amux.getStreamConfigs();
|
|
414
998
|
return {
|
|
415
999
|
windows: allWindows.map((w) => ({
|
|
416
1000
|
...w,
|
|
417
1001
|
panes: allPanes.filter((p) => p.windowId === w.id).map((p) => ({
|
|
418
1002
|
...p,
|
|
419
|
-
// Enrich
|
|
420
|
-
|
|
1003
|
+
// Enrich eventStream with full stream data
|
|
1004
|
+
stream: p.eventStream ? streamMap.get(p.eventStream.streamId) ?? null : null
|
|
421
1005
|
}))
|
|
422
1006
|
})),
|
|
423
1007
|
activeWindowId: activeRow?.value ?? allWindows[allWindows.length - 1]?.id ?? null,
|
|
424
|
-
|
|
1008
|
+
streamConfigs: streamConfigs2,
|
|
1009
|
+
// Legacy alias
|
|
1010
|
+
agentConfigs: streamConfigs2
|
|
425
1011
|
};
|
|
426
1012
|
}),
|
|
427
1013
|
/**
|
|
428
1014
|
* Get a single window by ID.
|
|
429
1015
|
*/
|
|
430
1016
|
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => {
|
|
431
|
-
const window = db.select().from(windows).where(
|
|
1017
|
+
const window = db.select().from(windows).where(eq3(windows.id, input.id)).get();
|
|
432
1018
|
if (!window) {
|
|
433
1019
|
throw new Error(`Window ${input.id} not found`);
|
|
434
1020
|
}
|
|
435
|
-
const windowPanes = db.select().from(panes).where(
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
const
|
|
1021
|
+
const windowPanes = db.select().from(panes).where(eq3(panes.windowId, input.id)).all();
|
|
1022
|
+
const streamIds = windowPanes.map((p) => p.eventStream?.streamId ?? null).filter((id) => id !== null);
|
|
1023
|
+
const streamsData = await amux.getStreams(streamIds);
|
|
1024
|
+
const streamMap = new Map(streamsData.map((s) => [s.id, s]));
|
|
439
1025
|
return {
|
|
440
1026
|
...window,
|
|
441
1027
|
panes: windowPanes.map((p) => ({
|
|
442
1028
|
...p,
|
|
443
|
-
|
|
1029
|
+
stream: p.eventStream ? streamMap.get(p.eventStream.streamId) ?? null : null
|
|
444
1030
|
}))
|
|
445
1031
|
};
|
|
446
1032
|
}),
|
|
447
1033
|
/**
|
|
448
1034
|
* Create a new window with a single pane.
|
|
449
|
-
* If
|
|
1035
|
+
* If streamConfigId is not provided, pane is created with no content (shows picker).
|
|
450
1036
|
*/
|
|
451
1037
|
create: publicProcedure.input(z.object({
|
|
452
1038
|
id: z.string().optional(),
|
|
453
1039
|
title: z.string().optional(),
|
|
454
1040
|
directory: z.string().optional(),
|
|
1041
|
+
streamConfigId: z.string().optional(),
|
|
1042
|
+
// Legacy alias
|
|
455
1043
|
agentConfigId: z.string().optional()
|
|
456
1044
|
})).mutation(async ({ input }) => {
|
|
457
1045
|
const directory = input.directory ?? process.cwd();
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
1046
|
+
const configId = input.streamConfigId ?? input.agentConfigId;
|
|
1047
|
+
let stream = null;
|
|
1048
|
+
if (configId) {
|
|
1049
|
+
stream = await amux.createStream({
|
|
461
1050
|
directory,
|
|
462
|
-
|
|
1051
|
+
streamConfigId: configId
|
|
463
1052
|
});
|
|
464
1053
|
}
|
|
465
|
-
const windowId = input.id ??
|
|
466
|
-
const paneId =
|
|
1054
|
+
const windowId = input.id ?? randomUUID3();
|
|
1055
|
+
const paneId = randomUUID3();
|
|
467
1056
|
const existingCount = db.select().from(windows).all().length;
|
|
468
1057
|
const layout = createLeaf(paneId);
|
|
469
1058
|
db.insert(windows).values({
|
|
@@ -476,16 +1065,17 @@ var windowsRouter = router({
|
|
|
476
1065
|
db.insert(panes).values({
|
|
477
1066
|
id: paneId,
|
|
478
1067
|
windowId,
|
|
479
|
-
content:
|
|
1068
|
+
content: null,
|
|
1069
|
+
eventStream: stream ? { type: stream.streamType, streamId: stream.id } : null
|
|
480
1070
|
}).run();
|
|
481
1071
|
db.insert(uiState).values({ key: "active_window_id", value: windowId }).onConflictDoUpdate({ target: uiState.key, set: { value: windowId } }).run();
|
|
482
|
-
const newWindow = db.select().from(windows).where(
|
|
483
|
-
const newPanes = db.select().from(panes).where(
|
|
1072
|
+
const newWindow = db.select().from(windows).where(eq3(windows.id, windowId)).get();
|
|
1073
|
+
const newPanes = db.select().from(panes).where(eq3(panes.windowId, windowId)).all();
|
|
484
1074
|
return {
|
|
485
1075
|
...newWindow,
|
|
486
1076
|
panes: newPanes.map((p) => ({
|
|
487
1077
|
...p,
|
|
488
|
-
|
|
1078
|
+
stream: stream && p.eventStream?.streamId === stream.id ? stream : null
|
|
489
1079
|
}))
|
|
490
1080
|
};
|
|
491
1081
|
}),
|
|
@@ -502,28 +1092,28 @@ var windowsRouter = router({
|
|
|
502
1092
|
updates.hasCustomTitle = true;
|
|
503
1093
|
}
|
|
504
1094
|
updates.lastAccessedAt = /* @__PURE__ */ new Date();
|
|
505
|
-
db.update(windows).set(updates).where(
|
|
506
|
-
return db.select().from(windows).where(
|
|
1095
|
+
db.update(windows).set(updates).where(eq3(windows.id, input.id)).run();
|
|
1096
|
+
return db.select().from(windows).where(eq3(windows.id, input.id)).get();
|
|
507
1097
|
}),
|
|
508
1098
|
/**
|
|
509
|
-
* Delete a window and all its panes/
|
|
1099
|
+
* Delete a window and all its panes/streams.
|
|
510
1100
|
*/
|
|
511
1101
|
delete: publicProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => {
|
|
512
|
-
const windowPanes = db.select().from(panes).where(
|
|
1102
|
+
const windowPanes = db.select().from(panes).where(eq3(panes.windowId, input.id)).all();
|
|
513
1103
|
for (const pane of windowPanes) {
|
|
514
|
-
if (pane.
|
|
515
|
-
await amux.
|
|
1104
|
+
if (pane.eventStream) {
|
|
1105
|
+
await amux.deleteStream(pane.eventStream.streamId);
|
|
516
1106
|
}
|
|
517
1107
|
}
|
|
518
|
-
db.delete(windows).where(
|
|
519
|
-
const activeRow = db.select().from(uiState).where(
|
|
1108
|
+
db.delete(windows).where(eq3(windows.id, input.id)).run();
|
|
1109
|
+
const activeRow = db.select().from(uiState).where(eq3(uiState.key, "active_window_id")).get();
|
|
520
1110
|
if (activeRow?.value === input.id) {
|
|
521
1111
|
const remaining = db.select().from(windows).orderBy(windows.createdAt).all();
|
|
522
1112
|
const newActiveId = remaining[remaining.length - 1]?.id ?? null;
|
|
523
1113
|
if (newActiveId) {
|
|
524
|
-
db.update(uiState).set({ value: newActiveId }).where(
|
|
1114
|
+
db.update(uiState).set({ value: newActiveId }).where(eq3(uiState.key, "active_window_id")).run();
|
|
525
1115
|
} else {
|
|
526
|
-
db.delete(uiState).where(
|
|
1116
|
+
db.delete(uiState).where(eq3(uiState.key, "active_window_id")).run();
|
|
527
1117
|
}
|
|
528
1118
|
}
|
|
529
1119
|
return { ok: true };
|
|
@@ -533,15 +1123,26 @@ var windowsRouter = router({
|
|
|
533
1123
|
*/
|
|
534
1124
|
setActive: publicProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => {
|
|
535
1125
|
db.insert(uiState).values({ key: "active_window_id", value: input.id }).onConflictDoUpdate({ target: uiState.key, set: { value: input.id } }).run();
|
|
536
|
-
db.update(windows).set({ lastAccessedAt: /* @__PURE__ */ new Date() }).where(
|
|
1126
|
+
db.update(windows).set({ lastAccessedAt: /* @__PURE__ */ new Date() }).where(eq3(windows.id, input.id)).run();
|
|
537
1127
|
return { ok: true };
|
|
538
1128
|
})
|
|
539
1129
|
});
|
|
540
1130
|
|
|
541
1131
|
// src/trpc/layout.ts
|
|
542
1132
|
import { z as z2 } from "zod";
|
|
543
|
-
import { randomUUID as
|
|
544
|
-
import
|
|
1133
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
1134
|
+
import * as path6 from "path";
|
|
1135
|
+
import * as os3 from "os";
|
|
1136
|
+
import { eq as eq4 } from "drizzle-orm";
|
|
1137
|
+
function expandTilde(p) {
|
|
1138
|
+
if (p.startsWith("~/")) {
|
|
1139
|
+
return path6.join(os3.homedir(), p.slice(2));
|
|
1140
|
+
}
|
|
1141
|
+
if (p === "~") {
|
|
1142
|
+
return os3.homedir();
|
|
1143
|
+
}
|
|
1144
|
+
return p;
|
|
1145
|
+
}
|
|
545
1146
|
var amux2;
|
|
546
1147
|
function setAmuxBridge2(bridge) {
|
|
547
1148
|
amux2 = bridge;
|
|
@@ -559,20 +1160,20 @@ var layoutRouter = router({
|
|
|
559
1160
|
newPaneId: z2.string().optional(),
|
|
560
1161
|
newSessionId: z2.string().optional()
|
|
561
1162
|
})).mutation(async ({ input }) => {
|
|
562
|
-
const sourcePane = db.select().from(panes).where(
|
|
1163
|
+
const sourcePane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
|
|
563
1164
|
if (!sourcePane) {
|
|
564
1165
|
throw new Error(`Pane ${input.paneId} not found`);
|
|
565
1166
|
}
|
|
566
|
-
const window = db.select().from(windows).where(
|
|
1167
|
+
const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
|
|
567
1168
|
if (!window) {
|
|
568
1169
|
throw new Error(`Window ${input.windowId} not found`);
|
|
569
1170
|
}
|
|
570
|
-
const
|
|
571
|
-
const newPaneId = input.newPaneId ?? randomUUID3();
|
|
1171
|
+
const newPaneId = input.newPaneId ?? randomUUID4();
|
|
572
1172
|
db.insert(panes).values({
|
|
573
1173
|
id: newPaneId,
|
|
574
1174
|
windowId: input.windowId,
|
|
575
|
-
content:
|
|
1175
|
+
content: null,
|
|
1176
|
+
eventStream: null
|
|
576
1177
|
}).run();
|
|
577
1178
|
const currentLayout = window.layout;
|
|
578
1179
|
const newLayout = splitPane(currentLayout, input.paneId, newPaneId, input.direction);
|
|
@@ -580,19 +1181,22 @@ var layoutRouter = router({
|
|
|
580
1181
|
layout: newLayout,
|
|
581
1182
|
activePaneId: newPaneId,
|
|
582
1183
|
lastAccessedAt: /* @__PURE__ */ new Date()
|
|
583
|
-
}).where(
|
|
1184
|
+
}).where(eq4(windows.id, input.windowId)).run();
|
|
584
1185
|
const now = /* @__PURE__ */ new Date();
|
|
585
1186
|
return {
|
|
586
1187
|
newPaneId,
|
|
587
|
-
|
|
1188
|
+
newStreamId: null,
|
|
588
1189
|
layout: newLayout,
|
|
589
1190
|
newPane: {
|
|
590
1191
|
id: newPaneId,
|
|
591
1192
|
windowId: input.windowId,
|
|
592
|
-
content:
|
|
1193
|
+
content: null,
|
|
1194
|
+
eventStream: null,
|
|
593
1195
|
createdAt: now.toISOString(),
|
|
594
|
-
|
|
595
|
-
}
|
|
1196
|
+
stream: null
|
|
1197
|
+
},
|
|
1198
|
+
// Legacy aliases
|
|
1199
|
+
newSessionId: null
|
|
596
1200
|
};
|
|
597
1201
|
}),
|
|
598
1202
|
/**
|
|
@@ -602,27 +1206,27 @@ var layoutRouter = router({
|
|
|
602
1206
|
windowId: z2.string(),
|
|
603
1207
|
paneId: z2.string()
|
|
604
1208
|
})).mutation(async ({ input }) => {
|
|
605
|
-
const window = db.select().from(windows).where(
|
|
1209
|
+
const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
|
|
606
1210
|
if (!window) {
|
|
607
1211
|
throw new Error(`Window ${input.windowId} not found`);
|
|
608
1212
|
}
|
|
609
|
-
const pane = db.select().from(panes).where(
|
|
1213
|
+
const pane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
|
|
610
1214
|
if (!pane) {
|
|
611
1215
|
throw new Error(`Pane ${input.paneId} not found`);
|
|
612
1216
|
}
|
|
613
1217
|
const currentLayout = window.layout;
|
|
614
1218
|
const allPaneIds = getAllPaneIds(currentLayout);
|
|
615
1219
|
if (allPaneIds.length === 1) {
|
|
616
|
-
if (pane.
|
|
617
|
-
await amux2.
|
|
1220
|
+
if (pane.eventStream) {
|
|
1221
|
+
await amux2.deleteStream(pane.eventStream.streamId);
|
|
618
1222
|
}
|
|
619
|
-
db.delete(windows).where(
|
|
1223
|
+
db.delete(windows).where(eq4(windows.id, input.windowId)).run();
|
|
620
1224
|
return { windowClosed: true, layout: null, activePaneId: null };
|
|
621
1225
|
}
|
|
622
|
-
if (pane.
|
|
623
|
-
await amux2.
|
|
1226
|
+
if (pane.eventStream) {
|
|
1227
|
+
await amux2.deleteStream(pane.eventStream.streamId);
|
|
624
1228
|
}
|
|
625
|
-
db.delete(panes).where(
|
|
1229
|
+
db.delete(panes).where(eq4(panes.id, input.paneId)).run();
|
|
626
1230
|
const newLayout = removePane(currentLayout, input.paneId);
|
|
627
1231
|
if (!newLayout) {
|
|
628
1232
|
throw new Error("Failed to remove pane from layout");
|
|
@@ -632,7 +1236,7 @@ var layoutRouter = router({
|
|
|
632
1236
|
layout: newLayout,
|
|
633
1237
|
activePaneId: newActivePaneId,
|
|
634
1238
|
lastAccessedAt: /* @__PURE__ */ new Date()
|
|
635
|
-
}).where(
|
|
1239
|
+
}).where(eq4(windows.id, input.windowId)).run();
|
|
636
1240
|
return {
|
|
637
1241
|
windowClosed: false,
|
|
638
1242
|
layout: newLayout,
|
|
@@ -649,7 +1253,7 @@ var layoutRouter = router({
|
|
|
649
1253
|
db.update(windows).set({
|
|
650
1254
|
activePaneId: input.paneId,
|
|
651
1255
|
lastAccessedAt: /* @__PURE__ */ new Date()
|
|
652
|
-
}).where(
|
|
1256
|
+
}).where(eq4(windows.id, input.windowId)).run();
|
|
653
1257
|
return { ok: true };
|
|
654
1258
|
}),
|
|
655
1259
|
/**
|
|
@@ -660,7 +1264,7 @@ var layoutRouter = router({
|
|
|
660
1264
|
currentPaneId: z2.string(),
|
|
661
1265
|
direction: z2.enum(["left", "right", "up", "down"])
|
|
662
1266
|
})).mutation(async ({ input }) => {
|
|
663
|
-
const window = db.select().from(windows).where(
|
|
1267
|
+
const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
|
|
664
1268
|
if (!window) {
|
|
665
1269
|
throw new Error(`Window ${input.windowId} not found`);
|
|
666
1270
|
}
|
|
@@ -670,7 +1274,7 @@ var layoutRouter = router({
|
|
|
670
1274
|
db.update(windows).set({
|
|
671
1275
|
activePaneId: adjacentPaneId,
|
|
672
1276
|
lastAccessedAt: /* @__PURE__ */ new Date()
|
|
673
|
-
}).where(
|
|
1277
|
+
}).where(eq4(windows.id, input.windowId)).run();
|
|
674
1278
|
return { newActivePaneId: adjacentPaneId };
|
|
675
1279
|
}
|
|
676
1280
|
return { newActivePaneId: null };
|
|
@@ -683,93 +1287,110 @@ var layoutRouter = router({
|
|
|
683
1287
|
path: z2.array(z2.enum(["first", "second"])),
|
|
684
1288
|
ratio: z2.number().min(10).max(90)
|
|
685
1289
|
})).mutation(async ({ input }) => {
|
|
686
|
-
const window = db.select().from(windows).where(
|
|
1290
|
+
const window = db.select().from(windows).where(eq4(windows.id, input.windowId)).get();
|
|
687
1291
|
if (!window) {
|
|
688
1292
|
throw new Error(`Window ${input.windowId} not found`);
|
|
689
1293
|
}
|
|
690
1294
|
const currentLayout = window.layout;
|
|
691
1295
|
const newLayout = updateRatio(currentLayout, input.path, input.ratio);
|
|
692
|
-
db.update(windows).set({ layout: newLayout }).where(
|
|
1296
|
+
db.update(windows).set({ layout: newLayout }).where(eq4(windows.id, input.windowId)).run();
|
|
693
1297
|
return { layout: newLayout };
|
|
694
1298
|
}),
|
|
695
1299
|
/**
|
|
696
|
-
* Set
|
|
697
|
-
*
|
|
1300
|
+
* Set static content for a pane (browser URL, etc.).
|
|
1301
|
+
* Upsert behavior - works for both initial set and updates.
|
|
698
1302
|
*/
|
|
699
|
-
|
|
1303
|
+
setPaneContent: publicProcedure.input(z2.object({
|
|
700
1304
|
paneId: z2.string(),
|
|
701
|
-
|
|
702
|
-
|
|
1305
|
+
content: z2.record(z2.unknown()).nullable(),
|
|
1306
|
+
rendererId: z2.string().optional()
|
|
703
1307
|
})).mutation(async ({ input }) => {
|
|
704
|
-
const pane = db.select().from(panes).where(
|
|
1308
|
+
const pane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
|
|
705
1309
|
if (!pane) {
|
|
706
1310
|
throw new Error(`Pane ${input.paneId} not found`);
|
|
707
1311
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
agentConfigId: input.agentConfigId
|
|
714
|
-
});
|
|
715
|
-
db.update(panes).set({ content: { type: "session", sessionId: session.id } }).where(eq3(panes.id, input.paneId)).run();
|
|
716
|
-
await amux2.startAgent(session.id);
|
|
717
|
-
return {
|
|
718
|
-
sessionId: session.id,
|
|
719
|
-
content: { type: "session", sessionId: session.id },
|
|
720
|
-
session: {
|
|
721
|
-
id: session.id,
|
|
722
|
-
directory: session.directory,
|
|
723
|
-
agentConfigId: session.agentConfigId,
|
|
724
|
-
model: session.model,
|
|
725
|
-
mode: session.mode,
|
|
726
|
-
createdAt: session.createdAt.toISOString(),
|
|
727
|
-
acpSessionId: session.acpSessionId
|
|
728
|
-
}
|
|
729
|
-
};
|
|
1312
|
+
db.update(panes).set({
|
|
1313
|
+
content: input.content,
|
|
1314
|
+
rendererId: input.rendererId ?? null
|
|
1315
|
+
}).where(eq4(panes.id, input.paneId)).run();
|
|
1316
|
+
return { content: input.content, rendererId: input.rendererId };
|
|
730
1317
|
}),
|
|
731
1318
|
/**
|
|
732
|
-
* Set
|
|
1319
|
+
* Set an event stream for a pane (agent or shell).
|
|
1320
|
+
* Creates stream in amux and starts the backend.
|
|
733
1321
|
*/
|
|
734
|
-
|
|
1322
|
+
setPaneEventStream: publicProcedure.input(z2.object({
|
|
735
1323
|
paneId: z2.string(),
|
|
736
|
-
|
|
1324
|
+
streamConfigId: z2.string(),
|
|
1325
|
+
directory: z2.string().optional(),
|
|
1326
|
+
rendererId: z2.string().optional(),
|
|
1327
|
+
// Legacy alias
|
|
1328
|
+
agentConfigId: z2.string().optional()
|
|
737
1329
|
})).mutation(async ({ input }) => {
|
|
738
|
-
const pane = db.select().from(panes).where(
|
|
1330
|
+
const pane = db.select().from(panes).where(eq4(panes.id, input.paneId)).get();
|
|
739
1331
|
if (!pane) {
|
|
740
1332
|
throw new Error(`Pane ${input.paneId} not found`);
|
|
741
1333
|
}
|
|
742
|
-
if (pane.
|
|
743
|
-
throw new Error(`Pane ${input.paneId} already has
|
|
1334
|
+
if (pane.eventStream) {
|
|
1335
|
+
throw new Error(`Pane ${input.paneId} already has an event stream`);
|
|
744
1336
|
}
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1337
|
+
const configId = input.streamConfigId ?? input.agentConfigId;
|
|
1338
|
+
const directory = input.directory ? expandTilde(input.directory) : process.cwd();
|
|
1339
|
+
const stream = await amux2.createStream({
|
|
1340
|
+
directory,
|
|
1341
|
+
streamConfigId: configId
|
|
1342
|
+
});
|
|
1343
|
+
const eventStream = { type: stream.streamType, streamId: stream.id };
|
|
1344
|
+
db.update(panes).set({
|
|
1345
|
+
eventStream,
|
|
1346
|
+
rendererId: input.rendererId ?? null
|
|
1347
|
+
}).where(eq4(panes.id, input.paneId)).run();
|
|
1348
|
+
await amux2.startStream(stream.id);
|
|
1349
|
+
return {
|
|
1350
|
+
streamId: stream.id,
|
|
1351
|
+
eventStream,
|
|
1352
|
+
stream: {
|
|
1353
|
+
id: stream.id,
|
|
1354
|
+
directory: stream.directory,
|
|
1355
|
+
streamConfigId: stream.streamConfigId,
|
|
1356
|
+
streamType: stream.streamType,
|
|
1357
|
+
model: stream.model,
|
|
1358
|
+
mode: stream.mode,
|
|
1359
|
+
createdAt: stream.createdAt.toISOString()
|
|
1360
|
+
},
|
|
1361
|
+
// Legacy aliases
|
|
1362
|
+
sessionId: stream.id,
|
|
1363
|
+
process: eventStream,
|
|
1364
|
+
session: {
|
|
1365
|
+
id: stream.id,
|
|
1366
|
+
directory: stream.directory,
|
|
1367
|
+
agentConfigId: stream.streamConfigId,
|
|
1368
|
+
model: stream.model,
|
|
1369
|
+
mode: stream.mode,
|
|
1370
|
+
createdAt: stream.createdAt.toISOString()
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
766
1373
|
})
|
|
767
1374
|
});
|
|
768
1375
|
|
|
769
1376
|
// src/trpc/agents.ts
|
|
770
1377
|
import { z as z3 } from "zod";
|
|
771
|
-
import { eq as eq4 } from "drizzle-orm";
|
|
772
1378
|
import { observable } from "@trpc/server/observable";
|
|
1379
|
+
|
|
1380
|
+
// src/lib/panes.ts
|
|
1381
|
+
import { eq as eq5 } from "drizzle-orm";
|
|
1382
|
+
function getStreamIdForPane(paneId) {
|
|
1383
|
+
const pane = db.select().from(panes).where(eq5(panes.id, paneId)).get();
|
|
1384
|
+
if (!pane) {
|
|
1385
|
+
throw new Error(`Pane ${paneId} not found`);
|
|
1386
|
+
}
|
|
1387
|
+
if (!pane.eventStream) {
|
|
1388
|
+
throw new Error(`Pane ${paneId} does not have an event stream`);
|
|
1389
|
+
}
|
|
1390
|
+
return pane.eventStream.streamId;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// src/trpc/agents.ts
|
|
773
1394
|
var amux3;
|
|
774
1395
|
function setAmuxBridge3(bridge) {
|
|
775
1396
|
amux3 = bridge;
|
|
@@ -777,71 +1398,61 @@ function setAmuxBridge3(bridge) {
|
|
|
777
1398
|
var paneInput = z3.object({
|
|
778
1399
|
paneId: z3.string()
|
|
779
1400
|
});
|
|
780
|
-
|
|
781
|
-
const pane = db.select().from(panes).where(eq4(panes.id, paneId)).get();
|
|
782
|
-
if (!pane) {
|
|
783
|
-
throw new Error(`Pane ${paneId} not found`);
|
|
784
|
-
}
|
|
785
|
-
if (pane.content?.type !== "session") {
|
|
786
|
-
throw new Error(`Pane ${paneId} is not a session pane`);
|
|
787
|
-
}
|
|
788
|
-
return pane.content.sessionId;
|
|
789
|
-
}
|
|
790
|
-
var agentsRouter = router({
|
|
1401
|
+
var streamsRouter = router({
|
|
791
1402
|
/**
|
|
792
|
-
* Start
|
|
1403
|
+
* Start stream for a pane.
|
|
793
1404
|
*/
|
|
794
1405
|
start: publicProcedure.input(paneInput).mutation(async ({ input }) => {
|
|
795
|
-
const
|
|
796
|
-
return amux3.
|
|
1406
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1407
|
+
return amux3.startStream(streamId);
|
|
797
1408
|
}),
|
|
798
1409
|
/**
|
|
799
|
-
* Stop
|
|
1410
|
+
* Stop stream for a pane.
|
|
800
1411
|
*/
|
|
801
1412
|
stop: publicProcedure.input(paneInput).mutation(async ({ input }) => {
|
|
802
|
-
const
|
|
803
|
-
await amux3.
|
|
1413
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1414
|
+
await amux3.stopStream(streamId);
|
|
804
1415
|
return { ok: true };
|
|
805
1416
|
}),
|
|
806
1417
|
/**
|
|
807
|
-
* Send
|
|
1418
|
+
* Send input to a pane's stream.
|
|
808
1419
|
*/
|
|
809
|
-
|
|
1420
|
+
input: publicProcedure.input(z3.object({
|
|
810
1421
|
paneId: z3.string(),
|
|
811
1422
|
message: z3.string()
|
|
812
1423
|
})).mutation(async ({ input }) => {
|
|
813
|
-
const
|
|
814
|
-
await amux3.
|
|
1424
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1425
|
+
await amux3.input(streamId, input.message);
|
|
815
1426
|
return { ok: true };
|
|
816
1427
|
}),
|
|
817
1428
|
/**
|
|
818
1429
|
* Cancel the current prompt.
|
|
819
1430
|
*/
|
|
820
1431
|
cancel: publicProcedure.input(paneInput).mutation(async ({ input }) => {
|
|
821
|
-
const
|
|
822
|
-
await amux3.cancel(
|
|
1432
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1433
|
+
await amux3.cancel(streamId);
|
|
823
1434
|
return { ok: true };
|
|
824
1435
|
}),
|
|
825
1436
|
/**
|
|
826
|
-
* Set
|
|
1437
|
+
* Set stream mode.
|
|
827
1438
|
*/
|
|
828
1439
|
setMode: publicProcedure.input(z3.object({
|
|
829
1440
|
paneId: z3.string(),
|
|
830
1441
|
modeId: z3.string()
|
|
831
1442
|
})).mutation(async ({ input }) => {
|
|
832
|
-
const
|
|
833
|
-
await amux3.setMode(
|
|
1443
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1444
|
+
await amux3.setMode(streamId, input.modeId);
|
|
834
1445
|
return { ok: true };
|
|
835
1446
|
}),
|
|
836
1447
|
/**
|
|
837
|
-
* Set
|
|
1448
|
+
* Set stream model.
|
|
838
1449
|
*/
|
|
839
1450
|
setModel: publicProcedure.input(z3.object({
|
|
840
1451
|
paneId: z3.string(),
|
|
841
1452
|
modelId: z3.string()
|
|
842
1453
|
})).mutation(async ({ input }) => {
|
|
843
|
-
const
|
|
844
|
-
await amux3.setModel(
|
|
1454
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1455
|
+
await amux3.setModel(streamId, input.modelId);
|
|
845
1456
|
return { ok: true };
|
|
846
1457
|
}),
|
|
847
1458
|
/**
|
|
@@ -852,17 +1463,17 @@ var agentsRouter = router({
|
|
|
852
1463
|
requestId: z3.string(),
|
|
853
1464
|
optionId: z3.string()
|
|
854
1465
|
})).mutation(async ({ input }) => {
|
|
855
|
-
const
|
|
856
|
-
amux3.respondPermission(
|
|
1466
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1467
|
+
amux3.respondPermission(streamId, input.requestId, input.optionId);
|
|
857
1468
|
return { ok: true };
|
|
858
1469
|
}),
|
|
859
1470
|
/**
|
|
860
|
-
* Subscribe to updates for a pane's
|
|
1471
|
+
* Subscribe to updates for a pane's stream.
|
|
861
1472
|
*/
|
|
862
1473
|
subscribe: publicProcedure.input(paneInput).subscription(({ input }) => {
|
|
863
|
-
const
|
|
1474
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
864
1475
|
return observable((emit) => {
|
|
865
|
-
return amux3.
|
|
1476
|
+
return amux3.subscribeToStream(streamId, (update) => {
|
|
866
1477
|
emit.next(update);
|
|
867
1478
|
});
|
|
868
1479
|
});
|
|
@@ -871,22 +1482,42 @@ var agentsRouter = router({
|
|
|
871
1482
|
|
|
872
1483
|
// src/trpc/files.ts
|
|
873
1484
|
import { z as z4 } from "zod";
|
|
874
|
-
import
|
|
1485
|
+
import * as fs6 from "fs/promises";
|
|
1486
|
+
import * as path7 from "path";
|
|
1487
|
+
import * as os4 from "os";
|
|
1488
|
+
function expandTilde2(p) {
|
|
1489
|
+
if (p.startsWith("~/")) {
|
|
1490
|
+
return path7.join(os4.homedir(), p.slice(2));
|
|
1491
|
+
}
|
|
1492
|
+
if (p === "~") {
|
|
1493
|
+
return os4.homedir();
|
|
1494
|
+
}
|
|
1495
|
+
return p;
|
|
1496
|
+
}
|
|
875
1497
|
var amux4;
|
|
876
1498
|
function setAmuxBridge4(bridge) {
|
|
877
1499
|
amux4 = bridge;
|
|
878
1500
|
}
|
|
879
|
-
function getSessionIdForPane2(paneId) {
|
|
880
|
-
const pane = db.select().from(panes).where(eq5(panes.id, paneId)).get();
|
|
881
|
-
if (!pane) {
|
|
882
|
-
throw new Error(`Pane ${paneId} not found`);
|
|
883
|
-
}
|
|
884
|
-
if (pane.content?.type !== "session") {
|
|
885
|
-
throw new Error(`Pane ${paneId} is not a session pane`);
|
|
886
|
-
}
|
|
887
|
-
return pane.content.sessionId;
|
|
888
|
-
}
|
|
889
1501
|
var filesRouter = router({
|
|
1502
|
+
/**
|
|
1503
|
+
* Get server's current working directory.
|
|
1504
|
+
*/
|
|
1505
|
+
getCwd: publicProcedure.query(() => process.cwd()),
|
|
1506
|
+
/**
|
|
1507
|
+
* List directories in a given path.
|
|
1508
|
+
* Used by DirectoryPicker for browsing.
|
|
1509
|
+
*/
|
|
1510
|
+
listDirectory: publicProcedure.input(z4.object({
|
|
1511
|
+
directory: z4.string()
|
|
1512
|
+
})).query(async ({ input }) => {
|
|
1513
|
+
try {
|
|
1514
|
+
const absPath = path7.resolve(expandTilde2(input.directory));
|
|
1515
|
+
const entries = await fs6.readdir(absPath, { withFileTypes: true });
|
|
1516
|
+
return entries.filter((e) => e.isDirectory()).map((e) => ({ name: e.name, isDirectory: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1517
|
+
} catch {
|
|
1518
|
+
return [];
|
|
1519
|
+
}
|
|
1520
|
+
}),
|
|
890
1521
|
/**
|
|
891
1522
|
* List files/directories matching a partial path for autocomplete.
|
|
892
1523
|
* Returns files in the session's working directory.
|
|
@@ -896,8 +1527,8 @@ var filesRouter = router({
|
|
|
896
1527
|
partialPath: z4.string(),
|
|
897
1528
|
limit: z4.number().default(20)
|
|
898
1529
|
})).query(async ({ input }) => {
|
|
899
|
-
const
|
|
900
|
-
return amux4.listFilesForAutocomplete(
|
|
1530
|
+
const streamId = getStreamIdForPane(input.paneId);
|
|
1531
|
+
return amux4.listFilesForAutocomplete(streamId, input.partialPath, input.limit);
|
|
901
1532
|
})
|
|
902
1533
|
});
|
|
903
1534
|
|
|
@@ -911,15 +1542,15 @@ function setAmuxBridge5(bridge) {
|
|
|
911
1542
|
var paneInput2 = z5.object({
|
|
912
1543
|
paneId: z5.string()
|
|
913
1544
|
});
|
|
914
|
-
function
|
|
1545
|
+
function getStreamIdForPane2(paneId) {
|
|
915
1546
|
const pane = db.select().from(panes).where(eq6(panes.id, paneId)).get();
|
|
916
1547
|
if (!pane) {
|
|
917
1548
|
throw new Error(`Pane ${paneId} not found`);
|
|
918
1549
|
}
|
|
919
|
-
if (pane.
|
|
920
|
-
throw new Error(`Pane ${paneId}
|
|
1550
|
+
if (!pane.eventStream) {
|
|
1551
|
+
throw new Error(`Pane ${paneId} does not have an event stream`);
|
|
921
1552
|
}
|
|
922
|
-
return pane.
|
|
1553
|
+
return pane.eventStream.streamId;
|
|
923
1554
|
}
|
|
924
1555
|
var terminalsRouter = router({
|
|
925
1556
|
/**
|
|
@@ -931,8 +1562,8 @@ var terminalsRouter = router({
|
|
|
931
1562
|
data: z5.string()
|
|
932
1563
|
})
|
|
933
1564
|
).mutation(({ input }) => {
|
|
934
|
-
const
|
|
935
|
-
amux5.terminalWrite(
|
|
1565
|
+
const streamId = getStreamIdForPane2(input.paneId);
|
|
1566
|
+
amux5.terminalWrite(streamId, input.data);
|
|
936
1567
|
return { ok: true };
|
|
937
1568
|
}),
|
|
938
1569
|
/**
|
|
@@ -945,16 +1576,16 @@ var terminalsRouter = router({
|
|
|
945
1576
|
rows: z5.number().int().positive()
|
|
946
1577
|
})
|
|
947
1578
|
).mutation(({ input }) => {
|
|
948
|
-
const
|
|
949
|
-
amux5.terminalResize(
|
|
1579
|
+
const streamId = getStreamIdForPane2(input.paneId);
|
|
1580
|
+
amux5.terminalResize(streamId, input.cols, input.rows);
|
|
950
1581
|
return { ok: true };
|
|
951
1582
|
}),
|
|
952
1583
|
/**
|
|
953
|
-
* Check if a pane is a terminal
|
|
1584
|
+
* Check if a pane is a terminal stream.
|
|
954
1585
|
*/
|
|
955
1586
|
isTerminal: publicProcedure.input(paneInput2).query(({ input }) => {
|
|
956
|
-
const
|
|
957
|
-
return { isTerminal: amux5.
|
|
1587
|
+
const streamId = getStreamIdForPane2(input.paneId);
|
|
1588
|
+
return { isTerminal: amux5.isTerminalStream(streamId) };
|
|
958
1589
|
}),
|
|
959
1590
|
/**
|
|
960
1591
|
* Set terminal title (from OSC escape sequence).
|
|
@@ -963,12 +1594,264 @@ var terminalsRouter = router({
|
|
|
963
1594
|
paneId: z5.string(),
|
|
964
1595
|
title: z5.string()
|
|
965
1596
|
})).mutation(({ input }) => {
|
|
966
|
-
const
|
|
967
|
-
amux5.
|
|
1597
|
+
const streamId = getStreamIdForPane2(input.paneId);
|
|
1598
|
+
amux5.updateStreamTitle(streamId, input.title);
|
|
968
1599
|
return { ok: true };
|
|
969
1600
|
})
|
|
970
1601
|
});
|
|
971
1602
|
|
|
1603
|
+
// src/trpc/renderers.ts
|
|
1604
|
+
import { observable as observable2 } from "@trpc/server/observable";
|
|
1605
|
+
|
|
1606
|
+
// src/lib/rendererWatcher.ts
|
|
1607
|
+
import chokidar2 from "chokidar";
|
|
1608
|
+
import * as path9 from "path";
|
|
1609
|
+
import * as os5 from "os";
|
|
1610
|
+
import * as fs8 from "fs/promises";
|
|
1611
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
1612
|
+
|
|
1613
|
+
// src/lib/rendererCompiler.ts
|
|
1614
|
+
import { build as build2 } from "esbuild";
|
|
1615
|
+
import * as fs7 from "fs/promises";
|
|
1616
|
+
import * as path8 from "path";
|
|
1617
|
+
async function compileRenderer(pluginDir) {
|
|
1618
|
+
const metaPath = path8.join(pluginDir, "meta.json");
|
|
1619
|
+
const entryPath = path8.join(pluginDir, "index.tsx");
|
|
1620
|
+
const pkgPath = path8.join(pluginDir, "package.json");
|
|
1621
|
+
let meta;
|
|
1622
|
+
try {
|
|
1623
|
+
const metaContent = await fs7.readFile(metaPath, "utf-8");
|
|
1624
|
+
meta = JSON.parse(metaContent);
|
|
1625
|
+
} catch (err) {
|
|
1626
|
+
throw new Error(
|
|
1627
|
+
`Failed to read meta file for ${pluginDir}. Expected ${metaPath} with { name, target } properties. Error: ${err instanceof Error ? err.message : String(err)}`
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
if (!meta.name || typeof meta.name !== "string") {
|
|
1631
|
+
throw new Error(`Meta file ${metaPath} must have a 'name' string property`);
|
|
1632
|
+
}
|
|
1633
|
+
if (!meta.target || !meta.target.kind) {
|
|
1634
|
+
throw new Error(`Meta file ${metaPath} must have a 'target' object with 'kind' property`);
|
|
1635
|
+
}
|
|
1636
|
+
let hasPackageJson = false;
|
|
1637
|
+
try {
|
|
1638
|
+
const pkgContent = await fs7.readFile(pkgPath, "utf-8");
|
|
1639
|
+
const pkg = JSON.parse(pkgContent);
|
|
1640
|
+
hasPackageJson = true;
|
|
1641
|
+
const deps = pkg.dependencies || {};
|
|
1642
|
+
if (deps["react"] || deps["react-dom"]) {
|
|
1643
|
+
throw new Error(
|
|
1644
|
+
`Plugin "${meta.name}": react and react-dom must be in peerDependencies, not dependencies. This ensures plugins share the same React instance as the host app.`
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
} catch (err) {
|
|
1648
|
+
if (err instanceof Error && err.message.includes("peerDependencies")) {
|
|
1649
|
+
throw err;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
const result = await build2({
|
|
1653
|
+
entryPoints: [entryPath],
|
|
1654
|
+
bundle: true,
|
|
1655
|
+
write: false,
|
|
1656
|
+
format: "esm",
|
|
1657
|
+
jsx: "automatic",
|
|
1658
|
+
target: "es2020",
|
|
1659
|
+
external: ["react", "react-dom", "react/jsx-runtime", "@bytespell/shella-sdk", "@bytespell/shella-sdk/components/ui"],
|
|
1660
|
+
// If plugin has package.json, look for node_modules
|
|
1661
|
+
nodePaths: hasPackageJson ? [path8.join(pluginDir, "node_modules")] : [],
|
|
1662
|
+
minify: false
|
|
1663
|
+
});
|
|
1664
|
+
const code = result.outputFiles[0].text;
|
|
1665
|
+
const id = `user:${path8.basename(pluginDir)}`;
|
|
1666
|
+
return {
|
|
1667
|
+
id,
|
|
1668
|
+
name: meta.name,
|
|
1669
|
+
description: meta.description,
|
|
1670
|
+
target: meta.target,
|
|
1671
|
+
code
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
async function isValidPlugin(dirPath) {
|
|
1675
|
+
try {
|
|
1676
|
+
await fs7.access(path8.join(dirPath, "meta.json"));
|
|
1677
|
+
await fs7.access(path8.join(dirPath, "index.tsx"));
|
|
1678
|
+
return true;
|
|
1679
|
+
} catch {
|
|
1680
|
+
return false;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// src/lib/rendererWatcher.ts
|
|
1685
|
+
var RendererEventEmitter = class extends EventEmitter2 {
|
|
1686
|
+
emit(event, data) {
|
|
1687
|
+
return super.emit(event, data);
|
|
1688
|
+
}
|
|
1689
|
+
on(event, listener) {
|
|
1690
|
+
return super.on(event, listener);
|
|
1691
|
+
}
|
|
1692
|
+
off(event, listener) {
|
|
1693
|
+
return super.off(event, listener);
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
function watchRenderers(emitter) {
|
|
1697
|
+
const userPath = path9.join(os5.homedir(), ".shella", "renderers");
|
|
1698
|
+
ensureDir2(userPath);
|
|
1699
|
+
const knownPlugins = /* @__PURE__ */ new Map();
|
|
1700
|
+
const pendingCompiles = /* @__PURE__ */ new Map();
|
|
1701
|
+
const watcher = chokidar2.watch(userPath, {
|
|
1702
|
+
ignoreInitial: false,
|
|
1703
|
+
persistent: true,
|
|
1704
|
+
awaitWriteFinish: {
|
|
1705
|
+
stabilityThreshold: 100,
|
|
1706
|
+
pollInterval: 50
|
|
1707
|
+
},
|
|
1708
|
+
depth: 3
|
|
1709
|
+
// Watch inside plugin folders for nested components
|
|
1710
|
+
});
|
|
1711
|
+
const getPluginDir = (filePath) => {
|
|
1712
|
+
const relative3 = path9.relative(userPath, filePath);
|
|
1713
|
+
const parts = relative3.split(path9.sep);
|
|
1714
|
+
if (parts.length >= 1 && parts[0]) {
|
|
1715
|
+
return path9.join(userPath, parts[0]);
|
|
1716
|
+
}
|
|
1717
|
+
return null;
|
|
1718
|
+
};
|
|
1719
|
+
const scheduleCompile = (pluginDir) => {
|
|
1720
|
+
if (pendingCompiles.has(pluginDir)) {
|
|
1721
|
+
clearTimeout(pendingCompiles.get(pluginDir));
|
|
1722
|
+
}
|
|
1723
|
+
pendingCompiles.set(
|
|
1724
|
+
pluginDir,
|
|
1725
|
+
setTimeout(async () => {
|
|
1726
|
+
pendingCompiles.delete(pluginDir);
|
|
1727
|
+
await handlePluginChange(pluginDir);
|
|
1728
|
+
}, 100)
|
|
1729
|
+
);
|
|
1730
|
+
};
|
|
1731
|
+
const handlePluginChange = async (pluginDir) => {
|
|
1732
|
+
if (!await isValidPlugin(pluginDir)) return;
|
|
1733
|
+
try {
|
|
1734
|
+
const compiled = await compileRenderer(pluginDir);
|
|
1735
|
+
if (!knownPlugins.has(pluginDir)) {
|
|
1736
|
+
knownPlugins.set(pluginDir, true);
|
|
1737
|
+
console.log(`[renderers] Added: ${compiled.id}`);
|
|
1738
|
+
emitter.emit("renderer:added", compiled);
|
|
1739
|
+
} else {
|
|
1740
|
+
console.log(`[renderers] Updated: ${compiled.id}`);
|
|
1741
|
+
emitter.emit("renderer:updated", { id: compiled.id, code: compiled.code });
|
|
1742
|
+
}
|
|
1743
|
+
} catch (err) {
|
|
1744
|
+
console.error(`[renderers] Error compiling ${pluginDir}:`, err);
|
|
1745
|
+
emitter.emit("renderer:error", {
|
|
1746
|
+
filePath: pluginDir,
|
|
1747
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
watcher.on("add", (filePath) => {
|
|
1752
|
+
const pluginDir = getPluginDir(filePath);
|
|
1753
|
+
if (pluginDir) scheduleCompile(pluginDir);
|
|
1754
|
+
});
|
|
1755
|
+
watcher.on("change", (filePath) => {
|
|
1756
|
+
const pluginDir = getPluginDir(filePath);
|
|
1757
|
+
if (pluginDir) scheduleCompile(pluginDir);
|
|
1758
|
+
});
|
|
1759
|
+
watcher.on("unlinkDir", (dirPath) => {
|
|
1760
|
+
if (knownPlugins.has(dirPath)) {
|
|
1761
|
+
const id = `user:${path9.basename(dirPath)}`;
|
|
1762
|
+
knownPlugins.delete(dirPath);
|
|
1763
|
+
console.log(`[renderers] Removed: ${id}`);
|
|
1764
|
+
emitter.emit("renderer:removed", { id });
|
|
1765
|
+
}
|
|
1766
|
+
});
|
|
1767
|
+
watcher.on("error", (err) => {
|
|
1768
|
+
console.error("[renderers] Watcher error:", err);
|
|
1769
|
+
});
|
|
1770
|
+
return () => {
|
|
1771
|
+
for (const timeout of pendingCompiles.values()) {
|
|
1772
|
+
clearTimeout(timeout);
|
|
1773
|
+
}
|
|
1774
|
+
pendingCompiles.clear();
|
|
1775
|
+
watcher.close();
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
async function ensureDir2(dirPath) {
|
|
1779
|
+
try {
|
|
1780
|
+
await fs8.mkdir(dirPath, { recursive: true });
|
|
1781
|
+
} catch {
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
async function scanRenderers() {
|
|
1785
|
+
const userPath = path9.join(os5.homedir(), ".shella", "renderers");
|
|
1786
|
+
const renderers = [];
|
|
1787
|
+
try {
|
|
1788
|
+
const entries = await fs8.readdir(userPath, { withFileTypes: true });
|
|
1789
|
+
for (const entry of entries) {
|
|
1790
|
+
if (!entry.isDirectory()) continue;
|
|
1791
|
+
const pluginDir = path9.join(userPath, entry.name);
|
|
1792
|
+
if (await isValidPlugin(pluginDir)) {
|
|
1793
|
+
try {
|
|
1794
|
+
const compiled = await compileRenderer(pluginDir);
|
|
1795
|
+
renderers.push(compiled);
|
|
1796
|
+
} catch (err) {
|
|
1797
|
+
console.error(`[renderers] Error compiling ${pluginDir}:`, err);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
} catch {
|
|
1802
|
+
}
|
|
1803
|
+
return renderers;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// src/trpc/renderers.ts
|
|
1807
|
+
var rendererEmitter = new RendererEventEmitter();
|
|
1808
|
+
var stopWatcher = null;
|
|
1809
|
+
function initRendererWatcher() {
|
|
1810
|
+
if (stopWatcher) {
|
|
1811
|
+
stopWatcher();
|
|
1812
|
+
}
|
|
1813
|
+
stopWatcher = watchRenderers(rendererEmitter);
|
|
1814
|
+
}
|
|
1815
|
+
var renderersRouter = router({
|
|
1816
|
+
/**
|
|
1817
|
+
* List all currently available user renderers
|
|
1818
|
+
* Used for initial hydration when client connects.
|
|
1819
|
+
*/
|
|
1820
|
+
list: publicProcedure.query(async () => {
|
|
1821
|
+
return scanRenderers();
|
|
1822
|
+
}),
|
|
1823
|
+
/**
|
|
1824
|
+
* Subscribe to renderer additions
|
|
1825
|
+
*/
|
|
1826
|
+
onAdded: publicProcedure.subscription(() => {
|
|
1827
|
+
return observable2((emit) => {
|
|
1828
|
+
const handler = (data) => emit.next(data);
|
|
1829
|
+
rendererEmitter.on("renderer:added", handler);
|
|
1830
|
+
return () => rendererEmitter.off("renderer:added", handler);
|
|
1831
|
+
});
|
|
1832
|
+
}),
|
|
1833
|
+
/**
|
|
1834
|
+
* Subscribe to renderer updates (code changes)
|
|
1835
|
+
*/
|
|
1836
|
+
onUpdated: publicProcedure.subscription(() => {
|
|
1837
|
+
return observable2((emit) => {
|
|
1838
|
+
const handler = (data) => emit.next(data);
|
|
1839
|
+
rendererEmitter.on("renderer:updated", handler);
|
|
1840
|
+
return () => rendererEmitter.off("renderer:updated", handler);
|
|
1841
|
+
});
|
|
1842
|
+
}),
|
|
1843
|
+
/**
|
|
1844
|
+
* Subscribe to renderer removals
|
|
1845
|
+
*/
|
|
1846
|
+
onRemoved: publicProcedure.subscription(() => {
|
|
1847
|
+
return observable2((emit) => {
|
|
1848
|
+
const handler = (data) => emit.next(data);
|
|
1849
|
+
rendererEmitter.on("renderer:removed", handler);
|
|
1850
|
+
return () => rendererEmitter.off("renderer:removed", handler);
|
|
1851
|
+
});
|
|
1852
|
+
})
|
|
1853
|
+
});
|
|
1854
|
+
|
|
972
1855
|
// src/trpc/router.ts
|
|
973
1856
|
function initializeRouters(amux6) {
|
|
974
1857
|
setAmuxBridge(amux6);
|
|
@@ -976,39 +1859,60 @@ function initializeRouters(amux6) {
|
|
|
976
1859
|
setAmuxBridge3(amux6);
|
|
977
1860
|
setAmuxBridge4(amux6);
|
|
978
1861
|
setAmuxBridge5(amux6);
|
|
1862
|
+
initRendererWatcher();
|
|
979
1863
|
}
|
|
980
1864
|
var shellaRouter = router({
|
|
981
1865
|
windows: windowsRouter,
|
|
982
1866
|
layout: layoutRouter,
|
|
983
|
-
|
|
1867
|
+
streams: streamsRouter,
|
|
984
1868
|
files: filesRouter,
|
|
985
|
-
terminals: terminalsRouter
|
|
1869
|
+
terminals: terminalsRouter,
|
|
1870
|
+
renderers: renderersRouter,
|
|
1871
|
+
// Legacy alias
|
|
1872
|
+
agents: streamsRouter
|
|
986
1873
|
});
|
|
987
1874
|
|
|
988
1875
|
// src/index.ts
|
|
989
|
-
|
|
1876
|
+
discoverAndRegisterConfigs(builtInDiscoverers);
|
|
1877
|
+
var __dirname2 = path10.dirname(fileURLToPath2(import.meta.url));
|
|
990
1878
|
function createShellaServer(options = {}) {
|
|
991
1879
|
const port = options.port ?? 3067;
|
|
992
1880
|
if (options.verbose) {
|
|
993
1881
|
setVerbose(true);
|
|
994
1882
|
}
|
|
1883
|
+
const driverEmitter = new DriverEventEmitter();
|
|
1884
|
+
let stopDriverWatcher = null;
|
|
1885
|
+
driverEmitter.on("driver:added", ({ id, name }) => {
|
|
1886
|
+
debug("shella", `Driver registered: ${id} (${name})`);
|
|
1887
|
+
});
|
|
1888
|
+
driverEmitter.on("driver:updated", ({ id, name }) => {
|
|
1889
|
+
debug("shella", `Driver updated: ${id} (${name})`);
|
|
1890
|
+
});
|
|
1891
|
+
driverEmitter.on("driver:removed", ({ id }) => {
|
|
1892
|
+
debug("shella", `Driver removed: ${id}`);
|
|
1893
|
+
});
|
|
1894
|
+
driverEmitter.on("driver:error", ({ pluginDir, error }) => {
|
|
1895
|
+
console.error(`Driver error in ${pluginDir}:`, error.message);
|
|
1896
|
+
});
|
|
995
1897
|
const amuxBridge = createAmuxBridge();
|
|
996
1898
|
initializeRouters(amuxBridge);
|
|
997
1899
|
const app = express();
|
|
998
1900
|
app.use(express.json());
|
|
999
|
-
const publicPath =
|
|
1901
|
+
const publicPath = path10.join(__dirname2, "..", "public");
|
|
1000
1902
|
app.use(express.static(publicPath));
|
|
1001
1903
|
app.use("/trpc", createExpressMiddleware({ router: shellaRouter }));
|
|
1002
1904
|
app.get("/health", (_req, res) => res.json({ status: "ok" }));
|
|
1003
1905
|
app.get("/{*splat}", (_req, res) => {
|
|
1004
|
-
res.sendFile(
|
|
1906
|
+
res.sendFile(path10.join(publicPath, "index.html"));
|
|
1005
1907
|
});
|
|
1006
1908
|
const server = createServer(app);
|
|
1007
1909
|
const wss = new WebSocketServer({ server, path: "/trpc" });
|
|
1008
1910
|
applyWSSHandler({ wss, router: shellaRouter });
|
|
1009
1911
|
return {
|
|
1010
|
-
start: () => {
|
|
1011
|
-
|
|
1912
|
+
start: async () => {
|
|
1913
|
+
await scanDrivers();
|
|
1914
|
+
stopDriverWatcher = watchDrivers(driverEmitter);
|
|
1915
|
+
return new Promise((resolve4, reject) => {
|
|
1012
1916
|
server.once("error", (err) => {
|
|
1013
1917
|
if (err.code === "EADDRINUSE") {
|
|
1014
1918
|
reject(new Error(`Port ${port} is already in use. Kill the other process or use a different port.`));
|
|
@@ -1020,14 +1924,15 @@ function createShellaServer(options = {}) {
|
|
|
1020
1924
|
const url = `http://localhost:${port}`;
|
|
1021
1925
|
debug("shella", `Running on ${url}`);
|
|
1022
1926
|
options.onReady?.(url);
|
|
1023
|
-
|
|
1927
|
+
resolve4();
|
|
1024
1928
|
});
|
|
1025
1929
|
});
|
|
1026
1930
|
},
|
|
1027
1931
|
stop: async () => {
|
|
1028
1932
|
debug("shella", "Shutting down...");
|
|
1933
|
+
stopDriverWatcher?.();
|
|
1029
1934
|
wss.close();
|
|
1030
|
-
await
|
|
1935
|
+
await muxManager3.stopAll();
|
|
1031
1936
|
server.close();
|
|
1032
1937
|
}
|
|
1033
1938
|
};
|