vslides 1.0.8 → 1.0.10

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/cli.js +215 -26
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3126,8 +3126,8 @@ function clearCLIAuth() {
3126
3126
  if ((0, import_node_fs.existsSync)(AUTH_FILE)) {
3127
3127
  try {
3128
3128
  (0, import_node_fs.writeFileSync)(AUTH_FILE, "{}");
3129
- const { unlinkSync } = require("node:fs");
3130
- unlinkSync(AUTH_FILE);
3129
+ const { unlinkSync: unlinkSync2 } = require("node:fs");
3130
+ unlinkSync2(AUTH_FILE);
3131
3131
  } catch {
3132
3132
  }
3133
3133
  }
@@ -3333,14 +3333,34 @@ async function deploy(slug, token) {
3333
3333
  body: JSON.stringify({})
3334
3334
  });
3335
3335
  }
3336
+ async function getPresentations(cliAuthToken) {
3337
+ return request("/api/presentations", {
3338
+ headers: {
3339
+ "X-CLI-Auth-Token": cliAuthToken
3340
+ }
3341
+ });
3342
+ }
3343
+ async function reconnectSession(slug, cliAuthToken) {
3344
+ return request(`/api/session/${slug}/reconnect`, {
3345
+ method: "POST",
3346
+ headers: {
3347
+ "X-CLI-Auth-Token": cliAuthToken,
3348
+ "Content-Type": "application/json"
3349
+ },
3350
+ body: JSON.stringify({})
3351
+ });
3352
+ }
3336
3353
 
3337
3354
  // src/lib/config.ts
3338
3355
  var import_node_fs2 = require("node:fs");
3339
3356
  var import_node_path2 = require("node:path");
3357
+ var import_node_os2 = require("node:os");
3340
3358
  var CONFIG_FILE = ".vslides.json";
3341
3359
  var GUIDE_FILE = ".vslides-guide.md";
3342
3360
  var SLIDES_FILE = "slides.md";
3343
3361
  var UPSTREAM_FILE = "upstream.md";
3362
+ var VSLIDES_DIR = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".vslides");
3363
+ var PENDING_LOGIN_FILE = (0, import_node_path2.join)(VSLIDES_DIR, "pending-login.json");
3344
3364
  var GUIDE_CACHE_TTL = 24 * 60 * 60 * 1e3;
3345
3365
  function getConfigPath() {
3346
3366
  return (0, import_node_path2.join)(process.cwd(), CONFIG_FILE);
@@ -3354,6 +3374,9 @@ function getSlidesPath() {
3354
3374
  function getUpstreamPath() {
3355
3375
  return (0, import_node_path2.join)(process.cwd(), UPSTREAM_FILE);
3356
3376
  }
3377
+ function configExists() {
3378
+ return (0, import_node_fs2.existsSync)(getConfigPath());
3379
+ }
3357
3380
  function readConfig() {
3358
3381
  const path = getConfigPath();
3359
3382
  if (!(0, import_node_fs2.existsSync)(path)) {
@@ -3432,6 +3455,34 @@ function requireToken() {
3432
3455
  }
3433
3456
  return { config, token: config.token };
3434
3457
  }
3458
+ function ensureVslidesDir() {
3459
+ if (!(0, import_node_fs2.existsSync)(VSLIDES_DIR)) {
3460
+ (0, import_node_fs2.mkdirSync)(VSLIDES_DIR, { mode: 448 });
3461
+ }
3462
+ }
3463
+ function readPendingLogin() {
3464
+ if (!(0, import_node_fs2.existsSync)(PENDING_LOGIN_FILE)) {
3465
+ return null;
3466
+ }
3467
+ try {
3468
+ const content = (0, import_node_fs2.readFileSync)(PENDING_LOGIN_FILE, "utf-8");
3469
+ return JSON.parse(content);
3470
+ } catch {
3471
+ return null;
3472
+ }
3473
+ }
3474
+ function writePendingLogin(pending) {
3475
+ ensureVslidesDir();
3476
+ (0, import_node_fs2.writeFileSync)(PENDING_LOGIN_FILE, JSON.stringify(pending, null, 2) + "\n");
3477
+ }
3478
+ function clearPendingLogin() {
3479
+ if ((0, import_node_fs2.existsSync)(PENDING_LOGIN_FILE)) {
3480
+ try {
3481
+ (0, import_node_fs2.unlinkSync)(PENDING_LOGIN_FILE);
3482
+ } catch {
3483
+ }
3484
+ }
3485
+ }
3435
3486
 
3436
3487
  // src/lib/output.ts
3437
3488
  function success(message) {
@@ -3654,12 +3705,12 @@ async function join3(urlOrCode) {
3654
3705
  pollSecret,
3655
3706
  previewUrl: `${getBaseUrl()}/slides/${slug}`
3656
3707
  });
3657
- url("AUTH_URL", joinUrl);
3658
- instructions([
3659
- "Run in background: vslides check --wait &",
3660
- "Open the AUTH_URL to authenticate",
3661
- "Then run: vslides get"
3662
- ]);
3708
+ success(`Joined session: ${slug}`);
3709
+ url("VERIFY_URL", joinUrl);
3710
+ newline();
3711
+ info("ACTION_REQUIRED: Open the URL above in your browser to sign in");
3712
+ info("After signing in, run: vslides check --wait");
3713
+ info("Then run: vslides get");
3663
3714
  }
3664
3715
 
3665
3716
  // src/commands/share.ts
@@ -3670,14 +3721,126 @@ async function share() {
3670
3721
  error(`Failed to get share info: ${JSON.stringify(result.data)}`);
3671
3722
  process.exit(ExitCode.NetworkError);
3672
3723
  }
3673
- const { joinUrl } = result.data;
3674
- url("JOIN_URL", joinUrl);
3724
+ const { joinUrl, code } = result.data;
3725
+ info("Share with your co-editor:");
3726
+ newline();
3727
+ url("LINK", joinUrl);
3675
3728
  newline();
3676
- info("Share this URL with your collaborator. They will:");
3677
- info("1. Run: vslides join <url>");
3678
- info("2. Run in background: vslides check --wait &");
3679
- info("3. Visit the URL and authenticate with @vercel.com account");
3680
- info("4. Run: vslides get");
3729
+ info("OR if they use Claude Code, tell them to paste this:");
3730
+ newline();
3731
+ info(` /vslides join ${code}`);
3732
+ }
3733
+
3734
+ // src/commands/list.ts
3735
+ async function list() {
3736
+ const auth = getCachedAuth();
3737
+ if (!auth) {
3738
+ error("Not logged in");
3739
+ instructions(["Run `vslides login` to authenticate"]);
3740
+ process.exit(ExitCode.AuthRequired);
3741
+ }
3742
+ if (!isAuthValid(auth)) {
3743
+ error("Login expired");
3744
+ instructions(["Run `vslides login` to re-authenticate"]);
3745
+ process.exit(ExitCode.AuthRequired);
3746
+ }
3747
+ const result = await getPresentations(auth.token);
3748
+ if (!result.ok) {
3749
+ if (result.status === 401) {
3750
+ error("Authentication failed");
3751
+ instructions(["Run `vslides login` to re-authenticate"]);
3752
+ process.exit(ExitCode.AuthRequired);
3753
+ }
3754
+ error(`Failed to fetch presentations: ${JSON.stringify(result.data)}`);
3755
+ process.exit(ExitCode.NetworkError);
3756
+ }
3757
+ const { presentations } = result.data;
3758
+ if (presentations.length === 0) {
3759
+ info("No presentations found");
3760
+ instructions(["Run `vslides init` to create a new presentation"]);
3761
+ return;
3762
+ }
3763
+ const formatDate = (timestamp) => {
3764
+ const date = new Date(timestamp);
3765
+ return date.toLocaleDateString("en-US", {
3766
+ month: "short",
3767
+ day: "numeric",
3768
+ year: "numeric"
3769
+ });
3770
+ };
3771
+ const baseUrl = getBaseUrl();
3772
+ const headers = ["TITLE", "SLUG", "CREATED", "PREVIEW"];
3773
+ const rows = presentations.map((p) => [
3774
+ p.title.length > 30 ? p.title.substring(0, 27) + "..." : p.title,
3775
+ p.slug,
3776
+ formatDate(p.createdAt),
3777
+ `${baseUrl}${p.url}`
3778
+ ]);
3779
+ table(headers, rows);
3780
+ }
3781
+
3782
+ // src/commands/clone.ts
3783
+ async function clone(slug) {
3784
+ if (configExists()) {
3785
+ error("This directory already has a .vslides.json file");
3786
+ instructions([
3787
+ "Remove .vslides.json to clone a different presentation",
3788
+ "Or use a different directory"
3789
+ ]);
3790
+ process.exit(ExitCode.ValidationError);
3791
+ }
3792
+ const auth = getCachedAuth();
3793
+ if (!auth) {
3794
+ error("Not logged in");
3795
+ instructions(["Run `vslides login` to authenticate"]);
3796
+ process.exit(ExitCode.AuthRequired);
3797
+ }
3798
+ if (!isAuthValid(auth)) {
3799
+ error("Login expired");
3800
+ instructions(["Run `vslides login` to re-authenticate"]);
3801
+ process.exit(ExitCode.AuthRequired);
3802
+ }
3803
+ const result = await reconnectSession(slug, auth.token);
3804
+ if (!result.ok) {
3805
+ if (result.status === 401) {
3806
+ error("Authentication failed");
3807
+ instructions(["Run `vslides login` to re-authenticate"]);
3808
+ process.exit(ExitCode.AuthRequired);
3809
+ }
3810
+ if (result.status === 403) {
3811
+ error("You do not own this presentation");
3812
+ instructions(["You can only clone your own presentations"]);
3813
+ process.exit(ExitCode.AuthRequired);
3814
+ }
3815
+ if (result.status === 404) {
3816
+ error(`Presentation not found: ${slug}`);
3817
+ instructions(["Run `vslides list` to see your presentations"]);
3818
+ process.exit(ExitCode.ValidationError);
3819
+ }
3820
+ error(`Failed to reconnect: ${JSON.stringify(result.data)}`);
3821
+ process.exit(ExitCode.NetworkError);
3822
+ }
3823
+ const { pollSecret, token, previewUrl } = result.data;
3824
+ const baseUrl = getBaseUrl();
3825
+ writeConfig({
3826
+ slug,
3827
+ pollSecret,
3828
+ token,
3829
+ previewUrl: `${baseUrl}${previewUrl}`
3830
+ });
3831
+ const slidesResult = await getSlides(slug, token);
3832
+ if (slidesResult.ok) {
3833
+ const { markdown, version } = slidesResult.data;
3834
+ writeSlides(markdown);
3835
+ updateConfig({ version });
3836
+ success(`Cloned presentation: ${slug}`);
3837
+ info(`Downloaded slides.md (version ${version})`);
3838
+ } else {
3839
+ success(`Cloned presentation: ${slug}`);
3840
+ info("Session is not running - slides.md not downloaded");
3841
+ instructions(["Run `vslides check --wait` to start the session", "Then run `vslides get` to download slides"]);
3842
+ }
3843
+ url("PREVIEW_URL", `${baseUrl}${previewUrl}`);
3681
3844
  }
3682
3845
 
3683
3846
  // src/commands/preview.ts
@@ -4027,11 +4190,15 @@ async function upload(file) {
4027
4190
  error(`Failed to upload: ${JSON.stringify(result.data)}`);
4028
4191
  process.exit(ExitCode.NetworkError);
4029
4192
  }
4030
- info(`UPLOADED: ${result.data.filename}`);
4193
+ success(`Uploaded: ${result.data.filename}`);
4031
4194
  info(`PATH: ${result.data.path}`);
4032
4195
  newline();
4033
- info("Use in slides as:");
4196
+ info("Use in slides frontmatter:");
4034
4197
  info(` image: ${result.data.path}`);
4198
+ info(` grayscale: 0 # for full color (default is grayscale)`);
4199
+ newline();
4200
+ info("For avatar images in 1-title layout:");
4201
+ info(` subtitle1Image: ${result.data.path}`);
4035
4202
  }
4036
4203
 
4037
4204
  // src/commands/export.ts
@@ -4119,7 +4286,7 @@ var POLL_TIMEOUT2 = 12e4;
4119
4286
  function sleep3(ms) {
4120
4287
  return new Promise((resolve) => setTimeout(resolve, ms));
4121
4288
  }
4122
- async function login() {
4289
+ async function login(options = {}) {
4123
4290
  const cachedAuth = getCachedAuth();
4124
4291
  if (cachedAuth && isAuthValid(cachedAuth)) {
4125
4292
  const daysLeft = Math.ceil((cachedAuth.expiresAt - Date.now()) / (24 * 60 * 60 * 1e3));
@@ -4127,6 +4294,11 @@ async function login() {
4127
4294
  info("Run `vslides logout` to sign out first.");
4128
4295
  return;
4129
4296
  }
4297
+ const pendingLogin = readPendingLogin();
4298
+ if (pendingLogin && options.wait) {
4299
+ await pollForAuth(pendingLogin.slug, pendingLogin.pollSecret);
4300
+ return;
4301
+ }
4130
4302
  const createResult = await createSession();
4131
4303
  if (!createResult.ok) {
4132
4304
  error(`Failed to create session: ${JSON.stringify(createResult.data)}`);
@@ -4134,11 +4306,22 @@ async function login() {
4134
4306
  }
4135
4307
  const { slug, pollSecret, verifyUrl } = createResult.data;
4136
4308
  const authUrl = `${verifyUrl}?cliAuth=true`;
4137
- info("Please authenticate in your browser:");
4138
- url("AUTH_URL", authUrl);
4309
+ writePendingLogin({ slug, pollSecret, authUrl });
4310
+ url("VERIFY_URL", authUrl);
4139
4311
  newline();
4140
- info("Waiting for authentication...");
4312
+ info("ACTION_REQUIRED: Open the URL above in your browser to sign in");
4313
+ if (options.wait) {
4314
+ info("Waiting for authentication (2 minute timeout)...");
4315
+ newline();
4316
+ await pollForAuth(slug, pollSecret);
4317
+ } else {
4318
+ newline();
4319
+ info("Run `vslides login --wait` to wait for authentication to complete");
4320
+ }
4321
+ }
4322
+ async function pollForAuth(slug, pollSecret) {
4141
4323
  const startTime = Date.now();
4324
+ let pollCount = 0;
4142
4325
  while (true) {
4143
4326
  const result = await getSession(slug, pollSecret);
4144
4327
  if (!result.ok) {
@@ -4150,21 +4333,25 @@ async function login() {
4150
4333
  const validateResult = await validateCLIAuth(cliAuthToken);
4151
4334
  if (validateResult.ok && validateResult.data.valid && validateResult.data.email && validateResult.data.expiresAt) {
4152
4335
  saveCLIAuth(cliAuthToken, validateResult.data.email, validateResult.data.expiresAt);
4153
- newline();
4336
+ clearPendingLogin();
4154
4337
  success(`Logged in as ${validateResult.data.email} (valid for 7 days)`);
4155
4338
  process.exit(ExitCode.Success);
4156
4339
  }
4157
4340
  }
4158
4341
  if (Date.now() - startTime > POLL_TIMEOUT2) {
4159
- newline();
4160
4342
  error("Timeout waiting for authentication");
4161
4343
  instructions([
4162
- "Open the AUTH_URL in your browser",
4344
+ "Open the VERIFY_URL in your browser",
4163
4345
  "Sign in with your @vercel.com account",
4164
- "Run `vslides login` again"
4346
+ "Run `vslides login --wait` again"
4165
4347
  ]);
4166
4348
  process.exit(ExitCode.Conflict);
4167
4349
  }
4350
+ pollCount++;
4351
+ if (pollCount % 10 === 0) {
4352
+ const secondsLeft = Math.ceil((POLL_TIMEOUT2 - (Date.now() - startTime)) / 1e3);
4353
+ info(`Still waiting... (${secondsLeft}s remaining)`);
4354
+ }
4168
4355
  await sleep3(POLL_INTERVAL2);
4169
4356
  }
4170
4357
  }
@@ -4229,13 +4416,15 @@ function wrapCommand(fn) {
4229
4416
  };
4230
4417
  }
4231
4418
  program.name("vslides").description("CLI for Vercel Slides API").version("1.0.0");
4232
- program.command("login").description("Authenticate with Vercel (valid for 7 days)").action(wrapCommand(login));
4419
+ program.command("login").description("Authenticate with Vercel (valid for 7 days)").option("--wait", "Wait for authentication to complete (2 min timeout)").action(wrapCommand((options) => login(options)));
4233
4420
  program.command("logout").description("Sign out and revoke credentials").action(wrapCommand(logout));
4234
4421
  program.command("whoami").description("Show current authentication status").option("--validate", "Validate token with server").action(wrapCommand((options) => whoami(options)));
4235
4422
  program.command("init").description("Create a new session").action(wrapCommand(init));
4236
4423
  program.command("check").description("Check session status").option("--wait", "Poll until running (60s timeout)").action(wrapCommand((options) => check(options)));
4237
4424
  program.command("join <url>").description("Join a shared session").action(wrapCommand(join3));
4238
4425
  program.command("share").description("Get join URL for collaborators").action(wrapCommand(share));
4426
+ program.command("list").description("List all your presentations").action(wrapCommand(list));
4427
+ program.command("clone <slug>").description("Set up an existing presentation in current directory").action(wrapCommand(clone));
4239
4428
  program.command("preview").description("Print or open the preview URL").option("--open", "Open in browser").action(wrapCommand((options) => preview(options)));
4240
4429
  program.command("guide").description("Print the slide layout guide").option("--refresh", "Force fresh fetch").action(wrapCommand((options) => guide(options)));
4241
4430
  program.command("get").description("Download current slides from server").action(wrapCommand(get));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vslides",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "CLI for Vercel Slides API",
5
5
  "license": "MIT",
6
6
  "author": "Vercel",