skillvault-publisher 0.11.1 → 0.11.2
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/credentials.js +59 -4
- package/dist/index.js +19 -19
- package/dist/projects-registry.js +97 -17
- package/package.json +1 -1
package/dist/credentials.js
CHANGED
|
@@ -15,10 +15,60 @@
|
|
|
15
15
|
* Stored at <roots.configDir>/project.json. Tracks active publishers and
|
|
16
16
|
* install metadata for one install (project- or global-scoped).
|
|
17
17
|
*/
|
|
18
|
-
import { mkdirSync, readFileSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
|
19
|
-
import { join } from 'node:path';
|
|
18
|
+
import { mkdirSync, readFileSync, writeFileSync, rmSync, existsSync, openSync, closeSync, unlinkSync, } from 'node:fs';
|
|
19
|
+
import { join, dirname } from 'node:path';
|
|
20
20
|
import { homedir } from 'node:os';
|
|
21
21
|
import { credentialsPath } from './scope.js';
|
|
22
|
+
/**
|
|
23
|
+
* Run `fn` while holding an exclusive advisory lock on `<targetPath>.lock`.
|
|
24
|
+
* Mirrors the helper in projects-registry.ts. See that file for design notes.
|
|
25
|
+
*/
|
|
26
|
+
function withFileLock(targetPath, fn) {
|
|
27
|
+
const lockPath = targetPath + '.lock';
|
|
28
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
29
|
+
const start = Date.now();
|
|
30
|
+
const TIMEOUT_MS = 1000;
|
|
31
|
+
let fd = null;
|
|
32
|
+
while (fd === null) {
|
|
33
|
+
try {
|
|
34
|
+
fd = openSync(lockPath, 'wx', 0o600);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const code = err.code;
|
|
38
|
+
if (code !== 'EEXIST')
|
|
39
|
+
throw err;
|
|
40
|
+
if (Date.now() - start > TIMEOUT_MS) {
|
|
41
|
+
try {
|
|
42
|
+
unlinkSync(lockPath);
|
|
43
|
+
}
|
|
44
|
+
catch { }
|
|
45
|
+
try {
|
|
46
|
+
fd = openSync(lockPath, 'wx', 0o600);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new Error(`Could not acquire lock at ${lockPath} after ${TIMEOUT_MS}ms`);
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
const buf = new SharedArrayBuffer(4);
|
|
54
|
+
const view = new Int32Array(buf);
|
|
55
|
+
Atomics.wait(view, 0, 0, 5 + Math.floor(Math.random() * 10));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return fn();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
try {
|
|
63
|
+
closeSync(fd);
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
try {
|
|
67
|
+
unlinkSync(lockPath);
|
|
68
|
+
}
|
|
69
|
+
catch { }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
22
72
|
const CONFIG_DIR = join(homedir(), '.skillvault');
|
|
23
73
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
24
74
|
const GRANTS_CACHE_FILE = join(CONFIG_DIR, 'grants-cache.json');
|
|
@@ -73,10 +123,15 @@ export function loadCredentials() {
|
|
|
73
123
|
/**
|
|
74
124
|
* Persist customer credentials to ~/.skillvault/credentials.json with mode 0600.
|
|
75
125
|
* The credentials file holds bearer tokens, so it must never be world-readable.
|
|
126
|
+
*
|
|
127
|
+
* Wrapped in withFileLock to serialize concurrent writes — see the equivalent
|
|
128
|
+
* fix in projects-registry.ts.
|
|
76
129
|
*/
|
|
77
130
|
export function saveCredentials(creds) {
|
|
78
|
-
|
|
79
|
-
|
|
131
|
+
withFileLock(credentialsPath(), () => {
|
|
132
|
+
ensureConfigDir();
|
|
133
|
+
writeFileSync(credentialsPath(), JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
134
|
+
});
|
|
80
135
|
}
|
|
81
136
|
function projectConfigPath(roots) {
|
|
82
137
|
return join(roots.configDir, 'project.json');
|
package/dist/index.js
CHANGED
|
@@ -151807,7 +151807,7 @@ var init_pdf_report = __esm({
|
|
|
151807
151807
|
import { Command as Command35 } from "commander";
|
|
151808
151808
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
151809
151809
|
import { fileURLToPath } from "node:url";
|
|
151810
|
-
import { dirname as
|
|
151810
|
+
import { dirname as dirname5, join as join17 } from "node:path";
|
|
151811
151811
|
|
|
151812
151812
|
// dist/commands/login.js
|
|
151813
151813
|
import { Command } from "commander";
|
|
@@ -151815,8 +151815,8 @@ import chalk from "chalk";
|
|
|
151815
151815
|
import { randomBytes } from "node:crypto";
|
|
151816
151816
|
|
|
151817
151817
|
// dist/credentials.js
|
|
151818
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, rmSync, existsSync as existsSync2 } from "node:fs";
|
|
151819
|
-
import { join as join2 } from "node:path";
|
|
151818
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, rmSync, existsSync as existsSync2, openSync as openSync2, closeSync as closeSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
151819
|
+
import { join as join2, dirname as dirname2 } from "node:path";
|
|
151820
151820
|
import { homedir as homedir2 } from "node:os";
|
|
151821
151821
|
|
|
151822
151822
|
// dist/scope.js
|
|
@@ -151824,7 +151824,7 @@ import { homedir } from "node:os";
|
|
|
151824
151824
|
import { join } from "node:path";
|
|
151825
151825
|
|
|
151826
151826
|
// dist/projects-registry.js
|
|
151827
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync, statSync } from "node:fs";
|
|
151827
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, statSync, openSync, closeSync, unlinkSync } from "node:fs";
|
|
151828
151828
|
import { dirname, resolve } from "node:path";
|
|
151829
151829
|
|
|
151830
151830
|
// dist/credentials.js
|
|
@@ -152534,7 +152534,7 @@ import chalk4 from "chalk";
|
|
|
152534
152534
|
|
|
152535
152535
|
// dist/publisher-workspace.js
|
|
152536
152536
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync4 } from "node:fs";
|
|
152537
|
-
import { join as join4, dirname as
|
|
152537
|
+
import { join as join4, dirname as dirname3, resolve as resolve2 } from "node:path";
|
|
152538
152538
|
import { homedir as homedir3 } from "node:os";
|
|
152539
152539
|
var MANIFEST_DIRNAME = ".skillvault-publisher";
|
|
152540
152540
|
var MANIFEST_FILENAME = "workspace.json";
|
|
@@ -152582,7 +152582,7 @@ function loadWorkspace(dir) {
|
|
|
152582
152582
|
}
|
|
152583
152583
|
function saveWorkspace(dir, manifest) {
|
|
152584
152584
|
const { manifestPath } = workspaceRootsFor(dir);
|
|
152585
|
-
mkdirSync4(
|
|
152585
|
+
mkdirSync4(dirname3(manifestPath), { recursive: true });
|
|
152586
152586
|
writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2), { mode: 420 });
|
|
152587
152587
|
}
|
|
152588
152588
|
function findWorkspace(cwd) {
|
|
@@ -152605,7 +152605,7 @@ function findWorkspace(cwd) {
|
|
|
152605
152605
|
return null;
|
|
152606
152606
|
if (dir === home)
|
|
152607
152607
|
return null;
|
|
152608
|
-
const parent =
|
|
152608
|
+
const parent = dirname3(dir);
|
|
152609
152609
|
if (parent === dir)
|
|
152610
152610
|
return null;
|
|
152611
152611
|
dir = parent;
|
|
@@ -152696,7 +152696,7 @@ function requireCommandContext(opts = {}) {
|
|
|
152696
152696
|
|
|
152697
152697
|
// dist/publisher-workspaces-registry.js
|
|
152698
152698
|
import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync5, statSync as statSync2 } from "node:fs";
|
|
152699
|
-
import { dirname as
|
|
152699
|
+
import { dirname as dirname4, join as join5, resolve as resolve3 } from "node:path";
|
|
152700
152700
|
import { homedir as homedir4 } from "node:os";
|
|
152701
152701
|
var REGISTRY_VERSION = 1;
|
|
152702
152702
|
var MANIFEST_DIRNAME2 = ".skillvault-publisher";
|
|
@@ -152722,7 +152722,7 @@ function readRegistry() {
|
|
|
152722
152722
|
}
|
|
152723
152723
|
function writeRegistry(reg) {
|
|
152724
152724
|
const path = publisherWorkspacesRegistryPath();
|
|
152725
|
-
mkdirSync5(
|
|
152725
|
+
mkdirSync5(dirname4(path), { recursive: true });
|
|
152726
152726
|
writeFileSync5(path, JSON.stringify(reg, null, 2), { mode: 384 });
|
|
152727
152727
|
}
|
|
152728
152728
|
function listWorkspaces() {
|
|
@@ -154307,12 +154307,12 @@ var inviteCommand = new Command15("invite").description("Manage customer invites
|
|
|
154307
154307
|
|
|
154308
154308
|
// dist/commands/scan-output.js
|
|
154309
154309
|
import { Command as Command16 } from "commander";
|
|
154310
|
-
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync11, readdirSync as readdirSync3, unlinkSync as
|
|
154310
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "node:fs";
|
|
154311
154311
|
import { join as join11 } from "node:path";
|
|
154312
154312
|
import { homedir as homedir6 } from "node:os";
|
|
154313
154313
|
|
|
154314
154314
|
// dist/commands/session-common.js
|
|
154315
|
-
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync10, unlinkSync } from "node:fs";
|
|
154315
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync10, unlinkSync as unlinkSync3 } from "node:fs";
|
|
154316
154316
|
import { join as join10 } from "node:path";
|
|
154317
154317
|
import { homedir as homedir5 } from "node:os";
|
|
154318
154318
|
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes6, hkdfSync as hkdfSync2 } from "node:crypto";
|
|
@@ -154372,7 +154372,7 @@ function loadActiveSession() {
|
|
|
154372
154372
|
const decrypted = decryptData(raw, HKDF_SALT2);
|
|
154373
154373
|
if (!decrypted) {
|
|
154374
154374
|
try {
|
|
154375
|
-
|
|
154375
|
+
unlinkSync3(ACTIVE_SESSION_PATH);
|
|
154376
154376
|
} catch {
|
|
154377
154377
|
}
|
|
154378
154378
|
return null;
|
|
@@ -154380,7 +154380,7 @@ function loadActiveSession() {
|
|
|
154380
154380
|
const session2 = JSON.parse(decrypted);
|
|
154381
154381
|
if (new Date(session2.expires_at).getTime() < Date.now()) {
|
|
154382
154382
|
try {
|
|
154383
|
-
|
|
154383
|
+
unlinkSync3(ACTIVE_SESSION_PATH);
|
|
154384
154384
|
} catch {
|
|
154385
154385
|
}
|
|
154386
154386
|
return null;
|
|
@@ -154392,7 +154392,7 @@ function loadActiveSession() {
|
|
|
154392
154392
|
return null;
|
|
154393
154393
|
if (new Date(session.expires_at).getTime() < Date.now()) {
|
|
154394
154394
|
try {
|
|
154395
|
-
|
|
154395
|
+
unlinkSync3(ACTIVE_SESSION_PATH);
|
|
154396
154396
|
} catch {
|
|
154397
154397
|
}
|
|
154398
154398
|
return null;
|
|
@@ -154405,7 +154405,7 @@ function loadActiveSession() {
|
|
|
154405
154405
|
function deleteActiveSession() {
|
|
154406
154406
|
try {
|
|
154407
154407
|
if (existsSync10(ACTIVE_SESSION_PATH)) {
|
|
154408
|
-
|
|
154408
|
+
unlinkSync3(ACTIVE_SESSION_PATH);
|
|
154409
154409
|
}
|
|
154410
154410
|
} catch {
|
|
154411
154411
|
}
|
|
@@ -154466,7 +154466,7 @@ function loadFingerprints() {
|
|
|
154466
154466
|
const decrypted = decryptData(raw, FINGERPRINT_HKDF_SALT);
|
|
154467
154467
|
if (!decrypted) {
|
|
154468
154468
|
try {
|
|
154469
|
-
|
|
154469
|
+
unlinkSync4(filePath);
|
|
154470
154470
|
} catch {
|
|
154471
154471
|
}
|
|
154472
154472
|
continue;
|
|
@@ -154776,7 +154776,7 @@ var sessionKeepaliveCommand = new Command18("session-keepalive").description("Un
|
|
|
154776
154776
|
|
|
154777
154777
|
// dist/commands/session-cleanup.js
|
|
154778
154778
|
import { Command as Command19 } from "commander";
|
|
154779
|
-
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync13, readdirSync as readdirSync4, unlinkSync as
|
|
154779
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync13, readdirSync as readdirSync4, unlinkSync as unlinkSync5 } from "node:fs";
|
|
154780
154780
|
import { join as join13 } from "node:path";
|
|
154781
154781
|
import { homedir as homedir8 } from "node:os";
|
|
154782
154782
|
var SETTINGS_PATH2 = join13(homedir8(), ".claude", "settings.json");
|
|
@@ -154830,7 +154830,7 @@ function clearFingerprintCache() {
|
|
|
154830
154830
|
const files = readdirSync4(FINGERPRINT_DIR3);
|
|
154831
154831
|
for (const file of files) {
|
|
154832
154832
|
try {
|
|
154833
|
-
|
|
154833
|
+
unlinkSync5(join13(FINGERPRINT_DIR3, file));
|
|
154834
154834
|
} catch {
|
|
154835
154835
|
}
|
|
154836
154836
|
}
|
|
@@ -156161,7 +156161,7 @@ workspacesCommand.command("find").description("Print the absolute path of a work
|
|
|
156161
156161
|
});
|
|
156162
156162
|
|
|
156163
156163
|
// dist/index.js
|
|
156164
|
-
var __dirname2 =
|
|
156164
|
+
var __dirname2 = dirname5(fileURLToPath(import.meta.url));
|
|
156165
156165
|
var pkg = JSON.parse(readFileSync18(join17(__dirname2, "..", "package.json"), "utf8"));
|
|
156166
156166
|
var program = new Command35();
|
|
156167
156167
|
program.name("skillvault-publisher").description("SkillVault publisher CLI \u2014 publish, manage, and distribute encrypted skills").version(pkg.version).addHelpText("after", `
|
|
@@ -6,10 +6,80 @@
|
|
|
6
6
|
* `getCurrentProject()` to figure out whether the current CWD already has a
|
|
7
7
|
* registered SkillVault install.
|
|
8
8
|
*/
|
|
9
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';
|
|
9
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, statSync, openSync, closeSync, unlinkSync, } from 'node:fs';
|
|
10
10
|
import { dirname, resolve } from 'node:path';
|
|
11
11
|
import { projectsRegistryPath } from './scope.js';
|
|
12
12
|
const REGISTRY_VERSION = 1;
|
|
13
|
+
/**
|
|
14
|
+
* Run `fn` while holding an exclusive advisory lock on `<targetPath>.lock`.
|
|
15
|
+
*
|
|
16
|
+
* Uses `openSync(..., 'wx')` to atomically create the lockfile (POSIX
|
|
17
|
+
* O_EXCL semantics — exactly one process succeeds, the rest get EEXIST).
|
|
18
|
+
* On contention, busy-waits with a small backoff up to ~1s before giving
|
|
19
|
+
* up. The lockfile is unlinked on completion (success or thrown error)
|
|
20
|
+
* via try/finally so a crash mid-operation can't permanently wedge
|
|
21
|
+
* subsequent invocations — though if a process is SIGKILL'd between the
|
|
22
|
+
* openSync and the closeSync, the lockfile will linger and the next
|
|
23
|
+
* caller will time out and either inherit the stale lock (forced) or
|
|
24
|
+
* throw. For our use case (a single-user CLI doing read-modify-write on
|
|
25
|
+
* a small JSON file), 1s is plenty.
|
|
26
|
+
*
|
|
27
|
+
* Used by registerProject + unregisterProject to fix the lost-update race
|
|
28
|
+
* when two `--invite` commands run concurrently against the same
|
|
29
|
+
* ~/.skillvault/projects.json (audit scenario 12-concurrent-install).
|
|
30
|
+
*/
|
|
31
|
+
function withFileLock(targetPath, fn) {
|
|
32
|
+
const lockPath = targetPath + '.lock';
|
|
33
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
34
|
+
const start = Date.now();
|
|
35
|
+
const TIMEOUT_MS = 1000;
|
|
36
|
+
let fd = null;
|
|
37
|
+
while (fd === null) {
|
|
38
|
+
try {
|
|
39
|
+
fd = openSync(lockPath, 'wx', 0o600);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
const code = err.code;
|
|
43
|
+
if (code !== 'EEXIST')
|
|
44
|
+
throw err;
|
|
45
|
+
if (Date.now() - start > TIMEOUT_MS) {
|
|
46
|
+
// Stale lock — assume the previous holder crashed and reclaim it.
|
|
47
|
+
try {
|
|
48
|
+
unlinkSync(lockPath);
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
// Loop one more time, but if it still fails, throw.
|
|
52
|
+
try {
|
|
53
|
+
fd = openSync(lockPath, 'wx', 0o600);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new Error(`Could not acquire lock at ${lockPath} after ${TIMEOUT_MS}ms`);
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
// Busy-wait briefly. The Node.js event loop is single-threaded so
|
|
61
|
+
// we can't yield to a peer process via Promise without making the
|
|
62
|
+
// function async — and registerProject is sync. Atomics.wait on a
|
|
63
|
+
// SharedArrayBuffer is the cleanest sync sleep:
|
|
64
|
+
const buf = new SharedArrayBuffer(4);
|
|
65
|
+
const view = new Int32Array(buf);
|
|
66
|
+
Atomics.wait(view, 0, 0, 5 + Math.floor(Math.random() * 10));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return fn();
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
try {
|
|
74
|
+
closeSync(fd);
|
|
75
|
+
}
|
|
76
|
+
catch { }
|
|
77
|
+
try {
|
|
78
|
+
unlinkSync(lockPath);
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
13
83
|
function readRegistry() {
|
|
14
84
|
const path = projectsRegistryPath();
|
|
15
85
|
if (!existsSync(path))
|
|
@@ -75,29 +145,39 @@ export function listProjects() {
|
|
|
75
145
|
/**
|
|
76
146
|
* Insert or update a project entry. Matching is by absolute path; if an entry
|
|
77
147
|
* with the same path already exists, it is replaced.
|
|
148
|
+
*
|
|
149
|
+
* The read-modify-write is wrapped in withFileLock so two concurrent
|
|
150
|
+
* `--invite` calls can't race and lose one of each other's updates.
|
|
78
151
|
*/
|
|
79
152
|
export function registerProject(entry) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
153
|
+
withFileLock(projectsRegistryPath(), () => {
|
|
154
|
+
const reg = readRegistry();
|
|
155
|
+
const normalized = { ...entry, path: resolve(entry.path) };
|
|
156
|
+
const idx = reg.projects.findIndex((p) => resolve(p.path) === normalized.path);
|
|
157
|
+
if (idx >= 0) {
|
|
158
|
+
reg.projects[idx] = normalized;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
reg.projects.push(normalized);
|
|
162
|
+
}
|
|
163
|
+
writeRegistry(reg);
|
|
164
|
+
});
|
|
90
165
|
}
|
|
91
166
|
/**
|
|
92
167
|
* Remove the project entry whose path matches. No-op if not present.
|
|
168
|
+
*
|
|
169
|
+
* Wrapped in withFileLock for the same reason as registerProject — a
|
|
170
|
+
* concurrent uninstall + register would otherwise race.
|
|
93
171
|
*/
|
|
94
172
|
export function unregisterProject(path) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
173
|
+
withFileLock(projectsRegistryPath(), () => {
|
|
174
|
+
const reg = readRegistry();
|
|
175
|
+
const target = resolve(path);
|
|
176
|
+
const before = reg.projects.length;
|
|
177
|
+
reg.projects = reg.projects.filter((p) => resolve(p.path) !== target);
|
|
178
|
+
if (reg.projects.length !== before)
|
|
179
|
+
writeRegistry(reg);
|
|
180
|
+
});
|
|
101
181
|
}
|
|
102
182
|
/**
|
|
103
183
|
* Look up the registry entry that matches the current working directory (or
|