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.
- package/dist/cli.js +215 -26
- 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
|
-
|
|
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
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
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
|
-
|
|
3724
|
+
const { joinUrl, code } = result.data;
|
|
3725
|
+
info("Share with your co-editor:");
|
|
3726
|
+
newline();
|
|
3727
|
+
url("LINK", joinUrl);
|
|
3675
3728
|
newline();
|
|
3676
|
-
info("
|
|
3677
|
-
|
|
3678
|
-
info(
|
|
3679
|
-
|
|
3680
|
-
|
|
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
|
-
|
|
4193
|
+
success(`Uploaded: ${result.data.filename}`);
|
|
4031
4194
|
info(`PATH: ${result.data.path}`);
|
|
4032
4195
|
newline();
|
|
4033
|
-
info("Use in slides
|
|
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
|
-
|
|
4138
|
-
url("
|
|
4309
|
+
writePendingLogin({ slug, pollSecret, authUrl });
|
|
4310
|
+
url("VERIFY_URL", authUrl);
|
|
4139
4311
|
newline();
|
|
4140
|
-
info("
|
|
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
|
-
|
|
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
|
|
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));
|