neural-loom 0.2.70 → 0.3.0
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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/diagnostics/route-bundle-stats.json +11 -11
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +20 -20
- package/.next/required-server-files.json +20 -20
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/configs/route.js.nft.json +1 -1
- package/.next/server/app/api/context/aider/route.js.nft.json +1 -1
- package/.next/server/app/api/context/claude/route.js.nft.json +1 -1
- package/.next/server/app/api/context/route.js.nft.json +1 -1
- package/.next/server/app/api/context/ssh/route.js.nft.json +1 -1
- package/.next/server/app/api/files/route.js +1 -1
- package/.next/server/app/api/files/route.js.nft.json +1 -1
- package/.next/server/app/api/recents/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/inject/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/input/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/launch/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/scope/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/start/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/stop/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/upload-image/route.js.nft.json +1 -1
- package/.next/server/app/api/sql/connections/route.js.nft.json +1 -1
- package/.next/server/app/api/sql/mcp/route.js +1 -1
- package/.next/server/app/api/sql/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/sql/query/route.js +1 -1
- package/.next/server/app/api/sql/query/route.js.nft.json +1 -1
- package/.next/server/app/api/sql/schema/route.js.nft.json +1 -1
- package/.next/server/app/api/telemetry/route.js +1 -1
- package/.next/server/app/api/telemetry/route.js.nft.json +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page/react-loadable-manifest.json +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0.hmw1f._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0.hmw1f._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__00-sqou._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__00-sqou._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__00l5btc._.js +4 -4
- package/.next/server/chunks/[root-of-the-server]__00l5btc._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__06men8g._.js +4 -4
- package/.next/server/chunks/[root-of-the-server]__06men8g._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__097vtn1._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__097vtn1._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0_v2.a8._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__0_v2.a8._.js.map +1 -1
- package/.next/server/chunks/{[root-of-the-server]__0.ptwor._.js → [root-of-the-server]__0ajln56._.js} +7 -7
- package/.next/server/chunks/[root-of-the-server]__0ajln56._.js.map +1 -0
- package/.next/server/chunks/{[root-of-the-server]__0co.g5k._.js → [root-of-the-server]__0auh-2z._.js} +5 -5
- package/.next/server/chunks/[root-of-the-server]__0auh-2z._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0e03ite._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__0e03ite._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0fxtx7z._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__0fxtx7z._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0h.~hv6._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__0h.~hv6._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0i83~95._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0li1h1u._.js +19 -19
- package/.next/server/chunks/[root-of-the-server]__0li1h1u._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0s1axek._.js +5 -5
- package/.next/server/chunks/[root-of-the-server]__0s1axek._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0t713sv._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0t713sv._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tq0maq._.js +5 -5
- package/.next/server/chunks/[root-of-the-server]__0tq0maq._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0viil6b._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__0viil6b._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0ykd4~u._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0ykd4~u._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__11h_nxt._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__11h_nxt._.js.map +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_016qk~0.js +2 -2
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_016qk~0.js.map +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0ev3h.z.js +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0ev3h.z.js.map +1 -1
- package/.next/server/edge/chunks/[root-of-the-server]__08ca2jv._.js +11 -0
- package/.next/server/edge/chunks/[root-of-the-server]__08ca2jv._.js.map +1 -0
- package/.next/server/edge/chunks/node_modules_next_dist_esm_build_templates_edge-wrapper_0kvehva.js +3 -0
- package/.next/server/edge/chunks/node_modules_next_dist_esm_build_templates_edge-wrapper_0kvehva.js.map +1 -0
- package/.next/server/edge/chunks/node_modules_next_dist_esm_build_templates_edge-wrapper_0lcpsxv.js.map +1 -0
- package/.next/server/edge/chunks/turbopack-node_modules_next_dist_esm_build_templates_edge-wrapper_0lcpsxv.js +3 -0
- package/.next/server/middleware/middleware-manifest.json +32 -0
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/middleware-manifest.json +30 -2
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/04j4ht7eCOOXSLQ5Wixq0/_clientMiddlewareManifest.js +6 -0
- package/.next/static/chunks/{0fxouf2n_5adf.js → 0uyuco-451j.n.js} +2 -2
- package/.next/static/chunks/{0r3x2gcn0sde..js → 14~-ojwsxf~oe.js} +4 -4
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/README.md +18 -0
- package/bin/cli.js +12 -2
- package/package.json +9 -6
- package/src/app/api/files/route.ts +30 -0
- package/src/app/api/git/route.ts +46 -31
- package/src/app/api/sessions/stats/route.ts +40 -22
- package/src/app/api/sql/query/route.ts +2 -15
- package/src/app/api/telemetry/route.ts +2 -1
- package/src/app/components/TerminalConsole.tsx +103 -39
- package/src/lib/agents/AgentRunner.ts +41 -5
- package/src/lib/agents/AiderRunner.ts +20 -36
- package/src/lib/agents/ClaudeRunner.ts +8 -62
- package/src/lib/agents/DockerRunner.ts +18 -54
- package/src/lib/agents/MockRunner.ts +3 -1
- package/src/lib/agents/SSHRunner.ts +8 -54
- package/src/lib/agents/SessionPersister.ts +11 -1
- package/src/lib/agents/ShellRunner.ts +10 -46
- package/src/lib/agents/WebSocketServer.ts +17 -2
- package/src/lib/agents/pricing.ts +45 -0
- package/src/lib/agents/runtime.ts +92 -0
- package/src/lib/gitSafe.ts +15 -0
- package/src/lib/security.ts +76 -0
- package/src/lib/sql/McpServer.ts +2 -11
- package/src/lib/sql/adapters/MssqlAdapter.ts +16 -1
- package/src/lib/sql/adapters/MysqlAdapter.ts +23 -1
- package/src/lib/sql/adapters/PostgresAdapter.ts +22 -1
- package/src/lib/sql/adapters/SqliteAdapter.ts +21 -10
- package/src/lib/sql/sqlGuard.ts +20 -0
- package/src/lib/workspaceParser.ts +23 -6
- package/src/middleware.ts +27 -0
- package/.next/dev/build-manifest.json +0 -50
- package/.next/dev/fallback-build-manifest.json +0 -37
- package/.next/dev/logs/next-development.log +0 -3737
- package/.next/dev/server/chunks/[root-of-the-server]__11uf~fd._.js +0 -2396
- package/.next/dev/server/chunks/[root-of-the-server]__11uf~fd._.js.map +0 -20
- package/.next/dev/server/chunks/ssr/[externals]__0.o~_~l._.js +0 -32
- package/.next/dev/server/chunks/ssr/[externals]__0.o~_~l._.js.map +0 -5
- package/.next/dev/server/chunks/ssr/[externals]_next_dist_shared_lib_no-fallback-error_external_0fgm~bn.js +0 -8
- package/.next/dev/server/chunks/ssr/[externals]_next_dist_shared_lib_no-fallback-error_external_0fgm~bn.js.map +0 -5
- package/.next/dev/server/chunks/ssr/[root-of-the-server]__0je5zm2._.js +0 -285
- package/.next/dev/server/chunks/ssr/[root-of-the-server]__0je5zm2._.js.map +0 -9
- package/.next/dev/server/chunks/ssr/node_modules_01ed9g5._.js +0 -6621
- package/.next/dev/server/chunks/ssr/node_modules_01ed9g5._.js.map +0 -76
- package/.next/dev/server/chunks/ssr/node_modules_129w7gn._.js +0 -4399
- package/.next/dev/server/chunks/ssr/node_modules_129w7gn._.js.map +0 -35
- package/.next/dev/server/chunks/ssr/node_modules_next_12i4inu._.js +0 -55
- package/.next/dev/server/chunks/ssr/node_modules_next_12i4inu._.js.map +0 -7
- package/.next/dev/server/middleware-build-manifest.js +0 -50
- package/.next/dev/server/next-font-manifest.js +0 -1
- package/.next/dev/server/next-font-manifest.json +0 -11
- package/.next/dev/server/pages/_app/build-manifest.json +0 -23
- package/.next/dev/server/pages/_app/client-build-manifest.json +0 -5
- package/.next/dev/server/pages/_app/next-font-manifest.json +0 -6
- package/.next/dev/server/pages/_app/pages-manifest.json +0 -3
- package/.next/dev/server/pages/_app/react-loadable-manifest.json +0 -1
- package/.next/dev/server/pages/_app.js +0 -4
- package/.next/dev/server/pages/_app.js.map +0 -5
- package/.next/dev/server/pages/_document/next-font-manifest.json +0 -6
- package/.next/dev/server/pages/_document/pages-manifest.json +0 -3
- package/.next/dev/server/pages/_document/react-loadable-manifest.json +0 -1
- package/.next/dev/server/pages/_document.js +0 -5
- package/.next/dev/server/pages/_document.js.map +0 -5
- package/.next/dev/server/pages/_error/build-manifest.json +0 -22
- package/.next/dev/server/pages/_error/client-build-manifest.json +0 -5
- package/.next/dev/server/pages/_error/next-font-manifest.json +0 -6
- package/.next/dev/server/pages/_error/pages-manifest.json +0 -3
- package/.next/dev/server/pages/_error/react-loadable-manifest.json +0 -1
- package/.next/dev/server/pages/_error.js +0 -8
- package/.next/dev/server/pages/_error.js.map +0 -5
- package/.next/dev/server/pages-manifest.json +0 -5
- package/.next/dev/static/chunks/[next]_entry_page-loader_ts_0j~flwh._.js +0 -23
- package/.next/dev/static/chunks/[next]_entry_page-loader_ts_0j~flwh._.js.map +0 -6
- package/.next/dev/static/chunks/[next]_entry_page-loader_ts_0rqw6yo._.js +0 -23
- package/.next/dev/static/chunks/[next]_entry_page-loader_ts_0rqw6yo._.js.map +0 -6
- package/.next/dev/static/chunks/[root-of-the-server]__01mw43t._.js +0 -476
- package/.next/dev/static/chunks/[root-of-the-server]__01mw43t._.js.map +0 -6
- package/.next/dev/static/chunks/[root-of-the-server]__0c0okpg._.js +0 -476
- package/.next/dev/static/chunks/[root-of-the-server]__0c0okpg._.js.map +0 -6
- package/.next/dev/static/chunks/node_modules_0lx093h._.js +0 -1343
- package/.next/dev/static/chunks/node_modules_0lx093h._.js.map +0 -13
- package/.next/dev/static/chunks/node_modules_next_app_0jt-zj..js +0 -8
- package/.next/dev/static/chunks/node_modules_next_app_0jt-zj..js.map +0 -6
- package/.next/dev/static/chunks/node_modules_next_dist_0rt-2cr._.js +0 -2334
- package/.next/dev/static/chunks/node_modules_next_dist_0rt-2cr._.js.map +0 -26
- package/.next/dev/static/chunks/node_modules_next_dist_0u_w_5s._.js +0 -2165
- package/.next/dev/static/chunks/node_modules_next_dist_0u_w_5s._.js.map +0 -25
- package/.next/dev/static/chunks/node_modules_next_dist_client_0pe1dg-._.js +0 -4091
- package/.next/dev/static/chunks/node_modules_next_dist_client_0pe1dg-._.js.map +0 -49
- package/.next/dev/static/chunks/node_modules_next_dist_compiled_0o6l_m6._.js +0 -6064
- package/.next/dev/static/chunks/node_modules_next_dist_compiled_0o6l_m6._.js.map +0 -17
- package/.next/dev/static/chunks/node_modules_next_dist_shared_lib_0ek5eq7._.js +0 -6535
- package/.next/dev/static/chunks/node_modules_next_dist_shared_lib_0ek5eq7._.js.map +0 -72
- package/.next/dev/static/chunks/node_modules_next_dist_shared_lib_0n1wg2c._.js +0 -6294
- package/.next/dev/static/chunks/node_modules_next_dist_shared_lib_0n1wg2c._.js.map +0 -70
- package/.next/dev/static/chunks/node_modules_react-dom_0bruynb._.js +0 -15381
- package/.next/dev/static/chunks/node_modules_react-dom_0bruynb._.js.map +0 -9
- package/.next/dev/static/chunks/pages/_app.js +0 -13
- package/.next/dev/static/chunks/pages/_error.js +0 -12
- package/.next/dev/static/chunks/pages__app_0-bg2z-._.js.map +0 -11
- package/.next/dev/static/chunks/pages__app_07xvfw~._.js +0 -5
- package/.next/dev/static/chunks/pages__error_0.i_zsq._.js.map +0 -11
- package/.next/dev/static/chunks/pages__error_07xvfw~._.js +0 -5
- package/.next/dev/static/chunks/turbopack-pages__app_0-bg2z-._.js +0 -2236
- package/.next/dev/static/chunks/turbopack-pages__error_0.i_zsq._.js +0 -2236
- package/.next/server/chunks/[externals]__048y_op._.js +0 -3
- package/.next/server/chunks/[externals]__048y_op._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0.ptwor._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__08r0s-s._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__08r0s-s._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0co.g5k._.js.map +0 -1
- package/.next/static/llmJsGQafolZV5_sqEw4l/_clientMiddlewareManifest.js +0 -1
- /package/.next/static/{llmJsGQafolZV5_sqEw4l → 04j4ht7eCOOXSLQ5Wixq0}/_buildManifest.js +0 -0
- /package/.next/static/{llmJsGQafolZV5_sqEw4l → 04j4ht7eCOOXSLQ5Wixq0}/_ssgManifest.js +0 -0
package/bin/cli.js
CHANGED
|
@@ -140,10 +140,20 @@ process.chdir(packageRoot);
|
|
|
140
140
|
// Next.js provides a cli entry point in next/dist/bin/next
|
|
141
141
|
const nextBin = require.resolve('next/dist/bin/next');
|
|
142
142
|
|
|
143
|
+
// Bind to loopback by default so the dashboard (which exposes shell, filesystem
|
|
144
|
+
// and DB access) is not reachable from the local network. Opt in with
|
|
145
|
+
// NEURAL_LOOM_ALLOW_LAN=1, or override the interface with NEURAL_LOOM_HOST.
|
|
146
|
+
const allowLan = ['1', 'true', 'yes'].includes((process.env.NEURAL_LOOM_ALLOW_LAN || '').toLowerCase());
|
|
147
|
+
const bindHost = process.env.NEURAL_LOOM_HOST || (allowLan ? '0.0.0.0' : '127.0.0.1');
|
|
148
|
+
const port = process.env.PORT || '9600';
|
|
149
|
+
|
|
143
150
|
console.log("🚀 Starting NeuralLoom AI Orchestrator dashboard...");
|
|
144
|
-
console.log(`🔗 Open http://localhost:${
|
|
151
|
+
console.log(`🔗 Open http://localhost:${port} in your browser to begin.`);
|
|
152
|
+
if (allowLan || process.env.NEURAL_LOOM_HOST) {
|
|
153
|
+
console.log(`🌐 Binding to ${bindHost} (network access enabled).`);
|
|
154
|
+
}
|
|
145
155
|
|
|
146
|
-
const nextProcess = spawn(process.execPath, [nextBin, 'start', '-p',
|
|
156
|
+
const nextProcess = spawn(process.execPath, [nextBin, 'start', '-p', port, '-H', bindHost], {
|
|
147
157
|
stdio: 'inherit',
|
|
148
158
|
env: {
|
|
149
159
|
...process.env,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neural-loom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"bin": {
|
|
5
5
|
"neural-loom": "./bin/cli.js"
|
|
6
6
|
},
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"dev": "next dev",
|
|
17
|
+
"dev": "next dev -H 127.0.0.1",
|
|
18
18
|
"build": "next build",
|
|
19
|
-
"start": "next start",
|
|
20
|
-
"lint": "eslint"
|
|
19
|
+
"start": "next start -H 127.0.0.1",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"test": "node --test --experimental-strip-types \"test/**/*.test.ts\"",
|
|
22
|
+
"prepublishOnly": "next build"
|
|
21
23
|
},
|
|
22
24
|
"dependencies": {
|
|
23
25
|
"@monaco-editor/react": "^4.7.0",
|
|
@@ -25,6 +27,7 @@
|
|
|
25
27
|
"mssql": "^12.5.5",
|
|
26
28
|
"next": "16.2.6",
|
|
27
29
|
"node-pty": "^1.1.0",
|
|
30
|
+
"pidusage": "^3.0.2",
|
|
28
31
|
"react": "19.2.4",
|
|
29
32
|
"react-dom": "19.2.4",
|
|
30
33
|
"ws": "^8.21.0",
|
|
@@ -33,11 +36,11 @@
|
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
38
|
"@types/node": "^20",
|
|
36
|
-
"@types/react": "
|
|
39
|
+
"@types/react": "19.2.17",
|
|
37
40
|
"@types/react-dom": "^19",
|
|
38
41
|
"@types/ws": "^8.18.1",
|
|
39
42
|
"eslint": "^9",
|
|
40
43
|
"eslint-config-next": "16.2.6",
|
|
41
|
-
"typescript": "
|
|
44
|
+
"typescript": "5.9.3"
|
|
42
45
|
}
|
|
43
46
|
}
|
|
@@ -2,6 +2,24 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import os from "os";
|
|
5
|
+
import { getConfigDir } from "@/lib/config";
|
|
6
|
+
|
|
7
|
+
// NeuralLoom's runtime/secret store (.ai/neural-loom, .claude/, the resolved
|
|
8
|
+
// config dir) holds credentials, SSH keys and process state. The generic file
|
|
9
|
+
// API must never read or mutate it, regardless of caller.
|
|
10
|
+
function isProtectedPath(targetPath: string): boolean {
|
|
11
|
+
const norm = path.resolve(targetPath).replace(/\\/g, "/").toLowerCase();
|
|
12
|
+
const segments = norm.split("/");
|
|
13
|
+
if (segments.includes(".ai") || segments.includes(".claude")) return true;
|
|
14
|
+
const cfg = getConfigDir().replace(/\\/g, "/").toLowerCase();
|
|
15
|
+
return norm === cfg || norm.startsWith(cfg + "/");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const protectedResponse = () =>
|
|
19
|
+
NextResponse.json(
|
|
20
|
+
{ error: "Access to NeuralLoom's runtime/config directory is not permitted." },
|
|
21
|
+
{ status: 403 }
|
|
22
|
+
);
|
|
5
23
|
|
|
6
24
|
// Helper to recursively generate a tree nodes list for UI representation
|
|
7
25
|
interface TreeNode {
|
|
@@ -229,6 +247,9 @@ export async function GET(request: Request) {
|
|
|
229
247
|
}
|
|
230
248
|
|
|
231
249
|
const filePath = filePathRaw.replace(/\\/g, "/");
|
|
250
|
+
if (isProtectedPath(filePath)) {
|
|
251
|
+
return protectedResponse();
|
|
252
|
+
}
|
|
232
253
|
if (!fs.existsSync(filePath)) {
|
|
233
254
|
return NextResponse.json({ error: "File does not exist" }, { status: 404 });
|
|
234
255
|
}
|
|
@@ -252,6 +273,15 @@ export async function POST(request: Request) {
|
|
|
252
273
|
const oldPath = rawOldPath ? rawOldPath.replace(/\\/g, "/") : undefined;
|
|
253
274
|
const newPath = rawNewPath ? rawNewPath.replace(/\\/g, "/") : undefined;
|
|
254
275
|
|
|
276
|
+
// Refuse any mutation that touches the runtime/secret store.
|
|
277
|
+
if (
|
|
278
|
+
(filePath && isProtectedPath(filePath)) ||
|
|
279
|
+
(oldPath && isProtectedPath(oldPath)) ||
|
|
280
|
+
(newPath && isProtectedPath(newPath))
|
|
281
|
+
) {
|
|
282
|
+
return protectedResponse();
|
|
283
|
+
}
|
|
284
|
+
|
|
255
285
|
if (action === "write") {
|
|
256
286
|
if (!filePath || content === undefined) {
|
|
257
287
|
return NextResponse.json({ error: "Missing 'path' or 'content' in body" }, { status: 400 });
|
package/src/app/api/git/route.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { promisify } from "util";
|
|
3
|
-
import {
|
|
3
|
+
import { execFile } from "child_process";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
|
+
import { isValidBranchName } from "@/lib/gitSafe";
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const execFilePromise = promisify(execFile);
|
|
9
|
+
|
|
10
|
+
// Run git without a shell. Arguments are passed as an array, so values like file
|
|
11
|
+
// names, branch names and commit messages cannot break out into shell commands.
|
|
12
|
+
async function git(args: string[], cwd: string): Promise<{ stdout: string; stderr: string }> {
|
|
13
|
+
const { stdout, stderr } = await execFilePromise("git", args, {
|
|
14
|
+
cwd,
|
|
15
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
16
|
+
windowsHide: true,
|
|
17
|
+
});
|
|
18
|
+
return { stdout: stdout.toString(), stderr: stderr.toString() };
|
|
19
|
+
}
|
|
8
20
|
|
|
9
21
|
export async function GET(request: Request) {
|
|
10
22
|
try {
|
|
@@ -17,13 +29,13 @@ export async function GET(request: Request) {
|
|
|
17
29
|
|
|
18
30
|
// Check if it's a git repo
|
|
19
31
|
try {
|
|
20
|
-
await
|
|
32
|
+
await git(["rev-parse", "--is-inside-work-tree"], targetRoot);
|
|
21
33
|
} catch {
|
|
22
34
|
return NextResponse.json({ success: true, isGit: false, files: [] });
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
if (action === "status") {
|
|
26
|
-
const { stdout } = await
|
|
38
|
+
const { stdout } = await git(["status", "--porcelain"], targetRoot);
|
|
27
39
|
const lines = stdout.split(/\r?\n/).filter(Boolean);
|
|
28
40
|
const files = lines.map(line => {
|
|
29
41
|
// porcelain format: XY path or XY "path"
|
|
@@ -49,7 +61,7 @@ export async function GET(request: Request) {
|
|
|
49
61
|
// Get branch name
|
|
50
62
|
let branch = "";
|
|
51
63
|
try {
|
|
52
|
-
const { stdout: branchOut } = await
|
|
64
|
+
const { stdout: branchOut } = await git(["rev-parse", "--abbrev-ref", "HEAD"], targetRoot);
|
|
53
65
|
branch = branchOut.trim();
|
|
54
66
|
} catch {}
|
|
55
67
|
|
|
@@ -57,7 +69,7 @@ export async function GET(request: Request) {
|
|
|
57
69
|
let ahead = 0;
|
|
58
70
|
let behind = 0;
|
|
59
71
|
try {
|
|
60
|
-
const { stdout: syncOut } = await
|
|
72
|
+
const { stdout: syncOut } = await git(["rev-list", "--left-right", "--count", "HEAD...@{u}"], targetRoot);
|
|
61
73
|
const parts = syncOut.trim().split(/\s+/);
|
|
62
74
|
if (parts.length === 2) {
|
|
63
75
|
ahead = parseInt(parts[0], 10) || 0;
|
|
@@ -79,7 +91,7 @@ export async function GET(request: Request) {
|
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
// Check status to see if it is untracked
|
|
82
|
-
const { stdout: statusOut } = await
|
|
94
|
+
const { stdout: statusOut } = await git(["status", "--porcelain", "--", file], targetRoot);
|
|
83
95
|
const isUntracked = statusOut.startsWith("??");
|
|
84
96
|
|
|
85
97
|
if (isUntracked) {
|
|
@@ -88,7 +100,7 @@ export async function GET(request: Request) {
|
|
|
88
100
|
return NextResponse.json({ success: true, diff: diffLines });
|
|
89
101
|
} else {
|
|
90
102
|
try {
|
|
91
|
-
const { stdout: diffOut } = await
|
|
103
|
+
const { stdout: diffOut } = await git(["diff", "--no-color", "--", file], targetRoot);
|
|
92
104
|
return NextResponse.json({ success: true, diff: diffOut });
|
|
93
105
|
} catch {
|
|
94
106
|
return NextResponse.json({ success: true, diff: `Binary file or diff unavailable.` });
|
|
@@ -97,7 +109,7 @@ export async function GET(request: Request) {
|
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
if (action === "branches") {
|
|
100
|
-
const { stdout } = await
|
|
112
|
+
const { stdout } = await git(["branch", "--format=%(refname:short)|%(HEAD)"], targetRoot);
|
|
101
113
|
const branches = stdout.split(/\r?\n/).filter(Boolean).map(line => {
|
|
102
114
|
const [name, active] = line.split("|");
|
|
103
115
|
return {
|
|
@@ -110,7 +122,10 @@ export async function GET(request: Request) {
|
|
|
110
122
|
|
|
111
123
|
if (action === "log") {
|
|
112
124
|
try {
|
|
113
|
-
const { stdout } = await
|
|
125
|
+
const { stdout } = await git(
|
|
126
|
+
["log", "--graph", "--abbrev-commit", "--decorate", "--format=format:%h||%p||%s||%an||%ar", "--all", "-n", "100"],
|
|
127
|
+
targetRoot
|
|
128
|
+
);
|
|
114
129
|
const lines = stdout.split(/\r?\n/);
|
|
115
130
|
const logItems = lines.map(line => {
|
|
116
131
|
const parts = line.split("||");
|
|
@@ -119,7 +134,7 @@ export async function GET(request: Request) {
|
|
|
119
134
|
const hashMatch = firstPart.match(/([0-9a-f]{7,40})$/);
|
|
120
135
|
const hash = hashMatch ? hashMatch[1] : "";
|
|
121
136
|
const graph = firstPart.substring(0, firstPart.length - hash.length);
|
|
122
|
-
|
|
137
|
+
|
|
123
138
|
return {
|
|
124
139
|
isCommit: true,
|
|
125
140
|
graph,
|
|
@@ -142,7 +157,7 @@ export async function GET(request: Request) {
|
|
|
142
157
|
}
|
|
143
158
|
});
|
|
144
159
|
return NextResponse.json({ success: true, log: logItems });
|
|
145
|
-
} catch
|
|
160
|
+
} catch {
|
|
146
161
|
return NextResponse.json({ success: true, log: [] });
|
|
147
162
|
}
|
|
148
163
|
}
|
|
@@ -163,25 +178,25 @@ export async function POST(request: Request) {
|
|
|
163
178
|
|
|
164
179
|
// Check if it's a git repo
|
|
165
180
|
try {
|
|
166
|
-
await
|
|
181
|
+
await git(["rev-parse", "--is-inside-work-tree"], targetRoot);
|
|
167
182
|
} catch {
|
|
168
183
|
return NextResponse.json({ error: "Not a git repository" }, { status: 400 });
|
|
169
184
|
}
|
|
170
185
|
|
|
171
186
|
if (action === "stage") {
|
|
172
187
|
if (file) {
|
|
173
|
-
await
|
|
188
|
+
await git(["add", "--", file], targetRoot);
|
|
174
189
|
} else {
|
|
175
|
-
await
|
|
190
|
+
await git(["add", "-A"], targetRoot);
|
|
176
191
|
}
|
|
177
192
|
return NextResponse.json({ success: true });
|
|
178
193
|
}
|
|
179
194
|
|
|
180
195
|
if (action === "unstage") {
|
|
181
196
|
if (file) {
|
|
182
|
-
await
|
|
197
|
+
await git(["reset", "HEAD", "--", file], targetRoot);
|
|
183
198
|
} else {
|
|
184
|
-
await
|
|
199
|
+
await git(["reset", "HEAD", "--", "."], targetRoot);
|
|
185
200
|
}
|
|
186
201
|
return NextResponse.json({ success: true });
|
|
187
202
|
}
|
|
@@ -192,7 +207,7 @@ export async function POST(request: Request) {
|
|
|
192
207
|
}
|
|
193
208
|
const absolutePath = path.resolve(targetRoot, file).replace(/\\/g, "/");
|
|
194
209
|
// Check status to see if it is untracked
|
|
195
|
-
const { stdout: statusOut } = await
|
|
210
|
+
const { stdout: statusOut } = await git(["status", "--porcelain", "--", file], targetRoot);
|
|
196
211
|
const isUntracked = statusOut.startsWith("??");
|
|
197
212
|
|
|
198
213
|
if (isUntracked) {
|
|
@@ -200,7 +215,7 @@ export async function POST(request: Request) {
|
|
|
200
215
|
fs.unlinkSync(absolutePath);
|
|
201
216
|
}
|
|
202
217
|
} else {
|
|
203
|
-
await
|
|
218
|
+
await git(["checkout", "--", file], targetRoot);
|
|
204
219
|
}
|
|
205
220
|
return NextResponse.json({ success: true });
|
|
206
221
|
}
|
|
@@ -209,7 +224,7 @@ export async function POST(request: Request) {
|
|
|
209
224
|
if (!message || !message.trim()) {
|
|
210
225
|
return NextResponse.json({ error: "Commit message cannot be empty" }, { status: 400 });
|
|
211
226
|
}
|
|
212
|
-
const { stdout } = await
|
|
227
|
+
const { stdout } = await git(["commit", "-m", message.trim()], targetRoot);
|
|
213
228
|
return NextResponse.json({ success: true, output: stdout });
|
|
214
229
|
}
|
|
215
230
|
|
|
@@ -231,40 +246,40 @@ export async function POST(request: Request) {
|
|
|
231
246
|
}
|
|
232
247
|
|
|
233
248
|
if (action === "pull") {
|
|
234
|
-
const { stdout, stderr } = await
|
|
249
|
+
const { stdout, stderr } = await git(["pull"], targetRoot);
|
|
235
250
|
return NextResponse.json({ success: true, output: stdout + stderr });
|
|
236
251
|
}
|
|
237
252
|
|
|
238
253
|
if (action === "push") {
|
|
239
|
-
const { stdout, stderr } = await
|
|
254
|
+
const { stdout, stderr } = await git(["push"], targetRoot);
|
|
240
255
|
return NextResponse.json({ success: true, output: stdout + stderr });
|
|
241
256
|
}
|
|
242
257
|
|
|
243
258
|
if (action === "checkout") {
|
|
244
259
|
const { branchName } = body;
|
|
245
|
-
if (!branchName) {
|
|
246
|
-
return NextResponse.json({ error: "Missing branchName parameter" }, { status: 400 });
|
|
260
|
+
if (!isValidBranchName(branchName)) {
|
|
261
|
+
return NextResponse.json({ error: "Missing or invalid branchName parameter" }, { status: 400 });
|
|
247
262
|
}
|
|
248
|
-
const { stdout, stderr } = await
|
|
263
|
+
const { stdout, stderr } = await git(["checkout", branchName], targetRoot);
|
|
249
264
|
return NextResponse.json({ success: true, output: stdout + stderr });
|
|
250
265
|
}
|
|
251
266
|
|
|
252
267
|
if (action === "create_branch") {
|
|
253
268
|
const { branchName } = body;
|
|
254
|
-
if (!branchName) {
|
|
255
|
-
return NextResponse.json({ error: "Missing branchName parameter" }, { status: 400 });
|
|
269
|
+
if (!isValidBranchName(branchName)) {
|
|
270
|
+
return NextResponse.json({ error: "Missing or invalid branchName parameter" }, { status: 400 });
|
|
256
271
|
}
|
|
257
|
-
const { stdout, stderr } = await
|
|
272
|
+
const { stdout, stderr } = await git(["checkout", "-b", branchName], targetRoot);
|
|
258
273
|
return NextResponse.json({ success: true, output: stdout + stderr });
|
|
259
274
|
}
|
|
260
275
|
|
|
261
276
|
if (action === "delete_branch") {
|
|
262
277
|
const { branchName, force } = body;
|
|
263
|
-
if (!branchName) {
|
|
264
|
-
return NextResponse.json({ error: "Missing branchName parameter" }, { status: 400 });
|
|
278
|
+
if (!isValidBranchName(branchName)) {
|
|
279
|
+
return NextResponse.json({ error: "Missing or invalid branchName parameter" }, { status: 400 });
|
|
265
280
|
}
|
|
266
281
|
const flag = force ? "-D" : "-d";
|
|
267
|
-
const { stdout, stderr } = await
|
|
282
|
+
const { stdout, stderr } = await git(["branch", flag, branchName], targetRoot);
|
|
268
283
|
return NextResponse.json({ success: true, output: stdout + stderr });
|
|
269
284
|
}
|
|
270
285
|
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { SessionManager } from "@/lib/agents/SessionManager";
|
|
3
3
|
import { promisify } from "util";
|
|
4
|
-
import {
|
|
4
|
+
import { execFile } from "child_process";
|
|
5
5
|
import os from "os";
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const execFilePromise = promisify(execFile);
|
|
8
|
+
|
|
9
|
+
// Real per-process stats for the PTY. Returns null when unavailable (pidusage
|
|
10
|
+
// not installed, or no live pid) so callers report honestly instead of faking it.
|
|
11
|
+
async function getProcessStats(pid: number | undefined): Promise<{ cpu: number; memory: number } | null> {
|
|
12
|
+
if (!pid) return null;
|
|
13
|
+
try {
|
|
14
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
15
|
+
const pidusage = eval("require")("pidusage");
|
|
16
|
+
const stat = await pidusage(pid);
|
|
17
|
+
return { cpu: stat.cpu, memory: stat.memory };
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
8
22
|
|
|
9
23
|
export async function GET(request: Request) {
|
|
10
24
|
try {
|
|
@@ -21,42 +35,45 @@ export async function GET(request: Request) {
|
|
|
21
35
|
return NextResponse.json({ error: "Session not found" }, { status: 404 });
|
|
22
36
|
}
|
|
23
37
|
|
|
24
|
-
// Default response metrics
|
|
38
|
+
// Default response metrics. `available` reports whether these are real
|
|
39
|
+
// measurements (vs. a placeholder when telemetry can't be collected).
|
|
25
40
|
let cpu = "0.0%";
|
|
26
41
|
let memory = "0.0MiB";
|
|
27
42
|
let memoryPercent = "0.0%";
|
|
43
|
+
let available = false;
|
|
28
44
|
|
|
29
45
|
if (session.status === "running") {
|
|
30
46
|
const isDocker = session.type === "claude-docker" || session.type === "aider-docker";
|
|
31
47
|
if (isDocker) {
|
|
32
48
|
try {
|
|
33
|
-
//
|
|
49
|
+
// Real container stats from the Docker daemon.
|
|
34
50
|
const containerName = `neural-loom-session-${id}`;
|
|
35
|
-
const { stdout } = await
|
|
36
|
-
|
|
51
|
+
const { stdout } = await execFilePromise("docker", [
|
|
52
|
+
"stats",
|
|
53
|
+
containerName,
|
|
54
|
+
"--no-stream",
|
|
55
|
+
"--format",
|
|
56
|
+
"{{.CPUPerc}},{{.MemUsage}},{{.MemPerc}}",
|
|
57
|
+
]);
|
|
58
|
+
const parts = stdout.toString().trim().split(",");
|
|
37
59
|
if (parts.length >= 3) {
|
|
38
60
|
cpu = parts[0].trim();
|
|
39
|
-
memory = parts[1].trim().split(" / ")[0]; //
|
|
61
|
+
memory = parts[1].trim().split(" / ")[0]; // used part, e.g. "142.2MiB"
|
|
40
62
|
memoryPercent = parts[2].trim();
|
|
63
|
+
available = true;
|
|
41
64
|
}
|
|
42
65
|
} catch {
|
|
43
|
-
// Docker
|
|
44
|
-
const simulatedCpu = (Math.random() * 3 + 1).toFixed(1) + "%";
|
|
45
|
-
const simulatedMem = (130 + Math.random() * 20).toFixed(1) + "MiB";
|
|
46
|
-
const simulatedMemPerc = (1.5 + Math.random() * 0.5).toFixed(2) + "%";
|
|
47
|
-
cpu = simulatedCpu;
|
|
48
|
-
memory = simulatedMem;
|
|
49
|
-
memoryPercent = simulatedMemPerc;
|
|
66
|
+
// Docker daemon/container not reachable — leave defaults, available=false.
|
|
50
67
|
}
|
|
51
68
|
} else {
|
|
52
|
-
//
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
// Real per-process stats for the local/SSH PTY process.
|
|
70
|
+
const stats = await getProcessStats(session.getPid());
|
|
71
|
+
if (stats) {
|
|
72
|
+
cpu = stats.cpu.toFixed(1) + "%";
|
|
73
|
+
memory = (stats.memory / (1024 * 1024)).toFixed(1) + "MiB";
|
|
74
|
+
memoryPercent = ((stats.memory / os.totalmem()) * 100).toFixed(2) + "%";
|
|
75
|
+
available = true;
|
|
76
|
+
}
|
|
60
77
|
}
|
|
61
78
|
}
|
|
62
79
|
|
|
@@ -68,6 +85,7 @@ export async function GET(request: Request) {
|
|
|
68
85
|
cpu,
|
|
69
86
|
memory,
|
|
70
87
|
memoryPercent,
|
|
88
|
+
available,
|
|
71
89
|
});
|
|
72
90
|
} catch (error) {
|
|
73
91
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { ConnectionManager } from "@/lib/sql/ConnectionManager";
|
|
3
|
-
|
|
4
|
-
const isMutation = (sql: string): boolean => {
|
|
5
|
-
const clean = sql.trim().toLowerCase();
|
|
6
|
-
return (
|
|
7
|
-
clean.startsWith("insert") ||
|
|
8
|
-
clean.startsWith("update") ||
|
|
9
|
-
clean.startsWith("delete") ||
|
|
10
|
-
clean.startsWith("drop") ||
|
|
11
|
-
clean.startsWith("alter") ||
|
|
12
|
-
clean.startsWith("create") ||
|
|
13
|
-
clean.startsWith("truncate") ||
|
|
14
|
-
clean.startsWith("replace")
|
|
15
|
-
);
|
|
16
|
-
};
|
|
3
|
+
import { isReadOnlyViolation } from "@/lib/sql/sqlGuard";
|
|
17
4
|
|
|
18
5
|
export async function POST(request: Request) {
|
|
19
6
|
try {
|
|
@@ -33,7 +20,7 @@ export async function POST(request: Request) {
|
|
|
33
20
|
|
|
34
21
|
const { adapter, profile } = active;
|
|
35
22
|
|
|
36
|
-
if (profile.readOnly &&
|
|
23
|
+
if (profile.readOnly && isReadOnlyViolation(sql)) {
|
|
37
24
|
return NextResponse.json({
|
|
38
25
|
error: "This connection profile is configured as Read-Only. Modifications are disabled."
|
|
39
26
|
}, { status: 403 });
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import { getConfigDir } from "@/lib/config";
|
|
4
5
|
|
|
5
6
|
export async function GET() {
|
|
6
7
|
try {
|
|
7
|
-
const historyFile = path.join(
|
|
8
|
+
const historyFile = path.join(getConfigDir(), "token-history.json");
|
|
8
9
|
if (!fs.existsSync(historyFile)) {
|
|
9
10
|
return NextResponse.json({ success: true, history: [] });
|
|
10
11
|
}
|