turtlecode 0.1.0 → 0.1.3

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 (127) hide show
  1. package/assets/assets/{_basePickBy-B_7ShcHh.js → _basePickBy-BcOvpC5b.js} +1 -1
  2. package/assets/assets/{_baseUniq-BsW7XEuT.js → _baseUniq-D9AKNI-S.js} +1 -1
  3. package/assets/assets/{arc-BbW57NUb.js → arc-ClcBVsDq.js} +1 -1
  4. package/assets/assets/architectureDiagram-2XIMDMQ5-CRV28Qi0.js +36 -0
  5. package/assets/assets/{auth-callback-C2n64Qcj.js → auth-callback-D7CK9pHp.js} +1 -1
  6. package/assets/assets/{blockDiagram-WCTKOSBZ-C1Ni5oSK.js → blockDiagram-WCTKOSBZ-CL-sI8NU.js} +2 -2
  7. package/assets/assets/{c4Diagram-IC4MRINW-g8EmnUUA.js → c4Diagram-IC4MRINW-AFG5p1yt.js} +2 -2
  8. package/assets/assets/channel-BXqEQVGi.js +1 -0
  9. package/assets/assets/{chunk-4BX2VUAB-CGR6NDgJ.js → chunk-4BX2VUAB-CgNy2Qqu.js} +1 -1
  10. package/assets/assets/chunk-55IACEB6-Bsz0SHtO.js +1 -0
  11. package/assets/assets/{chunk-FMBD7UC4-CBSUaebz.js → chunk-FMBD7UC4-BRuwu34q.js} +1 -1
  12. package/assets/assets/chunk-JSJVCQXG-Bt0GkrjS.js +1 -0
  13. package/assets/assets/{chunk-KX2RTZJC-D_r6FHlr.js → chunk-KX2RTZJC-BiUvn-D7.js} +1 -1
  14. package/assets/assets/{chunk-NQ4KR5QH-_-NyPl-E.js → chunk-NQ4KR5QH-DH4YUQE9.js} +1 -1
  15. package/assets/assets/{chunk-QZHKN3VN-CMDZeFvi.js → chunk-QZHKN3VN-CrLB363X.js} +1 -1
  16. package/assets/assets/chunk-VVM5DH6Z-D2vG_ZPx.js +1 -0
  17. package/assets/assets/{chunk-WL4C6EOR-p1mj0O6S.js → chunk-WL4C6EOR-913dgDB-.js} +1 -1
  18. package/assets/assets/classDiagram-VBA2DB6C-gcpKg8SN.js +1 -0
  19. package/assets/assets/classDiagram-v2-RAHNMMFH-gcpKg8SN.js +1 -0
  20. package/assets/assets/clone-oMsF326O.js +1 -0
  21. package/assets/assets/{connection-popover-Sip4k8lc.js → connection-popover-D2MEtT6z.js} +1 -1
  22. package/assets/assets/{context-popover-jX8NoD9w.js → context-popover-C1ZDT4aK.js} +1 -1
  23. package/assets/assets/cose-bilkent-S5V4N54A-DRWnwV5H.js +1 -0
  24. package/assets/assets/{cssMode-xHiM5mcb.js → cssMode-DX-s2EC8.js} +2 -2
  25. package/assets/assets/dagre-KLK3FWXG-CN4ule0l.js +4 -0
  26. package/assets/assets/diagram-E7M64L7V-docJyJu4.js +24 -0
  27. package/assets/assets/diagram-IFDJBPK2-ChVpZldk.js +43 -0
  28. package/assets/assets/diagram-P4PSJMXO-Bf1-kRnd.js +24 -0
  29. package/assets/assets/dialog-clone-project-B1wF7jYQ.js +1 -0
  30. package/assets/assets/{dialog-connect-provider-BEqcXIot.js → dialog-connect-provider-Ot6Qr5dL.js} +2 -2
  31. package/assets/assets/dialog-create-project-BUc4DxX6.js +1 -0
  32. package/assets/assets/{dialog-edit-project-BfeaBlLZ.js → dialog-edit-project-BrkjzrGe.js} +1 -1
  33. package/assets/assets/{dialog-fork-D0oj4IQ5.js → dialog-fork-CyeKjf0e.js} +1 -1
  34. package/assets/assets/{dialog-manage-models-mDJUOPob.js → dialog-manage-models-CoiwPdu-.js} +1 -1
  35. package/assets/assets/{dialog-select-directory-BocFiQ9d.js → dialog-select-directory-K0nOY5Ky.js} +3 -3
  36. package/assets/assets/{dialog-select-file-CifFpv3x.js → dialog-select-file-Dx0Bny2E.js} +1 -1
  37. package/assets/assets/{dialog-select-mcp-Bz0DoDof.js → dialog-select-mcp-ehJzhdpz.js} +1 -1
  38. package/assets/assets/{dialog-select-model-unpaid-Da_CQzO4.js → dialog-select-model-unpaid-IVl97Sa_.js} +2 -2
  39. package/assets/assets/{dialog-select-provider-2iOfyWxy.js → dialog-select-provider-CBq6hNDx.js} +1 -1
  40. package/assets/assets/{dialog-select-server-DBhmJ2f8.js → dialog-select-server-DA5hATxx.js} +1 -1
  41. package/assets/assets/{dialog-settings-gGxknIbS.js → dialog-settings-BoOPbXZp.js} +1 -1
  42. package/assets/assets/{erDiagram-INFDFZHY-7L6v8ikj.js → erDiagram-INFDFZHY-kDj6gvOH.js} +2 -2
  43. package/assets/assets/{file-icon-CgbvTpIc.js → file-icon-DVEMs7Sx.js} +1 -1
  44. package/assets/assets/{flowDiagram-PKNHOUZH-aJT2T3cx.js → flowDiagram-PKNHOUZH-CsQeJUub.js} +2 -2
  45. package/assets/assets/{freemarker2-BgcPHQt-.js → freemarker2-B_-gEFnj.js} +2 -2
  46. package/assets/assets/ganttDiagram-A5KZAMGK-D1W7NciY.js +292 -0
  47. package/assets/assets/{ghostty-web-CyXMoQwm.js → ghostty-web-BQl0ls6m.js} +1 -1
  48. package/assets/assets/gitGraphDiagram-K3NZZRJ6-D4eeLV-l.js +65 -0
  49. package/assets/assets/{graph-CFcaDaoV.js → graph-KGuLNbW2.js} +1 -1
  50. package/assets/assets/{handlebars-DSpGDIPh.js → handlebars-DlnQtQmF.js} +2 -2
  51. package/assets/assets/{home-6Va-8zmR.js → home-BnHPimua.js} +1 -1
  52. package/assets/assets/{html-CNWh6Ln_.js → html-DrjCwvQg.js} +2 -2
  53. package/assets/assets/{htmlMode-LcU-TZaJ.js → htmlMode-C1xX0YwN.js} +3 -3
  54. package/assets/assets/index-DKwABhnu.js +2584 -0
  55. package/assets/assets/{index-DpJDxStT.css → index-DTxAHLat.css} +1 -1
  56. package/assets/assets/infoDiagram-LFFYTUFH-DsQ9EVx0.js +2 -0
  57. package/assets/assets/{ishikawaDiagram-PHBUUO56-y62OJXdI.js → ishikawaDiagram-PHBUUO56-MzetUfFK.js} +6 -6
  58. package/assets/assets/{javascript-CTmFPESg.js → javascript-CBQQIA5q.js} +2 -2
  59. package/assets/assets/{jobs-popover-DRbIcR2O.js → jobs-popover-DQmVz-e0.js} +1 -1
  60. package/assets/assets/{journeyDiagram-4ABVD52K-Bi9XcKs5.js → journeyDiagram-4ABVD52K-BkKgSN_9.js} +2 -2
  61. package/assets/assets/{jsonMode-CW1XLtuQ.js → jsonMode-B2Ye6w7y.js} +3 -3
  62. package/assets/assets/{kanban-definition-K7BYSVSG-DE01LQ5I.js → kanban-definition-K7BYSVSG-DIJz6AsI.js} +4 -4
  63. package/assets/assets/{layout-C6rurAvx.js → layout-BZKpv2Gr.js} +1 -1
  64. package/assets/assets/linear-COY9pyF4.js +1 -0
  65. package/assets/assets/{liquid-DB5uFXiY.js → liquid-CQXVmj19.js} +2 -2
  66. package/assets/assets/{list-BPk4NMWt.js → list-DPJi0fzr.js} +1 -1
  67. package/assets/assets/{mdx-Q7_EfF7H.js → mdx-QuCI1OJC.js} +2 -2
  68. package/assets/assets/{mermaid.core-DdXWHS-1.js → mermaid.core-bhqKFRxj.js} +10 -10
  69. package/assets/assets/{mindmap-definition-YRQLILUH-BajbwusW.js → mindmap-definition-YRQLILUH-CVv44i-B.js} +2 -2
  70. package/assets/assets/pieDiagram-SKSYHLDU-2ekrtn6z.js +30 -0
  71. package/assets/assets/{python-B3qjyNJj.js → python-Bx2b-xP4.js} +2 -2
  72. package/assets/assets/{quadrantDiagram-337W2JSQ-CZMB9UKV.js → quadrantDiagram-337W2JSQ-BoTofVsV.js} +2 -2
  73. package/assets/assets/{razor-CJkx1qh1.js → razor-2zgukQRj.js} +2 -2
  74. package/assets/assets/{requirementDiagram-Z7DCOOCP-BlyteLn_.js → requirementDiagram-Z7DCOOCP-LSodooxy.js} +2 -2
  75. package/assets/assets/sankeyDiagram-WA2Y5GQK-DUXSSMar.js +10 -0
  76. package/assets/assets/{select-CEKfrTm2.js → select-BbwOgh1Q.js} +1 -1
  77. package/assets/assets/{sequenceDiagram-2WXFIKYE-B2cOVhRx.js → sequenceDiagram-2WXFIKYE-C01qZdHR.js} +11 -11
  78. package/assets/assets/{server-row-BhbnbNJy.js → server-row-mRVGCgvL.js} +1 -1
  79. package/assets/assets/session-BXKBDkSI.js +34 -0
  80. package/assets/assets/stateDiagram-RAJIS63D-BuPj1Py4.js +1 -0
  81. package/assets/assets/stateDiagram-v2-FVOUBMTO-CjYACBI3.js +1 -0
  82. package/assets/assets/{status-popover-body-DkLpfJ2k.js → status-popover-body-9YRn8z8W.js} +2 -2
  83. package/assets/assets/{switch-DPq0vsit.js → switch-CHySmW8J.js} +1 -1
  84. package/assets/assets/{tabs-dF68ik4J.js → tabs-CKNlwTKd.js} +1 -1
  85. package/assets/assets/{tag--BCZ5q7_.js → tag-C4xikCZp.js} +1 -1
  86. package/assets/assets/{timeline-definition-YZTLITO2-C18tfizI.js → timeline-definition-YZTLITO2-CPcDqVYw.js} +2 -2
  87. package/assets/assets/transform-BwXaE9hv.js +1 -0
  88. package/assets/assets/{treemap-KZPCXAKY-C7fWdgj-.js → treemap-KZPCXAKY-Bin819KW.js} +1 -1
  89. package/assets/assets/{trellis-6YKi26t8.js → trellis-Csm7WAqm.js} +217 -231
  90. package/assets/assets/{trellis-badge-demo-BDa3ILpo.js → trellis-badge-demo-nxtlIRoh.js} +1 -1
  91. package/assets/assets/{trellis-op-icon-5x1lMwNH.js → trellis-op-icon-CzIuo5X7.js} +1 -1
  92. package/assets/assets/{tsMode-Bg19hQiu.js → tsMode-qOM514sL.js} +2 -2
  93. package/assets/assets/{typescript-8CtqEj8t.js → typescript-XOJMwr_m.js} +2 -2
  94. package/assets/assets/{vennDiagram-LZ73GAT5-DWmAgqdE.js → vennDiagram-LZ73GAT5-BDAdfrV5.js} +2 -2
  95. package/assets/assets/{xml-BKjz9euG.js → xml-BIwomT_0.js} +2 -2
  96. package/assets/assets/{xychartDiagram-JWTSCODW-Ckcyhr_q.js → xychartDiagram-JWTSCODW-Be2x0vdX.js} +2 -2
  97. package/assets/assets/{yaml-jJWTWPtR.js → yaml-BZMZa-4Z.js} +2 -2
  98. package/assets/index.html +2 -4
  99. package/assets/oc-theme-preload.js +0 -2
  100. package/bin/cli.mjs +203 -25
  101. package/package.json +1 -1
  102. package/assets/assets/architectureDiagram-2XIMDMQ5-DVePwAIo.js +0 -36
  103. package/assets/assets/channel-CPcPUiyc.js +0 -1
  104. package/assets/assets/chunk-55IACEB6-CuagpIkW.js +0 -1
  105. package/assets/assets/chunk-JSJVCQXG-DK5RSVJJ.js +0 -1
  106. package/assets/assets/chunk-VVM5DH6Z-COaOSPgd.js +0 -1
  107. package/assets/assets/classDiagram-VBA2DB6C-BAnp4Zaa.js +0 -1
  108. package/assets/assets/classDiagram-v2-RAHNMMFH-BAnp4Zaa.js +0 -1
  109. package/assets/assets/clone-Cf5BTVxd.js +0 -1
  110. package/assets/assets/cose-bilkent-S5V4N54A-C5LXU9pd.js +0 -1
  111. package/assets/assets/dagre-KLK3FWXG-m-9cmD-w.js +0 -4
  112. package/assets/assets/diagram-E7M64L7V-Cn-5LVpt.js +0 -24
  113. package/assets/assets/diagram-IFDJBPK2-CD8BK0Gp.js +0 -43
  114. package/assets/assets/diagram-P4PSJMXO-JVUlmg7w.js +0 -24
  115. package/assets/assets/dialog-clone-project-BgrtjUcd.js +0 -1
  116. package/assets/assets/dialog-create-project-TcSOut-3.js +0 -1
  117. package/assets/assets/ganttDiagram-A5KZAMGK-C5vwClxA.js +0 -292
  118. package/assets/assets/gitGraphDiagram-K3NZZRJ6-CI0SeLE3.js +0 -65
  119. package/assets/assets/index-bSvs-Qh3.js +0 -2544
  120. package/assets/assets/infoDiagram-LFFYTUFH-H6835-cD.js +0 -2
  121. package/assets/assets/linear-ilW0Y4uJ.js +0 -1
  122. package/assets/assets/pieDiagram-SKSYHLDU-DxK57epR.js +0 -30
  123. package/assets/assets/purify.es-A66Cw1IH.js +0 -2
  124. package/assets/assets/sankeyDiagram-WA2Y5GQK-DrdtRBZz.js +0 -10
  125. package/assets/assets/session-CdcUAq9-.js +0 -60
  126. package/assets/assets/stateDiagram-RAJIS63D-C4udRYnl.js +0 -1
  127. package/assets/assets/stateDiagram-v2-FVOUBMTO-CECUON5A.js +0 -1
package/bin/cli.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createServer } from "node:http"
4
- import { readFileSync, existsSync, statSync } from "node:fs"
5
- import { join, extname, dirname } from "node:path"
3
+ import { createServer, request as httpRequest } from "node:http"
4
+ import { readFileSync, existsSync, statSync, mkdirSync } from "node:fs"
5
+ import { join, extname, dirname, resolve } from "node:path"
6
6
  import { fileURLToPath } from "node:url"
7
- import { exec } from "node:child_process"
7
+ import { exec, spawn } from "node:child_process"
8
+ import { homedir } from "node:os"
8
9
 
9
10
  const root = join(dirname(fileURLToPath(import.meta.url)), "..", "assets")
10
11
 
@@ -17,10 +18,14 @@ if (args.includes("--help") || args.includes("-h")) {
17
18
  npx turtlecode [options]
18
19
 
19
20
  Options:
20
- --port, -p <number> Port to serve on (default: 3333)
21
- --no-open Don't auto-open the browser
22
- --help, -h Show this help message
23
- --version, -v Show version
21
+ --port, -p <number> Port to serve on (default: 3333)
22
+ --backend, -b <url> OpenCode backend URL (default: http://localhost:4096)
23
+ --no-open Don't auto-open the browser
24
+ --help, -h Show this help message
25
+ --version, -v Show version
26
+
27
+ The backend server (opencode) is started automatically.
28
+ Install it with: npm i -g opencode-ai
24
29
  `)
25
30
  process.exit(0)
26
31
  }
@@ -31,9 +36,89 @@ if (args.includes("--version") || args.includes("-v")) {
31
36
  process.exit(0)
32
37
  }
33
38
 
34
- const idx = Math.max(args.indexOf("--port"), args.indexOf("-p"))
35
- const port = idx !== -1 && args[idx + 1] ? parseInt(args[idx + 1], 10) : 3333
39
+ function flag(long, short) {
40
+ const i = Math.max(args.indexOf(long), args.indexOf(short))
41
+ return i !== -1 && args[i + 1] ? args[i + 1] : undefined
42
+ }
43
+
44
+ const port = parseInt(flag("--port", "-p") || "3333", 10)
36
45
  const open = !args.includes("--no-open")
46
+ const backend = new URL(flag("--backend", "-b") || process.env.OPENCODE_URL || "http://localhost:4096")
47
+
48
+ // URL-safe base64 encode (matches @opencode-ai/util/encode)
49
+ function encode(str) {
50
+ return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
51
+ }
52
+
53
+ // Extract directory from base64-encoded path segment, returns { pathname, directory }
54
+ function extractDir(path) {
55
+ const match = path.match(/^\/([A-Za-z0-9_-]{10,})(\/.*)?$/)
56
+ if (!match) return { pathname: path }
57
+
58
+ const encoded = match[1]
59
+ const rest = match[2] || "/"
60
+
61
+ try {
62
+ const normalized = encoded.replace(/-/g, "+").replace(/_/g, "/")
63
+ const decoded = Buffer.from(normalized, "base64").toString("utf-8")
64
+ return { pathname: rest, directory: decoded }
65
+ } catch {
66
+ return { pathname: path }
67
+ }
68
+ }
69
+
70
+ function probe(origin) {
71
+ return new Promise((ok) => {
72
+ const req = httpRequest(origin, { method: "HEAD", timeout: 1000 }, (res) => {
73
+ res.resume()
74
+ ok(true)
75
+ })
76
+ req.on("error", () => ok(false))
77
+ req.on("timeout", () => {
78
+ req.destroy()
79
+ ok(false)
80
+ })
81
+ req.end()
82
+ })
83
+ }
84
+
85
+ async function boot(origin) {
86
+ if (await probe(origin)) return null
87
+
88
+ const url = new URL(origin)
89
+ if (url.hostname !== "localhost" && url.hostname !== "127.0.0.1") {
90
+ console.error(`\n Error: Backend not reachable at ${origin}\n`)
91
+ process.exit(1)
92
+ }
93
+
94
+ console.log(` Starting backend on port ${url.port}...`)
95
+ const child = spawn("opencode", ["serve", "--port", url.port], {
96
+ cwd: process.cwd(),
97
+ stdio: ["ignore", "pipe", "pipe"],
98
+ env: { ...process.env },
99
+ })
100
+
101
+ let exited = false
102
+ child.on("exit", (code) => {
103
+ exited = true
104
+ if (code && code !== 0) console.error(`\n Backend exited with code ${code}`)
105
+ })
106
+
107
+ child.on("error", (err) => {
108
+ console.error(`\n Error: Could not start backend.\n ${err.message}\n\n Install: npm i -g opencode-ai\n`)
109
+ process.exit(1)
110
+ })
111
+
112
+ const deadline = Date.now() + 30_000
113
+ while (Date.now() < deadline && !exited) {
114
+ if (await probe(origin)) return child
115
+ await new Promise((r) => setTimeout(r, 300))
116
+ }
117
+
118
+ if (!exited) child.kill()
119
+ console.error("\n Error: Backend failed to start within 30s.\n")
120
+ process.exit(1)
121
+ }
37
122
 
38
123
  const mime = {
39
124
  ".html": "text/html; charset=utf-8",
@@ -61,19 +146,77 @@ if (!existsSync(join(root, "index.html"))) {
61
146
  process.exit(1)
62
147
  }
63
148
 
149
+ const spawned = await boot(backend.origin)
150
+
151
+ // Ensure ~/.turtlecode exists for new project creation
152
+ const projectsDir = join(homedir(), ".turtlecode")
153
+ if (!existsSync(projectsDir)) {
154
+ try {
155
+ mkdirSync(projectsDir, { recursive: true })
156
+ } catch {
157
+ // Ignore errors (permissions, etc.) - backend will handle them
158
+ }
159
+ }
160
+
161
+ // Determine whether a request should be proxied to the backend.
162
+ // Static assets are served directly; browser navigation gets the SPA
163
+ // index.html; everything else (API calls, SSE, etc.) is proxied.
164
+ function shouldProxy(req, file) {
165
+ // Non-GET/HEAD methods are always API requests
166
+ if (req.method !== "GET" && req.method !== "HEAD") return true
167
+
168
+ // If the file exists as a static asset, serve it directly
169
+ if (existsSync(file) && !statSync(file).isDirectory()) return false
170
+
171
+ // GET requests that accept text/html are browser navigation → SPA fallback
172
+ const accept = req.headers.accept || ""
173
+ if (accept.includes("text/html")) return false
174
+
175
+ // Everything else (JSON, SSE, etc.) → proxy
176
+ return true
177
+ }
178
+
179
+ function proxy(req, res) {
180
+ const url = new URL(req.url, `http://localhost:${port}`)
181
+
182
+ // Extract directory from base64 path segment and add header for backend
183
+ const { pathname, directory } = extractDir(url.pathname)
184
+ const headers = { ...req.headers, host: backend.host }
185
+ if (directory) headers["x-opencode-directory"] = directory
186
+
187
+ const target = `${backend.origin}${pathname}${url.search}`
188
+
189
+ const proxyReq = httpRequest(target, { method: req.method, headers }, (proxyRes) => {
190
+ res.writeHead(proxyRes.statusCode, proxyRes.headers)
191
+ proxyRes.pipe(res)
192
+ })
193
+
194
+ proxyReq.on("error", () => {
195
+ if (res.headersSent) return res.end()
196
+ res.writeHead(502, { "Content-Type": "application/json" })
197
+ res.end(
198
+ JSON.stringify({
199
+ error: `Backend not reachable at ${backend.origin}. Start it with: opencode serve`,
200
+ }),
201
+ )
202
+ })
203
+
204
+ req.pipe(proxyReq)
205
+ }
206
+
64
207
  const server = createServer((req, res) => {
65
208
  const url = new URL(req.url, `http://localhost:${port}`)
66
- let file = join(root, url.pathname === "/" ? "index.html" : url.pathname)
209
+ const file = join(root, url.pathname === "/" ? "index.html" : url.pathname)
67
210
 
68
- if (!existsSync(file) || statSync(file).isDirectory()) {
69
- file = join(root, "index.html")
70
- }
211
+ if (shouldProxy(req, file)) return proxy(req, res)
71
212
 
72
- const ext = extname(file)
213
+ // Serve static file or SPA fallback
214
+ const resolved = existsSync(file) && !statSync(file).isDirectory() ? file : join(root, "index.html")
215
+ const ext = extname(resolved)
73
216
  const type = mime[ext] || "application/octet-stream"
74
217
 
75
218
  try {
76
- const body = readFileSync(file)
219
+ const body = readFileSync(resolved)
77
220
  res.writeHead(200, {
78
221
  "Content-Type": type,
79
222
  "Content-Length": body.length,
@@ -86,26 +229,61 @@ const server = createServer((req, res) => {
86
229
  }
87
230
  })
88
231
 
232
+ // Handle WebSocket upgrade (used by PTY terminal connections)
233
+ server.on("upgrade", (req, socket, head) => {
234
+ const url = new URL(req.url, `http://localhost:${port}`)
235
+
236
+ // Extract directory from base64 path segment and add header (same logic as proxy function)
237
+ const { pathname, directory } = extractDir(url.pathname)
238
+ const headers = { ...req.headers, host: backend.host }
239
+ if (directory) headers["x-opencode-directory"] = directory
240
+
241
+ const target = `${backend.origin}${pathname}${url.search}`
242
+ const proxyReq = httpRequest(target, { method: "GET", headers })
243
+
244
+ proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
245
+ socket.write(
246
+ `HTTP/1.1 101 Switching Protocols\r\n` +
247
+ Object.entries(proxyRes.headers)
248
+ .map(([k, v]) => `${k}: ${v}`)
249
+ .join("\r\n") +
250
+ "\r\n\r\n",
251
+ )
252
+ if (proxyHead.length) socket.write(proxyHead)
253
+ proxySocket.pipe(socket)
254
+ socket.pipe(proxySocket)
255
+ })
256
+
257
+ proxyReq.on("error", () => {
258
+ socket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n")
259
+ })
260
+
261
+ proxyReq.end()
262
+ })
263
+
89
264
  server.listen(port, () => {
90
- const url = `http://localhost:${port}`
265
+ const dir = encode(resolve(process.cwd()))
266
+ const url = `http://localhost:${port}/${dir}`
91
267
  console.log(`
92
268
  🐢 turtlecode v${process.env.npm_package_version || "0.1.0"}
93
269
 
94
- Local: ${url}
270
+ Local: ${url}
271
+ Backend: ${backend.origin}
272
+ Project: ${process.cwd()}
95
273
  Press Ctrl+C to stop
96
274
  `)
97
275
 
98
276
  if (open) {
99
- const cmd = process.platform === "darwin"
100
- ? "open"
101
- : process.platform === "win32"
102
- ? "start"
103
- : "xdg-open"
277
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open"
104
278
  exec(`${cmd} ${url}`)
105
279
  }
106
280
  })
107
281
 
108
- process.on("SIGINT", () => {
282
+ function cleanup() {
283
+ if (spawned) spawned.kill()
109
284
  console.log("\n Stopped.\n")
110
285
  process.exit(0)
111
- })
286
+ }
287
+
288
+ process.on("SIGINT", cleanup)
289
+ process.on("SIGTERM", cleanup)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turtlecode",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "AI-powered creative workspace — launch with npx turtlecode",
5
5
  "type": "module",
6
6
  "bin": {