@bytespell/shella 0.1.6 → 0.1.8

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.
Files changed (143) hide show
  1. package/dist/bin/cli.js +277 -80
  2. package/dist/bin/cli.js.map +1 -1
  3. package/dist/index.js +269 -79
  4. package/dist/index.js.map +1 -1
  5. package/dist/public/assets/{_baseUniq-ofDPcIU7.js → _baseUniq-xMBLm_vj.js} +1 -1
  6. package/{public/assets/arc-CN1QbtAQ.js → dist/public/assets/arc-CCivY36l.js} +1 -1
  7. package/{public/assets/architectureDiagram-VXUJARFQ-B7xrwB3a.js → dist/public/assets/architectureDiagram-VXUJARFQ-Cx1ftnZs.js} +1 -1
  8. package/{public/assets/blockDiagram-VD42YOAC-ByLGlNws.js → dist/public/assets/blockDiagram-VD42YOAC-Dd5J4pAa.js} +1 -1
  9. package/dist/public/assets/{c4Diagram-YG6GDRKO-YpYx4Aj8.js → c4Diagram-YG6GDRKO-C4yoJFk1.js} +1 -1
  10. package/dist/public/assets/channel-CgkYY8Ci.js +1 -0
  11. package/dist/public/assets/{chunk-4BX2VUAB-D0zp96k1.js → chunk-4BX2VUAB-CiImuKHD.js} +1 -1
  12. package/dist/public/assets/{chunk-55IACEB6-C5GJeIrO.js → chunk-55IACEB6-V6l1i-_P.js} +1 -1
  13. package/{public/assets/chunk-B4BG7PRW-Ci17wd3H.js → dist/public/assets/chunk-B4BG7PRW-DU8Wql9S.js} +1 -1
  14. package/{public/assets/chunk-DI55MBZ5-B2XMqfQC.js → dist/public/assets/chunk-DI55MBZ5-B_Jw-lhj.js} +1 -1
  15. package/dist/public/assets/{chunk-FMBD7UC4-Crk5_wey.js → chunk-FMBD7UC4-C1LlA37K.js} +1 -1
  16. package/dist/public/assets/{chunk-QN33PNHL-DqQoCDuG.js → chunk-QN33PNHL-CcgyTGxJ.js} +1 -1
  17. package/dist/public/assets/{chunk-QZHKN3VN-DtbXtmmy.js → chunk-QZHKN3VN-C2H0lOPM.js} +1 -1
  18. package/dist/public/assets/{chunk-TZMSLE5B-Boi_YcuZ.js → chunk-TZMSLE5B-Cp4Dpzkh.js} +1 -1
  19. package/dist/public/assets/classDiagram-2ON5EDUG-BuYVNwmc.js +1 -0
  20. package/dist/public/assets/classDiagram-v2-WZHVMYZB-BuYVNwmc.js +1 -0
  21. package/dist/public/assets/clone-mkrd6lC9.js +1 -0
  22. package/dist/public/assets/{code-block-IT6T5CEO-BAcnOjsj.js → code-block-IT6T5CEO-DOOuyfxG.js} +1 -1
  23. package/dist/public/assets/{cose-bilkent-S5V4N54A-DAIcBIuP.js → cose-bilkent-S5V4N54A-DefjjRca.js} +1 -1
  24. package/dist/public/assets/{dagre-6UL2VRFP-zz51dmH0.js → dagre-6UL2VRFP-hHpxpOKX.js} +1 -1
  25. package/dist/public/assets/demo.svg +1 -0
  26. package/dist/public/assets/{diagram-PSM6KHXK-bVpD6rI7.js → diagram-PSM6KHXK-ZgZ2X0Q-.js} +1 -1
  27. package/dist/public/assets/{diagram-QEK2KX5R-I8YB5Wm0.js → diagram-QEK2KX5R-gCeAqKj1.js} +1 -1
  28. package/dist/public/assets/{diagram-S2PKOQOG-D1xlOPWJ.js → diagram-S2PKOQOG-9IyVqMFa.js} +1 -1
  29. package/{public/assets/erDiagram-Q2GNP2WA-DEdjbB3m.js → dist/public/assets/erDiagram-Q2GNP2WA-Dzm33cik.js} +1 -1
  30. package/{public/assets/flowDiagram-NV44I4VS-BBCU2dfK.js → dist/public/assets/flowDiagram-NV44I4VS-BamNhi0g.js} +1 -1
  31. package/dist/public/assets/{ganttDiagram-JELNMOA3-B0jnifD_.js → ganttDiagram-JELNMOA3-CrECl9wX.js} +1 -1
  32. package/dist/public/assets/{gitGraphDiagram-NY62KEGX-BX21wvJS.js → gitGraphDiagram-NY62KEGX-C7_E64uK.js} +1 -1
  33. package/dist/public/assets/{graph-BE95p5Ca.js → graph-CtTAhETf.js} +1 -1
  34. package/dist/public/assets/index-Czr2q0NP.css +1 -0
  35. package/dist/public/assets/index-kEIJA0pC.js +1781 -0
  36. package/dist/public/assets/{infoDiagram-WHAUD3N6-CrU0UXwW.js → infoDiagram-WHAUD3N6-CLOougXO.js} +1 -1
  37. package/dist/public/assets/{journeyDiagram-XKPGCS4Q-DzukVB-1.js → journeyDiagram-XKPGCS4Q-g0dISD-I.js} +1 -1
  38. package/{public/assets/kanban-definition-3W4ZIXB7-CJVLdxxJ.js → dist/public/assets/kanban-definition-3W4ZIXB7-DFIVrLyR.js} +1 -1
  39. package/dist/public/assets/{layout-CM_3juMX.js → layout-DolajoeL.js} +1 -1
  40. package/dist/public/assets/{linear-BXRjo7t5.js → linear-B5Hf7uIN.js} +1 -1
  41. package/dist/public/assets/{mermaid.core-Hp60xAyJ.js → mermaid.core-DqP3HtOk.js} +5 -5
  42. package/dist/public/assets/{min-oqogeQSX.js → min-UFvm8GLY.js} +1 -1
  43. package/dist/public/assets/{mindmap-definition-VGOIOE7T-CCgMWgfM.js → mindmap-definition-VGOIOE7T-CnSTRcIt.js} +1 -1
  44. package/dist/public/assets/{pieDiagram-ADFJNKIX-DAkNkjx-.js → pieDiagram-ADFJNKIX-CuMH9Po8.js} +1 -1
  45. package/{public/assets/quadrantDiagram-AYHSOK5B-CPEqSA2x.js → dist/public/assets/quadrantDiagram-AYHSOK5B-6i0SX3Xo.js} +1 -1
  46. package/dist/public/assets/{requirementDiagram-UZGBJVZJ-jOv297_A.js → requirementDiagram-UZGBJVZJ-ChwO2HU-.js} +1 -1
  47. package/dist/public/assets/{sankeyDiagram-TZEHDZUN-CwvAKmpc.js → sankeyDiagram-TZEHDZUN-DG-8crEL.js} +1 -1
  48. package/dist/public/assets/{sequenceDiagram-WL72ISMW-D_WZ2ogT.js → sequenceDiagram-WL72ISMW-BQH_OfEp.js} +1 -1
  49. package/{public/assets/stateDiagram-FKZM4ZOC-tqYMmK3v.js → dist/public/assets/stateDiagram-FKZM4ZOC-CBOkbVQn.js} +1 -1
  50. package/dist/public/assets/stateDiagram-v2-4FDKWEC3-BqHz04Bh.js +1 -0
  51. package/{public/assets/timeline-definition-IT6M3QCI-BIqJkdQ0.js → dist/public/assets/timeline-definition-IT6M3QCI-CkNd8y6W.js} +1 -1
  52. package/dist/public/assets/{treemap-KMMF4GRG-CmJM6xaw.js → treemap-KMMF4GRG-D6z_eudj.js} +1 -1
  53. package/dist/public/assets/{xychartDiagram-PRI3JC2R-D7-tZ8iR.js → xychartDiagram-PRI3JC2R-B5wk4XRt.js} +1 -1
  54. package/dist/public/favicon-16.ico +0 -0
  55. package/dist/public/favicon-32.ico +0 -0
  56. package/dist/public/favicon-48.ico +0 -0
  57. package/dist/public/favicon.svg +4 -0
  58. package/dist/public/index.html +2 -2
  59. package/dist/public/logo-1024.png +0 -0
  60. package/dist/public/logo-128.png +0 -0
  61. package/dist/public/logo-16.png +0 -0
  62. package/dist/public/logo-256.png +0 -0
  63. package/dist/public/logo-32.png +0 -0
  64. package/dist/public/logo-48.png +0 -0
  65. package/dist/public/logo-512.png +0 -0
  66. package/dist/public/logo-64.png +0 -0
  67. package/package.json +6 -2
  68. package/public/assets/{_baseUniq-ofDPcIU7.js → _baseUniq-xMBLm_vj.js} +1 -1
  69. package/{dist/public/assets/arc-CN1QbtAQ.js → public/assets/arc-CCivY36l.js} +1 -1
  70. package/{dist/public/assets/architectureDiagram-VXUJARFQ-B7xrwB3a.js → public/assets/architectureDiagram-VXUJARFQ-Cx1ftnZs.js} +1 -1
  71. package/{dist/public/assets/blockDiagram-VD42YOAC-ByLGlNws.js → public/assets/blockDiagram-VD42YOAC-Dd5J4pAa.js} +1 -1
  72. package/public/assets/{c4Diagram-YG6GDRKO-YpYx4Aj8.js → c4Diagram-YG6GDRKO-C4yoJFk1.js} +1 -1
  73. package/public/assets/channel-CgkYY8Ci.js +1 -0
  74. package/public/assets/{chunk-4BX2VUAB-D0zp96k1.js → chunk-4BX2VUAB-CiImuKHD.js} +1 -1
  75. package/public/assets/{chunk-55IACEB6-C5GJeIrO.js → chunk-55IACEB6-V6l1i-_P.js} +1 -1
  76. package/{dist/public/assets/chunk-B4BG7PRW-Ci17wd3H.js → public/assets/chunk-B4BG7PRW-DU8Wql9S.js} +1 -1
  77. package/{dist/public/assets/chunk-DI55MBZ5-B2XMqfQC.js → public/assets/chunk-DI55MBZ5-B_Jw-lhj.js} +1 -1
  78. package/public/assets/{chunk-FMBD7UC4-Crk5_wey.js → chunk-FMBD7UC4-C1LlA37K.js} +1 -1
  79. package/public/assets/{chunk-QN33PNHL-DqQoCDuG.js → chunk-QN33PNHL-CcgyTGxJ.js} +1 -1
  80. package/public/assets/{chunk-QZHKN3VN-DtbXtmmy.js → chunk-QZHKN3VN-C2H0lOPM.js} +1 -1
  81. package/public/assets/{chunk-TZMSLE5B-Boi_YcuZ.js → chunk-TZMSLE5B-Cp4Dpzkh.js} +1 -1
  82. package/public/assets/classDiagram-2ON5EDUG-BuYVNwmc.js +1 -0
  83. package/public/assets/classDiagram-v2-WZHVMYZB-BuYVNwmc.js +1 -0
  84. package/public/assets/clone-mkrd6lC9.js +1 -0
  85. package/public/assets/{code-block-IT6T5CEO-BAcnOjsj.js → code-block-IT6T5CEO-DOOuyfxG.js} +1 -1
  86. package/public/assets/{cose-bilkent-S5V4N54A-DAIcBIuP.js → cose-bilkent-S5V4N54A-DefjjRca.js} +1 -1
  87. package/public/assets/{dagre-6UL2VRFP-zz51dmH0.js → dagre-6UL2VRFP-hHpxpOKX.js} +1 -1
  88. package/public/assets/demo.svg +1 -0
  89. package/public/assets/{diagram-PSM6KHXK-bVpD6rI7.js → diagram-PSM6KHXK-ZgZ2X0Q-.js} +1 -1
  90. package/public/assets/{diagram-QEK2KX5R-I8YB5Wm0.js → diagram-QEK2KX5R-gCeAqKj1.js} +1 -1
  91. package/public/assets/{diagram-S2PKOQOG-D1xlOPWJ.js → diagram-S2PKOQOG-9IyVqMFa.js} +1 -1
  92. package/{dist/public/assets/erDiagram-Q2GNP2WA-DEdjbB3m.js → public/assets/erDiagram-Q2GNP2WA-Dzm33cik.js} +1 -1
  93. package/{dist/public/assets/flowDiagram-NV44I4VS-BBCU2dfK.js → public/assets/flowDiagram-NV44I4VS-BamNhi0g.js} +1 -1
  94. package/public/assets/{ganttDiagram-JELNMOA3-B0jnifD_.js → ganttDiagram-JELNMOA3-CrECl9wX.js} +1 -1
  95. package/public/assets/{gitGraphDiagram-NY62KEGX-BX21wvJS.js → gitGraphDiagram-NY62KEGX-C7_E64uK.js} +1 -1
  96. package/public/assets/{graph-BE95p5Ca.js → graph-CtTAhETf.js} +1 -1
  97. package/public/assets/index-Czr2q0NP.css +1 -0
  98. package/public/assets/index-kEIJA0pC.js +1781 -0
  99. package/public/assets/{infoDiagram-WHAUD3N6-CrU0UXwW.js → infoDiagram-WHAUD3N6-CLOougXO.js} +1 -1
  100. package/public/assets/{journeyDiagram-XKPGCS4Q-DzukVB-1.js → journeyDiagram-XKPGCS4Q-g0dISD-I.js} +1 -1
  101. package/{dist/public/assets/kanban-definition-3W4ZIXB7-CJVLdxxJ.js → public/assets/kanban-definition-3W4ZIXB7-DFIVrLyR.js} +1 -1
  102. package/public/assets/{layout-CM_3juMX.js → layout-DolajoeL.js} +1 -1
  103. package/public/assets/{linear-BXRjo7t5.js → linear-B5Hf7uIN.js} +1 -1
  104. package/public/assets/{mermaid.core-Hp60xAyJ.js → mermaid.core-DqP3HtOk.js} +5 -5
  105. package/public/assets/{min-oqogeQSX.js → min-UFvm8GLY.js} +1 -1
  106. package/public/assets/{mindmap-definition-VGOIOE7T-CCgMWgfM.js → mindmap-definition-VGOIOE7T-CnSTRcIt.js} +1 -1
  107. package/public/assets/{pieDiagram-ADFJNKIX-DAkNkjx-.js → pieDiagram-ADFJNKIX-CuMH9Po8.js} +1 -1
  108. package/{dist/public/assets/quadrantDiagram-AYHSOK5B-CPEqSA2x.js → public/assets/quadrantDiagram-AYHSOK5B-6i0SX3Xo.js} +1 -1
  109. package/public/assets/{requirementDiagram-UZGBJVZJ-jOv297_A.js → requirementDiagram-UZGBJVZJ-ChwO2HU-.js} +1 -1
  110. package/public/assets/{sankeyDiagram-TZEHDZUN-CwvAKmpc.js → sankeyDiagram-TZEHDZUN-DG-8crEL.js} +1 -1
  111. package/public/assets/{sequenceDiagram-WL72ISMW-D_WZ2ogT.js → sequenceDiagram-WL72ISMW-BQH_OfEp.js} +1 -1
  112. package/{dist/public/assets/stateDiagram-FKZM4ZOC-tqYMmK3v.js → public/assets/stateDiagram-FKZM4ZOC-CBOkbVQn.js} +1 -1
  113. package/public/assets/stateDiagram-v2-4FDKWEC3-BqHz04Bh.js +1 -0
  114. package/{dist/public/assets/timeline-definition-IT6M3QCI-BIqJkdQ0.js → public/assets/timeline-definition-IT6M3QCI-CkNd8y6W.js} +1 -1
  115. package/public/assets/{treemap-KMMF4GRG-CmJM6xaw.js → treemap-KMMF4GRG-D6z_eudj.js} +1 -1
  116. package/public/assets/{xychartDiagram-PRI3JC2R-D7-tZ8iR.js → xychartDiagram-PRI3JC2R-B5wk4XRt.js} +1 -1
  117. package/public/favicon-16.ico +0 -0
  118. package/public/favicon-32.ico +0 -0
  119. package/public/favicon-48.ico +0 -0
  120. package/public/favicon.svg +4 -0
  121. package/public/index.html +2 -2
  122. package/public/logo-1024.png +0 -0
  123. package/public/logo-128.png +0 -0
  124. package/public/logo-16.png +0 -0
  125. package/public/logo-256.png +0 -0
  126. package/public/logo-32.png +0 -0
  127. package/public/logo-48.png +0 -0
  128. package/public/logo-512.png +0 -0
  129. package/public/logo-64.png +0 -0
  130. package/dist/public/assets/channel-BtuRdvAy.js +0 -1
  131. package/dist/public/assets/classDiagram-2ON5EDUG-CDjYhjfv.js +0 -1
  132. package/dist/public/assets/classDiagram-v2-WZHVMYZB-CDjYhjfv.js +0 -1
  133. package/dist/public/assets/clone-C8pGQ3Xy.js +0 -1
  134. package/dist/public/assets/index-CA2Ed_I8.css +0 -1
  135. package/dist/public/assets/index-CbxztLlk.js +0 -1701
  136. package/dist/public/assets/stateDiagram-v2-4FDKWEC3-BF0ecSBs.js +0 -1
  137. package/public/assets/channel-BtuRdvAy.js +0 -1
  138. package/public/assets/classDiagram-2ON5EDUG-CDjYhjfv.js +0 -1
  139. package/public/assets/classDiagram-v2-WZHVMYZB-CDjYhjfv.js +0 -1
  140. package/public/assets/clone-C8pGQ3Xy.js +0 -1
  141. package/public/assets/index-CA2Ed_I8.css +0 -1
  142. package/public/assets/index-CbxztLlk.js +0 -1701
  143. package/public/assets/stateDiagram-v2-4FDKWEC3-BF0ecSBs.js +0 -1
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import { fileURLToPath } from "url";
13
13
  import { createExpressMiddleware } from "@trpc/server/adapters/express";
14
14
  import { applyWSSHandler } from "@trpc/server/adapters/ws";
15
15
  import { agentManager as agentManager2 } from "@bytespell/amux/agents/manager";
16
+ import { setVerbose, debug } from "@bytespell/amux/lib/logger";
16
17
  import "@bytespell/amux/db";
17
18
 
18
19
  // src/db/index.ts
@@ -73,8 +74,8 @@ var windows = sqliteTable("windows", {
73
74
  var panes = sqliteTable("panes", {
74
75
  id: text("id").primaryKey(),
75
76
  windowId: text("window_id").notNull().references(() => windows.id, { onDelete: "cascade" }),
76
- /** References amux session ID (cross-database) */
77
- sessionId: text("session_id").notNull(),
77
+ /** Content configuration. Null when pane type not yet selected. */
78
+ content: text("content", { mode: "json" }).$type(),
78
79
  createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
79
80
  });
80
81
  var uiState = sqliteTable("ui_state", {
@@ -102,7 +103,7 @@ sqlite.exec(`
102
103
  CREATE TABLE IF NOT EXISTS panes (
103
104
  id TEXT PRIMARY KEY,
104
105
  window_id TEXT NOT NULL REFERENCES windows(id) ON DELETE CASCADE,
105
- session_id TEXT NOT NULL,
106
+ content TEXT,
106
107
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
107
108
  );
108
109
 
@@ -111,6 +112,46 @@ sqlite.exec(`
111
112
  value TEXT NOT NULL
112
113
  );
113
114
  `);
115
+ try {
116
+ const tableInfo = sqlite.prepare("PRAGMA table_info(panes)").all();
117
+ const hasSessionId = tableInfo.some((col) => col.name === "session_id");
118
+ const hasContent = tableInfo.some((col) => col.name === "content");
119
+ if (hasSessionId && !hasContent) {
120
+ sqlite.exec(`
121
+ -- Disable foreign keys temporarily
122
+ PRAGMA foreign_keys = OFF;
123
+
124
+ -- Create new table with content column
125
+ CREATE TABLE panes_new (
126
+ id TEXT PRIMARY KEY,
127
+ window_id TEXT NOT NULL REFERENCES windows(id) ON DELETE CASCADE,
128
+ content TEXT,
129
+ created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
130
+ );
131
+
132
+ -- Copy data, converting session_id to content JSON
133
+ INSERT INTO panes_new (id, window_id, content, created_at)
134
+ SELECT
135
+ id,
136
+ window_id,
137
+ CASE
138
+ WHEN session_id IS NOT NULL THEN json_object('type', 'session', 'sessionId', session_id)
139
+ ELSE NULL
140
+ END,
141
+ created_at
142
+ FROM panes;
143
+
144
+ -- Drop old table and rename new
145
+ DROP TABLE panes;
146
+ ALTER TABLE panes_new RENAME TO panes;
147
+
148
+ -- Re-enable foreign keys
149
+ PRAGMA foreign_keys = ON;
150
+ `);
151
+ console.log("[shella-db] Migrated panes from session_id to content column");
152
+ }
153
+ } catch {
154
+ }
114
155
 
115
156
  // src/amuxBridge.ts
116
157
  import { randomUUID } from "crypto";
@@ -134,8 +175,8 @@ function createAmuxBridge() {
134
175
  if (!configId) {
135
176
  const lastUsedRow = db2.select().from(appState).where(eq(appState.key, "last_used_agent_config_id")).get();
136
177
  if (lastUsedRow?.value) {
137
- const config = db2.select().from(agentConfigs).where(eq(agentConfigs.id, lastUsedRow.value)).get();
138
- if (config) configId = config.id;
178
+ const config2 = db2.select().from(agentConfigs).where(eq(agentConfigs.id, lastUsedRow.value)).get();
179
+ if (config2) configId = config2.id;
139
180
  }
140
181
  if (!configId) {
141
182
  const firstConfig = db2.select().from(agentConfigs).get();
@@ -149,11 +190,14 @@ function createAmuxBridge() {
149
190
  }
150
191
  const id = params.id ?? randomUUID();
151
192
  const now = /* @__PURE__ */ new Date();
193
+ const config = db2.select().from(agentConfigs).where(eq(agentConfigs.id, configId)).get();
194
+ const initialTitle = config?.streamType === "pty" ? params.directory.split("/").pop() || null : null;
152
195
  db2.insert(sessions).values({
153
196
  id,
154
197
  directory: params.directory,
155
198
  agentConfigId: configId,
156
199
  acpSessionId: null,
200
+ title: initialTitle,
157
201
  model: null,
158
202
  mode: null,
159
203
  createdAt: now
@@ -189,17 +233,6 @@ function createAmuxBridge() {
189
233
  respondPermission(sessionId, requestId, optionId) {
190
234
  agentManager.respondPermission(sessionId, requestId, optionId);
191
235
  },
192
- async switchAgent(sessionId, agentConfigId) {
193
- await agentManager.stopForSession(sessionId);
194
- clearEventsForSession(sessionId);
195
- db2.update(sessions).set({
196
- agentConfigId,
197
- acpSessionId: null,
198
- model: null,
199
- mode: null
200
- }).where(eq(sessions.id, sessionId)).run();
201
- db2.insert(appState).values({ key: "last_used_agent_config_id", value: agentConfigId }).onConflictDoUpdate({ target: appState.key, set: { value: agentConfigId } }).run();
202
- },
203
236
  subscribeToSession(sessionId, callback) {
204
237
  const handler = (event) => {
205
238
  if (event.sessionId === sessionId) {
@@ -213,6 +246,22 @@ function createAmuxBridge() {
213
246
  const session = db2.select().from(sessions).where(eq(sessions.id, sessionId)).get();
214
247
  if (!session) throw new Error(`Session ${sessionId} not found`);
215
248
  return listFilesForAutocomplete(session.directory, partialPath, limit);
249
+ },
250
+ // Terminal operations
251
+ terminalWrite(sessionId, data) {
252
+ agentManager.terminalWrite(sessionId, data);
253
+ },
254
+ terminalResize(sessionId, cols, rows) {
255
+ agentManager.terminalResize(sessionId, cols, rows);
256
+ },
257
+ getTerminalScrollback(sessionId) {
258
+ return agentManager.getTerminalScrollback(sessionId);
259
+ },
260
+ isTerminalSession(sessionId) {
261
+ return agentManager.isTerminalSession(sessionId);
262
+ },
263
+ updateSessionTitle(sessionId, title) {
264
+ db2.update(sessions).set({ title }).where(eq(sessions.id, sessionId)).run();
216
265
  }
217
266
  };
218
267
  }
@@ -356,7 +405,9 @@ var windowsRouter = router({
356
405
  const allWindows = db.select().from(windows).orderBy(windows.createdAt).all();
357
406
  const allPanes = db.select().from(panes).all();
358
407
  const activeRow = db.select().from(uiState).where(eq2(uiState.key, "active_window_id")).get();
359
- const sessionIds = [...new Set(allPanes.map((p) => p.sessionId))];
408
+ const sessionIds = [...new Set(
409
+ allPanes.map((p) => p.content?.type === "session" ? p.content.sessionId : null).filter((id) => id !== null)
410
+ )];
360
411
  const sessionsData = await amux.getSessions(sessionIds);
361
412
  const sessionMap = new Map(sessionsData.map((s) => [s.id, s]));
362
413
  const agentConfigs2 = await amux.getAgentConfigs();
@@ -365,7 +416,8 @@ var windowsRouter = router({
365
416
  ...w,
366
417
  panes: allPanes.filter((p) => p.windowId === w.id).map((p) => ({
367
418
  ...p,
368
- session: sessionMap.get(p.sessionId) ?? null
419
+ // Enrich session-type content with full session data
420
+ session: p.content?.type === "session" ? sessionMap.get(p.content.sessionId) ?? null : null
369
421
  }))
370
422
  })),
371
423
  activeWindowId: activeRow?.value ?? allWindows[allWindows.length - 1]?.id ?? null,
@@ -381,19 +433,20 @@ var windowsRouter = router({
381
433
  throw new Error(`Window ${input.id} not found`);
382
434
  }
383
435
  const windowPanes = db.select().from(panes).where(eq2(panes.windowId, input.id)).all();
384
- const sessionIds = windowPanes.map((p) => p.sessionId);
436
+ const sessionIds = windowPanes.map((p) => p.content?.type === "session" ? p.content.sessionId : null).filter((id) => id !== null);
385
437
  const sessionsData = await amux.getSessions(sessionIds);
386
438
  const sessionMap = new Map(sessionsData.map((s) => [s.id, s]));
387
439
  return {
388
440
  ...window,
389
441
  panes: windowPanes.map((p) => ({
390
442
  ...p,
391
- session: sessionMap.get(p.sessionId) ?? null
443
+ session: p.content?.type === "session" ? sessionMap.get(p.content.sessionId) ?? null : null
392
444
  }))
393
445
  };
394
446
  }),
395
447
  /**
396
448
  * Create a new window with a single pane.
449
+ * If agentConfigId is not provided, pane is created with no content (shows picker).
397
450
  */
398
451
  create: publicProcedure.input(z.object({
399
452
  id: z.string().optional(),
@@ -402,10 +455,13 @@ var windowsRouter = router({
402
455
  agentConfigId: z.string().optional()
403
456
  })).mutation(async ({ input }) => {
404
457
  const directory = input.directory ?? process.cwd();
405
- const session = await amux.createSession({
406
- directory,
407
- agentConfigId: input.agentConfigId
408
- });
458
+ let session = null;
459
+ if (input.agentConfigId) {
460
+ session = await amux.createSession({
461
+ directory,
462
+ agentConfigId: input.agentConfigId
463
+ });
464
+ }
409
465
  const windowId = input.id ?? randomUUID2();
410
466
  const paneId = randomUUID2();
411
467
  const existingCount = db.select().from(windows).all().length;
@@ -414,14 +470,13 @@ var windowsRouter = router({
414
470
  id: windowId,
415
471
  title: input.title || generateTitle(existingCount, directory),
416
472
  hasCustomTitle: !!input.title,
417
- // Only true if title is non-empty string
418
473
  layout,
419
474
  activePaneId: paneId
420
475
  }).run();
421
476
  db.insert(panes).values({
422
477
  id: paneId,
423
478
  windowId,
424
- sessionId: session.id
479
+ content: session ? { type: "session", sessionId: session.id } : null
425
480
  }).run();
426
481
  db.insert(uiState).values({ key: "active_window_id", value: windowId }).onConflictDoUpdate({ target: uiState.key, set: { value: windowId } }).run();
427
482
  const newWindow = db.select().from(windows).where(eq2(windows.id, windowId)).get();
@@ -430,7 +485,7 @@ var windowsRouter = router({
430
485
  ...newWindow,
431
486
  panes: newPanes.map((p) => ({
432
487
  ...p,
433
- session: p.sessionId === session.id ? session : null
488
+ session: session && p.content?.type === "session" && p.content.sessionId === session.id ? session : null
434
489
  }))
435
490
  };
436
491
  }),
@@ -456,7 +511,9 @@ var windowsRouter = router({
456
511
  delete: publicProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => {
457
512
  const windowPanes = db.select().from(panes).where(eq2(panes.windowId, input.id)).all();
458
513
  for (const pane of windowPanes) {
459
- await amux.deleteSession(pane.sessionId);
514
+ if (pane.content?.type === "session") {
515
+ await amux.deleteSession(pane.content.sessionId);
516
+ }
460
517
  }
461
518
  db.delete(windows).where(eq2(windows.id, input.id)).run();
462
519
  const activeRow = db.select().from(uiState).where(eq2(uiState.key, "active_window_id")).get();
@@ -492,7 +549,7 @@ function setAmuxBridge2(bridge) {
492
549
  var layoutRouter = router({
493
550
  /**
494
551
  * Split a pane horizontally or vertically.
495
- * Creates a new session in amux and a new pane in shella.
552
+ * If source pane has a session, clones it. Otherwise creates empty pane.
496
553
  */
497
554
  split: publicProcedure.input(z2.object({
498
555
  windowId: z2.string(),
@@ -506,25 +563,17 @@ var layoutRouter = router({
506
563
  if (!sourcePane) {
507
564
  throw new Error(`Pane ${input.paneId} not found`);
508
565
  }
509
- const sourceSession = await amux2.getSession(sourcePane.sessionId);
510
- if (!sourceSession) {
511
- throw new Error(`Session ${sourcePane.sessionId} not found`);
566
+ const window = db.select().from(windows).where(eq3(windows.id, input.windowId)).get();
567
+ if (!window) {
568
+ throw new Error(`Window ${input.windowId} not found`);
512
569
  }
513
- const newSession = await amux2.createSession({
514
- id: input.newSessionId,
515
- directory: sourceSession.directory,
516
- agentConfigId: sourceSession.agentConfigId
517
- });
570
+ const newContent = null;
518
571
  const newPaneId = input.newPaneId ?? randomUUID3();
519
572
  db.insert(panes).values({
520
573
  id: newPaneId,
521
574
  windowId: input.windowId,
522
- sessionId: newSession.id
575
+ content: newContent
523
576
  }).run();
524
- const window = db.select().from(windows).where(eq3(windows.id, input.windowId)).get();
525
- if (!window) {
526
- throw new Error(`Window ${input.windowId} not found`);
527
- }
528
577
  const currentLayout = window.layout;
529
578
  const newLayout = splitPane(currentLayout, input.paneId, newPaneId, input.direction);
530
579
  db.update(windows).set({
@@ -532,27 +581,17 @@ var layoutRouter = router({
532
581
  activePaneId: newPaneId,
533
582
  lastAccessedAt: /* @__PURE__ */ new Date()
534
583
  }).where(eq3(windows.id, input.windowId)).run();
535
- await amux2.startAgent(newSession.id);
536
584
  const now = /* @__PURE__ */ new Date();
537
585
  return {
538
586
  newPaneId,
539
- newSessionId: newSession.id,
587
+ newSessionId: null,
540
588
  layout: newLayout,
541
- // Match exact shape from windows.list: { ...pane, session }
542
589
  newPane: {
543
590
  id: newPaneId,
544
591
  windowId: input.windowId,
545
- sessionId: newSession.id,
592
+ content: newContent,
546
593
  createdAt: now.toISOString(),
547
- session: {
548
- id: newSession.id,
549
- directory: newSession.directory,
550
- agentConfigId: newSession.agentConfigId,
551
- model: newSession.model,
552
- mode: newSession.mode,
553
- createdAt: newSession.createdAt.toISOString(),
554
- acpSessionId: newSession.acpSessionId
555
- }
594
+ session: null
556
595
  }
557
596
  };
558
597
  }),
@@ -574,11 +613,15 @@ var layoutRouter = router({
574
613
  const currentLayout = window.layout;
575
614
  const allPaneIds = getAllPaneIds(currentLayout);
576
615
  if (allPaneIds.length === 1) {
577
- await amux2.deleteSession(pane.sessionId);
616
+ if (pane.content?.type === "session") {
617
+ await amux2.deleteSession(pane.content.sessionId);
618
+ }
578
619
  db.delete(windows).where(eq3(windows.id, input.windowId)).run();
579
620
  return { windowClosed: true, layout: null, activePaneId: null };
580
621
  }
581
- await amux2.deleteSession(pane.sessionId);
622
+ if (pane.content?.type === "session") {
623
+ await amux2.deleteSession(pane.content.sessionId);
624
+ }
582
625
  db.delete(panes).where(eq3(panes.id, input.paneId)).run();
583
626
  const newLayout = removePane(currentLayout, input.paneId);
584
627
  if (!newLayout) {
@@ -648,6 +691,78 @@ var layoutRouter = router({
648
691
  const newLayout = updateRatio(currentLayout, input.path, input.ratio);
649
692
  db.update(windows).set({ layout: newLayout }).where(eq3(windows.id, input.windowId)).run();
650
693
  return { layout: newLayout };
694
+ }),
695
+ /**
696
+ * Set the session for an empty pane (used when user picks agent/shell type).
697
+ * Creates a session in amux and updates the pane.
698
+ */
699
+ setSession: publicProcedure.input(z2.object({
700
+ paneId: z2.string(),
701
+ agentConfigId: z2.string(),
702
+ directory: z2.string().optional()
703
+ })).mutation(async ({ input }) => {
704
+ const pane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
705
+ if (!pane) {
706
+ throw new Error(`Pane ${input.paneId} not found`);
707
+ }
708
+ if (pane.content) {
709
+ throw new Error(`Pane ${input.paneId} already has content`);
710
+ }
711
+ const session = await amux2.createSession({
712
+ directory: input.directory ?? process.cwd(),
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
+ };
730
+ }),
731
+ /**
732
+ * Set browser content for an empty pane.
733
+ */
734
+ setBrowserContent: publicProcedure.input(z2.object({
735
+ paneId: z2.string(),
736
+ url: z2.string().url()
737
+ })).mutation(async ({ input }) => {
738
+ const pane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
739
+ if (!pane) {
740
+ throw new Error(`Pane ${input.paneId} not found`);
741
+ }
742
+ if (pane.content) {
743
+ throw new Error(`Pane ${input.paneId} already has content`);
744
+ }
745
+ const content = { type: "browser", url: input.url };
746
+ db.update(panes).set({ content }).where(eq3(panes.id, input.paneId)).run();
747
+ return { content };
748
+ }),
749
+ /**
750
+ * Update the URL of an existing browser pane.
751
+ */
752
+ updateBrowserUrl: publicProcedure.input(z2.object({
753
+ paneId: z2.string(),
754
+ url: z2.string().url()
755
+ })).mutation(async ({ input }) => {
756
+ const pane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
757
+ if (!pane) {
758
+ throw new Error(`Pane ${input.paneId} not found`);
759
+ }
760
+ if (pane.content?.type !== "browser") {
761
+ throw new Error(`Pane ${input.paneId} is not a browser pane`);
762
+ }
763
+ const content = { type: "browser", url: input.url };
764
+ db.update(panes).set({ content }).where(eq3(panes.id, input.paneId)).run();
765
+ return { content };
651
766
  })
652
767
  });
653
768
 
@@ -667,7 +782,10 @@ function getSessionIdForPane(paneId) {
667
782
  if (!pane) {
668
783
  throw new Error(`Pane ${paneId} not found`);
669
784
  }
670
- return pane.sessionId;
785
+ if (pane.content?.type !== "session") {
786
+ throw new Error(`Pane ${paneId} is not a session pane`);
787
+ }
788
+ return pane.content.sessionId;
671
789
  }
672
790
  var agentsRouter = router({
673
791
  /**
@@ -738,17 +856,6 @@ var agentsRouter = router({
738
856
  amux3.respondPermission(sessionId, input.requestId, input.optionId);
739
857
  return { ok: true };
740
858
  }),
741
- /**
742
- * Switch to a different agent.
743
- */
744
- switchAgent: publicProcedure.input(z3.object({
745
- paneId: z3.string(),
746
- agentConfigId: z3.string()
747
- })).mutation(async ({ input }) => {
748
- const sessionId = getSessionIdForPane(input.paneId);
749
- await amux3.switchAgent(sessionId, input.agentConfigId);
750
- return { ok: true };
751
- }),
752
859
  /**
753
860
  * Subscribe to updates for a pane's agent session.
754
861
  */
@@ -774,7 +881,10 @@ function getSessionIdForPane2(paneId) {
774
881
  if (!pane) {
775
882
  throw new Error(`Pane ${paneId} not found`);
776
883
  }
777
- return pane.sessionId;
884
+ if (pane.content?.type !== "session") {
885
+ throw new Error(`Pane ${paneId} is not a session pane`);
886
+ }
887
+ return pane.content.sessionId;
778
888
  }
779
889
  var filesRouter = router({
780
890
  /**
@@ -791,24 +901,97 @@ var filesRouter = router({
791
901
  })
792
902
  });
793
903
 
904
+ // src/trpc/terminals.ts
905
+ import { z as z5 } from "zod";
906
+ import { eq as eq6 } from "drizzle-orm";
907
+ var amux5;
908
+ function setAmuxBridge5(bridge) {
909
+ amux5 = bridge;
910
+ }
911
+ var paneInput2 = z5.object({
912
+ paneId: z5.string()
913
+ });
914
+ function getSessionIdForPane3(paneId) {
915
+ const pane = db.select().from(panes).where(eq6(panes.id, paneId)).get();
916
+ if (!pane) {
917
+ throw new Error(`Pane ${paneId} not found`);
918
+ }
919
+ if (pane.content?.type !== "session") {
920
+ throw new Error(`Pane ${paneId} is not a session pane`);
921
+ }
922
+ return pane.content.sessionId;
923
+ }
924
+ var terminalsRouter = router({
925
+ /**
926
+ * Write raw input to terminal (keystrokes from client).
927
+ */
928
+ write: publicProcedure.input(
929
+ z5.object({
930
+ paneId: z5.string(),
931
+ data: z5.string()
932
+ })
933
+ ).mutation(({ input }) => {
934
+ const sessionId = getSessionIdForPane3(input.paneId);
935
+ amux5.terminalWrite(sessionId, input.data);
936
+ return { ok: true };
937
+ }),
938
+ /**
939
+ * Resize terminal dimensions.
940
+ */
941
+ resize: publicProcedure.input(
942
+ z5.object({
943
+ paneId: z5.string(),
944
+ cols: z5.number().int().positive(),
945
+ rows: z5.number().int().positive()
946
+ })
947
+ ).mutation(({ input }) => {
948
+ const sessionId = getSessionIdForPane3(input.paneId);
949
+ amux5.terminalResize(sessionId, input.cols, input.rows);
950
+ return { ok: true };
951
+ }),
952
+ /**
953
+ * Check if a pane is a terminal session.
954
+ */
955
+ isTerminal: publicProcedure.input(paneInput2).query(({ input }) => {
956
+ const sessionId = getSessionIdForPane3(input.paneId);
957
+ return { isTerminal: amux5.isTerminalSession(sessionId) };
958
+ }),
959
+ /**
960
+ * Set terminal title (from OSC escape sequence).
961
+ */
962
+ setTitle: publicProcedure.input(z5.object({
963
+ paneId: z5.string(),
964
+ title: z5.string()
965
+ })).mutation(({ input }) => {
966
+ const sessionId = getSessionIdForPane3(input.paneId);
967
+ amux5.updateSessionTitle(sessionId, input.title);
968
+ return { ok: true };
969
+ })
970
+ });
971
+
794
972
  // src/trpc/router.ts
795
- function initializeRouters(amux5) {
796
- setAmuxBridge(amux5);
797
- setAmuxBridge2(amux5);
798
- setAmuxBridge3(amux5);
799
- setAmuxBridge4(amux5);
973
+ function initializeRouters(amux6) {
974
+ setAmuxBridge(amux6);
975
+ setAmuxBridge2(amux6);
976
+ setAmuxBridge3(amux6);
977
+ setAmuxBridge4(amux6);
978
+ setAmuxBridge5(amux6);
800
979
  }
801
980
  var shellaRouter = router({
802
981
  windows: windowsRouter,
803
982
  layout: layoutRouter,
804
983
  agents: agentsRouter,
805
- files: filesRouter
984
+ files: filesRouter,
985
+ terminals: terminalsRouter
806
986
  });
807
987
 
808
988
  // src/index.ts
809
989
  var __dirname = path2.dirname(fileURLToPath(import.meta.url));
810
990
  function createShellaServer(options = {}) {
811
991
  const port = options.port ?? 3067;
992
+ if (options.verbose) {
993
+ setVerbose(true);
994
+ }
812
995
  const amuxBridge = createAmuxBridge();
813
996
  initializeRouters(amuxBridge);
814
997
  const app = express();
@@ -825,17 +1008,24 @@ function createShellaServer(options = {}) {
825
1008
  applyWSSHandler({ wss, router: shellaRouter });
826
1009
  return {
827
1010
  start: () => {
828
- return new Promise((resolve) => {
1011
+ return new Promise((resolve, reject) => {
1012
+ server.once("error", (err) => {
1013
+ if (err.code === "EADDRINUSE") {
1014
+ reject(new Error(`Port ${port} is already in use. Kill the other process or use a different port.`));
1015
+ } else {
1016
+ reject(err);
1017
+ }
1018
+ });
829
1019
  server.listen(port, () => {
830
1020
  const url = `http://localhost:${port}`;
831
- console.log(`[shella] Running on ${url}`);
1021
+ debug("shella", `Running on ${url}`);
832
1022
  options.onReady?.(url);
833
1023
  resolve();
834
1024
  });
835
1025
  });
836
1026
  },
837
1027
  stop: async () => {
838
- console.log("[shella] Shutting down...");
1028
+ debug("shella", "Shutting down...");
839
1029
  wss.close();
840
1030
  await agentManager2.stopAll();
841
1031
  server.close();