@spike-forms/cli 0.2.1 → 0.2.3

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/dist/index.js CHANGED
@@ -1,299 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import * as fs3 from 'fs';
4
- import * as path3 from 'path';
5
- import * as os2 from 'os';
6
- import { platform } from 'os';
7
- import chalk from 'chalk';
8
- import { SpikeClient, SpikeError } from '@spike-forms/sdk';
9
- import { spawn, exec, execSync } from 'child_process';
10
- import * as http from 'http';
11
- import * as crypto from 'crypto';
12
-
13
- var DEFAULT_CONFIG = {
14
- apiKey: "",
15
- baseUrl: "https://api.spike.ac"
16
- };
17
- var ENV_VARS = {
18
- API_KEY: "SPIKE_API_KEY",
19
- TOKEN: "SPIKE_TOKEN",
20
- API_URL: "SPIKE_API_URL"
21
- };
22
- function getConfigPath() {
23
- const homeDir = os2.homedir();
24
- return path3.join(homeDir, ".spike", "config.json");
25
- }
26
- function getConfigDir() {
27
- const homeDir = os2.homedir();
28
- return path3.join(homeDir, ".spike");
29
- }
30
- function loadConfigFromFile() {
31
- const configPath = getConfigPath();
32
- try {
33
- if (!fs3.existsSync(configPath)) {
34
- return {};
35
- }
36
- const fileContent = fs3.readFileSync(configPath, "utf-8");
37
- const parsed = JSON.parse(fileContent);
38
- const config = {};
39
- if (typeof parsed.apiKey === "string") {
40
- config.apiKey = parsed.apiKey;
41
- }
42
- if (typeof parsed.baseUrl === "string") {
43
- config.baseUrl = parsed.baseUrl;
44
- }
45
- return config;
46
- } catch {
47
- return {};
48
- }
49
- }
50
- function loadConfigFromEnv() {
51
- const config = {};
52
- const apiKey = process.env[ENV_VARS.API_KEY] || process.env[ENV_VARS.TOKEN];
53
- if (apiKey) {
54
- config.apiKey = apiKey;
55
- }
56
- const apiUrl = process.env[ENV_VARS.API_URL];
57
- if (apiUrl) {
58
- config.baseUrl = apiUrl;
59
- }
60
- return config;
61
- }
62
- function loadConfig() {
63
- const fileConfig = loadConfigFromFile();
64
- const envConfig = loadConfigFromEnv();
65
- return {
66
- ...DEFAULT_CONFIG,
67
- ...fileConfig,
68
- ...envConfig
69
- };
70
- }
71
- function saveConfig(config) {
72
- const configDir = getConfigDir();
73
- const configPath = getConfigPath();
74
- if (!fs3.existsSync(configDir)) {
75
- fs3.mkdirSync(configDir, { recursive: true });
76
- }
77
- const existingConfig = loadConfigFromFile();
78
- const mergedConfig = {
79
- ...existingConfig,
80
- ...config
81
- };
82
- const cleanConfig = {};
83
- if (mergedConfig.apiKey) {
84
- cleanConfig.apiKey = mergedConfig.apiKey;
85
- }
86
- if (mergedConfig.baseUrl) {
87
- cleanConfig.baseUrl = mergedConfig.baseUrl;
88
- }
89
- fs3.writeFileSync(configPath, JSON.stringify(cleanConfig, null, 2) + "\n", "utf-8");
90
- }
91
- function output(data, format = "table") {
92
- if (format === "json") {
93
- outputJson(data);
94
- } else {
95
- outputTable(data);
96
- }
97
- }
98
- function outputJson(data) {
99
- console.log(JSON.stringify(data, null, 2));
100
- }
101
- function outputTable(data) {
102
- if (data === null || data === void 0) {
103
- console.log("No data");
104
- return;
105
- }
106
- if (Array.isArray(data)) {
107
- outputArrayAsTable(data);
108
- } else if (typeof data === "object") {
109
- outputObjectAsTable(data);
110
- } else {
111
- console.log(String(data));
112
- }
113
- }
114
- function outputArrayAsTable(data) {
115
- if (data.length === 0) {
116
- console.log("No items found");
117
- return;
118
- }
119
- const keys = /* @__PURE__ */ new Set();
120
- for (const item of data) {
121
- if (item && typeof item === "object") {
122
- Object.keys(item).forEach((key) => keys.add(key));
123
- }
124
- }
125
- const columns = Array.from(keys);
126
- if (columns.length === 0) {
127
- data.forEach((item) => console.log(String(item)));
128
- return;
129
- }
130
- const columnWidths = /* @__PURE__ */ new Map();
131
- for (const col of columns) {
132
- let maxWidth = col.length;
133
- for (const item of data) {
134
- if (item && typeof item === "object") {
135
- const value = formatCellValue(item[col]);
136
- maxWidth = Math.max(maxWidth, value.length);
137
- }
138
- }
139
- columnWidths.set(col, Math.min(maxWidth, 50));
140
- }
141
- const headerRow = columns.map((col) => padRight(col.toUpperCase(), columnWidths.get(col))).join(" ");
142
- console.log(chalk.bold(headerRow));
143
- const separator = columns.map((col) => "-".repeat(columnWidths.get(col))).join(" ");
144
- console.log(separator);
145
- for (const item of data) {
146
- if (item && typeof item === "object") {
147
- const row = columns.map((col) => {
148
- const value = formatCellValue(item[col]);
149
- return padRight(truncate(value, columnWidths.get(col)), columnWidths.get(col));
150
- }).join(" ");
151
- console.log(row);
152
- }
153
- }
154
- console.log("");
155
- console.log(chalk.dim(`${data.length} item${data.length === 1 ? "" : "s"}`));
156
- }
157
- function outputObjectAsTable(data) {
158
- const entries = Object.entries(data);
159
- if (entries.length === 0) {
160
- console.log("No data");
161
- return;
162
- }
163
- const maxKeyLength = Math.max(...entries.map(([key]) => key.length));
164
- for (const [key, value] of entries) {
165
- const formattedKey = chalk.bold(padRight(key, maxKeyLength));
166
- const formattedValue = formatCellValue(value);
167
- console.log(`${formattedKey} ${formattedValue}`);
168
- }
169
- }
170
- function formatCellValue(value) {
171
- if (value === null || value === void 0) {
172
- return "-";
173
- }
174
- if (typeof value === "boolean") {
175
- return value ? chalk.green("yes") : chalk.dim("no");
176
- }
177
- if (typeof value === "object") {
178
- if (Array.isArray(value)) {
179
- return `[${value.length} items]`;
180
- }
181
- return JSON.stringify(value);
182
- }
183
- return String(value);
184
- }
185
- function padRight(str, length) {
186
- const visibleLength = stripAnsi(str).length;
187
- if (visibleLength >= length) {
188
- return str;
189
- }
190
- return str + " ".repeat(length - visibleLength);
191
- }
192
- function truncate(str, maxLength) {
193
- const visibleLength = stripAnsi(str).length;
194
- if (visibleLength <= maxLength) {
195
- return str;
196
- }
197
- return str.slice(0, maxLength - 3) + "...";
198
- }
199
- function stripAnsi(str) {
200
- return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
201
- }
202
- function success(message) {
203
- console.log(chalk.green(`\u2713 ${message}`));
204
- }
205
- function error(message) {
206
- console.error(chalk.red(`\u2717 ${message}`));
207
- }
208
- function warn(message) {
209
- console.log(chalk.yellow(`\u26A0 ${message}`));
210
- }
211
- function info(message) {
212
- console.log(chalk.cyan(`\u2139 ${message}`));
213
- }
214
- function maskApiKey(apiKey) {
215
- if (!apiKey) {
216
- return "";
217
- }
218
- if (apiKey.length <= 11) {
219
- const visibleStart = Math.min(4, apiKey.length - 1);
220
- return apiKey.slice(0, visibleStart) + "...";
221
- }
222
- const prefix = apiKey.slice(0, 7);
223
- const suffix = apiKey.slice(-4);
224
- return `${prefix}...${suffix}`;
225
- }
226
-
227
- // src/commands/config.ts
228
- var VALID_KEYS = ["api-key", "base-url"];
229
- var KEY_MAP = {
230
- "api-key": "apiKey",
231
- "base-url": "baseUrl"
232
- };
233
- function isValidKey(key) {
234
- return VALID_KEYS.includes(key);
235
- }
236
- function createConfigCommand() {
237
- const configCommand = new Command("config").description("Manage CLI configuration");
238
- configCommand.command("set").description("Set a configuration value").argument("<key>", `Configuration key (${VALID_KEYS.join(", ")})`).argument("<value>", "Configuration value").action((key, value) => {
239
- handleSet(key, value);
240
- });
241
- configCommand.command("get").description("Get configuration value(s)").argument("[key]", `Configuration key (${VALID_KEYS.join(", ")})`).option("-f, --format <format>", "Output format (json, table)", "table").action((key, options) => {
242
- handleGet(key, options.format);
243
- });
244
- return configCommand;
245
- }
246
- function handleSet(key, value) {
247
- if (!isValidKey(key)) {
248
- error(`Invalid configuration key: ${key}`);
249
- info(`Valid keys are: ${VALID_KEYS.join(", ")}`);
250
- process.exit(1);
251
- }
252
- const internalKey = KEY_MAP[key];
253
- try {
254
- saveConfig({ [internalKey]: value });
255
- success(`Configuration '${key}' has been set`);
256
- } catch (err) {
257
- error(`Failed to save configuration: ${err instanceof Error ? err.message : String(err)}`);
258
- process.exit(1);
259
- }
260
- }
261
- function handleGet(key, format) {
262
- const config = loadConfig();
263
- if (key !== void 0) {
264
- if (!isValidKey(key)) {
265
- error(`Invalid configuration key: ${key}`);
266
- info(`Valid keys are: ${VALID_KEYS.join(", ")}`);
267
- process.exit(1);
268
- }
269
- const internalKey = KEY_MAP[key];
270
- let value = config[internalKey];
271
- if (key === "api-key" && value) {
272
- value = maskApiKey(value);
273
- }
274
- if (!value) {
275
- info(`Configuration '${key}' is not set`);
276
- return;
277
- }
278
- if (format === "json") {
279
- output({ [key]: value }, format);
280
- } else {
281
- console.log(value);
282
- }
283
- } else {
284
- const displayConfig = {};
285
- if (config.apiKey) {
286
- displayConfig["api-key"] = maskApiKey(config.apiKey);
287
- } else {
288
- displayConfig["api-key"] = "(not set)";
289
- }
290
- displayConfig["base-url"] = config.baseUrl || "(not set)";
291
- info(`Configuration file: ${getConfigPath()}`);
292
- console.log("");
293
- output(displayConfig, format);
294
- }
295
- }
296
- var SSE_SCRIPT = `<script>
2
+ import {Command}from'commander';import*as u from'fs';import*as P from'path';import*as G from'os';import {platform}from'os';import C from'chalk';import {SpikeClient,SpikeError}from'@spike-forms/sdk';import {spawn,exec,execSync}from'child_process';import*as ce from'http';import*as ke from'crypto';var Ee={apiKey:"",baseUrl:"https://api.spike.ac"},q={API_KEY:"SPIKE_API_KEY",TOKEN:"SPIKE_TOKEN",API_URL:"SPIKE_API_URL"};function M(){let e=G.homedir();return P.join(e,".spike","config.json")}function Te(){let e=G.homedir();return P.join(e,".spike")}function te(){let e=M();try{if(!u.existsSync(e))return {};let t=u.readFileSync(e,"utf-8"),o=JSON.parse(t),r={};return typeof o.apiKey=="string"&&(r.apiKey=o.apiKey),typeof o.baseUrl=="string"&&(r.baseUrl=o.baseUrl),r}catch{return {}}}function _e(){let e={},t=process.env[q.API_KEY]||process.env[q.TOKEN];t&&(e.apiKey=t);let o=process.env[q.API_URL];return o&&(e.baseUrl=o),e}function b(){let e=te(),t=_e();return {...Ee,...e,...t}}function K(e){let t=Te(),o=M();u.existsSync(t)||u.mkdirSync(t,{recursive:true});let n={...te(),...e},s={};n.apiKey&&(s.apiKey=n.apiKey),n.baseUrl&&(s.baseUrl=n.baseUrl),u.writeFileSync(o,JSON.stringify(s,null,2)+`
3
+ `,"utf-8");}function f(e,t="table"){t==="json"?Ae(e):Ue(e);}function Ae(e){console.log(JSON.stringify(e,null,2));}function Ue(e){if(e==null){console.log("No data");return}Array.isArray(e)?Re(e):typeof e=="object"?De(e):console.log(String(e));}function Re(e){if(e.length===0){console.log("No items found");return}let t=new Set;for(let m of e)m&&typeof m=="object"&&Object.keys(m).forEach(a=>t.add(a));let o=Array.from(t);if(o.length===0){e.forEach(m=>console.log(String(m)));return}let r=new Map;for(let m of o){let a=m.length;for(let d of e)if(d&&typeof d=="object"){let p=z(d[m]);a=Math.max(a,p.length);}r.set(m,Math.min(a,50));}let n=o.map(m=>Y(m.toUpperCase(),r.get(m))).join(" ");console.log(C.bold(n));let s=o.map(m=>"-".repeat(r.get(m))).join(" ");console.log(s);for(let m of e)if(m&&typeof m=="object"){let a=o.map(d=>{let p=z(m[d]);return Y($e(p,r.get(d)),r.get(d))}).join(" ");console.log(a);}console.log(""),console.log(C.dim(`${e.length} item${e.length===1?"":"s"}`));}function De(e){let t=Object.entries(e);if(t.length===0){console.log("No data");return}let o=Math.max(...t.map(([r])=>r.length));for(let[r,n]of t){let s=C.bold(Y(r,o)),m=z(n);console.log(`${s} ${m}`);}}function z(e){return e==null?"-":typeof e=="boolean"?e?C.green("yes"):C.dim("no"):typeof e=="object"?Array.isArray(e)?`[${e.length} items]`:JSON.stringify(e):String(e)}function Y(e,t){let o=oe(e).length;return o>=t?e:e+" ".repeat(t-o)}function $e(e,t){return oe(e).length<=t?e:e.slice(0,t-3)+"..."}function oe(e){return e.replace(/\x1B\[[0-9;]*[a-zA-Z]/g,"")}function l(e){console.log(C.green(`\u2713 ${e}`));}function c(e){console.error(C.red(`\u2717 ${e}`));}function re(e){console.log(C.yellow(`\u26A0 ${e}`));}function i(e){console.log(C.cyan(`\u2139 ${e}`));}function L(e){if(!e)return "";if(e.length<=11){let r=Math.min(4,e.length-1);return e.slice(0,r)+"..."}let t=e.slice(0,7),o=e.slice(-4);return `${t}...${o}`}var E=["api-key","base-url"],ne={"api-key":"apiKey","base-url":"baseUrl"};function ie(e){return E.includes(e)}function se(){let e=new Command("config").description("Manage CLI configuration");return e.command("set").description("Set a configuration value").argument("<key>",`Configuration key (${E.join(", ")})`).argument("<value>","Configuration value").action((t,o)=>{Ke(t,o);}),e.command("get").description("Get configuration value(s)").argument("[key]",`Configuration key (${E.join(", ")})`).option("-f, --format <format>","Output format (json, table)","table").action((t,o)=>{Ne(t,o.format);}),e}function Ke(e,t){ie(e)||(c(`Invalid configuration key: ${e}`),i(`Valid keys are: ${E.join(", ")}`),process.exit(1));let o=ne[e];try{K({[o]:t}),l(`Configuration '${e}' has been set`);}catch(r){c(`Failed to save configuration: ${r instanceof Error?r.message:String(r)}`),process.exit(1);}}function Ne(e,t){let o=b();if(e!==void 0){ie(e)||(c(`Invalid configuration key: ${e}`),i(`Valid keys are: ${E.join(", ")}`),process.exit(1));let r=ne[e],n=o[r];if(e==="api-key"&&n&&(n=L(n)),!n){i(`Configuration '${e}' is not set`);return}t==="json"?f({[e]:n},t):console.log(n);}else {let r={};o.apiKey?r["api-key"]=L(o.apiKey):r["api-key"]="(not set)",r["base-url"]=o.baseUrl||"(not set)",i(`Configuration file: ${M()}`),console.log(""),f(r,t);}}var ae=`<script>
297
4
  (function() {
298
5
  var es = new EventSource('/__live-reload');
299
6
  es.onmessage = function(event) {
@@ -305,178 +12,16 @@ var SSE_SCRIPT = `<script>
305
12
  console.log('[Live Preview] Connection lost, attempting to reconnect...');
306
13
  };
307
14
  })();
308
- </script>`;
309
- function injectLiveReload(html) {
310
- const bodyCloseRegex = /<\/body>/i;
311
- const match = html.match(bodyCloseRegex);
312
- if (match && match.index !== void 0) {
313
- return html.slice(0, match.index) + SSE_SCRIPT + html.slice(match.index);
314
- }
315
- return html + SSE_SCRIPT;
316
- }
317
- function startLivePreviewServer(filePath) {
318
- return new Promise((resolve3, reject) => {
319
- const absolutePath = path3.resolve(filePath);
320
- const sseClients = [];
321
- let debounceTimer = null;
322
- function broadcastToClients(message) {
323
- const data = `data: ${message}
324
-
325
- `;
326
- for (let i = sseClients.length - 1; i >= 0; i--) {
327
- const client = sseClients[i];
328
- if (!client) continue;
329
- try {
330
- if (!client.writableEnded) {
331
- client.write(data);
332
- } else {
333
- sseClients.splice(i, 1);
334
- }
335
- } catch {
336
- sseClients.splice(i, 1);
337
- }
338
- }
339
- }
340
- function onFileChange() {
341
- if (debounceTimer) {
342
- clearTimeout(debounceTimer);
343
- }
344
- debounceTimer = setTimeout(() => {
345
- broadcastToClients("reload");
346
- debounceTimer = null;
347
- }, 100);
348
- }
349
- function handleRequest(req, res) {
350
- const url = req.url || "/";
351
- if (url === "/__live-reload") {
352
- res.writeHead(200, {
353
- "Content-Type": "text/event-stream",
354
- "Cache-Control": "no-cache",
355
- Connection: "keep-alive",
356
- "Access-Control-Allow-Origin": "*"
357
- });
358
- res.write("data: connected\n\n");
359
- sseClients.push(res);
360
- req.on("close", () => {
361
- const index = sseClients.indexOf(res);
362
- if (index !== -1) {
363
- sseClients.splice(index, 1);
364
- }
365
- });
366
- return;
367
- }
368
- if (url === "/" && req.method === "GET") {
369
- try {
370
- const html = fs3.readFileSync(absolutePath, "utf-8");
371
- const injectedHtml = injectLiveReload(html);
372
- res.writeHead(200, {
373
- "Content-Type": "text/html; charset=utf-8",
374
- "Cache-Control": "no-cache"
375
- });
376
- res.end(injectedHtml);
377
- } catch (err) {
378
- const errorMessage = err instanceof Error ? err.message : "Unknown error";
379
- res.writeHead(500, { "Content-Type": "text/plain" });
380
- res.end(`Error reading file: ${errorMessage}`);
381
- }
382
- return;
383
- }
384
- res.writeHead(404, { "Content-Type": "text/plain" });
385
- res.end("Not Found");
386
- }
387
- const server = http.createServer(handleRequest);
388
- server.on("error", (err) => {
389
- reject(err);
390
- });
391
- let watcher = null;
392
- try {
393
- watcher = fs3.watch(absolutePath, (eventType) => {
394
- if (eventType === "change") {
395
- onFileChange();
396
- }
397
- });
398
- watcher.on("error", (err) => {
399
- console.error("[Live Preview] File watcher error:", err.message);
400
- });
401
- } catch (err) {
402
- console.warn("[Live Preview] Could not start file watcher:", err instanceof Error ? err.message : "Unknown error");
403
- }
404
- server.listen(0, "127.0.0.1", () => {
405
- const address = server.address();
406
- if (address && typeof address === "object") {
407
- const port = address.port;
408
- resolve3(port);
409
- } else {
410
- reject(new Error("Failed to get server address"));
411
- }
412
- });
413
- });
414
- }
415
- async function openBrowser(url) {
416
- const currentPlatform = platform();
417
- let command;
418
- switch (currentPlatform) {
419
- case "darwin":
420
- command = `open "${url}"`;
421
- break;
422
- case "linux":
423
- command = `xdg-open "${url}"`;
424
- break;
425
- case "win32":
426
- command = `start "" "${url}"`;
427
- break;
428
- default:
429
- return false;
430
- }
431
- return new Promise((resolve3) => {
432
- exec(command, (error2) => {
433
- if (error2) {
434
- resolve3(false);
435
- } else {
436
- resolve3(true);
437
- }
438
- });
439
- });
440
- }
15
+ </script>`;function He(e){let t=/<\/body>/i,o=e.match(t);return o&&o.index!==void 0?e.slice(0,o.index)+ae+e.slice(o.index):e+ae}function le(e){return new Promise((t,o)=>{let r=P.resolve(e),n=[],s=null;function m(g){let y=`data: ${g}
441
16
 
442
- // src/commands/forms.ts
443
- function createClient() {
444
- const config = loadConfig();
445
- if (!config.apiKey) {
446
- error("API key not configured. Run `spike config set api-key <your-key>` to set it.");
447
- process.exit(1);
448
- }
449
- return new SpikeClient({
450
- apiKey: config.apiKey,
451
- baseUrl: config.baseUrl
452
- });
453
- }
454
- function handleError(err) {
455
- if (err instanceof SpikeError) {
456
- error(err.message);
457
- if (err.code) {
458
- info(`Error code: ${err.code}`);
459
- }
460
- } else if (err instanceof Error) {
461
- error(err.message);
462
- } else {
463
- error("An unexpected error occurred");
464
- }
465
- process.exit(1);
466
- }
467
- var PREVIEW_PID_FILE = path3.join(os2.tmpdir(), "spike-preview.pid");
468
- var PREVIEW_PORT_FILE = path3.join(os2.tmpdir(), "spike-preview.port");
469
- var PREVIEW_FORM_ID_FILE = path3.join(os2.tmpdir(), "spike-preview.formid");
470
- function generateFormHtml(form) {
471
- const config = loadConfig();
472
- const baseUrl = config.baseUrl || "https://api.spike.ac";
473
- const actionUrl = `${baseUrl}/f/${form.slug}`;
474
- return `<!DOCTYPE html>
17
+ `;for(let x=n.length-1;x>=0;x--){let v=n[x];if(v)try{v.writableEnded?n.splice(x,1):v.write(y);}catch{n.splice(x,1);}}}function a(){s&&clearTimeout(s),s=setTimeout(()=>{m("reload"),s=null;},100);}function d(g,y){let x=g.url||"/";if(x==="/__live-reload"){y.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*"}),y.write(`data: connected
18
+
19
+ `),n.push(y),g.on("close",()=>{let v=n.indexOf(y);v!==-1&&n.splice(v,1);});return}if(x==="/"&&g.method==="GET"){try{let v=u.readFileSync(r,"utf-8"),B=He(v);y.writeHead(200,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-cache"}),y.end(B);}catch(v){let B=v instanceof Error?v.message:"Unknown error";y.writeHead(500,{"Content-Type":"text/plain"}),y.end(`Error reading file: ${B}`);}return}y.writeHead(404,{"Content-Type":"text/plain"}),y.end("Not Found");}let p=ce.createServer(d);p.on("error",g=>{o(g);});let h=null;try{h=u.watch(r,g=>{g==="change"&&a();}),h.on("error",g=>{console.error("[Live Preview] File watcher error:",g.message);});}catch(g){console.warn("[Live Preview] Could not start file watcher:",g instanceof Error?g.message:"Unknown error");}p.listen(0,"127.0.0.1",()=>{let g=p.address();if(g&&typeof g=="object"){let y=g.port;t(y);}else o(new Error("Failed to get server address"));});})}async function T(e){let t=platform(),o;switch(t){case "darwin":o=`open "${e}"`;break;case "linux":o=`xdg-open "${e}"`;break;case "win32":o=`start "" "${e}"`;break;default:return false}return new Promise(r=>{exec(o,n=>{r(!n);});})}function j(){let e=b();return e.apiKey||(c("API key not configured. Run `spike config set api-key <your-key>` to set it."),process.exit(1)),new SpikeClient({apiKey:e.apiKey,baseUrl:e.baseUrl})}function S(e){e instanceof SpikeError?(c(e.message),e.code&&i(`Error code: ${e.code}`)):e instanceof Error?c(e.message):c("An unexpected error occurred"),process.exit(1);}var I=P.join(G.tmpdir(),"spike-preview.pid"),de=P.join(G.tmpdir(),"spike-preview.port"),F=P.join(G.tmpdir(),"spike-preview.formid");function Ye(e){let r=`${b().baseUrl||"https://api.spike.ac"}/f/${e.slug}`;return `<!DOCTYPE html>
475
20
  <html lang="en">
476
21
  <head>
477
22
  <meta charset="UTF-8">
478
23
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
479
- <title>${form.name}</title>
24
+ <title>${e.name}</title>
480
25
  <style>
481
26
  body {
482
27
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -530,8 +75,8 @@ function generateFormHtml(form) {
530
75
  </style>
531
76
  </head>
532
77
  <body>
533
- <form action="${actionUrl}" method="POST">
534
- <h1>${form.name}</h1>
78
+ <form action="${r}" method="POST">
79
+ <h1>${e.name}</h1>
535
80
 
536
81
  <label for="name">Name</label>
537
82
  <input type="text" id="name" name="name" required>
@@ -545,309 +90,18 @@ function generateFormHtml(form) {
545
90
  <button type="submit">Submit</button>
546
91
  </form>
547
92
  </body>
548
- </html>`;
549
- }
550
- function killExistingPreviewServer() {
551
- try {
552
- if (!fs3.existsSync(PREVIEW_PID_FILE)) {
553
- return false;
554
- }
555
- const pidStr = fs3.readFileSync(PREVIEW_PID_FILE, "utf-8").trim();
556
- const pid = parseInt(pidStr, 10);
557
- if (isNaN(pid)) {
558
- cleanupPreviewTempFiles();
559
- return false;
560
- }
561
- try {
562
- process.kill(pid, "SIGTERM");
563
- info(`Stopped existing preview server (PID: ${pid})`);
564
- } catch {
565
- }
566
- cleanupPreviewTempFiles();
567
- return true;
568
- } catch {
569
- return false;
570
- }
571
- }
572
- function cleanupPreviewTempFiles() {
573
- try {
574
- if (fs3.existsSync(PREVIEW_PID_FILE)) {
575
- fs3.unlinkSync(PREVIEW_PID_FILE);
576
- }
577
- } catch {
578
- }
579
- try {
580
- if (fs3.existsSync(PREVIEW_PORT_FILE)) {
581
- fs3.unlinkSync(PREVIEW_PORT_FILE);
582
- }
583
- } catch {
584
- }
585
- try {
586
- if (fs3.existsSync(PREVIEW_FORM_ID_FILE)) {
587
- fs3.unlinkSync(PREVIEW_FORM_ID_FILE);
588
- }
589
- } catch {
590
- }
591
- }
592
- function createFormsCommand() {
593
- const formsCommand = new Command("forms").description("Manage forms");
594
- formsCommand.command("list").description("List all forms").option("-l, --limit <number>", "Maximum number of forms to return", parseInt).option("-p, --project-id <id>", "Filter forms by project ID").option("-i, --include-inactive", "Include inactive forms in the results").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
595
- await handleList(options);
596
- });
597
- formsCommand.command("get").description("Get a specific form by ID").argument("<id>", "Form ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (id, options) => {
598
- await handleGet2(id, options.format);
599
- });
600
- formsCommand.command("create").description("Create a new form").requiredOption("-n, --name <name>", "Name for the form").option("-p, --project-id <id>", "Project ID to add the form to").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
601
- await handleCreate(options);
602
- });
603
- formsCommand.command("update").description("Update a form").argument("<id>", "Form ID").option("-n, --name <name>", "New name for the form").option("-p, --project-id <id>", "Project ID to move the form to").option("--is-active <boolean>", "Whether the form should be active", (value) => {
604
- if (value === "true") return true;
605
- if (value === "false") return false;
606
- throw new Error('--is-active must be "true" or "false"');
607
- }).option("-f, --format <format>", "Output format (json, table)", "table").action(async (id, options) => {
608
- await handleUpdate(id, options);
609
- });
610
- formsCommand.command("delete").description("Delete a form").argument("<id>", "Form ID").action(async (id) => {
611
- await handleDelete(id);
612
- });
613
- formsCommand.command("edit").description("Edit a form with live preview").argument("<id>", "Form ID").option("--file <path>", "Output file path for the HTML", "./form.html").option("--background", "Run preview server as a detached background process").action(async (id, options) => {
614
- await handleEdit(id, options);
615
- });
616
- formsCommand.command("stop-preview").description("Stop the background preview server").action(async () => {
617
- await handleStopPreview();
618
- });
619
- formsCommand.command("save").description("Save local HTML changes to server").argument("[id]", "Form ID (optional if preview server is running)").option("--file <path>", "Path to the HTML file", "./form.html").action(async (id, options) => {
620
- await handleSave(id, options);
621
- });
622
- formsCommand.command("create-page").description("Create a new form and generate an HTML form page (beta)").requiredOption("-n, --name <name>", "Name for the form").option("-p, --project-id <id>", "Project ID to add the form to").option("--file <path>", "Output file path for the HTML", "./form.html").option("--preview", "Start preview server after creation").action(async (options) => {
623
- await handleCreatePage(options);
624
- });
625
- return formsCommand;
626
- }
627
- async function handleList(options) {
628
- try {
629
- const client = createClient();
630
- const forms = await client.forms.list({
631
- limit: options.limit,
632
- project_id: options.projectId,
633
- include_inactive: options.includeInactive
634
- });
635
- if (forms.length === 0) {
636
- info("No forms found");
637
- return;
638
- }
639
- output(forms, options.format);
640
- } catch (err) {
641
- handleError(err);
642
- }
643
- }
644
- async function handleGet2(id, format) {
645
- try {
646
- const client = createClient();
647
- const form = await client.forms.get(id);
648
- output(form, format);
649
- } catch (err) {
650
- handleError(err);
651
- }
652
- }
653
- async function handleCreate(options) {
654
- try {
655
- const client = createClient();
656
- const form = await client.forms.create({
657
- name: options.name,
658
- project_id: options.projectId
659
- });
660
- success(`Form "${form.name}" created successfully`);
661
- console.log("");
662
- output(form, options.format);
663
- } catch (err) {
664
- handleError(err);
665
- }
666
- }
667
- async function handleUpdate(id, options) {
668
- try {
669
- if (options.name === void 0 && options.projectId === void 0 && options.isActive === void 0) {
670
- error("No update options provided. Use --name, --project-id, or --is-active to update the form.");
671
- process.exit(1);
672
- }
673
- const client = createClient();
674
- const updateData = {};
675
- if (options.name !== void 0) {
676
- updateData.name = options.name;
677
- }
678
- if (options.projectId !== void 0) {
679
- updateData.project_id = options.projectId;
680
- }
681
- if (options.isActive !== void 0) {
682
- updateData.is_active = options.isActive;
683
- }
684
- const form = await client.forms.update(id, updateData);
685
- success(`Form "${form.name}" updated successfully`);
686
- console.log("");
687
- output(form, options.format);
688
- } catch (err) {
689
- handleError(err);
690
- }
691
- }
692
- async function handleDelete(id) {
693
- try {
694
- const client = createClient();
695
- await client.forms.delete(id);
696
- success(`Form "${id}" deleted successfully`);
697
- } catch (err) {
698
- handleError(err);
699
- }
700
- }
701
- async function handleStopPreview() {
702
- try {
703
- if (!fs3.existsSync(PREVIEW_PID_FILE)) {
704
- info("No preview server is currently running");
705
- return;
706
- }
707
- const pidStr = fs3.readFileSync(PREVIEW_PID_FILE, "utf-8").trim();
708
- const pid = parseInt(pidStr, 10);
709
- if (isNaN(pid)) {
710
- cleanupPreviewTempFiles();
711
- info("No preview server is currently running");
712
- return;
713
- }
714
- try {
715
- process.kill(pid, "SIGTERM");
716
- success(`Preview server stopped (PID: ${pid})`);
717
- } catch (killError) {
718
- if (killError instanceof Error && "code" in killError && killError.code === "ESRCH") {
719
- info("Preview server was not running");
720
- } else {
721
- throw killError;
722
- }
723
- }
724
- cleanupPreviewTempFiles();
725
- } catch (err) {
726
- handleError(err);
727
- }
728
- }
729
- async function handleCreatePage(options) {
730
- try {
731
- console.log("\u26A0\uFE0F Note: The create-page command is currently in beta.\n");
732
- const client = createClient();
733
- info(`Creating form "${options.name}"...`);
734
- const form = await client.forms.create({
735
- name: options.name,
736
- project_id: options.projectId
737
- });
738
- success(`Form "${form.name}" created successfully`);
739
- const html = generateFormHtml(form);
740
- info("Saving form HTML to server...");
741
- const saveResult = await client.forms.saveHtml(form.id, html);
742
- if (saveResult.success) {
743
- success("Form HTML saved to server");
744
- }
745
- const filePath = path3.resolve(options.file);
746
- fs3.writeFileSync(filePath, html, "utf-8");
747
- success(`Form HTML saved locally to ${filePath}`);
748
- fs3.writeFileSync(PREVIEW_FORM_ID_FILE, form.id, "utf-8");
749
- if (options.preview) {
750
- killExistingPreviewServer();
751
- info("\nMake changes to the HTML file and save to see them in the browser.");
752
- info("When done, run `spike forms save` to upload changes to the server.\n");
753
- await startForegroundPreviewServer(filePath);
754
- }
755
- } catch (err) {
756
- handleError(err);
757
- }
758
- }
759
- async function handleEdit(id, options) {
760
- try {
761
- const client = createClient();
762
- info(`Fetching form ${id}...`);
763
- const form = await client.forms.get(id);
764
- info("Fetching form HTML from server...");
765
- const { html } = await client.forms.getHtml(id);
766
- const filePath = path3.resolve(options.file);
767
- fs3.writeFileSync(filePath, html, "utf-8");
768
- success(`Form HTML saved to ${filePath}`);
769
- fs3.writeFileSync(PREVIEW_FORM_ID_FILE, id, "utf-8");
770
- killExistingPreviewServer();
771
- info(`
772
- Editing form: ${form.name}`);
773
- info("Make changes to the HTML file and save to see them in the browser.");
774
- info("When done, run `spike forms save` to upload changes to the server.\n");
775
- if (options.background) {
776
- await startBackgroundPreviewServer(filePath);
777
- } else {
778
- await startForegroundPreviewServer(filePath);
779
- }
780
- } catch (err) {
781
- handleError(err);
782
- }
783
- }
784
- async function handleSave(id, options) {
785
- try {
786
- let formId = id;
787
- if (!formId) {
788
- if (fs3.existsSync(PREVIEW_FORM_ID_FILE)) {
789
- formId = fs3.readFileSync(PREVIEW_FORM_ID_FILE, "utf-8").trim();
790
- }
791
- if (!formId) {
792
- error("No form ID provided and no active preview session found.");
793
- info("Usage: spike forms save <form-id> --file ./form.html");
794
- process.exit(1);
795
- }
796
- }
797
- const filePath = path3.resolve(options.file);
798
- if (!fs3.existsSync(filePath)) {
799
- error(`File not found: ${filePath}`);
800
- process.exit(1);
801
- }
802
- const html = fs3.readFileSync(filePath, "utf-8");
803
- if (!html.trim()) {
804
- error("HTML file is empty");
805
- process.exit(1);
806
- }
807
- const client = createClient();
808
- info(`Saving form HTML to server...`);
809
- const result = await client.forms.saveHtml(formId, html);
810
- if (result.success) {
811
- success("Form HTML saved successfully!");
812
- if (result.html_url) {
813
- info(`Stored at: ${result.html_url}`);
814
- }
815
- } else {
816
- error("Failed to save form HTML");
817
- process.exit(1);
818
- }
819
- } catch (err) {
820
- handleError(err);
821
- }
822
- }
823
- async function startForegroundPreviewServer(filePath) {
824
- const port = await startLivePreviewServer(filePath);
825
- const previewUrl = `http://127.0.0.1:${port}`;
826
- success(`Preview server running at ${previewUrl}`);
827
- info("Press Ctrl+C to stop the server");
828
- const browserOpened = await openBrowser(previewUrl);
829
- if (!browserOpened) {
830
- info(`Open ${previewUrl} in your browser to preview the form`);
831
- }
832
- await new Promise(() => {
833
- process.on("SIGINT", () => {
834
- info("\nStopping preview server...");
835
- process.exit(0);
836
- });
837
- process.on("SIGTERM", () => {
838
- info("\nStopping preview server...");
839
- process.exit(0);
840
- });
841
- });
842
- }
843
- async function startBackgroundPreviewServer(filePath) {
844
- const serverCode = `
93
+ </html>`}function fe(){try{if(!u.existsSync(I))return !1;let e=u.readFileSync(I,"utf-8").trim(),t=parseInt(e,10);if(isNaN(t))return H(),!1;try{process.kill(t,"SIGTERM"),i(`Stopped existing preview server (PID: ${t})`);}catch{}return H(),!0}catch{return false}}function H(){try{u.existsSync(I)&&u.unlinkSync(I);}catch{}try{u.existsSync(de)&&u.unlinkSync(de);}catch{}try{u.existsSync(F)&&u.unlinkSync(F);}catch{}}function pe(){let e=new Command("forms").description("Manage forms");return e.command("list").description("List all forms").option("-l, --limit <number>","Maximum number of forms to return",parseInt).option("-p, --project-id <id>","Filter forms by project ID").option("-i, --include-inactive","Include inactive forms in the results").option("-f, --format <format>","Output format (json, table)","table").action(async t=>{await Je(t);}),e.command("get").description("Get a specific form by ID").argument("<id>","Form ID").option("-f, --format <format>","Output format (json, table)","table").action(async(t,o)=>{await Ze(t,o.format);}),e.command("create").description("Create a new form").requiredOption("-n, --name <name>","Name for the form").option("-p, --project-id <id>","Project ID to add the form to").option("-f, --format <format>","Output format (json, table)","table").action(async t=>{await Qe(t);}),e.command("update").description("Update a form").argument("<id>","Form ID").option("-n, --name <name>","New name for the form").option("-p, --project-id <id>","Project ID to move the form to").option("--is-active <boolean>","Whether the form should be active",t=>{if(t==="true")return true;if(t==="false")return false;throw new Error('--is-active must be "true" or "false"')}).option("-f, --format <format>","Output format (json, table)","table").action(async(t,o)=>{await Xe(t,o);}),e.command("delete").description("Delete a form").argument("<id>","Form ID").action(async t=>{await et(t);}),e.command("edit").description("Edit a form with live preview").argument("<id>","Form ID").option("--file <path>","Output file path for the HTML","./form.html").option("--background","Run preview server as a detached background process").action(async(t,o)=>{await rt(t,o);}),e.command("stop-preview").description("Stop the background preview server").action(async()=>{await tt();}),e.command("save").description("Save local HTML changes to server").argument("[id]","Form ID (optional if preview server is running)").option("--file <path>","Path to the HTML file","./form.html").action(async(t,o)=>{await nt(t,o);}),e.command("create-page").description("Create a new form and generate an HTML form page (beta)").requiredOption("-n, --name <name>","Name for the form").option("-p, --project-id <id>","Project ID to add the form to").option("--file <path>","Output file path for the HTML","./form.html").option("--preview","Start preview server after creation").action(async t=>{await ot(t);}),e}async function Je(e){try{let o=await j().forms.list({limit:e.limit,project_id:e.projectId,include_inactive:e.includeInactive});if(o.length===0){i("No forms found");return}f(o,e.format);}catch(t){S(t);}}async function Ze(e,t){try{let r=await j().forms.get(e);f(r,t);}catch(o){S(o);}}async function Qe(e){try{let o=await j().forms.create({name:e.name,project_id:e.projectId});l(`Form "${o.name}" created successfully`),console.log(""),f(o,e.format);}catch(t){S(t);}}async function Xe(e,t){try{t.name===void 0&&t.projectId===void 0&&t.isActive===void 0&&(c("No update options provided. Use --name, --project-id, or --is-active to update the form."),process.exit(1));let o=j(),r={};t.name!==void 0&&(r.name=t.name),t.projectId!==void 0&&(r.project_id=t.projectId),t.isActive!==void 0&&(r.is_active=t.isActive);let n=await o.forms.update(e,r);l(`Form "${n.name}" updated successfully`),console.log(""),f(n,t.format);}catch(o){S(o);}}async function et(e){try{await j().forms.delete(e),l(`Form "${e}" deleted successfully`);}catch(t){S(t);}}async function tt(){try{if(!u.existsSync(I)){i("No preview server is currently running");return}let e=u.readFileSync(I,"utf-8").trim(),t=parseInt(e,10);if(isNaN(t)){H(),i("No preview server is currently running");return}try{process.kill(t,"SIGTERM"),l(`Preview server stopped (PID: ${t})`);}catch(o){if(o instanceof Error&&"code"in o&&o.code==="ESRCH")i("Preview server was not running");else throw o}H();}catch(e){S(e);}}async function ot(e){try{console.log(`\u26A0\uFE0F Note: The create-page command is currently in beta.
94
+ `);let t=j();i(`Creating form "${e.name}"...`);let o=await t.forms.create({name:e.name,project_id:e.projectId});l(`Form "${o.name}" created successfully`);let r=Ye(o);i("Saving form HTML to server..."),(await t.forms.saveHtml(o.id,r)).success&&l("Form HTML saved to server");let s=P.resolve(e.file);u.writeFileSync(s,r,"utf-8"),l(`Form HTML saved locally to ${s}`),u.writeFileSync(F,o.id,"utf-8"),e.preview&&(fe(),i(`
95
+ Make changes to the HTML file and save to see them in the browser.`),i("When done, run `spike forms save` to upload changes to the server.\n"),await ue(s));}catch(t){S(t);}}async function rt(e,t){try{let o=j();i(`Fetching form ${e}...`);let r=await o.forms.get(e);i("Fetching form HTML from server...");let{html:n}=await o.forms.getHtml(e),s=P.resolve(t.file);u.writeFileSync(s,n,"utf-8"),l(`Form HTML saved to ${s}`),u.writeFileSync(F,e,"utf-8"),fe(),i(`
96
+ Editing form: ${r.name}`),i("Make changes to the HTML file and save to see them in the browser."),i("When done, run `spike forms save` to upload changes to the server.\n"),t.background?await it(s):await ue(s);}catch(o){S(o);}}async function nt(e,t){try{let o=e;o||(u.existsSync(F)&&(o=u.readFileSync(F,"utf-8").trim()),o||(c("No form ID provided and no active preview session found."),i("Usage: spike forms save <form-id> --file ./form.html"),process.exit(1)));let r=P.resolve(t.file);u.existsSync(r)||(c(`File not found: ${r}`),process.exit(1));let n=u.readFileSync(r,"utf-8");n.trim()||(c("HTML file is empty"),process.exit(1));let s=j();i("Saving form HTML to server..."),(await s.forms.saveHtml(o,n)).success?l("Form HTML saved successfully!"):(c("Failed to save form HTML"),process.exit(1));}catch(o){S(o);}}async function ue(e){let o=`http://127.0.0.1:${await le(e)}`;l(`Preview server running at ${o}`),i("Press Ctrl+C to stop the server"),await T(o)||i(`Open ${o} in your browser to preview the form`),await new Promise(()=>{process.on("SIGINT",()=>{i(`
97
+ Stopping preview server...`),process.exit(0);}),process.on("SIGTERM",()=>{i(`
98
+ Stopping preview server...`),process.exit(0);});});}async function it(e){let t=`
845
99
  const http = require('node:http');
846
100
  const fs = require('node:fs');
847
101
  const path = require('node:path');
848
102
  const os = require('node:os');
849
103
 
850
- const filePath = ${JSON.stringify(filePath)};
104
+ const filePath = ${JSON.stringify(e)};
851
105
  const pidFile = path.join(os.tmpdir(), 'spike-preview.pid');
852
106
  const portFile = path.join(os.tmpdir(), 'spike-preview.port');
853
107
 
@@ -967,590 +221,8 @@ server.listen(0, '127.0.0.1', () => {
967
221
  // Output port for parent process to read
968
222
  console.log('PORT:' + port);
969
223
  });
970
- `;
971
- return new Promise((resolve3, reject) => {
972
- const child = spawn("node", ["-e", serverCode], {
973
- detached: true,
974
- stdio: ["ignore", "pipe", "ignore"]
975
- });
976
- let portReceived = false;
977
- let outputBuffer = "";
978
- child.stdout?.on("data", (data) => {
979
- outputBuffer += data.toString();
980
- const match = outputBuffer.match(/PORT:(\d+)/);
981
- if (match && !portReceived) {
982
- portReceived = true;
983
- const port = parseInt(match[1], 10);
984
- const previewUrl = `http://127.0.0.1:${port}`;
985
- success(`Background preview server started at ${previewUrl}`);
986
- info(`PID: ${child.pid}`);
987
- info("Run `spike forms stop-preview` to stop the server");
988
- openBrowser(previewUrl).then((browserOpened) => {
989
- if (!browserOpened) {
990
- info(`Open ${previewUrl} in your browser to preview the form`);
991
- }
992
- child.unref();
993
- resolve3();
994
- });
995
- }
996
- });
997
- child.on("error", (err) => {
998
- reject(new Error(`Failed to start background server: ${err.message}`));
999
- });
1000
- setTimeout(() => {
1001
- if (!portReceived) {
1002
- child.kill();
1003
- reject(new Error("Timeout waiting for background server to start"));
1004
- }
1005
- }, 1e4);
1006
- });
1007
- }
1008
- function createClient2() {
1009
- const config = loadConfig();
1010
- if (!config.apiKey) {
1011
- error("API key not configured. Run `spike config set api-key <your-key>` to set it.");
1012
- process.exit(1);
1013
- }
1014
- return new SpikeClient({
1015
- apiKey: config.apiKey,
1016
- baseUrl: config.baseUrl
1017
- });
1018
- }
1019
- function handleError2(err) {
1020
- if (err instanceof SpikeError) {
1021
- error(err.message);
1022
- if (err.code) {
1023
- info(`Error code: ${err.code}`);
1024
- }
1025
- } else if (err instanceof Error) {
1026
- error(err.message);
1027
- } else {
1028
- error("An unexpected error occurred");
1029
- }
1030
- process.exit(1);
1031
- }
1032
- function createSubmissionsCommand() {
1033
- const submissionsCommand = new Command("submissions").description("Manage form submissions");
1034
- submissionsCommand.command("list").description("List submissions for a form").argument("<form-id>", "Form ID").option("-l, --limit <number>", "Maximum number of submissions to return", parseInt).option("-s, --status <status>", "Filter by status (read, unread, spam, starred)").option("--from <date>", "Filter submissions from this date (ISO 8601 format)").option("--to <date>", "Filter submissions to this date (ISO 8601 format)").option("-o, --order <order>", "Sort order (asc, desc)", "desc").option("-f, --format <format>", "Output format (json, table)", "table").action(async (formId, options) => {
1035
- await handleList2(formId, options);
1036
- });
1037
- submissionsCommand.command("export").description("Export all submissions for a form").argument("<form-id>", "Form ID").option("-f, --format <format>", "Output format (json, csv)", "json").action(async (formId, options) => {
1038
- await handleExport(formId, options.format);
1039
- });
1040
- submissionsCommand.command("stats").description("Display submission statistics for a form").argument("<form-id>", "Form ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (formId, options) => {
1041
- await handleStats(formId, options.format);
1042
- });
1043
- return submissionsCommand;
1044
- }
1045
- async function handleList2(formId, options) {
1046
- try {
1047
- const client = createClient2();
1048
- const params = {};
1049
- if (options.limit !== void 0) {
1050
- params.limit = options.limit;
1051
- }
1052
- if (options.from) {
1053
- params.since = options.from;
1054
- }
1055
- if (options.order === "asc" || options.order === "desc") {
1056
- params.order = options.order;
1057
- }
1058
- if (options.status) {
1059
- switch (options.status.toLowerCase()) {
1060
- case "read":
1061
- params.is_read = true;
1062
- break;
1063
- case "unread":
1064
- params.is_read = false;
1065
- break;
1066
- case "spam":
1067
- params.is_spam = true;
1068
- break;
1069
- case "starred":
1070
- break;
1071
- default:
1072
- error(`Invalid status: ${options.status}. Valid values are: read, unread, spam, starred`);
1073
- process.exit(1);
1074
- }
1075
- }
1076
- const submissions = await client.submissions.list(formId, params);
1077
- let filteredSubmissions = submissions;
1078
- if (options.status?.toLowerCase() === "starred") {
1079
- filteredSubmissions = submissions.filter((s) => s.is_starred);
1080
- }
1081
- if (options.to) {
1082
- const toDate = new Date(options.to);
1083
- filteredSubmissions = filteredSubmissions.filter((s) => new Date(s.created_at) <= toDate);
1084
- }
1085
- if (filteredSubmissions.length === 0) {
1086
- info("No submissions found");
1087
- return;
1088
- }
1089
- const displayData = filteredSubmissions.map((s) => ({
1090
- id: s.id,
1091
- created_at: s.created_at,
1092
- is_read: s.is_read,
1093
- is_spam: s.is_spam,
1094
- is_starred: s.is_starred,
1095
- data: JSON.stringify(s.data).slice(0, 50) + (JSON.stringify(s.data).length > 50 ? "..." : "")
1096
- }));
1097
- output(displayData, options.format);
1098
- } catch (err) {
1099
- handleError2(err);
1100
- }
1101
- }
1102
- async function handleExport(formId, format) {
1103
- try {
1104
- const client = createClient2();
1105
- const submissions = await client.submissions.export(formId);
1106
- if (submissions.length === 0) {
1107
- info("No submissions to export");
1108
- return;
1109
- }
1110
- if (format === "csv") {
1111
- outputCsv(submissions);
1112
- } else {
1113
- console.log(JSON.stringify(submissions, null, 2));
1114
- }
1115
- success(`Exported ${submissions.length} submission${submissions.length === 1 ? "" : "s"}`);
1116
- } catch (err) {
1117
- handleError2(err);
1118
- }
1119
- }
1120
- function outputCsv(submissions) {
1121
- if (submissions.length === 0) {
1122
- return;
1123
- }
1124
- const dataKeys = /* @__PURE__ */ new Set();
1125
- for (const submission of submissions) {
1126
- Object.keys(submission.data).forEach((key) => dataKeys.add(key));
1127
- }
1128
- const baseHeaders = ["id", "form_id", "is_spam", "is_read", "is_starred", "ip_address", "user_agent", "created_at"];
1129
- const allHeaders = [...baseHeaders, ...Array.from(dataKeys)];
1130
- console.log(allHeaders.map(escapeCsvValue).join(","));
1131
- for (const submission of submissions) {
1132
- const row = [
1133
- submission.id,
1134
- submission.form_id,
1135
- String(submission.is_spam),
1136
- String(submission.is_read),
1137
- String(submission.is_starred),
1138
- submission.ip_address || "",
1139
- submission.user_agent || "",
1140
- submission.created_at,
1141
- ...Array.from(dataKeys).map((key) => {
1142
- const value = submission.data[key];
1143
- if (value === void 0 || value === null) {
1144
- return "";
1145
- }
1146
- if (typeof value === "object") {
1147
- return JSON.stringify(value);
1148
- }
1149
- return String(value);
1150
- })
1151
- ];
1152
- console.log(row.map(escapeCsvValue).join(","));
1153
- }
1154
- }
1155
- function escapeCsvValue(value) {
1156
- if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
1157
- return `"${value.replace(/"/g, '""')}"`;
1158
- }
1159
- return value;
1160
- }
1161
- async function handleStats(formId, format) {
1162
- try {
1163
- const client = createClient2();
1164
- const stats = await client.submissions.getStats(formId);
1165
- output(stats, format);
1166
- } catch (err) {
1167
- handleError2(err);
1168
- }
1169
- }
1170
- function createClient3() {
1171
- const config = loadConfig();
1172
- if (!config.apiKey) {
1173
- error("API key not configured. Run `spike config set api-key <your-key>` to set it.");
1174
- process.exit(1);
1175
- }
1176
- return new SpikeClient({
1177
- apiKey: config.apiKey,
1178
- baseUrl: config.baseUrl
1179
- });
1180
- }
1181
- function handleError3(err) {
1182
- if (err instanceof SpikeError) {
1183
- error(err.message);
1184
- if (err.code) {
1185
- info(`Error code: ${err.code}`);
1186
- }
1187
- } else if (err instanceof Error) {
1188
- error(err.message);
1189
- } else {
1190
- error("An unexpected error occurred");
1191
- }
1192
- process.exit(1);
1193
- }
1194
- function createProjectsCommand() {
1195
- const projectsCommand = new Command("projects").description("Manage projects");
1196
- projectsCommand.command("list").description("List all projects").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
1197
- await handleList3(options.format);
1198
- });
1199
- projectsCommand.command("get").description("Get a specific project by ID").argument("<id>", "Project ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (id, options) => {
1200
- await handleGet3(id, options.format);
1201
- });
1202
- projectsCommand.command("create").description("Create a new project").requiredOption("-n, --name <name>", "Name for the project").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
1203
- await handleCreate2(options);
1204
- });
1205
- projectsCommand.command("update").description("Update a project").argument("<id>", "Project ID").option("-n, --name <name>", "New name for the project").option("-f, --format <format>", "Output format (json, table)", "table").action(async (id, options) => {
1206
- await handleUpdate2(id, options);
1207
- });
1208
- projectsCommand.command("delete").description("Delete a project").argument("<id>", "Project ID").action(async (id) => {
1209
- await handleDelete2(id);
1210
- });
1211
- return projectsCommand;
1212
- }
1213
- async function handleList3(format) {
1214
- try {
1215
- const client = createClient3();
1216
- const projects = await client.projects.list();
1217
- if (projects.length === 0) {
1218
- info("No projects found");
1219
- return;
1220
- }
1221
- output(projects, format);
1222
- } catch (err) {
1223
- handleError3(err);
1224
- }
1225
- }
1226
- async function handleGet3(id, format) {
1227
- try {
1228
- const client = createClient3();
1229
- const project = await client.projects.get(id);
1230
- output(project, format);
1231
- } catch (err) {
1232
- handleError3(err);
1233
- }
1234
- }
1235
- async function handleCreate2(options) {
1236
- try {
1237
- const client = createClient3();
1238
- const project = await client.projects.create({
1239
- name: options.name
1240
- });
1241
- success(`Project "${project.name}" created successfully`);
1242
- console.log("");
1243
- output(project, options.format);
1244
- } catch (err) {
1245
- handleError3(err);
1246
- }
1247
- }
1248
- async function handleUpdate2(id, options) {
1249
- try {
1250
- if (options.name === void 0) {
1251
- error("No update options provided. Use --name to update the project.");
1252
- process.exit(1);
1253
- }
1254
- const client = createClient3();
1255
- const updateData = {};
1256
- if (options.name !== void 0) {
1257
- updateData.name = options.name;
1258
- }
1259
- const project = await client.projects.update(id, updateData);
1260
- success(`Project "${project.name}" updated successfully`);
1261
- console.log("");
1262
- output(project, options.format);
1263
- } catch (err) {
1264
- handleError3(err);
1265
- }
1266
- }
1267
- async function handleDelete2(id) {
1268
- try {
1269
- const client = createClient3();
1270
- await client.projects.delete(id);
1271
- success(`Project "${id}" deleted successfully`);
1272
- } catch (err) {
1273
- handleError3(err);
1274
- }
1275
- }
1276
- function createClient4() {
1277
- const config = loadConfig();
1278
- if (!config.apiKey) {
1279
- error("API key not configured. Run `spike config set api-key <your-key>` to set it.");
1280
- process.exit(1);
1281
- }
1282
- return new SpikeClient({
1283
- apiKey: config.apiKey,
1284
- baseUrl: config.baseUrl
1285
- });
1286
- }
1287
- function handleError4(err) {
1288
- if (err instanceof SpikeError) {
1289
- error(err.message);
1290
- if (err.code) {
1291
- info(`Error code: ${err.code}`);
1292
- }
1293
- } else if (err instanceof Error) {
1294
- error(err.message);
1295
- } else {
1296
- error("An unexpected error occurred");
1297
- }
1298
- process.exit(1);
1299
- }
1300
- function createTeamsCommand() {
1301
- const teamsCommand = new Command("teams").description("Manage teams");
1302
- teamsCommand.command("list").description("List all teams").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
1303
- await handleList4(options.format);
1304
- });
1305
- teamsCommand.command("get").description("Get a specific team by ID").argument("<id>", "Team ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (id, options) => {
1306
- await handleGet4(id, options.format);
1307
- });
1308
- teamsCommand.command("create").description("Create a new team").requiredOption("-n, --name <name>", "Name for the team").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
1309
- await handleCreate3(options);
1310
- });
1311
- teamsCommand.command("members").description("List members of a team").argument("<id>", "Team ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (id, options) => {
1312
- await handleMembers(id, options.format);
1313
- });
1314
- teamsCommand.command("invite").description("Invite a user to a team").argument("<id>", "Team ID").requiredOption("-e, --email <email>", "Email address of the user to invite").requiredOption("-r, --role <role>", "Role for the invited user (admin, member)").option("-f, --format <format>", "Output format (json, table)", "table").action(async (id, options) => {
1315
- await handleInvite(id, options);
1316
- });
1317
- return teamsCommand;
1318
- }
1319
- async function handleList4(format) {
1320
- try {
1321
- const client = createClient4();
1322
- const teams = await client.teams.list();
1323
- if (teams.length === 0) {
1324
- info("No teams found");
1325
- return;
1326
- }
1327
- output(teams, format);
1328
- } catch (err) {
1329
- handleError4(err);
1330
- }
1331
- }
1332
- async function handleGet4(id, format) {
1333
- try {
1334
- const client = createClient4();
1335
- const team = await client.teams.get(id);
1336
- output(team, format);
1337
- } catch (err) {
1338
- handleError4(err);
1339
- }
1340
- }
1341
- async function handleCreate3(options) {
1342
- try {
1343
- const client = createClient4();
1344
- const team = await client.teams.create({
1345
- name: options.name
1346
- });
1347
- success(`Team "${team.name}" created successfully`);
1348
- console.log("");
1349
- output(team, options.format);
1350
- } catch (err) {
1351
- handleError4(err);
1352
- }
1353
- }
1354
- async function handleMembers(id, format) {
1355
- try {
1356
- const client = createClient4();
1357
- const members = await client.teams.listMembers(id);
1358
- if (members.length === 0) {
1359
- info("No members found");
1360
- return;
1361
- }
1362
- const displayData = members.map((member) => ({
1363
- id: member.id,
1364
- user_id: member.user_id,
1365
- name: member.user.name,
1366
- email: member.user.email,
1367
- role: member.role,
1368
- created_at: member.created_at
1369
- }));
1370
- output(displayData, format);
1371
- } catch (err) {
1372
- handleError4(err);
1373
- }
1374
- }
1375
- async function handleInvite(id, options) {
1376
- try {
1377
- const role = options.role.toLowerCase();
1378
- if (role !== "admin" && role !== "member") {
1379
- error("Invalid role. Valid values are: admin, member");
1380
- process.exit(1);
1381
- }
1382
- const client = createClient4();
1383
- const invitation = await client.teams.invite(id, {
1384
- email: options.email,
1385
- role
1386
- });
1387
- success(`Invitation sent to "${options.email}" as ${role}`);
1388
- console.log("");
1389
- output(invitation, options.format);
1390
- } catch (err) {
1391
- handleError4(err);
1392
- }
1393
- }
1394
- function createClient5() {
1395
- const config = loadConfig();
1396
- if (!config.apiKey) {
1397
- error("API key not configured. Run `spike config set api-key <your-key>` to set it.");
1398
- process.exit(1);
1399
- }
1400
- return new SpikeClient({
1401
- apiKey: config.apiKey,
1402
- baseUrl: config.baseUrl
1403
- });
1404
- }
1405
- function handleError5(err) {
1406
- if (err instanceof SpikeError) {
1407
- error(err.message);
1408
- if (err.code) {
1409
- info(`Error code: ${err.code}`);
1410
- }
1411
- } else if (err instanceof Error) {
1412
- error(err.message);
1413
- } else {
1414
- error("An unexpected error occurred");
1415
- }
1416
- process.exit(1);
1417
- }
1418
- function createUserCommand() {
1419
- const userCommand = new Command("user").description("Manage user profile and API keys");
1420
- userCommand.option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
1421
- await handleGetProfile(options.format);
1422
- });
1423
- userCommand.command("update").description("Update user profile").option("-n, --name <name>", "New name for the user").option("-e, --email <email>", "New email address for the user").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
1424
- await handleUpdateProfile(options);
1425
- });
1426
- const apiKeysCommand = new Command("api-keys").description("Manage user API keys");
1427
- apiKeysCommand.option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
1428
- await handleListApiKeys(options.format);
1429
- });
1430
- apiKeysCommand.command("create").description("Create a new API key").requiredOption("-n, --name <name>", "Name for the API key").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
1431
- await handleCreateApiKey(options);
1432
- });
1433
- apiKeysCommand.command("delete").description("Delete an API key").argument("<id>", "API key ID").action(async (id) => {
1434
- await handleDeleteApiKey(id);
1435
- });
1436
- userCommand.addCommand(apiKeysCommand);
1437
- return userCommand;
1438
- }
1439
- async function handleGetProfile(format) {
1440
- try {
1441
- const client = createClient5();
1442
- const user = await client.user.get();
1443
- output(user, format);
1444
- } catch (err) {
1445
- handleError5(err);
1446
- }
1447
- }
1448
- async function handleUpdateProfile(options) {
1449
- try {
1450
- if (options.name === void 0 && options.email === void 0) {
1451
- error("No update options provided. Use --name or --email to update the profile.");
1452
- process.exit(1);
1453
- }
1454
- const client = createClient5();
1455
- const updateData = {};
1456
- if (options.name !== void 0) {
1457
- updateData.name = options.name;
1458
- }
1459
- if (options.email !== void 0) {
1460
- updateData.email = options.email;
1461
- }
1462
- const user = await client.user.update(updateData);
1463
- success("Profile updated successfully");
1464
- console.log("");
1465
- output(user, options.format);
1466
- } catch (err) {
1467
- handleError5(err);
1468
- }
1469
- }
1470
- async function handleListApiKeys(format) {
1471
- try {
1472
- const client = createClient5();
1473
- const apiKeys = await client.user.listApiKeys();
1474
- if (apiKeys.length === 0) {
1475
- info("No API keys found");
1476
- return;
1477
- }
1478
- const displayData = apiKeys.map((key) => ({
1479
- id: key.id,
1480
- name: key.name,
1481
- key: key.key ? maskApiKey(key.key) : "-",
1482
- last_used_at: key.last_used_at || "Never",
1483
- created_at: key.created_at
1484
- }));
1485
- output(displayData, format);
1486
- } catch (err) {
1487
- handleError5(err);
1488
- }
1489
- }
1490
- async function handleCreateApiKey(options) {
1491
- try {
1492
- const client = createClient5();
1493
- const apiKey = await client.user.createApiKey({
1494
- name: options.name
1495
- });
1496
- success(`API key "${apiKey.name}" created successfully`);
1497
- console.log("");
1498
- if (options.format === "json") {
1499
- output(apiKey, "json");
1500
- } else {
1501
- console.log("Your new API key (save this - it will not be shown again):");
1502
- console.log("");
1503
- console.log(` ${apiKey.key}`);
1504
- console.log("");
1505
- output({
1506
- id: apiKey.id,
1507
- name: apiKey.name,
1508
- created_at: apiKey.created_at
1509
- }, "table");
1510
- }
1511
- } catch (err) {
1512
- handleError5(err);
1513
- }
1514
- }
1515
- async function handleDeleteApiKey(id) {
1516
- try {
1517
- const client = createClient5();
1518
- await client.user.deleteApiKey(id);
1519
- success(`API key "${id}" deleted successfully`);
1520
- } catch (err) {
1521
- handleError5(err);
1522
- }
1523
- }
1524
- function generateState() {
1525
- return crypto.randomBytes(32).toString("hex");
1526
- }
1527
- function verifyState(received, expected) {
1528
- return received === expected;
1529
- }
1530
- function parseCallbackParams(url) {
1531
- try {
1532
- let searchParams;
1533
- if (url.startsWith("http://") || url.startsWith("https://")) {
1534
- const parsed = new URL(url);
1535
- searchParams = parsed.searchParams;
1536
- } else {
1537
- const queryIndex = url.indexOf("?");
1538
- if (queryIndex === -1) {
1539
- return {};
1540
- }
1541
- searchParams = new URLSearchParams(url.slice(queryIndex + 1));
1542
- }
1543
- return {
1544
- key: searchParams.get("key") ?? void 0,
1545
- state: searchParams.get("state") ?? void 0,
1546
- error: searchParams.get("error") ?? void 0,
1547
- message: searchParams.get("message") ?? void 0
1548
- };
1549
- } catch {
1550
- return {};
1551
- }
1552
- }
1553
- var SUCCESS_HTML = `<!DOCTYPE html>
224
+ `;return new Promise((o,r)=>{let n=spawn("node",["-e",t],{detached:true,stdio:["ignore","pipe","ignore"]}),s=false,m="";n.stdout?.on("data",a=>{m+=a.toString();let d=m.match(/PORT:(\d+)/);if(d&&!s){s=true;let h=`http://127.0.0.1:${parseInt(d[1],10)}`;l(`Background preview server started at ${h}`),i(`PID: ${n.pid}`),i("Run `spike forms stop-preview` to stop the server"),T(h).then(g=>{g||i(`Open ${h} in your browser to preview the form`),n.unref(),o();});}}),n.on("error",a=>{r(new Error(`Failed to start background server: ${a.message}`));}),setTimeout(()=>{s||(n.kill(),r(new Error("Timeout waiting for background server to start")));},1e4);})}function J(){let e=b();return e.apiKey||(c("API key not configured. Run `spike config set api-key <your-key>` to set it."),process.exit(1)),new SpikeClient({apiKey:e.apiKey,baseUrl:e.baseUrl})}function Z(e){e instanceof SpikeError?(c(e.message),e.code&&i(`Error code: ${e.code}`)):e instanceof Error?c(e.message):c("An unexpected error occurred"),process.exit(1);}function he(){let e=new Command("submissions").description("Manage form submissions");return e.command("list").description("List submissions for a form").argument("<form-id>","Form ID").option("-l, --limit <number>","Maximum number of submissions to return",parseInt).option("-s, --status <status>","Filter by status (read, unread, spam, starred)").option("--from <date>","Filter submissions from this date (ISO 8601 format)").option("--to <date>","Filter submissions to this date (ISO 8601 format)").option("-o, --order <order>","Sort order (asc, desc)","desc").option("-f, --format <format>","Output format (json, table)","table").action(async(t,o)=>{await mt(t,o);}),e.command("export").description("Export all submissions for a form").argument("<form-id>","Form ID").option("-f, --format <format>","Output format (json, csv)","json").action(async(t,o)=>{await lt(t,o.format);}),e.command("stats").description("Display submission statistics for a form").argument("<form-id>","Form ID").option("-f, --format <format>","Output format (json, table)","table").action(async(t,o)=>{await ft(t,o.format);}),e}async function mt(e,t){try{let o=J(),r={};if(t.limit!==void 0&&(r.limit=t.limit),t.from&&(r.since=t.from),(t.order==="asc"||t.order==="desc")&&(r.order=t.order),t.status)switch(t.status.toLowerCase()){case "read":r.is_read=!0;break;case "unread":r.is_read=!1;break;case "spam":r.is_spam=!0;break;case "starred":break;default:c(`Invalid status: ${t.status}. Valid values are: read, unread, spam, starred`),process.exit(1);}let n=await o.submissions.list(e,r),s=n;if(t.status?.toLowerCase()==="starred"&&(s=n.filter(a=>a.is_starred)),t.to){let a=new Date(t.to);s=s.filter(d=>new Date(d.created_at)<=a);}if(s.length===0){i("No submissions found");return}let m=s.map(a=>({id:a.id,created_at:a.created_at,is_read:a.is_read,is_spam:a.is_spam,is_starred:a.is_starred,data:JSON.stringify(a.data).slice(0,50)+(JSON.stringify(a.data).length>50?"...":"")}));f(m,t.format);}catch(o){Z(o);}}async function lt(e,t){try{let r=await J().submissions.export(e);if(r.length===0){i("No submissions to export");return}t==="csv"?dt(r):console.log(JSON.stringify(r,null,2)),l(`Exported ${r.length} submission${r.length===1?"":"s"}`);}catch(o){Z(o);}}function dt(e){if(e.length===0)return;let t=new Set;for(let n of e)Object.keys(n.data).forEach(s=>t.add(s));let r=[...["id","form_id","is_spam","is_read","is_starred","ip_address","user_agent","created_at"],...Array.from(t)];console.log(r.map(ge).join(","));for(let n of e){let s=[n.id,n.form_id,String(n.is_spam),String(n.is_read),String(n.is_starred),n.ip_address||"",n.user_agent||"",n.created_at,...Array.from(t).map(m=>{let a=n.data[m];return a==null?"":typeof a=="object"?JSON.stringify(a):String(a)})];console.log(s.map(ge).join(","));}}function ge(e){return e.includes(",")||e.includes('"')||e.includes(`
225
+ `)||e.includes("\r")?`"${e.replace(/"/g,'""')}"`:e}async function ft(e,t){try{let r=await J().submissions.getStats(e);f(r,t);}catch(o){Z(o);}}function _(){let e=b();return e.apiKey||(c("API key not configured. Run `spike config set api-key <your-key>` to set it."),process.exit(1)),new SpikeClient({apiKey:e.apiKey,baseUrl:e.baseUrl})}function A(e){e instanceof SpikeError?(c(e.message),e.code&&i(`Error code: ${e.code}`)):e instanceof Error?c(e.message):c("An unexpected error occurred"),process.exit(1);}function ye(){let e=new Command("projects").description("Manage projects");return e.command("list").description("List all projects").option("-f, --format <format>","Output format (json, table)","table").action(async t=>{await ht(t.format);}),e.command("get").description("Get a specific project by ID").argument("<id>","Project ID").option("-f, --format <format>","Output format (json, table)","table").action(async(t,o)=>{await yt(t,o.format);}),e.command("create").description("Create a new project").requiredOption("-n, --name <name>","Name for the project").option("-f, --format <format>","Output format (json, table)","table").action(async t=>{await bt(t);}),e.command("update").description("Update a project").argument("<id>","Project ID").option("-n, --name <name>","New name for the project").option("-f, --format <format>","Output format (json, table)","table").action(async(t,o)=>{await vt(t,o);}),e.command("delete").description("Delete a project").argument("<id>","Project ID").action(async t=>{await wt(t);}),e}async function ht(e){try{let o=await _().projects.list();if(o.length===0){i("No projects found");return}f(o,e);}catch(t){A(t);}}async function yt(e,t){try{let r=await _().projects.get(e);f(r,t);}catch(o){A(o);}}async function bt(e){try{let o=await _().projects.create({name:e.name});l(`Project "${o.name}" created successfully`),console.log(""),f(o,e.format);}catch(t){A(t);}}async function vt(e,t){try{t.name===void 0&&(c("No update options provided. Use --name to update the project."),process.exit(1));let o=_(),r={};t.name!==void 0&&(r.name=t.name);let n=await o.projects.update(e,r);l(`Project "${n.name}" updated successfully`),console.log(""),f(n,t.format);}catch(o){A(o);}}async function wt(e){try{await _().projects.delete(e),l(`Project "${e}" deleted successfully`);}catch(t){A(t);}}function U(){let e=b();return e.apiKey||(c("API key not configured. Run `spike config set api-key <your-key>` to set it."),process.exit(1)),new SpikeClient({apiKey:e.apiKey,baseUrl:e.baseUrl})}function R(e){e instanceof SpikeError?(c(e.message),e.code&&i(`Error code: ${e.code}`)):e instanceof Error?c(e.message):c("An unexpected error occurred"),process.exit(1);}function be(){let e=new Command("teams").description("Manage teams");return e.command("list").description("List all teams").option("-f, --format <format>","Output format (json, table)","table").action(async t=>{await xt(t.format);}),e.command("get").description("Get a specific team by ID").argument("<id>","Team ID").option("-f, --format <format>","Output format (json, table)","table").action(async(t,o)=>{await Pt(t,o.format);}),e.command("create").description("Create a new team").requiredOption("-n, --name <name>","Name for the team").option("-f, --format <format>","Output format (json, table)","table").action(async t=>{await jt(t);}),e.command("members").description("List members of a team").argument("<id>","Team ID").option("-f, --format <format>","Output format (json, table)","table").action(async(t,o)=>{await It(t,o.format);}),e.command("invite").description("Invite a user to a team").argument("<id>","Team ID").requiredOption("-e, --email <email>","Email address of the user to invite").requiredOption("-r, --role <role>","Role for the invited user (admin, member)").option("-f, --format <format>","Output format (json, table)","table").action(async(t,o)=>{await Ft(t,o);}),e}async function xt(e){try{let o=await U().teams.list();if(o.length===0){i("No teams found");return}f(o,e);}catch(t){R(t);}}async function Pt(e,t){try{let r=await U().teams.get(e);f(r,t);}catch(o){R(o);}}async function jt(e){try{let o=await U().teams.create({name:e.name});l(`Team "${o.name}" created successfully`),console.log(""),f(o,e.format);}catch(t){R(t);}}async function It(e,t){try{let r=await U().teams.listMembers(e);if(r.length===0){i("No members found");return}let n=r.map(s=>({id:s.id,user_id:s.user_id,name:s.user.name,email:s.user.email,role:s.role,created_at:s.created_at}));f(n,t);}catch(o){R(o);}}async function Ft(e,t){try{let o=t.role.toLowerCase();o!=="admin"&&o!=="member"&&(c("Invalid role. Valid values are: admin, member"),process.exit(1));let n=await U().teams.invite(e,{email:t.email,role:o});l(`Invitation sent to "${t.email}" as ${o}`),console.log(""),f(n,t.format);}catch(o){R(o);}}function D(){let e=b();return e.apiKey||(c("API key not configured. Run `spike config set api-key <your-key>` to set it."),process.exit(1)),new SpikeClient({apiKey:e.apiKey,baseUrl:e.baseUrl})}function $(e){e instanceof SpikeError?(c(e.message),e.code&&i(`Error code: ${e.code}`)):e instanceof Error?c(e.message):c("An unexpected error occurred"),process.exit(1);}function we(){let e=new Command("user").description("Manage user profile and API keys");e.option("-f, --format <format>","Output format (json, table)","table").action(async o=>{await Et(o.format);}),e.command("update").description("Update user profile").option("-n, --name <name>","New name for the user").option("-e, --email <email>","New email address for the user").option("-f, --format <format>","Output format (json, table)","table").action(async o=>{await Tt(o);});let t=new Command("api-keys").description("Manage user API keys");return t.option("-f, --format <format>","Output format (json, table)","table").action(async o=>{await _t(o.format);}),t.command("create").description("Create a new API key").requiredOption("-n, --name <name>","Name for the API key").option("-f, --format <format>","Output format (json, table)","table").action(async o=>{await At(o);}),t.command("delete").description("Delete an API key").argument("<id>","API key ID").action(async o=>{await Ut(o);}),e.addCommand(t),e}async function Et(e){try{let o=await D().user.get();f(o,e);}catch(t){$(t);}}async function Tt(e){try{e.name===void 0&&e.email===void 0&&(c("No update options provided. Use --name or --email to update the profile."),process.exit(1));let t=D(),o={};e.name!==void 0&&(o.name=e.name),e.email!==void 0&&(o.email=e.email);let r=await t.user.update(o);l("Profile updated successfully"),console.log(""),f(r,e.format);}catch(t){$(t);}}async function _t(e){try{let o=await D().user.listApiKeys();if(o.length===0){i("No API keys found");return}let r=o.map(n=>({id:n.id,name:n.name,key:n.key?L(n.key):"-",last_used_at:n.last_used_at||"Never",created_at:n.created_at}));f(r,e);}catch(t){$(t);}}async function At(e){try{let o=await D().user.createApiKey({name:e.name});l(`API key "${o.name}" created successfully`),console.log(""),e.format==="json"?f(o,"json"):(console.log("Your new API key (save this - it will not be shown again):"),console.log(""),console.log(` ${o.key}`),console.log(""),f({id:o.id,name:o.name,created_at:o.created_at},"table"));}catch(t){$(t);}}async function Ut(e){try{await D().user.deleteApiKey(e),l(`API key "${e}" deleted successfully`);}catch(t){$(t);}}function Ce(){return ke.randomBytes(32).toString("hex")}function Se(e,t){return e===t}function Rt(e){try{let t;if(e.startsWith("http://")||e.startsWith("https://"))t=new URL(e).searchParams;else {let o=e.indexOf("?");if(o===-1)return {};t=new URLSearchParams(e.slice(o+1));}return {key:t.get("key")??void 0,state:t.get("state")??void 0,error:t.get("error")??void 0,message:t.get("message")??void 0}}catch{return {}}}var Dt=`<!DOCTYPE html>
1554
226
  <html lang="en">
1555
227
  <head>
1556
228
  <meta charset="UTF-8">
@@ -1596,10 +268,7 @@ var SUCCESS_HTML = `<!DOCTYPE html>
1596
268
  <p>You can close this window and return to the terminal.</p>
1597
269
  </div>
1598
270
  </body>
1599
- </html>`;
1600
- function getErrorHtml(errorMessage) {
1601
- const escapedMessage = errorMessage.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1602
- return `<!DOCTYPE html>
271
+ </html>`;function Q(e){return `<!DOCTYPE html>
1603
272
  <html lang="en">
1604
273
  <head>
1605
274
  <meta charset="UTF-8">
@@ -1651,210 +320,10 @@ function getErrorHtml(errorMessage) {
1651
320
  <div class="icon">\u2717</div>
1652
321
  <h1>Login Failed</h1>
1653
322
  <p>Something went wrong during authentication.</p>
1654
- <div class="error-message">${escapedMessage}</div>
323
+ <div class="error-message">${e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}</div>
1655
324
  </div>
1656
325
  </body>
1657
- </html>`;
1658
- }
1659
- function createCallbackServer() {
1660
- let server = null;
1661
- let port = 0;
1662
- let expectedState = null;
1663
- let callbackResolve = null;
1664
- let timeoutId = null;
1665
- let callbackReceived = false;
1666
- function handleRequest(req, res) {
1667
- if (req.method !== "GET" || !req.url?.startsWith("/callback")) {
1668
- res.writeHead(404, { "Content-Type": "text/plain" });
1669
- res.end("Not Found");
1670
- return;
1671
- }
1672
- const params = parseCallbackParams(req.url);
1673
- if (params.error) {
1674
- const errorMessage = params.message || params.error;
1675
- res.writeHead(200, { "Content-Type": "text/html" });
1676
- res.end(getErrorHtml(errorMessage));
1677
- if (callbackResolve && !callbackReceived) {
1678
- callbackReceived = true;
1679
- callbackResolve({
1680
- success: false,
1681
- error: errorMessage
1682
- });
1683
- }
1684
- return;
1685
- }
1686
- if (!params.state || !expectedState || !verifyState(params.state, expectedState)) {
1687
- const errorMessage = "State verification failed. This may be a security issue.";
1688
- res.writeHead(400, { "Content-Type": "text/html" });
1689
- res.end(getErrorHtml(errorMessage));
1690
- if (callbackResolve && !callbackReceived) {
1691
- callbackReceived = true;
1692
- callbackResolve({
1693
- success: false,
1694
- error: errorMessage
1695
- });
1696
- }
1697
- return;
1698
- }
1699
- if (!params.key) {
1700
- const errorMessage = "No API key received in callback.";
1701
- res.writeHead(400, { "Content-Type": "text/html" });
1702
- res.end(getErrorHtml(errorMessage));
1703
- if (callbackResolve && !callbackReceived) {
1704
- callbackReceived = true;
1705
- callbackResolve({
1706
- success: false,
1707
- error: errorMessage
1708
- });
1709
- }
1710
- return;
1711
- }
1712
- res.writeHead(200, { "Content-Type": "text/html" });
1713
- res.end(SUCCESS_HTML);
1714
- if (callbackResolve && !callbackReceived) {
1715
- callbackReceived = true;
1716
- callbackResolve({
1717
- success: true,
1718
- apiKey: params.key
1719
- });
1720
- }
1721
- }
1722
- return {
1723
- async start() {
1724
- return new Promise((resolve3, reject) => {
1725
- server = http.createServer(handleRequest);
1726
- server.on("error", (err) => {
1727
- reject(err);
1728
- });
1729
- server.listen(0, "127.0.0.1", () => {
1730
- const address = server.address();
1731
- if (address && typeof address === "object") {
1732
- port = address.port;
1733
- const url = `http://127.0.0.1:${port}`;
1734
- resolve3({ port, url });
1735
- } else {
1736
- reject(new Error("Failed to get server address"));
1737
- }
1738
- });
1739
- });
1740
- },
1741
- async waitForCallback(state, timeoutMs) {
1742
- expectedState = state;
1743
- callbackReceived = false;
1744
- return new Promise((resolve3) => {
1745
- callbackResolve = resolve3;
1746
- timeoutId = setTimeout(() => {
1747
- if (!callbackReceived) {
1748
- callbackReceived = true;
1749
- resolve3({
1750
- success: false,
1751
- error: "Login timed out. Please try again."
1752
- });
1753
- }
1754
- }, timeoutMs);
1755
- });
1756
- },
1757
- async stop() {
1758
- if (timeoutId) {
1759
- clearTimeout(timeoutId);
1760
- timeoutId = null;
1761
- }
1762
- if (server) {
1763
- return new Promise((resolve3) => {
1764
- server.close(() => {
1765
- server = null;
1766
- resolve3();
1767
- });
1768
- });
1769
- }
1770
- }
1771
- };
1772
- }
1773
-
1774
- // src/commands/login.ts
1775
- var DEFAULT_TIMEOUT_SECONDS = 120;
1776
- var DEFAULT_DASHBOARD_URL = "https://app.spike.ac";
1777
- function getDashboardUrl() {
1778
- return process.env.SPIKE_DASHBOARD_URL || DEFAULT_DASHBOARD_URL;
1779
- }
1780
- function buildAuthorizationUrl(state, callbackPort) {
1781
- const dashboardUrl = getDashboardUrl();
1782
- const callbackUrl = `http://127.0.0.1:${callbackPort}/callback`;
1783
- const url = new URL("/cli/authorize", dashboardUrl);
1784
- url.searchParams.set("state", state);
1785
- url.searchParams.set("callback_url", callbackUrl);
1786
- return url.toString();
1787
- }
1788
- function createLoginCommand() {
1789
- const loginCommand = new Command("login").description("Authenticate the CLI via browser").option(
1790
- "-t, --timeout <seconds>",
1791
- "Timeout for the login flow in seconds",
1792
- String(DEFAULT_TIMEOUT_SECONDS)
1793
- ).option(
1794
- "--no-browser",
1795
- "Skip automatic browser opening and display URL only"
1796
- ).action(async (options) => {
1797
- await handleLogin(options);
1798
- });
1799
- return loginCommand;
1800
- }
1801
- async function handleLogin(options) {
1802
- const timeoutSeconds = parseInt(options.timeout, 10);
1803
- if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
1804
- error("Invalid timeout value. Please provide a positive number of seconds.");
1805
- process.exit(1);
1806
- }
1807
- const timeoutMs = timeoutSeconds * 1e3;
1808
- info("Starting login flow...");
1809
- const state = generateState();
1810
- const callbackServer = createCallbackServer();
1811
- let serverInfo;
1812
- try {
1813
- serverInfo = await callbackServer.start();
1814
- } catch (err) {
1815
- error(`Failed to start callback server: ${err instanceof Error ? err.message : String(err)}`);
1816
- info("You can manually configure your API key with: spike config set api-key <your-key>");
1817
- process.exit(1);
1818
- }
1819
- const authUrl = buildAuthorizationUrl(state, serverInfo.port);
1820
- if (options.browser) {
1821
- info("Opening browser for authentication...");
1822
- const browserOpened = await openBrowser(authUrl);
1823
- if (!browserOpened) {
1824
- info("Could not open browser automatically.");
1825
- console.log("");
1826
- info("Please open the following URL in your browser:");
1827
- console.log("");
1828
- console.log(` ${authUrl}`);
1829
- console.log("");
1830
- }
1831
- } else {
1832
- info("Please open the following URL in your browser:");
1833
- console.log("");
1834
- console.log(` ${authUrl}`);
1835
- console.log("");
1836
- }
1837
- info(`Waiting for authentication (timeout: ${timeoutSeconds}s)...`);
1838
- try {
1839
- const result = await callbackServer.waitForCallback(state, timeoutMs);
1840
- if (result.success && result.apiKey) {
1841
- saveConfig({ apiKey: result.apiKey });
1842
- console.log("");
1843
- success("Login successful! API key has been saved.");
1844
- info("You can now use the CLI to manage your forms and submissions.");
1845
- } else {
1846
- console.log("");
1847
- error(result.error || "Login failed. Please try again.");
1848
- process.exit(1);
1849
- }
1850
- } catch (err) {
1851
- error(`Login error: ${err instanceof Error ? err.message : String(err)}`);
1852
- process.exit(1);
1853
- } finally {
1854
- await callbackServer.stop();
1855
- }
1856
- }
1857
- var SKILL_FILE_CONTENT = `# Spike Forms CLI Skill
326
+ </html>`}function Pe(){let e=null,t=0,o=null,r=null,n=null,s=false;function m(a,d){if(a.method!=="GET"||!a.url?.startsWith("/callback")){d.writeHead(404,{"Content-Type":"text/plain"}),d.end("Not Found");return}let p=Rt(a.url);if(p.error){let h=p.message||p.error;d.writeHead(200,{"Content-Type":"text/html"}),d.end(Q(h)),r&&!s&&(s=true,r({success:false,error:h}));return}if(!p.state||!o||!Se(p.state,o)){let h="State verification failed. This may be a security issue.";d.writeHead(400,{"Content-Type":"text/html"}),d.end(Q(h)),r&&!s&&(s=true,r({success:false,error:h}));return}if(!p.key){let h="No API key received in callback.";d.writeHead(400,{"Content-Type":"text/html"}),d.end(Q(h)),r&&!s&&(s=true,r({success:false,error:h}));return}d.writeHead(200,{"Content-Type":"text/html"}),d.end(Dt),r&&!s&&(s=true,r({success:true,apiKey:p.key}));}return {async start(){return new Promise((a,d)=>{e=ce.createServer(m),e.on("error",p=>{d(p);}),e.listen(0,"127.0.0.1",()=>{let p=e.address();if(p&&typeof p=="object"){t=p.port;let h=`http://127.0.0.1:${t}`;a({port:t,url:h});}else d(new Error("Failed to get server address"));});})},async waitForCallback(a,d){return o=a,s=false,new Promise(p=>{r=p,n=setTimeout(()=>{s||(s=true,p({success:false,error:"Login timed out. Please try again."}));},d);})},async stop(){if(n&&(clearTimeout(n),n=null),e)return new Promise(a=>{e.close(()=>{e=null,a();});})}}}var Mt=120,Kt="https://app.spike.ac";function Nt(){return process.env.SPIKE_DASHBOARD_URL||Kt}function Ht(e,t){let o=Nt(),r=`http://127.0.0.1:${t}/callback`,n=new URL("/consent/authorize",o);return n.searchParams.set("state",e),n.searchParams.set("callback_url",r),n.toString()}function je(){return new Command("login").description("Authenticate the CLI via browser").option("-t, --timeout <seconds>","Timeout for the login flow in seconds",String(Mt)).option("--no-browser","Skip automatic browser opening and display URL only").action(async t=>{await Gt(t);})}async function Gt(e){let t=parseInt(e.timeout,10);(isNaN(t)||t<=0)&&(c("Invalid timeout value. Please provide a positive number of seconds."),process.exit(1));let o=t*1e3;i("Starting login flow...");let r=Ce(),n=Pe(),s;try{s=await n.start();}catch(a){c(`Failed to start callback server: ${a instanceof Error?a.message:String(a)}`),i("You can manually configure your API key with: spike config set api-key <your-key>"),process.exit(1);}let m=Ht(r,s.port);e.browser?(i("Opening browser for authentication..."),await T(m)||(i("Could not open browser automatically."),console.log(""),i("Please open the following URL in your browser:"),console.log(""),console.log(` ${m}`),console.log(""))):(i("Please open the following URL in your browser:"),console.log(""),console.log(` ${m}`),console.log("")),i(`Waiting for authentication (timeout: ${t}s)...`);try{let a=await n.waitForCallback(r,o);a.success&&a.apiKey?(K({apiKey:a.apiKey}),console.log(""),l("Login successful! API key has been saved."),i("You can now use the CLI to manage your forms and submissions.")):(console.log(""),c(a.error||"Login failed. Please try again."),process.exit(1));}catch(a){c(`Login error: ${a instanceof Error?a.message:String(a)}`),process.exit(1);}finally{await n.stop();}}var Vt=`# Spike Forms CLI Skill
1858
327
 
1859
328
  This skill provides knowledge about the Spike Forms CLI (\`spike\`) commands for managing forms, submissions, projects, teams, and configuration.
1860
329
 
@@ -2151,137 +620,4 @@ spike <command> --help
2151
620
  - \`SPIKE_API_KEY\` or \`SPIKE_TOKEN\`: API key for authentication
2152
621
  - \`SPIKE_API_URL\`: Custom API base URL
2153
622
  - \`SPIKE_DASHBOARD_URL\`: Custom dashboard URL for login flow
2154
- `;
2155
- function getSkillsDir() {
2156
- const homeDir = os2.homedir();
2157
- return path3.join(homeDir, ".config", "opencode", "skills", "spike");
2158
- }
2159
- function getSkillFilePath() {
2160
- return path3.join(getSkillsDir(), "SKILL.md");
2161
- }
2162
- function isOpenCodeInstalled() {
2163
- try {
2164
- const command = process.platform === "win32" ? "where opencode" : "which opencode";
2165
- execSync(command, { stdio: "ignore" });
2166
- return true;
2167
- } catch {
2168
- return false;
2169
- }
2170
- }
2171
- function ensureSkillInstalled() {
2172
- const skillsDir = getSkillsDir();
2173
- const skillFilePath = getSkillFilePath();
2174
- if (!fs3.existsSync(skillsDir)) {
2175
- fs3.mkdirSync(skillsDir, { recursive: true });
2176
- }
2177
- fs3.writeFileSync(skillFilePath, SKILL_FILE_CONTENT, "utf-8");
2178
- }
2179
- function installOpenCode() {
2180
- try {
2181
- info("Installing OpenCode via npm...");
2182
- execSync("npm install -g opencode", { stdio: "inherit" });
2183
- return true;
2184
- } catch {
2185
- return false;
2186
- }
2187
- }
2188
- function createAgentCommand() {
2189
- const agentCommand = new Command("agent").description("Launch OpenCode AI agent with Spike Forms knowledge").option("--install", "Install OpenCode via npm if not already installed").option("--model <model>", "Model to use with OpenCode").action(async (options) => {
2190
- await handleAgent(options);
2191
- });
2192
- return agentCommand;
2193
- }
2194
- async function handleAgent(options) {
2195
- if (!isOpenCodeInstalled()) {
2196
- if (options.install) {
2197
- const installed = installOpenCode();
2198
- if (!installed) {
2199
- error("Failed to install OpenCode via npm.");
2200
- info("Please install OpenCode manually:");
2201
- console.log("");
2202
- console.log(" npm install -g opencode");
2203
- console.log("");
2204
- info("Or visit: https://opencode.ai for more installation options.");
2205
- process.exit(1);
2206
- }
2207
- success("OpenCode installed successfully");
2208
- } else {
2209
- error("OpenCode is not installed.");
2210
- info("Install OpenCode using one of these methods:");
2211
- console.log("");
2212
- console.log(" # Install via npm");
2213
- console.log(" npm install -g opencode");
2214
- console.log("");
2215
- console.log(" # Or use the --install flag");
2216
- console.log(" spike agent --install");
2217
- console.log("");
2218
- info("Or visit: https://opencode.ai for more installation options.");
2219
- process.exit(1);
2220
- }
2221
- }
2222
- try {
2223
- ensureSkillInstalled();
2224
- info("Spike Forms skill installed for OpenCode");
2225
- } catch (err) {
2226
- warn(`Could not install skill file: ${err instanceof Error ? err.message : String(err)}`);
2227
- }
2228
- const args = [];
2229
- if (options.model) {
2230
- args.push("--model", options.model);
2231
- }
2232
- info("Launching OpenCode...");
2233
- console.log("");
2234
- const opencode = spawn("opencode", args, {
2235
- stdio: "inherit"
2236
- });
2237
- opencode.on("close", (code) => {
2238
- process.exit(code ?? 0);
2239
- });
2240
- opencode.on("error", (err) => {
2241
- error(`Failed to launch OpenCode: ${err.message}`);
2242
- process.exit(1);
2243
- });
2244
- }
2245
-
2246
- // src/index.ts
2247
- var program = new Command();
2248
- program.name("spike").description("Command-line interface for the Spike Forms API").version("0.1.0");
2249
- program.option(
2250
- "-f, --format <format>",
2251
- "Output format (json, table)",
2252
- "table"
2253
- );
2254
- program.addCommand(createConfigCommand());
2255
- program.addCommand(createFormsCommand());
2256
- program.addCommand(createSubmissionsCommand());
2257
- program.addCommand(createProjectsCommand());
2258
- program.addCommand(createTeamsCommand());
2259
- program.addCommand(createUserCommand());
2260
- program.addCommand(createLoginCommand());
2261
- program.addCommand(createAgentCommand());
2262
- async function main() {
2263
- try {
2264
- await program.parseAsync(process.argv);
2265
- } catch (err) {
2266
- handleFatalError(err);
2267
- }
2268
- }
2269
- function handleFatalError(err) {
2270
- if (err instanceof Error) {
2271
- error(err.message);
2272
- } else {
2273
- error("An unexpected error occurred");
2274
- }
2275
- process.exitCode = 1;
2276
- }
2277
- process.on("uncaughtException", (err) => {
2278
- handleFatalError(err);
2279
- process.exit(1);
2280
- });
2281
- process.on("unhandledRejection", (reason) => {
2282
- handleFatalError(reason instanceof Error ? reason : new Error(String(reason)));
2283
- process.exit(1);
2284
- });
2285
- main();
2286
- //# sourceMappingURL=index.js.map
2287
- //# sourceMappingURL=index.js.map
623
+ `;function Oe(){let e=G.homedir();return P.join(e,".config","opencode","skills","spike")}function Wt(){return P.join(Oe(),"SKILL.md")}function zt(){try{let e=process.platform==="win32"?"where opencode":"which opencode";return execSync(e,{stdio:"ignore"}),!0}catch{return false}}function Yt(){let e=Oe(),t=Wt();u.existsSync(e)||u.mkdirSync(e,{recursive:true}),u.writeFileSync(t,Vt,"utf-8");}function Jt(){try{return i("Installing OpenCode via npm..."),execSync("npm install -g opencode",{stdio:"inherit"}),!0}catch{return false}}function Le(){return new Command("agent").description("Launch OpenCode AI agent with Spike Forms knowledge").option("--install","Install OpenCode via npm if not already installed").option("--model <model>","Model to use with OpenCode").action(async t=>{await Zt(t);})}async function Zt(e){zt()||(e.install?(Jt()||(c("Failed to install OpenCode via npm."),i("Please install OpenCode manually:"),console.log(""),console.log(" npm install -g opencode"),console.log(""),i("Or visit: https://opencode.ai for more installation options."),process.exit(1)),l("OpenCode installed successfully")):(c("OpenCode is not installed."),i("Install OpenCode using one of these methods:"),console.log(""),console.log(" # Install via npm"),console.log(" npm install -g opencode"),console.log(""),console.log(" # Or use the --install flag"),console.log(" spike agent --install"),console.log(""),i("Or visit: https://opencode.ai for more installation options."),process.exit(1)));try{Yt(),i("Spike Forms skill installed for OpenCode");}catch(r){re(`Could not install skill file: ${r instanceof Error?r.message:String(r)}`);}let t=[];e.model&&t.push("--model",e.model),i("Launching OpenCode..."),console.log("");let o=spawn("opencode",t,{stdio:"inherit"});o.on("close",r=>{process.exit(r??0);}),o.on("error",r=>{c(`Failed to launch OpenCode: ${r.message}`),process.exit(1);});}var w=new Command;w.name("spike").description("Command-line interface for the Spike Forms API").version("0.1.0");w.option("-f, --format <format>","Output format (json, table)","table");w.addCommand(se());w.addCommand(pe());w.addCommand(he());w.addCommand(ye());w.addCommand(be());w.addCommand(we());w.addCommand(je());w.addCommand(Le());async function Xt(){try{await w.parseAsync(process.argv);}catch(e){ee(e);}}function ee(e){e instanceof Error?c(e.message):c("An unexpected error occurred"),process.exitCode=1;}process.on("uncaughtException",e=>{ee(e),process.exit(1);});process.on("unhandledRejection",e=>{ee(e instanceof Error?e:new Error(String(e))),process.exit(1);});Xt();