teamcopilot 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/.env.example +10 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +131 -0
  4. package/bin/teamcopilot.js +281 -0
  5. package/dist/auth/index.js +189 -0
  6. package/dist/change-user-role.js +77 -0
  7. package/dist/chat/index.js +849 -0
  8. package/dist/constants.js +2 -0
  9. package/dist/create-user.js +98 -0
  10. package/dist/cronjob/index.js +16 -0
  11. package/dist/cronjob/resource-reconciliation.js +33 -0
  12. package/dist/delete-user.js +66 -0
  13. package/dist/frontend/assets/abap-CRCWOmpq.js +1 -0
  14. package/dist/frontend/assets/apex-DnsZk_dE.js +1 -0
  15. package/dist/frontend/assets/azcli-1IWB1ccx.js +1 -0
  16. package/dist/frontend/assets/bat-DPkNLes8.js +1 -0
  17. package/dist/frontend/assets/bicep-Corcdgou.js +2 -0
  18. package/dist/frontend/assets/cameligo-CGrWLZr3.js +1 -0
  19. package/dist/frontend/assets/clojure-D9WOWImG.js +1 -0
  20. package/dist/frontend/assets/codicon-DCmgc-ay.ttf +0 -0
  21. package/dist/frontend/assets/coffee-B7EJu28W.js +1 -0
  22. package/dist/frontend/assets/cpp-SEyurbux.js +1 -0
  23. package/dist/frontend/assets/csharp-BoL64M5l.js +1 -0
  24. package/dist/frontend/assets/csp-C46ZqvIl.js +1 -0
  25. package/dist/frontend/assets/css-DQU6DXDx.js +3 -0
  26. package/dist/frontend/assets/cssMode-BDT3WbVs.js +4 -0
  27. package/dist/frontend/assets/cypher-D84EuPTj.js +1 -0
  28. package/dist/frontend/assets/dart-D8lhlL1r.js +1 -0
  29. package/dist/frontend/assets/dockerfile-DLk6rpji.js +1 -0
  30. package/dist/frontend/assets/ecl-BO6FnfXk.js +1 -0
  31. package/dist/frontend/assets/editor.worker-B4pQIWZD.js +12 -0
  32. package/dist/frontend/assets/elixir-BRjLKONM.js +1 -0
  33. package/dist/frontend/assets/flow9-Cac8vKd7.js +1 -0
  34. package/dist/frontend/assets/freemarker2-C7-hEgID.js +3 -0
  35. package/dist/frontend/assets/fsharp-fd1GTHhf.js +1 -0
  36. package/dist/frontend/assets/go-O9LJTZXk.js +1 -0
  37. package/dist/frontend/assets/graphql-LQdxqEYJ.js +1 -0
  38. package/dist/frontend/assets/handlebars-4cwTkPir.js +1 -0
  39. package/dist/frontend/assets/hcl-DxDQ3s82.js +1 -0
  40. package/dist/frontend/assets/html-YNfE1Q0A.js +1 -0
  41. package/dist/frontend/assets/htmlMode-opTQ1HoB.js +4 -0
  42. package/dist/frontend/assets/index-DWyaVa1h.js +782 -0
  43. package/dist/frontend/assets/index-lXrsgeTF.css +1 -0
  44. package/dist/frontend/assets/ini-BvajGCUy.js +1 -0
  45. package/dist/frontend/assets/java-SYsfObOQ.js +1 -0
  46. package/dist/frontend/assets/javascript-BEwGzk7T.js +1 -0
  47. package/dist/frontend/assets/jsonMode-CGhIS5Al.js +10 -0
  48. package/dist/frontend/assets/julia-DQXNmw_w.js +1 -0
  49. package/dist/frontend/assets/kotlin-qQ0MG-9I.js +1 -0
  50. package/dist/frontend/assets/less-GGFNNJHn.js +2 -0
  51. package/dist/frontend/assets/lexon-Canl7DCW.js +1 -0
  52. package/dist/frontend/assets/liquid-QekTGCGJ.js +1 -0
  53. package/dist/frontend/assets/lua-D28Ae8-K.js +1 -0
  54. package/dist/frontend/assets/m3-DPitgjJI.js +1 -0
  55. package/dist/frontend/assets/markdown-B811l8j2.js +1 -0
  56. package/dist/frontend/assets/mdx-BAVDaB7v.js +1 -0
  57. package/dist/frontend/assets/mips-CdjsipkG.js +1 -0
  58. package/dist/frontend/assets/msdax-CYqgjx_P.js +1 -0
  59. package/dist/frontend/assets/mysql-BHd6q0vd.js +1 -0
  60. package/dist/frontend/assets/objective-c-B1aVtJYH.js +1 -0
  61. package/dist/frontend/assets/pascal-BhNW15KB.js +1 -0
  62. package/dist/frontend/assets/pascaligo-5jv8CcQD.js +1 -0
  63. package/dist/frontend/assets/perl-DlYyT36c.js +1 -0
  64. package/dist/frontend/assets/pgsql-Dy0bjov7.js +1 -0
  65. package/dist/frontend/assets/php-120yhfDK.js +1 -0
  66. package/dist/frontend/assets/pla-CjnFlu4u.js +1 -0
  67. package/dist/frontend/assets/postiats-CQpG440k.js +1 -0
  68. package/dist/frontend/assets/powerquery-DdJtto1Z.js +1 -0
  69. package/dist/frontend/assets/powershell-Bu_VLpJB.js +1 -0
  70. package/dist/frontend/assets/protobuf-IBS6jZEB.js +2 -0
  71. package/dist/frontend/assets/pug-kFxLfcjb.js +1 -0
  72. package/dist/frontend/assets/python-BQlHw7XO.js +1 -0
  73. package/dist/frontend/assets/qsharp-q7JyzKFN.js +1 -0
  74. package/dist/frontend/assets/r-BIFz-_sK.js +1 -0
  75. package/dist/frontend/assets/razor-Be3Wwc2E.js +1 -0
  76. package/dist/frontend/assets/redis-CHOsPHWR.js +1 -0
  77. package/dist/frontend/assets/redshift-CBifECDb.js +1 -0
  78. package/dist/frontend/assets/restructuredtext-CghPJEOS.js +1 -0
  79. package/dist/frontend/assets/ruby-CYWGW-b1.js +1 -0
  80. package/dist/frontend/assets/rust-DMDD0SHb.js +1 -0
  81. package/dist/frontend/assets/sb-BYAiYHFx.js +1 -0
  82. package/dist/frontend/assets/scala-Bqvq8jcR.js +1 -0
  83. package/dist/frontend/assets/scheme-Dhb-2j9p.js +1 -0
  84. package/dist/frontend/assets/scss-CTwUZ5N7.js +3 -0
  85. package/dist/frontend/assets/shell-CsDZo4DB.js +1 -0
  86. package/dist/frontend/assets/solidity-CME5AdoB.js +1 -0
  87. package/dist/frontend/assets/sophia-RYC1BQQz.js +1 -0
  88. package/dist/frontend/assets/sparql-KEyrF7De.js +1 -0
  89. package/dist/frontend/assets/sql-BdTr02Mf.js +1 -0
  90. package/dist/frontend/assets/st-C7iG7M4S.js +1 -0
  91. package/dist/frontend/assets/swift-D7IUmUK8.js +1 -0
  92. package/dist/frontend/assets/systemverilog-DgMryOEJ.js +1 -0
  93. package/dist/frontend/assets/tcl-PloMZuKG.js +1 -0
  94. package/dist/frontend/assets/tsMode-CIBFoN3z.js +11 -0
  95. package/dist/frontend/assets/twig-BfRIq3la.js +1 -0
  96. package/dist/frontend/assets/typescript-BuV9wEIE.js +1 -0
  97. package/dist/frontend/assets/typespec-CzxlYoT_.js +1 -0
  98. package/dist/frontend/assets/vb-BwAE3J76.js +1 -0
  99. package/dist/frontend/assets/wgsl-B_1kOXbF.js +298 -0
  100. package/dist/frontend/assets/xml-DcDKYaM4.js +1 -0
  101. package/dist/frontend/assets/yaml-CuBNmOuI.js +1 -0
  102. package/dist/frontend/index.html +14 -0
  103. package/dist/frontend/logo.svg +50 -0
  104. package/dist/index.js +169 -0
  105. package/dist/logging.js +30 -0
  106. package/dist/opencode-auth/index.js +122 -0
  107. package/dist/opencode-server.js +91 -0
  108. package/dist/prisma/client.js +38 -0
  109. package/dist/reset-password.js +73 -0
  110. package/dist/rotate-jwt-secret.js +20 -0
  111. package/dist/scripts/prisma-workspace.js +34 -0
  112. package/dist/skills/index.js +311 -0
  113. package/dist/types/permissions.js +2 -0
  114. package/dist/types/shared/permissions.js +17 -0
  115. package/dist/types/shared/skill.js +17 -0
  116. package/dist/types/shared/workflow-files.js +17 -0
  117. package/dist/types/shared/workflow.js +17 -0
  118. package/dist/types/skill.js +2 -0
  119. package/dist/types/workflow-files.js +2 -0
  120. package/dist/types/workflow.js +2 -0
  121. package/dist/users/index.js +22 -0
  122. package/dist/utils/approval-snapshot-common.js +596 -0
  123. package/dist/utils/assert.js +20 -0
  124. package/dist/utils/chat-session.js +44 -0
  125. package/dist/utils/cli-bootstrap.js +26 -0
  126. package/dist/utils/index.js +95 -0
  127. package/dist/utils/jwt-secret.js +63 -0
  128. package/dist/utils/opencode-auth.js +126 -0
  129. package/dist/utils/opencode-client.js +109 -0
  130. package/dist/utils/password-policy.js +12 -0
  131. package/dist/utils/permission-common.js +280 -0
  132. package/dist/utils/redact.js +108 -0
  133. package/dist/utils/resource-access.js +37 -0
  134. package/dist/utils/resource-file-routes.js +115 -0
  135. package/dist/utils/resource-files.js +572 -0
  136. package/dist/utils/runtime-paths.js +61 -0
  137. package/dist/utils/session-abort.js +52 -0
  138. package/dist/utils/skill-approval-snapshot.js +39 -0
  139. package/dist/utils/skill-files.js +17 -0
  140. package/dist/utils/skill-permissions.js +15 -0
  141. package/dist/utils/skill.js +217 -0
  142. package/dist/utils/user-role.js +14 -0
  143. package/dist/utils/workflow-approval-snapshot.js +38 -0
  144. package/dist/utils/workflow-files.js +17 -0
  145. package/dist/utils/workflow-interruption.js +50 -0
  146. package/dist/utils/workflow-permissions.js +27 -0
  147. package/dist/utils/workflow-runner.js +414 -0
  148. package/dist/utils/workflow.js +158 -0
  149. package/dist/utils/workspace-sync.js +204 -0
  150. package/dist/workflows/index.js +751 -0
  151. package/dist/workspace_files/.opencode/opencode.json +17 -0
  152. package/dist/workspace_files/.opencode/package.json +14 -0
  153. package/dist/workspace_files/.opencode/plugins/createSkill.ts +339 -0
  154. package/dist/workspace_files/.opencode/plugins/createWorkflow.ts +345 -0
  155. package/dist/workspace_files/.opencode/plugins/findSimilarWorkflow.ts +173 -0
  156. package/dist/workspace_files/.opencode/plugins/findSkill.ts +211 -0
  157. package/dist/workspace_files/.opencode/plugins/getSkillContent.ts +135 -0
  158. package/dist/workspace_files/.opencode/plugins/honeytoken-protection.ts +64 -0
  159. package/dist/workspace_files/.opencode/plugins/listAvailableSkills.ts +93 -0
  160. package/dist/workspace_files/.opencode/plugins/listAvailableWorkflows.ts +93 -0
  161. package/dist/workspace_files/.opencode/plugins/python-protection.ts +184 -0
  162. package/dist/workspace_files/.opencode/plugins/runWorkflow.ts +168 -0
  163. package/dist/workspace_files/.opencode/tsconfig.json +16 -0
  164. package/dist/workspace_files/AGENTS.md +483 -0
  165. package/dist/workspace_files/package-lock.json +167 -0
  166. package/dist/workspace_files/package.json +5 -0
  167. package/package.json +86 -0
  168. package/prisma/migrations/20260203040755_init/migration.sql +20 -0
  169. package/prisma/migrations/20260204034845_replace_google_auth_with_email_password/migration.sql +25 -0
  170. package/prisma/migrations/20260207022226_add_user_role/migration.sql +25 -0
  171. package/prisma/migrations/20260210161254_add_workflow_runs/migration.sql +16 -0
  172. package/prisma/migrations/20260211050606_adds_workflow_table/migration.sql +40 -0
  173. package/prisma/migrations/20260211050750_adds_fkey_constraint/migration.sql +21 -0
  174. package/prisma/migrations/20260211051912_removes_workflow_table/migration.sql +34 -0
  175. package/prisma/migrations/20260211052238_changes_workflow_id_to_slug/migration.sql +27 -0
  176. package/prisma/migrations/20260212051912_add_output_to_workflow_runs/migration.sql +2 -0
  177. package/prisma/migrations/20260213073006_add_chat_sessions/migration.sql +13 -0
  178. package/prisma/migrations/20260216053202_add_chat_sessions_opencode_session_id_idx/migration.sql +2 -0
  179. package/prisma/migrations/20260216053237_drop_redundant_chat_sessions_opencode_idx/migration.sql +2 -0
  180. package/prisma/migrations/20260219060705_makes/migration.sql +24 -0
  181. package/prisma/migrations/20260222040542_add_workflow_execution_permissions/migration.sql +18 -0
  182. package/prisma/migrations/20260222040815_remove_workflow_execution_permissions/migration.sql +10 -0
  183. package/prisma/migrations/20260222041348_add_workflow_execution_permissions_final/migration.sql +17 -0
  184. package/prisma/migrations/20260222041741_rename_to_tool_execution_permissions/migration.sql +30 -0
  185. package/prisma/migrations/20260222041826_simplify_tool_execution_permissions/migration.sql +29 -0
  186. package/prisma/migrations/20260222041950_add_fields_for_standalone_permissions/migration.sql +32 -0
  187. package/prisma/migrations/20260222042954_simplify_tool_permissions_table/migration.sql +27 -0
  188. package/prisma/migrations/20260223073902_add_workflow_run_permissions_tables/migration.sql +23 -0
  189. package/prisma/migrations/20260225025151_add_workflow_metadata/migration.sql +16 -0
  190. package/prisma/migrations/20260225031035_merge_workflow_permissions_into_metadata/migration.sql +44 -0
  191. package/prisma/migrations/20260225031752_removes_default_for_run_permission_mode/migration.sql +20 -0
  192. package/prisma/migrations/20260225033603_remove_workflow_metadata_user_fkeys/migration.sql +18 -0
  193. package/prisma/migrations/20260225043032_restore_workflow_metadata_user_fkeys/migration.sql +20 -0
  194. package/prisma/migrations/20260225091423_add_workflow_approved_snapshots/migration.sql +28 -0
  195. package/prisma/migrations/20260226032121_add_is_approved_to_workflow_metadata/migration.sql +21 -0
  196. package/prisma/migrations/20260226032444_undoes_last_db_change/migration.sql +26 -0
  197. package/prisma/migrations/20260227120000_remove_snapshot_hash_from_approved_snapshots/migration.sql +16 -0
  198. package/prisma/migrations/20260228071125_adds_workspace_path_to_snapshot_table/migration.sql +22 -0
  199. package/prisma/migrations/20260228071217_modifies_index_and_removes_default_value/migration.sql +22 -0
  200. package/prisma/migrations/20260228071710_undoes_previous/migration.sql +27 -0
  201. package/prisma/migrations/20260228105022_add_must_change_password_first_login/migration.sql +20 -0
  202. package/prisma/migrations/20260301115439_add_workflow_run_log_refs/migration.sql +8 -0
  203. package/prisma/migrations/20260301122557_add_workflow_aborted_sessions/migration.sql +5 -0
  204. package/prisma/migrations/20260302045545_move_workflow_run_log_refs_into_workflow_runs/migration.sql +17 -0
  205. package/prisma/migrations/20260303040318_add_skill_tables/migration.sql +61 -0
  206. package/prisma/migrations/20260303051533_unify_resource_permissions/migration.sql +97 -0
  207. package/prisma/migrations/20260303064255_unify_resource_metadata_and_snapshots/migration.sql +179 -0
  208. package/prisma/migrations/migration_lock.toml +3 -0
  209. package/prisma/schema.prisma +147 -0
@@ -0,0 +1,572 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createResourceFileManager = createResourceFileManager;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const redact_1 = require("./redact");
11
+ function createResourceFileManager(options) {
12
+ const { getResourcePath, resourceLabel, editorLabel } = options;
13
+ const rootLabel = `${resourceLabel} root`;
14
+ const symlinkRootError = `${editorLabel} editor does not support symlinked ${resourceLabel} directories`;
15
+ const symlinkError = `${editorLabel} editor does not support symlinks`;
16
+ function toEtag(buffer) {
17
+ return crypto_1.default.createHash("sha256").update(buffer).digest("hex");
18
+ }
19
+ function normalizeRelativePath(rawPath, allowEmpty) {
20
+ const input = rawPath.trim();
21
+ if (!input) {
22
+ if (allowEmpty) {
23
+ return "";
24
+ }
25
+ throw {
26
+ status: 400,
27
+ message: "path is required"
28
+ };
29
+ }
30
+ if (input.includes("\0")) {
31
+ throw {
32
+ status: 400,
33
+ message: "path contains invalid characters"
34
+ };
35
+ }
36
+ const withForwardSlashes = input.replace(/\\/g, "/");
37
+ if (withForwardSlashes.startsWith("/")) {
38
+ throw {
39
+ status: 400,
40
+ message: `path must be relative to the ${rootLabel}`
41
+ };
42
+ }
43
+ const normalized = path_1.default.posix.normalize(withForwardSlashes);
44
+ if (normalized === "." && allowEmpty) {
45
+ return "";
46
+ }
47
+ if (normalized === "." || normalized.startsWith("../") || normalized === "..") {
48
+ throw {
49
+ status: 400,
50
+ message: `path escapes the ${rootLabel}`
51
+ };
52
+ }
53
+ return normalized;
54
+ }
55
+ function resolveTarget(slug, relativePath) {
56
+ const root = getResourcePath(slug);
57
+ const absolute = path_1.default.resolve(root, relativePath);
58
+ const normalizedRoot = path_1.default.resolve(root);
59
+ if (absolute !== normalizedRoot && !absolute.startsWith(`${normalizedRoot}${path_1.default.sep}`)) {
60
+ throw {
61
+ status: 400,
62
+ message: `path escapes the ${rootLabel}`
63
+ };
64
+ }
65
+ return absolute;
66
+ }
67
+ function assertRootNotSymlink(slug) {
68
+ const resourceRoot = getResourcePath(slug);
69
+ if (!fs_1.default.existsSync(resourceRoot)) {
70
+ return;
71
+ }
72
+ const rootLstat = fs_1.default.lstatSync(resourceRoot);
73
+ if (rootLstat.isSymbolicLink()) {
74
+ throw {
75
+ status: 400,
76
+ message: symlinkRootError
77
+ };
78
+ }
79
+ }
80
+ function assertRealPathWithinRoot(slug, absolutePath) {
81
+ const realRoot = fs_1.default.realpathSync(getResourcePath(slug));
82
+ const realTarget = fs_1.default.realpathSync(absolutePath);
83
+ if (realTarget !== realRoot && !realTarget.startsWith(`${realRoot}${path_1.default.sep}`)) {
84
+ throw {
85
+ status: 400,
86
+ message: `path escapes the ${rootLabel}`
87
+ };
88
+ }
89
+ }
90
+ function assertNoSymlinkInAncestors(slug, absolutePath) {
91
+ const root = path_1.default.resolve(getResourcePath(slug));
92
+ let current = path_1.default.resolve(absolutePath);
93
+ while (true) {
94
+ const lstat = fs_1.default.lstatSync(current);
95
+ if (lstat.isSymbolicLink()) {
96
+ throw {
97
+ status: 400,
98
+ message: symlinkError
99
+ };
100
+ }
101
+ if (current === root) {
102
+ return;
103
+ }
104
+ const parent = path_1.default.dirname(current);
105
+ if (parent === current) {
106
+ throw {
107
+ status: 400,
108
+ message: `path escapes the ${rootLabel}`
109
+ };
110
+ }
111
+ current = parent;
112
+ }
113
+ }
114
+ function assertExistingPathIsSafe(slug, absolutePath) {
115
+ assertRootNotSymlink(slug);
116
+ assertRealPathWithinRoot(slug, absolutePath);
117
+ assertNoSymlinkInAncestors(slug, absolutePath);
118
+ }
119
+ function assertParentDirectoryIsSafeForCreate(slug, parentAbsolutePath) {
120
+ assertRootNotSymlink(slug);
121
+ assertRealPathWithinRoot(slug, parentAbsolutePath);
122
+ assertNoSymlinkInAncestors(slug, parentAbsolutePath);
123
+ }
124
+ function assertValidName(name) {
125
+ const trimmed = name.trim();
126
+ if (!trimmed || trimmed === "." || trimmed === "..") {
127
+ throw {
128
+ status: 400,
129
+ message: "Invalid file or folder name"
130
+ };
131
+ }
132
+ if (trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("\0")) {
133
+ throw {
134
+ status: 400,
135
+ message: "Invalid file or folder name"
136
+ };
137
+ }
138
+ }
139
+ function findClosingQuoteIndex(input, quote) {
140
+ for (let i = 1; i < input.length; i += 1) {
141
+ const ch = input[i];
142
+ if (ch === "\\") {
143
+ i += 1;
144
+ continue;
145
+ }
146
+ if (ch === quote) {
147
+ return i;
148
+ }
149
+ }
150
+ return -1;
151
+ }
152
+ function toFileNode(parentRelativePath, name, absolutePath) {
153
+ const lstat = fs_1.default.lstatSync(absolutePath);
154
+ const isDir = lstat.isDirectory();
155
+ let readable = true;
156
+ try {
157
+ fs_1.default.accessSync(absolutePath, fs_1.default.constants.R_OK);
158
+ }
159
+ catch {
160
+ readable = false;
161
+ }
162
+ let hasChildren = null;
163
+ if (isDir && readable) {
164
+ try {
165
+ hasChildren = fs_1.default.readdirSync(absolutePath).length > 0;
166
+ }
167
+ catch {
168
+ hasChildren = false;
169
+ readable = false;
170
+ }
171
+ }
172
+ const relativePath = parentRelativePath ? `${parentRelativePath}/${name}` : name;
173
+ return {
174
+ path: relativePath,
175
+ name,
176
+ kind: isDir ? "directory" : "file",
177
+ size_bytes: isDir ? null : lstat.size,
178
+ modified_at_ms: lstat.mtimeMs,
179
+ has_children: isDir ? (hasChildren ?? false) : null,
180
+ readable,
181
+ };
182
+ }
183
+ function compareNodes(a, b) {
184
+ if (a.kind !== b.kind) {
185
+ return a.kind === "directory" ? -1 : 1;
186
+ }
187
+ return a.name.localeCompare(b.name);
188
+ }
189
+ function looksBinary(buffer) {
190
+ if (buffer.length === 0) {
191
+ return false;
192
+ }
193
+ const sample = buffer.subarray(0, Math.min(buffer.length, 8000));
194
+ for (let i = 0; i < sample.length; i += 1) {
195
+ if (sample[i] === 0) {
196
+ return true;
197
+ }
198
+ }
199
+ return false;
200
+ }
201
+ function parseEnvAssignmentLine(line) {
202
+ const match = line.match(/^(\s*(?:export\s+)?)([A-Za-z_][A-Za-z0-9_-]*)(\s*=\s*)(.*)$/);
203
+ if (!match) {
204
+ return { kind: "other" };
205
+ }
206
+ const [, prefix, key, separator, remainder] = match;
207
+ if (remainder.startsWith('"')) {
208
+ const closeIdx = findClosingQuoteIndex(remainder, '"');
209
+ if (closeIdx === -1) {
210
+ return { kind: "other" };
211
+ }
212
+ return {
213
+ kind: "assignment",
214
+ prefix,
215
+ key,
216
+ separator,
217
+ quote: '"',
218
+ value: remainder.slice(1, closeIdx),
219
+ suffix: remainder.slice(closeIdx + 1),
220
+ };
221
+ }
222
+ if (remainder.startsWith("'")) {
223
+ const closeIdx = findClosingQuoteIndex(remainder, "'");
224
+ if (closeIdx === -1) {
225
+ return { kind: "other" };
226
+ }
227
+ return {
228
+ kind: "assignment",
229
+ prefix,
230
+ key,
231
+ separator,
232
+ quote: "'",
233
+ value: remainder.slice(1, closeIdx),
234
+ suffix: remainder.slice(closeIdx + 1),
235
+ };
236
+ }
237
+ const commentStart = remainder.search(/\s#/);
238
+ if (commentStart === -1) {
239
+ return {
240
+ kind: "assignment",
241
+ prefix,
242
+ key,
243
+ separator,
244
+ quote: null,
245
+ value: remainder.trim(),
246
+ suffix: "",
247
+ };
248
+ }
249
+ const valuePart = remainder.slice(0, commentStart).trim();
250
+ const suffix = remainder.slice(commentStart);
251
+ return {
252
+ kind: "assignment",
253
+ prefix,
254
+ key,
255
+ separator,
256
+ quote: null,
257
+ value: valuePart,
258
+ suffix,
259
+ };
260
+ }
261
+ function serializeEnvAssignment(parsed) {
262
+ if (parsed.quote === '"') {
263
+ return `${parsed.prefix}${parsed.key}${parsed.separator}"${parsed.value}"${parsed.suffix}`;
264
+ }
265
+ if (parsed.quote === "'") {
266
+ return `${parsed.prefix}${parsed.key}${parsed.separator}'${parsed.value}'${parsed.suffix}`;
267
+ }
268
+ return `${parsed.prefix}${parsed.key}${parsed.separator}${parsed.value}${parsed.suffix}`;
269
+ }
270
+ function splitLinesPreserveNewline(input) {
271
+ const parts = [];
272
+ const regex = /([^\r\n]*)(\r\n|\n|\r|$)/g;
273
+ let match;
274
+ while ((match = regex.exec(input)) !== null) {
275
+ const line = match[1];
276
+ const newline = match[2];
277
+ if (line === "" && newline === "" && match.index === input.length) {
278
+ break;
279
+ }
280
+ parts.push({ line, newline });
281
+ if (newline === "") {
282
+ break;
283
+ }
284
+ }
285
+ return parts;
286
+ }
287
+ function mergeDotenvMaskedValues(currentRaw, editedContent) {
288
+ const currentLines = splitLinesPreserveNewline(currentRaw);
289
+ const editedLines = splitLinesPreserveNewline(editedContent);
290
+ const currentQueuesByKey = new Map();
291
+ for (const item of currentLines) {
292
+ const parsed = parseEnvAssignmentLine(item.line);
293
+ if (parsed.kind !== "assignment")
294
+ continue;
295
+ const arr = currentQueuesByKey.get(parsed.key) ?? [];
296
+ arr.push(parsed);
297
+ currentQueuesByKey.set(parsed.key, arr);
298
+ }
299
+ const output = [];
300
+ for (const item of editedLines) {
301
+ const parsedEdited = parseEnvAssignmentLine(item.line);
302
+ if (parsedEdited.kind !== "assignment" || !(0, redact_1.isLikelySensitiveKey)(parsedEdited.key)) {
303
+ output.push(item.line + item.newline);
304
+ continue;
305
+ }
306
+ const queue = currentQueuesByKey.get(parsedEdited.key);
307
+ const currentParsed = queue?.shift();
308
+ if (!currentParsed) {
309
+ output.push(item.line + item.newline);
310
+ continue;
311
+ }
312
+ if (parsedEdited.quote !== currentParsed.quote) {
313
+ output.push(item.line + item.newline);
314
+ continue;
315
+ }
316
+ const maskedCurrent = (0, redact_1.maskValue)(currentParsed.value);
317
+ if (parsedEdited.value !== maskedCurrent) {
318
+ output.push(item.line + item.newline);
319
+ continue;
320
+ }
321
+ const mergedLine = serializeEnvAssignment({
322
+ ...parsedEdited,
323
+ value: currentParsed.value,
324
+ });
325
+ output.push(mergedLine + item.newline);
326
+ }
327
+ return output.join("");
328
+ }
329
+ function listDirectory(slug, rawPath) {
330
+ const relativePath = normalizeRelativePath(rawPath ?? "", true);
331
+ const absolutePath = resolveTarget(slug, relativePath);
332
+ if (!fs_1.default.existsSync(absolutePath)) {
333
+ throw {
334
+ status: 404,
335
+ message: "Directory not found"
336
+ };
337
+ }
338
+ assertExistingPathIsSafe(slug, absolutePath);
339
+ const stat = fs_1.default.statSync(absolutePath);
340
+ if (!stat.isDirectory()) {
341
+ throw {
342
+ status: 400,
343
+ message: "path must be a directory"
344
+ };
345
+ }
346
+ const names = fs_1.default.readdirSync(absolutePath);
347
+ for (const name of names) {
348
+ const childPath = path_1.default.join(absolutePath, name);
349
+ if (fs_1.default.lstatSync(childPath).isSymbolicLink()) {
350
+ throw {
351
+ status: 400,
352
+ message: symlinkError
353
+ };
354
+ }
355
+ }
356
+ const entries = names
357
+ .map((name) => toFileNode(relativePath, name, path_1.default.join(absolutePath, name)))
358
+ .sort(compareNodes);
359
+ return {
360
+ path: relativePath,
361
+ entries,
362
+ };
363
+ }
364
+ function readFileContent(slug, rawPath) {
365
+ const relativePath = normalizeRelativePath(rawPath ?? "", false);
366
+ const absolutePath = resolveTarget(slug, relativePath);
367
+ if (!fs_1.default.existsSync(absolutePath)) {
368
+ throw {
369
+ status: 404,
370
+ message: "File not found"
371
+ };
372
+ }
373
+ assertExistingPathIsSafe(slug, absolutePath);
374
+ const stat = fs_1.default.statSync(absolutePath);
375
+ if (!stat.isFile()) {
376
+ throw {
377
+ status: 400,
378
+ message: "path must be a file"
379
+ };
380
+ }
381
+ const bytes = fs_1.default.readFileSync(absolutePath);
382
+ const etag = toEtag(bytes);
383
+ const name = path_1.default.basename(relativePath);
384
+ if (looksBinary(bytes)) {
385
+ const response = {
386
+ path: relativePath,
387
+ name,
388
+ kind: "binary",
389
+ etag,
390
+ size_bytes: bytes.length,
391
+ modified_at_ms: stat.mtimeMs,
392
+ message: "This file appears to be binary and is not editable in the browser.",
393
+ };
394
+ return response;
395
+ }
396
+ const rawContent = bytes.toString("utf-8");
397
+ const isDotenv = name === ".env";
398
+ const content = isDotenv ? (0, redact_1.sanitizeStringContent)(rawContent) : rawContent;
399
+ const response = {
400
+ path: relativePath,
401
+ name,
402
+ kind: "text",
403
+ encoding: "utf-8",
404
+ content,
405
+ etag,
406
+ size_bytes: bytes.length,
407
+ modified_at_ms: stat.mtimeMs,
408
+ };
409
+ return response;
410
+ }
411
+ function saveFileContent(slug, request) {
412
+ const relativePath = normalizeRelativePath(request.path, false);
413
+ const absolutePath = resolveTarget(slug, relativePath);
414
+ if (!fs_1.default.existsSync(absolutePath)) {
415
+ throw {
416
+ status: 404,
417
+ message: "File not found"
418
+ };
419
+ }
420
+ assertExistingPathIsSafe(slug, absolutePath);
421
+ const stat = fs_1.default.statSync(absolutePath);
422
+ if (!stat.isFile()) {
423
+ throw {
424
+ status: 400,
425
+ message: "path must be a file"
426
+ };
427
+ }
428
+ const currentBytes = fs_1.default.readFileSync(absolutePath);
429
+ const currentEtag = toEtag(currentBytes);
430
+ if (currentEtag !== request.base_etag) {
431
+ throw {
432
+ status: 409,
433
+ message: "File changed on disk. Please reload and try again."
434
+ };
435
+ }
436
+ const name = path_1.default.basename(relativePath);
437
+ const isDotenv = name === ".env";
438
+ const nextContent = isDotenv
439
+ ? mergeDotenvMaskedValues(currentBytes.toString("utf-8"), request.content)
440
+ : request.content;
441
+ fs_1.default.writeFileSync(absolutePath, nextContent, "utf-8");
442
+ const nextBytes = fs_1.default.readFileSync(absolutePath);
443
+ const nextStat = fs_1.default.statSync(absolutePath);
444
+ return {
445
+ path: relativePath,
446
+ etag: toEtag(nextBytes),
447
+ modified_at_ms: nextStat.mtimeMs,
448
+ size_bytes: nextBytes.length,
449
+ };
450
+ }
451
+ function createFileOrFolder(slug, rawParentPath, name, kind) {
452
+ assertValidName(name);
453
+ const parentRelativePath = normalizeRelativePath(rawParentPath ?? "", true);
454
+ const parentAbsolutePath = resolveTarget(slug, parentRelativePath);
455
+ if (!fs_1.default.existsSync(parentAbsolutePath)) {
456
+ throw {
457
+ status: 404,
458
+ message: "Parent directory not found"
459
+ };
460
+ }
461
+ assertParentDirectoryIsSafeForCreate(slug, parentAbsolutePath);
462
+ if (!fs_1.default.statSync(parentAbsolutePath).isDirectory()) {
463
+ throw {
464
+ status: 400,
465
+ message: "parent_path must be a directory"
466
+ };
467
+ }
468
+ const targetAbsolutePath = path_1.default.join(parentAbsolutePath, name);
469
+ if (fs_1.default.existsSync(targetAbsolutePath)) {
470
+ throw {
471
+ status: 409,
472
+ message: "A file or folder with that name already exists"
473
+ };
474
+ }
475
+ if (kind === "directory") {
476
+ fs_1.default.mkdirSync(targetAbsolutePath);
477
+ }
478
+ else {
479
+ fs_1.default.writeFileSync(targetAbsolutePath, "", "utf-8");
480
+ }
481
+ return toFileNode(parentRelativePath, name, targetAbsolutePath);
482
+ }
483
+ function uploadFileFromTempPath(slug, rawParentPath, name, tempFilePath) {
484
+ assertValidName(name);
485
+ const parentRelativePath = normalizeRelativePath(rawParentPath ?? "", true);
486
+ const parentAbsolutePath = resolveTarget(slug, parentRelativePath);
487
+ if (!fs_1.default.existsSync(parentAbsolutePath)) {
488
+ throw {
489
+ status: 404,
490
+ message: "Parent directory not found"
491
+ };
492
+ }
493
+ assertParentDirectoryIsSafeForCreate(slug, parentAbsolutePath);
494
+ if (!fs_1.default.statSync(parentAbsolutePath).isDirectory()) {
495
+ throw {
496
+ status: 400,
497
+ message: "parent_path must be a directory"
498
+ };
499
+ }
500
+ if (!fs_1.default.existsSync(tempFilePath) || !fs_1.default.statSync(tempFilePath).isFile()) {
501
+ throw {
502
+ status: 400,
503
+ message: "Uploaded file was not received correctly"
504
+ };
505
+ }
506
+ const targetAbsolutePath = path_1.default.join(parentAbsolutePath, name);
507
+ if (fs_1.default.existsSync(targetAbsolutePath)) {
508
+ throw {
509
+ status: 409,
510
+ message: "A file or folder with that name already exists"
511
+ };
512
+ }
513
+ fs_1.default.copyFileSync(tempFilePath, targetAbsolutePath);
514
+ return toFileNode(parentRelativePath, name, targetAbsolutePath);
515
+ }
516
+ function renamePath(slug, rawPath, newName) {
517
+ assertValidName(newName);
518
+ const relativePath = normalizeRelativePath(rawPath ?? "", false);
519
+ const absolutePath = resolveTarget(slug, relativePath);
520
+ if (!fs_1.default.existsSync(absolutePath)) {
521
+ throw {
522
+ status: 404,
523
+ message: "File or folder not found"
524
+ };
525
+ }
526
+ assertExistingPathIsSafe(slug, absolutePath);
527
+ const parentRelativePath = path_1.default.posix.dirname(relativePath) === "." ? "" : path_1.default.posix.dirname(relativePath);
528
+ const parentAbsolutePath = resolveTarget(slug, parentRelativePath);
529
+ assertParentDirectoryIsSafeForCreate(slug, parentAbsolutePath);
530
+ const newAbsolutePath = path_1.default.join(parentAbsolutePath, newName);
531
+ const newRelativePath = parentRelativePath ? `${parentRelativePath}/${newName}` : newName;
532
+ if (fs_1.default.existsSync(newAbsolutePath)) {
533
+ throw {
534
+ status: 409,
535
+ message: "A file or folder with that name already exists"
536
+ };
537
+ }
538
+ fs_1.default.renameSync(absolutePath, newAbsolutePath);
539
+ const node = toFileNode(parentRelativePath, newName, newAbsolutePath);
540
+ return {
541
+ old_path: relativePath,
542
+ new_path: newRelativePath,
543
+ node,
544
+ };
545
+ }
546
+ function deletePath(slug, rawPath) {
547
+ const relativePath = normalizeRelativePath(rawPath ?? "", false);
548
+ const absolutePath = resolveTarget(slug, relativePath);
549
+ if (!fs_1.default.existsSync(absolutePath)) {
550
+ throw {
551
+ status: 404,
552
+ message: "File or folder not found"
553
+ };
554
+ }
555
+ assertExistingPathIsSafe(slug, absolutePath);
556
+ const stat = fs_1.default.statSync(absolutePath);
557
+ if (stat.isDirectory()) {
558
+ fs_1.default.rmSync(absolutePath, { recursive: true, force: false });
559
+ return;
560
+ }
561
+ fs_1.default.unlinkSync(absolutePath);
562
+ }
563
+ return {
564
+ listDirectory,
565
+ readFileContent,
566
+ saveFileContent,
567
+ createFileOrFolder,
568
+ uploadFileFromTempPath,
569
+ renamePath,
570
+ deletePath,
571
+ };
572
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getPackageRoot = getPackageRoot;
7
+ exports.getWorkspaceTemplateDirectory = getWorkspaceTemplateDirectory;
8
+ exports.getFrontendDistDirectory = getFrontendDistDirectory;
9
+ exports.getPrismaSchemaPath = getPrismaSchemaPath;
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ function findPackageRoot(startDirectory) {
13
+ let currentDirectory = startDirectory;
14
+ while (true) {
15
+ const packageJsonPath = path_1.default.join(currentDirectory, "package.json");
16
+ if (fs_1.default.existsSync(packageJsonPath)) {
17
+ return currentDirectory;
18
+ }
19
+ const parentDirectory = path_1.default.dirname(currentDirectory);
20
+ if (parentDirectory === currentDirectory) {
21
+ throw new Error(`Could not find package.json above ${startDirectory}`);
22
+ }
23
+ currentDirectory = parentDirectory;
24
+ }
25
+ }
26
+ function getPackageRoot() {
27
+ return findPackageRoot(__dirname);
28
+ }
29
+ function getWorkspaceTemplateDirectory() {
30
+ const packageRoot = getPackageRoot();
31
+ const candidateDirectories = [
32
+ path_1.default.join(packageRoot, "dist", "workspace_files"),
33
+ path_1.default.join(packageRoot, "src", "workspace_files"),
34
+ ];
35
+ for (const candidateDirectory of candidateDirectories) {
36
+ if (fs_1.default.existsSync(candidateDirectory)) {
37
+ return candidateDirectory;
38
+ }
39
+ }
40
+ throw new Error(`Workspace template directory not found in ${packageRoot}`);
41
+ }
42
+ function getFrontendDistDirectory() {
43
+ const packageRoot = getPackageRoot();
44
+ const candidateDirectories = [
45
+ path_1.default.join(packageRoot, "dist", "frontend"),
46
+ path_1.default.join(packageRoot, "frontend", "dist"),
47
+ ];
48
+ for (const candidateDirectory of candidateDirectories) {
49
+ if (fs_1.default.existsSync(candidateDirectory)) {
50
+ return candidateDirectory;
51
+ }
52
+ }
53
+ throw new Error(`Frontend build directory not found in ${packageRoot}`);
54
+ }
55
+ function getPrismaSchemaPath() {
56
+ const schemaPath = path_1.default.join(getPackageRoot(), "prisma", "schema.prisma");
57
+ if (!fs_1.default.existsSync(schemaPath)) {
58
+ throw new Error(`Prisma schema not found: ${schemaPath}`);
59
+ }
60
+ return schemaPath;
61
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.abortOpencodeSession = abortOpencodeSession;
7
+ const client_1 = __importDefault(require("../prisma/client"));
8
+ const assert_1 = require("./assert");
9
+ const opencode_client_1 = require("./opencode-client");
10
+ function getErrorMessage(error) {
11
+ if (error && typeof error === "object" && "detail" in error) {
12
+ return String(error.detail);
13
+ }
14
+ if (error instanceof Error) {
15
+ return error.message;
16
+ }
17
+ return "Unknown error";
18
+ }
19
+ async function abortOpencodeSession(opencodeSessionId) {
20
+ const pendingQuestion = await (0, opencode_client_1.getPendingQuestionForSession)(opencodeSessionId);
21
+ if (pendingQuestion) {
22
+ const abortAnswer = "User aborted";
23
+ const answers = pendingQuestion.questions.map(() => [abortAnswer]);
24
+ await (0, opencode_client_1.replyToPendingQuestion)(pendingQuestion.id, answers);
25
+ }
26
+ const pendingPermissions = await (0, opencode_client_1.listPendingPermissionsForSession)(opencodeSessionId);
27
+ for (const pendingPermission of pendingPermissions) {
28
+ await (0, opencode_client_1.replyToPendingPermission)(opencodeSessionId, pendingPermission.id, "reject");
29
+ }
30
+ await client_1.default.tool_execution_permissions.updateMany({
31
+ where: {
32
+ opencode_session_id: opencodeSessionId,
33
+ status: "pending"
34
+ },
35
+ data: {
36
+ status: "rejected",
37
+ responded_at: BigInt(Date.now())
38
+ }
39
+ });
40
+ const client = await (0, opencode_client_1.getOpencodeClient)();
41
+ await client.session.command({
42
+ path: { id: opencodeSessionId },
43
+ body: {
44
+ command: "session.interrupt",
45
+ arguments: ""
46
+ }
47
+ });
48
+ const abortResult = await client.session.abort({
49
+ path: { id: opencodeSessionId }
50
+ });
51
+ (0, assert_1.assertCondition)(!abortResult.error, getErrorMessage(abortResult.error));
52
+ }