videodraft 0.15.0 → 0.15.1
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/client.d.ts +18 -5
- package/dist/client.js +107 -60
- package/dist/index.js +153 -81
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -36,13 +36,21 @@ interface ConnectionSessionStore {
|
|
|
36
36
|
* Persist a freshly minted token for this scope and return the token that
|
|
37
37
|
* now owns the scope. Two processes racing on an empty scope both mint;
|
|
38
38
|
* the first writer wins and the second gets the winner's token back, so
|
|
39
|
-
* one directory never ends up split across two sessions.
|
|
39
|
+
* one directory never ends up split across two sessions. Returns null
|
|
40
|
+
* when the token could NOT be persisted (read-only config dir): the
|
|
41
|
+
* caller must then go stateless — sending an unpersisted token would
|
|
42
|
+
* create a fresh AI Studio session per invocation, when the documented
|
|
43
|
+
* degraded mode is the shared server-side fallback.
|
|
40
44
|
*/
|
|
41
|
-
save(token: string): string;
|
|
45
|
+
save(token: string): string | null;
|
|
42
46
|
/** Replace the scope's token unconditionally (server re-mint). */
|
|
43
47
|
replace(token: string): void;
|
|
44
|
-
/**
|
|
45
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Forget the current token; the next call re-initialises. Returns whether
|
|
50
|
+
* the deletion actually ran — false means the scope lock could not be
|
|
51
|
+
* taken (or the filesystem refused) and the record may still exist.
|
|
52
|
+
*/
|
|
53
|
+
reset(): boolean;
|
|
46
54
|
/** Where this scope's record lives (for `sessions current`). */
|
|
47
55
|
describe(): {
|
|
48
56
|
scope: string;
|
|
@@ -64,7 +72,12 @@ interface SessionScopeInput {
|
|
|
64
72
|
/** Stable label for this scope; what the record is keyed by. */
|
|
65
73
|
declare function sessionScope(input: SessionScopeInput): string;
|
|
66
74
|
declare function createConnectionSessionStore(input: SessionScopeInput): ConnectionSessionStore;
|
|
67
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Drop every stored connection session (all scopes) for this config dir.
|
|
77
|
+
* Each record is removed under its per-scope lock — the same protocol as
|
|
78
|
+
* the single-scope reset — so a concurrent touch/replace that already read
|
|
79
|
+
* the old record cannot recreate it right after the sweep deletes it.
|
|
80
|
+
*/
|
|
68
81
|
declare function resetAllConnectionSessions(env?: NodeJS.ProcessEnv): number;
|
|
69
82
|
|
|
70
83
|
/**
|
package/dist/client.js
CHANGED
|
@@ -214,7 +214,7 @@ async function withLock(lockName, fn, env = process.env) {
|
|
|
214
214
|
// src/core/session.ts
|
|
215
215
|
import fs2 from "fs";
|
|
216
216
|
import path2 from "path";
|
|
217
|
-
import { createHash } from "crypto";
|
|
217
|
+
import { createHash, randomBytes } from "crypto";
|
|
218
218
|
var SESSION_IDLE_MS = 12 * 60 * 60 * 1e3;
|
|
219
219
|
var MCP_SESSION_HEADER = "Mcp-Session-Id";
|
|
220
220
|
function sessionsDir(env) {
|
|
@@ -268,19 +268,6 @@ function writeRecord(file, record) {
|
|
|
268
268
|
fs2.writeFileSync(tmp, JSON.stringify(record, null, 2), { mode: 384 });
|
|
269
269
|
fs2.renameSync(tmp, file);
|
|
270
270
|
}
|
|
271
|
-
function claimRecord(file, record) {
|
|
272
|
-
fs2.mkdirSync(path2.dirname(file), { recursive: true, mode: 448 });
|
|
273
|
-
try {
|
|
274
|
-
fs2.writeFileSync(file, JSON.stringify(record, null, 2), {
|
|
275
|
-
mode: 384,
|
|
276
|
-
flag: "wx"
|
|
277
|
-
});
|
|
278
|
-
return true;
|
|
279
|
-
} catch (err) {
|
|
280
|
-
if (err?.code === "EEXIST") return false;
|
|
281
|
-
throw err;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
271
|
function isLive(record, nowMs) {
|
|
285
272
|
const idle = nowMs - Date.parse(record.lastUsedAt);
|
|
286
273
|
return Number.isFinite(idle) && idle <= SESSION_IDLE_MS;
|
|
@@ -296,33 +283,61 @@ function sleepSync(ms) {
|
|
|
296
283
|
}
|
|
297
284
|
function acquireReclaimLock(file) {
|
|
298
285
|
const lock = `${file}.lock`;
|
|
286
|
+
const owner = `${process.pid}:${randomBytes(6).toString("hex")}`;
|
|
299
287
|
try {
|
|
300
|
-
fs2.
|
|
301
|
-
|
|
288
|
+
fs2.mkdirSync(path2.dirname(lock), { recursive: true, mode: 448 });
|
|
289
|
+
fs2.writeFileSync(lock, owner, { mode: 384, flag: "wx" });
|
|
290
|
+
return owner;
|
|
302
291
|
} catch (err) {
|
|
303
|
-
if (err?.code !== "EEXIST")
|
|
292
|
+
if (err?.code !== "EEXIST") throw err;
|
|
304
293
|
try {
|
|
305
294
|
const age = Date.now() - fs2.statSync(lock).mtimeMs;
|
|
306
|
-
if (age <= RECLAIM_LOCK_STALE_MS) return
|
|
295
|
+
if (age <= RECLAIM_LOCK_STALE_MS) return null;
|
|
307
296
|
const claimed = `${lock}.break.${process.pid}`;
|
|
308
297
|
fs2.renameSync(lock, claimed);
|
|
309
298
|
fs2.rmSync(claimed, { force: true });
|
|
310
|
-
fs2.writeFileSync(lock,
|
|
311
|
-
|
|
312
|
-
flag: "wx"
|
|
313
|
-
});
|
|
314
|
-
return true;
|
|
299
|
+
fs2.writeFileSync(lock, owner, { mode: 384, flag: "wx" });
|
|
300
|
+
return owner;
|
|
315
301
|
} catch {
|
|
316
|
-
return
|
|
302
|
+
return null;
|
|
317
303
|
}
|
|
318
304
|
}
|
|
319
305
|
}
|
|
320
|
-
function releaseReclaimLock(file) {
|
|
306
|
+
function releaseReclaimLock(file, owner) {
|
|
307
|
+
const lock = `${file}.lock`;
|
|
321
308
|
try {
|
|
322
|
-
fs2.
|
|
309
|
+
if (fs2.readFileSync(lock, "utf8") !== owner) return;
|
|
310
|
+
fs2.rmSync(lock, { force: true });
|
|
323
311
|
} catch {
|
|
324
312
|
}
|
|
325
313
|
}
|
|
314
|
+
function withScopeLock(file, fn) {
|
|
315
|
+
const deadline = Date.now() + RECLAIM_LOCK_STALE_MS + RECLAIM_WAIT_MS;
|
|
316
|
+
let owner;
|
|
317
|
+
try {
|
|
318
|
+
owner = acquireReclaimLock(file);
|
|
319
|
+
while (!owner && Date.now() < deadline) {
|
|
320
|
+
sleepSync(RECLAIM_POLL_MS);
|
|
321
|
+
owner = acquireReclaimLock(file);
|
|
322
|
+
}
|
|
323
|
+
} catch {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
if (!owner) return false;
|
|
327
|
+
const lockPath = `${file}.lock`;
|
|
328
|
+
const stillOwner = () => {
|
|
329
|
+
try {
|
|
330
|
+
return fs2.readFileSync(lockPath, "utf8") === owner;
|
|
331
|
+
} catch {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
try {
|
|
336
|
+
return fn(stillOwner);
|
|
337
|
+
} finally {
|
|
338
|
+
releaseReclaimLock(file, owner);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
326
341
|
function createConnectionSessionStore(input) {
|
|
327
342
|
const env = input.env ?? process.env;
|
|
328
343
|
const now = input.now ?? Date.now;
|
|
@@ -345,58 +360,76 @@ function createConnectionSessionStore(input) {
|
|
|
345
360
|
load() {
|
|
346
361
|
const record = readRecord(file);
|
|
347
362
|
if (!record || !isLive(record, now())) return null;
|
|
348
|
-
|
|
363
|
+
const quickDeadline = Date.now() + 5 * RECLAIM_POLL_MS;
|
|
364
|
+
let owner;
|
|
365
|
+
try {
|
|
366
|
+
owner = acquireReclaimLock(file);
|
|
367
|
+
while (!owner && Date.now() < quickDeadline) {
|
|
368
|
+
sleepSync(RECLAIM_POLL_MS);
|
|
369
|
+
owner = acquireReclaimLock(file);
|
|
370
|
+
}
|
|
371
|
+
} catch {
|
|
372
|
+
return record.token;
|
|
373
|
+
}
|
|
374
|
+
if (owner) {
|
|
349
375
|
try {
|
|
350
376
|
const current = readRecord(file);
|
|
351
|
-
if (current
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
377
|
+
if (!current || !isLive(current, now())) return null;
|
|
378
|
+
try {
|
|
379
|
+
const lockPath = `${file}.lock`;
|
|
380
|
+
if (fs2.readFileSync(lockPath, "utf8") === owner) {
|
|
381
|
+
writeRecord(file, {
|
|
382
|
+
...current,
|
|
383
|
+
lastUsedAt: new Date(now()).toISOString()
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
356
387
|
}
|
|
357
|
-
|
|
388
|
+
return current.token;
|
|
358
389
|
} finally {
|
|
359
|
-
releaseReclaimLock(file);
|
|
390
|
+
releaseReclaimLock(file, owner);
|
|
360
391
|
}
|
|
361
392
|
}
|
|
362
|
-
return
|
|
393
|
+
return null;
|
|
363
394
|
},
|
|
364
395
|
save(token) {
|
|
365
396
|
try {
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
if (winner && isLive(winner, now())) return winner.token;
|
|
369
|
-
if (acquireReclaimLock(file)) {
|
|
370
|
-
try {
|
|
371
|
-
const current = readRecord(file);
|
|
372
|
-
if (current && isLive(current, now())) return current.token;
|
|
373
|
-
writeRecord(file, fresh(token));
|
|
374
|
-
return token;
|
|
375
|
-
} finally {
|
|
376
|
-
releaseReclaimLock(file);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
const deadline = Date.now() + RECLAIM_WAIT_MS;
|
|
380
|
-
while (Date.now() < deadline) {
|
|
397
|
+
let result = null;
|
|
398
|
+
const ran = withScopeLock(file, (stillOwner) => {
|
|
381
399
|
const current = readRecord(file);
|
|
382
|
-
if (current && isLive(current, now()))
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
400
|
+
if (current && isLive(current, now())) {
|
|
401
|
+
result = current.token;
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
if (!stillOwner()) return false;
|
|
405
|
+
writeRecord(file, fresh(token));
|
|
406
|
+
result = token;
|
|
407
|
+
return true;
|
|
408
|
+
});
|
|
409
|
+
return ran ? result : null;
|
|
386
410
|
} catch {
|
|
387
|
-
return
|
|
411
|
+
return null;
|
|
388
412
|
}
|
|
389
413
|
},
|
|
390
414
|
replace(token) {
|
|
391
415
|
try {
|
|
392
|
-
|
|
416
|
+
withScopeLock(file, (stillOwner) => {
|
|
417
|
+
if (!stillOwner()) return false;
|
|
418
|
+
writeRecord(file, fresh(token));
|
|
419
|
+
return true;
|
|
420
|
+
});
|
|
393
421
|
} catch {
|
|
394
422
|
}
|
|
395
423
|
},
|
|
396
424
|
reset() {
|
|
397
425
|
try {
|
|
398
|
-
|
|
426
|
+
return withScopeLock(file, (stillOwner) => {
|
|
427
|
+
if (!stillOwner()) return false;
|
|
428
|
+
fs2.rmSync(file, { force: true });
|
|
429
|
+
return true;
|
|
430
|
+
});
|
|
399
431
|
} catch {
|
|
432
|
+
return false;
|
|
400
433
|
}
|
|
401
434
|
},
|
|
402
435
|
describe() {
|
|
@@ -417,8 +450,17 @@ function resetAllConnectionSessions(env = process.env) {
|
|
|
417
450
|
try {
|
|
418
451
|
for (const name of fs2.readdirSync(dir)) {
|
|
419
452
|
if (!name.endsWith(".json")) continue;
|
|
420
|
-
|
|
421
|
-
|
|
453
|
+
const file = path2.join(dir, name);
|
|
454
|
+
try {
|
|
455
|
+
if (withScopeLock(file, (stillOwner) => {
|
|
456
|
+
if (!stillOwner()) return false;
|
|
457
|
+
fs2.rmSync(file, { force: true });
|
|
458
|
+
return true;
|
|
459
|
+
})) {
|
|
460
|
+
removed += 1;
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
422
464
|
}
|
|
423
465
|
} catch {
|
|
424
466
|
}
|
|
@@ -462,7 +504,12 @@ var VideoDraftClient = class {
|
|
|
462
504
|
if (!this.session || this.sessionId !== void 0) return;
|
|
463
505
|
if (!this.handshake) {
|
|
464
506
|
this.handshake = (async () => {
|
|
465
|
-
|
|
507
|
+
let stored = null;
|
|
508
|
+
try {
|
|
509
|
+
stored = this.session.load();
|
|
510
|
+
} catch {
|
|
511
|
+
stored = null;
|
|
512
|
+
}
|
|
466
513
|
if (stored) {
|
|
467
514
|
this.sessionId = stored;
|
|
468
515
|
return;
|
package/dist/index.js
CHANGED
|
@@ -24,8 +24,8 @@ function readVersionFromDisk() {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
function resolveVersion() {
|
|
27
|
-
if ("0.15.
|
|
28
|
-
return "0.15.
|
|
27
|
+
if ("0.15.1") {
|
|
28
|
+
return "0.15.1";
|
|
29
29
|
}
|
|
30
30
|
return readVersionFromDisk();
|
|
31
31
|
}
|
|
@@ -663,7 +663,7 @@ async function revokeToken(baseUrl, token, fetchImpl = fetch) {
|
|
|
663
663
|
// src/core/session.ts
|
|
664
664
|
import fs3 from "fs";
|
|
665
665
|
import path2 from "path";
|
|
666
|
-
import { createHash } from "crypto";
|
|
666
|
+
import { createHash, randomBytes } from "crypto";
|
|
667
667
|
var SESSION_IDLE_MS = 12 * 60 * 60 * 1e3;
|
|
668
668
|
var MCP_SESSION_HEADER = "Mcp-Session-Id";
|
|
669
669
|
function sessionsDir(env) {
|
|
@@ -717,19 +717,6 @@ function writeRecord(file, record) {
|
|
|
717
717
|
fs3.writeFileSync(tmp, JSON.stringify(record, null, 2), { mode: 384 });
|
|
718
718
|
fs3.renameSync(tmp, file);
|
|
719
719
|
}
|
|
720
|
-
function claimRecord(file, record) {
|
|
721
|
-
fs3.mkdirSync(path2.dirname(file), { recursive: true, mode: 448 });
|
|
722
|
-
try {
|
|
723
|
-
fs3.writeFileSync(file, JSON.stringify(record, null, 2), {
|
|
724
|
-
mode: 384,
|
|
725
|
-
flag: "wx"
|
|
726
|
-
});
|
|
727
|
-
return true;
|
|
728
|
-
} catch (err) {
|
|
729
|
-
if (err?.code === "EEXIST") return false;
|
|
730
|
-
throw err;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
720
|
function isLive(record, nowMs) {
|
|
734
721
|
const idle = nowMs - Date.parse(record.lastUsedAt);
|
|
735
722
|
return Number.isFinite(idle) && idle <= SESSION_IDLE_MS;
|
|
@@ -745,33 +732,61 @@ function sleepSync(ms) {
|
|
|
745
732
|
}
|
|
746
733
|
function acquireReclaimLock(file) {
|
|
747
734
|
const lock = `${file}.lock`;
|
|
735
|
+
const owner = `${process.pid}:${randomBytes(6).toString("hex")}`;
|
|
748
736
|
try {
|
|
749
|
-
fs3.
|
|
750
|
-
|
|
737
|
+
fs3.mkdirSync(path2.dirname(lock), { recursive: true, mode: 448 });
|
|
738
|
+
fs3.writeFileSync(lock, owner, { mode: 384, flag: "wx" });
|
|
739
|
+
return owner;
|
|
751
740
|
} catch (err) {
|
|
752
|
-
if (err?.code !== "EEXIST")
|
|
741
|
+
if (err?.code !== "EEXIST") throw err;
|
|
753
742
|
try {
|
|
754
743
|
const age = Date.now() - fs3.statSync(lock).mtimeMs;
|
|
755
|
-
if (age <= RECLAIM_LOCK_STALE_MS) return
|
|
744
|
+
if (age <= RECLAIM_LOCK_STALE_MS) return null;
|
|
756
745
|
const claimed = `${lock}.break.${process.pid}`;
|
|
757
746
|
fs3.renameSync(lock, claimed);
|
|
758
747
|
fs3.rmSync(claimed, { force: true });
|
|
759
|
-
fs3.writeFileSync(lock,
|
|
760
|
-
|
|
761
|
-
flag: "wx"
|
|
762
|
-
});
|
|
763
|
-
return true;
|
|
748
|
+
fs3.writeFileSync(lock, owner, { mode: 384, flag: "wx" });
|
|
749
|
+
return owner;
|
|
764
750
|
} catch {
|
|
765
|
-
return
|
|
751
|
+
return null;
|
|
766
752
|
}
|
|
767
753
|
}
|
|
768
754
|
}
|
|
769
|
-
function releaseReclaimLock(file) {
|
|
755
|
+
function releaseReclaimLock(file, owner) {
|
|
756
|
+
const lock = `${file}.lock`;
|
|
770
757
|
try {
|
|
771
|
-
fs3.
|
|
758
|
+
if (fs3.readFileSync(lock, "utf8") !== owner) return;
|
|
759
|
+
fs3.rmSync(lock, { force: true });
|
|
772
760
|
} catch {
|
|
773
761
|
}
|
|
774
762
|
}
|
|
763
|
+
function withScopeLock(file, fn) {
|
|
764
|
+
const deadline = Date.now() + RECLAIM_LOCK_STALE_MS + RECLAIM_WAIT_MS;
|
|
765
|
+
let owner;
|
|
766
|
+
try {
|
|
767
|
+
owner = acquireReclaimLock(file);
|
|
768
|
+
while (!owner && Date.now() < deadline) {
|
|
769
|
+
sleepSync(RECLAIM_POLL_MS);
|
|
770
|
+
owner = acquireReclaimLock(file);
|
|
771
|
+
}
|
|
772
|
+
} catch {
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
if (!owner) return false;
|
|
776
|
+
const lockPath = `${file}.lock`;
|
|
777
|
+
const stillOwner = () => {
|
|
778
|
+
try {
|
|
779
|
+
return fs3.readFileSync(lockPath, "utf8") === owner;
|
|
780
|
+
} catch {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
try {
|
|
785
|
+
return fn(stillOwner);
|
|
786
|
+
} finally {
|
|
787
|
+
releaseReclaimLock(file, owner);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
775
790
|
function createConnectionSessionStore(input) {
|
|
776
791
|
const env = input.env ?? process.env;
|
|
777
792
|
const now = input.now ?? Date.now;
|
|
@@ -794,58 +809,76 @@ function createConnectionSessionStore(input) {
|
|
|
794
809
|
load() {
|
|
795
810
|
const record = readRecord(file);
|
|
796
811
|
if (!record || !isLive(record, now())) return null;
|
|
797
|
-
|
|
812
|
+
const quickDeadline = Date.now() + 5 * RECLAIM_POLL_MS;
|
|
813
|
+
let owner;
|
|
814
|
+
try {
|
|
815
|
+
owner = acquireReclaimLock(file);
|
|
816
|
+
while (!owner && Date.now() < quickDeadline) {
|
|
817
|
+
sleepSync(RECLAIM_POLL_MS);
|
|
818
|
+
owner = acquireReclaimLock(file);
|
|
819
|
+
}
|
|
820
|
+
} catch {
|
|
821
|
+
return record.token;
|
|
822
|
+
}
|
|
823
|
+
if (owner) {
|
|
798
824
|
try {
|
|
799
825
|
const current = readRecord(file);
|
|
800
|
-
if (current
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
826
|
+
if (!current || !isLive(current, now())) return null;
|
|
827
|
+
try {
|
|
828
|
+
const lockPath = `${file}.lock`;
|
|
829
|
+
if (fs3.readFileSync(lockPath, "utf8") === owner) {
|
|
830
|
+
writeRecord(file, {
|
|
831
|
+
...current,
|
|
832
|
+
lastUsedAt: new Date(now()).toISOString()
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
} catch {
|
|
805
836
|
}
|
|
806
|
-
|
|
837
|
+
return current.token;
|
|
807
838
|
} finally {
|
|
808
|
-
releaseReclaimLock(file);
|
|
839
|
+
releaseReclaimLock(file, owner);
|
|
809
840
|
}
|
|
810
841
|
}
|
|
811
|
-
return
|
|
842
|
+
return null;
|
|
812
843
|
},
|
|
813
844
|
save(token) {
|
|
814
845
|
try {
|
|
815
|
-
|
|
816
|
-
const
|
|
817
|
-
if (winner && isLive(winner, now())) return winner.token;
|
|
818
|
-
if (acquireReclaimLock(file)) {
|
|
819
|
-
try {
|
|
820
|
-
const current = readRecord(file);
|
|
821
|
-
if (current && isLive(current, now())) return current.token;
|
|
822
|
-
writeRecord(file, fresh(token));
|
|
823
|
-
return token;
|
|
824
|
-
} finally {
|
|
825
|
-
releaseReclaimLock(file);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
const deadline = Date.now() + RECLAIM_WAIT_MS;
|
|
829
|
-
while (Date.now() < deadline) {
|
|
846
|
+
let result = null;
|
|
847
|
+
const ran = withScopeLock(file, (stillOwner) => {
|
|
830
848
|
const current = readRecord(file);
|
|
831
|
-
if (current && isLive(current, now()))
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
849
|
+
if (current && isLive(current, now())) {
|
|
850
|
+
result = current.token;
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
if (!stillOwner()) return false;
|
|
854
|
+
writeRecord(file, fresh(token));
|
|
855
|
+
result = token;
|
|
856
|
+
return true;
|
|
857
|
+
});
|
|
858
|
+
return ran ? result : null;
|
|
835
859
|
} catch {
|
|
836
|
-
return
|
|
860
|
+
return null;
|
|
837
861
|
}
|
|
838
862
|
},
|
|
839
863
|
replace(token) {
|
|
840
864
|
try {
|
|
841
|
-
|
|
865
|
+
withScopeLock(file, (stillOwner) => {
|
|
866
|
+
if (!stillOwner()) return false;
|
|
867
|
+
writeRecord(file, fresh(token));
|
|
868
|
+
return true;
|
|
869
|
+
});
|
|
842
870
|
} catch {
|
|
843
871
|
}
|
|
844
872
|
},
|
|
845
873
|
reset() {
|
|
846
874
|
try {
|
|
847
|
-
|
|
875
|
+
return withScopeLock(file, (stillOwner) => {
|
|
876
|
+
if (!stillOwner()) return false;
|
|
877
|
+
fs3.rmSync(file, { force: true });
|
|
878
|
+
return true;
|
|
879
|
+
});
|
|
848
880
|
} catch {
|
|
881
|
+
return false;
|
|
849
882
|
}
|
|
850
883
|
},
|
|
851
884
|
describe() {
|
|
@@ -866,8 +899,17 @@ function resetAllConnectionSessions(env = process.env) {
|
|
|
866
899
|
try {
|
|
867
900
|
for (const name of fs3.readdirSync(dir)) {
|
|
868
901
|
if (!name.endsWith(".json")) continue;
|
|
869
|
-
|
|
870
|
-
|
|
902
|
+
const file = path2.join(dir, name);
|
|
903
|
+
try {
|
|
904
|
+
if (withScopeLock(file, (stillOwner) => {
|
|
905
|
+
if (!stillOwner()) return false;
|
|
906
|
+
fs3.rmSync(file, { force: true });
|
|
907
|
+
return true;
|
|
908
|
+
})) {
|
|
909
|
+
removed += 1;
|
|
910
|
+
}
|
|
911
|
+
} catch {
|
|
912
|
+
}
|
|
871
913
|
}
|
|
872
914
|
} catch {
|
|
873
915
|
}
|
|
@@ -911,7 +953,12 @@ var VideoDraftClient = class {
|
|
|
911
953
|
if (!this.session || this.sessionId !== void 0) return;
|
|
912
954
|
if (!this.handshake) {
|
|
913
955
|
this.handshake = (async () => {
|
|
914
|
-
|
|
956
|
+
let stored = null;
|
|
957
|
+
try {
|
|
958
|
+
stored = this.session.load();
|
|
959
|
+
} catch {
|
|
960
|
+
stored = null;
|
|
961
|
+
}
|
|
915
962
|
if (stored) {
|
|
916
963
|
this.sessionId = stored;
|
|
917
964
|
return;
|
|
@@ -1199,6 +1246,9 @@ var VideoDraftClient = class {
|
|
|
1199
1246
|
}
|
|
1200
1247
|
};
|
|
1201
1248
|
|
|
1249
|
+
// src/cli/context.ts
|
|
1250
|
+
import { createHash as createHash2 } from "crypto";
|
|
1251
|
+
|
|
1202
1252
|
// src/auth/token-provider.ts
|
|
1203
1253
|
var EXPIRY_SKEW_MS = 6e4;
|
|
1204
1254
|
function isExpiring(profile) {
|
|
@@ -1312,6 +1362,14 @@ function sessionArg(command, opts) {
|
|
|
1312
1362
|
}
|
|
1313
1363
|
return opts.session;
|
|
1314
1364
|
}
|
|
1365
|
+
function sessionProfileKey(profileName, explicitToken) {
|
|
1366
|
+
if (profileName) return `profile:${profileName}`;
|
|
1367
|
+
if (explicitToken) {
|
|
1368
|
+
const fp = createHash2("sha256").update(explicitToken).digest("hex").slice(0, 16);
|
|
1369
|
+
return `token:${fp}`;
|
|
1370
|
+
}
|
|
1371
|
+
return "default";
|
|
1372
|
+
}
|
|
1315
1373
|
function connectionSessionEnabled(env = process.env) {
|
|
1316
1374
|
const v = env.VIDEODRAFT_NO_SESSION?.trim().toLowerCase();
|
|
1317
1375
|
return !(v === "1" || v === "true" || v === "yes");
|
|
@@ -1342,7 +1400,10 @@ function buildContext(command) {
|
|
|
1342
1400
|
});
|
|
1343
1401
|
const session = connectionSessionEnabled() ? createConnectionSessionStore({
|
|
1344
1402
|
baseUrl: auth.baseUrl,
|
|
1345
|
-
profile:
|
|
1403
|
+
profile: sessionProfileKey(
|
|
1404
|
+
auth.profileName,
|
|
1405
|
+
flags.token ?? process.env.VIDEODRAFT_API_KEY
|
|
1406
|
+
)
|
|
1346
1407
|
}) : void 0;
|
|
1347
1408
|
const client = new VideoDraftClient({
|
|
1348
1409
|
tokenProvider: auth.tokenProvider,
|
|
@@ -1887,25 +1948,33 @@ ${section.toUpperCase()}
|
|
|
1887
1948
|
"Show the connection session this directory's standalone generations are filed under"
|
|
1888
1949
|
).action(async function() {
|
|
1889
1950
|
const ctx = buildContext(this);
|
|
1890
|
-
const
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
const sessionId = active ? info?.sessionId ?? null : null;
|
|
1894
|
-
let created = false;
|
|
1895
|
-
if (sessionId) {
|
|
1951
|
+
const knownIds = /* @__PURE__ */ new Set();
|
|
1952
|
+
let listed = false;
|
|
1953
|
+
if (ctx.session) {
|
|
1896
1954
|
try {
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
(
|
|
1904
|
-
|
|
1955
|
+
for (let offset = 0; offset < 2e3; ) {
|
|
1956
|
+
const page = await ctx.client.callTool(
|
|
1957
|
+
"list_ai_studio_sessions",
|
|
1958
|
+
{ limit: 200, offset }
|
|
1959
|
+
);
|
|
1960
|
+
const pageRows = page?.sessions ?? [];
|
|
1961
|
+
for (const row of pageRows) {
|
|
1962
|
+
const id = row?.id ?? row?.session_id;
|
|
1963
|
+
if (typeof id === "string") knownIds.add(id);
|
|
1964
|
+
}
|
|
1965
|
+
if (pageRows.length < 200) break;
|
|
1966
|
+
offset += pageRows.length;
|
|
1967
|
+
}
|
|
1968
|
+
listed = true;
|
|
1905
1969
|
} catch {
|
|
1906
|
-
|
|
1970
|
+
listed = false;
|
|
1907
1971
|
}
|
|
1908
1972
|
}
|
|
1973
|
+
const info = ctx.session?.describe();
|
|
1974
|
+
const record = info?.record ?? null;
|
|
1975
|
+
const active = Boolean(record) && !info?.expired;
|
|
1976
|
+
const sessionId = active ? info?.sessionId ?? null : null;
|
|
1977
|
+
const created = Boolean(sessionId) && listed && knownIds.has(sessionId);
|
|
1909
1978
|
const result = {
|
|
1910
1979
|
enabled: Boolean(ctx.session),
|
|
1911
1980
|
scope: info?.scope ?? null,
|
|
@@ -1936,21 +2005,24 @@ ${result.url ? `${result.url}
|
|
|
1936
2005
|
const ctx = buildContext(this);
|
|
1937
2006
|
const opts = this.opts();
|
|
1938
2007
|
let removed = 0;
|
|
2008
|
+
let declined = false;
|
|
1939
2009
|
if (opts.all) {
|
|
1940
2010
|
removed = resetAllConnectionSessions();
|
|
1941
2011
|
} else if (ctx.session) {
|
|
1942
2012
|
const had = Boolean(ctx.session.describe().record);
|
|
1943
|
-
ctx.session.reset();
|
|
1944
|
-
removed = had ? 1 : 0;
|
|
2013
|
+
const ran = ctx.session.reset();
|
|
2014
|
+
removed = had && ran ? 1 : 0;
|
|
2015
|
+
declined = had && !ran;
|
|
1945
2016
|
}
|
|
1946
2017
|
const result = {
|
|
1947
2018
|
reset: removed,
|
|
2019
|
+
declined,
|
|
1948
2020
|
scope: opts.all ? "all" : ctx.session?.describe().scope ?? null
|
|
1949
2021
|
};
|
|
1950
2022
|
emit(ctx.out, result, () => {
|
|
1951
2023
|
process.stdout.write(
|
|
1952
2024
|
removed > 0 ? `Reset ${removed} connection session${removed === 1 ? "" : "s"}; the next generation starts a new AI Studio session.
|
|
1953
|
-
` : "Nothing to reset.\n"
|
|
2025
|
+
` : declined ? "Could not reset: the session store is locked or unwritable; the session may still be in use.\n" : "Nothing to reset.\n"
|
|
1954
2026
|
);
|
|
1955
2027
|
});
|
|
1956
2028
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "videodraft",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Official VideoDraft CLI — create AI videos, images and audio from your terminal. Agent-friendly: --json everywhere, stable exit codes, async job polling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|