whale-code 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/bin/swag-agent.js +9 -0
- package/bin/swagmanager-mcp.js +321 -0
- package/dist/cli/app.d.ts +26 -0
- package/dist/cli/app.js +64 -0
- package/dist/cli/chat/AgentSelector.d.ts +14 -0
- package/dist/cli/chat/AgentSelector.js +14 -0
- package/dist/cli/chat/ChatApp.d.ts +9 -0
- package/dist/cli/chat/ChatApp.js +267 -0
- package/dist/cli/chat/ChatInput.d.ts +39 -0
- package/dist/cli/chat/ChatInput.js +509 -0
- package/dist/cli/chat/MarkdownText.d.ts +10 -0
- package/dist/cli/chat/MarkdownText.js +20 -0
- package/dist/cli/chat/MessageList.d.ts +37 -0
- package/dist/cli/chat/MessageList.js +80 -0
- package/dist/cli/chat/ModelSelector.d.ts +20 -0
- package/dist/cli/chat/ModelSelector.js +73 -0
- package/dist/cli/chat/RewindViewer.d.ts +26 -0
- package/dist/cli/chat/RewindViewer.js +185 -0
- package/dist/cli/chat/StoreSelector.d.ts +14 -0
- package/dist/cli/chat/StoreSelector.js +24 -0
- package/dist/cli/chat/StreamingText.d.ts +12 -0
- package/dist/cli/chat/StreamingText.js +12 -0
- package/dist/cli/chat/SubagentPanel.d.ts +45 -0
- package/dist/cli/chat/SubagentPanel.js +110 -0
- package/dist/cli/chat/TeamPanel.d.ts +21 -0
- package/dist/cli/chat/TeamPanel.js +42 -0
- package/dist/cli/chat/ToolIndicator.d.ts +25 -0
- package/dist/cli/chat/ToolIndicator.js +436 -0
- package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
- package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
- package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
- package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
- package/dist/cli/commands/config-cmd.d.ts +10 -0
- package/dist/cli/commands/config-cmd.js +99 -0
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +172 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.js +278 -0
- package/dist/cli/commands/mcp.d.ts +12 -0
- package/dist/cli/commands/mcp.js +162 -0
- package/dist/cli/login/LoginApp.d.ts +7 -0
- package/dist/cli/login/LoginApp.js +157 -0
- package/dist/cli/print-mode.d.ts +31 -0
- package/dist/cli/print-mode.js +202 -0
- package/dist/cli/serve-mode.d.ts +37 -0
- package/dist/cli/serve-mode.js +636 -0
- package/dist/cli/services/agent-definitions.d.ts +25 -0
- package/dist/cli/services/agent-definitions.js +91 -0
- package/dist/cli/services/agent-events.d.ts +178 -0
- package/dist/cli/services/agent-events.js +175 -0
- package/dist/cli/services/agent-loop.d.ts +90 -0
- package/dist/cli/services/agent-loop.js +762 -0
- package/dist/cli/services/agent-worker-base.d.ts +97 -0
- package/dist/cli/services/agent-worker-base.js +220 -0
- package/dist/cli/services/auth-service.d.ts +30 -0
- package/dist/cli/services/auth-service.js +160 -0
- package/dist/cli/services/background-processes.d.ts +126 -0
- package/dist/cli/services/background-processes.js +318 -0
- package/dist/cli/services/browser-auth.d.ts +24 -0
- package/dist/cli/services/browser-auth.js +180 -0
- package/dist/cli/services/claude-md-loader.d.ts +16 -0
- package/dist/cli/services/claude-md-loader.js +58 -0
- package/dist/cli/services/config-store.d.ts +47 -0
- package/dist/cli/services/config-store.js +79 -0
- package/dist/cli/services/debug-log.d.ts +10 -0
- package/dist/cli/services/debug-log.js +52 -0
- package/dist/cli/services/error-logger.d.ts +58 -0
- package/dist/cli/services/error-logger.js +269 -0
- package/dist/cli/services/file-history.d.ts +21 -0
- package/dist/cli/services/file-history.js +83 -0
- package/dist/cli/services/format-server-response.d.ts +16 -0
- package/dist/cli/services/format-server-response.js +440 -0
- package/dist/cli/services/git-context.d.ts +11 -0
- package/dist/cli/services/git-context.js +66 -0
- package/dist/cli/services/hooks.d.ts +85 -0
- package/dist/cli/services/hooks.js +258 -0
- package/dist/cli/services/interactive-tools.d.ts +125 -0
- package/dist/cli/services/interactive-tools.js +260 -0
- package/dist/cli/services/keybinding-manager.d.ts +52 -0
- package/dist/cli/services/keybinding-manager.js +115 -0
- package/dist/cli/services/local-tools.d.ts +22 -0
- package/dist/cli/services/local-tools.js +697 -0
- package/dist/cli/services/lsp-manager.d.ts +18 -0
- package/dist/cli/services/lsp-manager.js +717 -0
- package/dist/cli/services/mcp-client.d.ts +48 -0
- package/dist/cli/services/mcp-client.js +157 -0
- package/dist/cli/services/memory-manager.d.ts +16 -0
- package/dist/cli/services/memory-manager.js +57 -0
- package/dist/cli/services/model-manager.d.ts +18 -0
- package/dist/cli/services/model-manager.js +71 -0
- package/dist/cli/services/model-router.d.ts +26 -0
- package/dist/cli/services/model-router.js +149 -0
- package/dist/cli/services/permission-modes.d.ts +13 -0
- package/dist/cli/services/permission-modes.js +43 -0
- package/dist/cli/services/rewind.d.ts +84 -0
- package/dist/cli/services/rewind.js +194 -0
- package/dist/cli/services/ripgrep.d.ts +28 -0
- package/dist/cli/services/ripgrep.js +138 -0
- package/dist/cli/services/sandbox.d.ts +29 -0
- package/dist/cli/services/sandbox.js +97 -0
- package/dist/cli/services/server-tools.d.ts +61 -0
- package/dist/cli/services/server-tools.js +543 -0
- package/dist/cli/services/session-persistence.d.ts +23 -0
- package/dist/cli/services/session-persistence.js +99 -0
- package/dist/cli/services/subagent-worker.d.ts +19 -0
- package/dist/cli/services/subagent-worker.js +41 -0
- package/dist/cli/services/subagent.d.ts +47 -0
- package/dist/cli/services/subagent.js +647 -0
- package/dist/cli/services/system-prompt.d.ts +7 -0
- package/dist/cli/services/system-prompt.js +198 -0
- package/dist/cli/services/team-lead.d.ts +73 -0
- package/dist/cli/services/team-lead.js +512 -0
- package/dist/cli/services/team-state.d.ts +77 -0
- package/dist/cli/services/team-state.js +398 -0
- package/dist/cli/services/teammate.d.ts +31 -0
- package/dist/cli/services/teammate.js +689 -0
- package/dist/cli/services/telemetry.d.ts +61 -0
- package/dist/cli/services/telemetry.js +209 -0
- package/dist/cli/services/tools/agent-tools.d.ts +14 -0
- package/dist/cli/services/tools/agent-tools.js +347 -0
- package/dist/cli/services/tools/file-ops.d.ts +15 -0
- package/dist/cli/services/tools/file-ops.js +487 -0
- package/dist/cli/services/tools/search-tools.d.ts +8 -0
- package/dist/cli/services/tools/search-tools.js +186 -0
- package/dist/cli/services/tools/shell-exec.d.ts +10 -0
- package/dist/cli/services/tools/shell-exec.js +168 -0
- package/dist/cli/services/tools/task-manager.d.ts +28 -0
- package/dist/cli/services/tools/task-manager.js +209 -0
- package/dist/cli/services/tools/web-tools.d.ts +11 -0
- package/dist/cli/services/tools/web-tools.js +395 -0
- package/dist/cli/setup/SetupApp.d.ts +9 -0
- package/dist/cli/setup/SetupApp.js +191 -0
- package/dist/cli/shared/MatrixIntro.d.ts +4 -0
- package/dist/cli/shared/MatrixIntro.js +83 -0
- package/dist/cli/shared/Theme.d.ts +74 -0
- package/dist/cli/shared/Theme.js +127 -0
- package/dist/cli/shared/WhaleBanner.d.ts +10 -0
- package/dist/cli/shared/WhaleBanner.js +12 -0
- package/dist/cli/shared/markdown.d.ts +21 -0
- package/dist/cli/shared/markdown.js +756 -0
- package/dist/cli/status/StatusApp.d.ts +4 -0
- package/dist/cli/status/StatusApp.js +105 -0
- package/dist/cli/stores/StoreApp.d.ts +7 -0
- package/dist/cli/stores/StoreApp.js +81 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +538 -0
- package/dist/local-agent/connection.d.ts +48 -0
- package/dist/local-agent/connection.js +332 -0
- package/dist/local-agent/discovery.d.ts +18 -0
- package/dist/local-agent/discovery.js +146 -0
- package/dist/local-agent/executor.d.ts +34 -0
- package/dist/local-agent/executor.js +241 -0
- package/dist/local-agent/index.d.ts +14 -0
- package/dist/local-agent/index.js +198 -0
- package/dist/node/adapters/base.d.ts +35 -0
- package/dist/node/adapters/base.js +10 -0
- package/dist/node/adapters/discord.d.ts +29 -0
- package/dist/node/adapters/discord.js +299 -0
- package/dist/node/adapters/email.d.ts +23 -0
- package/dist/node/adapters/email.js +218 -0
- package/dist/node/adapters/imessage.d.ts +17 -0
- package/dist/node/adapters/imessage.js +118 -0
- package/dist/node/adapters/slack.d.ts +26 -0
- package/dist/node/adapters/slack.js +259 -0
- package/dist/node/adapters/sms.d.ts +23 -0
- package/dist/node/adapters/sms.js +161 -0
- package/dist/node/adapters/telegram.d.ts +17 -0
- package/dist/node/adapters/telegram.js +101 -0
- package/dist/node/adapters/webchat.d.ts +27 -0
- package/dist/node/adapters/webchat.js +160 -0
- package/dist/node/adapters/whatsapp.d.ts +28 -0
- package/dist/node/adapters/whatsapp.js +230 -0
- package/dist/node/cli.d.ts +2 -0
- package/dist/node/cli.js +325 -0
- package/dist/node/config.d.ts +17 -0
- package/dist/node/config.js +31 -0
- package/dist/node/runtime.d.ts +50 -0
- package/dist/node/runtime.js +351 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
- package/dist/server/handlers/analytics.d.ts +17 -0
- package/dist/server/handlers/analytics.js +266 -0
- package/dist/server/handlers/api-keys.d.ts +6 -0
- package/dist/server/handlers/api-keys.js +221 -0
- package/dist/server/handlers/billing.d.ts +33 -0
- package/dist/server/handlers/billing.js +272 -0
- package/dist/server/handlers/browser.d.ts +10 -0
- package/dist/server/handlers/browser.js +517 -0
- package/dist/server/handlers/catalog.d.ts +99 -0
- package/dist/server/handlers/catalog.js +976 -0
- package/dist/server/handlers/comms.d.ts +254 -0
- package/dist/server/handlers/comms.js +588 -0
- package/dist/server/handlers/creations.d.ts +6 -0
- package/dist/server/handlers/creations.js +479 -0
- package/dist/server/handlers/crm.d.ts +89 -0
- package/dist/server/handlers/crm.js +538 -0
- package/dist/server/handlers/discovery.d.ts +6 -0
- package/dist/server/handlers/discovery.js +288 -0
- package/dist/server/handlers/embeddings.d.ts +92 -0
- package/dist/server/handlers/embeddings.js +197 -0
- package/dist/server/handlers/enrichment.d.ts +8 -0
- package/dist/server/handlers/enrichment.js +768 -0
- package/dist/server/handlers/image-gen.d.ts +6 -0
- package/dist/server/handlers/image-gen.js +409 -0
- package/dist/server/handlers/inventory.d.ts +319 -0
- package/dist/server/handlers/inventory.js +447 -0
- package/dist/server/handlers/kali.d.ts +10 -0
- package/dist/server/handlers/kali.js +210 -0
- package/dist/server/handlers/llm-providers.d.ts +6 -0
- package/dist/server/handlers/llm-providers.js +673 -0
- package/dist/server/handlers/local-agent.d.ts +6 -0
- package/dist/server/handlers/local-agent.js +118 -0
- package/dist/server/handlers/meta-ads.d.ts +111 -0
- package/dist/server/handlers/meta-ads.js +2279 -0
- package/dist/server/handlers/nodes.d.ts +33 -0
- package/dist/server/handlers/nodes.js +699 -0
- package/dist/server/handlers/operations.d.ts +138 -0
- package/dist/server/handlers/operations.js +131 -0
- package/dist/server/handlers/platform.d.ts +23 -0
- package/dist/server/handlers/platform.js +227 -0
- package/dist/server/handlers/supply-chain.d.ts +19 -0
- package/dist/server/handlers/supply-chain.js +327 -0
- package/dist/server/handlers/transcription.d.ts +17 -0
- package/dist/server/handlers/transcription.js +121 -0
- package/dist/server/handlers/video-gen.d.ts +6 -0
- package/dist/server/handlers/video-gen.js +466 -0
- package/dist/server/handlers/voice.d.ts +8 -0
- package/dist/server/handlers/voice.js +1146 -0
- package/dist/server/handlers/workflow-steps.d.ts +86 -0
- package/dist/server/handlers/workflow-steps.js +2349 -0
- package/dist/server/handlers/workflows.d.ts +7 -0
- package/dist/server/handlers/workflows.js +989 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +2427 -0
- package/dist/server/lib/batch-client.d.ts +80 -0
- package/dist/server/lib/batch-client.js +467 -0
- package/dist/server/lib/code-worker-pool.d.ts +31 -0
- package/dist/server/lib/code-worker-pool.js +224 -0
- package/dist/server/lib/code-worker.d.ts +1 -0
- package/dist/server/lib/code-worker.js +188 -0
- package/dist/server/lib/compaction-service.d.ts +32 -0
- package/dist/server/lib/compaction-service.js +162 -0
- package/dist/server/lib/logger.d.ts +19 -0
- package/dist/server/lib/logger.js +46 -0
- package/dist/server/lib/otel.d.ts +38 -0
- package/dist/server/lib/otel.js +126 -0
- package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
- package/dist/server/lib/pg-rate-limiter.js +86 -0
- package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
- package/dist/server/lib/prompt-sanitizer.js +177 -0
- package/dist/server/lib/provider-capabilities.d.ts +85 -0
- package/dist/server/lib/provider-capabilities.js +190 -0
- package/dist/server/lib/provider-failover.d.ts +74 -0
- package/dist/server/lib/provider-failover.js +210 -0
- package/dist/server/lib/rate-limiter.d.ts +39 -0
- package/dist/server/lib/rate-limiter.js +147 -0
- package/dist/server/lib/server-agent-loop.d.ts +107 -0
- package/dist/server/lib/server-agent-loop.js +667 -0
- package/dist/server/lib/server-subagent.d.ts +78 -0
- package/dist/server/lib/server-subagent.js +203 -0
- package/dist/server/lib/session-checkpoint.d.ts +51 -0
- package/dist/server/lib/session-checkpoint.js +145 -0
- package/dist/server/lib/ssrf-guard.d.ts +13 -0
- package/dist/server/lib/ssrf-guard.js +240 -0
- package/dist/server/lib/supabase-client.d.ts +7 -0
- package/dist/server/lib/supabase-client.js +78 -0
- package/dist/server/lib/template-resolver.d.ts +31 -0
- package/dist/server/lib/template-resolver.js +215 -0
- package/dist/server/lib/utils.d.ts +16 -0
- package/dist/server/lib/utils.js +147 -0
- package/dist/server/local-agent-gateway.d.ts +82 -0
- package/dist/server/local-agent-gateway.js +426 -0
- package/dist/server/providers/anthropic.d.ts +20 -0
- package/dist/server/providers/anthropic.js +199 -0
- package/dist/server/providers/bedrock.d.ts +20 -0
- package/dist/server/providers/bedrock.js +194 -0
- package/dist/server/providers/gemini.d.ts +24 -0
- package/dist/server/providers/gemini.js +486 -0
- package/dist/server/providers/openai.d.ts +24 -0
- package/dist/server/providers/openai.js +522 -0
- package/dist/server/providers/registry.d.ts +32 -0
- package/dist/server/providers/registry.js +58 -0
- package/dist/server/providers/shared.d.ts +32 -0
- package/dist/server/providers/shared.js +124 -0
- package/dist/server/providers/types.d.ts +92 -0
- package/dist/server/providers/types.js +12 -0
- package/dist/server/proxy-handlers.d.ts +6 -0
- package/dist/server/proxy-handlers.js +89 -0
- package/dist/server/tool-router.d.ts +149 -0
- package/dist/server/tool-router.js +803 -0
- package/dist/server/validation.d.ts +24 -0
- package/dist/server/validation.js +301 -0
- package/dist/server/worker.d.ts +19 -0
- package/dist/server/worker.js +201 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +181 -0
- package/dist/shared/agent-core.d.ts +157 -0
- package/dist/shared/agent-core.js +534 -0
- package/dist/shared/anthropic-types.d.ts +105 -0
- package/dist/shared/anthropic-types.js +7 -0
- package/dist/shared/api-client.d.ts +90 -0
- package/dist/shared/api-client.js +379 -0
- package/dist/shared/constants.d.ts +33 -0
- package/dist/shared/constants.js +80 -0
- package/dist/shared/sse-parser.d.ts +26 -0
- package/dist/shared/sse-parser.js +259 -0
- package/dist/shared/tool-dispatch.d.ts +52 -0
- package/dist/shared/tool-dispatch.js +191 -0
- package/dist/shared/types.d.ts +72 -0
- package/dist/shared/types.js +7 -0
- package/dist/updater.d.ts +25 -0
- package/dist/updater.js +140 -0
- package/dist/webchat/widget.d.ts +0 -0
- package/dist/webchat/widget.js +397 -0
- package/package.json +95 -0
- package/src/cli/services/builtin-skills/commit.md +19 -0
- package/src/cli/services/builtin-skills/review-pr.md +21 -0
- package/src/cli/services/builtin-skills/review.md +18 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// UNIFIED RPC FUNCTIONS — same functions used by Swift app
|
|
3
|
+
// PO: create_purchase_order_atomic, update_po_status, receive_po_items
|
|
4
|
+
// Transfer: create_inventory_transfer, approve_inventory_transfer,
|
|
5
|
+
// mark_transfer_in_transit, complete_inventory_transfer,
|
|
6
|
+
// cancel_inventory_transfer
|
|
7
|
+
// ============================================================================
|
|
8
|
+
export async function handlePurchaseOrders(sb, args, storeId) {
|
|
9
|
+
const sid = storeId;
|
|
10
|
+
switch (args.action) {
|
|
11
|
+
case "list": {
|
|
12
|
+
let q = sb.from("purchase_orders").select("*, supplier:suppliers(external_name, external_company), location:locations(name)")
|
|
13
|
+
.eq("store_id", sid).order("created_at", { ascending: false }).limit(args.limit || 25);
|
|
14
|
+
if (args.status)
|
|
15
|
+
q = q.eq("status", args.status);
|
|
16
|
+
const { data, error } = await q;
|
|
17
|
+
if (error)
|
|
18
|
+
return { success: false, error: error.message };
|
|
19
|
+
// Flatten supplier/location joins for table display
|
|
20
|
+
const flattened = (data || []).map((row) => {
|
|
21
|
+
const { supplier, location, ...rest } = row;
|
|
22
|
+
return { ...rest, supplier_name: supplier?.external_name || supplier?.external_company || "—", location_name: location?.name || "—" };
|
|
23
|
+
});
|
|
24
|
+
return { success: true, data: flattened };
|
|
25
|
+
}
|
|
26
|
+
case "get": {
|
|
27
|
+
const { data, error } = await sb.from("purchase_orders")
|
|
28
|
+
.select("*, items:purchase_order_items(id, product_id, quantity, unit_price, received_quantity, subtotal, product:products(name, sku)), supplier:suppliers(id, external_name, external_company, contact_email, contact_phone)")
|
|
29
|
+
.eq("id", args.purchase_order_id).eq("store_id", sid).single();
|
|
30
|
+
if (error)
|
|
31
|
+
return { success: false, error: error.message };
|
|
32
|
+
// Flatten items so product names appear as columns (formatter drops nested objects in sub-tables)
|
|
33
|
+
const po = data;
|
|
34
|
+
const items = (po.items || []).map((item) => ({
|
|
35
|
+
id: item.id, product_id: item.product_id,
|
|
36
|
+
product_name: item.product?.name || "—", product_sku: item.product?.sku || "—",
|
|
37
|
+
quantity: item.quantity, unit_price: item.unit_price,
|
|
38
|
+
received: item.received_quantity || 0, subtotal: item.subtotal,
|
|
39
|
+
}));
|
|
40
|
+
const { items: _, supplier, ...poFields } = po;
|
|
41
|
+
return {
|
|
42
|
+
success: true,
|
|
43
|
+
data: {
|
|
44
|
+
...poFields,
|
|
45
|
+
supplier_name: supplier?.external_name || supplier?.external_company || "—",
|
|
46
|
+
supplier_email: supplier?.contact_email || null,
|
|
47
|
+
items,
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
case "create": {
|
|
52
|
+
// create_purchase_order_atomic RPC — SECURITY DEFINER, returns JSONB {success, po_id, po_number}
|
|
53
|
+
// p_items is TEXT type (JSON string), not JSONB
|
|
54
|
+
const items = (args.items || []).map(item => ({
|
|
55
|
+
product_id: item.product_id,
|
|
56
|
+
quantity: Math.max(Number(item.quantity) || 1, 0),
|
|
57
|
+
unit_cost: Math.max(Number(item.unit_cost || item.unit_price) || 0, 0),
|
|
58
|
+
}));
|
|
59
|
+
const { data: result, error: rpcError } = await sb.rpc("create_purchase_order_atomic", {
|
|
60
|
+
p_store_id: sid,
|
|
61
|
+
p_po_type: "inbound",
|
|
62
|
+
p_supplier_id: args.supplier_id || null,
|
|
63
|
+
p_location_id: args.location_id || null,
|
|
64
|
+
p_expected_delivery_date: args.expected_delivery_date || null,
|
|
65
|
+
p_notes: args.notes || null,
|
|
66
|
+
p_items: JSON.stringify(items),
|
|
67
|
+
});
|
|
68
|
+
if (rpcError)
|
|
69
|
+
return { success: false, error: rpcError.message };
|
|
70
|
+
const rpcResult = parseRpcResult(result);
|
|
71
|
+
if (!rpcResult.success)
|
|
72
|
+
return { success: false, error: rpcResult.error || "PO creation failed" };
|
|
73
|
+
// Fetch full PO for response — flatten nested joins
|
|
74
|
+
const { data: fullPo } = await sb.from("purchase_orders")
|
|
75
|
+
.select("*, items:purchase_order_items(id, product_id, quantity, unit_price, subtotal, product:products(name, sku)), supplier:suppliers(id, external_name, external_company)")
|
|
76
|
+
.eq("id", rpcResult.po_id).single();
|
|
77
|
+
if (!fullPo)
|
|
78
|
+
return { success: true, data: { po_id: rpcResult.po_id, po_number: rpcResult.po_number } };
|
|
79
|
+
const po = fullPo;
|
|
80
|
+
const flatItems = (po.items || []).map((item) => ({
|
|
81
|
+
id: item.id, product_id: item.product_id,
|
|
82
|
+
product_name: item.product?.name || "—", product_sku: item.product?.sku || "—",
|
|
83
|
+
quantity: item.quantity, unit_price: item.unit_price, subtotal: item.subtotal,
|
|
84
|
+
}));
|
|
85
|
+
const { items: _poItems, supplier, ...poFields } = po;
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
data: {
|
|
89
|
+
...poFields,
|
|
90
|
+
supplier_name: supplier?.external_name || supplier?.external_company || "—",
|
|
91
|
+
items: flatItems,
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
case "add_items": {
|
|
96
|
+
// No atomic RPC exists for adding items to an existing PO — direct insert
|
|
97
|
+
const poId = args.purchase_order_id;
|
|
98
|
+
if (!poId)
|
|
99
|
+
return { success: false, error: "purchase_order_id is required" };
|
|
100
|
+
const items = args.items;
|
|
101
|
+
if (!items || !Array.isArray(items) || !items.length)
|
|
102
|
+
return { success: false, error: "items array is required" };
|
|
103
|
+
const { data: po } = await sb.from("purchase_orders").select("status").eq("id", poId).eq("store_id", sid).single();
|
|
104
|
+
if (!po)
|
|
105
|
+
return { success: false, error: "PO not found" };
|
|
106
|
+
if (po.status === "received")
|
|
107
|
+
return { success: false, error: "Cannot add items — PO already received" };
|
|
108
|
+
if (po.status === "cancelled")
|
|
109
|
+
return { success: false, error: "Cannot add items — PO is cancelled" };
|
|
110
|
+
const rows = items.map(item => {
|
|
111
|
+
const qty = Math.max(Number(item.quantity) || 1, 0);
|
|
112
|
+
const price = Math.max(Number(item.unit_cost || item.unit_price) || 0, 0);
|
|
113
|
+
return { purchase_order_id: poId, product_id: item.product_id, quantity: qty, unit_price: price, subtotal: qty * price };
|
|
114
|
+
});
|
|
115
|
+
const { data, error } = await sb.from("purchase_order_items").insert(rows).select("*, product:products(name, sku)");
|
|
116
|
+
return error ? { success: false, error: error.message } : { success: true, data };
|
|
117
|
+
}
|
|
118
|
+
case "approve":
|
|
119
|
+
case "mark_ordered": {
|
|
120
|
+
// PO flow: draft → ordered. "approve" = mark as ordered.
|
|
121
|
+
// update_po_status RPC — SECURITY DEFINER, returns JSONB {success, old_status, new_status}
|
|
122
|
+
const { data: result, error: rpcError } = await sb.rpc("update_po_status", {
|
|
123
|
+
p_po_id: args.purchase_order_id,
|
|
124
|
+
p_status: "ordered",
|
|
125
|
+
});
|
|
126
|
+
if (rpcError)
|
|
127
|
+
return { success: false, error: rpcError.message };
|
|
128
|
+
const rpcResult = parseRpcResult(result);
|
|
129
|
+
if (!rpcResult.success)
|
|
130
|
+
return { success: false, error: rpcResult.error || "Status update failed" };
|
|
131
|
+
return { success: true, data: { old_status: rpcResult.old_status, new_status: rpcResult.new_status } };
|
|
132
|
+
}
|
|
133
|
+
case "receive": {
|
|
134
|
+
// receive_po_items RPC — SECURITY DEFINER, returns JSONB {success, items_processed}
|
|
135
|
+
// Params: p_po_id, p_location_id, p_items [{item_id, quantity}]
|
|
136
|
+
const poId = args.purchase_order_id;
|
|
137
|
+
const { data: po, error: poErr } = await sb.from("purchase_orders")
|
|
138
|
+
.select("location_id, items:purchase_order_items(id, quantity, received_quantity)")
|
|
139
|
+
.eq("id", poId).eq("store_id", sid).single();
|
|
140
|
+
if (poErr || !po)
|
|
141
|
+
return { success: false, error: poErr?.message || "PO not found" };
|
|
142
|
+
// If caller provides specific items use those, otherwise receive all remaining
|
|
143
|
+
const receiveItems = args.items
|
|
144
|
+
? args.items
|
|
145
|
+
: (po.items || [])
|
|
146
|
+
.map((item) => ({
|
|
147
|
+
item_id: item.id,
|
|
148
|
+
quantity: Math.max((item.quantity || 0) - (item.received_quantity || 0), 0),
|
|
149
|
+
}))
|
|
150
|
+
.filter((item) => item.quantity > 0);
|
|
151
|
+
if (!receiveItems.length)
|
|
152
|
+
return { success: false, error: "No items to receive (all fully received)" };
|
|
153
|
+
const locationId = args.location_id || po.location_id;
|
|
154
|
+
if (!locationId)
|
|
155
|
+
return { success: false, error: "No location_id on PO — cannot receive" };
|
|
156
|
+
const { data: result, error: rpcError } = await sb.rpc("receive_po_items", {
|
|
157
|
+
p_po_id: poId,
|
|
158
|
+
p_location_id: locationId,
|
|
159
|
+
p_items: receiveItems,
|
|
160
|
+
});
|
|
161
|
+
if (rpcError)
|
|
162
|
+
return { success: false, error: rpcError.message };
|
|
163
|
+
const rpcResult = parseRpcResult(result);
|
|
164
|
+
if (!rpcResult.success)
|
|
165
|
+
return { success: false, error: rpcResult.error || "PO receive failed" };
|
|
166
|
+
return {
|
|
167
|
+
success: true,
|
|
168
|
+
data: {
|
|
169
|
+
po_id: poId,
|
|
170
|
+
items_processed: rpcResult.items_processed,
|
|
171
|
+
message: `Received ${rpcResult.items_processed || 0} item(s) into inventory`,
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
case "cancel": {
|
|
176
|
+
// update_po_status RPC — SECURITY DEFINER, returns JSONB {success, old_status, new_status}
|
|
177
|
+
const { data: result, error: rpcError } = await sb.rpc("update_po_status", {
|
|
178
|
+
p_po_id: args.purchase_order_id,
|
|
179
|
+
p_status: "cancelled",
|
|
180
|
+
});
|
|
181
|
+
if (rpcError)
|
|
182
|
+
return { success: false, error: rpcError.message };
|
|
183
|
+
const rpcResult = parseRpcResult(result);
|
|
184
|
+
if (!rpcResult.success)
|
|
185
|
+
return { success: false, error: rpcResult.error || "Cancel failed" };
|
|
186
|
+
return { success: true, data: { old_status: rpcResult.old_status, new_status: rpcResult.new_status } };
|
|
187
|
+
}
|
|
188
|
+
default:
|
|
189
|
+
return { success: false, error: `Unknown purchase_orders action: ${args.action}. Valid: list, get, create, add_items, approve, mark_ordered, receive, cancel` };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export async function handleTransfers(sb, args, storeId) {
|
|
193
|
+
const sid = storeId;
|
|
194
|
+
switch (args.action) {
|
|
195
|
+
case "list": {
|
|
196
|
+
let q = sb.from("inventory_transfers")
|
|
197
|
+
.select("*, from_location:locations!source_location_id(name), to_location:locations!destination_location_id(name)")
|
|
198
|
+
.eq("store_id", sid).order("created_at", { ascending: false }).limit(args.limit || 25);
|
|
199
|
+
if (args.status)
|
|
200
|
+
q = q.eq("status", args.status);
|
|
201
|
+
const { data, error } = await q;
|
|
202
|
+
if (error)
|
|
203
|
+
return { success: false, error: error.message };
|
|
204
|
+
// Flatten location joins for table display
|
|
205
|
+
const flattened = (data || []).map((row) => {
|
|
206
|
+
const { from_location, to_location, ...rest } = row;
|
|
207
|
+
return { ...rest, from_location: from_location?.name || "—", to_location: to_location?.name || "—" };
|
|
208
|
+
});
|
|
209
|
+
return { success: true, data: flattened };
|
|
210
|
+
}
|
|
211
|
+
case "create": {
|
|
212
|
+
// create_inventory_transfer RPC — SECURITY DEFINER, returns JSONB {success, transfer_id, transfer_number}
|
|
213
|
+
// p_items is JSONB (pass as object, not string)
|
|
214
|
+
const sourceLocId = (args.from_location_id || args.source_location_id);
|
|
215
|
+
const destLocId = (args.to_location_id || args.destination_location_id);
|
|
216
|
+
const items = (args.items || []).map(i => ({
|
|
217
|
+
product_id: i.product_id,
|
|
218
|
+
quantity: i.quantity,
|
|
219
|
+
}));
|
|
220
|
+
const params = {
|
|
221
|
+
p_store_id: sid,
|
|
222
|
+
p_source_location_id: sourceLocId,
|
|
223
|
+
p_destination_location_id: destLocId,
|
|
224
|
+
p_items: items,
|
|
225
|
+
};
|
|
226
|
+
if (args.notes)
|
|
227
|
+
params.p_notes = args.notes;
|
|
228
|
+
const { data: result, error: rpcError } = await sb.rpc("create_inventory_transfer", params);
|
|
229
|
+
if (rpcError)
|
|
230
|
+
return { success: false, error: rpcError.message };
|
|
231
|
+
const rpcResult = parseRpcResult(result);
|
|
232
|
+
if (!rpcResult.success)
|
|
233
|
+
return { success: false, error: rpcResult.error || "Transfer creation failed" };
|
|
234
|
+
return { success: true, data: { transfer_id: rpcResult.transfer_id, transfer_number: rpcResult.transfer_number } };
|
|
235
|
+
}
|
|
236
|
+
case "get": {
|
|
237
|
+
const { data, error } = await sb.from("inventory_transfers")
|
|
238
|
+
.select("*, items:inventory_transfer_items(id, product_id, quantity, product:products(name, sku)), from_location:locations!source_location_id(id, name), to_location:locations!destination_location_id(id, name)")
|
|
239
|
+
.eq("id", args.transfer_id).eq("store_id", sid).single();
|
|
240
|
+
if (error)
|
|
241
|
+
return { success: false, error: error.message };
|
|
242
|
+
// Flatten nested joins so items and locations are visible
|
|
243
|
+
const transfer = data;
|
|
244
|
+
const items = (transfer.items || []).map((item) => ({
|
|
245
|
+
id: item.id, product_id: item.product_id,
|
|
246
|
+
product_name: item.product?.name || "—", product_sku: item.product?.sku || "—",
|
|
247
|
+
quantity: item.quantity,
|
|
248
|
+
}));
|
|
249
|
+
const { items: _, from_location, to_location, ...transferFields } = transfer;
|
|
250
|
+
return {
|
|
251
|
+
success: true,
|
|
252
|
+
data: {
|
|
253
|
+
...transferFields,
|
|
254
|
+
from_location_name: from_location?.name || "—",
|
|
255
|
+
to_location_name: to_location?.name || "—",
|
|
256
|
+
items,
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
case "approve": {
|
|
261
|
+
// approve_inventory_transfer RPC — SECURITY DEFINER, returns boolean
|
|
262
|
+
// Validates stock, draft → approved
|
|
263
|
+
const { data: result, error: rpcError } = await sb.rpc("approve_inventory_transfer", {
|
|
264
|
+
p_transfer_id: args.transfer_id,
|
|
265
|
+
});
|
|
266
|
+
if (rpcError)
|
|
267
|
+
return { success: false, error: rpcError.message };
|
|
268
|
+
return { success: true, data: { approved: result } };
|
|
269
|
+
}
|
|
270
|
+
case "ship":
|
|
271
|
+
case "mark_in_transit": {
|
|
272
|
+
// mark_transfer_in_transit RPC — SECURITY DEFINER, returns boolean
|
|
273
|
+
// Validates stock, creates holds, draft/approved → in_transit
|
|
274
|
+
const { data: result, error: rpcError } = await sb.rpc("mark_transfer_in_transit", {
|
|
275
|
+
p_transfer_id: args.transfer_id,
|
|
276
|
+
});
|
|
277
|
+
if (rpcError)
|
|
278
|
+
return { success: false, error: rpcError.message };
|
|
279
|
+
return { success: true, data: { in_transit: result } };
|
|
280
|
+
}
|
|
281
|
+
case "receive":
|
|
282
|
+
case "complete": {
|
|
283
|
+
// complete_inventory_transfer RPC — SECURITY DEFINER, returns JSONB {success, items_processed}
|
|
284
|
+
// Deducts source, adds to destination, releases holds
|
|
285
|
+
const { data: result, error: rpcError } = await sb.rpc("complete_inventory_transfer", {
|
|
286
|
+
p_transfer_id: args.transfer_id,
|
|
287
|
+
});
|
|
288
|
+
if (rpcError)
|
|
289
|
+
return { success: false, error: rpcError.message };
|
|
290
|
+
const rpcResult = parseRpcResult(result);
|
|
291
|
+
if (!rpcResult.success)
|
|
292
|
+
return { success: false, error: rpcResult.error || "Transfer complete failed" };
|
|
293
|
+
return {
|
|
294
|
+
success: true,
|
|
295
|
+
data: {
|
|
296
|
+
transfer_id: args.transfer_id,
|
|
297
|
+
items_processed: rpcResult.items_processed,
|
|
298
|
+
message: `Completed transfer, ${rpcResult.items_processed || 0} item(s) moved to destination`,
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
case "cancel": {
|
|
303
|
+
// cancel_inventory_transfer RPC — SECURITY DEFINER, returns boolean
|
|
304
|
+
// Releases holds, any non-final → cancelled
|
|
305
|
+
const { data: result, error: rpcError } = await sb.rpc("cancel_inventory_transfer", {
|
|
306
|
+
p_transfer_id: args.transfer_id,
|
|
307
|
+
});
|
|
308
|
+
if (rpcError)
|
|
309
|
+
return { success: false, error: rpcError.message };
|
|
310
|
+
return { success: true, data: { cancelled: result, message: "Transfer cancelled" } };
|
|
311
|
+
}
|
|
312
|
+
default:
|
|
313
|
+
return { success: false, error: `Unknown transfers action: ${args.action}. Valid: list, get, create, approve, ship, mark_in_transit, receive, complete, cancel` };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/** Parse RPC result — handles both raw objects and stringified JSON */
|
|
317
|
+
function parseRpcResult(result) {
|
|
318
|
+
if (typeof result === "string") {
|
|
319
|
+
try {
|
|
320
|
+
return JSON.parse(result);
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return { success: false, error: result };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return result || { success: false, error: "Empty RPC response" };
|
|
327
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
export interface TranscribeResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
transcript?: string;
|
|
5
|
+
language?: string;
|
|
6
|
+
duration?: number;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Transcribe audio using OpenAI Whisper API.
|
|
11
|
+
*
|
|
12
|
+
* @param sb - Supabase client (service role) for credential lookup
|
|
13
|
+
* @param storeId - Store ID to resolve OpenAI API key
|
|
14
|
+
* @param audioBase64 - Base64-encoded audio data
|
|
15
|
+
* @param mediaType - MIME type (e.g. "audio/mpeg", "audio/wav")
|
|
16
|
+
*/
|
|
17
|
+
export declare function handleTranscribe(sb: SupabaseClient, storeId: string, audioBase64: string, mediaType: string): Promise<TranscribeResult>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// server/handlers/transcription.ts — Audio transcription via OpenAI Whisper
|
|
2
|
+
const WHISPER_URL = "https://api.openai.com/v1/audio/transcriptions";
|
|
3
|
+
const WHISPER_MODEL = "whisper-1";
|
|
4
|
+
/** Map media types to file extensions for the Whisper API */
|
|
5
|
+
const MEDIA_TYPE_EXTENSIONS = {
|
|
6
|
+
"audio/mpeg": "mp3",
|
|
7
|
+
"audio/wav": "wav",
|
|
8
|
+
"audio/aiff": "aiff",
|
|
9
|
+
"audio/aac": "aac",
|
|
10
|
+
"audio/ogg": "ogg",
|
|
11
|
+
"audio/flac": "flac",
|
|
12
|
+
"audio/mp4": "m4a",
|
|
13
|
+
"audio/webm": "webm",
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Transcribe audio using OpenAI Whisper API.
|
|
17
|
+
*
|
|
18
|
+
* @param sb - Supabase client (service role) for credential lookup
|
|
19
|
+
* @param storeId - Store ID to resolve OpenAI API key
|
|
20
|
+
* @param audioBase64 - Base64-encoded audio data
|
|
21
|
+
* @param mediaType - MIME type (e.g. "audio/mpeg", "audio/wav")
|
|
22
|
+
*/
|
|
23
|
+
export async function handleTranscribe(sb, storeId, audioBase64, mediaType) {
|
|
24
|
+
// Resolve OpenAI API key
|
|
25
|
+
let apiKey = null;
|
|
26
|
+
try {
|
|
27
|
+
const { data, error } = await sb.rpc("decrypt_secret", {
|
|
28
|
+
p_name: "OPENAI_API_KEY",
|
|
29
|
+
p_store_id: storeId,
|
|
30
|
+
});
|
|
31
|
+
if (!error && data)
|
|
32
|
+
apiKey = data;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// key not found
|
|
36
|
+
}
|
|
37
|
+
if (!apiKey) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
error: "OpenAI API key not configured. Add OPENAI_API_KEY to your tool secrets for audio transcription.",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Decode base64 to buffer
|
|
44
|
+
const audioBuffer = Buffer.from(audioBase64, "base64");
|
|
45
|
+
// Determine file extension from media type
|
|
46
|
+
const ext = MEDIA_TYPE_EXTENSIONS[mediaType] || "mp3";
|
|
47
|
+
const filename = `audio.${ext}`;
|
|
48
|
+
// Build multipart form data for Whisper API
|
|
49
|
+
const blob = new Blob([audioBuffer], { type: mediaType });
|
|
50
|
+
const formData = new FormData();
|
|
51
|
+
formData.append("file", blob, filename);
|
|
52
|
+
formData.append("model", WHISPER_MODEL);
|
|
53
|
+
formData.append("response_format", "verbose_json");
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(WHISPER_URL, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${apiKey}`,
|
|
59
|
+
},
|
|
60
|
+
body: formData,
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const errText = await response.text();
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: `Whisper API error ${response.status}: ${errText}`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
// Format transcript with timestamps if segments available
|
|
71
|
+
let transcript;
|
|
72
|
+
if (data.segments && data.segments.length > 0) {
|
|
73
|
+
const lines = data.segments.map((seg) => {
|
|
74
|
+
const start = formatTimestamp(seg.start);
|
|
75
|
+
return `[${start}] ${seg.text.trim()}`;
|
|
76
|
+
});
|
|
77
|
+
transcript = lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
transcript = data.text || "";
|
|
81
|
+
}
|
|
82
|
+
// Add metadata header
|
|
83
|
+
const durationStr = data.duration ? formatDuration(data.duration) : "unknown";
|
|
84
|
+
const langStr = data.language || "unknown";
|
|
85
|
+
const header = `Audio transcript (${durationStr}, ${langStr})`;
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
transcript: `${header}\n\n${transcript}`,
|
|
89
|
+
language: data.language,
|
|
90
|
+
duration: data.duration,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
error: `Transcription failed: ${err.message}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Format seconds to MM:SS or HH:MM:SS */
|
|
101
|
+
function formatTimestamp(seconds) {
|
|
102
|
+
const h = Math.floor(seconds / 3600);
|
|
103
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
104
|
+
const s = Math.floor(seconds % 60);
|
|
105
|
+
if (h > 0) {
|
|
106
|
+
return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
107
|
+
}
|
|
108
|
+
return `${m}:${String(s).padStart(2, "0")}`;
|
|
109
|
+
}
|
|
110
|
+
/** Format total duration */
|
|
111
|
+
function formatDuration(seconds) {
|
|
112
|
+
if (seconds < 60)
|
|
113
|
+
return `${Math.round(seconds)}s`;
|
|
114
|
+
const m = Math.floor(seconds / 60);
|
|
115
|
+
const s = Math.round(seconds % 60);
|
|
116
|
+
if (m < 60)
|
|
117
|
+
return `${m}:${String(s).padStart(2, "0")}`;
|
|
118
|
+
const h = Math.floor(m / 60);
|
|
119
|
+
const rm = m % 60;
|
|
120
|
+
return `${h}:${String(rm).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
121
|
+
}
|