pelagora 0.1.5

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 (122) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/LICENSE +663 -0
  3. package/README.md +139 -0
  4. package/dist/ai/product-lookup.d.ts +26 -0
  5. package/dist/ai/product-lookup.d.ts.map +1 -0
  6. package/dist/ai/product-lookup.js +153 -0
  7. package/dist/ai/product-lookup.js.map +1 -0
  8. package/dist/api/collections.d.ts +3 -0
  9. package/dist/api/collections.d.ts.map +1 -0
  10. package/dist/api/collections.js +69 -0
  11. package/dist/api/collections.js.map +1 -0
  12. package/dist/api/favorites.d.ts +3 -0
  13. package/dist/api/favorites.d.ts.map +1 -0
  14. package/dist/api/favorites.js +43 -0
  15. package/dist/api/favorites.js.map +1 -0
  16. package/dist/api/health.d.ts +12 -0
  17. package/dist/api/health.d.ts.map +1 -0
  18. package/dist/api/health.js +54 -0
  19. package/dist/api/health.js.map +1 -0
  20. package/dist/api/index.d.ts +3 -0
  21. package/dist/api/index.d.ts.map +1 -0
  22. package/dist/api/index.js +56 -0
  23. package/dist/api/index.js.map +1 -0
  24. package/dist/api/items.d.ts +3 -0
  25. package/dist/api/items.d.ts.map +1 -0
  26. package/dist/api/items.js +170 -0
  27. package/dist/api/items.js.map +1 -0
  28. package/dist/api/media.d.ts +3 -0
  29. package/dist/api/media.d.ts.map +1 -0
  30. package/dist/api/media.js +241 -0
  31. package/dist/api/media.js.map +1 -0
  32. package/dist/api/negotiations.d.ts +3 -0
  33. package/dist/api/negotiations.d.ts.map +1 -0
  34. package/dist/api/negotiations.js +244 -0
  35. package/dist/api/negotiations.js.map +1 -0
  36. package/dist/api/offers.d.ts +3 -0
  37. package/dist/api/offers.d.ts.map +1 -0
  38. package/dist/api/offers.js +57 -0
  39. package/dist/api/offers.js.map +1 -0
  40. package/dist/api/refs.d.ts +3 -0
  41. package/dist/api/refs.d.ts.map +1 -0
  42. package/dist/api/refs.js +390 -0
  43. package/dist/api/refs.js.map +1 -0
  44. package/dist/api/scans.d.ts +3 -0
  45. package/dist/api/scans.d.ts.map +1 -0
  46. package/dist/api/scans.js +384 -0
  47. package/dist/api/scans.js.map +1 -0
  48. package/dist/api/settings.d.ts +3 -0
  49. package/dist/api/settings.d.ts.map +1 -0
  50. package/dist/api/settings.js +442 -0
  51. package/dist/api/settings.js.map +1 -0
  52. package/dist/db/index.d.ts +4 -0
  53. package/dist/db/index.d.ts.map +1 -0
  54. package/dist/db/index.js +18 -0
  55. package/dist/db/index.js.map +1 -0
  56. package/dist/db/queries.d.ts +237 -0
  57. package/dist/db/queries.d.ts.map +1 -0
  58. package/dist/db/queries.js +891 -0
  59. package/dist/db/queries.js.map +1 -0
  60. package/dist/db/schema.d.ts +6 -0
  61. package/dist/db/schema.d.ts.map +1 -0
  62. package/dist/db/schema.js +531 -0
  63. package/dist/db/schema.js.map +1 -0
  64. package/dist/dht/discovery.d.ts +33 -0
  65. package/dist/dht/discovery.d.ts.map +1 -0
  66. package/dist/dht/discovery.js +281 -0
  67. package/dist/dht/discovery.js.map +1 -0
  68. package/dist/dht/index.d.ts +2 -0
  69. package/dist/dht/index.d.ts.map +1 -0
  70. package/dist/dht/index.js +6 -0
  71. package/dist/dht/index.js.map +1 -0
  72. package/dist/index.d.ts +2 -0
  73. package/dist/index.d.ts.map +1 -0
  74. package/dist/index.js +215 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/ref-schemas.d.ts +59 -0
  77. package/dist/ref-schemas.d.ts.map +1 -0
  78. package/dist/ref-schemas.js +1038 -0
  79. package/dist/ref-schemas.js.map +1 -0
  80. package/dist/skills/export.d.ts +12 -0
  81. package/dist/skills/export.d.ts.map +1 -0
  82. package/dist/skills/export.js +54 -0
  83. package/dist/skills/export.js.map +1 -0
  84. package/dist/skills/index.d.ts +4 -0
  85. package/dist/skills/index.d.ts.map +1 -0
  86. package/dist/skills/index.js +11 -0
  87. package/dist/skills/index.js.map +1 -0
  88. package/dist/skills/loader.d.ts +37 -0
  89. package/dist/skills/loader.d.ts.map +1 -0
  90. package/dist/skills/loader.js +183 -0
  91. package/dist/skills/loader.js.map +1 -0
  92. package/dist/skills/registry.d.ts +37 -0
  93. package/dist/skills/registry.d.ts.map +1 -0
  94. package/dist/skills/registry.js +136 -0
  95. package/dist/skills/registry.js.map +1 -0
  96. package/dist/sync/index.d.ts +58 -0
  97. package/dist/sync/index.d.ts.map +1 -0
  98. package/dist/sync/index.js +331 -0
  99. package/dist/sync/index.js.map +1 -0
  100. package/dist/sync/reffo-client.d.ts +144 -0
  101. package/dist/sync/reffo-client.d.ts.map +1 -0
  102. package/dist/sync/reffo-client.js +279 -0
  103. package/dist/sync/reffo-client.js.map +1 -0
  104. package/dist/taxonomy.d.ts +4 -0
  105. package/dist/taxonomy.d.ts.map +1 -0
  106. package/dist/taxonomy.js +141 -0
  107. package/dist/taxonomy.js.map +1 -0
  108. package/dist/types/index.d.ts +198 -0
  109. package/dist/types/index.d.ts.map +1 -0
  110. package/dist/types/index.js +25 -0
  111. package/dist/types/index.js.map +1 -0
  112. package/dist/ui.d.ts +2 -0
  113. package/dist/ui.d.ts.map +1 -0
  114. package/dist/ui.js +6786 -0
  115. package/dist/ui.js.map +1 -0
  116. package/dist/version.d.ts +2 -0
  117. package/dist/version.d.ts.map +1 -0
  118. package/dist/version.js +14 -0
  119. package/dist/version.js.map +1 -0
  120. package/footer-brand.png +0 -0
  121. package/header-brand.png +0 -0
  122. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # reffo-beacon
2
+
3
+ Self-hosted beacon server for the protocol.
4
+
5
+ Beacons store inventory locally in SQLite and announce to a Hyperswarm DHT so other beacons can discover and query your listings without any central server. Data is structured using [Schema.org](https://schema.org) types for universal compatibility with search engines, LLMs, and third-party platforms.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npm install
11
+ npm run build
12
+ npm start
13
+ ```
14
+
15
+ The beacon runs on `http://localhost:3000` by default. Open it in a browser to use the web UI.
16
+
17
+ For development with auto-reload:
18
+ ```bash
19
+ npm run dev
20
+ ```
21
+
22
+ ## Web UI
23
+
24
+ Navigate to `http://localhost:3000` in your browser. The built-in UI lets you:
25
+
26
+ - **List a Ref** — fill in name, category/subcategory, category-specific attributes, price and click submit
27
+ - **My Refs** — see all your inventory with prices, condition badges, and attribute summaries
28
+ - **Search Network** — search across connected peer beacons by keyword, category, or max price
29
+
30
+ ## Data Model
31
+
32
+ Every listing is a **Ref** — the universal base unit in Reffo. Refs support category-specific attributes (car fields for cars, house fields for houses, etc.) with Schema.org compatibility.
33
+
34
+ Key concepts:
35
+ - **Category Schemas**: Each category defines typed fields, condition options, and Schema.org mappings
36
+ - **Trait System**: Refs compose behavior through traits (Priceable, Conditional, Valueable, Serialized, LocationBound, etc.)
37
+ - **Schema.org JSON-LD**: Attributes transform into standard Schema.org structured data when synced
38
+
39
+ See the [Data Model documentation](./docs/REF_DATA_MODEL.md) for full details.
40
+
41
+ ## API
42
+
43
+ Full API documentation: [docs/API_REFERENCE.md](./docs/API_REFERENCE.md)
44
+
45
+ ### Refs
46
+ ```
47
+ GET /refs # List all (or ?category=X&subcategory=Y or ?search=X)
48
+ GET /refs/:id # Get one
49
+ POST /refs # Create { name, description?, category?, subcategory?, condition?, attributes?, ... }
50
+ PATCH /refs/:id # Update fields
51
+ DELETE /refs/:id # Remove
52
+ ```
53
+
54
+ ### Media
55
+ ```
56
+ GET /refs/:refId/media # List media for a ref
57
+ POST /refs/:refId/media # Upload photos/video (multipart)
58
+ DELETE /refs/:refId/media/:id # Delete one
59
+ ```
60
+
61
+ ### Offers
62
+ ```
63
+ GET /offers # List all (or ?refId=X)
64
+ GET /offers/:id # Get one
65
+ POST /offers # Create { refId, price, priceCurrency?, status?, location? }
66
+ PATCH /offers/:id # Update fields
67
+ DELETE /offers/:id # Remove
68
+ ```
69
+
70
+ ### Negotiations
71
+ ```
72
+ GET /negotiations # List all (or ?role=buyer|seller)
73
+ POST /negotiations # Send proposal
74
+ PATCH /negotiations/:id # Respond (accept/reject/counter)
75
+ ```
76
+
77
+ ### Peer Search
78
+ ```
79
+ GET /search?q=guitar # Search across all connected beacons
80
+ GET /search?c=Electronics&sc=Gaming&maxPrice=100
81
+ GET /search?lat=28.5&lng=-81.3&radiusMiles=50
82
+ ```
83
+
84
+ | Param | Description |
85
+ |---|---|
86
+ | `q` | Keyword search |
87
+ | `c` | Category filter |
88
+ | `sc` | Subcategory filter |
89
+ | `maxPrice` | Maximum price |
90
+ | `lat` | Search center latitude |
91
+ | `lng` | Search center longitude |
92
+ | `radiusMiles` | Search radius in miles |
93
+
94
+ ## Environment Variables
95
+
96
+ | Variable | Default | Description |
97
+ |---|---|---|
98
+ | `PORT` | `3000` | HTTP server port |
99
+ | `BEACON_ID` | random | 64-char hex beacon identity |
100
+ | `REFFO_DB_PATH` | `./reffo-beacon.db` | SQLite database path |
101
+
102
+ ## Docker
103
+
104
+ ```bash
105
+ docker build -t reffo-beacon .
106
+ docker run -p 3000:3000 -v reffo-data:/app reffo-beacon
107
+ ```
108
+
109
+ ## How It Works
110
+
111
+ 1. You list refs and create offers via the web UI or REST API
112
+ 2. Your beacon joins the Reffo DHT topic via Hyperswarm
113
+ 3. Other beacons connect as peers and exchange announcements
114
+ 4. When someone searches (`GET /search`), your beacon queries all connected peers
115
+ 5. Peers respond with matching refs and offers from their local databases
116
+ 6. When synced to reffo.ai, attributes are transformed to Schema.org JSON-LD
117
+
118
+ No central server required. Each beacon is a fully independent node.
119
+
120
+ ## Contributing
121
+
122
+ Reffo's category system is designed to be community-extensible. To add a new category schema:
123
+
124
+ 1. Define the Schema.org type mapping and form fields in `src/ref-schemas.ts`
125
+ 2. Register it in the `CATEGORY_SCHEMAS` map
126
+ 3. See [docs/ADDING_CATEGORIES.md](./docs/ADDING_CATEGORIES.md) for the complete guide
127
+
128
+ ## Documentation
129
+
130
+ - [Data Model](./docs/REF_DATA_MODEL.md) — Ref base class, traits, and privacy tiers
131
+ - [Schema.org Guide](./docs/SCHEMA_GUIDE.md) — How Refs map to Schema.org JSON-LD
132
+ - [Adding Categories](./docs/ADDING_CATEGORIES.md) — Step-by-step guide for new category schemas
133
+ - [API Reference](./docs/API_REFERENCE.md) — Complete REST API documentation
134
+
135
+ ## Testing
136
+
137
+ ```bash
138
+ npm test
139
+ ```
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Multi-provider AI product lookup abstraction.
3
+ * Supports: anthropic, openai, google, xai — all returning the same response shape.
4
+ */
5
+ export type AiProvider = 'anthropic' | 'openai' | 'google' | 'xai';
6
+ export interface ProductLookupRequest {
7
+ name: string;
8
+ category?: string;
9
+ subcategory?: string;
10
+ attributeKeys: string[];
11
+ }
12
+ export interface ProductLookupResponse {
13
+ description: string | null;
14
+ sku: string | null;
15
+ product_url: string | null;
16
+ image_url: string | null;
17
+ attributes: Record<string, string>;
18
+ price_estimate: {
19
+ low: number;
20
+ high: number;
21
+ typical: number;
22
+ confidence: 'low' | 'medium' | 'high';
23
+ };
24
+ }
25
+ export declare function callProductLookup(provider: AiProvider, apiKey: string, req: ProductLookupRequest): Promise<ProductLookupResponse>;
26
+ //# sourceMappingURL=product-lookup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-lookup.d.ts","sourceRoot":"","sources":["../../src/ai/product-lookup.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEnE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,cAAc,EAAE;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;KACvC,CAAC;CACH;AAyJD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,UAAU,EACpB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,oBAAoB,GACxB,OAAO,CAAC,qBAAqB,CAAC,CAehC"}
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ /**
3
+ * Multi-provider AI product lookup abstraction.
4
+ * Supports: anthropic, openai, google, xai — all returning the same response shape.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.callProductLookup = callProductLookup;
8
+ function buildPrompt(req) {
9
+ const parts = [
10
+ `Product: ${req.name}`,
11
+ req.category && `Category: ${req.category}`,
12
+ req.subcategory && `Subcategory: ${req.subcategory}`,
13
+ ].filter(Boolean).join('\n');
14
+ const attrList = req.attributeKeys.length > 0
15
+ ? `Only fill these attribute keys: ${req.attributeKeys.join(', ')}`
16
+ : 'No category-specific attributes for this category.';
17
+ return `You are a product data expert. Given a product name and category, return structured product data as JSON.
18
+
19
+ ${parts}
20
+
21
+ ${attrList}
22
+
23
+ Return this exact JSON format:
24
+ {
25
+ "description": "<Start with 1 concise summary sentence. Then 1-2 detailed paragraphs covering key features, specs, and typical use cases. Factual only, not marketing copy. Null if unknown.>",
26
+ "sku": "<manufacturer SKU/part number, or null if unknown>",
27
+ "product_url": "<official manufacturer product page URL, or null if unknown>",
28
+ "image_url": "<official product image URL from manufacturer domain, or null if unknown>",
29
+ "attributes": {<only keys from the provided list, values as strings, omit unknown>},
30
+ "price_estimate": {"low": <number>, "high": <number>, "typical": <number>, "confidence": "<low|medium|high>"}
31
+ }
32
+
33
+ Rules:
34
+ - Return null for any field you are uncertain about. Do NOT hallucinate.
35
+ - Only provide URLs from the manufacturer's official domain.
36
+ - Only fill attribute keys from the provided list.
37
+ - Description should start with one concise summary sentence, followed by 1-2 detailed paragraphs. Factual only, not marketing copy.
38
+ - All prices in USD as numbers (no $ sign).
39
+ - confidence: "high" if well-known product, "medium" if somewhat familiar, "low" if uncertain.
40
+ - Respond with ONLY valid JSON, no other text.`;
41
+ }
42
+ async function callAnthropic(apiKey, prompt) {
43
+ const res = await fetch('https://api.anthropic.com/v1/messages', {
44
+ method: 'POST',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ 'x-api-key': apiKey,
48
+ 'anthropic-version': '2023-06-01',
49
+ },
50
+ body: JSON.stringify({
51
+ model: 'claude-3-haiku-20240307',
52
+ max_tokens: 1024,
53
+ temperature: 0,
54
+ messages: [{ role: 'user', content: prompt }],
55
+ }),
56
+ });
57
+ if (!res.ok) {
58
+ const errText = await res.text();
59
+ throw new Error(`Anthropic API error ${res.status}: ${errText}`);
60
+ }
61
+ const data = await res.json();
62
+ const text = data.content?.[0]?.type === 'text' ? data.content[0].text : '';
63
+ return parseResponse(text, 'claude-3-haiku-20240307');
64
+ }
65
+ async function callOpenAICompatible(apiKey, prompt, endpoint, model) {
66
+ const res = await fetch(endpoint, {
67
+ method: 'POST',
68
+ headers: {
69
+ 'Content-Type': 'application/json',
70
+ 'Authorization': `Bearer ${apiKey}`,
71
+ },
72
+ body: JSON.stringify({
73
+ model,
74
+ temperature: 0,
75
+ max_tokens: 1024,
76
+ response_format: { type: 'json_object' },
77
+ messages: [{ role: 'user', content: prompt }],
78
+ }),
79
+ });
80
+ if (!res.ok) {
81
+ const errText = await res.text();
82
+ throw new Error(`${model} API error ${res.status}: ${errText}`);
83
+ }
84
+ const data = await res.json();
85
+ const text = data.choices?.[0]?.message?.content ?? '';
86
+ return parseResponse(text, model);
87
+ }
88
+ async function callGoogle(apiKey, prompt) {
89
+ const model = 'gemini-2.0-flash-lite';
90
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
91
+ const res = await fetch(url, {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/json' },
94
+ body: JSON.stringify({
95
+ contents: [{ parts: [{ text: prompt }] }],
96
+ generationConfig: {
97
+ temperature: 0,
98
+ maxOutputTokens: 1024,
99
+ responseMimeType: 'application/json',
100
+ },
101
+ }),
102
+ });
103
+ if (!res.ok) {
104
+ const errText = await res.text();
105
+ throw new Error(`Google AI error ${res.status}: ${errText}`);
106
+ }
107
+ const data = await res.json();
108
+ const text = data.candidates?.[0]?.content?.parts?.[0]?.text ?? '';
109
+ return parseResponse(text, model);
110
+ }
111
+ function parseResponse(text, model) {
112
+ const cleaned = text.replace(/```json\s*/g, '').replace(/```\s*/g, '').trim();
113
+ const parsed = JSON.parse(cleaned);
114
+ const DEVICE_UNIQUE = new Set(['vin', 'hin', 'imei', 'parcel_id']);
115
+ const attrs = {};
116
+ if (parsed.attributes && typeof parsed.attributes === 'object') {
117
+ for (const [k, v] of Object.entries(parsed.attributes)) {
118
+ if (v != null && v !== '' && !DEVICE_UNIQUE.has(k))
119
+ attrs[k] = String(v);
120
+ }
121
+ }
122
+ const pe = parsed.price_estimate || {};
123
+ const confidence = ['low', 'medium', 'high'].includes(pe.confidence) ? pe.confidence : 'low';
124
+ return {
125
+ description: typeof parsed.description === 'string' ? parsed.description : null,
126
+ sku: typeof parsed.sku === 'string' ? parsed.sku : null,
127
+ product_url: typeof parsed.product_url === 'string' ? parsed.product_url : null,
128
+ image_url: typeof parsed.image_url === 'string' ? parsed.image_url : null,
129
+ attributes: attrs,
130
+ price_estimate: {
131
+ low: Math.max(0, Number(pe.low) || 0),
132
+ high: Math.max(0, Number(pe.high) || 0),
133
+ typical: Math.max(0, Number(pe.typical) || 0),
134
+ confidence,
135
+ },
136
+ };
137
+ }
138
+ async function callProductLookup(provider, apiKey, req) {
139
+ const prompt = buildPrompt(req);
140
+ switch (provider) {
141
+ case 'anthropic':
142
+ return callAnthropic(apiKey, prompt);
143
+ case 'openai':
144
+ return callOpenAICompatible(apiKey, prompt, 'https://api.openai.com/v1/chat/completions', 'gpt-4o-mini');
145
+ case 'google':
146
+ return callGoogle(apiKey, prompt);
147
+ case 'xai':
148
+ return callOpenAICompatible(apiKey, prompt, 'https://api.x.ai/v1/chat/completions', 'grok-3-mini-fast');
149
+ default:
150
+ throw new Error(`Unsupported AI provider: ${provider}`);
151
+ }
152
+ }
153
+ //# sourceMappingURL=product-lookup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-lookup.js","sourceRoot":"","sources":["../../src/ai/product-lookup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
3
+ //# sourceMappingURL=collections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../../src/api/collections.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAyExB,eAAe,MAAM,CAAC"}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const db_1 = require("../db");
5
+ const router = (0, express_1.Router)();
6
+ // GET /collections
7
+ router.get('/', (_req, res) => {
8
+ const collections = new db_1.CollectionQueries();
9
+ const list = collections.list();
10
+ const result = list.map(c => ({
11
+ ...c,
12
+ refCount: collections.countRefs(c.id),
13
+ }));
14
+ res.json(result);
15
+ });
16
+ // POST /collections
17
+ router.post('/', (req, res) => {
18
+ const collections = new db_1.CollectionQueries();
19
+ const { name, description } = req.body;
20
+ if (!name || typeof name !== 'string') {
21
+ return res.status(400).json({ error: 'name is required' });
22
+ }
23
+ const collection = collections.create({ name, description });
24
+ res.status(201).json({
25
+ ...collection,
26
+ refCount: 0,
27
+ });
28
+ });
29
+ // GET /collections/:id
30
+ router.get('/:id', (req, res) => {
31
+ const collections = new db_1.CollectionQueries();
32
+ const collection = collections.get(String(req.params.id));
33
+ if (!collection)
34
+ return res.status(404).json({ error: 'Collection not found' });
35
+ res.json({
36
+ ...collection,
37
+ refCount: collections.countRefs(collection.id),
38
+ });
39
+ });
40
+ // PATCH /collections/:id
41
+ router.patch('/:id', (req, res) => {
42
+ const collections = new db_1.CollectionQueries();
43
+ const { name, description } = req.body;
44
+ const updated = collections.update(String(req.params.id), { name, description });
45
+ if (!updated)
46
+ return res.status(404).json({ error: 'Collection not found' });
47
+ res.json({
48
+ ...updated,
49
+ refCount: collections.countRefs(updated.id),
50
+ });
51
+ });
52
+ // DELETE /collections/:id
53
+ router.delete('/:id', (req, res) => {
54
+ const collections = new db_1.CollectionQueries();
55
+ const deleted = collections.delete(String(req.params.id));
56
+ if (!deleted)
57
+ return res.status(404).json({ error: 'Collection not found' });
58
+ res.status(204).send();
59
+ });
60
+ // GET /collections/:id/refs
61
+ router.get('/:id/refs', (req, res) => {
62
+ const collections = new db_1.CollectionQueries();
63
+ const collection = collections.get(String(req.params.id));
64
+ if (!collection)
65
+ return res.status(404).json({ error: 'Collection not found' });
66
+ res.json(collections.listRefs(collection.id));
67
+ });
68
+ exports.default = router;
69
+ //# sourceMappingURL=collections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections.js","sourceRoot":"","sources":["../../src/api/collections.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
3
+ //# sourceMappingURL=favorites.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"favorites.d.ts","sourceRoot":"","sources":["../../src/api/favorites.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA8CxB,eAAe,MAAM,CAAC"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const db_1 = require("../db");
5
+ const router = (0, express_1.Router)();
6
+ // GET /favorites — list all favorites
7
+ router.get('/', (_req, res) => {
8
+ const favorites = new db_1.FavoriteQueries();
9
+ res.json(favorites.list());
10
+ });
11
+ // GET /favorites/ids — return array of "refId:beaconId" strings for quick card overlay
12
+ router.get('/ids', (_req, res) => {
13
+ const favorites = new db_1.FavoriteQueries();
14
+ res.json(favorites.listKeys());
15
+ });
16
+ // POST /favorites/toggle — toggle favorite on/off
17
+ router.post('/toggle', (req, res) => {
18
+ const favorites = new db_1.FavoriteQueries();
19
+ const { refId, refName, beaconId, offerPrice, offerCurrency, listingStatus, category, subcategory, locationCity, locationState, locationZip, imageUrl } = req.body;
20
+ if (!refId || typeof refId !== 'string') {
21
+ return res.status(400).json({ error: 'refId is required' });
22
+ }
23
+ if (!beaconId || typeof beaconId !== 'string') {
24
+ return res.status(400).json({ error: 'beaconId is required' });
25
+ }
26
+ const result = favorites.toggle({ refId, refName, beaconId, offerPrice, offerCurrency, listingStatus, category, subcategory, locationCity, locationState, locationZip, imageUrl });
27
+ res.json(result);
28
+ });
29
+ // DELETE /favorites — remove by refId + beaconId
30
+ router.delete('/', (req, res) => {
31
+ const favorites = new db_1.FavoriteQueries();
32
+ const { refId, beaconId } = req.body;
33
+ if (!refId || typeof refId !== 'string') {
34
+ return res.status(400).json({ error: 'refId is required' });
35
+ }
36
+ if (!beaconId || typeof beaconId !== 'string') {
37
+ return res.status(400).json({ error: 'beaconId is required' });
38
+ }
39
+ const removed = favorites.remove(refId, beaconId);
40
+ res.json({ removed });
41
+ });
42
+ exports.default = router;
43
+ //# sourceMappingURL=favorites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"favorites.js","sourceRoot":"","sources":["../../src/api/favorites.ts"],"names":[],"mappings":";;AAAA,qCAAoD;AACpD,8BAAwC;AAExC,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AAExB,sCAAsC;AACtC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;IAC/C,MAAM,SAAS,GAAG,IAAI,oBAAe,EAAE,CAAC;IACxC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEH,uFAAuF;AACvF,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;IAClD,MAAM,SAAS,GAAG,IAAI,oBAAe,EAAE,CAAC;IACxC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEH,kDAAkD;AAClD,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;IACrD,MAAM,SAAS,GAAG,IAAI,oBAAe,EAAE,CAAC;IACxC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;IAEnK,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC9C,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;IACnL,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACnB,CAAC,CAAC,CAAC;AAEH,iDAAiD;AACjD,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;IACjD,MAAM,SAAS,GAAG,IAAI,oBAAe,EAAE,CAAC;IACxC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;IAErC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC9C,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAClD,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AACxB,CAAC,CAAC,CAAC;AAEH,kBAAe,MAAM,CAAC"}
@@ -0,0 +1,12 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export declare function setBeaconId(id: string): void;
3
+ export declare function setDhtStatus(status: {
4
+ connected: boolean;
5
+ peers: number;
6
+ }): void;
7
+ export declare function setUpdateInfo(info: {
8
+ available: boolean;
9
+ version: string | null;
10
+ }): void;
11
+ export default router;
12
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/api/health.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAOxB,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAEhF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAExF;AAqCD,eAAe,MAAM,CAAC"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setBeaconId = setBeaconId;
4
+ exports.setDhtStatus = setDhtStatus;
5
+ exports.setUpdateInfo = setUpdateInfo;
6
+ const express_1 = require("express");
7
+ const db_1 = require("../db");
8
+ const version_1 = require("../version");
9
+ const router = (0, express_1.Router)();
10
+ const startTime = Date.now();
11
+ let beaconId = '';
12
+ let dhtStatus = { connected: false, peers: 0 };
13
+ let updateInfo = { available: false, version: null };
14
+ function setBeaconId(id) {
15
+ beaconId = id;
16
+ }
17
+ function setDhtStatus(status) {
18
+ dhtStatus = status;
19
+ }
20
+ function setUpdateInfo(info) {
21
+ updateInfo = info;
22
+ }
23
+ router.get('/dashboard', (_req, res) => {
24
+ const refs = new db_1.RefQueries();
25
+ const offers = new db_1.OfferQueries();
26
+ const negotiations = new db_1.NegotiationQueries();
27
+ const favorites = new db_1.FavoriteQueries();
28
+ res.json({
29
+ totalListed: refs.count(),
30
+ activeOffers: offers.countActive(),
31
+ pendingNegotiations: negotiations.countPending(),
32
+ favoritesCount: favorites.count(),
33
+ archivedCount: refs.listArchived().length,
34
+ recentItems: refs.list().slice(0, 6),
35
+ recentOffers: offers.list().slice(0, 3),
36
+ });
37
+ });
38
+ router.get('/', (_req, res) => {
39
+ const refs = new db_1.RefQueries();
40
+ const offers = new db_1.OfferQueries();
41
+ const info = {
42
+ id: beaconId,
43
+ version: (0, version_1.getVersion)(),
44
+ refCount: refs.count(),
45
+ offerCount: offers.countActive(),
46
+ uptime: Math.floor((Date.now() - startTime) / 1000),
47
+ dht: dhtStatus,
48
+ updateAvailable: updateInfo.available,
49
+ latestVersion: updateInfo.version,
50
+ };
51
+ res.json(info);
52
+ });
53
+ exports.default = router;
54
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/api/health.ts"],"names":[],"mappings":";;AAYA,kCAEC;AAED,oCAEC;AAED,sCAEC;AAtBD,qCAAoD;AACpD,8BAAsF;AAEtF,wCAAwC;AAExC,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AACxB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AAE7B,IAAI,QAAQ,GAAG,EAAE,CAAC;AAClB,IAAI,SAAS,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAC/C,IAAI,UAAU,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,IAAqB,EAAE,CAAC;AAEtE,SAAgB,WAAW,CAAC,EAAU;IACpC,QAAQ,GAAG,EAAE,CAAC;AAChB,CAAC;AAED,SAAgB,YAAY,CAAC,MAA6C;IACxE,SAAS,GAAG,MAAM,CAAC;AACrB,CAAC;AAED,SAAgB,aAAa,CAAC,IAAoD;IAChF,UAAU,GAAG,IAAI,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;IACxD,MAAM,IAAI,GAAG,IAAI,eAAU,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,iBAAY,EAAE,CAAC;IAClC,MAAM,YAAY,GAAG,IAAI,uBAAkB,EAAE,CAAC;IAC9C,MAAM,SAAS,GAAG,IAAI,oBAAe,EAAE,CAAC;IAExC,GAAG,CAAC,IAAI,CAAC;QACP,WAAW,EAAE,IAAI,CAAC,KAAK,EAAE;QACzB,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE;QAClC,mBAAmB,EAAE,YAAY,CAAC,YAAY,EAAE;QAChD,cAAc,EAAE,SAAS,CAAC,KAAK,EAAE;QACjC,aAAa,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM;QACzC,WAAW,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QACpC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;KACxC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;IAC/C,MAAM,IAAI,GAAG,IAAI,eAAU,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,iBAAY,EAAE,CAAC;IAElC,MAAM,IAAI,GAA4E;QACpF,EAAE,EAAE,QAAQ;QACZ,OAAO,EAAE,IAAA,oBAAU,GAAE;QACrB,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE;QACtB,UAAU,EAAE,MAAM,CAAC,WAAW,EAAE;QAChC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;QACnD,GAAG,EAAE,SAAS;QACd,eAAe,EAAE,UAAU,CAAC,SAAS;QACrC,aAAa,EAAE,UAAU,CAAC,OAAO;KAClC,CAAC;IAEF,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC;AAEH,kBAAe,MAAM,CAAC"}
@@ -0,0 +1,3 @@
1
+ import express from 'express';
2
+ export declare function createApp(): express.Express;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAc9B,wBAAgB,SAAS,IAAI,OAAO,CAAC,OAAO,CA4C3C"}
@@ -0,0 +1,56 @@
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.createApp = createApp;
7
+ const express_1 = __importDefault(require("express"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const health_1 = __importDefault(require("./health"));
10
+ const refs_1 = __importDefault(require("./refs"));
11
+ const offers_1 = __importDefault(require("./offers"));
12
+ const media_1 = __importDefault(require("./media"));
13
+ const negotiations_1 = __importDefault(require("./negotiations"));
14
+ const settings_1 = __importDefault(require("./settings"));
15
+ const favorites_1 = __importDefault(require("./favorites"));
16
+ const collections_1 = __importDefault(require("./collections"));
17
+ const scans_1 = __importDefault(require("./scans"));
18
+ const ui_1 = require("../ui");
19
+ const taxonomy_1 = require("../taxonomy");
20
+ function createApp() {
21
+ const app = (0, express_1.default)();
22
+ app.use(express_1.default.json());
23
+ // Allow cross-origin requests (so browsers can fetch media from peer beacons)
24
+ app.use((_req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); next(); });
25
+ // Serve uploaded media files
26
+ app.use('/uploads', express_1.default.static(path_1.default.join(process.cwd(), 'uploads')));
27
+ // Serve brand assets (favicon.ico, beacon.png, footer_bolt.png)
28
+ app.get('/favicon.ico', (_req, res) => { res.type('image/x-icon').sendFile(path_1.default.join(__dirname, '../../favicon.ico')); });
29
+ app.get('/beacon.png', (_req, res) => { res.sendFile(path_1.default.join(__dirname, '../../beacon.png')); });
30
+ app.get('/footer_bolt.png', (_req, res) => { res.sendFile(path_1.default.join(__dirname, '../../footer_bolt.png')); });
31
+ app.get('/header-brand.png', (_req, res) => { res.sendFile(path_1.default.join(__dirname, '../../header-brand.png')); });
32
+ app.get('/footer-brand.png', (_req, res) => { res.sendFile(path_1.default.join(__dirname, '../../footer-brand.png')); });
33
+ app.get('/', (_req, res) => {
34
+ res.type('html').send((0, ui_1.renderUI)());
35
+ });
36
+ app.get('/taxonomy', (_req, res) => {
37
+ res.json(taxonomy_1.TAXONOMY);
38
+ });
39
+ app.use('/health', health_1.default);
40
+ app.use('/refs', refs_1.default);
41
+ app.use('/refs/:refId/media', media_1.default);
42
+ app.use('/offers', offers_1.default);
43
+ app.use('/negotiations', negotiations_1.default);
44
+ app.use('/settings', settings_1.default);
45
+ app.use('/favorites', favorites_1.default);
46
+ app.use('/collections', collections_1.default);
47
+ app.use('/scans', scans_1.default);
48
+ // Global error handler — catches multer errors, etc., and returns JSON instead of HTML
49
+ app.use((err, _req, res, _next) => {
50
+ console.error('[API] Error:', err.message);
51
+ const status = err.status || err.statusCode || 500;
52
+ res.status(status).json({ error: err.message });
53
+ });
54
+ return app;
55
+ }
56
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
3
+ //# sourceMappingURL=items.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"items.d.ts","sourceRoot":"","sources":["../../src/api/items.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA+LxB,eAAe,MAAM,CAAC"}