atoo-studio 0.0.1 → 0.0.2
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/LICENSE +21 -0
- package/README.github.md +322 -0
- package/README.md +112 -0
- package/README.npm.md +112 -0
- package/bin/atoo-studio.js +90 -0
- package/dist/src/agents/claude-code-terminal/adapter.d.ts +42 -0
- package/dist/src/agents/claude-code-terminal/adapter.js +166 -0
- package/dist/src/agents/claude-code-terminal/index.d.ts +13 -0
- package/dist/src/agents/claude-code-terminal/index.js +45 -0
- package/dist/src/agents/claude-code-terminal/spawner.d.ts +9 -0
- package/dist/src/agents/claude-code-terminal/spawner.js +37 -0
- package/dist/src/agents/claude-code-terminal-chatro/adapter.d.ts +51 -0
- package/dist/src/agents/claude-code-terminal-chatro/adapter.js +301 -0
- package/dist/src/agents/claude-code-terminal-chatro/index.d.ts +13 -0
- package/dist/src/agents/claude-code-terminal-chatro/index.js +45 -0
- package/dist/src/agents/claude-code-terminal-chatro/jsonl-watcher.d.ts +67 -0
- package/dist/src/agents/claude-code-terminal-chatro/jsonl-watcher.js +431 -0
- package/dist/src/agents/claude-code-terminal-chatro/spawner.d.ts +9 -0
- package/dist/src/agents/claude-code-terminal-chatro/spawner.js +37 -0
- package/dist/src/agents/codex-terminal/adapter.d.ts +40 -0
- package/dist/src/agents/codex-terminal/adapter.js +160 -0
- package/dist/src/agents/codex-terminal/index.d.ts +13 -0
- package/dist/src/agents/codex-terminal/index.js +47 -0
- package/dist/src/agents/codex-terminal/spawner.d.ts +9 -0
- package/dist/src/agents/codex-terminal/spawner.js +56 -0
- package/dist/src/agents/codex-terminal-chatro/adapter.d.ts +58 -0
- package/dist/src/agents/codex-terminal-chatro/adapter.js +266 -0
- package/dist/src/agents/codex-terminal-chatro/index.d.ts +13 -0
- package/dist/src/agents/codex-terminal-chatro/index.js +50 -0
- package/dist/src/agents/codex-terminal-chatro/jsonl-watcher.d.ts +36 -0
- package/dist/src/agents/codex-terminal-chatro/jsonl-watcher.js +205 -0
- package/dist/src/agents/codex-terminal-chatro/spawner.d.ts +9 -0
- package/dist/src/agents/codex-terminal-chatro/spawner.js +57 -0
- package/dist/src/agents/lib/chain-builder.d.ts +21 -0
- package/dist/src/agents/lib/chain-builder.js +139 -0
- package/dist/src/agents/lib/claude/fs-sessions.d.ts +31 -0
- package/dist/src/agents/lib/claude/fs-sessions.js +329 -0
- package/dist/src/agents/lib/claude/jsonl-writer.d.ts +32 -0
- package/dist/src/agents/lib/claude/jsonl-writer.js +342 -0
- package/dist/src/agents/lib/claude/workspace-trust.d.ts +1 -0
- package/dist/src/agents/lib/claude/workspace-trust.js +29 -0
- package/dist/src/agents/lib/codex/fs-sessions.d.ts +34 -0
- package/dist/src/agents/lib/codex/fs-sessions.js +255 -0
- package/dist/src/agents/lib/codex/jsonl-mapper.d.ts +11 -0
- package/dist/src/agents/lib/codex/jsonl-mapper.js +154 -0
- package/dist/src/agents/lib/codex/jsonl-writer.d.ts +8 -0
- package/dist/src/agents/lib/codex/jsonl-writer.js +440 -0
- package/dist/src/agents/lib/fs-tracking.d.ts +36 -0
- package/dist/src/agents/lib/fs-tracking.js +109 -0
- package/dist/src/agents/lib/pty-activity-tracker.d.ts +37 -0
- package/dist/src/agents/lib/pty-activity-tracker.js +105 -0
- package/dist/src/agents/lib/session-id-utils.d.ts +46 -0
- package/dist/src/agents/lib/session-id-utils.js +147 -0
- package/dist/src/agents/lib/session-precreate.d.ts +17 -0
- package/dist/src/agents/lib/session-precreate.js +177 -0
- package/dist/src/agents/registry.d.ts +72 -0
- package/dist/src/agents/registry.js +337 -0
- package/dist/src/agents/types.d.ts +135 -0
- package/dist/src/agents/types.js +1 -0
- package/dist/src/auth/crypto-key.d.ts +6 -0
- package/dist/src/auth/crypto-key.js +45 -0
- package/dist/src/auth/middleware.d.ts +18 -0
- package/dist/src/auth/middleware.js +54 -0
- package/dist/src/auth/password.d.ts +2 -0
- package/dist/src/auth/password.js +12 -0
- package/dist/src/auth/session.d.ts +10 -0
- package/dist/src/auth/session.js +33 -0
- package/dist/src/auth/totp.d.ts +12 -0
- package/dist/src/auth/totp.js +61 -0
- package/dist/src/auth/webauthn.d.ts +6 -0
- package/dist/src/auth/webauthn.js +117 -0
- package/dist/src/config.d.ts +10 -0
- package/dist/src/config.js +16 -0
- package/dist/src/database/connection-manager.d.ts +25 -0
- package/dist/src/database/connection-manager.js +211 -0
- package/dist/src/database/discovery/container.d.ts +6 -0
- package/dist/src/database/discovery/container.js +226 -0
- package/dist/src/database/discovery/env-parser.d.ts +9 -0
- package/dist/src/database/discovery/env-parser.js +525 -0
- package/dist/src/database/discovery/local-files.d.ts +6 -0
- package/dist/src/database/discovery/local-files.js +58 -0
- package/dist/src/database/discovery/port-scan.d.ts +7 -0
- package/dist/src/database/discovery/port-scan.js +61 -0
- package/dist/src/database/drivers/cassandra.d.ts +12 -0
- package/dist/src/database/drivers/cassandra.js +91 -0
- package/dist/src/database/drivers/clickhouse.d.ts +11 -0
- package/dist/src/database/drivers/clickhouse.js +127 -0
- package/dist/src/database/drivers/elasticsearch.d.ts +12 -0
- package/dist/src/database/drivers/elasticsearch.js +169 -0
- package/dist/src/database/drivers/influxdb.d.ts +14 -0
- package/dist/src/database/drivers/influxdb.js +194 -0
- package/dist/src/database/drivers/memcached.d.ts +11 -0
- package/dist/src/database/drivers/memcached.js +117 -0
- package/dist/src/database/drivers/mongodb.d.ts +12 -0
- package/dist/src/database/drivers/mongodb.js +128 -0
- package/dist/src/database/drivers/mysql.d.ts +11 -0
- package/dist/src/database/drivers/mysql.js +112 -0
- package/dist/src/database/drivers/neo4j.d.ts +11 -0
- package/dist/src/database/drivers/neo4j.js +158 -0
- package/dist/src/database/drivers/postgresql.d.ts +11 -0
- package/dist/src/database/drivers/postgresql.js +133 -0
- package/dist/src/database/drivers/redis.d.ts +11 -0
- package/dist/src/database/drivers/redis.js +91 -0
- package/dist/src/database/drivers/sqlite.d.ts +10 -0
- package/dist/src/database/drivers/sqlite.js +100 -0
- package/dist/src/database/query-stream.d.ts +5 -0
- package/dist/src/database/query-stream.js +75 -0
- package/dist/src/database/types.d.ts +71 -0
- package/dist/src/database/types.js +1 -0
- package/dist/src/events/index.d.ts +3 -0
- package/dist/src/events/index.js +3 -0
- package/dist/src/events/types.d.ts +214 -0
- package/dist/src/events/types.js +22 -0
- package/dist/src/events/wire.d.ts +114 -0
- package/dist/src/events/wire.js +296 -0
- package/dist/src/fs-monitor-types.d.ts +24 -0
- package/dist/src/fs-monitor-types.js +1 -0
- package/dist/src/fs-monitor.d.ts +80 -0
- package/dist/src/fs-monitor.js +637 -0
- package/dist/src/handlers/auth.d.ts +1 -0
- package/dist/src/handlers/auth.js +170 -0
- package/dist/src/handlers/changes.d.ts +1 -0
- package/dist/src/handlers/changes.js +203 -0
- package/dist/src/handlers/containers.d.ts +12 -0
- package/dist/src/handlers/containers.js +379 -0
- package/dist/src/handlers/databases.d.ts +3 -0
- package/dist/src/handlers/databases.js +327 -0
- package/dist/src/handlers/environments.d.ts +3 -0
- package/dist/src/handlers/environments.js +286 -0
- package/dist/src/handlers/github.d.ts +1 -0
- package/dist/src/handlers/github.js +153 -0
- package/dist/src/handlers/projects.d.ts +1 -0
- package/dist/src/handlers/projects.js +895 -0
- package/dist/src/handlers/ssh.d.ts +1 -0
- package/dist/src/handlers/ssh.js +162 -0
- package/dist/src/handlers/users.d.ts +1 -0
- package/dist/src/handlers/users.js +195 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +228 -0
- package/dist/src/mcp/config.d.ts +32 -0
- package/dist/src/mcp/config.js +227 -0
- package/dist/src/mcp/server.d.ts +1 -0
- package/dist/src/mcp/server.js +574 -0
- package/dist/src/serial/cuse-device.d.ts +19 -0
- package/dist/src/serial/cuse-device.js +260 -0
- package/dist/src/serial/manager.d.ts +63 -0
- package/dist/src/serial/manager.js +206 -0
- package/dist/src/serial/pty-pair.d.ts +16 -0
- package/dist/src/serial/pty-pair.js +68 -0
- package/dist/src/services/fs-browser.d.ts +14 -0
- package/dist/src/services/fs-browser.js +98 -0
- package/dist/src/services/git-ops.d.ts +78 -0
- package/dist/src/services/git-ops.js +288 -0
- package/dist/src/services/github-ops.d.ts +104 -0
- package/dist/src/services/github-ops.js +192 -0
- package/dist/src/services/obfuscation.d.ts +2 -0
- package/dist/src/services/obfuscation.js +16 -0
- package/dist/src/services/preview/headless-backend.d.ts +62 -0
- package/dist/src/services/preview/headless-backend.js +698 -0
- package/dist/src/services/preview/injected-scripts.d.ts +9 -0
- package/dist/src/services/preview/injected-scripts.js +232 -0
- package/dist/src/services/preview/preview-backend.d.ts +92 -0
- package/dist/src/services/preview/preview-backend.js +15 -0
- package/dist/src/services/preview/universal-setter.d.ts +7 -0
- package/dist/src/services/preview/universal-setter.js +46 -0
- package/dist/src/services/preview-manager.d.ts +50 -0
- package/dist/src/services/preview-manager.js +216 -0
- package/dist/src/services/project-watcher.d.ts +6 -0
- package/dist/src/services/project-watcher.js +307 -0
- package/dist/src/services/remote-fs-browser.d.ts +11 -0
- package/dist/src/services/remote-fs-browser.js +50 -0
- package/dist/src/services/remote-git-ops.d.ts +71 -0
- package/dist/src/services/remote-git-ops.js +215 -0
- package/dist/src/services/session-search.d.ts +56 -0
- package/dist/src/services/session-search.js +303 -0
- package/dist/src/services/ssh-manager.d.ts +44 -0
- package/dist/src/services/ssh-manager.js +359 -0
- package/dist/src/session-writer.d.ts +9 -0
- package/dist/src/session-writer.js +66 -0
- package/dist/src/spawner.d.ts +56 -0
- package/dist/src/spawner.js +135 -0
- package/dist/src/state/db.d.ts +214 -0
- package/dist/src/state/db.js +897 -0
- package/dist/src/state/store.d.ts +37 -0
- package/dist/src/state/store.js +108 -0
- package/dist/src/state/types.d.ts +13 -0
- package/dist/src/state/types.js +1 -0
- package/dist/src/web/devtools-proxy.d.ts +7 -0
- package/dist/src/web/devtools-proxy.js +176 -0
- package/dist/src/web/port-proxy.d.ts +15 -0
- package/dist/src/web/port-proxy.js +124 -0
- package/dist/src/web/preview-ws.d.ts +5 -0
- package/dist/src/web/preview-ws.js +207 -0
- package/dist/src/web/server.d.ts +6 -0
- package/dist/src/web/server.js +1694 -0
- package/dist/src/ws/agent-ws.d.ts +5 -0
- package/dist/src/ws/agent-ws.js +93 -0
- package/frontend/dist/assets/_basePickBy-B-LibQ4-.js +1 -0
- package/frontend/dist/assets/_baseUniq-CprifHap.js +1 -0
- package/frontend/dist/assets/_createAssigner-ByDUqGii.js +1 -0
- package/frontend/dist/assets/abap-DuT-3z4x.js +1 -0
- package/frontend/dist/assets/addon-fit-CxQet2ja.js +1 -0
- package/frontend/dist/assets/addon-web-links-D_jRkPIl.js +1 -0
- package/frontend/dist/assets/apex-B-em86xX.js +1 -0
- package/frontend/dist/assets/api-SUPuHhSY.js +2 -0
- package/frontend/dist/assets/arc-Z0_eVteO.js +1 -0
- package/frontend/dist/assets/architecture-PBZL5I3N-hvVXGhqd.js +1 -0
- package/frontend/dist/assets/architectureDiagram-2XIMDMQ5-DiHPxX4j.js +36 -0
- package/frontend/dist/assets/array-CwG8vNfn.js +1 -0
- package/frontend/dist/assets/auth-store-R7eW5SVu.js +1 -0
- package/frontend/dist/assets/azcli-Bg9wQloi.js +1 -0
- package/frontend/dist/assets/bat-BM46z99L.js +1 -0
- package/frontend/dist/assets/bicep-DcBsJUfh.js +2 -0
- package/frontend/dist/assets/blockDiagram-WCTKOSBZ-C40u_hLo.js +132 -0
- package/frontend/dist/assets/c4Diagram-IC4MRINW-Ct7LjWFQ.js +10 -0
- package/frontend/dist/assets/cameligo-zw7JTtim.js +1 -0
- package/frontend/dist/assets/channel-ClCsE6HN.js +1 -0
- package/frontend/dist/assets/chunk-4BX2VUAB-zZ6P90VO.js +1 -0
- package/frontend/dist/assets/chunk-55IACEB6-DXllTDQl.js +1 -0
- package/frontend/dist/assets/chunk-7E7YKBS2-7zRaOLjj.js +1 -0
- package/frontend/dist/assets/chunk-7R4GIKGN-Csst1274.js +80 -0
- package/frontend/dist/assets/chunk-C72U2L5F-_JbQPbLN.js +1 -0
- package/frontend/dist/assets/chunk-CFjPhJqf.js +1 -0
- package/frontend/dist/assets/chunk-EGIJ26TM-B--aFyPw.js +1 -0
- package/frontend/dist/assets/chunk-FMBD7UC4-DVR34RNb.js +15 -0
- package/frontend/dist/assets/chunk-GEFDOKGD-CnmN6cC8.js +2 -0
- package/frontend/dist/assets/chunk-JSJVCQXG-CWxHBzeJ.js +1 -0
- package/frontend/dist/assets/chunk-KX2RTZJC-DkRk56s7.js +1 -0
- package/frontend/dist/assets/chunk-KYZI473N-DCCsG2dK.js +53 -0
- package/frontend/dist/assets/chunk-L3YUKLVL-C-DkZTMr.js +1 -0
- package/frontend/dist/assets/chunk-MX3YWQON-OUdzv5sZ.js +1 -0
- package/frontend/dist/assets/chunk-NQ4KR5QH-Bpu9FsM7.js +220 -0
- package/frontend/dist/assets/chunk-O4XLMI2P-BMLK6_ib.js +7 -0
- package/frontend/dist/assets/chunk-OZEHJAEY-CNNiJtG0.js +1 -0
- package/frontend/dist/assets/chunk-PQ6SQG4A-evVHD3KM.js +1 -0
- package/frontend/dist/assets/chunk-PU5JKC2W-DPFTYuvl.js +70 -0
- package/frontend/dist/assets/chunk-QZHKN3VN-JRdddPvu.js +1 -0
- package/frontend/dist/assets/chunk-R5LLSJPH-CHQzVVOV.js +1 -0
- package/frontend/dist/assets/chunk-WL4C6EOR-BNFU6IIi.js +189 -0
- package/frontend/dist/assets/chunk-XIRO2GV7-98T93G85.js +1 -0
- package/frontend/dist/assets/chunk-XZSTWKYB-BcW3cyNp.js +94 -0
- package/frontend/dist/assets/chunk-YBOYWFTD-BgKO1qAJ.js +1 -0
- package/frontend/dist/assets/classDiagram-VBA2DB6C-DikXzgcD.js +1 -0
- package/frontend/dist/assets/classDiagram-v2-RAHNMMFH-D7E3tQUK.js +1 -0
- package/frontend/dist/assets/clojure-FspFoNNQ.js +1 -0
- package/frontend/dist/assets/clone-mOXuZa7C.js +1 -0
- package/frontend/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/frontend/dist/assets/coffee-13n8Bk2W.js +1 -0
- package/frontend/dist/assets/cose-bilkent-S5V4N54A-zUOWQqLe.js +1 -0
- package/frontend/dist/assets/cpp-BVm2xGEs.js +1 -0
- package/frontend/dist/assets/csharp-D2kAWmUm.js +1 -0
- package/frontend/dist/assets/csp-Ezvgpf0e.js +1 -0
- package/frontend/dist/assets/css-CYxRwcFy.js +3 -0
- package/frontend/dist/assets/css.worker-Cd5h-ZOL.js +89 -0
- package/frontend/dist/assets/cssMode-CrXej49V.js +1 -0
- package/frontend/dist/assets/cypher-jg3SGErc.js +1 -0
- package/frontend/dist/assets/cytoscape.esm-kyyvzxNV.js +321 -0
- package/frontend/dist/assets/dagre-DH4bgZO7.js +1 -0
- package/frontend/dist/assets/dagre-KLK3FWXG-DNSqDkwT.js +4 -0
- package/frontend/dist/assets/dart-179jqhK4.js +1 -0
- package/frontend/dist/assets/defaultLocale-Dda4OpKy.js +1 -0
- package/frontend/dist/assets/diagram-E7M64L7V-RqPNT5Vs.js +24 -0
- package/frontend/dist/assets/diagram-IFDJBPK2-B-5NRyaE.js +43 -0
- package/frontend/dist/assets/diagram-P4PSJMXO-BrP69Hk0.js +24 -0
- package/frontend/dist/assets/dist-CU_Nb1G5.js +1 -0
- package/frontend/dist/assets/dockerfile-CIAtSGxS.js +1 -0
- package/frontend/dist/assets/ecl-CGVKfDxD.js +1 -0
- package/frontend/dist/assets/editor-Br_kD0ds.css +1 -0
- package/frontend/dist/assets/editor.api2-YXkDn0Gm.js +872 -0
- package/frontend/dist/assets/editor.main-fBaXZjJ0.js +6 -0
- package/frontend/dist/assets/elixir-BZ-6w0y3.js +1 -0
- package/frontend/dist/assets/erDiagram-INFDFZHY-BYiB9NYg.js +70 -0
- package/frontend/dist/assets/flow9-CVuOjTMv.js +1 -0
- package/frontend/dist/assets/flowDiagram-PKNHOUZH-Cwq47rsR.js +162 -0
- package/frontend/dist/assets/freemarker2-DM-pztJU.js +3 -0
- package/frontend/dist/assets/fsharp-q0pGJYr6.js +1 -0
- package/frontend/dist/assets/ganttDiagram-A5KZAMGK-Dnx3szD9.js +292 -0
- package/frontend/dist/assets/gitGraph-HDMCJU4V-COlTQ7bA.js +1 -0
- package/frontend/dist/assets/gitGraphDiagram-K3NZZRJ6-BaUxboNc.js +65 -0
- package/frontend/dist/assets/go-dzSPfdEO.js +1 -0
- package/frontend/dist/assets/graphlib-kEFlkt3U.js +1 -0
- package/frontend/dist/assets/graphql-CG4OUoEV.js +1 -0
- package/frontend/dist/assets/handlebars-BbK53Vec.js +1 -0
- package/frontend/dist/assets/hcl-Cy14JPk3.js +1 -0
- package/frontend/dist/assets/html-DYtTQNOG.js +1 -0
- package/frontend/dist/assets/html.worker-BjVEKLoU.js +502 -0
- package/frontend/dist/assets/htmlMode-C6GTouth.js +1 -0
- package/frontend/dist/assets/index-DMLxes_u.js +157 -0
- package/frontend/dist/assets/index-DmzeqkB1.css +1 -0
- package/frontend/dist/assets/info-3K5VOQVL-DBtHyA4C.js +1 -0
- package/frontend/dist/assets/infoDiagram-LFFYTUFH-yBXLgMPI.js +2 -0
- package/frontend/dist/assets/ini-Pbg8HGVD.js +1 -0
- package/frontend/dist/assets/init-D6KNwrax.js +1 -0
- package/frontend/dist/assets/ishikawaDiagram-PHBUUO56-Bld4two_.js +70 -0
- package/frontend/dist/assets/java-BmVu6Qrl.js +1 -0
- package/frontend/dist/assets/javascript-PbfQEdcJ.js +1 -0
- package/frontend/dist/assets/journeyDiagram-4ABVD52K-4HyMd4R2.js +139 -0
- package/frontend/dist/assets/json.worker-DqU5Wxnl.js +58 -0
- package/frontend/dist/assets/jsonMode-CASsGppE.js +7 -0
- package/frontend/dist/assets/julia-3cGnieBq.js +1 -0
- package/frontend/dist/assets/kanban-definition-K7BYSVSG-DpgsZmpG.js +89 -0
- package/frontend/dist/assets/katex-CEw3x5bf.js +261 -0
- package/frontend/dist/assets/kotlin-BuWkVcfV.js +1 -0
- package/frontend/dist/assets/less-CJ_VPy2C.js +2 -0
- package/frontend/dist/assets/lexon-BygAuZPu.js +1 -0
- package/frontend/dist/assets/line-CA_wh_TY.js +1 -0
- package/frontend/dist/assets/linear-BAcLW45z.js +1 -0
- package/frontend/dist/assets/liquid-kz84dle6.js +1 -0
- package/frontend/dist/assets/lspLanguageFeatures-C7hAHFn1.js +4 -0
- package/frontend/dist/assets/lua-C8Xs3dCx.js +1 -0
- package/frontend/dist/assets/m3-DTJeKBk4.js +1 -0
- package/frontend/dist/assets/markdown-QCgx8JqZ.js +1 -0
- package/frontend/dist/assets/math-D0YcMJAn.js +1 -0
- package/frontend/dist/assets/mdx-yRw0ap-E.js +1 -0
- package/frontend/dist/assets/mermaid-parser.core-DAeTodBQ.js +4 -0
- package/frontend/dist/assets/mindmap-definition-YRQLILUH-CoNlFyVl.js +68 -0
- package/frontend/dist/assets/mips-DopWaYgE.js +1 -0
- package/frontend/dist/assets/monaco.contribution-DeY0Qei-.js +2 -0
- package/frontend/dist/assets/msdax-BDis4ARV.js +1 -0
- package/frontend/dist/assets/mysql-BV6MLsOI.js +1 -0
- package/frontend/dist/assets/objective-c-B1UuzKs6.js +1 -0
- package/frontend/dist/assets/ordinal-jM7S0YHN.js +1 -0
- package/frontend/dist/assets/packet-RMMSAZCW-FF6-Tmai.js +1 -0
- package/frontend/dist/assets/pascal-BkvESCrc.js +1 -0
- package/frontend/dist/assets/pascaligo-lTy0kZYr.js +1 -0
- package/frontend/dist/assets/path-DNPd7Py7.js +1 -0
- package/frontend/dist/assets/perl-CrtUPXLV.js +1 -0
- package/frontend/dist/assets/pgsql-B9IbNWx2.js +1 -0
- package/frontend/dist/assets/php-CXvQBY2p.js +1 -0
- package/frontend/dist/assets/pie-UPGHQEXC-CFvXY2o-.js +1 -0
- package/frontend/dist/assets/pieDiagram-SKSYHLDU-CM_hbCcn.js +30 -0
- package/frontend/dist/assets/pla-DxBxuqWu.js +1 -0
- package/frontend/dist/assets/postiats-OkEuT5YF.js +1 -0
- package/frontend/dist/assets/powerquery-CMx5Tq4K.js +1 -0
- package/frontend/dist/assets/powershell-CstRxrEc.js +1 -0
- package/frontend/dist/assets/preload-helper-D4M6sveU.js +1 -0
- package/frontend/dist/assets/protobuf-Bx0Z-uRj.js +2 -0
- package/frontend/dist/assets/pug--W8vanWl.js +1 -0
- package/frontend/dist/assets/python-DA0rnlw3.js +1 -0
- package/frontend/dist/assets/qsharp-CRtr0YbN.js +1 -0
- package/frontend/dist/assets/quadrantDiagram-337W2JSQ-B3n3IUhC.js +7 -0
- package/frontend/dist/assets/r-C6E1d6iv.js +1 -0
- package/frontend/dist/assets/radar-KQ55EAFF-MPZu7SdX.js +1 -0
- package/frontend/dist/assets/razor-yd73uata.js +1 -0
- package/frontend/dist/assets/redis-Dx13voP3.js +1 -0
- package/frontend/dist/assets/redshift-D66HwlyV.js +1 -0
- package/frontend/dist/assets/requirementDiagram-Z7DCOOCP-CorP7L7F.js +73 -0
- package/frontend/dist/assets/restructuredtext-DQT2NKJ2.js +1 -0
- package/frontend/dist/assets/rough.esm-DxAX5Vpo.js +1 -0
- package/frontend/dist/assets/ruby-iFXI8hwH.js +1 -0
- package/frontend/dist/assets/rust-CSKiei34.js +1 -0
- package/frontend/dist/assets/sankeyDiagram-WA2Y5GQK-RDx6Bd-B.js +10 -0
- package/frontend/dist/assets/sb-Bo3ttdP2.js +1 -0
- package/frontend/dist/assets/scala-BC1D-Nxp.js +1 -0
- package/frontend/dist/assets/scheme-Z4OAo4Lv.js +1 -0
- package/frontend/dist/assets/scss-BvrdPs6B.js +3 -0
- package/frontend/dist/assets/sequenceDiagram-2WXFIKYE-JMqJSFq6.js +145 -0
- package/frontend/dist/assets/shell-Bh_aCyF-.js +1 -0
- package/frontend/dist/assets/solidity-CWHj6tSe.js +1 -0
- package/frontend/dist/assets/sophia-raoNtKtm.js +1 -0
- package/frontend/dist/assets/sparql-XzmoGnue.js +1 -0
- package/frontend/dist/assets/sql-BD0i9Gvg.js +1 -0
- package/frontend/dist/assets/src-Bn-kKzs7.js +1 -0
- package/frontend/dist/assets/st-DtVKyms6.js +1 -0
- package/frontend/dist/assets/stateDiagram-RAJIS63D-CgFfENdy.js +1 -0
- package/frontend/dist/assets/stateDiagram-v2-FVOUBMTO-C4Hh2P-U.js +1 -0
- package/frontend/dist/assets/swift--UZs77wT.js +1 -0
- package/frontend/dist/assets/systemverilog-CDnBSWUd.js +1 -0
- package/frontend/dist/assets/tcl-DdCEuTHZ.js +1 -0
- package/frontend/dist/assets/timeline-definition-YZTLITO2-BnatPBR5.js +61 -0
- package/frontend/dist/assets/treemap-KZPCXAKY-qb1Pl9la.js +1 -0
- package/frontend/dist/assets/ts.worker-DyPAEIuH.js +67719 -0
- package/frontend/dist/assets/tsMode-iuvyEpyO.js +11 -0
- package/frontend/dist/assets/twig-SSL-Altf.js +1 -0
- package/frontend/dist/assets/typescript-17918Hud.js +1 -0
- package/frontend/dist/assets/typespec-BT7S0ETg.js +1 -0
- package/frontend/dist/assets/vb-CrIgucua.js +1 -0
- package/frontend/dist/assets/vennDiagram-LZ73GAT5-DygS4Zzd.js +34 -0
- package/frontend/dist/assets/wgsl-BeKc3oEp.js +298 -0
- package/frontend/dist/assets/workers-DTfwKVoM.js +1 -0
- package/frontend/dist/assets/xml-CBMr_Wbw.js +1 -0
- package/frontend/dist/assets/xterm-BrP-ENHg.css +1 -0
- package/frontend/dist/assets/xterm-CBX2m0YM.js +36 -0
- package/frontend/dist/assets/xychartDiagram-JWTSCODW-D6wY1Jwd.js +7 -0
- package/frontend/dist/assets/yaml-CTjCH7Bv.js +1 -0
- package/frontend/dist/fonts/inter-300.ttf +0 -0
- package/frontend/dist/fonts/inter-400.ttf +0 -0
- package/frontend/dist/fonts/inter-500.ttf +0 -0
- package/frontend/dist/fonts/inter-600.ttf +0 -0
- package/frontend/dist/fonts/inter-700.ttf +0 -0
- package/frontend/dist/index.html +49 -0
- package/frontend/dist/logo_192x192.png +0 -0
- package/frontend/dist/logo_32x32.png +0 -0
- package/frontend/dist/logo_512x512.png +0 -0
- package/frontend/dist/logo_64x64.png +0 -0
- package/frontend/dist/logobg_192x192.png +0 -0
- package/frontend/dist/logobg_512x512.png +0 -0
- package/frontend/dist/logobg_64x64.png +0 -0
- package/frontend/dist/manifest.json +25 -0
- package/frontend/dist/sw.js +22 -0
- package/package.json +74 -7
- package/preload/Makefile +12 -0
- package/preload/atoo-studio-preload.c +647 -0
- package/preload/atoo-studio-preload.so +0 -0
- package/setup-cuse.sh +260 -0
- package/setup.sh +81 -0
- package/src/serial/native/binding.gyp +10 -0
- package/src/serial/native/pty_pair.c +222 -0
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
const STORE_DIR = path.join(os.homedir(), '.atoo-studio');
|
|
7
|
+
const DB_PATH = path.join(STORE_DIR, 'atoo-studio.db');
|
|
8
|
+
const OLD_PROJECTS_FILE = path.join(STORE_DIR, 'projects.json');
|
|
9
|
+
class StudioDatabase {
|
|
10
|
+
db;
|
|
11
|
+
constructor() {
|
|
12
|
+
if (!fs.existsSync(STORE_DIR)) {
|
|
13
|
+
fs.mkdirSync(STORE_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
this.db = new Database(DB_PATH);
|
|
16
|
+
this.db.pragma('journal_mode = WAL');
|
|
17
|
+
this.db.pragma('foreign_keys = ON');
|
|
18
|
+
this.db.pragma('busy_timeout = 5000');
|
|
19
|
+
this.initSchema();
|
|
20
|
+
this.migrateProjectsSshColumns();
|
|
21
|
+
this.migrateProjectsParentColumn();
|
|
22
|
+
this.migrateWorktreeHistory();
|
|
23
|
+
this.migrate();
|
|
24
|
+
this.migrateUserManagement();
|
|
25
|
+
this.migrateSessionMetadata();
|
|
26
|
+
this.migrateDatabaseConnections();
|
|
27
|
+
this.migrateProjectChanges();
|
|
28
|
+
}
|
|
29
|
+
initSchema() {
|
|
30
|
+
this.db.exec(`
|
|
31
|
+
CREATE TABLE IF NOT EXISTS environments (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
name TEXT NOT NULL,
|
|
34
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
38
|
+
id TEXT PRIMARY KEY,
|
|
39
|
+
name TEXT NOT NULL,
|
|
40
|
+
path TEXT NOT NULL,
|
|
41
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE IF NOT EXISTS project_environment (
|
|
45
|
+
id TEXT PRIMARY KEY,
|
|
46
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
47
|
+
environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
|
|
48
|
+
linked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
49
|
+
UNIQUE (project_id, environment_id)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE IF NOT EXISTS environment_settings (
|
|
53
|
+
environment_id TEXT PRIMARY KEY REFERENCES environments(id) ON DELETE CASCADE,
|
|
54
|
+
sidebar_width TEXT DEFAULT '260px',
|
|
55
|
+
sidebar_collapsed INTEGER DEFAULT 0
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
CREATE TABLE IF NOT EXISTS project_settings (
|
|
59
|
+
pe_id TEXT PRIMARY KEY REFERENCES project_environment(id) ON DELETE CASCADE,
|
|
60
|
+
settings_json TEXT NOT NULL DEFAULT '{}',
|
|
61
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS ssh_connections (
|
|
65
|
+
id TEXT PRIMARY KEY,
|
|
66
|
+
label TEXT NOT NULL,
|
|
67
|
+
host TEXT NOT NULL,
|
|
68
|
+
port INTEGER NOT NULL DEFAULT 22,
|
|
69
|
+
username TEXT NOT NULL,
|
|
70
|
+
auth_method TEXT NOT NULL,
|
|
71
|
+
password_obfuscated TEXT,
|
|
72
|
+
private_key_obfuscated TEXT,
|
|
73
|
+
passphrase_obfuscated TEXT,
|
|
74
|
+
system_key_path TEXT,
|
|
75
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
76
|
+
);
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
migrate() {
|
|
80
|
+
// Migrate project_environment schema: add `id` column if missing
|
|
81
|
+
this.migrateProjectEnvironmentSchema();
|
|
82
|
+
// Migrate from projects.json if it exists and DB is empty
|
|
83
|
+
if (!fs.existsSync(OLD_PROJECTS_FILE))
|
|
84
|
+
return;
|
|
85
|
+
const envCount = this.db.prepare('SELECT COUNT(*) as cnt FROM environments').get();
|
|
86
|
+
if (envCount.cnt > 0)
|
|
87
|
+
return;
|
|
88
|
+
console.log('[db] Migrating from projects.json...');
|
|
89
|
+
try {
|
|
90
|
+
const data = JSON.parse(fs.readFileSync(OLD_PROJECTS_FILE, 'utf-8'));
|
|
91
|
+
const projects = Array.isArray(data) ? data : [];
|
|
92
|
+
if (projects.length === 0)
|
|
93
|
+
return;
|
|
94
|
+
const envId = uuidv4();
|
|
95
|
+
const txn = this.db.transaction(() => {
|
|
96
|
+
// Create "Default" environment
|
|
97
|
+
this.db.prepare('INSERT INTO environments (id, name) VALUES (?, ?)').run(envId, 'Default');
|
|
98
|
+
this.db.prepare('INSERT INTO environment_settings (environment_id) VALUES (?)').run(envId);
|
|
99
|
+
// Import projects
|
|
100
|
+
for (const p of projects) {
|
|
101
|
+
const projectId = p.id || uuidv4();
|
|
102
|
+
const peId = uuidv4();
|
|
103
|
+
this.db.prepare('INSERT INTO projects (id, name, path, created_at) VALUES (?, ?, ?, ?)')
|
|
104
|
+
.run(projectId, p.name, p.path, p.createdAt || new Date().toISOString());
|
|
105
|
+
this.db.prepare('INSERT INTO project_environment (id, project_id, environment_id) VALUES (?, ?, ?)')
|
|
106
|
+
.run(peId, projectId, envId);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
txn();
|
|
110
|
+
// Rename old file
|
|
111
|
+
fs.renameSync(OLD_PROJECTS_FILE, OLD_PROJECTS_FILE + '.bak');
|
|
112
|
+
console.log(`[db] Migrated ${projects.length} projects into "Default" environment`);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
console.error('[db] Migration failed:', err);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
migrateProjectsSshColumns() {
|
|
119
|
+
const columns = this.db.prepare("PRAGMA table_info(projects)").all();
|
|
120
|
+
const hasSshCol = columns.some((c) => c.name === 'ssh_connection_id');
|
|
121
|
+
if (hasSshCol || columns.length === 0)
|
|
122
|
+
return;
|
|
123
|
+
this.db.exec(`
|
|
124
|
+
ALTER TABLE projects ADD COLUMN ssh_connection_id TEXT REFERENCES ssh_connections(id);
|
|
125
|
+
ALTER TABLE projects ADD COLUMN remote_path TEXT;
|
|
126
|
+
`);
|
|
127
|
+
console.log('[db] Added ssh_connection_id and remote_path columns to projects');
|
|
128
|
+
}
|
|
129
|
+
migrateProjectsParentColumn() {
|
|
130
|
+
const columns = this.db.prepare("PRAGMA table_info(projects)").all();
|
|
131
|
+
const hasParentCol = columns.some((c) => c.name === 'parent_project_id');
|
|
132
|
+
if (hasParentCol || columns.length === 0)
|
|
133
|
+
return;
|
|
134
|
+
this.db.exec(`
|
|
135
|
+
ALTER TABLE projects ADD COLUMN parent_project_id TEXT REFERENCES projects(id) ON DELETE CASCADE;
|
|
136
|
+
`);
|
|
137
|
+
console.log('[db] Added parent_project_id column to projects');
|
|
138
|
+
}
|
|
139
|
+
migrateWorktreeHistory() {
|
|
140
|
+
this.db.exec(`
|
|
141
|
+
CREATE TABLE IF NOT EXISTS worktree_history (
|
|
142
|
+
id TEXT PRIMARY KEY,
|
|
143
|
+
parent_project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
144
|
+
worktree_path TEXT NOT NULL,
|
|
145
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
146
|
+
UNIQUE (parent_project_id, worktree_path)
|
|
147
|
+
);
|
|
148
|
+
`);
|
|
149
|
+
}
|
|
150
|
+
migrateProjectEnvironmentSchema() {
|
|
151
|
+
// Check if project_environment table has an `id` column
|
|
152
|
+
const columns = this.db.prepare("PRAGMA table_info(project_environment)").all();
|
|
153
|
+
const hasId = columns.some((c) => c.name === 'id');
|
|
154
|
+
if (hasId || columns.length === 0)
|
|
155
|
+
return; // Already migrated or table doesn't exist yet
|
|
156
|
+
console.log('[db] Migrating project_environment schema to add id column...');
|
|
157
|
+
const txn = this.db.transaction(() => {
|
|
158
|
+
// Read existing PE rows
|
|
159
|
+
const peRows = this.db.prepare('SELECT project_id, environment_id, linked_at FROM project_environment').all();
|
|
160
|
+
// Read existing project_settings rows (old composite key schema)
|
|
161
|
+
let psRows = [];
|
|
162
|
+
try {
|
|
163
|
+
const psColumns = this.db.prepare("PRAGMA table_info(project_settings)").all();
|
|
164
|
+
const hasProjectId = psColumns.some((c) => c.name === 'project_id');
|
|
165
|
+
if (hasProjectId) {
|
|
166
|
+
psRows = this.db.prepare('SELECT project_id, environment_id, settings_json, updated_at FROM project_settings').all();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch { }
|
|
170
|
+
// Drop old tables
|
|
171
|
+
this.db.exec('DROP TABLE IF EXISTS project_settings');
|
|
172
|
+
this.db.exec('DROP TABLE IF EXISTS project_environment');
|
|
173
|
+
// Recreate with new schema
|
|
174
|
+
this.db.exec(`
|
|
175
|
+
CREATE TABLE project_environment (
|
|
176
|
+
id TEXT PRIMARY KEY,
|
|
177
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
178
|
+
environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
|
|
179
|
+
linked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
180
|
+
UNIQUE (project_id, environment_id)
|
|
181
|
+
);
|
|
182
|
+
CREATE TABLE project_settings (
|
|
183
|
+
pe_id TEXT PRIMARY KEY REFERENCES project_environment(id) ON DELETE CASCADE,
|
|
184
|
+
settings_json TEXT NOT NULL DEFAULT '{}',
|
|
185
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
186
|
+
);
|
|
187
|
+
`);
|
|
188
|
+
// Re-insert PE rows with generated UUIDs, map old composite key to new ID
|
|
189
|
+
const keyToId = new Map();
|
|
190
|
+
for (const row of peRows) {
|
|
191
|
+
const peId = uuidv4();
|
|
192
|
+
keyToId.set(`${row.project_id}/${row.environment_id}`, peId);
|
|
193
|
+
this.db.prepare('INSERT INTO project_environment (id, project_id, environment_id, linked_at) VALUES (?, ?, ?, ?)')
|
|
194
|
+
.run(peId, row.project_id, row.environment_id, row.linked_at);
|
|
195
|
+
}
|
|
196
|
+
// Migrate project_settings to use pe_id
|
|
197
|
+
for (const ps of psRows) {
|
|
198
|
+
const peId = keyToId.get(`${ps.project_id}/${ps.environment_id}`);
|
|
199
|
+
if (peId) {
|
|
200
|
+
this.db.prepare('INSERT INTO project_settings (pe_id, settings_json, updated_at) VALUES (?, ?, ?)')
|
|
201
|
+
.run(peId, ps.settings_json, ps.updated_at);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
console.log(`[db] Migrated ${peRows.length} project_environment rows, ${psRows.length} project_settings rows`);
|
|
205
|
+
});
|
|
206
|
+
txn();
|
|
207
|
+
}
|
|
208
|
+
// ═══════════════════════════════════════════════════
|
|
209
|
+
// ENVIRONMENTS
|
|
210
|
+
// ═══════════════════════════════════════════════════
|
|
211
|
+
listEnvironments() {
|
|
212
|
+
return this.db.prepare(`
|
|
213
|
+
SELECT e.id, e.name, e.created_at,
|
|
214
|
+
(SELECT COUNT(*) FROM project_environment pe WHERE pe.environment_id = e.id) as project_count
|
|
215
|
+
FROM environments e
|
|
216
|
+
ORDER BY e.created_at
|
|
217
|
+
`).all();
|
|
218
|
+
}
|
|
219
|
+
getEnvironment(id) {
|
|
220
|
+
return this.db.prepare(`
|
|
221
|
+
SELECT e.id, e.name, e.created_at,
|
|
222
|
+
(SELECT COUNT(*) FROM project_environment pe WHERE pe.environment_id = e.id) as project_count
|
|
223
|
+
FROM environments e WHERE e.id = ?
|
|
224
|
+
`).get(id);
|
|
225
|
+
}
|
|
226
|
+
createEnvironment(name) {
|
|
227
|
+
const id = uuidv4();
|
|
228
|
+
this.db.prepare('INSERT INTO environments (id, name) VALUES (?, ?)').run(id, name);
|
|
229
|
+
this.db.prepare('INSERT INTO environment_settings (environment_id) VALUES (?)').run(id);
|
|
230
|
+
return this.getEnvironment(id);
|
|
231
|
+
}
|
|
232
|
+
deleteEnvironment(id) {
|
|
233
|
+
const result = this.db.prepare('DELETE FROM environments WHERE id = ?').run(id);
|
|
234
|
+
return result.changes > 0;
|
|
235
|
+
}
|
|
236
|
+
// ═══════════════════════════════════════════════════
|
|
237
|
+
// PROJECTS
|
|
238
|
+
// ═══════════════════════════════════════════════════
|
|
239
|
+
getProject(id) {
|
|
240
|
+
return this.db.prepare('SELECT id, name, path, created_at, ssh_connection_id, remote_path, parent_project_id FROM projects WHERE id = ?')
|
|
241
|
+
.get(id);
|
|
242
|
+
}
|
|
243
|
+
listAllProjects() {
|
|
244
|
+
return this.db.prepare('SELECT id, name, path, created_at, ssh_connection_id, remote_path, parent_project_id FROM projects ORDER BY created_at')
|
|
245
|
+
.all();
|
|
246
|
+
}
|
|
247
|
+
createProject(name, projectPath, opts) {
|
|
248
|
+
const id = uuidv4();
|
|
249
|
+
const resolved = opts?.sshConnectionId ? projectPath : path.resolve(projectPath);
|
|
250
|
+
this.db.prepare('INSERT INTO projects (id, name, path, ssh_connection_id, remote_path, parent_project_id) VALUES (?, ?, ?, ?, ?, ?)')
|
|
251
|
+
.run(id, name, resolved, opts?.sshConnectionId || null, opts?.remotePath || null, opts?.parentProjectId || null);
|
|
252
|
+
return this.getProject(id);
|
|
253
|
+
}
|
|
254
|
+
deleteProject(id) {
|
|
255
|
+
const result = this.db.prepare('DELETE FROM projects WHERE id = ?').run(id);
|
|
256
|
+
return result.changes > 0;
|
|
257
|
+
}
|
|
258
|
+
getChildProjects(parentId) {
|
|
259
|
+
return this.db.prepare('SELECT id, name, path, created_at, ssh_connection_id, remote_path, parent_project_id FROM projects WHERE parent_project_id = ? ORDER BY created_at')
|
|
260
|
+
.all(parentId);
|
|
261
|
+
}
|
|
262
|
+
findProjectByPath(projectPath) {
|
|
263
|
+
return this.db.prepare('SELECT id, name, path, created_at, ssh_connection_id, remote_path, parent_project_id FROM projects WHERE path = ?')
|
|
264
|
+
.get(projectPath);
|
|
265
|
+
}
|
|
266
|
+
// ═══════════════════════════════════════════════════
|
|
267
|
+
// N:N LINKING
|
|
268
|
+
// ═══════════════════════════════════════════════════
|
|
269
|
+
getProjectsForEnvironment(envId) {
|
|
270
|
+
return this.db.prepare(`
|
|
271
|
+
SELECT p.id, p.name, p.path, p.created_at, p.ssh_connection_id, p.remote_path, p.parent_project_id, pe.id as pe_id
|
|
272
|
+
FROM projects p
|
|
273
|
+
JOIN project_environment pe ON pe.project_id = p.id
|
|
274
|
+
WHERE pe.environment_id = ?
|
|
275
|
+
ORDER BY pe.linked_at
|
|
276
|
+
`).all(envId);
|
|
277
|
+
}
|
|
278
|
+
getEnvironmentsForProject(projectId) {
|
|
279
|
+
return this.db.prepare(`
|
|
280
|
+
SELECT e.id, e.name, e.created_at
|
|
281
|
+
FROM environments e
|
|
282
|
+
JOIN project_environment pe ON pe.environment_id = e.id
|
|
283
|
+
WHERE pe.project_id = ?
|
|
284
|
+
`).all(projectId);
|
|
285
|
+
}
|
|
286
|
+
linkProject(projectId, envId) {
|
|
287
|
+
const existing = this.db.prepare('SELECT id FROM project_environment WHERE project_id = ? AND environment_id = ?').get(projectId, envId);
|
|
288
|
+
if (existing)
|
|
289
|
+
return existing.id;
|
|
290
|
+
const peId = uuidv4();
|
|
291
|
+
this.db.prepare('INSERT INTO project_environment (id, project_id, environment_id) VALUES (?, ?, ?)').run(peId, projectId, envId);
|
|
292
|
+
// Also link child projects (worktrees) to the same environment
|
|
293
|
+
const children = this.getChildProjects(projectId);
|
|
294
|
+
for (const child of children) {
|
|
295
|
+
this.linkProject(child.id, envId);
|
|
296
|
+
}
|
|
297
|
+
return peId;
|
|
298
|
+
}
|
|
299
|
+
unlinkProject(peId) {
|
|
300
|
+
const pe = this.getProjectEnvironment(peId);
|
|
301
|
+
this.db.prepare('DELETE FROM project_environment WHERE id = ?').run(peId);
|
|
302
|
+
// Also unlink child projects from this environment
|
|
303
|
+
if (pe) {
|
|
304
|
+
const children = this.getChildProjects(pe.project_id);
|
|
305
|
+
for (const child of children) {
|
|
306
|
+
const childPe = this.db.prepare('SELECT id FROM project_environment WHERE project_id = ? AND environment_id = ?').get(child.id, pe.environment_id);
|
|
307
|
+
if (childPe) {
|
|
308
|
+
this.unlinkProject(childPe.id);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
getProjectEnvironment(peId) {
|
|
314
|
+
return this.db.prepare('SELECT id, project_id, environment_id FROM project_environment WHERE id = ?').get(peId);
|
|
315
|
+
}
|
|
316
|
+
// ═══════════════════════════════════════════════════
|
|
317
|
+
// ENVIRONMENT SETTINGS
|
|
318
|
+
// ═══════════════════════════════════════════════════
|
|
319
|
+
getEnvironmentSettings(envId) {
|
|
320
|
+
return this.db.prepare('SELECT * FROM environment_settings WHERE environment_id = ?')
|
|
321
|
+
.get(envId);
|
|
322
|
+
}
|
|
323
|
+
updateEnvironmentSettings(envId, partial) {
|
|
324
|
+
const current = this.getEnvironmentSettings(envId);
|
|
325
|
+
if (!current)
|
|
326
|
+
return;
|
|
327
|
+
const updates = [];
|
|
328
|
+
const values = [];
|
|
329
|
+
if (partial.sidebar_width !== undefined) {
|
|
330
|
+
updates.push('sidebar_width = ?');
|
|
331
|
+
values.push(partial.sidebar_width);
|
|
332
|
+
}
|
|
333
|
+
if (partial.sidebar_collapsed !== undefined) {
|
|
334
|
+
updates.push('sidebar_collapsed = ?');
|
|
335
|
+
values.push(partial.sidebar_collapsed);
|
|
336
|
+
}
|
|
337
|
+
if (updates.length === 0)
|
|
338
|
+
return;
|
|
339
|
+
values.push(envId);
|
|
340
|
+
this.db.prepare(`UPDATE environment_settings SET ${updates.join(', ')} WHERE environment_id = ?`).run(...values);
|
|
341
|
+
}
|
|
342
|
+
// ═══════════════════════════════════════════════════
|
|
343
|
+
// PROJECT SETTINGS (per env)
|
|
344
|
+
// ═══════════════════════════════════════════════════
|
|
345
|
+
getProjectSettings(peId) {
|
|
346
|
+
const row = this.db.prepare('SELECT settings_json FROM project_settings WHERE pe_id = ?').get(peId);
|
|
347
|
+
if (!row)
|
|
348
|
+
return {};
|
|
349
|
+
try {
|
|
350
|
+
return JSON.parse(row.settings_json);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return {};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
updateProjectSettings(peId, partial) {
|
|
357
|
+
const current = this.getProjectSettings(peId);
|
|
358
|
+
const merged = { ...current, ...partial };
|
|
359
|
+
const json = JSON.stringify(merged);
|
|
360
|
+
this.db.prepare(`
|
|
361
|
+
INSERT INTO project_settings (pe_id, settings_json, updated_at)
|
|
362
|
+
VALUES (?, ?, datetime('now'))
|
|
363
|
+
ON CONFLICT(pe_id) DO UPDATE SET
|
|
364
|
+
settings_json = excluded.settings_json,
|
|
365
|
+
updated_at = excluded.updated_at
|
|
366
|
+
`).run(peId, json);
|
|
367
|
+
}
|
|
368
|
+
// ═══════════════════════════════════════════════════
|
|
369
|
+
// SSH CONNECTIONS
|
|
370
|
+
// ═══════════════════════════════════════════════════
|
|
371
|
+
createSshConnection(config) {
|
|
372
|
+
const id = uuidv4();
|
|
373
|
+
this.db.prepare(`
|
|
374
|
+
INSERT INTO ssh_connections (id, label, host, port, username, auth_method,
|
|
375
|
+
password_obfuscated, private_key_obfuscated, passphrase_obfuscated, system_key_path)
|
|
376
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
377
|
+
`).run(id, config.label, config.host, config.port, config.username, config.auth_method, config.password_obfuscated || null, config.private_key_obfuscated || null, config.passphrase_obfuscated || null, config.system_key_path || null);
|
|
378
|
+
return this.getSshConnection(id);
|
|
379
|
+
}
|
|
380
|
+
getSshConnection(id) {
|
|
381
|
+
return this.db.prepare('SELECT * FROM ssh_connections WHERE id = ?')
|
|
382
|
+
.get(id);
|
|
383
|
+
}
|
|
384
|
+
listSshConnections() {
|
|
385
|
+
return this.db.prepare('SELECT * FROM ssh_connections ORDER BY created_at')
|
|
386
|
+
.all();
|
|
387
|
+
}
|
|
388
|
+
deleteSshConnection(id) {
|
|
389
|
+
const result = this.db.prepare('DELETE FROM ssh_connections WHERE id = ?').run(id);
|
|
390
|
+
return result.changes > 0;
|
|
391
|
+
}
|
|
392
|
+
// ═══════════════════════════════════════════════════
|
|
393
|
+
// WORKTREE HISTORY (persists paths after removal)
|
|
394
|
+
// ═══════════════════════════════════════════════════
|
|
395
|
+
/**
|
|
396
|
+
* Record a worktree path for a parent project. Idempotent — skips duplicates.
|
|
397
|
+
*/
|
|
398
|
+
recordWorktreePath(parentProjectId, worktreePath) {
|
|
399
|
+
this.db.prepare(`
|
|
400
|
+
INSERT OR IGNORE INTO worktree_history (id, parent_project_id, worktree_path)
|
|
401
|
+
VALUES (?, ?, ?)
|
|
402
|
+
`).run(uuidv4(), parentProjectId, worktreePath);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Get all project paths related to a given path (main project + all worktrees, current and historical).
|
|
406
|
+
* Works bidirectionally: pass a main project path or a worktree path.
|
|
407
|
+
*/
|
|
408
|
+
getAllRelatedProjectPaths(projectPath) {
|
|
409
|
+
// First, find the project by path
|
|
410
|
+
const project = this.findProjectByPath(projectPath);
|
|
411
|
+
if (!project) {
|
|
412
|
+
// Path might be a historical worktree that's been deleted from projects table
|
|
413
|
+
const hist = this.db.prepare('SELECT parent_project_id FROM worktree_history WHERE worktree_path = ?').get(projectPath);
|
|
414
|
+
if (!hist)
|
|
415
|
+
return [projectPath]; // Unknown path, just return itself
|
|
416
|
+
const parent = this.getProject(hist.parent_project_id);
|
|
417
|
+
if (!parent)
|
|
418
|
+
return [projectPath];
|
|
419
|
+
return this.collectAllPaths(parent.id, parent.path);
|
|
420
|
+
}
|
|
421
|
+
// If this is a child project (worktree), resolve to parent
|
|
422
|
+
const parentId = project.parent_project_id;
|
|
423
|
+
if (parentId) {
|
|
424
|
+
const parent = this.getProject(parentId);
|
|
425
|
+
if (parent) {
|
|
426
|
+
return this.collectAllPaths(parent.id, parent.path);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// This is a root project — collect all its paths
|
|
430
|
+
return this.collectAllPaths(project.id, project.path);
|
|
431
|
+
}
|
|
432
|
+
collectAllPaths(parentProjectId, parentPath) {
|
|
433
|
+
const paths = new Set();
|
|
434
|
+
paths.add(parentPath);
|
|
435
|
+
// Current child projects (active worktrees)
|
|
436
|
+
const children = this.getChildProjects(parentProjectId);
|
|
437
|
+
for (const child of children) {
|
|
438
|
+
paths.add(child.path);
|
|
439
|
+
}
|
|
440
|
+
// Historical worktree paths (includes removed worktrees)
|
|
441
|
+
const historical = this.db.prepare('SELECT worktree_path FROM worktree_history WHERE parent_project_id = ?').all(parentProjectId);
|
|
442
|
+
for (const row of historical) {
|
|
443
|
+
paths.add(row.worktree_path);
|
|
444
|
+
}
|
|
445
|
+
return Array.from(paths);
|
|
446
|
+
}
|
|
447
|
+
// ═══════════════════════════════════════════════════
|
|
448
|
+
// USER MANAGEMENT MIGRATION
|
|
449
|
+
// ═══════════════════════════════════════════════════
|
|
450
|
+
migrateUserManagement() {
|
|
451
|
+
this.db.exec(`
|
|
452
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
453
|
+
id TEXT PRIMARY KEY,
|
|
454
|
+
username TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
|
455
|
+
display_name TEXT NOT NULL,
|
|
456
|
+
role TEXT NOT NULL CHECK (role IN ('admin', 'basic')),
|
|
457
|
+
password_hash TEXT NOT NULL,
|
|
458
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
459
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
CREATE TABLE IF NOT EXISTS auth_sessions (
|
|
463
|
+
id TEXT PRIMARY KEY,
|
|
464
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
465
|
+
expires_at TEXT NOT NULL,
|
|
466
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
467
|
+
ip_address TEXT,
|
|
468
|
+
user_agent TEXT
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
CREATE TABLE IF NOT EXISTS totp_secrets (
|
|
472
|
+
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
473
|
+
secret_encrypted TEXT NOT NULL,
|
|
474
|
+
verified INTEGER NOT NULL DEFAULT 0,
|
|
475
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
CREATE TABLE IF NOT EXISTS passkeys (
|
|
479
|
+
id TEXT PRIMARY KEY,
|
|
480
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
481
|
+
credential_id TEXT NOT NULL UNIQUE,
|
|
482
|
+
public_key TEXT NOT NULL,
|
|
483
|
+
counter INTEGER NOT NULL DEFAULT 0,
|
|
484
|
+
transports TEXT,
|
|
485
|
+
device_name TEXT,
|
|
486
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
CREATE TABLE IF NOT EXISTS environment_shares (
|
|
490
|
+
id TEXT PRIMARY KEY,
|
|
491
|
+
environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
|
|
492
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
493
|
+
shared_by TEXT NOT NULL REFERENCES users(id),
|
|
494
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
495
|
+
UNIQUE (environment_id, user_id)
|
|
496
|
+
);
|
|
497
|
+
`);
|
|
498
|
+
// Add owner_user_id column to environments if missing
|
|
499
|
+
const columns = this.db.prepare("PRAGMA table_info(environments)").all();
|
|
500
|
+
const hasOwnerCol = columns.some((c) => c.name === 'owner_user_id');
|
|
501
|
+
if (!hasOwnerCol && columns.length > 0) {
|
|
502
|
+
this.db.exec('ALTER TABLE environments ADD COLUMN owner_user_id TEXT REFERENCES users(id)');
|
|
503
|
+
console.log('[db] Added owner_user_id column to environments');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// ═══════════════════════════════════════════════════
|
|
507
|
+
// USERS
|
|
508
|
+
// ═══════════════════════════════════════════════════
|
|
509
|
+
getUserCount() {
|
|
510
|
+
return this.db.prepare('SELECT COUNT(*) as cnt FROM users').get().cnt;
|
|
511
|
+
}
|
|
512
|
+
createUser(username, displayName, role, passwordHash) {
|
|
513
|
+
const id = uuidv4();
|
|
514
|
+
this.db.prepare('INSERT INTO users (id, username, display_name, role, password_hash) VALUES (?, ?, ?, ?, ?)').run(id, username, displayName, role, passwordHash);
|
|
515
|
+
return this.getUser(id);
|
|
516
|
+
}
|
|
517
|
+
getUser(id) {
|
|
518
|
+
return this.db.prepare('SELECT * FROM users WHERE id = ?').get(id);
|
|
519
|
+
}
|
|
520
|
+
getUserByUsername(username) {
|
|
521
|
+
return this.db.prepare('SELECT * FROM users WHERE username = ?').get(username);
|
|
522
|
+
}
|
|
523
|
+
listUsers() {
|
|
524
|
+
return this.db.prepare('SELECT id, username, display_name, role, created_at, updated_at FROM users ORDER BY created_at').all();
|
|
525
|
+
}
|
|
526
|
+
updateUser(id, updates) {
|
|
527
|
+
const parts = [];
|
|
528
|
+
const values = [];
|
|
529
|
+
if (updates.display_name !== undefined) {
|
|
530
|
+
parts.push('display_name = ?');
|
|
531
|
+
values.push(updates.display_name);
|
|
532
|
+
}
|
|
533
|
+
if (updates.role !== undefined) {
|
|
534
|
+
parts.push('role = ?');
|
|
535
|
+
values.push(updates.role);
|
|
536
|
+
}
|
|
537
|
+
if (parts.length === 0)
|
|
538
|
+
return false;
|
|
539
|
+
parts.push("updated_at = datetime('now')");
|
|
540
|
+
values.push(id);
|
|
541
|
+
const result = this.db.prepare(`UPDATE users SET ${parts.join(', ')} WHERE id = ?`).run(...values);
|
|
542
|
+
return result.changes > 0;
|
|
543
|
+
}
|
|
544
|
+
updateUserPassword(id, passwordHash) {
|
|
545
|
+
const result = this.db.prepare("UPDATE users SET password_hash = ?, updated_at = datetime('now') WHERE id = ?").run(passwordHash, id);
|
|
546
|
+
return result.changes > 0;
|
|
547
|
+
}
|
|
548
|
+
deleteUser(id) {
|
|
549
|
+
const result = this.db.prepare('DELETE FROM users WHERE id = ?').run(id);
|
|
550
|
+
return result.changes > 0;
|
|
551
|
+
}
|
|
552
|
+
// ═══════════════════════════════════════════════════
|
|
553
|
+
// AUTH SESSIONS
|
|
554
|
+
// ═══════════════════════════════════════════════════
|
|
555
|
+
createAuthSession(id, userId, expiresAt, ip, userAgent) {
|
|
556
|
+
this.db.prepare('INSERT INTO auth_sessions (id, user_id, expires_at, ip_address, user_agent) VALUES (?, ?, ?, ?, ?)').run(id, userId, expiresAt, ip, userAgent);
|
|
557
|
+
}
|
|
558
|
+
getAuthSessionUser(sessionId) {
|
|
559
|
+
const row = this.db.prepare(`
|
|
560
|
+
SELECT u.* FROM users u
|
|
561
|
+
JOIN auth_sessions s ON s.user_id = u.id
|
|
562
|
+
WHERE s.id = ? AND s.expires_at > datetime('now')
|
|
563
|
+
`).get(sessionId);
|
|
564
|
+
return row || null;
|
|
565
|
+
}
|
|
566
|
+
deleteAuthSession(sessionId) {
|
|
567
|
+
this.db.prepare('DELETE FROM auth_sessions WHERE id = ?').run(sessionId);
|
|
568
|
+
}
|
|
569
|
+
deleteAllUserAuthSessions(userId) {
|
|
570
|
+
this.db.prepare('DELETE FROM auth_sessions WHERE user_id = ?').run(userId);
|
|
571
|
+
}
|
|
572
|
+
// ═══════════════════════════════════════════════════
|
|
573
|
+
// TOTP SECRETS
|
|
574
|
+
// ═══════════════════════════════════════════════════
|
|
575
|
+
saveTotpSecret(userId, secretEncrypted) {
|
|
576
|
+
this.db.prepare(`
|
|
577
|
+
INSERT INTO totp_secrets (user_id, secret_encrypted, verified)
|
|
578
|
+
VALUES (?, ?, 0)
|
|
579
|
+
ON CONFLICT(user_id) DO UPDATE SET secret_encrypted = excluded.secret_encrypted, verified = 0
|
|
580
|
+
`).run(userId, secretEncrypted);
|
|
581
|
+
}
|
|
582
|
+
getTotpSecret(userId) {
|
|
583
|
+
return this.db.prepare('SELECT * FROM totp_secrets WHERE user_id = ?').get(userId) || null;
|
|
584
|
+
}
|
|
585
|
+
markTotpVerified(userId) {
|
|
586
|
+
this.db.prepare('UPDATE totp_secrets SET verified = 1 WHERE user_id = ?').run(userId);
|
|
587
|
+
}
|
|
588
|
+
deleteTotpSecret(userId) {
|
|
589
|
+
this.db.prepare('DELETE FROM totp_secrets WHERE user_id = ?').run(userId);
|
|
590
|
+
}
|
|
591
|
+
// ═══════════════════════════════════════════════════
|
|
592
|
+
// PASSKEYS
|
|
593
|
+
// ═══════════════════════════════════════════════════
|
|
594
|
+
createPasskey(pk) {
|
|
595
|
+
this.db.prepare(`
|
|
596
|
+
INSERT INTO passkeys (id, user_id, credential_id, public_key, counter, transports, device_name)
|
|
597
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
598
|
+
`).run(pk.id, pk.user_id, pk.credential_id, pk.public_key, pk.counter, pk.transports, pk.device_name);
|
|
599
|
+
}
|
|
600
|
+
listPasskeys(userId) {
|
|
601
|
+
return this.db.prepare('SELECT * FROM passkeys WHERE user_id = ? ORDER BY created_at').all(userId);
|
|
602
|
+
}
|
|
603
|
+
findPasskeyByCredentialId(credentialId) {
|
|
604
|
+
return this.db.prepare('SELECT * FROM passkeys WHERE credential_id = ?').get(credentialId) || null;
|
|
605
|
+
}
|
|
606
|
+
updatePasskeyCounter(id, counter) {
|
|
607
|
+
this.db.prepare('UPDATE passkeys SET counter = ? WHERE id = ?').run(counter, id);
|
|
608
|
+
}
|
|
609
|
+
deletePasskey(id, userId) {
|
|
610
|
+
const result = this.db.prepare('DELETE FROM passkeys WHERE id = ? AND user_id = ?').run(id, userId);
|
|
611
|
+
return result.changes > 0;
|
|
612
|
+
}
|
|
613
|
+
deleteAllUserPasskeys(userId) {
|
|
614
|
+
this.db.prepare('DELETE FROM passkeys WHERE user_id = ?').run(userId);
|
|
615
|
+
}
|
|
616
|
+
getUserIdsWithPasskeys() {
|
|
617
|
+
return this.db.prepare('SELECT DISTINCT user_id FROM passkeys').all().map(r => r.user_id);
|
|
618
|
+
}
|
|
619
|
+
// ═══════════════════════════════════════════════════
|
|
620
|
+
// ENVIRONMENT OWNERSHIP & SHARING
|
|
621
|
+
// ═══════════════════════════════════════════════════
|
|
622
|
+
listEnvironmentsForUser(userId) {
|
|
623
|
+
return this.db.prepare(`
|
|
624
|
+
SELECT e.id, e.name, e.created_at, e.owner_user_id,
|
|
625
|
+
(SELECT COUNT(*) FROM project_environment pe WHERE pe.environment_id = e.id) as project_count
|
|
626
|
+
FROM environments e
|
|
627
|
+
WHERE e.owner_user_id = ?
|
|
628
|
+
OR e.id IN (SELECT environment_id FROM environment_shares WHERE user_id = ?)
|
|
629
|
+
ORDER BY e.created_at
|
|
630
|
+
`).all(userId, userId);
|
|
631
|
+
}
|
|
632
|
+
createEnvironmentWithOwner(name, ownerUserId) {
|
|
633
|
+
const id = uuidv4();
|
|
634
|
+
this.db.prepare('INSERT INTO environments (id, name, owner_user_id) VALUES (?, ?, ?)').run(id, name, ownerUserId);
|
|
635
|
+
this.db.prepare('INSERT INTO environment_settings (environment_id) VALUES (?)').run(id);
|
|
636
|
+
return this.getEnvironment(id);
|
|
637
|
+
}
|
|
638
|
+
assignUnownedEnvironments(userId) {
|
|
639
|
+
this.db.prepare('UPDATE environments SET owner_user_id = ? WHERE owner_user_id IS NULL').run(userId);
|
|
640
|
+
}
|
|
641
|
+
canAccessEnvironment(userId, envId) {
|
|
642
|
+
const row = this.db.prepare(`
|
|
643
|
+
SELECT 1 FROM environments
|
|
644
|
+
WHERE id = ? AND (owner_user_id = ? OR id IN (SELECT environment_id FROM environment_shares WHERE user_id = ?))
|
|
645
|
+
`).get(envId, userId, userId);
|
|
646
|
+
return !!row;
|
|
647
|
+
}
|
|
648
|
+
canAccessProject(userId, projectId) {
|
|
649
|
+
const row = this.db.prepare(`
|
|
650
|
+
SELECT 1 FROM project_environment pe
|
|
651
|
+
JOIN environments e ON e.id = pe.environment_id
|
|
652
|
+
WHERE pe.project_id = ?
|
|
653
|
+
AND (e.owner_user_id = ? OR e.id IN (SELECT environment_id FROM environment_shares WHERE user_id = ?))
|
|
654
|
+
LIMIT 1
|
|
655
|
+
`).get(projectId, userId, userId);
|
|
656
|
+
return !!row;
|
|
657
|
+
}
|
|
658
|
+
getEnvironmentOwner(envId) {
|
|
659
|
+
const row = this.db.prepare('SELECT owner_user_id FROM environments WHERE id = ?').get(envId);
|
|
660
|
+
return row?.owner_user_id || null;
|
|
661
|
+
}
|
|
662
|
+
shareEnvironment(envId, userId, sharedBy) {
|
|
663
|
+
const id = uuidv4();
|
|
664
|
+
this.db.prepare(`
|
|
665
|
+
INSERT OR IGNORE INTO environment_shares (id, environment_id, user_id, shared_by)
|
|
666
|
+
VALUES (?, ?, ?, ?)
|
|
667
|
+
`).run(id, envId, userId, sharedBy);
|
|
668
|
+
}
|
|
669
|
+
unshareEnvironment(envId, userId) {
|
|
670
|
+
const result = this.db.prepare('DELETE FROM environment_shares WHERE environment_id = ? AND user_id = ?').run(envId, userId);
|
|
671
|
+
return result.changes > 0;
|
|
672
|
+
}
|
|
673
|
+
listEnvironmentShares(envId) {
|
|
674
|
+
return this.db.prepare(`
|
|
675
|
+
SELECT es.*, u.username, u.display_name
|
|
676
|
+
FROM environment_shares es
|
|
677
|
+
JOIN users u ON u.id = es.user_id
|
|
678
|
+
WHERE es.environment_id = ?
|
|
679
|
+
ORDER BY es.created_at
|
|
680
|
+
`).all(envId);
|
|
681
|
+
}
|
|
682
|
+
// ═══════════════════════════════════════════════════
|
|
683
|
+
// SESSION METADATA
|
|
684
|
+
// ═══════════════════════════════════════════════════
|
|
685
|
+
migrateSessionMetadata() {
|
|
686
|
+
this.db.exec(`
|
|
687
|
+
CREATE TABLE IF NOT EXISTS session_metadata (
|
|
688
|
+
session_uuid TEXT PRIMARY KEY,
|
|
689
|
+
name TEXT,
|
|
690
|
+
description TEXT,
|
|
691
|
+
tags_json TEXT NOT NULL DEFAULT '[]',
|
|
692
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
693
|
+
);
|
|
694
|
+
`);
|
|
695
|
+
// Migrate data from old session_tags table if it exists
|
|
696
|
+
try {
|
|
697
|
+
const oldTags = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='session_tags'").get();
|
|
698
|
+
if (oldTags) {
|
|
699
|
+
const rows = this.db.prepare('SELECT session_uuid, GROUP_CONCAT(tag) as tags FROM session_tags GROUP BY session_uuid').all();
|
|
700
|
+
for (const row of rows) {
|
|
701
|
+
const tags = row.tags.split(',');
|
|
702
|
+
this.db.prepare(`
|
|
703
|
+
INSERT OR IGNORE INTO session_metadata (session_uuid, tags_json)
|
|
704
|
+
VALUES (?, ?)
|
|
705
|
+
`).run(row.session_uuid, JSON.stringify(tags));
|
|
706
|
+
}
|
|
707
|
+
this.db.exec('DROP TABLE session_tags');
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
catch { }
|
|
711
|
+
}
|
|
712
|
+
getSessionMetadata(sessionUuid) {
|
|
713
|
+
const row = this.db.prepare('SELECT session_uuid, name, description, tags_json FROM session_metadata WHERE session_uuid = ?').get(sessionUuid);
|
|
714
|
+
if (!row)
|
|
715
|
+
return null;
|
|
716
|
+
return {
|
|
717
|
+
sessionUuid: row.session_uuid,
|
|
718
|
+
name: row.name || undefined,
|
|
719
|
+
description: row.description || undefined,
|
|
720
|
+
tags: JSON.parse(row.tags_json || '[]'),
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
getMetadataForSessions(sessionUuids) {
|
|
724
|
+
if (!sessionUuids.length)
|
|
725
|
+
return {};
|
|
726
|
+
const placeholders = sessionUuids.map(() => '?').join(',');
|
|
727
|
+
const rows = this.db.prepare(`SELECT session_uuid, name, description, tags_json FROM session_metadata WHERE session_uuid IN (${placeholders})`).all(...sessionUuids);
|
|
728
|
+
const result = {};
|
|
729
|
+
for (const row of rows) {
|
|
730
|
+
result[row.session_uuid] = {
|
|
731
|
+
sessionUuid: row.session_uuid,
|
|
732
|
+
name: row.name || undefined,
|
|
733
|
+
description: row.description || undefined,
|
|
734
|
+
tags: JSON.parse(row.tags_json || '[]'),
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
return result;
|
|
738
|
+
}
|
|
739
|
+
setSessionMetadata(sessionUuid, updates) {
|
|
740
|
+
const existing = this.getSessionMetadata(sessionUuid);
|
|
741
|
+
const name = updates.name !== undefined ? updates.name : (existing?.name || null);
|
|
742
|
+
const description = updates.description !== undefined ? updates.description : (existing?.description || null);
|
|
743
|
+
const tags = updates.tags !== undefined ? updates.tags : (existing?.tags || []);
|
|
744
|
+
this.db.prepare(`
|
|
745
|
+
INSERT INTO session_metadata (session_uuid, name, description, tags_json, updated_at)
|
|
746
|
+
VALUES (?, ?, ?, ?, datetime('now'))
|
|
747
|
+
ON CONFLICT(session_uuid) DO UPDATE SET
|
|
748
|
+
name = excluded.name,
|
|
749
|
+
description = excluded.description,
|
|
750
|
+
tags_json = excluded.tags_json,
|
|
751
|
+
updated_at = excluded.updated_at
|
|
752
|
+
`).run(sessionUuid, name, description, JSON.stringify(tags));
|
|
753
|
+
}
|
|
754
|
+
// ═══════════════════════════════════════════════════
|
|
755
|
+
// ═══════════════════════════════════════════════════
|
|
756
|
+
// DATABASE CONNECTIONS (saved connections for Database Explorer)
|
|
757
|
+
// ═══════════════════════════════════════════════════
|
|
758
|
+
migrateDatabaseConnections() {
|
|
759
|
+
this.db.exec(`
|
|
760
|
+
CREATE TABLE IF NOT EXISTS saved_db_connections (
|
|
761
|
+
id TEXT PRIMARY KEY,
|
|
762
|
+
name TEXT NOT NULL,
|
|
763
|
+
db_type TEXT NOT NULL,
|
|
764
|
+
params_json TEXT NOT NULL,
|
|
765
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
766
|
+
last_used_at TEXT
|
|
767
|
+
)
|
|
768
|
+
`);
|
|
769
|
+
}
|
|
770
|
+
getSavedDbConnections() {
|
|
771
|
+
return this.db.prepare(`
|
|
772
|
+
SELECT id, name, db_type, params_json FROM saved_db_connections ORDER BY last_used_at DESC NULLS LAST, name
|
|
773
|
+
`).all().map((r) => ({
|
|
774
|
+
id: r.id,
|
|
775
|
+
name: r.name,
|
|
776
|
+
db_type: r.db_type,
|
|
777
|
+
params: JSON.parse(r.params_json),
|
|
778
|
+
}));
|
|
779
|
+
}
|
|
780
|
+
saveDbConnection(id, name, dbType, params) {
|
|
781
|
+
this.db.prepare(`
|
|
782
|
+
INSERT INTO saved_db_connections (id, name, db_type, params_json)
|
|
783
|
+
VALUES (?, ?, ?, ?)
|
|
784
|
+
ON CONFLICT(id) DO UPDATE SET name = excluded.name, db_type = excluded.db_type, params_json = excluded.params_json
|
|
785
|
+
`).run(id, name, dbType, JSON.stringify(params));
|
|
786
|
+
}
|
|
787
|
+
touchDbConnection(id) {
|
|
788
|
+
this.db.prepare(`UPDATE saved_db_connections SET last_used_at = datetime('now') WHERE id = ?`).run(id);
|
|
789
|
+
}
|
|
790
|
+
deleteDbConnection(id) {
|
|
791
|
+
this.db.prepare(`DELETE FROM saved_db_connections WHERE id = ?`).run(id);
|
|
792
|
+
}
|
|
793
|
+
// ═══════════════════════════════════════════════════
|
|
794
|
+
// PROJECT CHANGES ("What has been done" tracking)
|
|
795
|
+
// ═══════════════════════════════════════════════════
|
|
796
|
+
migrateProjectChanges() {
|
|
797
|
+
this.db.exec(`
|
|
798
|
+
CREATE TABLE IF NOT EXISTS project_changes (
|
|
799
|
+
id TEXT PRIMARY KEY,
|
|
800
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
801
|
+
short_description TEXT NOT NULL DEFAULT '',
|
|
802
|
+
long_description TEXT NOT NULL DEFAULT '',
|
|
803
|
+
tags_json TEXT NOT NULL DEFAULT '[]',
|
|
804
|
+
approx_files_affected INTEGER NOT NULL DEFAULT 0,
|
|
805
|
+
session_id TEXT,
|
|
806
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
807
|
+
)
|
|
808
|
+
`);
|
|
809
|
+
// Migrate from old schema that had a single 'description' column
|
|
810
|
+
const columns = this.db.prepare("PRAGMA table_info(project_changes)").all();
|
|
811
|
+
const hasOldDesc = columns.some((c) => c.name === 'description');
|
|
812
|
+
const hasShortDesc = columns.some((c) => c.name === 'short_description');
|
|
813
|
+
if (hasOldDesc && !hasShortDesc) {
|
|
814
|
+
this.db.exec(`
|
|
815
|
+
ALTER TABLE project_changes ADD COLUMN short_description TEXT NOT NULL DEFAULT '';
|
|
816
|
+
ALTER TABLE project_changes ADD COLUMN long_description TEXT NOT NULL DEFAULT '';
|
|
817
|
+
ALTER TABLE project_changes ADD COLUMN tags_json TEXT NOT NULL DEFAULT '[]';
|
|
818
|
+
`);
|
|
819
|
+
// Move old description into long_description, truncate for short
|
|
820
|
+
const rows = this.db.prepare('SELECT id, description FROM project_changes').all();
|
|
821
|
+
const stmt = this.db.prepare('UPDATE project_changes SET short_description = ?, long_description = ? WHERE id = ?');
|
|
822
|
+
for (const row of rows) {
|
|
823
|
+
const short = (row.description || '').split(/[.—\-\n]/)[0].trim().split(/\s+/).slice(0, 10).join(' ');
|
|
824
|
+
stmt.run(short, row.description || '', row.id);
|
|
825
|
+
}
|
|
826
|
+
console.log(`[db] Migrated ${rows.length} project_changes rows to new schema`);
|
|
827
|
+
}
|
|
828
|
+
// Drop old 'description' column if it still exists (it has NOT NULL and breaks INSERTs)
|
|
829
|
+
if (hasOldDesc) {
|
|
830
|
+
try {
|
|
831
|
+
this.db.exec('ALTER TABLE project_changes DROP COLUMN description');
|
|
832
|
+
console.log('[db] Dropped old description column from project_changes');
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
// SQLite < 3.35.0 doesn't support DROP COLUMN — recreate table
|
|
836
|
+
const txn = this.db.transaction(() => {
|
|
837
|
+
const existing = this.db.prepare('SELECT id, project_id, short_description, long_description, tags_json, approx_files_affected, session_id, created_at FROM project_changes').all();
|
|
838
|
+
this.db.exec('DROP TABLE project_changes');
|
|
839
|
+
this.db.exec(`
|
|
840
|
+
CREATE TABLE project_changes (
|
|
841
|
+
id TEXT PRIMARY KEY,
|
|
842
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
843
|
+
short_description TEXT NOT NULL DEFAULT '',
|
|
844
|
+
long_description TEXT NOT NULL DEFAULT '',
|
|
845
|
+
tags_json TEXT NOT NULL DEFAULT '[]',
|
|
846
|
+
approx_files_affected INTEGER NOT NULL DEFAULT 0,
|
|
847
|
+
session_id TEXT,
|
|
848
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
849
|
+
)
|
|
850
|
+
`);
|
|
851
|
+
const ins = this.db.prepare('INSERT INTO project_changes (id, project_id, short_description, long_description, tags_json, approx_files_affected, session_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
|
|
852
|
+
for (const r of existing) {
|
|
853
|
+
ins.run(r.id, r.project_id, r.short_description || '', r.long_description || '', r.tags_json || '[]', r.approx_files_affected || 0, r.session_id, r.created_at);
|
|
854
|
+
}
|
|
855
|
+
console.log(`[db] Recreated project_changes table without old description column (${existing.length} rows preserved)`);
|
|
856
|
+
});
|
|
857
|
+
txn();
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
PC_COLS = 'id, project_id, short_description, long_description, tags_json, approx_files_affected, session_id, created_at';
|
|
862
|
+
listProjectChanges(projectId) {
|
|
863
|
+
return this.db.prepare(`SELECT ${this.PC_COLS} FROM project_changes WHERE project_id = ? ORDER BY created_at DESC`).all(projectId);
|
|
864
|
+
}
|
|
865
|
+
getProjectChange(id) {
|
|
866
|
+
return this.db.prepare(`SELECT ${this.PC_COLS} FROM project_changes WHERE id = ?`).get(id);
|
|
867
|
+
}
|
|
868
|
+
createProjectChange(projectId, shortDesc, longDesc, tags, approxFilesAffected, sessionId) {
|
|
869
|
+
const id = uuidv4();
|
|
870
|
+
this.db.prepare('INSERT INTO project_changes (id, project_id, short_description, long_description, tags_json, approx_files_affected, session_id) VALUES (?, ?, ?, ?, ?, ?, ?)').run(id, projectId, shortDesc, longDesc, JSON.stringify(tags), approxFilesAffected, sessionId || null);
|
|
871
|
+
return this.getProjectChange(id);
|
|
872
|
+
}
|
|
873
|
+
updateProjectChange(id, shortDesc, longDesc, tags, approxFilesAffected) {
|
|
874
|
+
const result = this.db.prepare('UPDATE project_changes SET short_description = ?, long_description = ?, tags_json = ?, approx_files_affected = ? WHERE id = ?').run(shortDesc, longDesc, JSON.stringify(tags), approxFilesAffected, id);
|
|
875
|
+
return result.changes > 0;
|
|
876
|
+
}
|
|
877
|
+
deleteProjectChange(id) {
|
|
878
|
+
const result = this.db.prepare('DELETE FROM project_changes WHERE id = ?').run(id);
|
|
879
|
+
return result.changes > 0;
|
|
880
|
+
}
|
|
881
|
+
deleteAllProjectChanges(projectId) {
|
|
882
|
+
const result = this.db.prepare('DELETE FROM project_changes WHERE project_id = ?').run(projectId);
|
|
883
|
+
return result.changes;
|
|
884
|
+
}
|
|
885
|
+
// LIFECYCLE
|
|
886
|
+
// ═══════════════════════════════════════════════════
|
|
887
|
+
close() {
|
|
888
|
+
try {
|
|
889
|
+
this.db.close();
|
|
890
|
+
console.log('[db] Database closed');
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
console.error('[db] Error closing database:', err);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
export const db = new StudioDatabase();
|