@claudeskill/cli 0.1.0
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/api.d.ts +148 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +218 -0
- package/dist/api.js.map +1 -0
- package/dist/commands/checkout.d.ts +8 -0
- package/dist/commands/checkout.d.ts.map +1 -0
- package/dist/commands/checkout.js +134 -0
- package/dist/commands/checkout.js.map +1 -0
- package/dist/commands/diff.d.ts +8 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +156 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/list.d.ts +14 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +251 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/log.d.ts +8 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +74 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/login.d.ts +8 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +201 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +8 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +33 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/pull.d.ts +11 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +123 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +12 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +128 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +104 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +54 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials.d.ts +24 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +54 -0
- package/dist/credentials.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +1 -0
- package/dist/menu.d.ts +8 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +130 -0
- package/dist/menu.js.map +1 -0
- package/dist/onboarding.d.ts +8 -0
- package/dist/onboarding.d.ts.map +1 -0
- package/dist/onboarding.js +252 -0
- package/dist/onboarding.js.map +1 -0
- package/dist/sync.d.ts +83 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +294 -0
- package/dist/sync.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive onboarding flow for new users
|
|
3
|
+
*/
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import { generateSalt, generateMasterKey, generateRecoveryKey, deriveKeyFromPassphrase, encryptMasterKey, formatRecoveryKey, toBase64, listAllSkills, } from "@claudeskill/core";
|
|
6
|
+
import { saveConfig, getDefaultConfig, getConfigDir } from "./config.js";
|
|
7
|
+
import { saveCredentials } from "./credentials.js";
|
|
8
|
+
import * as api from "./api.js";
|
|
9
|
+
import { pushSkills } from "./sync.js";
|
|
10
|
+
/**
|
|
11
|
+
* Run the interactive onboarding flow
|
|
12
|
+
*/
|
|
13
|
+
export const runOnboarding = async () => {
|
|
14
|
+
p.intro("Claude Skill Sync");
|
|
15
|
+
// Step 1: Choose sync mode
|
|
16
|
+
const mode = await p.select({
|
|
17
|
+
message: "How would you like to sync your skills?",
|
|
18
|
+
options: [
|
|
19
|
+
{
|
|
20
|
+
value: "cloud",
|
|
21
|
+
label: "Cloud",
|
|
22
|
+
hint: "claudeskill.io - free, encrypted, zero-knowledge",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: "selfhosted",
|
|
26
|
+
label: "Self-hosted",
|
|
27
|
+
hint: "bring your own server",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
value: "local",
|
|
31
|
+
label: "Local only",
|
|
32
|
+
hint: "no sync, just organize",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
if (p.isCancel(mode)) {
|
|
37
|
+
p.cancel("Setup cancelled.");
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
const config = getDefaultConfig();
|
|
41
|
+
config.mode = mode;
|
|
42
|
+
// Step 2: Self-hosted server URL
|
|
43
|
+
if (mode === "selfhosted") {
|
|
44
|
+
const serverUrl = await p.text({
|
|
45
|
+
message: "Enter your server URL:",
|
|
46
|
+
placeholder: "https://skills.example.com",
|
|
47
|
+
validate: (value) => {
|
|
48
|
+
if (!value)
|
|
49
|
+
return "Server URL is required";
|
|
50
|
+
try {
|
|
51
|
+
new URL(value);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return "Invalid URL";
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
if (p.isCancel(serverUrl)) {
|
|
59
|
+
p.cancel("Setup cancelled.");
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
config.serverUrl = serverUrl;
|
|
63
|
+
}
|
|
64
|
+
// Save config early so API client can use it
|
|
65
|
+
await saveConfig(config);
|
|
66
|
+
// For cloud/selfhosted: authentication
|
|
67
|
+
if (mode === "cloud" || mode === "selfhosted") {
|
|
68
|
+
// Check server connectivity
|
|
69
|
+
const spinner = p.spinner();
|
|
70
|
+
spinner.start("Connecting to server...");
|
|
71
|
+
const healthResult = await api.checkHealth();
|
|
72
|
+
if (!healthResult.ok) {
|
|
73
|
+
spinner.stop("Connection failed");
|
|
74
|
+
p.log.error(`Cannot connect to server: ${healthResult.error}`);
|
|
75
|
+
p.log.info("Make sure the server is running and accessible.");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
spinner.stop(`Connected to ${healthResult.data.name} v${healthResult.data.version}`);
|
|
79
|
+
// Step 3: Email
|
|
80
|
+
const email = await p.text({
|
|
81
|
+
message: "Email for your account:",
|
|
82
|
+
placeholder: "you@example.com",
|
|
83
|
+
validate: (value) => {
|
|
84
|
+
if (!value)
|
|
85
|
+
return "Email is required";
|
|
86
|
+
if (!value.includes("@"))
|
|
87
|
+
return "Invalid email";
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
if (p.isCancel(email)) {
|
|
91
|
+
p.cancel("Setup cancelled.");
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
config.email = email;
|
|
95
|
+
await saveConfig(config);
|
|
96
|
+
// Step 4: Request OTP
|
|
97
|
+
spinner.start("Sending verification code...");
|
|
98
|
+
const otpResult = await api.requestOtp(email);
|
|
99
|
+
if (!otpResult.ok) {
|
|
100
|
+
spinner.stop("Failed to send code");
|
|
101
|
+
p.log.error(otpResult.error);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
spinner.stop("Verification code sent!");
|
|
105
|
+
// Step 5: Verify OTP
|
|
106
|
+
const code = await p.text({
|
|
107
|
+
message: "Enter the 6-digit code from your email:",
|
|
108
|
+
validate: (value) => {
|
|
109
|
+
if (!value)
|
|
110
|
+
return "Code is required";
|
|
111
|
+
if (!/^\d{6}$/.test(value))
|
|
112
|
+
return "Code must be 6 digits";
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
if (p.isCancel(code)) {
|
|
116
|
+
p.cancel("Setup cancelled.");
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
spinner.start("Verifying...");
|
|
120
|
+
const verifyResult = await api.verifyOtp(email, code);
|
|
121
|
+
if (!verifyResult.ok) {
|
|
122
|
+
spinner.stop("Verification failed");
|
|
123
|
+
p.log.error(verifyResult.error);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
spinner.stop("Verified!");
|
|
127
|
+
const { accessToken, refreshToken, user } = verifyResult.data;
|
|
128
|
+
// Step 6: Create passphrase
|
|
129
|
+
const passphrase = await p.password({
|
|
130
|
+
message: "Create a vault passphrase:",
|
|
131
|
+
mask: "*",
|
|
132
|
+
validate: (value) => {
|
|
133
|
+
if (!value)
|
|
134
|
+
return "Passphrase is required";
|
|
135
|
+
if (value.length < 8)
|
|
136
|
+
return "Passphrase must be at least 8 characters";
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
if (p.isCancel(passphrase)) {
|
|
140
|
+
p.cancel("Setup cancelled.");
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
const confirmPassphrase = await p.password({
|
|
144
|
+
message: "Confirm passphrase:",
|
|
145
|
+
mask: "*",
|
|
146
|
+
validate: (value) => {
|
|
147
|
+
if (value !== passphrase)
|
|
148
|
+
return "Passphrases do not match";
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
if (p.isCancel(confirmPassphrase)) {
|
|
152
|
+
p.cancel("Setup cancelled.");
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
// Step 7: Generate encryption keys
|
|
156
|
+
spinner.start("Generating encryption keys...");
|
|
157
|
+
const salt = generateSalt();
|
|
158
|
+
const masterKey = generateMasterKey();
|
|
159
|
+
const recoveryKey = generateRecoveryKey();
|
|
160
|
+
const derivedKey = deriveKeyFromPassphrase(passphrase, salt);
|
|
161
|
+
const encryptedMaster = encryptMasterKey(masterKey, derivedKey.key);
|
|
162
|
+
// Combine IV + tag + ciphertext for storage
|
|
163
|
+
const encryptedMasterKeyFull = new Uint8Array(encryptedMaster.iv.length +
|
|
164
|
+
encryptedMaster.tag.length +
|
|
165
|
+
encryptedMaster.encrypted.length);
|
|
166
|
+
encryptedMasterKeyFull.set(encryptedMaster.iv, 0);
|
|
167
|
+
encryptedMasterKeyFull.set(encryptedMaster.tag, encryptedMaster.iv.length);
|
|
168
|
+
encryptedMasterKeyFull.set(encryptedMaster.encrypted, encryptedMaster.iv.length + encryptedMaster.tag.length);
|
|
169
|
+
// Save credentials first so API calls have the access token
|
|
170
|
+
await saveCredentials({
|
|
171
|
+
accessToken,
|
|
172
|
+
refreshToken,
|
|
173
|
+
encryptedMasterKey: toBase64(encryptedMasterKeyFull),
|
|
174
|
+
salt: toBase64(salt),
|
|
175
|
+
});
|
|
176
|
+
// Upload salt to server
|
|
177
|
+
const saltResult = await api.setSalt(toBase64(salt));
|
|
178
|
+
if (!saltResult.ok) {
|
|
179
|
+
spinner.stop("Failed to save encryption settings");
|
|
180
|
+
p.log.error(saltResult.error);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
spinner.stop("Encryption keys generated");
|
|
184
|
+
// Step 8: Show recovery key
|
|
185
|
+
const formattedRecoveryKey = formatRecoveryKey(recoveryKey);
|
|
186
|
+
p.note(`
|
|
187
|
+
${formattedRecoveryKey}
|
|
188
|
+
|
|
189
|
+
This is the ONLY way to recover your skills if you
|
|
190
|
+
forget your passphrase. Store it somewhere safe
|
|
191
|
+
(password manager, printed paper, etc).
|
|
192
|
+
|
|
193
|
+
We cannot recover your data.
|
|
194
|
+
`.trim(), "SAVE YOUR RECOVERY KEY");
|
|
195
|
+
const savedRecoveryKey = await p.confirm({
|
|
196
|
+
message: "I have saved my recovery key",
|
|
197
|
+
});
|
|
198
|
+
if (p.isCancel(savedRecoveryKey) || !savedRecoveryKey) {
|
|
199
|
+
const showAgain = await p.confirm({
|
|
200
|
+
message: "Show recovery key again?",
|
|
201
|
+
});
|
|
202
|
+
if (showAgain) {
|
|
203
|
+
p.note(formattedRecoveryKey, "RECOVERY KEY");
|
|
204
|
+
}
|
|
205
|
+
const reallySaved = await p.confirm({
|
|
206
|
+
message: "I have saved my recovery key",
|
|
207
|
+
});
|
|
208
|
+
if (p.isCancel(reallySaved) || !reallySaved) {
|
|
209
|
+
p.cancel("You must save your recovery key to continue.");
|
|
210
|
+
process.exit(0);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
p.log.success("Encryption configured");
|
|
214
|
+
// Step 10: Check for existing skills and offer to sync
|
|
215
|
+
const skills = await listAllSkills();
|
|
216
|
+
if (skills.length > 0) {
|
|
217
|
+
const syncNow = await p.confirm({
|
|
218
|
+
message: `Found ${skills.length} item${skills.length === 1 ? "" : "s"} (skills, commands, agents). Push them now?`,
|
|
219
|
+
});
|
|
220
|
+
if (!p.isCancel(syncNow) && syncNow) {
|
|
221
|
+
spinner.start("Pushing skills...");
|
|
222
|
+
const { pushed, errors } = await pushSkills(masterKey, (msg) => {
|
|
223
|
+
spinner.message(msg);
|
|
224
|
+
}, undefined);
|
|
225
|
+
spinner.stop("Push complete");
|
|
226
|
+
if (pushed > 0) {
|
|
227
|
+
p.log.success(`Pushed ${pushed} skill${pushed === 1 ? "" : "s"}`);
|
|
228
|
+
}
|
|
229
|
+
if (errors.length > 0) {
|
|
230
|
+
p.log.warning("Some skills failed:");
|
|
231
|
+
errors.forEach((error) => {
|
|
232
|
+
p.log.error(` ${error}`);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// Local mode - just save config
|
|
240
|
+
await saveConfig(config);
|
|
241
|
+
}
|
|
242
|
+
// Done!
|
|
243
|
+
p.note(`
|
|
244
|
+
claude-skill-sync list List all skills
|
|
245
|
+
claude-skill-sync pull Pull from cloud
|
|
246
|
+
claude-skill-sync push Push to cloud
|
|
247
|
+
claude-skill-sync status Check sync status
|
|
248
|
+
`.trim(), "Quick commands");
|
|
249
|
+
p.log.info(`Config saved to ${getConfigDir()}`);
|
|
250
|
+
p.outro("You're all set!");
|
|
251
|
+
};
|
|
252
|
+
//# sourceMappingURL=onboarding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboarding.js","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"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"}
|
package/dist/sync.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync logic for skills
|
|
3
|
+
*
|
|
4
|
+
* Handles encrypting, uploading, downloading, and decrypting skills.
|
|
5
|
+
*/
|
|
6
|
+
import { type Skill, type SkillType } from "@claudeskill/core";
|
|
7
|
+
/** Local index file tracking synced skills */
|
|
8
|
+
type SyncIndex = {
|
|
9
|
+
/** Map of skill key to sync info */
|
|
10
|
+
skills: Record<string, {
|
|
11
|
+
/** Content hash of the skill (version identifier) */
|
|
12
|
+
hash: string;
|
|
13
|
+
/** Legacy blob ID (for migration) */
|
|
14
|
+
blobId: string | null;
|
|
15
|
+
/** Local content hash for change detection */
|
|
16
|
+
localHash: string;
|
|
17
|
+
/** When the remote was last updated */
|
|
18
|
+
remoteUpdatedAt: string;
|
|
19
|
+
}>;
|
|
20
|
+
lastSyncAt: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Load sync index
|
|
24
|
+
*/
|
|
25
|
+
export declare const loadSyncIndex: () => Promise<SyncIndex>;
|
|
26
|
+
/**
|
|
27
|
+
* Save sync index
|
|
28
|
+
*/
|
|
29
|
+
export declare const saveSyncIndex: (index: SyncIndex) => Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Get the master key from credentials
|
|
32
|
+
*/
|
|
33
|
+
export declare const getMasterKey: (passphrase: string) => Promise<Uint8Array<ArrayBufferLike> | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Encrypt a skill for upload
|
|
36
|
+
*/
|
|
37
|
+
export declare const encryptSkill: (skill: Skill, masterKey: Uint8Array) => {
|
|
38
|
+
encryptedData: string;
|
|
39
|
+
iv: string;
|
|
40
|
+
tag: string;
|
|
41
|
+
};
|
|
42
|
+
/** Decrypted skill payload */
|
|
43
|
+
type DecryptedSkillPayload = {
|
|
44
|
+
name: string;
|
|
45
|
+
content: string;
|
|
46
|
+
path: string;
|
|
47
|
+
modifiedAt: string;
|
|
48
|
+
type: SkillType | undefined;
|
|
49
|
+
files: {
|
|
50
|
+
name: string;
|
|
51
|
+
content: string;
|
|
52
|
+
}[] | undefined;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Decrypt a skill from download
|
|
56
|
+
*/
|
|
57
|
+
export declare const decryptSkill: (encryptedData: string, iv: string, tag: string, masterKey: Uint8Array) => DecryptedSkillPayload;
|
|
58
|
+
/**
|
|
59
|
+
* Push local skills to server
|
|
60
|
+
*/
|
|
61
|
+
export declare const pushSkills: (masterKey: Uint8Array, onProgress: ((message: string) => void) | undefined, message: string | undefined) => Promise<{
|
|
62
|
+
pushed: number;
|
|
63
|
+
errors: string[];
|
|
64
|
+
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Pull remote skills to local
|
|
67
|
+
*/
|
|
68
|
+
export declare const pullSkills: (masterKey: Uint8Array, onProgress: ((message: string) => void) | undefined) => Promise<{
|
|
69
|
+
pulled: number;
|
|
70
|
+
errors: string[];
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Get sync status for all skills
|
|
74
|
+
*/
|
|
75
|
+
export declare const getSyncStatus: () => Promise<{
|
|
76
|
+
local: number;
|
|
77
|
+
synced: number;
|
|
78
|
+
pendingPush: number;
|
|
79
|
+
pendingPull: number;
|
|
80
|
+
lastSyncAt: string | null;
|
|
81
|
+
}>;
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAUL,KAAK,KAAK,EACV,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAK3B,8CAA8C;AAC9C,KAAK,SAAS,GAAG;IACf,oCAAoC;IACpC,MAAM,EAAE,MAAM,CACZ,MAAM,EACN;QACE,qDAAqD;QACrD,IAAI,EAAE,MAAM,CAAC;QACb,qCAAqC;QACrC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,8CAA8C;QAC9C,SAAS,EAAE,MAAM,CAAC;QAClB,uCAAuC;QACvC,eAAe,EAAE,MAAM,CAAC;KACzB,CACF,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAYF;;GAEG;AACH,eAAO,MAAM,aAAa,0BAOzB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAU,OAAO,SAAS,kBAInD,CAAC;AAcF;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,YAAY,MAAM,gDAwBnB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,OAAO,KAAK,EACZ,WAAW,UAAU,KACpB;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAalD,CAAC;AAEF,8BAA8B;AAC9B,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,SAAS,CAAC;CACxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,eAAe,MAAM,EACrB,IAAI,MAAM,EACV,KAAK,MAAM,EACX,WAAW,UAAU,KACpB,qBAGF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,GACrB,WAAW,UAAU,EACrB,YAAY,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,EACnD,SAAS,MAAM,GAAG,SAAS;;;EA4E5B,CAAC;AAaF;;GAEG;AACH,eAAO,MAAM,UAAU,GACrB,WAAW,UAAU,EACrB,YAAY,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS;;;EA0HpD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;EA8BzB,CAAC"}
|
package/dist/sync.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync logic for skills
|
|
3
|
+
*
|
|
4
|
+
* Handles encrypting, uploading, downloading, and decrypting skills.
|
|
5
|
+
*/
|
|
6
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
7
|
+
import { join, dirname } from "node:path";
|
|
8
|
+
import { listAllSkills, encryptString, decryptString, deriveKeyFromPassphrase, decryptMasterKey, fromBase64, getClaudeDir, computeSkillHash, getSkillKey, } from "@claudeskill/core";
|
|
9
|
+
import { loadCredentials } from "./credentials.js";
|
|
10
|
+
import * as api from "./api.js";
|
|
11
|
+
const SYNC_INDEX_FILE = "sync-index.json";
|
|
12
|
+
/**
|
|
13
|
+
* Get path to sync index file
|
|
14
|
+
*/
|
|
15
|
+
const getSyncIndexPath = async () => {
|
|
16
|
+
const { getConfigDir } = await import("./config.js");
|
|
17
|
+
return join(getConfigDir(), SYNC_INDEX_FILE);
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Load sync index
|
|
21
|
+
*/
|
|
22
|
+
export const loadSyncIndex = async () => {
|
|
23
|
+
try {
|
|
24
|
+
const content = await readFile(await getSyncIndexPath(), "utf-8");
|
|
25
|
+
return JSON.parse(content);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { skills: {}, lastSyncAt: "" };
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Save sync index
|
|
33
|
+
*/
|
|
34
|
+
export const saveSyncIndex = async (index) => {
|
|
35
|
+
const indexPath = await getSyncIndexPath();
|
|
36
|
+
await mkdir(dirname(indexPath), { recursive: true });
|
|
37
|
+
await writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Simple hash for change detection
|
|
41
|
+
*/
|
|
42
|
+
const hashContent = (content) => {
|
|
43
|
+
const hash = Array.from(content).reduce((hash, char) => {
|
|
44
|
+
const code = char.charCodeAt(0);
|
|
45
|
+
const newHash = (hash << 5) - hash + code;
|
|
46
|
+
return newHash & newHash; // Convert to 32-bit integer
|
|
47
|
+
}, 0);
|
|
48
|
+
return hash.toString(16);
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Get the master key from credentials
|
|
52
|
+
*/
|
|
53
|
+
export const getMasterKey = async (passphrase) => {
|
|
54
|
+
const credentials = await loadCredentials();
|
|
55
|
+
if (!credentials?.encryptedMasterKey || !credentials?.salt) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const salt = fromBase64(credentials.salt);
|
|
60
|
+
const encryptedMasterKey = fromBase64(credentials.encryptedMasterKey);
|
|
61
|
+
// The encrypted master key includes IV and tag
|
|
62
|
+
// Format: iv (12 bytes) + tag (16 bytes) + ciphertext
|
|
63
|
+
const iv = encryptedMasterKey.slice(0, 12);
|
|
64
|
+
const tag = encryptedMasterKey.slice(12, 28);
|
|
65
|
+
const ciphertext = encryptedMasterKey.slice(28);
|
|
66
|
+
const derivedKey = deriveKeyFromPassphrase(passphrase, salt);
|
|
67
|
+
const masterKey = decryptMasterKey(ciphertext, derivedKey.key, iv, tag);
|
|
68
|
+
return masterKey;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Encrypt a skill for upload
|
|
76
|
+
*/
|
|
77
|
+
export const encryptSkill = (skill, masterKey) => {
|
|
78
|
+
// Create a JSON payload with skill metadata
|
|
79
|
+
const payload = JSON.stringify({
|
|
80
|
+
name: skill.name,
|
|
81
|
+
content: skill.content,
|
|
82
|
+
path: skill.path,
|
|
83
|
+
modifiedAt: skill.modifiedAt.toISOString(),
|
|
84
|
+
type: skill.type,
|
|
85
|
+
files: skill.files,
|
|
86
|
+
});
|
|
87
|
+
const { ciphertext, iv, tag } = encryptString(payload, masterKey);
|
|
88
|
+
return { encryptedData: ciphertext, iv, tag };
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Decrypt a skill from download
|
|
92
|
+
*/
|
|
93
|
+
export const decryptSkill = (encryptedData, iv, tag, masterKey) => {
|
|
94
|
+
const payload = decryptString(encryptedData, masterKey, iv, tag);
|
|
95
|
+
return JSON.parse(payload);
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Push local skills to server
|
|
99
|
+
*/
|
|
100
|
+
export const pushSkills = async (masterKey, onProgress, message) => {
|
|
101
|
+
const skills = await listAllSkills();
|
|
102
|
+
const index = await loadSyncIndex();
|
|
103
|
+
const results = await Promise.all(skills.map(async (skill) => {
|
|
104
|
+
// Use type:name as key to allow same name across types
|
|
105
|
+
const skillKey = getSkillKey(skill);
|
|
106
|
+
const contentHash = computeSkillHash(skill);
|
|
107
|
+
const localHash = hashContent(skill.content + JSON.stringify(skill.files ?? []));
|
|
108
|
+
const existing = index.skills[skillKey];
|
|
109
|
+
// Skip if unchanged (same content hash)
|
|
110
|
+
if (existing?.hash === contentHash) {
|
|
111
|
+
onProgress?.(`Skipping ${skill.name} (unchanged)`);
|
|
112
|
+
return { type: 'skipped' };
|
|
113
|
+
}
|
|
114
|
+
onProgress?.(`Pushing ${skill.type}/${skill.name} [${contentHash}]...`);
|
|
115
|
+
// Encrypt skill
|
|
116
|
+
const encrypted = encryptSkill(skill, masterKey);
|
|
117
|
+
// Push new version
|
|
118
|
+
const result = await api.pushSkillVersion(skillKey, contentHash, encrypted.encryptedData, encrypted.iv, encrypted.tag, message);
|
|
119
|
+
if (result.ok) {
|
|
120
|
+
return {
|
|
121
|
+
type: 'success',
|
|
122
|
+
skillKey,
|
|
123
|
+
hash: contentHash,
|
|
124
|
+
localHash,
|
|
125
|
+
remoteUpdatedAt: result.data.createdAt,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
type: 'error',
|
|
130
|
+
message: `Failed to push ${skill.type}/${skill.name}: ${result.error}`,
|
|
131
|
+
};
|
|
132
|
+
}));
|
|
133
|
+
// Process results
|
|
134
|
+
index.skills = results
|
|
135
|
+
.filter((result) => result.type === 'success')
|
|
136
|
+
.reduce((acc, result) => {
|
|
137
|
+
if (result.type === 'success') {
|
|
138
|
+
acc[result.skillKey] = {
|
|
139
|
+
hash: result.hash,
|
|
140
|
+
blobId: null,
|
|
141
|
+
localHash: result.localHash,
|
|
142
|
+
remoteUpdatedAt: result.remoteUpdatedAt,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return acc;
|
|
146
|
+
}, index.skills);
|
|
147
|
+
const pushed = results.filter((r) => r.type === 'success').length;
|
|
148
|
+
const errors = results
|
|
149
|
+
.filter((r) => r.type === 'error')
|
|
150
|
+
.map((r) => r.type === 'error' ? r.message : '');
|
|
151
|
+
// Save updated index
|
|
152
|
+
index.lastSyncAt = new Date().toISOString();
|
|
153
|
+
await saveSyncIndex(index);
|
|
154
|
+
return { pushed, errors };
|
|
155
|
+
};
|
|
156
|
+
/** Get the target directory for a skill type */
|
|
157
|
+
const getSkillTypeDir = (type) => {
|
|
158
|
+
const claudeDir = getClaudeDir();
|
|
159
|
+
const typeDirs = {
|
|
160
|
+
command: "commands",
|
|
161
|
+
skill: "skills",
|
|
162
|
+
agent: "agents",
|
|
163
|
+
};
|
|
164
|
+
return join(claudeDir, typeDirs[type]);
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Pull remote skills to local
|
|
168
|
+
*/
|
|
169
|
+
export const pullSkills = async (masterKey, onProgress) => {
|
|
170
|
+
const index = await loadSyncIndex();
|
|
171
|
+
// Get list of remote skills
|
|
172
|
+
const listResult = await api.listSkills();
|
|
173
|
+
if (!listResult.ok) {
|
|
174
|
+
return { pulled: 0, errors: [`Failed to list skills: ${listResult.error}`] };
|
|
175
|
+
}
|
|
176
|
+
const results = await Promise.all(listResult.data.skills.map(async (remoteSkill) => {
|
|
177
|
+
const existing = index.skills[remoteSkill.skillKey];
|
|
178
|
+
// Skip if unchanged (same hash)
|
|
179
|
+
if (existing?.hash === remoteSkill.currentHash) {
|
|
180
|
+
onProgress?.(`Skipping ${remoteSkill.skillKey} (unchanged)`);
|
|
181
|
+
return { type: 'skipped' };
|
|
182
|
+
}
|
|
183
|
+
// Skip if no versions
|
|
184
|
+
if (!remoteSkill.currentHash) {
|
|
185
|
+
onProgress?.(`Skipping ${remoteSkill.skillKey} (no versions)`);
|
|
186
|
+
return { type: 'skipped' };
|
|
187
|
+
}
|
|
188
|
+
onProgress?.(`Pulling ${remoteSkill.skillKey} [${remoteSkill.currentHash}]...`);
|
|
189
|
+
// Download current version
|
|
190
|
+
const skillResult = await api.getSkill(remoteSkill.skillKey);
|
|
191
|
+
if (!skillResult.ok) {
|
|
192
|
+
return {
|
|
193
|
+
type: 'error',
|
|
194
|
+
message: `Failed to get ${remoteSkill.skillKey}: ${skillResult.error}`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// Decrypt
|
|
198
|
+
let decrypted;
|
|
199
|
+
try {
|
|
200
|
+
decrypted = decryptSkill(skillResult.data.encryptedData, skillResult.data.iv, skillResult.data.tag, masterKey);
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
return {
|
|
204
|
+
type: 'error',
|
|
205
|
+
message: `Failed to decrypt ${remoteSkill.skillKey}: ${err}`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const skillType = decrypted.type ?? "command";
|
|
209
|
+
const skillKey = remoteSkill.skillKey;
|
|
210
|
+
const baseDir = getSkillTypeDir(skillType);
|
|
211
|
+
try {
|
|
212
|
+
if (skillType === "skill" && decrypted.files) {
|
|
213
|
+
// Directory-based skill
|
|
214
|
+
const skillDir = join(baseDir, decrypted.name);
|
|
215
|
+
await mkdir(skillDir, { recursive: true });
|
|
216
|
+
// Write main skill file
|
|
217
|
+
await writeFile(join(skillDir, "SKILL.md"), decrypted.content, "utf-8");
|
|
218
|
+
// Write supporting files
|
|
219
|
+
await Promise.all(decrypted.files.map((f) => writeFile(join(skillDir, f.name), f.content, "utf-8")));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
// File-based skill (command or agent)
|
|
223
|
+
await mkdir(baseDir, { recursive: true });
|
|
224
|
+
await writeFile(join(baseDir, `${decrypted.name}.md`), decrypted.content, "utf-8");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
return {
|
|
229
|
+
type: 'error',
|
|
230
|
+
message: `Failed to write ${decrypted.name}: ${err}`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
onProgress?.(`Pulled ${skillType}/${decrypted.name} [${remoteSkill.currentHash}]`);
|
|
234
|
+
return {
|
|
235
|
+
type: 'success',
|
|
236
|
+
skillKey,
|
|
237
|
+
hash: remoteSkill.currentHash,
|
|
238
|
+
content: decrypted.content,
|
|
239
|
+
files: decrypted.files,
|
|
240
|
+
updatedAt: remoteSkill.updatedAt,
|
|
241
|
+
};
|
|
242
|
+
}));
|
|
243
|
+
// Process results
|
|
244
|
+
index.skills = results
|
|
245
|
+
.filter((result) => result.type === 'success')
|
|
246
|
+
.reduce((acc, result) => {
|
|
247
|
+
if (result.type === 'success') {
|
|
248
|
+
acc[result.skillKey] = {
|
|
249
|
+
hash: result.hash,
|
|
250
|
+
blobId: null,
|
|
251
|
+
localHash: hashContent(result.content + JSON.stringify(result.files ?? [])),
|
|
252
|
+
remoteUpdatedAt: result.updatedAt,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return acc;
|
|
256
|
+
}, index.skills);
|
|
257
|
+
const pulled = results.filter((r) => r.type === 'success').length;
|
|
258
|
+
const errors = results
|
|
259
|
+
.filter((r) => r.type === 'error')
|
|
260
|
+
.map((r) => r.type === 'error' ? r.message : '');
|
|
261
|
+
// Save updated index
|
|
262
|
+
index.lastSyncAt = new Date().toISOString();
|
|
263
|
+
await saveSyncIndex(index);
|
|
264
|
+
return { pulled, errors };
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* Get sync status for all skills
|
|
268
|
+
*/
|
|
269
|
+
export const getSyncStatus = async () => {
|
|
270
|
+
const skills = await listAllSkills();
|
|
271
|
+
const index = await loadSyncIndex();
|
|
272
|
+
const skillStatuses = skills.map((skill) => {
|
|
273
|
+
const skillKey = getSkillKey(skill);
|
|
274
|
+
const contentHash = computeSkillHash(skill);
|
|
275
|
+
const existing = index.skills[skillKey];
|
|
276
|
+
return existing?.hash === contentHash ? 'synced' : 'pending';
|
|
277
|
+
});
|
|
278
|
+
const synced = skillStatuses.filter((s) => s === 'synced').length;
|
|
279
|
+
const pendingPush = skillStatuses.filter((s) => s === 'pending').length;
|
|
280
|
+
// Check for remote-only skills (pending pull)
|
|
281
|
+
const listResult = await api.listSkills();
|
|
282
|
+
const localSkillKeys = new Set(skills.map((s) => getSkillKey(s)));
|
|
283
|
+
const pendingPull = listResult.ok
|
|
284
|
+
? listResult.data.skills.filter((s) => s.currentHash && !localSkillKeys.has(s.skillKey)).length
|
|
285
|
+
: 0;
|
|
286
|
+
return {
|
|
287
|
+
local: skills.length,
|
|
288
|
+
synced,
|
|
289
|
+
pendingPush,
|
|
290
|
+
pendingPull,
|
|
291
|
+
lastSyncAt: index.lastSyncAt || null,
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
//# sourceMappingURL=sync.js.map
|
package/dist/sync.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
|