apiblaze 0.1.3 → 0.1.5

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