@agi-ventures-canada/hackathon-cli 0.1.3 → 0.1.4

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.
@@ -7,7 +7,7 @@ const CONFIG_DIR = join(homedir(), ".hackathon");
7
7
  const CONFIG_FILE = join(CONFIG_DIR, "config.json");
8
8
  const REQUEST_TIMEOUT_MS = 3e4;
9
9
  const POLL_INTERVAL_MS = 2e3;
10
- const AUTH_TIMEOUT_MS = 3e5;
10
+ const AUTH_TIMEOUT_MS = 6e5;
11
11
  //#endregion
12
12
  export { POLL_INTERVAL_MS as a, DEFAULT_BASE_URL as i, CONFIG_DIR as n, REQUEST_TIMEOUT_MS as o, CONFIG_FILE as r, VERSION as s, AUTH_TIMEOUT_MS as t };
13
13
 
@@ -1 +1 @@
1
- {"version":3,"file":"constants.mjs","names":[],"sources":["../../src/constants.ts"],"sourcesContent":["import { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nexport const VERSION = \"0.1.0\"\nexport const DEFAULT_BASE_URL = \"https://getoatmeal.com\"\nexport const CONFIG_DIR = join(homedir(), \".hackathon\")\nexport const CONFIG_FILE = join(CONFIG_DIR, \"config.json\")\nexport const REQUEST_TIMEOUT_MS = 30_000\nexport const POLL_INTERVAL_MS = 2_000\nexport const AUTH_TIMEOUT_MS = 300_000\n"],"mappings":";;;AAGA,MAAa,UAAU;AACvB,MAAa,mBAAmB;AAChC,MAAa,aAAa,KAAK,SAAS,EAAE,aAAa;AACvD,MAAa,cAAc,KAAK,YAAY,cAAc;AAC1D,MAAa,qBAAqB;AAClC,MAAa,mBAAmB;AAChC,MAAa,kBAAkB"}
1
+ {"version":3,"file":"constants.mjs","names":[],"sources":["../../src/constants.ts"],"sourcesContent":["import { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nexport const VERSION = \"0.1.0\"\nexport const DEFAULT_BASE_URL = \"https://getoatmeal.com\"\nexport const CONFIG_DIR = join(homedir(), \".hackathon\")\nexport const CONFIG_FILE = join(CONFIG_DIR, \"config.json\")\nexport const REQUEST_TIMEOUT_MS = 30_000\nexport const POLL_INTERVAL_MS = 2_000\nexport const AUTH_TIMEOUT_MS = 600_000\n"],"mappings":";;;AAGA,MAAa,UAAU;AACvB,MAAa,mBAAmB;AAChC,MAAa,aAAa,KAAK,SAAS,EAAE,aAAa;AACvD,MAAa,cAAc,KAAK,YAAY,cAAc;AAC1D,MAAa,qBAAqB;AAClC,MAAa,mBAAmB;AAChC,MAAa,kBAAkB"}
@@ -13,6 +13,9 @@ function parseCreateOptions(args) {
13
13
  case "--description":
14
14
  options.description = args[++i];
15
15
  break;
16
+ case "--from-url":
17
+ options.fromUrl = args[++i];
18
+ break;
16
19
  case "--json":
17
20
  options.json = true;
18
21
  break;
@@ -24,6 +27,19 @@ async function runHackathonsCreate(client, args) {
24
27
  let name = options.name;
25
28
  let slug = options.slug;
26
29
  let description = options.description;
30
+ if (options.fromUrl) {
31
+ const hackathon = await client.post("/api/dashboard/import/luma-url", {
32
+ url: options.fromUrl,
33
+ name,
34
+ description
35
+ });
36
+ if (options.json) {
37
+ console.log(formatJson(hackathon));
38
+ return;
39
+ }
40
+ console.log(formatSuccess(`Imported hackathon "${hackathon.name}" (${hackathon.id})`));
41
+ return;
42
+ }
27
43
  if (!name && process.stdout.isTTY) {
28
44
  const result = await p.text({
29
45
  message: "Hackathon name:",
@@ -1 +1 @@
1
- {"version":3,"file":"create.mjs","names":[],"sources":["../../src/commands/hackathons/create.ts"],"sourcesContent":["import * as p from \"@clack/prompts\"\nimport type { OatmealClient } from \"../../client.js\"\nimport { formatJson, formatSuccess } from \"../../output.js\"\nimport type { Hackathon } from \"../../types.js\"\n\ninterface CreateOptions {\n name?: string\n slug?: string\n description?: string\n json?: boolean\n}\n\nexport function parseCreateOptions(args: string[]): CreateOptions {\n const options: CreateOptions = {}\n for (let i = 0; i < args.length; i++) {\n switch (args[i]) {\n case \"--name\":\n options.name = args[++i]\n break\n case \"--slug\":\n options.slug = args[++i]\n break\n case \"--description\":\n options.description = args[++i]\n break\n case \"--json\":\n options.json = true\n break\n }\n }\n return options\n}\n\nexport async function runHackathonsCreate(\n client: OatmealClient,\n args: string[]\n): Promise<void> {\n const options = parseCreateOptions(args)\n\n let name = options.name\n let slug = options.slug\n let description = options.description\n\n if (!name && process.stdout.isTTY) {\n const result = await p.text({ message: \"Hackathon name:\", validate: (v: string) => (v ? undefined : \"Name is required\") })\n if (p.isCancel(result)) return\n name = result\n }\n\n if (!name) {\n console.error(\"Error: --name is required\")\n process.exit(1)\n }\n\n if (!slug && process.stdout.isTTY) {\n const suggested = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\")\n const result = await p.text({ message: \"Slug:\", initialValue: suggested })\n if (p.isCancel(result)) return\n slug = result\n }\n\n if (!description && process.stdout.isTTY) {\n const result = await p.text({ message: \"Description (optional):\" })\n if (!p.isCancel(result)) description = result || undefined\n }\n\n const hackathon = await client.post<Hackathon>(\"/api/dashboard/hackathons\", {\n name,\n slug,\n description,\n })\n\n if (options.json) {\n console.log(formatJson(hackathon))\n return\n }\n\n console.log(formatSuccess(`Created hackathon \"${hackathon.name}\" (${hackathon.id})`))\n}\n"],"mappings":";;;AAYA,SAAgB,mBAAmB,MAA+B;CAChE,MAAM,UAAyB,EAAE;AACjC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAC/B,SAAQ,KAAK,IAAb;EACE,KAAK;AACH,WAAQ,OAAO,KAAK,EAAE;AACtB;EACF,KAAK;AACH,WAAQ,OAAO,KAAK,EAAE;AACtB;EACF,KAAK;AACH,WAAQ,cAAc,KAAK,EAAE;AAC7B;EACF,KAAK;AACH,WAAQ,OAAO;AACf;;AAGN,QAAO;;AAGT,eAAsB,oBACpB,QACA,MACe;CACf,MAAM,UAAU,mBAAmB,KAAK;CAExC,IAAI,OAAO,QAAQ;CACnB,IAAI,OAAO,QAAQ;CACnB,IAAI,cAAc,QAAQ;AAE1B,KAAI,CAAC,QAAQ,QAAQ,OAAO,OAAO;EACjC,MAAM,SAAS,MAAM,EAAE,KAAK;GAAE,SAAS;GAAmB,WAAW,MAAe,IAAI,KAAA,IAAY;GAAqB,CAAC;AAC1H,MAAI,EAAE,SAAS,OAAO,CAAE;AACxB,SAAO;;AAGT,KAAI,CAAC,MAAM;AACT,UAAQ,MAAM,4BAA4B;AAC1C,UAAQ,KAAK,EAAE;;AAGjB,KAAI,CAAC,QAAQ,QAAQ,OAAO,OAAO;EACjC,MAAM,YAAY,KAAK,aAAa,CAAC,QAAQ,eAAe,IAAI,CAAC,QAAQ,UAAU,GAAG;EACtF,MAAM,SAAS,MAAM,EAAE,KAAK;GAAE,SAAS;GAAS,cAAc;GAAW,CAAC;AAC1E,MAAI,EAAE,SAAS,OAAO,CAAE;AACxB,SAAO;;AAGT,KAAI,CAAC,eAAe,QAAQ,OAAO,OAAO;EACxC,MAAM,SAAS,MAAM,EAAE,KAAK,EAAE,SAAS,2BAA2B,CAAC;AACnE,MAAI,CAAC,EAAE,SAAS,OAAO,CAAE,eAAc,UAAU,KAAA;;CAGnD,MAAM,YAAY,MAAM,OAAO,KAAgB,6BAA6B;EAC1E;EACA;EACA;EACD,CAAC;AAEF,KAAI,QAAQ,MAAM;AAChB,UAAQ,IAAI,WAAW,UAAU,CAAC;AAClC;;AAGF,SAAQ,IAAI,cAAc,sBAAsB,UAAU,KAAK,KAAK,UAAU,GAAG,GAAG,CAAC"}
1
+ {"version":3,"file":"create.mjs","names":[],"sources":["../../src/commands/hackathons/create.ts"],"sourcesContent":["import * as p from \"@clack/prompts\"\nimport type { OatmealClient } from \"../../client.js\"\nimport { formatJson, formatSuccess } from \"../../output.js\"\nimport type { Hackathon } from \"../../types.js\"\n\ninterface CreateOptions {\n name?: string\n slug?: string\n description?: string\n fromUrl?: string\n json?: boolean\n}\n\ninterface ImportedHackathonResponse {\n id: string\n name: string\n slug: string\n}\n\nexport function parseCreateOptions(args: string[]): CreateOptions {\n const options: CreateOptions = {}\n for (let i = 0; i < args.length; i++) {\n switch (args[i]) {\n case \"--name\":\n options.name = args[++i]\n break\n case \"--slug\":\n options.slug = args[++i]\n break\n case \"--description\":\n options.description = args[++i]\n break\n case \"--from-url\":\n options.fromUrl = args[++i]\n break\n case \"--json\":\n options.json = true\n break\n }\n }\n return options\n}\n\nexport async function runHackathonsCreate(\n client: OatmealClient,\n args: string[]\n): Promise<void> {\n const options = parseCreateOptions(args)\n\n let name = options.name\n let slug = options.slug\n let description = options.description\n\n if (options.fromUrl) {\n const hackathon = await client.post<ImportedHackathonResponse>(\"/api/dashboard/import/luma-url\", {\n url: options.fromUrl,\n name,\n description,\n })\n\n if (options.json) {\n console.log(formatJson(hackathon))\n return\n }\n\n console.log(formatSuccess(`Imported hackathon \"${hackathon.name}\" (${hackathon.id})`))\n return\n }\n\n if (!name && process.stdout.isTTY) {\n const result = await p.text({ message: \"Hackathon name:\", validate: (v: string) => (v ? undefined : \"Name is required\") })\n if (p.isCancel(result)) return\n name = result\n }\n\n if (!name) {\n console.error(\"Error: --name is required\")\n process.exit(1)\n }\n\n if (!slug && process.stdout.isTTY) {\n const suggested = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\")\n const result = await p.text({ message: \"Slug:\", initialValue: suggested })\n if (p.isCancel(result)) return\n slug = result\n }\n\n if (!description && process.stdout.isTTY) {\n const result = await p.text({ message: \"Description (optional):\" })\n if (!p.isCancel(result)) description = result || undefined\n }\n\n const hackathon = await client.post<Hackathon>(\"/api/dashboard/hackathons\", {\n name,\n slug,\n description,\n })\n\n if (options.json) {\n console.log(formatJson(hackathon))\n return\n }\n\n console.log(formatSuccess(`Created hackathon \"${hackathon.name}\" (${hackathon.id})`))\n}\n"],"mappings":";;;AAmBA,SAAgB,mBAAmB,MAA+B;CAChE,MAAM,UAAyB,EAAE;AACjC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAC/B,SAAQ,KAAK,IAAb;EACE,KAAK;AACH,WAAQ,OAAO,KAAK,EAAE;AACtB;EACF,KAAK;AACH,WAAQ,OAAO,KAAK,EAAE;AACtB;EACF,KAAK;AACH,WAAQ,cAAc,KAAK,EAAE;AAC7B;EACF,KAAK;AACH,WAAQ,UAAU,KAAK,EAAE;AACzB;EACF,KAAK;AACH,WAAQ,OAAO;AACf;;AAGN,QAAO;;AAGT,eAAsB,oBACpB,QACA,MACe;CACf,MAAM,UAAU,mBAAmB,KAAK;CAExC,IAAI,OAAO,QAAQ;CACnB,IAAI,OAAO,QAAQ;CACnB,IAAI,cAAc,QAAQ;AAE1B,KAAI,QAAQ,SAAS;EACnB,MAAM,YAAY,MAAM,OAAO,KAAgC,kCAAkC;GAC/F,KAAK,QAAQ;GACb;GACA;GACD,CAAC;AAEF,MAAI,QAAQ,MAAM;AAChB,WAAQ,IAAI,WAAW,UAAU,CAAC;AAClC;;AAGF,UAAQ,IAAI,cAAc,uBAAuB,UAAU,KAAK,KAAK,UAAU,GAAG,GAAG,CAAC;AACtF;;AAGF,KAAI,CAAC,QAAQ,QAAQ,OAAO,OAAO;EACjC,MAAM,SAAS,MAAM,EAAE,KAAK;GAAE,SAAS;GAAmB,WAAW,MAAe,IAAI,KAAA,IAAY;GAAqB,CAAC;AAC1H,MAAI,EAAE,SAAS,OAAO,CAAE;AACxB,SAAO;;AAGT,KAAI,CAAC,MAAM;AACT,UAAQ,MAAM,4BAA4B;AAC1C,UAAQ,KAAK,EAAE;;AAGjB,KAAI,CAAC,QAAQ,QAAQ,OAAO,OAAO;EACjC,MAAM,YAAY,KAAK,aAAa,CAAC,QAAQ,eAAe,IAAI,CAAC,QAAQ,UAAU,GAAG;EACtF,MAAM,SAAS,MAAM,EAAE,KAAK;GAAE,SAAS;GAAS,cAAc;GAAW,CAAC;AAC1E,MAAI,EAAE,SAAS,OAAO,CAAE;AACxB,SAAO;;AAGT,KAAI,CAAC,eAAe,QAAQ,OAAO,OAAO;EACxC,MAAM,SAAS,MAAM,EAAE,KAAK,EAAE,SAAS,2BAA2B,CAAC;AACnE,MAAI,CAAC,EAAE,SAAS,OAAO,CAAE,eAAc,UAAU,KAAA;;CAGnD,MAAM,YAAY,MAAM,OAAO,KAAgB,6BAA6B;EAC1E;EACA;EACA;EACD,CAAC;AAEF,KAAI,QAAQ,MAAM;AAChB,UAAQ,IAAI,WAAW,UAAU,CAAC;AAClC;;AAGF,SAAQ,IAAI,cAAc,sBAAsB,UAAU,KAAK,KAAK,UAAU,GAAG,GAAG,CAAC"}
@@ -85,7 +85,7 @@ async function pollForKey(baseUrl, deviceToken) {
85
85
  if (result.status === "expired") throw new Error("Authentication session expired. Please try again.");
86
86
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
87
87
  }
88
- throw new Error("Authentication timed out after 5 minutes. Please try again.");
88
+ throw new Error("Authentication timed out after 10 minutes. Please try again.");
89
89
  }
90
90
  async function validateAndSaveKey(apiKey, baseUrl) {
91
91
  const client = new OatmealClient({
@@ -1 +1 @@
1
- {"version":3,"file":"login.mjs","names":[],"sources":["../../src/commands/login.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\"\nimport { execSync } from \"node:child_process\"\nimport * as p from \"@clack/prompts\"\nimport { OatmealClient } from \"../client.js\"\nimport { loadConfig, saveConfig } from \"../config.js\"\nimport { AUTH_TIMEOUT_MS, DEFAULT_BASE_URL, POLL_INTERVAL_MS } from \"../constants.js\"\nimport type { CliConfig, WhoAmIResponse } from \"../types.js\"\n\ninterface LoginOptions {\n apiKey?: string\n noBrowser?: boolean\n baseUrl?: string\n yes?: boolean\n}\n\nexport function parseLoginOptions(args: string[]): LoginOptions {\n const options: LoginOptions = {}\n for (let i = 0; i < args.length; i++) {\n switch (args[i]) {\n case \"--api-key\":\n options.apiKey = args[++i]\n break\n case \"--no-browser\":\n options.noBrowser = true\n break\n case \"--base-url\":\n options.baseUrl = args[++i]\n break\n case \"--yes\":\n case \"-y\":\n options.yes = true\n break\n }\n }\n return options\n}\n\nexport async function runLogin(args: string[]): Promise<void> {\n const options = parseLoginOptions(args)\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL\n\n const existingConfig = loadConfig()\n if (existingConfig && !options.yes && !options.apiKey && !process.env.HACKATHON_API_KEY) {\n const overwrite = await p.confirm({\n message: \"You are already logged in. Overwrite existing config?\",\n })\n if (p.isCancel(overwrite) || !overwrite) {\n p.log.info(\"Login cancelled.\")\n return\n }\n }\n\n const key = options.apiKey ?? process.env.HACKATHON_API_KEY\n if (key) {\n await validateAndSaveKey(key, baseUrl)\n return\n }\n\n if (options.noBrowser || !process.stdout.isTTY) {\n const pastedKey = await p.password({ message: \"Paste your API key:\" })\n if (p.isCancel(pastedKey)) {\n p.log.info(\"Login cancelled.\")\n return\n }\n await validateAndSaveKey(pastedKey, baseUrl)\n return\n }\n\n const deviceToken = randomBytes(32).toString(\"hex\")\n const authUrl = `${baseUrl}/cli-auth?token=${deviceToken}`\n\n const initClient = new OatmealClient({ baseUrl })\n try {\n await initClient.get(\"/api/public/cli-auth/poll\", { params: { token: deviceToken } })\n } catch {\n // Session creation failed — continue anyway, poll loop will retry\n }\n\n p.log.info(`Opening browser to sign in...`)\n p.log.info(authUrl)\n\n try {\n openBrowser(authUrl)\n } catch {\n p.log.warn(\"Could not open browser. Visit the URL above manually.\")\n }\n\n const spinner = p.spinner()\n spinner.start(\"Waiting for authentication...\")\n\n try {\n const apiKey = await pollForKey(baseUrl, deviceToken)\n spinner.stop(\"Authenticated!\")\n await validateAndSaveKey(apiKey, baseUrl)\n } catch (error) {\n spinner.stop(\"Authentication failed.\")\n throw error\n }\n}\n\nfunction openBrowser(url: string): void {\n const platform = process.platform\n const cmd =\n platform === \"darwin\"\n ? \"open\"\n : platform === \"win32\"\n ? \"start\"\n : \"xdg-open\"\n execSync(`${cmd} \"${url}\"`, { stdio: \"ignore\" })\n}\n\nasync function pollForKey(baseUrl: string, deviceToken: string): Promise<string> {\n const client = new OatmealClient({ baseUrl })\n const start = Date.now()\n\n while (Date.now() - start < AUTH_TIMEOUT_MS) {\n const result = await client.get<{ status: string; apiKey?: string }>(\n \"/api/public/cli-auth/poll\",\n { params: { token: deviceToken } }\n )\n\n if (result.status === \"complete\" && result.apiKey) {\n return result.apiKey\n }\n\n if (result.status === \"expired\") {\n throw new Error(\"Authentication session expired. Please try again.\")\n }\n\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))\n }\n\n throw new Error(\"Authentication timed out after 5 minutes. Please try again.\")\n}\n\nasync function validateAndSaveKey(apiKey: string, baseUrl: string): Promise<void> {\n const client = new OatmealClient({ baseUrl, apiKey })\n\n const spinner = p.spinner()\n spinner.start(\"Validating API key...\")\n\n try {\n const whoami = await client.get<WhoAmIResponse>(\"/api/v1/whoami\")\n spinner.stop(\"Key validated!\")\n\n const config: CliConfig = {\n apiKey,\n baseUrl,\n tenantId: whoami.tenantId,\n keyId: whoami.keyId,\n scopes: whoami.scopes,\n }\n\n saveConfig(config)\n\n p.log.success(`Logged in! Key saved to ~/.hackathon/config.json`)\n p.log.info(`Tenant: ${whoami.tenantId}`)\n p.log.info(`Scopes: ${whoami.scopes.join(\", \")}`)\n } catch (error) {\n spinner.stop(\"Validation failed.\")\n throw error\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"login.mjs","names":[],"sources":["../../src/commands/login.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\"\nimport { execSync } from \"node:child_process\"\nimport * as p from \"@clack/prompts\"\nimport { OatmealClient } from \"../client.js\"\nimport { loadConfig, saveConfig } from \"../config.js\"\nimport { AUTH_TIMEOUT_MS, DEFAULT_BASE_URL, POLL_INTERVAL_MS } from \"../constants.js\"\nimport type { CliConfig, WhoAmIResponse } from \"../types.js\"\n\ninterface LoginOptions {\n apiKey?: string\n noBrowser?: boolean\n baseUrl?: string\n yes?: boolean\n}\n\nexport function parseLoginOptions(args: string[]): LoginOptions {\n const options: LoginOptions = {}\n for (let i = 0; i < args.length; i++) {\n switch (args[i]) {\n case \"--api-key\":\n options.apiKey = args[++i]\n break\n case \"--no-browser\":\n options.noBrowser = true\n break\n case \"--base-url\":\n options.baseUrl = args[++i]\n break\n case \"--yes\":\n case \"-y\":\n options.yes = true\n break\n }\n }\n return options\n}\n\nexport async function runLogin(args: string[]): Promise<void> {\n const options = parseLoginOptions(args)\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL\n\n const existingConfig = loadConfig()\n if (existingConfig && !options.yes && !options.apiKey && !process.env.HACKATHON_API_KEY) {\n const overwrite = await p.confirm({\n message: \"You are already logged in. Overwrite existing config?\",\n })\n if (p.isCancel(overwrite) || !overwrite) {\n p.log.info(\"Login cancelled.\")\n return\n }\n }\n\n const key = options.apiKey ?? process.env.HACKATHON_API_KEY\n if (key) {\n await validateAndSaveKey(key, baseUrl)\n return\n }\n\n if (options.noBrowser || !process.stdout.isTTY) {\n const pastedKey = await p.password({ message: \"Paste your API key:\" })\n if (p.isCancel(pastedKey)) {\n p.log.info(\"Login cancelled.\")\n return\n }\n await validateAndSaveKey(pastedKey, baseUrl)\n return\n }\n\n const deviceToken = randomBytes(32).toString(\"hex\")\n const authUrl = `${baseUrl}/cli-auth?token=${deviceToken}`\n\n const initClient = new OatmealClient({ baseUrl })\n try {\n await initClient.get(\"/api/public/cli-auth/poll\", { params: { token: deviceToken } })\n } catch {\n // Session creation failed — continue anyway, poll loop will retry\n }\n\n p.log.info(`Opening browser to sign in...`)\n p.log.info(authUrl)\n\n try {\n openBrowser(authUrl)\n } catch {\n p.log.warn(\"Could not open browser. Visit the URL above manually.\")\n }\n\n const spinner = p.spinner()\n spinner.start(\"Waiting for authentication...\")\n\n try {\n const apiKey = await pollForKey(baseUrl, deviceToken)\n spinner.stop(\"Authenticated!\")\n await validateAndSaveKey(apiKey, baseUrl)\n } catch (error) {\n spinner.stop(\"Authentication failed.\")\n throw error\n }\n}\n\nfunction openBrowser(url: string): void {\n const platform = process.platform\n const cmd =\n platform === \"darwin\"\n ? \"open\"\n : platform === \"win32\"\n ? \"start\"\n : \"xdg-open\"\n execSync(`${cmd} \"${url}\"`, { stdio: \"ignore\" })\n}\n\nasync function pollForKey(baseUrl: string, deviceToken: string): Promise<string> {\n const client = new OatmealClient({ baseUrl })\n const start = Date.now()\n\n while (Date.now() - start < AUTH_TIMEOUT_MS) {\n const result = await client.get<{ status: string; apiKey?: string }>(\n \"/api/public/cli-auth/poll\",\n { params: { token: deviceToken } }\n )\n\n if (result.status === \"complete\" && result.apiKey) {\n return result.apiKey\n }\n\n if (result.status === \"expired\") {\n throw new Error(\"Authentication session expired. Please try again.\")\n }\n\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))\n }\n\n throw new Error(\"Authentication timed out after 10 minutes. Please try again.\")\n}\n\nasync function validateAndSaveKey(apiKey: string, baseUrl: string): Promise<void> {\n const client = new OatmealClient({ baseUrl, apiKey })\n\n const spinner = p.spinner()\n spinner.start(\"Validating API key...\")\n\n try {\n const whoami = await client.get<WhoAmIResponse>(\"/api/v1/whoami\")\n spinner.stop(\"Key validated!\")\n\n const config: CliConfig = {\n apiKey,\n baseUrl,\n tenantId: whoami.tenantId,\n keyId: whoami.keyId,\n scopes: whoami.scopes,\n }\n\n saveConfig(config)\n\n p.log.success(`Logged in! Key saved to ~/.hackathon/config.json`)\n p.log.info(`Tenant: ${whoami.tenantId}`)\n p.log.info(`Scopes: ${whoami.scopes.join(\", \")}`)\n } catch (error) {\n spinner.stop(\"Validation failed.\")\n throw error\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agi-ventures-canada/hackathon-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "CLI for the Oatmeal hackathon platform",
5
5
  "type": "module",
6
6
  "bin": {