@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/bin/cli.js CHANGED
@@ -20,6 +20,7 @@ import { fileURLToPath } from "url";
20
20
  import { createExpressMiddleware } from "@trpc/server/adapters/express";
21
21
  import { applyWSSHandler } from "@trpc/server/adapters/ws";
22
22
  import { agentManager as agentManager2 } from "@bytespell/amux/agents/manager";
23
+ import { setVerbose, debug } from "@bytespell/amux/lib/logger";
23
24
  import "@bytespell/amux/db";
24
25
 
25
26
  // src/db/index.ts
@@ -80,8 +81,8 @@ var windows = sqliteTable("windows", {
80
81
  var panes = sqliteTable("panes", {
81
82
  id: text("id").primaryKey(),
82
83
  windowId: text("window_id").notNull().references(() => windows.id, { onDelete: "cascade" }),
83
- /** References amux session ID (cross-database) */
84
- sessionId: text("session_id").notNull(),
84
+ /** Content configuration. Null when pane type not yet selected. */
85
+ content: text("content", { mode: "json" }).$type(),
85
86
  createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
86
87
  });
87
88
  var uiState = sqliteTable("ui_state", {
@@ -109,7 +110,7 @@ sqlite.exec(`
109
110
  CREATE TABLE IF NOT EXISTS panes (
110
111
  id TEXT PRIMARY KEY,
111
112
  window_id TEXT NOT NULL REFERENCES windows(id) ON DELETE CASCADE,
112
- session_id TEXT NOT NULL,
113
+ content TEXT,
113
114
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
114
115
  );
115
116
 
@@ -118,6 +119,46 @@ sqlite.exec(`
118
119
  value TEXT NOT NULL
119
120
  );
120
121
  `);
122
+ try {
123
+ const tableInfo = sqlite.prepare("PRAGMA table_info(panes)").all();
124
+ const hasSessionId = tableInfo.some((col) => col.name === "session_id");
125
+ const hasContent = tableInfo.some((col) => col.name === "content");
126
+ if (hasSessionId && !hasContent) {
127
+ sqlite.exec(`
128
+ -- Disable foreign keys temporarily
129
+ PRAGMA foreign_keys = OFF;
130
+
131
+ -- Create new table with content column
132
+ CREATE TABLE panes_new (
133
+ id TEXT PRIMARY KEY,
134
+ window_id TEXT NOT NULL REFERENCES windows(id) ON DELETE CASCADE,
135
+ content TEXT,
136
+ created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
137
+ );
138
+
139
+ -- Copy data, converting session_id to content JSON
140
+ INSERT INTO panes_new (id, window_id, content, created_at)
141
+ SELECT
142
+ id,
143
+ window_id,
144
+ CASE
145
+ WHEN session_id IS NOT NULL THEN json_object('type', 'session', 'sessionId', session_id)
146
+ ELSE NULL
147
+ END,
148
+ created_at
149
+ FROM panes;
150
+
151
+ -- Drop old table and rename new
152
+ DROP TABLE panes;
153
+ ALTER TABLE panes_new RENAME TO panes;
154
+
155
+ -- Re-enable foreign keys
156
+ PRAGMA foreign_keys = ON;
157
+ `);
158
+ console.log("[shella-db] Migrated panes from session_id to content column");
159
+ }
160
+ } catch {
161
+ }
121
162
 
122
163
  // src/amuxBridge.ts
123
164
  import { randomUUID } from "crypto";
@@ -141,8 +182,8 @@ function createAmuxBridge() {
141
182
  if (!configId) {
142
183
  const lastUsedRow = db2.select().from(appState).where(eq(appState.key, "last_used_agent_config_id")).get();
143
184
  if (lastUsedRow?.value) {
144
- const config = db2.select().from(agentConfigs).where(eq(agentConfigs.id, lastUsedRow.value)).get();
145
- if (config) configId = config.id;
185
+ const config2 = db2.select().from(agentConfigs).where(eq(agentConfigs.id, lastUsedRow.value)).get();
186
+ if (config2) configId = config2.id;
146
187
  }
147
188
  if (!configId) {
148
189
  const firstConfig = db2.select().from(agentConfigs).get();
@@ -156,11 +197,14 @@ function createAmuxBridge() {
156
197
  }
157
198
  const id = params.id ?? randomUUID();
158
199
  const now = /* @__PURE__ */ new Date();
200
+ const config = db2.select().from(agentConfigs).where(eq(agentConfigs.id, configId)).get();
201
+ const initialTitle = config?.streamType === "pty" ? params.directory.split("/").pop() || null : null;
159
202
  db2.insert(sessions).values({
160
203
  id,
161
204
  directory: params.directory,
162
205
  agentConfigId: configId,
163
206
  acpSessionId: null,
207
+ title: initialTitle,
164
208
  model: null,
165
209
  mode: null,
166
210
  createdAt: now
@@ -196,17 +240,6 @@ function createAmuxBridge() {
196
240
  respondPermission(sessionId, requestId, optionId) {
197
241
  agentManager.respondPermission(sessionId, requestId, optionId);
198
242
  },
199
- async switchAgent(sessionId, agentConfigId) {
200
- await agentManager.stopForSession(sessionId);
201
- clearEventsForSession(sessionId);
202
- db2.update(sessions).set({
203
- agentConfigId,
204
- acpSessionId: null,
205
- model: null,
206
- mode: null
207
- }).where(eq(sessions.id, sessionId)).run();
208
- db2.insert(appState).values({ key: "last_used_agent_config_id", value: agentConfigId }).onConflictDoUpdate({ target: appState.key, set: { value: agentConfigId } }).run();
209
- },
210
243
  subscribeToSession(sessionId, callback) {
211
244
  const handler = (event) => {
212
245
  if (event.sessionId === sessionId) {
@@ -220,6 +253,22 @@ function createAmuxBridge() {
220
253
  const session = db2.select().from(sessions).where(eq(sessions.id, sessionId)).get();
221
254
  if (!session) throw new Error(`Session ${sessionId} not found`);
222
255
  return listFilesForAutocomplete(session.directory, partialPath, limit);
256
+ },
257
+ // Terminal operations
258
+ terminalWrite(sessionId, data) {
259
+ agentManager.terminalWrite(sessionId, data);
260
+ },
261
+ terminalResize(sessionId, cols, rows) {
262
+ agentManager.terminalResize(sessionId, cols, rows);
263
+ },
264
+ getTerminalScrollback(sessionId) {
265
+ return agentManager.getTerminalScrollback(sessionId);
266
+ },
267
+ isTerminalSession(sessionId) {
268
+ return agentManager.isTerminalSession(sessionId);
269
+ },
270
+ updateSessionTitle(sessionId, title) {
271
+ db2.update(sessions).set({ title }).where(eq(sessions.id, sessionId)).run();
223
272
  }
224
273
  };
225
274
  }
@@ -363,7 +412,9 @@ var windowsRouter = router({
363
412
  const allWindows = db.select().from(windows).orderBy(windows.createdAt).all();
364
413
  const allPanes = db.select().from(panes).all();
365
414
  const activeRow = db.select().from(uiState).where(eq2(uiState.key, "active_window_id")).get();
366
- const sessionIds = [...new Set(allPanes.map((p2) => p2.sessionId))];
415
+ const sessionIds = [...new Set(
416
+ allPanes.map((p2) => p2.content?.type === "session" ? p2.content.sessionId : null).filter((id) => id !== null)
417
+ )];
367
418
  const sessionsData = await amux.getSessions(sessionIds);
368
419
  const sessionMap = new Map(sessionsData.map((s) => [s.id, s]));
369
420
  const agentConfigs2 = await amux.getAgentConfigs();
@@ -372,7 +423,8 @@ var windowsRouter = router({
372
423
  ...w,
373
424
  panes: allPanes.filter((p2) => p2.windowId === w.id).map((p2) => ({
374
425
  ...p2,
375
- session: sessionMap.get(p2.sessionId) ?? null
426
+ // Enrich session-type content with full session data
427
+ session: p2.content?.type === "session" ? sessionMap.get(p2.content.sessionId) ?? null : null
376
428
  }))
377
429
  })),
378
430
  activeWindowId: activeRow?.value ?? allWindows[allWindows.length - 1]?.id ?? null,
@@ -388,19 +440,20 @@ var windowsRouter = router({
388
440
  throw new Error(`Window ${input.id} not found`);
389
441
  }
390
442
  const windowPanes = db.select().from(panes).where(eq2(panes.windowId, input.id)).all();
391
- const sessionIds = windowPanes.map((p2) => p2.sessionId);
443
+ const sessionIds = windowPanes.map((p2) => p2.content?.type === "session" ? p2.content.sessionId : null).filter((id) => id !== null);
392
444
  const sessionsData = await amux.getSessions(sessionIds);
393
445
  const sessionMap = new Map(sessionsData.map((s) => [s.id, s]));
394
446
  return {
395
447
  ...window,
396
448
  panes: windowPanes.map((p2) => ({
397
449
  ...p2,
398
- session: sessionMap.get(p2.sessionId) ?? null
450
+ session: p2.content?.type === "session" ? sessionMap.get(p2.content.sessionId) ?? null : null
399
451
  }))
400
452
  };
401
453
  }),
402
454
  /**
403
455
  * Create a new window with a single pane.
456
+ * If agentConfigId is not provided, pane is created with no content (shows picker).
404
457
  */
405
458
  create: publicProcedure.input(z.object({
406
459
  id: z.string().optional(),
@@ -409,10 +462,13 @@ var windowsRouter = router({
409
462
  agentConfigId: z.string().optional()
410
463
  })).mutation(async ({ input }) => {
411
464
  const directory = input.directory ?? process.cwd();
412
- const session = await amux.createSession({
413
- directory,
414
- agentConfigId: input.agentConfigId
415
- });
465
+ let session = null;
466
+ if (input.agentConfigId) {
467
+ session = await amux.createSession({
468
+ directory,
469
+ agentConfigId: input.agentConfigId
470
+ });
471
+ }
416
472
  const windowId = input.id ?? randomUUID2();
417
473
  const paneId = randomUUID2();
418
474
  const existingCount = db.select().from(windows).all().length;
@@ -421,14 +477,13 @@ var windowsRouter = router({
421
477
  id: windowId,
422
478
  title: input.title || generateTitle(existingCount, directory),
423
479
  hasCustomTitle: !!input.title,
424
- // Only true if title is non-empty string
425
480
  layout,
426
481
  activePaneId: paneId
427
482
  }).run();
428
483
  db.insert(panes).values({
429
484
  id: paneId,
430
485
  windowId,
431
- sessionId: session.id
486
+ content: session ? { type: "session", sessionId: session.id } : null
432
487
  }).run();
433
488
  db.insert(uiState).values({ key: "active_window_id", value: windowId }).onConflictDoUpdate({ target: uiState.key, set: { value: windowId } }).run();
434
489
  const newWindow = db.select().from(windows).where(eq2(windows.id, windowId)).get();
@@ -437,7 +492,7 @@ var windowsRouter = router({
437
492
  ...newWindow,
438
493
  panes: newPanes.map((p2) => ({
439
494
  ...p2,
440
- session: p2.sessionId === session.id ? session : null
495
+ session: session && p2.content?.type === "session" && p2.content.sessionId === session.id ? session : null
441
496
  }))
442
497
  };
443
498
  }),
@@ -463,7 +518,9 @@ var windowsRouter = router({
463
518
  delete: publicProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => {
464
519
  const windowPanes = db.select().from(panes).where(eq2(panes.windowId, input.id)).all();
465
520
  for (const pane of windowPanes) {
466
- await amux.deleteSession(pane.sessionId);
521
+ if (pane.content?.type === "session") {
522
+ await amux.deleteSession(pane.content.sessionId);
523
+ }
467
524
  }
468
525
  db.delete(windows).where(eq2(windows.id, input.id)).run();
469
526
  const activeRow = db.select().from(uiState).where(eq2(uiState.key, "active_window_id")).get();
@@ -499,7 +556,7 @@ function setAmuxBridge2(bridge) {
499
556
  var layoutRouter = router({
500
557
  /**
501
558
  * Split a pane horizontally or vertically.
502
- * Creates a new session in amux and a new pane in shella.
559
+ * If source pane has a session, clones it. Otherwise creates empty pane.
503
560
  */
504
561
  split: publicProcedure.input(z2.object({
505
562
  windowId: z2.string(),
@@ -513,25 +570,17 @@ var layoutRouter = router({
513
570
  if (!sourcePane) {
514
571
  throw new Error(`Pane ${input.paneId} not found`);
515
572
  }
516
- const sourceSession = await amux2.getSession(sourcePane.sessionId);
517
- if (!sourceSession) {
518
- throw new Error(`Session ${sourcePane.sessionId} not found`);
573
+ const window = db.select().from(windows).where(eq3(windows.id, input.windowId)).get();
574
+ if (!window) {
575
+ throw new Error(`Window ${input.windowId} not found`);
519
576
  }
520
- const newSession = await amux2.createSession({
521
- id: input.newSessionId,
522
- directory: sourceSession.directory,
523
- agentConfigId: sourceSession.agentConfigId
524
- });
577
+ const newContent = null;
525
578
  const newPaneId = input.newPaneId ?? randomUUID3();
526
579
  db.insert(panes).values({
527
580
  id: newPaneId,
528
581
  windowId: input.windowId,
529
- sessionId: newSession.id
582
+ content: newContent
530
583
  }).run();
531
- const window = db.select().from(windows).where(eq3(windows.id, input.windowId)).get();
532
- if (!window) {
533
- throw new Error(`Window ${input.windowId} not found`);
534
- }
535
584
  const currentLayout = window.layout;
536
585
  const newLayout = splitPane(currentLayout, input.paneId, newPaneId, input.direction);
537
586
  db.update(windows).set({
@@ -539,27 +588,17 @@ var layoutRouter = router({
539
588
  activePaneId: newPaneId,
540
589
  lastAccessedAt: /* @__PURE__ */ new Date()
541
590
  }).where(eq3(windows.id, input.windowId)).run();
542
- await amux2.startAgent(newSession.id);
543
591
  const now = /* @__PURE__ */ new Date();
544
592
  return {
545
593
  newPaneId,
546
- newSessionId: newSession.id,
594
+ newSessionId: null,
547
595
  layout: newLayout,
548
- // Match exact shape from windows.list: { ...pane, session }
549
596
  newPane: {
550
597
  id: newPaneId,
551
598
  windowId: input.windowId,
552
- sessionId: newSession.id,
599
+ content: newContent,
553
600
  createdAt: now.toISOString(),
554
- session: {
555
- id: newSession.id,
556
- directory: newSession.directory,
557
- agentConfigId: newSession.agentConfigId,
558
- model: newSession.model,
559
- mode: newSession.mode,
560
- createdAt: newSession.createdAt.toISOString(),
561
- acpSessionId: newSession.acpSessionId
562
- }
601
+ session: null
563
602
  }
564
603
  };
565
604
  }),
@@ -581,11 +620,15 @@ var layoutRouter = router({
581
620
  const currentLayout = window.layout;
582
621
  const allPaneIds = getAllPaneIds(currentLayout);
583
622
  if (allPaneIds.length === 1) {
584
- await amux2.deleteSession(pane.sessionId);
623
+ if (pane.content?.type === "session") {
624
+ await amux2.deleteSession(pane.content.sessionId);
625
+ }
585
626
  db.delete(windows).where(eq3(windows.id, input.windowId)).run();
586
627
  return { windowClosed: true, layout: null, activePaneId: null };
587
628
  }
588
- await amux2.deleteSession(pane.sessionId);
629
+ if (pane.content?.type === "session") {
630
+ await amux2.deleteSession(pane.content.sessionId);
631
+ }
589
632
  db.delete(panes).where(eq3(panes.id, input.paneId)).run();
590
633
  const newLayout = removePane(currentLayout, input.paneId);
591
634
  if (!newLayout) {
@@ -655,6 +698,78 @@ var layoutRouter = router({
655
698
  const newLayout = updateRatio(currentLayout, input.path, input.ratio);
656
699
  db.update(windows).set({ layout: newLayout }).where(eq3(windows.id, input.windowId)).run();
657
700
  return { layout: newLayout };
701
+ }),
702
+ /**
703
+ * Set the session for an empty pane (used when user picks agent/shell type).
704
+ * Creates a session in amux and updates the pane.
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.
740
+ */
741
+ setBrowserContent: publicProcedure.input(z2.object({
742
+ paneId: z2.string(),
743
+ url: z2.string().url()
744
+ })).mutation(async ({ input }) => {
745
+ const pane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
746
+ if (!pane) {
747
+ throw new Error(`Pane ${input.paneId} not found`);
748
+ }
749
+ if (pane.content) {
750
+ throw new Error(`Pane ${input.paneId} already has content`);
751
+ }
752
+ const content = { type: "browser", url: input.url };
753
+ db.update(panes).set({ content }).where(eq3(panes.id, input.paneId)).run();
754
+ return { content };
755
+ }),
756
+ /**
757
+ * Update the URL of an existing browser pane.
758
+ */
759
+ updateBrowserUrl: publicProcedure.input(z2.object({
760
+ paneId: z2.string(),
761
+ url: z2.string().url()
762
+ })).mutation(async ({ input }) => {
763
+ const pane = db.select().from(panes).where(eq3(panes.id, input.paneId)).get();
764
+ if (!pane) {
765
+ throw new Error(`Pane ${input.paneId} not found`);
766
+ }
767
+ if (pane.content?.type !== "browser") {
768
+ throw new Error(`Pane ${input.paneId} is not a browser pane`);
769
+ }
770
+ const content = { type: "browser", url: input.url };
771
+ db.update(panes).set({ content }).where(eq3(panes.id, input.paneId)).run();
772
+ return { content };
658
773
  })
659
774
  });
660
775
 
@@ -674,7 +789,10 @@ function getSessionIdForPane(paneId) {
674
789
  if (!pane) {
675
790
  throw new Error(`Pane ${paneId} not found`);
676
791
  }
677
- return pane.sessionId;
792
+ if (pane.content?.type !== "session") {
793
+ throw new Error(`Pane ${paneId} is not a session pane`);
794
+ }
795
+ return pane.content.sessionId;
678
796
  }
679
797
  var agentsRouter = router({
680
798
  /**
@@ -745,17 +863,6 @@ var agentsRouter = router({
745
863
  amux3.respondPermission(sessionId, input.requestId, input.optionId);
746
864
  return { ok: true };
747
865
  }),
748
- /**
749
- * Switch to a different agent.
750
- */
751
- switchAgent: publicProcedure.input(z3.object({
752
- paneId: z3.string(),
753
- agentConfigId: z3.string()
754
- })).mutation(async ({ input }) => {
755
- const sessionId = getSessionIdForPane(input.paneId);
756
- await amux3.switchAgent(sessionId, input.agentConfigId);
757
- return { ok: true };
758
- }),
759
866
  /**
760
867
  * Subscribe to updates for a pane's agent session.
761
868
  */
@@ -781,7 +888,10 @@ function getSessionIdForPane2(paneId) {
781
888
  if (!pane) {
782
889
  throw new Error(`Pane ${paneId} not found`);
783
890
  }
784
- return pane.sessionId;
891
+ if (pane.content?.type !== "session") {
892
+ throw new Error(`Pane ${paneId} is not a session pane`);
893
+ }
894
+ return pane.content.sessionId;
785
895
  }
786
896
  var filesRouter = router({
787
897
  /**
@@ -798,24 +908,97 @@ var filesRouter = router({
798
908
  })
799
909
  });
800
910
 
911
+ // src/trpc/terminals.ts
912
+ import { z as z5 } from "zod";
913
+ import { eq as eq6 } from "drizzle-orm";
914
+ var amux5;
915
+ function setAmuxBridge5(bridge) {
916
+ amux5 = bridge;
917
+ }
918
+ var paneInput2 = z5.object({
919
+ paneId: z5.string()
920
+ });
921
+ function getSessionIdForPane3(paneId) {
922
+ const pane = db.select().from(panes).where(eq6(panes.id, paneId)).get();
923
+ if (!pane) {
924
+ throw new Error(`Pane ${paneId} not found`);
925
+ }
926
+ if (pane.content?.type !== "session") {
927
+ throw new Error(`Pane ${paneId} is not a session pane`);
928
+ }
929
+ return pane.content.sessionId;
930
+ }
931
+ var terminalsRouter = router({
932
+ /**
933
+ * Write raw input to terminal (keystrokes from client).
934
+ */
935
+ write: publicProcedure.input(
936
+ z5.object({
937
+ paneId: z5.string(),
938
+ data: z5.string()
939
+ })
940
+ ).mutation(({ input }) => {
941
+ const sessionId = getSessionIdForPane3(input.paneId);
942
+ amux5.terminalWrite(sessionId, input.data);
943
+ return { ok: true };
944
+ }),
945
+ /**
946
+ * Resize terminal dimensions.
947
+ */
948
+ resize: publicProcedure.input(
949
+ z5.object({
950
+ paneId: z5.string(),
951
+ cols: z5.number().int().positive(),
952
+ rows: z5.number().int().positive()
953
+ })
954
+ ).mutation(({ input }) => {
955
+ const sessionId = getSessionIdForPane3(input.paneId);
956
+ amux5.terminalResize(sessionId, input.cols, input.rows);
957
+ return { ok: true };
958
+ }),
959
+ /**
960
+ * Check if a pane is a terminal session.
961
+ */
962
+ isTerminal: publicProcedure.input(paneInput2).query(({ input }) => {
963
+ const sessionId = getSessionIdForPane3(input.paneId);
964
+ return { isTerminal: amux5.isTerminalSession(sessionId) };
965
+ }),
966
+ /**
967
+ * Set terminal title (from OSC escape sequence).
968
+ */
969
+ setTitle: publicProcedure.input(z5.object({
970
+ paneId: z5.string(),
971
+ title: z5.string()
972
+ })).mutation(({ input }) => {
973
+ const sessionId = getSessionIdForPane3(input.paneId);
974
+ amux5.updateSessionTitle(sessionId, input.title);
975
+ return { ok: true };
976
+ })
977
+ });
978
+
801
979
  // src/trpc/router.ts
802
- function initializeRouters(amux5) {
803
- setAmuxBridge(amux5);
804
- setAmuxBridge2(amux5);
805
- setAmuxBridge3(amux5);
806
- setAmuxBridge4(amux5);
980
+ function initializeRouters(amux6) {
981
+ setAmuxBridge(amux6);
982
+ setAmuxBridge2(amux6);
983
+ setAmuxBridge3(amux6);
984
+ setAmuxBridge4(amux6);
985
+ setAmuxBridge5(amux6);
807
986
  }
808
987
  var shellaRouter = router({
809
988
  windows: windowsRouter,
810
989
  layout: layoutRouter,
811
990
  agents: agentsRouter,
812
- files: filesRouter
991
+ files: filesRouter,
992
+ terminals: terminalsRouter
813
993
  });
814
994
 
815
995
  // src/index.ts
816
996
  var __dirname = path2.dirname(fileURLToPath(import.meta.url));
817
997
  function createShellaServer(options = {}) {
818
998
  const port = options.port ?? 3067;
999
+ if (options.verbose) {
1000
+ setVerbose(true);
1001
+ }
819
1002
  const amuxBridge = createAmuxBridge();
820
1003
  initializeRouters(amuxBridge);
821
1004
  const app = express();
@@ -832,17 +1015,24 @@ function createShellaServer(options = {}) {
832
1015
  applyWSSHandler({ wss, router: shellaRouter });
833
1016
  return {
834
1017
  start: () => {
835
- return new Promise((resolve) => {
1018
+ return new Promise((resolve, reject) => {
1019
+ server.once("error", (err) => {
1020
+ if (err.code === "EADDRINUSE") {
1021
+ reject(new Error(`Port ${port} is already in use. Kill the other process or use a different port.`));
1022
+ } else {
1023
+ reject(err);
1024
+ }
1025
+ });
836
1026
  server.listen(port, () => {
837
1027
  const url = `http://localhost:${port}`;
838
- console.log(`[shella] Running on ${url}`);
1028
+ debug("shella", `Running on ${url}`);
839
1029
  options.onReady?.(url);
840
1030
  resolve();
841
1031
  });
842
1032
  });
843
1033
  },
844
1034
  stop: async () => {
845
- console.log("[shella] Shutting down...");
1035
+ debug("shella", "Shutting down...");
846
1036
  wss.close();
847
1037
  await agentManager2.stopAll();
848
1038
  server.close();
@@ -883,6 +1073,7 @@ async function startCommand(options) {
883
1073
  else log2("Starting server...");
884
1074
  const shella = createShellaServer({
885
1075
  port,
1076
+ verbose,
886
1077
  onReady: (url) => {
887
1078
  if (s) s.stop("Ready");
888
1079
  const lanIp = getLanIp();
@@ -905,7 +1096,13 @@ ${dim("press ctrl+c to stop")}
905
1096
  }
906
1097
  }
907
1098
  });
908
- await shella.start();
1099
+ try {
1100
+ await shella.start();
1101
+ } catch (err) {
1102
+ if (s) s.stop("Failed");
1103
+ p.log.error(err instanceof Error ? err.message : String(err));
1104
+ process.exit(1);
1105
+ }
909
1106
  const shutdown = async () => {
910
1107
  console.log("");
911
1108
  console.log(dim("stopping..."));