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