apiblaze 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +148 -88
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -89,12 +89,13 @@ var api_exports = {};
89
89
  __export(api_exports, {
90
90
  deleteDevTunnel: () => deleteDevTunnel,
91
91
  getLocalhostTargets: () => getLocalhostTargets,
92
+ getProjects: () => getProjects,
92
93
  getTeams: () => getTeams,
93
94
  putDevTunnel: () => putDevTunnel
94
95
  });
95
96
  async function apiFetch(path3, options = {}) {
96
97
  const token = getAccessToken();
97
- const url = `${API_BASE}${path3}`;
98
+ const url = `${DASHBOARD_BASE}${path3}`;
98
99
  const res = await fetch(url, {
99
100
  ...options,
100
101
  headers: {
@@ -118,132 +119,121 @@ async function apiFetch(path3, options = {}) {
118
119
  return res.json();
119
120
  }
120
121
  async function getTeams() {
121
- return apiFetch("/me/teams");
122
+ const res = await apiFetch("/api/cli/teams");
123
+ const raw = Array.isArray(res) ? res : res?.teams ?? res?.data ?? [];
124
+ return raw.map((t) => {
125
+ const teamId = t.teamId ?? t.team_id;
126
+ return { teamId: teamId ?? "", name: t.name ?? teamId ?? "" };
127
+ }).filter((t) => t.teamId);
122
128
  }
123
129
  async function getLocalhostTargets(teamId) {
124
- return apiFetch(`/projects/localhost-targets?team_id=${encodeURIComponent(teamId)}`);
130
+ return apiFetch(`/api/cli/localhost-targets?team_id=${encodeURIComponent(teamId)}`);
131
+ }
132
+ async function getProjects(teamId) {
133
+ return apiFetch(`/api/cli/projects?team_id=${encodeURIComponent(teamId)}`);
125
134
  }
126
135
  async function putDevTunnel(payload) {
127
- return apiFetch("/dev-tunnel", {
136
+ return apiFetch("/api/cli/dev-tunnel", {
128
137
  method: "PUT",
129
138
  body: JSON.stringify(payload)
130
139
  });
131
140
  }
132
141
  async function deleteDevTunnel() {
133
- return apiFetch("/dev-tunnel", { method: "DELETE" });
142
+ return apiFetch("/api/cli/dev-tunnel", { method: "DELETE" });
134
143
  }
135
- var API_BASE;
144
+ var DASHBOARD_BASE;
136
145
  var init_api = __esm({
137
146
  "src/lib/api.ts"() {
138
147
  "use strict";
139
148
  init_auth();
140
149
  init_types();
141
- API_BASE = "https://cli.admin.apiblaze.com";
150
+ DASHBOARD_BASE = "https://dashboard.apiblaze.com";
142
151
  }
143
152
  });
144
153
 
145
154
  // src/index.ts
146
155
  var import_commander = require("commander");
147
- var import_chalk4 = __toESM(require("chalk"));
156
+ var import_chalk5 = __toESM(require("chalk"));
148
157
  init_types();
149
158
 
150
159
  // src/commands/login.ts
151
160
  var import_chalk = __toESM(require("chalk"));
152
161
  var import_ora = __toESM(require("ora"));
153
- var crypto = __toESM(require("crypto"));
154
162
  init_auth();
155
163
  init_api();
156
- var AUTH_BASE = "https://auth.apiblaze.com";
157
- var CLIENT_ID = "emdE4-Pt9LGOAXL5MA1zEQ";
158
- var SCOPE = "openid email profile offline_access";
159
- function generateCodeVerifier() {
160
- return crypto.randomBytes(32).toString("base64url");
161
- }
162
- function generateCodeChallenge(verifier) {
163
- return crypto.createHash("sha256").update(verifier).digest("base64url");
164
- }
164
+ var DASHBOARD_BASE2 = "https://dashboard.apiblaze.com";
165
165
  function openBrowser(url) {
166
166
  const { exec } = require("child_process");
167
167
  const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
168
168
  exec(cmd);
169
169
  }
170
- async function requestDeviceCode(codeChallenge) {
171
- const res = await fetch(`${AUTH_BASE}/device_authorization`, {
172
- method: "POST",
173
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
174
- body: new URLSearchParams({
175
- client_id: CLIENT_ID,
176
- scope: SCOPE,
177
- code_challenge: codeChallenge,
178
- code_challenge_method: "S256"
179
- })
180
- });
181
- if (!res.ok) {
182
- const body = await res.text();
183
- throw new Error(`Failed to start device flow: ${res.status} ${body}`);
184
- }
185
- return res.json();
170
+ function decodeJWTPayload(token) {
171
+ const part = token.split(".")[1];
172
+ return JSON.parse(Buffer.from(part, "base64url").toString());
186
173
  }
187
- async function pollForToken(deviceCode, codeVerifier, intervalSecs) {
188
- const DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
189
- let pollInterval = intervalSecs * 1e3;
174
+ async function runLogin() {
175
+ console.log(import_chalk.default.bold("\nLogging in to APIblaze...\n"));
176
+ const codeRes = await fetch(`${DASHBOARD_BASE2}/api/device/code`, { method: "POST" });
177
+ if (!codeRes.ok) {
178
+ throw new Error(`Failed to start login: ${codeRes.status}`);
179
+ }
180
+ const deviceAuth = await codeRes.json();
181
+ console.log(`${import_chalk.default.cyan("\u2192")} Open this URL in your browser to confirm login:`);
182
+ console.log(` ${import_chalk.default.bold.underline(deviceAuth.verification_uri)}
183
+ `);
184
+ console.log(`${import_chalk.default.cyan("\u2192")} Your code: ${import_chalk.default.bold(deviceAuth.user_code)}
185
+ `);
186
+ openBrowser(deviceAuth.verification_uri_complete ?? deviceAuth.verification_uri);
187
+ const spinner = (0, import_ora.default)("Waiting for authorization in browser...").start();
188
+ let accessToken;
189
+ let pollInterval = (deviceAuth.interval ?? 5) * 1e3;
190
190
  while (true) {
191
191
  await new Promise((r) => setTimeout(r, pollInterval));
192
- const res = await fetch(`${AUTH_BASE}/token`, {
192
+ const tokenRes = await fetch(`${DASHBOARD_BASE2}/api/device/token`, {
193
193
  method: "POST",
194
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
195
- body: new URLSearchParams({
196
- grant_type: DEVICE_GRANT,
197
- device_code: deviceCode,
198
- client_id: CLIENT_ID,
199
- code_verifier: codeVerifier
200
- })
194
+ headers: { "Content-Type": "application/json" },
195
+ body: JSON.stringify({ device_code: deviceAuth.device_code })
201
196
  });
202
- const body = await res.json();
203
- if (res.ok) {
204
- return body;
197
+ const body = await tokenRes.json();
198
+ if (body.access_token) {
199
+ accessToken = body.access_token;
200
+ break;
205
201
  }
206
202
  if (body.error === "authorization_pending") {
207
203
  continue;
208
204
  } else if (body.error === "slow_down") {
209
205
  pollInterval += 5e3;
210
206
  } else if (body.error === "expired_token") {
207
+ spinner.fail("Login failed.");
211
208
  throw new Error("Device code expired. Run `apiblaze login` again.");
212
209
  } else if (body.error === "access_denied") {
210
+ spinner.fail("Login failed.");
213
211
  throw new Error("Login was denied or cancelled.");
214
212
  } else {
213
+ spinner.fail("Login failed.");
215
214
  throw new Error(`Unexpected error during login: ${body.error ?? "unknown"}`);
216
215
  }
217
216
  }
218
- }
219
- async function runLogin() {
220
- console.log(import_chalk.default.bold("\nLogging in to APIblaze...\n"));
221
- const codeVerifier = generateCodeVerifier();
222
- const codeChallenge = generateCodeChallenge(codeVerifier);
223
- const deviceAuth = await requestDeviceCode(codeChallenge);
224
- const verifyUrl = deviceAuth.verification_uri_complete ?? deviceAuth.verification_uri;
225
- console.log(`${import_chalk.default.cyan("\u2192")} Open this URL in your browser to confirm login:`);
226
- console.log(` ${import_chalk.default.bold.underline(verifyUrl)}
227
- `);
228
- console.log(`${import_chalk.default.cyan("\u2192")} Your code: ${import_chalk.default.bold(deviceAuth.user_code)}
229
- `);
230
- openBrowser(verifyUrl);
231
- const spinner = (0, import_ora.default)("Waiting for authorization in browser...").start();
232
- let token;
233
- try {
234
- token = await pollForToken(deviceAuth.device_code, codeVerifier, deviceAuth.interval);
235
- } catch (err) {
236
- spinner.fail("Login failed.");
237
- throw err;
238
- }
217
+ const jwtPayload = decodeJWTPayload(accessToken);
218
+ const expiresAt = jwtPayload.exp * 1e3;
219
+ const apiblazeUserId = jwtPayload.sub;
220
+ const githubHandle = jwtPayload.handle;
221
+ const email = jwtPayload.email;
222
+ const defaultTeamId = jwtPayload.defaultTeamId;
239
223
  saveCredentials({
240
- accessToken: token.access_token,
241
- refreshToken: token.refresh_token,
242
- expiresAt: Date.now() + token.expires_in * 1e3,
243
- tokenType: token.token_type
224
+ accessToken,
225
+ expiresAt,
226
+ tokenType: "Bearer",
227
+ apiblazeUserId,
228
+ githubHandle,
229
+ email,
230
+ teamId: defaultTeamId ?? void 0
244
231
  });
245
232
  spinner.succeed(import_chalk.default.green("Authorized!"));
246
- let teamId;
233
+ if (githubHandle) {
234
+ console.log(`${import_chalk.default.cyan("\u2192")} Logged in as ${import_chalk.default.bold("@" + githubHandle)}${apiblazeUserId ? import_chalk.default.dim(` (${apiblazeUserId})`) : ""}`);
235
+ }
236
+ let teamId = defaultTeamId ?? void 0;
247
237
  let teamName;
248
238
  try {
249
239
  const teams = await getTeams();
@@ -253,7 +243,7 @@ async function runLogin() {
253
243
  teamId = teams[0].teamId;
254
244
  teamName = teams[0].name;
255
245
  console.log(`
256
- ${import_chalk.default.cyan("\u2192")} Using team: ${import_chalk.default.bold(teamName)}`);
246
+ ${import_chalk.default.cyan("\u2192")} Team: ${import_chalk.default.bold(teamName)}`);
257
247
  } else {
258
248
  const { default: inquirer2 } = await import("inquirer");
259
249
  const { chosen } = await inquirer2.prompt([{
@@ -267,16 +257,16 @@ ${import_chalk.default.cyan("\u2192")} Using team: ${import_chalk.default.bold(t
267
257
  }
268
258
  } catch {
269
259
  }
270
- if (teamId) {
271
- saveCredentials({
272
- accessToken: token.access_token,
273
- refreshToken: token.refresh_token,
274
- expiresAt: Date.now() + token.expires_in * 1e3,
275
- tokenType: token.token_type,
276
- teamId,
277
- teamName
278
- });
279
- }
260
+ saveCredentials({
261
+ accessToken,
262
+ expiresAt,
263
+ tokenType: "Bearer",
264
+ apiblazeUserId,
265
+ githubHandle,
266
+ email,
267
+ teamId,
268
+ teamName
269
+ });
280
270
  console.log(import_chalk.default.green("\n\u2714 Logged in successfully!"));
281
271
  }
282
272
 
@@ -605,6 +595,68 @@ Tunneling ${selectedTargets.length} project(s) to localhost:${options.port}
605
595
  }
606
596
  }
607
597
 
598
+ // src/commands/projects.ts
599
+ var import_chalk4 = __toESM(require("chalk"));
600
+ var import_ora4 = __toESM(require("ora"));
601
+ init_auth();
602
+ init_api();
603
+ async function runProjects() {
604
+ const creds = loadCredentials();
605
+ if (!creds) {
606
+ console.error(import_chalk4.default.red("Not logged in. Run `apiblaze login` first."));
607
+ process.exit(1);
608
+ }
609
+ if (creds.githubHandle) {
610
+ console.log(
611
+ `${import_chalk4.default.cyan("\u2192")} Logged in as ${import_chalk4.default.bold("@" + creds.githubHandle)}` + (creds.apiblazeUserId ? import_chalk4.default.dim(` (${creds.apiblazeUserId})`) : "")
612
+ );
613
+ }
614
+ let teamId = creds.teamId;
615
+ let teamName = creds.teamName;
616
+ if (!teamId) {
617
+ const teams = await getTeams().catch(() => []);
618
+ if (teams.length === 1) {
619
+ teamId = teams[0].teamId;
620
+ teamName = teams[0].name;
621
+ } else if (teams.length > 1) {
622
+ const { default: inquirer2 } = await import("inquirer");
623
+ const { chosen } = await inquirer2.prompt([{
624
+ type: "list",
625
+ name: "chosen",
626
+ message: "Which team do you want to use?",
627
+ choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
628
+ }]);
629
+ teamId = chosen;
630
+ teamName = teams.find((t) => t.teamId === chosen)?.name;
631
+ }
632
+ }
633
+ if (!teamId) {
634
+ console.error(import_chalk4.default.red("No team found. Run `apiblaze login` to set up your team."));
635
+ process.exit(1);
636
+ }
637
+ console.log(`${import_chalk4.default.cyan("\u2192")} Team: ${import_chalk4.default.bold(teamName ?? teamId)} ${import_chalk4.default.dim(teamId)}
638
+ `);
639
+ const spinner = (0, import_ora4.default)("Fetching projects...").start();
640
+ let projects;
641
+ try {
642
+ projects = await getProjects(teamId);
643
+ spinner.stop();
644
+ } catch (err) {
645
+ spinner.fail("Failed to fetch projects.");
646
+ throw err;
647
+ }
648
+ if (projects.length === 0) {
649
+ console.log(import_chalk4.default.yellow("No projects found for this team."));
650
+ return;
651
+ }
652
+ for (const p of projects) {
653
+ console.log(`${import_chalk4.default.bold(p.projectName)} ${import_chalk4.default.dim(p.projectId)}`);
654
+ console.log(` ${import_chalk4.default.dim("version:")} ${p.apiVersion} ${import_chalk4.default.dim("team:")} ${p.teamId}`);
655
+ }
656
+ console.log(import_chalk4.default.dim(`
657
+ ${projects.length} project(s).`));
658
+ }
659
+
608
660
  // src/index.ts
609
661
  var program = new import_commander.Command();
610
662
  program.name("apiblaze").description("APIblaze dev tunnel CLI").version("0.1.0");
@@ -616,6 +668,14 @@ program.command("login").description("Authenticate with APIblaze").action(async
616
668
  process.exit(1);
617
669
  }
618
670
  });
671
+ program.command("projects").description("List the projects in your team").action(async () => {
672
+ try {
673
+ await runProjects();
674
+ } catch (err) {
675
+ printError(err);
676
+ process.exit(1);
677
+ }
678
+ });
619
679
  program.command("dev").description("Start a dev tunnel for your localhost projects").option("-p, --port <number>", "Local port to tunnel", "3000").action(async (opts) => {
620
680
  try {
621
681
  await runDev({ port: parseInt(opts.port, 10) });
@@ -626,13 +686,13 @@ program.command("dev").description("Start a dev tunnel for your localhost projec
626
686
  });
627
687
  function printError(err) {
628
688
  if (err instanceof ApiError) {
629
- console.error(import_chalk4.default.red(`
689
+ console.error(import_chalk5.default.red(`
630
690
  API error (${err.status}): ${err.message}`));
631
691
  } else if (err instanceof Error) {
632
- console.error(import_chalk4.default.red(`
692
+ console.error(import_chalk5.default.red(`
633
693
  Error: ${err.message}`));
634
694
  } else {
635
- console.error(import_chalk4.default.red("\nUnknown error"));
695
+ console.error(import_chalk5.default.red("\nUnknown error"));
636
696
  }
637
697
  }
638
698
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Dev tunnel CLI for APIblaze — route localhost projects through Cloudflare tunnels",
5
5
  "keywords": [
6
6
  "apiblaze",