teamcopilot 0.2.2 → 0.3.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 (39) hide show
  1. package/.env.example +6 -0
  2. package/dist/chat/index.js +18 -0
  3. package/dist/frontend/assets/{cssMode-D5XpZugr.js → cssMode-DVODTskC.js} +1 -1
  4. package/dist/frontend/assets/{freemarker2-1bzrSLLO.js → freemarker2-VNR3qIlX.js} +1 -1
  5. package/dist/frontend/assets/{handlebars-BrCOyfYR.js → handlebars-Tl9rb_wi.js} +1 -1
  6. package/dist/frontend/assets/{html-CeIQZCI8.js → html-DPF40zp3.js} +1 -1
  7. package/dist/frontend/assets/{htmlMode-B6ILgHd7.js → htmlMode-zWkU3Y3t.js} +1 -1
  8. package/dist/frontend/assets/{index-B0u3xzuM.js → index-BBQJYgr0.js} +232 -232
  9. package/dist/frontend/assets/index-CQhgzoZH.css +1 -0
  10. package/dist/frontend/assets/{javascript-BBiK7v6P.js → javascript-BpsgEO5T.js} +1 -1
  11. package/dist/frontend/assets/{jsonMode-DUvNLAsU.js → jsonMode-CczDM46r.js} +1 -1
  12. package/dist/frontend/assets/{liquid-DbDUmqHQ.js → liquid-LFSrT6MG.js} +1 -1
  13. package/dist/frontend/assets/{mdx-DhBBmLRc.js → mdx-CIgkEV38.js} +1 -1
  14. package/dist/frontend/assets/{python-DjRmQTqo.js → python-f-APDdLJ.js} +1 -1
  15. package/dist/frontend/assets/{razor-B0p8vOFC.js → razor-CcZ5caQe.js} +1 -1
  16. package/dist/frontend/assets/{tsMode-CmtykvH8.js → tsMode-BeWp_Zmp.js} +1 -1
  17. package/dist/frontend/assets/{typescript-ByGe7b1m.js → typescript-BWSPzIAG.js} +1 -1
  18. package/dist/frontend/assets/{xml-BJ90e21o.js → xml-kQvpQHq-.js} +1 -1
  19. package/dist/frontend/assets/{yaml-BJKnMsp4.js → yaml-Bl9AEojJ.js} +1 -1
  20. package/dist/frontend/index.html +2 -2
  21. package/dist/index.js +2 -0
  22. package/dist/usage/index.js +14 -0
  23. package/dist/utils/chat-usage.js +258 -0
  24. package/dist/utils/model-pricing.js +107 -0
  25. package/dist/workspace_files/.opencode/plugins/runWorkflow.ts +6 -4
  26. package/package.json +2 -2
  27. package/prisma/generated/client/edge.js +16 -3
  28. package/prisma/generated/client/index-browser.js +13 -0
  29. package/prisma/generated/client/index.d.ts +1995 -259
  30. package/prisma/generated/client/index.js +16 -3
  31. package/prisma/generated/client/package.json +1 -1
  32. package/prisma/generated/client/schema.prisma +14 -0
  33. package/prisma/generated/client/wasm.js +16 -3
  34. package/prisma/migrations/20260408073541_add_chat_session_usage/migration.sql +11 -0
  35. package/prisma/migrations/20260409044955_add_last_synced_message_id_to_chat_session_usage/migration.sql +2 -0
  36. package/prisma/migrations/20260409045848_add_provider_id_to_chat_session_usage/migration.sql +2 -0
  37. package/prisma/migrations/20260409051857_require_provider_and_last_synced_message_id_in_chat_session_usage/migration.sql +27 -0
  38. package/prisma/schema.prisma +14 -0
  39. package/dist/frontend/assets/index-DwTKL-xp.css +0 -1
@@ -0,0 +1,258 @@
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.syncChatSessionUsage = syncChatSessionUsage;
7
+ exports.buildUsageOverview = buildUsageOverview;
8
+ const client_1 = __importDefault(require("../prisma/client"));
9
+ const assert_1 = require("./assert");
10
+ const opencode_client_1 = require("./opencode-client");
11
+ const model_pricing_1 = require("./model-pricing");
12
+ function assertNonNegativeNumber(value, label) {
13
+ (0, assert_1.assertCondition)(typeof value === "number" && Number.isFinite(value) && value >= 0, `${label} must be a non-negative number`);
14
+ }
15
+ function assertSessionMessages(value) {
16
+ (0, assert_1.assertCondition)(Array.isArray(value), "Session messages response is not an array");
17
+ for (const [index, message] of value.entries()) {
18
+ (0, assert_1.assertCondition)(message !== null && typeof message === "object", `Session message at index ${index} must be an object`);
19
+ const messageRecord = message;
20
+ (0, assert_1.assertCondition)(messageRecord.info !== null && typeof messageRecord.info === "object", `Session message at index ${index} is missing info`);
21
+ const info = messageRecord.info;
22
+ (0, assert_1.assertCondition)(info.role === "user" || info.role === "assistant", `Session message at index ${index} has invalid role`);
23
+ (0, assert_1.assertCondition)(info.time !== null && typeof info.time === "object", `Session message at index ${index} is missing time`);
24
+ const time = info.time;
25
+ assertNonNegativeNumber(time.created, `Session message at index ${index} time.created`);
26
+ (0, assert_1.assertCondition)(typeof info.id === "string" && info.id.length > 0, `Session message at index ${index} is missing id`);
27
+ if (info.role === "assistant") {
28
+ (0, assert_1.assertCondition)(typeof info.modelID === "string" && info.modelID.length > 0, `Assistant message at index ${index} is missing modelID`);
29
+ (0, assert_1.assertCondition)(typeof info.providerID === "string" && info.providerID.length > 0, `Assistant message at index ${index} is missing providerID`);
30
+ (0, assert_1.assertCondition)(info.tokens !== null && typeof info.tokens === "object", `Assistant message at index ${index} is missing tokens`);
31
+ const tokens = info.tokens;
32
+ assertNonNegativeNumber(tokens.input, `Assistant message at index ${index} tokens.input`);
33
+ assertNonNegativeNumber(tokens.output, `Assistant message at index ${index} tokens.output`);
34
+ assertNonNegativeNumber(tokens.reasoning, `Assistant message at index ${index} tokens.reasoning`);
35
+ (0, assert_1.assertCondition)(tokens.cache !== null && typeof tokens.cache === "object", `Assistant message at index ${index} tokens.cache is missing`);
36
+ const cache = tokens.cache;
37
+ assertNonNegativeNumber(cache.read, `Assistant message at index ${index} tokens.cache.read`);
38
+ assertNonNegativeNumber(cache.write, `Assistant message at index ${index} tokens.cache.write`);
39
+ if (time.completed !== undefined) {
40
+ assertNonNegativeNumber(time.completed, `Assistant message at index ${index} time.completed`);
41
+ }
42
+ }
43
+ }
44
+ }
45
+ async function syncChatSessionUsage(chatSessionId, opencodeSessionId) {
46
+ const client = await (0, opencode_client_1.getOpencodeClient)();
47
+ const result = await client.session.messages({
48
+ path: { id: opencodeSessionId }
49
+ });
50
+ if (result.error) {
51
+ throw new Error("Failed to load session messages for usage sync");
52
+ }
53
+ assertSessionMessages(result.data);
54
+ const messages = result.data;
55
+ const existingUsage = await client_1.default.chat_session_usage.findUnique({
56
+ where: {
57
+ chat_session_id: chatSessionId,
58
+ }
59
+ });
60
+ let startIndex = 0;
61
+ if (existingUsage) {
62
+ const lastSyncedIndex = messages.findIndex((message) => (message.info.role === "assistant"
63
+ && message.info.id === existingUsage.last_synced_message_id));
64
+ if (lastSyncedIndex === -1) {
65
+ return;
66
+ }
67
+ startIndex = lastSyncedIndex + 1;
68
+ }
69
+ let deltaInputTokens = 0;
70
+ let deltaOutputTokens = 0;
71
+ let deltaCachedTokens = 0;
72
+ let latestProcessedAssistantMessageId = null;
73
+ let latestProcessedProviderId = null;
74
+ let latestProcessedModelId = null;
75
+ for (const message of messages.slice(startIndex)) {
76
+ const info = message.info;
77
+ if (info.role !== "assistant") {
78
+ continue;
79
+ }
80
+ deltaInputTokens += info.tokens.input;
81
+ deltaOutputTokens += info.tokens.output;
82
+ deltaCachedTokens += info.tokens.cache.read;
83
+ latestProcessedAssistantMessageId = info.id;
84
+ latestProcessedProviderId = info.providerID;
85
+ latestProcessedModelId = info.modelID;
86
+ }
87
+ if (latestProcessedAssistantMessageId === null
88
+ || latestProcessedProviderId === null
89
+ || latestProcessedModelId === null) {
90
+ return;
91
+ }
92
+ const deltaCostUsd = (0, model_pricing_1.calculateEstimatedCostUsd)({
93
+ providerId: latestProcessedProviderId,
94
+ modelId: latestProcessedModelId,
95
+ inputTokens: deltaInputTokens,
96
+ outputTokens: deltaOutputTokens,
97
+ cachedTokens: deltaCachedTokens,
98
+ });
99
+ const nextInputTokens = (existingUsage?.input_tokens ?? 0) + deltaInputTokens;
100
+ const nextOutputTokens = (existingUsage?.output_tokens ?? 0) + deltaOutputTokens;
101
+ const nextCachedTokens = (existingUsage?.cached_tokens ?? 0) + deltaCachedTokens;
102
+ const nextCostUsd = (existingUsage?.cost_usd ?? 0) + deltaCostUsd;
103
+ await client_1.default.chat_session_usage.upsert({
104
+ where: {
105
+ chat_session_id: chatSessionId,
106
+ },
107
+ create: {
108
+ chat_session_id: chatSessionId,
109
+ last_synced_message_id: latestProcessedAssistantMessageId,
110
+ provider_id: latestProcessedProviderId,
111
+ input_tokens: nextInputTokens,
112
+ output_tokens: nextOutputTokens,
113
+ cached_tokens: nextCachedTokens,
114
+ cost_usd: nextCostUsd,
115
+ model_id: latestProcessedModelId,
116
+ updated_at: BigInt(Date.now()),
117
+ },
118
+ update: {
119
+ last_synced_message_id: latestProcessedAssistantMessageId,
120
+ provider_id: latestProcessedProviderId,
121
+ input_tokens: nextInputTokens,
122
+ output_tokens: nextOutputTokens,
123
+ cached_tokens: nextCachedTokens,
124
+ cost_usd: nextCostUsd,
125
+ model_id: latestProcessedModelId,
126
+ updated_at: BigInt(Date.now()),
127
+ }
128
+ });
129
+ }
130
+ function getRangeConfig(range) {
131
+ switch (range) {
132
+ case "24h":
133
+ return { bucketMs: 60 * 60 * 1000, windowMs: 24 * 60 * 60 * 1000 };
134
+ case "7d":
135
+ return { bucketMs: 24 * 60 * 60 * 1000, windowMs: 7 * 24 * 60 * 60 * 1000 };
136
+ case "30d":
137
+ return { bucketMs: 24 * 60 * 60 * 1000, windowMs: 30 * 24 * 60 * 60 * 1000 };
138
+ case "90d":
139
+ return { bucketMs: 24 * 60 * 60 * 1000, windowMs: 90 * 24 * 60 * 60 * 1000 };
140
+ }
141
+ }
142
+ function normalizeUsageRange(value) {
143
+ if (value === "24h" || value === "7d" || value === "30d" || value === "90d") {
144
+ return value;
145
+ }
146
+ return "7d";
147
+ }
148
+ function getBucketStart(timestamp, bucketMs) {
149
+ return Math.floor(timestamp / bucketMs) * bucketMs;
150
+ }
151
+ async function buildUsageOverview(rawRange) {
152
+ const range = normalizeUsageRange(rawRange);
153
+ const { bucketMs, windowMs } = getRangeConfig(range);
154
+ const now = Date.now();
155
+ const rangeStart = now - windowMs;
156
+ const usageRows = await client_1.default.chat_session_usage.findMany({
157
+ where: {
158
+ session: {
159
+ updated_at: {
160
+ gte: BigInt(rangeStart),
161
+ lte: BigInt(now),
162
+ }
163
+ }
164
+ },
165
+ include: {
166
+ session: {
167
+ select: {
168
+ updated_at: true,
169
+ }
170
+ }
171
+ }
172
+ });
173
+ const bucketMap = new Map();
174
+ const modelMap = new Map();
175
+ let totalInputTokens = 0;
176
+ let totalOutputTokens = 0;
177
+ let totalCachedTokens = 0;
178
+ let totalCostUsd = 0;
179
+ for (const row of usageRows) {
180
+ const sessionUpdatedAt = Number(row.session.updated_at);
181
+ const bucketStart = getBucketStart(sessionUpdatedAt, bucketMs);
182
+ const bucket = bucketMap.get(bucketStart) ?? {
183
+ bucket_start: bucketStart,
184
+ input_tokens: 0,
185
+ output_tokens: 0,
186
+ cached_tokens: 0,
187
+ cost_usd: 0,
188
+ session_count: 0,
189
+ };
190
+ bucket.input_tokens += row.input_tokens;
191
+ bucket.output_tokens += row.output_tokens;
192
+ bucket.cached_tokens += row.cached_tokens;
193
+ bucket.cost_usd += row.cost_usd;
194
+ bucket.session_count += 1;
195
+ bucketMap.set(bucketStart, bucket);
196
+ const modelKey = `${row.provider_id}:${row.model_id}`;
197
+ const model = modelMap.get(modelKey) ?? {
198
+ provider_id: row.provider_id,
199
+ model_id: row.model_id,
200
+ input_tokens: 0,
201
+ output_tokens: 0,
202
+ cached_tokens: 0,
203
+ cost_usd: 0,
204
+ session_count: 0,
205
+ pricing_available: (0, model_pricing_1.getModelPricing)(row.provider_id, row.model_id) !== null,
206
+ };
207
+ model.input_tokens += row.input_tokens;
208
+ model.output_tokens += row.output_tokens;
209
+ model.cached_tokens += row.cached_tokens;
210
+ model.cost_usd += row.cost_usd;
211
+ model.session_count += 1;
212
+ modelMap.set(modelKey, model);
213
+ totalInputTokens += row.input_tokens;
214
+ totalOutputTokens += row.output_tokens;
215
+ totalCachedTokens += row.cached_tokens;
216
+ totalCostUsd += row.cost_usd;
217
+ }
218
+ const bucketStarts = [];
219
+ for (let time = getBucketStart(rangeStart, bucketMs); time <= getBucketStart(now, bucketMs); time += bucketMs) {
220
+ bucketStarts.push(time);
221
+ }
222
+ const timeseries = bucketStarts.map((bucketStart) => bucketMap.get(bucketStart) ?? {
223
+ bucket_start: bucketStart,
224
+ input_tokens: 0,
225
+ output_tokens: 0,
226
+ cached_tokens: 0,
227
+ cost_usd: 0,
228
+ session_count: 0,
229
+ });
230
+ const models = Array.from(modelMap.values()).sort((a, b) => b.cost_usd - a.cost_usd);
231
+ const pricing = {};
232
+ for (const model of models) {
233
+ const modelPricing = (0, model_pricing_1.getModelPricing)(model.provider_id, model.model_id);
234
+ if (!modelPricing) {
235
+ continue;
236
+ }
237
+ pricing[`${model.provider_id}:${model.model_id}`] = {
238
+ provider_id: model.provider_id,
239
+ input_per_million_usd: modelPricing.inputPerMillionUsd,
240
+ cached_input_per_million_usd: modelPricing.cachedInputPerMillionUsd,
241
+ output_per_million_usd: modelPricing.outputPerMillionUsd,
242
+ };
243
+ }
244
+ return {
245
+ range,
246
+ estimated: true,
247
+ summary: {
248
+ total_input_tokens: totalInputTokens,
249
+ total_output_tokens: totalOutputTokens,
250
+ total_cached_tokens: totalCachedTokens,
251
+ total_cost_usd: totalCostUsd,
252
+ session_count: usageRows.length,
253
+ },
254
+ timeseries,
255
+ models,
256
+ pricing,
257
+ };
258
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getModelPricing = getModelPricing;
4
+ exports.calculateEstimatedCostUsd = calculateEstimatedCostUsd;
5
+ const MODEL_PRICING = {
6
+ openai: {
7
+ "gpt-5.4": {
8
+ inputPerMillionUsd: 2.5,
9
+ cachedInputPerMillionUsd: 0.25,
10
+ outputPerMillionUsd: 15,
11
+ },
12
+ "gpt-5.4-mini": {
13
+ inputPerMillionUsd: 0.75,
14
+ cachedInputPerMillionUsd: 0.075,
15
+ outputPerMillionUsd: 4.5,
16
+ },
17
+ "gpt-5.4-nano": {
18
+ inputPerMillionUsd: 0.2,
19
+ cachedInputPerMillionUsd: 0.02,
20
+ outputPerMillionUsd: 1.25,
21
+ },
22
+ "gpt-5.3-chat-latest": {
23
+ inputPerMillionUsd: 1.75,
24
+ cachedInputPerMillionUsd: 0.175,
25
+ outputPerMillionUsd: 14,
26
+ },
27
+ "gpt-5.3-codex": {
28
+ inputPerMillionUsd: 1.75,
29
+ cachedInputPerMillionUsd: 0.175,
30
+ outputPerMillionUsd: 14,
31
+ },
32
+ "gpt-5.1": {
33
+ inputPerMillionUsd: 1.25,
34
+ cachedInputPerMillionUsd: 0.125,
35
+ outputPerMillionUsd: 10,
36
+ },
37
+ "gpt-5": {
38
+ inputPerMillionUsd: 1.25,
39
+ cachedInputPerMillionUsd: 0.125,
40
+ outputPerMillionUsd: 10,
41
+ },
42
+ "gpt-5-mini": {
43
+ inputPerMillionUsd: 0.25,
44
+ cachedInputPerMillionUsd: 0.025,
45
+ outputPerMillionUsd: 2,
46
+ },
47
+ "gpt-5-nano": {
48
+ inputPerMillionUsd: 0.05,
49
+ cachedInputPerMillionUsd: 0.005,
50
+ outputPerMillionUsd: 0.4,
51
+ }
52
+ },
53
+ };
54
+ function assertNonNegativeNumber(value, label) {
55
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
56
+ throw new Error(`${label} must be a non-negative number`);
57
+ }
58
+ }
59
+ function parseOptionalNonNegativeNumber(raw, label) {
60
+ if (raw === undefined || raw.length === 0) {
61
+ return null;
62
+ }
63
+ const parsed = Number(raw);
64
+ assertNonNegativeNumber(parsed, label);
65
+ return parsed;
66
+ }
67
+ function getPricingOverrideForConfiguredModel(providerId, modelId) {
68
+ const configuredModel = process.env.OPENCODE_MODEL;
69
+ const [configuredProviderId, ...configuredModelParts] = configuredModel.split("/");
70
+ const configuredModelId = configuredModelParts.join("/");
71
+ if (!configuredProviderId || !configuredModelId) {
72
+ throw new Error("OPENCODE_MODEL must be in the format <provider>/<model>");
73
+ }
74
+ if (providerId !== configuredProviderId || modelId !== configuredModelId) {
75
+ return null;
76
+ }
77
+ const inputPerMillionUsd = parseOptionalNonNegativeNumber(process.env.TEAMCOPILOT_MODEL_INPUT_PER_MILLION_USD, "TEAMCOPILOT_MODEL_INPUT_PER_MILLION_USD");
78
+ const cachedInputPerMillionUsd = parseOptionalNonNegativeNumber(process.env.TEAMCOPILOT_MODEL_CACHED_INPUT_PER_MILLION_USD, "TEAMCOPILOT_MODEL_CACHED_INPUT_PER_MILLION_USD");
79
+ const outputPerMillionUsd = parseOptionalNonNegativeNumber(process.env.TEAMCOPILOT_MODEL_OUTPUT_PER_MILLION_USD, "TEAMCOPILOT_MODEL_OUTPUT_PER_MILLION_USD");
80
+ if (inputPerMillionUsd === null && cachedInputPerMillionUsd === null && outputPerMillionUsd === null) {
81
+ return null;
82
+ }
83
+ if (inputPerMillionUsd === null || cachedInputPerMillionUsd === null || outputPerMillionUsd === null) {
84
+ throw new Error("TEAMCOPILOT_MODEL_INPUT_PER_MILLION_USD, TEAMCOPILOT_MODEL_CACHED_INPUT_PER_MILLION_USD, and TEAMCOPILOT_MODEL_OUTPUT_PER_MILLION_USD must either all be set or all be unset");
85
+ }
86
+ return {
87
+ inputPerMillionUsd,
88
+ cachedInputPerMillionUsd,
89
+ outputPerMillionUsd,
90
+ };
91
+ }
92
+ function getModelPricing(providerId, modelId) {
93
+ const override = getPricingOverrideForConfiguredModel(providerId, modelId);
94
+ if (override) {
95
+ return override;
96
+ }
97
+ return MODEL_PRICING[providerId]?.[modelId] ?? null;
98
+ }
99
+ function calculateEstimatedCostUsd(args) {
100
+ const pricing = getModelPricing(args.providerId, args.modelId);
101
+ if (!pricing) {
102
+ return 0;
103
+ }
104
+ return ((args.inputTokens * pricing.inputPerMillionUsd) / 1000000
105
+ + (args.cachedTokens * pricing.cachedInputPerMillionUsd) / 1000000
106
+ + (args.outputTokens * pricing.outputPerMillionUsd) / 1000000);
107
+ }
@@ -108,19 +108,21 @@ export const RunWorkflowPlugin: Plugin = async ({ client }) => {
108
108
  ),
109
109
  inputs: tool.schema
110
110
  .record(tool.schema.string(), tool.schema.unknown())
111
- .optional()
112
- .default({})
113
111
  .describe(
114
- "Runtime workflow arguments. Provide key-value pairs matching the `inputs` schema in workflow.json; these values are validated and passed through to `run.py`."
112
+ "Runtime workflow arguments. Provide key-value pairs matching the `inputs` schema in workflow.json for the workflow you want to run; these values are validated and passed through to `run.py`. If the workflow takes no arguments, then pass an empty object assigned to this key, otherwise, pass all the expected arguments always (even if there are default values assigned to them)."
115
113
  ),
116
114
  },
117
115
  async execute(args, context) {
118
116
  const { sessionID } = context
119
117
  const authSessionID = await resolveRootSessionID(sessionID)
120
- const { slug, inputs = {} } = args
118
+ const { slug, inputs } = args
121
119
  const messageId = extractMessageId(context)
122
120
  const callId = extractCallId(context)
123
121
 
122
+ if (inputs === undefined) {
123
+ throw new Error(`You must pass an inputs object when calling runWorkflow. The inputs object contains the workflow arguments. Inspect the workflow README or use the findSimilarWorkflow tool to see the available arguments and their default values. If a workflow has arguments, it's best practice to pass all of them even if they have default values, so that the user can see exactly what's passed. Example: {"inputs":{"topic":"weekly update","dry_run":true}}. If the workflow has no arguments, pass {"inputs":{}}.`)
124
+ }
125
+
124
126
  if (!messageId) {
125
127
  throw new Error("Could not determine message id from tool context.")
126
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamcopilot",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "A shared AI Agent for Teams",
5
5
  "homepage": "https://teamcopilot.ai",
6
6
  "repository": {
@@ -24,7 +24,7 @@
24
24
  "scripts": {
25
25
  "clean": "rm -rf dist",
26
26
  "check:unused:functions": "tsc --noEmit --noUnusedLocals --noUnusedParameters",
27
- "check:unused:exports": "ts-prune -e -p tsconfig.json -i \"^src/types/shared/|^prisma/generated/|^src/index.ts:31 - createApp \\(used in module\\)$|^src/utils/secret-contract-validation.ts:45 - extractReferencedWorkflowSecrets \\(used in module\\)$|^src/utils/secret-contract-validation.ts:57 - extractReferencedSkillSecrets \\(used in module\\)$|^src/utils/secret-contract-validation.ts:158 - parseSkillRequiredSecrets \\(used in module\\)$\"",
27
+ "check:unused:exports": "ts-prune -e -p tsconfig.json -i \"^src/types/shared/|^prisma/generated/|^src/index.ts:[0-9]+ - createApp \\(used in module\\)$|^src/utils/secret-contract-validation.ts:45 - extractReferencedWorkflowSecrets \\(used in module\\)$|^src/utils/secret-contract-validation.ts:57 - extractReferencedSkillSecrets \\(used in module\\)$|^src/utils/secret-contract-validation.ts:158 - parseSkillRequiredSecrets \\(used in module\\)$\"",
28
28
  "check:unused": "npm run check:unused:functions && npm run check:unused:exports",
29
29
  "build": "npm run clean && npm run check:unused && cd frontend && npm run build && cd .. && prisma generate --schema prisma/schema.prisma && tsc && node scripts/copy-runtime-assets.mjs",
30
30
  "start": "node dist/index.js",