create-hedgeboard 1.2.0 → 1.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.
Files changed (2) hide show
  1. package/index.js +114 -36
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -51,7 +51,7 @@ function parseArgs() {
51
51
  npx create-hedgeboard --key YOUR_API_KEY --dir ./my-workspace
52
52
 
53
53
  Options:
54
- --key, -k Your HedgeBoard API key (required)
54
+ --key, -k Your HB SEC Filing Data API key (optional)
55
55
  --dir, -d Output directory (default: ./hedgeboard)
56
56
  --help, -h Show this help
57
57
  `);
@@ -60,12 +60,8 @@ function parseArgs() {
60
60
  }
61
61
 
62
62
  if (!opts.key) {
63
- console.error(
64
- "Error: --key is required.\n" +
65
- " Get your API key at https://www.hedgeboardhq.com/dashboard\n\n" +
66
- " Usage: npx create-hedgeboard --key YOUR_API_KEY"
67
- );
68
- process.exit(1);
63
+ // Key is optional — platform works without it
64
+ opts.key = "";
69
65
  }
70
66
 
71
67
  return opts;
@@ -97,18 +93,85 @@ function fetchBuffer(url, headers = {}) {
97
93
  }
98
94
 
99
95
  // ---------------------------------------------------------------------------
100
- // ZIP extraction (pure JS, no dependencies)
96
+ // Recursive directory copy (pure JS, no shell — replaces cp -r)
97
+ // ---------------------------------------------------------------------------
98
+
99
+ function copyDirRecursive(src, dest) {
100
+ fs.mkdirSync(dest, { recursive: true });
101
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
102
+ const srcPath = path.join(src, entry.name);
103
+ const destPath = path.join(dest, entry.name);
104
+ if (entry.isDirectory()) {
105
+ copyDirRecursive(srcPath, destPath);
106
+ } else {
107
+ fs.copyFileSync(srcPath, destPath);
108
+ }
109
+ }
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // ZIP extraction (pure JS — uses only Node built-in zlib, no dependencies)
101
114
  // ---------------------------------------------------------------------------
102
115
 
103
116
  function extractZip(zipBuffer, destDir) {
104
- // Simple ZIP parser — handles DEFLATE and STORE methods
105
- // For robustness in production, consider using a proper zip library
106
- const { execSync } = require("child_process");
107
- const tmpZip = path.join(require("os").tmpdir(), `hedgeboard-${Date.now()}.zip`);
108
- fs.writeFileSync(tmpZip, zipBuffer);
117
+ const zlib = require("zlib");
109
118
  fs.mkdirSync(destDir, { recursive: true });
110
- execSync(`unzip -o -q "${tmpZip}" -d "${destDir}"`);
111
- fs.unlinkSync(tmpZip);
119
+
120
+ // Parse ZIP central directory (from end-of-central-directory record)
121
+ const buf = Buffer.isBuffer(zipBuffer) ? zipBuffer : Buffer.from(zipBuffer);
122
+ let offset = 0;
123
+
124
+ while (offset < buf.length - 4) {
125
+ // Local file header signature = 0x04034b50
126
+ const sig = buf.readUInt32LE(offset);
127
+ if (sig !== 0x04034b50) break;
128
+
129
+ const method = buf.readUInt16LE(offset + 8);
130
+ const compSize = buf.readUInt32LE(offset + 18);
131
+ const uncompSize = buf.readUInt32LE(offset + 22);
132
+ const nameLen = buf.readUInt16LE(offset + 26);
133
+ const extraLen = buf.readUInt16LE(offset + 28);
134
+ const nameStart = offset + 30;
135
+ const name = buf.toString("utf8", nameStart, nameStart + nameLen);
136
+ const dataStart = nameStart + nameLen + extraLen;
137
+
138
+ offset = dataStart + compSize;
139
+
140
+ // Skip directories (with path traversal guard — P3-9)
141
+ if (name.endsWith("/")) {
142
+ const dirPath = path.join(destDir, name);
143
+ if (!dirPath.startsWith(path.resolve(destDir) + path.sep) &&
144
+ dirPath !== path.resolve(destDir)) {
145
+ console.warn(` Skipping ${name}: path traversal detected`);
146
+ continue;
147
+ }
148
+ fs.mkdirSync(dirPath, { recursive: true });
149
+ continue;
150
+ }
151
+
152
+ // Path traversal guard — prevent ../../.bashrc attacks (P0-8)
153
+ const filePath = path.join(destDir, name);
154
+ if (!filePath.startsWith(path.resolve(destDir) + path.sep) &&
155
+ filePath !== path.resolve(destDir)) {
156
+ console.warn(` Skipping ${name}: path traversal detected`);
157
+ continue;
158
+ }
159
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
160
+
161
+ const rawData = buf.subarray(dataStart, dataStart + compSize);
162
+
163
+ if (method === 0) {
164
+ // STORE — no compression
165
+ fs.writeFileSync(filePath, rawData);
166
+ } else if (method === 8) {
167
+ // DEFLATE — use raw inflate (no zlib header)
168
+ const inflated = zlib.inflateRawSync(rawData);
169
+ fs.writeFileSync(filePath, inflated);
170
+ } else {
171
+ // Unsupported method — skip
172
+ console.warn(` Skipping ${name}: unsupported compression method ${method}`);
173
+ }
174
+ }
112
175
  }
113
176
 
114
177
  // ---------------------------------------------------------------------------
@@ -143,7 +206,7 @@ ${c.dim} ██████╔╝██║ ██║███████
143
206
  ${c.dim} ██╔══██╗██║ ██║██╔══██║██╔══██╗██║ ██║${c.reset}
144
207
  ${c.dim} ██████╔╝╚██████╔╝██║ ██║██║ ██║██████╔╝${c.reset}
145
208
  ${c.dim} ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ${c.reset}
146
- ${c.dim} AI-Native Financial Intelligence — v1.0.8${c.reset}
209
+ ${c.dim} AI-Native Financial Intelligence — v1.3.0${c.reset}
147
210
  `);
148
211
  }
149
212
 
@@ -156,15 +219,19 @@ async function main() {
156
219
 
157
220
  banner();
158
221
 
159
- // 1. Validate API key against the app
160
- process.stdout.write(` ${c.gray}◆${c.reset} Validating API key ${c.dim}...${c.reset} `);
161
- try {
162
- const resp = await fetchBuffer(`${APP_URL}/api/data-sources?key=${key}`);
163
- const data = JSON.parse(resp.toString());
164
- if (data.error) throw new Error(data.error);
165
- console.log(`${c.green}✓${c.reset}`);
166
- } catch (err) {
167
- console.log(`${c.green}✓${c.reset} ${c.dim}(offline)${c.reset}`);
222
+ // 1. Validate API key against the app (if provided)
223
+ if (key) {
224
+ process.stdout.write(` ${c.gray}◆${c.reset} Validating API key ${c.dim}...${c.reset} `);
225
+ try {
226
+ const resp = await fetchBuffer(`${APP_URL}/api/data-sources`, {
227
+ "x-api-key": key,
228
+ });
229
+ const data = JSON.parse(resp.toString());
230
+ if (data.error) throw new Error(data.error);
231
+ console.log(`${c.green}✓${c.reset}`);
232
+ } catch (err) {
233
+ console.log(`${c.green}✓${c.reset} ${c.dim}(offline)${c.reset}`);
234
+ }
168
235
  }
169
236
 
170
237
  // 2. Download toolkit from S3
@@ -191,8 +258,8 @@ async function main() {
191
258
  if (fs.existsSync(extractedDir)) {
192
259
  fs.mkdirSync(path.dirname(absDir), { recursive: true });
193
260
  if (fs.existsSync(absDir)) {
194
- const { execSync } = require("child_process");
195
- execSync(`cp -r "${extractedDir}/"* "${absDir}/"`);
261
+ // Merge into existing dir — pure JS, no shell (P3-8)
262
+ copyDirRecursive(extractedDir, absDir);
196
263
  } else {
197
264
  fs.renameSync(extractedDir, absDir);
198
265
  }
@@ -205,13 +272,20 @@ async function main() {
205
272
  process.exit(1);
206
273
  }
207
274
 
208
- // 4. Write .env with the user's API key
209
- process.stdout.write(` ${c.gray}◆${c.reset} Configuring API key ${c.dim}...${c.reset} `);
275
+ // 4. Write .env with the user's API key (if provided)
276
+ process.stdout.write(` ${c.gray}◆${c.reset} Configuring workspace ${c.dim}...${c.reset} `);
210
277
  const envPath = path.join(absDir, ".env");
211
- fs.writeFileSync(
212
- envPath,
213
- `# HedgeBoard API\nHEDGEBOARD_API_KEY=${key}\nHEDGEBOARD_API_URL=${API_URL}\n`
214
- );
278
+ if (key) {
279
+ fs.writeFileSync(
280
+ envPath,
281
+ `# HedgeBoard Data Sources\nHEDGEBOARD_API_KEY=${key}\nHEDGEBOARD_API_URL=${API_URL}\n`
282
+ );
283
+ } else {
284
+ fs.writeFileSync(
285
+ envPath,
286
+ `# HedgeBoard Data Sources\n# Add your HB SEC Filing Data key from: https://www.hedgeboardhq.com/dashboard\nHEDGEBOARD_API_KEY=\nHEDGEBOARD_API_URL=${API_URL}\n`
287
+ );
288
+ }
215
289
  console.log(`${c.green}✓${c.reset}`);
216
290
 
217
291
  // 5. Summary
@@ -222,15 +296,19 @@ async function main() {
222
296
  ${c.dim} ─────────────────────────────────────${c.reset}
223
297
 
224
298
  ${c.bold}Directory${c.reset} ${c.cyan}${relDir}/${c.reset}
225
- ${c.bold}API Key${c.reset} ${c.dim}${key.slice(0, 8)}${"•".repeat(12)}${key.slice(-4)}${c.reset}
299
+ ${key
300
+ ? `${c.bold}API Key${c.reset} ${c.dim}${key.slice(0, 8)}${"•".repeat(12)}${key.slice(-4)}${c.reset}`
301
+ : `${c.bold}API Key${c.reset} ${c.dim}not set — add from dashboard${c.reset}`
302
+ }
226
303
  ${c.bold}API URL${c.reset} ${c.dim}${API_URL}${c.reset}
227
304
 
228
305
  ${c.bold}What's inside:${c.reset}
229
306
  ${c.gray}├──${c.reset} ${c.white}INSTRUCTIONS.md${c.reset} ${c.dim}Agent instructions & prompt${c.reset}
230
- ${c.gray}├──${c.reset} ${c.white}data/${c.reset} ${c.dim}SEC data fetching modules${c.reset}
307
+ ${c.gray}├──${c.reset} ${c.white}data/${c.reset} ${c.dim}Data source adapters${c.reset}
231
308
  ${c.gray}├──${c.reset} ${c.white}viz/${c.reset} ${c.dim}Dashboard templates & charts${c.reset}
232
309
  ${c.gray}├──${c.reset} ${c.white}brand/${c.reset} ${c.dim}Your brand settings${c.reset}
233
310
  ${c.gray}├──${c.reset} ${c.white}modules/${c.reset} ${c.dim}356 analysis recipes + indexes${c.reset}
311
+ ${c.gray}├──${c.reset} ${c.white}docs/${c.reset} ${c.dim}Data catalog — 35 providers, 15 categories${c.reset}
234
312
  ${c.gray}└──${c.reset} ${c.white}output/${c.reset} ${c.dim}Generated dashboards land here${c.reset}
235
313
 
236
314
  ${c.dim} ─────────────────────────────────────${c.reset}
@@ -243,7 +321,7 @@ ${c.dim} ───────────────────────
243
321
  ${c.cyan}2.${c.reset} Ask something:
244
322
  ${c.magenta}"Give me a company overview of Apple"${c.reset}
245
323
 
246
- ${c.cyan}3.${c.reset} Customize your brand at:
324
+ ${c.cyan}3.${c.reset} Connect data sources at:
247
325
  ${c.underline}https://www.hedgeboardhq.com/dashboard${c.reset}
248
326
  `);
249
327
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-hedgeboard",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Set up a HedgeBoard financial intelligence workspace",
5
5
  "bin": {
6
6
  "create-hedgeboard": "index.js"