@notis_ai/cli 0.2.0-beta.162.1 → 0.2.0-beta.164.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/agent-hooks/notis-agent-hook.mjs +44 -8
- package/dist/skill-sync/index.js +44 -8
- package/dist/skill-sync/index.js.map +2 -2
- package/dist/skill-sync-worker.mjs +42 -7
- package/package.json +1 -1
- package/src/runtime/skill-sync/index.ts +59 -4
- package/src/runtime/skill-sync/sync-plan.ts +26 -1
- package/src/runtime/skill-sync/types.ts +8 -0
|
@@ -4140,11 +4140,24 @@ async function syncSymlinks(skills, skillsDir = LEGACY_AGENTS_SKILLS_DIR, option
|
|
|
4140
4140
|
}
|
|
4141
4141
|
return result;
|
|
4142
4142
|
}
|
|
4143
|
-
function
|
|
4143
|
+
function selectCloudSkillsToApply(skills) {
|
|
4144
|
+
const winners = /* @__PURE__ */ new Map();
|
|
4145
|
+
for (const skill of skills) {
|
|
4146
|
+
const current = winners.get(skill.name);
|
|
4147
|
+
if (!current || (skill.updated_at || "") >= (current.updated_at || "")) {
|
|
4148
|
+
winners.set(skill.name, skill);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
return [...winners.values()];
|
|
4152
|
+
}
|
|
4153
|
+
function getPushCandidates(localSkills, syncState, cloudCuratedSkillNames = /* @__PURE__ */ new Set(), cloudSkillNames, cloudAppOwnedSkillNames = /* @__PURE__ */ new Set()) {
|
|
4144
4154
|
return localSkills.filter((skill) => {
|
|
4145
4155
|
if (cloudCuratedSkillNames.has(skill.name)) {
|
|
4146
4156
|
return false;
|
|
4147
4157
|
}
|
|
4158
|
+
if (cloudAppOwnedSkillNames.has(skill.name)) {
|
|
4159
|
+
return false;
|
|
4160
|
+
}
|
|
4148
4161
|
const previous = syncState.skills[skill.name];
|
|
4149
4162
|
if (!previous) {
|
|
4150
4163
|
return true;
|
|
@@ -4238,17 +4251,28 @@ function shouldWriteCloudSkill(cloudSkill, localSkills, previousState) {
|
|
|
4238
4251
|
}
|
|
4239
4252
|
const previous = previousState.skills[skillName];
|
|
4240
4253
|
if (previous?.folderHash === localSkill.folderHash && previous.cloudContentHash === cloudContentHash(cloudSkill)) return false;
|
|
4254
|
+
if (previous && previous.cloudContentHash === void 0 && previous.cloudId === cloudSkill.id && previous.appliedCloudFolderHash !== void 0 && previous.appliedCloudFolderHash === cloudHash && previous.cloudUpdatedAt === cloudSkill.updated_at && previous.folderHash === localSkill.folderHash) {
|
|
4255
|
+
return false;
|
|
4256
|
+
}
|
|
4241
4257
|
if (cloudSkill.source === "curated") {
|
|
4242
4258
|
return cloudHash ? cloudHash !== localSkill.folderHash : true;
|
|
4243
4259
|
}
|
|
4244
4260
|
const localChangedSinceLastSync = !previous || previous.folderHash !== localSkill.folderHash;
|
|
4245
4261
|
return !localChangedSinceLastSync && (Boolean(cloudHash) && cloudHash !== localSkill.folderHash || Boolean(previous?.cloudContentHash && previous.cloudContentHash !== cloudContentHash(cloudSkill)));
|
|
4246
4262
|
}
|
|
4247
|
-
function
|
|
4263
|
+
function collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames) {
|
|
4264
|
+
const applied = {};
|
|
4265
|
+
for (const skill of selectCloudSkillsToApply(pullResponse.skills)) {
|
|
4266
|
+
applied[skill.name] = writtenSkillNames.has(skill.name) ? skill.skill_folder_hash || "" : previousState.skills[skill.name]?.appliedCloudFolderHash;
|
|
4267
|
+
}
|
|
4268
|
+
return applied;
|
|
4269
|
+
}
|
|
4270
|
+
function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLinks = {}, failedContentNames = /* @__PURE__ */ new Set(), appliedRevisions = {}) {
|
|
4248
4271
|
const localSkillMap = toSkillMap(localSkills);
|
|
4249
4272
|
const skills = Object.fromEntries(
|
|
4250
|
-
pullResponse.skills.map((skill) => {
|
|
4273
|
+
selectCloudSkillsToApply(pullResponse.skills).map((skill) => {
|
|
4251
4274
|
const localSkill = localSkillMap.get(skill.name);
|
|
4275
|
+
const appliedCloudFolderHash = appliedRevisions[skill.name];
|
|
4252
4276
|
return [
|
|
4253
4277
|
skill.name,
|
|
4254
4278
|
{
|
|
@@ -4258,6 +4282,7 @@ function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLi
|
|
|
4258
4282
|
verifiedAgentLinks: skill.status === "active" ? verifiedAgentLinks[skill.name] ?? {} : {},
|
|
4259
4283
|
cloudUpdatedAt: skill.updated_at,
|
|
4260
4284
|
...!failedContentNames.has(skill.name) && !skill.skill_source_url ? { cloudContentHash: cloudContentHash(skill) } : {},
|
|
4285
|
+
...appliedCloudFolderHash !== void 0 ? { appliedCloudFolderHash } : {},
|
|
4261
4286
|
syncedAt: lastSyncedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
4262
4287
|
}
|
|
4263
4288
|
];
|
|
@@ -4319,7 +4344,7 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
|
|
|
4319
4344
|
console.warn(`[Notis] ${message}`, error);
|
|
4320
4345
|
};
|
|
4321
4346
|
let downloaded = 0;
|
|
4322
|
-
for (const cloudSkill of pullResponse.skills) {
|
|
4347
|
+
for (const cloudSkill of selectCloudSkillsToApply(pullResponse.skills)) {
|
|
4323
4348
|
if (!shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {
|
|
4324
4349
|
continue;
|
|
4325
4350
|
}
|
|
@@ -4400,7 +4425,8 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
4400
4425
|
finalLocalSkills,
|
|
4401
4426
|
lastSyncedAt,
|
|
4402
4427
|
verifiedLinks,
|
|
4403
|
-
new Set(failedDownloads.map((item) => item.name))
|
|
4428
|
+
new Set(failedDownloads.map((item) => item.name)),
|
|
4429
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames)
|
|
4404
4430
|
);
|
|
4405
4431
|
for (const [name, entry] of Object.entries(materializedState.skills)) {
|
|
4406
4432
|
if (writtenSkillNames.has(name)) continue;
|
|
@@ -4555,7 +4581,8 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
4555
4581
|
localSkills,
|
|
4556
4582
|
previousState,
|
|
4557
4583
|
cloudCuratedSkillNames,
|
|
4558
|
-
new Set(pullResponse.skills.map((skill) => skill.name))
|
|
4584
|
+
new Set(pullResponse.skills.map((skill) => skill.name)),
|
|
4585
|
+
new Set(pullResponse.skills.filter((skill) => skill.owner_app_id).map((skill) => skill.name))
|
|
4559
4586
|
);
|
|
4560
4587
|
const failedPushes = [];
|
|
4561
4588
|
if (pushCandidates.length > 0) {
|
|
@@ -4579,13 +4606,15 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
4579
4606
|
}
|
|
4580
4607
|
}
|
|
4581
4608
|
const failedDownloads = [];
|
|
4609
|
+
const writtenSkillNames = /* @__PURE__ */ new Set();
|
|
4582
4610
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
4583
4611
|
pullResponse,
|
|
4584
4612
|
localSkills,
|
|
4585
4613
|
previousState,
|
|
4586
4614
|
syncPaths,
|
|
4587
4615
|
deps,
|
|
4588
|
-
failedDownloads
|
|
4616
|
+
failedDownloads,
|
|
4617
|
+
writtenSkillNames
|
|
4589
4618
|
);
|
|
4590
4619
|
const finalLocalSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES2.has(skill.name));
|
|
4591
4620
|
const symlinkResult = await deps.syncSymlinks(
|
|
@@ -4596,7 +4625,14 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
4596
4625
|
for (const failure of failedDownloads) delete verifiedLinks[failure.name];
|
|
4597
4626
|
const lastSyncedAt = pullResponse.last_synced_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
4598
4627
|
await deps.writeSyncState(
|
|
4599
|
-
buildSyncState(
|
|
4628
|
+
buildSyncState(
|
|
4629
|
+
pullResponse,
|
|
4630
|
+
finalLocalSkills,
|
|
4631
|
+
lastSyncedAt,
|
|
4632
|
+
verifiedLinks,
|
|
4633
|
+
new Set(failedDownloads.map((item) => item.name)),
|
|
4634
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames)
|
|
4635
|
+
),
|
|
4600
4636
|
syncPaths
|
|
4601
4637
|
);
|
|
4602
4638
|
return {
|
package/dist/skill-sync/index.js
CHANGED
|
@@ -1118,11 +1118,24 @@ async function syncSymlinks(skills, skillsDir = LEGACY_AGENTS_SKILLS_DIR, option
|
|
|
1118
1118
|
}
|
|
1119
1119
|
|
|
1120
1120
|
// src/runtime/skill-sync/sync-plan.ts
|
|
1121
|
-
function
|
|
1121
|
+
function selectCloudSkillsToApply(skills) {
|
|
1122
|
+
const winners = /* @__PURE__ */ new Map();
|
|
1123
|
+
for (const skill of skills) {
|
|
1124
|
+
const current = winners.get(skill.name);
|
|
1125
|
+
if (!current || (skill.updated_at || "") >= (current.updated_at || "")) {
|
|
1126
|
+
winners.set(skill.name, skill);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return [...winners.values()];
|
|
1130
|
+
}
|
|
1131
|
+
function getPushCandidates(localSkills, syncState, cloudCuratedSkillNames = /* @__PURE__ */ new Set(), cloudSkillNames, cloudAppOwnedSkillNames = /* @__PURE__ */ new Set()) {
|
|
1122
1132
|
return localSkills.filter((skill) => {
|
|
1123
1133
|
if (cloudCuratedSkillNames.has(skill.name)) {
|
|
1124
1134
|
return false;
|
|
1125
1135
|
}
|
|
1136
|
+
if (cloudAppOwnedSkillNames.has(skill.name)) {
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1126
1139
|
const previous = syncState.skills[skill.name];
|
|
1127
1140
|
if (!previous) {
|
|
1128
1141
|
return true;
|
|
@@ -1239,17 +1252,28 @@ function shouldWriteCloudSkill(cloudSkill, localSkills, previousState) {
|
|
|
1239
1252
|
}
|
|
1240
1253
|
const previous = previousState.skills[skillName];
|
|
1241
1254
|
if (previous?.folderHash === localSkill.folderHash && previous.cloudContentHash === cloudContentHash(cloudSkill)) return false;
|
|
1255
|
+
if (previous && previous.cloudContentHash === void 0 && previous.cloudId === cloudSkill.id && previous.appliedCloudFolderHash !== void 0 && previous.appliedCloudFolderHash === cloudHash && previous.cloudUpdatedAt === cloudSkill.updated_at && previous.folderHash === localSkill.folderHash) {
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1242
1258
|
if (cloudSkill.source === "curated") {
|
|
1243
1259
|
return cloudHash ? cloudHash !== localSkill.folderHash : true;
|
|
1244
1260
|
}
|
|
1245
1261
|
const localChangedSinceLastSync = !previous || previous.folderHash !== localSkill.folderHash;
|
|
1246
1262
|
return !localChangedSinceLastSync && (Boolean(cloudHash) && cloudHash !== localSkill.folderHash || Boolean(previous?.cloudContentHash && previous.cloudContentHash !== cloudContentHash(cloudSkill)));
|
|
1247
1263
|
}
|
|
1248
|
-
function
|
|
1264
|
+
function collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames) {
|
|
1265
|
+
const applied = {};
|
|
1266
|
+
for (const skill of selectCloudSkillsToApply(pullResponse.skills)) {
|
|
1267
|
+
applied[skill.name] = writtenSkillNames.has(skill.name) ? skill.skill_folder_hash || "" : previousState.skills[skill.name]?.appliedCloudFolderHash;
|
|
1268
|
+
}
|
|
1269
|
+
return applied;
|
|
1270
|
+
}
|
|
1271
|
+
function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLinks = {}, failedContentNames = /* @__PURE__ */ new Set(), appliedRevisions = {}) {
|
|
1249
1272
|
const localSkillMap = toSkillMap(localSkills);
|
|
1250
1273
|
const skills = Object.fromEntries(
|
|
1251
|
-
pullResponse.skills.map((skill) => {
|
|
1274
|
+
selectCloudSkillsToApply(pullResponse.skills).map((skill) => {
|
|
1252
1275
|
const localSkill = localSkillMap.get(skill.name);
|
|
1276
|
+
const appliedCloudFolderHash = appliedRevisions[skill.name];
|
|
1253
1277
|
return [
|
|
1254
1278
|
skill.name,
|
|
1255
1279
|
{
|
|
@@ -1259,6 +1283,7 @@ function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLi
|
|
|
1259
1283
|
verifiedAgentLinks: skill.status === "active" ? verifiedAgentLinks[skill.name] ?? {} : {},
|
|
1260
1284
|
cloudUpdatedAt: skill.updated_at,
|
|
1261
1285
|
...!failedContentNames.has(skill.name) && !skill.skill_source_url ? { cloudContentHash: cloudContentHash(skill) } : {},
|
|
1286
|
+
...appliedCloudFolderHash !== void 0 ? { appliedCloudFolderHash } : {},
|
|
1262
1287
|
syncedAt: lastSyncedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
1263
1288
|
}
|
|
1264
1289
|
];
|
|
@@ -1320,7 +1345,7 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
|
|
|
1320
1345
|
console.warn(`[Notis] ${message}`, error);
|
|
1321
1346
|
};
|
|
1322
1347
|
let downloaded = 0;
|
|
1323
|
-
for (const cloudSkill of pullResponse.skills) {
|
|
1348
|
+
for (const cloudSkill of selectCloudSkillsToApply(pullResponse.skills)) {
|
|
1324
1349
|
if (!shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {
|
|
1325
1350
|
continue;
|
|
1326
1351
|
}
|
|
@@ -1401,7 +1426,8 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
1401
1426
|
finalLocalSkills,
|
|
1402
1427
|
lastSyncedAt,
|
|
1403
1428
|
verifiedLinks,
|
|
1404
|
-
new Set(failedDownloads.map((item) => item.name))
|
|
1429
|
+
new Set(failedDownloads.map((item) => item.name)),
|
|
1430
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames)
|
|
1405
1431
|
);
|
|
1406
1432
|
for (const [name, entry] of Object.entries(materializedState.skills)) {
|
|
1407
1433
|
if (writtenSkillNames.has(name)) continue;
|
|
@@ -1556,7 +1582,8 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
1556
1582
|
localSkills,
|
|
1557
1583
|
previousState,
|
|
1558
1584
|
cloudCuratedSkillNames,
|
|
1559
|
-
new Set(pullResponse.skills.map((skill) => skill.name))
|
|
1585
|
+
new Set(pullResponse.skills.map((skill) => skill.name)),
|
|
1586
|
+
new Set(pullResponse.skills.filter((skill) => skill.owner_app_id).map((skill) => skill.name))
|
|
1560
1587
|
);
|
|
1561
1588
|
const failedPushes = [];
|
|
1562
1589
|
if (pushCandidates.length > 0) {
|
|
@@ -1580,13 +1607,15 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
1580
1607
|
}
|
|
1581
1608
|
}
|
|
1582
1609
|
const failedDownloads = [];
|
|
1610
|
+
const writtenSkillNames = /* @__PURE__ */ new Set();
|
|
1583
1611
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
1584
1612
|
pullResponse,
|
|
1585
1613
|
localSkills,
|
|
1586
1614
|
previousState,
|
|
1587
1615
|
syncPaths,
|
|
1588
1616
|
deps,
|
|
1589
|
-
failedDownloads
|
|
1617
|
+
failedDownloads,
|
|
1618
|
+
writtenSkillNames
|
|
1590
1619
|
);
|
|
1591
1620
|
const finalLocalSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES.has(skill.name));
|
|
1592
1621
|
const symlinkResult = await deps.syncSymlinks(
|
|
@@ -1597,7 +1626,14 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
1597
1626
|
for (const failure of failedDownloads) delete verifiedLinks[failure.name];
|
|
1598
1627
|
const lastSyncedAt = pullResponse.last_synced_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
1599
1628
|
await deps.writeSyncState(
|
|
1600
|
-
buildSyncState(
|
|
1629
|
+
buildSyncState(
|
|
1630
|
+
pullResponse,
|
|
1631
|
+
finalLocalSkills,
|
|
1632
|
+
lastSyncedAt,
|
|
1633
|
+
verifiedLinks,
|
|
1634
|
+
new Set(failedDownloads.map((item) => item.name)),
|
|
1635
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames)
|
|
1636
|
+
),
|
|
1601
1637
|
syncPaths
|
|
1602
1638
|
);
|
|
1603
1639
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/runtime/skill-sync/index.ts", "../../src/runtime/skill-sync/types.ts", "../../src/runtime/skill-sync/local-scanner.ts", "../../src/runtime/skill-sync/cloud-client.ts", "../../src/runtime/skill-sync/symlink-manager.ts", "../../src/runtime/skill-sync/sync-plan.ts", "../../src/runtime/skill-sync/write-cloud-skill.ts"],
|
|
4
|
-
"sourcesContent": ["import { createHash } from 'node:crypto';\nimport type {\n AgentTargets,\n LocalSkill,\n NotisSyncState,\n SkillSyncFailure,\n SyncPullResponse,\n SyncSettings,\n} from \"./types\";\nimport { normalizeAgentTargets } from \"./types\";\nimport {\n downloadSkillBundle,\n fetchSyncSettings,\n pullSkills,\n pushChangedSkills,\n updateAgentTargets,\n} from \"./cloud-client\";\nimport {\n deleteLocalSkill,\n gatherTopLevelLocalSkills,\n getSkillSyncPathsForUser,\n readLegacySyncState,\n readSyncState,\n scanLocalSkills,\n writeCloudSkillToDisk,\n writeSyncState,\n} from \"./local-scanner\";\nimport {\n detectDeletedAgentSymlinks,\n removeForeignAccountSymlinks,\n removeAllSymlinksForSkill,\n syncSymlinks,\n type DeletedAgentSymlink,\n} from \"./symlink-manager\";\nimport { getPushCandidates } from \"./sync-plan\";\nimport { writeCloudSkillWithBundleFallback } from \"./write-cloud-skill\";\nexport { fetchSyncSettings } from './cloud-client';\n\nexport interface RunSkillSyncResult {\n syncEnabled: boolean;\n pushed: number;\n pulled: number;\n downloaded: number;\n deleted: number;\n /** Skills deactivated for a specific agent because the user deleted that agent's local\n * symlink (e.g. `rm ~/.claude/skills/<skill>`); the deletion is honored instead of recreated. */\n deactivated: number;\n linked: number;\n removed: number;\n skipped: number;\n lastSyncedAt: string | null;\n /** Skills the server rejected (e.g. an invalid SKILL.md description). The rest of\n * the batch still syncs; these are surfaced so the user knows what to fix. */\n failedPushes: SkillSyncFailure[];\n failedLinks?: SkillSyncFailure[];\n}\n\nexport interface RunSkillSyncOptions {\n /** Electron's scheduled invocation honors the account preference. A manual\n * CLI sync always runs, even when automatic Desktop refresh is disabled. */\n honorSyncEnabled?: boolean;\n}\n\nconst BASE_SKILL_NAMES = new Set(['notis-apps', 'notis-query', 'notis-cli']);\n\nfunction withoutBaseSkills(pullResponse: SyncPullResponse): SyncPullResponse {\n return {\n ...pullResponse,\n skills: pullResponse.skills.filter((skill) => !BASE_SKILL_NAMES.has(skill.name)),\n };\n}\n\nfunction withoutBaseSkillState(state: NotisSyncState): NotisSyncState {\n return {\n ...state,\n skills: Object.fromEntries(\n Object.entries(state.skills).filter(([name]) => !BASE_SKILL_NAMES.has(name)),\n ),\n };\n}\n\nexport interface MaterializeCloudSkillsResult {\n /** Cloud skills verified present in the final scoped disk scan. */\n materializedSkillNames: string[];\n pulled: number;\n downloaded: number;\n deleted: number;\n removed: number;\n lastSyncedAt: string | null;\n failedLinks?: SkillSyncFailure[];\n}\n\nexport interface MaterializeCloudSkillsOptions {\n /** Server-verified Notis identity, when Desktop's auth subject differs. */\n canonicalUserId?: string;\n /** Re-link only these cloud skills without removing or adopting other links. */\n relinkSkillNames?: readonly string[];\n}\n\ninterface RunSkillSyncDependencies {\n fetchSyncSettings: typeof fetchSyncSettings;\n pullSkills: typeof pullSkills;\n pushChangedSkills: typeof pushChangedSkills;\n downloadSkillBundle: typeof downloadSkillBundle;\n gatherTopLevelLocalSkills: typeof gatherTopLevelLocalSkills;\n readLegacySyncState: typeof readLegacySyncState;\n readSyncState: typeof readSyncState;\n scanLocalSkills: typeof scanLocalSkills;\n deleteLocalSkill: typeof deleteLocalSkill;\n writeCloudSkillToDisk: typeof writeCloudSkillToDisk;\n writeSyncState: typeof writeSyncState;\n removeAllSymlinksForSkill: typeof removeAllSymlinksForSkill;\n syncSymlinks: typeof syncSymlinks;\n detectDeletedAgentSymlinks: typeof detectDeletedAgentSymlinks;\n removeForeignAccountSymlinks: typeof removeForeignAccountSymlinks;\n updateAgentTargets: typeof updateAgentTargets;\n}\n\nconst DEFAULT_RUN_SKILL_SYNC_DEPS: RunSkillSyncDependencies = {\n fetchSyncSettings,\n pullSkills,\n pushChangedSkills,\n downloadSkillBundle,\n gatherTopLevelLocalSkills,\n readLegacySyncState,\n readSyncState,\n scanLocalSkills,\n deleteLocalSkill,\n writeCloudSkillToDisk,\n writeSyncState,\n removeAllSymlinksForSkill,\n syncSymlinks,\n detectDeletedAgentSymlinks,\n removeForeignAccountSymlinks,\n updateAgentTargets,\n};\nfunction toSkillMap(skills: LocalSkill[]): Map<string, LocalSkill> {\n return new Map(skills.map((skill) => [skill.name, skill]));\n}\n\nfunction decodeJwtSubject(jwt: string): string | null {\n try {\n const parts = jwt.split(\".\");\n if (parts.length !== 3) return null;\n const decoded = JSON.parse(\n Buffer.from(parts[1], \"base64url\").toString(),\n ) as { sub?: unknown };\n return typeof decoded.sub === \"string\" && decoded.sub.trim()\n ? decoded.sub.trim()\n : null;\n } catch {\n return null;\n }\n}\n\nfunction cloudContentHash(skill: SyncPullResponse['skills'][number]): string {\n return createHash('sha256').update(JSON.stringify({\n md: skill.skill_md,\n hash: skill.skill_folder_hash,\n source: skill.skill_source_url,\n files: skill.bundle_files?.slice().sort((a, b) => a.path.localeCompare(b.path)),\n hydrationFailed: skill.bundle_hydration_failed === true,\n })).digest('hex');\n}\n\nexport function shouldWriteCloudSkill(\n cloudSkill: SyncPullResponse[\"skills\"][number],\n localSkills: Map<string, LocalSkill>,\n previousState: NotisSyncState,\n): boolean {\n const skillName = cloudSkill.name;\n const cloudHash = cloudSkill.skill_folder_hash || \"\";\n const localSkill = localSkills.get(skillName);\n if (!localSkill) {\n return true;\n }\n\n const previous = previousState.skills[skillName];\n if (previous?.folderHash === localSkill.folderHash\n && previous.cloudContentHash === cloudContentHash(cloudSkill)) return false;\n\n if (cloudSkill.source === \"curated\") {\n return cloudHash ? cloudHash !== localSkill.folderHash : true;\n }\n\n const localChangedSinceLastSync =\n !previous || previous.folderHash !== localSkill.folderHash;\n return (\n !localChangedSinceLastSync &&\n ((Boolean(cloudHash) && cloudHash !== localSkill.folderHash)\n || Boolean(previous?.cloudContentHash && previous.cloudContentHash !== cloudContentHash(cloudSkill)))\n );\n}\n\nfunction buildSyncState(\n pullResponse: SyncPullResponse,\n localSkills: LocalSkill[],\n lastSyncedAt: string | null,\n verifiedAgentLinks: Record<string, Partial<AgentTargets>> = {},\n failedContentNames: ReadonlySet<string> = new Set(),\n): NotisSyncState {\n const localSkillMap = toSkillMap(localSkills);\n const skills = Object.fromEntries(\n pullResponse.skills.map((skill) => {\n const localSkill = localSkillMap.get(skill.name);\n return [\n skill.name,\n {\n cloudId: skill.id,\n folderHash: localSkill?.folderHash || skill.skill_folder_hash || \"\",\n agentTargets: normalizeAgentTargets(skill.agent_targets),\n verifiedAgentLinks: skill.status === \"active\" ? verifiedAgentLinks[skill.name] ?? {} : {},\n cloudUpdatedAt: skill.updated_at,\n ...(!failedContentNames.has(skill.name) && !skill.skill_source_url\n ? { cloudContentHash: cloudContentHash(skill) } : {}),\n syncedAt: lastSyncedAt || new Date().toISOString(),\n },\n ];\n }),\n );\n\n return {\n version: 1,\n lastSyncedAt,\n skills,\n };\n}\n\nfunction buildLocalSymlinkCandidates(\n pullResponse: SyncPullResponse,\n localSkills: LocalSkill[],\n previousState: NotisSyncState,\n): SyncPullResponse[\"skills\"] {\n const cloudSkillNames = new Set(\n pullResponse.skills.map((skill) => skill.name),\n );\n const localOnlySkills = localSkills\n .filter((skill) => !cloudSkillNames.has(skill.name))\n .map((skill) => {\n const previous = previousState.skills[skill.name];\n return {\n id: previous?.cloudId || `local-${skill.name}`,\n name: skill.name,\n description: skill.description || null,\n skill_md: skill.skillMd,\n agent_targets: previous?.agentTargets,\n skill_folder_hash: skill.folderHash,\n source: \"local\",\n status: \"active\",\n };\n });\n\n return [...pullResponse.skills, ...localOnlySkills];\n}\n\nfunction isEmptySyncState(state: NotisSyncState): boolean {\n return state.lastSyncedAt === null && Object.keys(state.skills).length === 0;\n}\n\nfunction applyLegacyFirstRunState(\n localSkills: LocalSkill[],\n scopedState: NotisSyncState,\n legacyState: NotisSyncState | null,\n): NotisSyncState {\n if (!isEmptySyncState(scopedState) || !legacyState) {\n return scopedState;\n }\n\n const migratedSkills = Object.fromEntries(\n localSkills.flatMap((skill) => {\n const previous = legacyState.skills[skill.name];\n if (!previous || previous.folderHash !== skill.folderHash) {\n return [];\n }\n return [[skill.name, previous]];\n }),\n );\n\n if (Object.keys(migratedSkills).length === 0) {\n return scopedState;\n }\n\n return {\n version: 1,\n lastSyncedAt: legacyState.lastSyncedAt,\n skills: migratedSkills,\n };\n}\n\nasync function writePulledSkillsToScopedMirror(\n pullResponse: SyncPullResponse,\n localSkills: LocalSkill[],\n previousState: NotisSyncState,\n syncPaths: ReturnType<typeof getSkillSyncPathsForUser>,\n deps: Pick<\n RunSkillSyncDependencies,\n \"downloadSkillBundle\" | \"writeCloudSkillToDisk\"\n >,\n failures: SkillSyncFailure[] = [],\n writtenSkillNames: Set<string> = new Set(),\n): Promise<number> {\n const localSkillMap = toSkillMap(localSkills);\n const warnSkillSync = (message: string, error: unknown): void => {\n console.warn(`[Notis] ${message}`, error);\n };\n\n let downloaded = 0;\n for (const cloudSkill of pullResponse.skills) {\n if (!shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {\n continue;\n }\n\n if (\n await writeCloudSkillWithBundleFallback(cloudSkill, {\n downloadSkillBundle: deps.downloadSkillBundle,\n writeCloudSkillToDisk: (skill, bundleBytes) =>\n deps.writeCloudSkillToDisk(skill, bundleBytes, syncPaths),\n onWarning: warnSkillSync,\n })\n ) {\n downloaded += 1;\n writtenSkillNames.add(cloudSkill.name);\n } else {\n failures.push({ name: cloudSkill.name, error: \"Skill content could not be downloaded or written; sync will retry\" });\n }\n }\n\n return downloaded;\n}\n\nfunction assertSkillsPullAuthorized(\n pullResponse: SyncPullResponse,\n): void {\n if (\n pullResponse.entitlement_access?.code === \"entitlement_upgrade_required\"\n && pullResponse.entitlement_access.entitlement === \"skills\"\n ) {\n // Current servers return HTTP 403, so cloud-client rejects before producing\n // a SyncPullResponse. Keep this guard for an older server's successful\n // empty-denial envelope, but fail closed: it is never authorization to\n // delete a user's managed local mirror.\n throw new Error(\n \"Skill sync access was denied; preserving existing local skills.\",\n );\n }\n}\n\nexport async function materializeCloudSkillsForLocalShell(\n serverUrl: string,\n jwt: string,\n dependencies: Partial<Pick<\n RunSkillSyncDependencies,\n | \"downloadSkillBundle\"\n | \"deleteLocalSkill\"\n | \"pullSkills\"\n | \"readSyncState\"\n | \"removeAllSymlinksForSkill\"\n | \"scanLocalSkills\"\n | \"syncSymlinks\"\n | \"writeCloudSkillToDisk\"\n | \"writeSyncState\"\n >> = {},\n options: MaterializeCloudSkillsOptions = {},\n): Promise<MaterializeCloudSkillsResult> {\n const deps = {\n ...DEFAULT_RUN_SKILL_SYNC_DEPS,\n ...dependencies,\n };\n const authUserId = decodeJwtSubject(jwt);\n if (!authUserId) {\n throw new Error(\n \"Cannot materialize skills without a valid authenticated desktop session.\",\n );\n }\n\n const syncPaths = getSkillSyncPathsForUser(options.canonicalUserId?.trim() || authUserId);\n const pullResponse = await deps.pullSkills(serverUrl, jwt);\n assertSkillsPullAuthorized(pullResponse);\n const previousState = await deps.readSyncState(syncPaths);\n\n const localSkills = await deps.scanLocalSkills(syncPaths);\n const failedDownloads: SkillSyncFailure[] = [];\n const writtenSkillNames = new Set<string>();\n const downloaded = await writePulledSkillsToScopedMirror(\n pullResponse,\n localSkills,\n previousState,\n syncPaths,\n deps,\n failedDownloads,\n writtenSkillNames,\n );\n const finalLocalSkills = await deps.scanLocalSkills(syncPaths);\n const lastSyncedAt = pullResponse.last_synced_at || new Date().toISOString();\n\n const relinkSkillNames = new Set(options.relinkSkillNames || []);\n const failures = [...failedDownloads];\n const verifiedLinks: Record<string, Partial<AgentTargets>> = {};\n for (const skill of pullResponse.skills) {\n const previous = previousState.skills[skill.name];\n if (skill.updated_at && previous?.cloudId === skill.id && previous.cloudUpdatedAt === skill.updated_at) {\n verifiedLinks[skill.name] = { ...previous.verifiedAgentLinks };\n }\n }\n if (relinkSkillNames.size > 0) {\n const relinked = await deps.syncSymlinks(\n pullResponse.skills.filter((skill) => relinkSkillNames.has(skill.name)),\n syncPaths.skillsDir,\n { removeUndesired: false },\n );\n failures.push(...(relinked.failures ?? []).filter(\n (failure) => !failedDownloads.some((download) => download.name === failure.name),\n ));\n for (const name of relinkSkillNames) {\n verifiedLinks[name] = relinked.verifiedAgentLinks?.[name] ?? {};\n }\n }\n for (const failure of failedDownloads) delete verifiedLinks[failure.name];\n\n const materializedState = buildSyncState(\n pullResponse, finalLocalSkills, lastSyncedAt, verifiedLinks,\n new Set(failedDownloads.map(item => item.name)),\n );\n // Pull-only refresh is not an upload acknowledgement. Keep content baselines\n // unless we actually wrote cloud content, and retain cloud-missing entries so\n // the next two-way sync can delete them instead of uploading them as new.\n for (const [name, entry] of Object.entries(materializedState.skills)) {\n if (writtenSkillNames.has(name)) continue;\n const previous = previousState.skills[name];\n if (previous) {\n entry.folderHash = previous.folderHash;\n entry.cloudContentHash = previous.cloudContentHash;\n } else {\n delete materializedState.skills[name];\n }\n }\n materializedState.skills = { ...previousState.skills, ...materializedState.skills };\n await deps.writeSyncState(materializedState, syncPaths);\n\n return {\n materializedSkillNames: pullResponse.skills\n .filter((skill) => finalLocalSkills.some((local) => local.name === skill.name)\n && !failedDownloads.some((failure) => failure.name === skill.name))\n .map((skill) => skill.name),\n pulled: pullResponse.skills.length,\n downloaded,\n deleted: 0,\n removed: 0,\n lastSyncedAt,\n ...(failures.length ? { failedLinks: failures } : {}),\n };\n}\n\n/**\n * When the user deletes a skill's symlink for a single agent (e.g. `rm ~/.claude/skills/<skill>`),\n * honor that as \"remove this skill from that agent\" by deactivating it in the portal, instead of\n * recreating the symlink on the next sync. The cloud `agent_targets` are mutated in place so the\n * subsequent symlink reconciliation treats the link as undesired. Returns the number of\n * (skill, agent) pairs deactivated.\n */\nasync function deactivateDeletedAgentSkills(\n serverUrl: string,\n jwt: string,\n pullResponse: SyncPullResponse,\n previousState: NotisSyncState,\n scopedState: NotisSyncState,\n skillsDir: string,\n deps: Pick<\n RunSkillSyncDependencies,\n \"detectDeletedAgentSymlinks\" | \"updateAgentTargets\" | \"pullSkills\"\n >,\n failures: SkillSyncFailure[],\n): Promise<number> {\n // First sync (incl. legacy migration) has no reliable \"we created this link\" signal, so we\n // cannot tell a user deletion apart from a never-created link \u2014 skip detection entirely.\n if (isEmptySyncState(scopedState)) {\n return 0;\n }\n\n const deletions = await deps.detectDeletedAgentSymlinks(\n pullResponse.skills,\n previousState,\n skillsDir,\n );\n if (deletions.length === 0) {\n return 0;\n }\n\n const agentsBySkill = new Map<string, { skillName: string; agents: Set<DeletedAgentSymlink[\"agent\"]> }>();\n for (const deletion of deletions) {\n const entry = agentsBySkill.get(deletion.skillId) ?? {\n skillName: deletion.skillName,\n agents: new Set<DeletedAgentSymlink[\"agent\"]>(),\n };\n entry.agents.add(deletion.agent);\n agentsBySkill.set(deletion.skillId, entry);\n }\n\n const fresh = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));\n assertSkillsPullAuthorized(fresh);\n Object.assign(pullResponse, fresh);\n let needsRefresh = false;\n let deactivated = 0;\n for (const [skillId, { skillName, agents }] of agentsBySkill) {\n const skill = pullResponse.skills.find((item) => item.id === skillId);\n const previous = previousState.skills[skillName];\n if (!skill?.updated_at || previous?.cloudUpdatedAt !== skill.updated_at) continue;\n const patch = Object.fromEntries([...agents].map((agent) => [agent, false]));\n try {\n const saved = await deps.updateAgentTargets(serverUrl, jwt, skillId, patch, skill.updated_at);\n if (saved.success !== true || !saved.updated_at?.trim()\n || saved.updated_at === skill.updated_at\n || !['notis', 'claude_code', 'cursor', 'codex'].every((agent) =>\n typeof saved.agent_targets?.[agent as keyof AgentTargets] === 'boolean')\n || ![...agents].every((agent) => saved.agent_targets[agent] === false)) {\n throw new Error('Assignment update did not return a verified saved revision');\n }\n skill.agent_targets = saved.agent_targets;\n skill.updated_at = saved.updated_at;\n deactivated += agents.size;\n } catch (error) {\n needsRefresh = true;\n failures.push({ name: skillName, error: 'Could not save the local agent removal; refreshed saved assignments' });\n console.warn(`[skill-sync] Assignment changed or could not be saved for \"${skillName}\"; refreshing before reconciliation.`, error);\n }\n }\n if (needsRefresh) {\n const refreshed = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));\n assertSkillsPullAuthorized(refreshed);\n Object.assign(pullResponse, refreshed);\n }\n return deactivated;\n}\n\nexport async function runSkillSync(\n serverUrl: string,\n jwt: string,\n dependencies: Partial<RunSkillSyncDependencies> = {},\n options: RunSkillSyncOptions = {},\n): Promise<RunSkillSyncResult> {\n const deps = {\n ...DEFAULT_RUN_SKILL_SYNC_DEPS,\n ...dependencies,\n };\n const syncSettings: SyncSettings = await deps.fetchSyncSettings(\n serverUrl,\n jwt,\n );\n // Supabase Desktop sessions use the auth-user id as JWT `sub`, while CLI\n // OAuth credentials use the canonical Notis `users.user_id`. Trust the\n // authenticated server response so both transports share one local mirror.\n // The token fallback keeps the CLI compatible with an older server during\n // rollout, where sync-settings did not yet return user_id.\n const syncUserId = syncSettings.user_id?.trim() || decodeJwtSubject(jwt);\n if (!syncUserId) {\n throw new Error(\n \"Cannot sync skills without a server-verified account identity.\",\n );\n }\n const syncPaths = getSkillSyncPathsForUser(syncUserId);\n const foreignLinksRemoved = await deps.removeForeignAccountSymlinks(\n syncPaths.skillsDir,\n );\n if (options.honorSyncEnabled !== false && !syncSettings.sync_enabled) {\n return {\n syncEnabled: false,\n pushed: 0,\n pulled: 0,\n downloaded: 0,\n deleted: 0,\n deactivated: 0,\n linked: 0,\n removed: foreignLinksRemoved,\n skipped: 0,\n lastSyncedAt: syncSettings.last_synced_at,\n failedPushes: [],\n };\n }\n\n let pullResponse = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));\n assertSkillsPullAuthorized(pullResponse);\n\n const cloudCuratedSkillNames = new Set(\n pullResponse.skills\n .filter((skill) => skill.source === \"curated\")\n .map((skill) => skill.name),\n );\n const protectedSkillNames = new Set([...cloudCuratedSkillNames, ...BASE_SKILL_NAMES]);\n const scopedState = withoutBaseSkillState(await deps.readSyncState(syncPaths));\n const assignmentFailures: SkillSyncFailure[] = [];\n const deactivated = syncSettings.agent_targets_conditional_updates === true\n ? await deactivateDeletedAgentSkills(\n serverUrl, jwt, pullResponse, scopedState, scopedState, syncPaths.skillsDir, deps, assignmentFailures,\n )\n : 0;\n const authUserId = decodeJwtSubject(jwt);\n let previousAuthState: NotisSyncState | null = null;\n if (authUserId && authUserId !== syncUserId) {\n const previousAuthPaths = getSkillSyncPathsForUser(authUserId);\n previousAuthState = await deps.readSyncState(previousAuthPaths);\n await deps.gatherTopLevelLocalSkills(syncPaths, {\n sourceRoots: [{ label: \"previous-auth-scope\", root: previousAuthPaths.skillsDir }],\n protectedSkillNames,\n });\n }\n await deps.gatherTopLevelLocalSkills(syncPaths, {\n protectedSkillNames,\n });\n const localSkills = (await deps.scanLocalSkills(syncPaths))\n .filter((skill) => !BASE_SKILL_NAMES.has(skill.name));\n const previousState = withoutBaseSkillState(applyLegacyFirstRunState(\n localSkills,\n scopedState,\n isEmptySyncState(scopedState)\n ? (!previousAuthState || isEmptySyncState(previousAuthState)\n ? await deps.readLegacySyncState(syncPaths)\n : previousAuthState)\n : null,\n ));\n const gatheredSymlinkResult = await deps.syncSymlinks(\n buildLocalSymlinkCandidates(pullResponse, localSkills, previousState),\n syncPaths.skillsDir,\n );\n const pushCandidates = getPushCandidates(\n localSkills,\n previousState,\n cloudCuratedSkillNames,\n new Set(pullResponse.skills.map((skill) => skill.name)),\n );\n\n const failedPushes: SkillSyncFailure[] = [];\n if (pushCandidates.length > 0) {\n const pushResult = await deps.pushChangedSkills(serverUrl, jwt, pushCandidates);\n if (Array.isArray(pushResult?.failed) && pushResult.failed.length > 0) {\n failedPushes.push(...pushResult.failed);\n console.warn(\n `[skill-sync] ${pushResult.failed.length} skill(s) were rejected during push: ` +\n pushResult.failed.map((f) => `${f.name} (${f.error})`).join(\"; \"),\n );\n }\n pullResponse = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));\n assertSkillsPullAuthorized(pullResponse);\n }\n\n // Delete phase: remove skills that were previously synced but are no longer in the cloud\n const cloudSkillNames = new Set(pullResponse.skills.map((s) => s.name));\n let deleted = 0;\n for (const skillName of Object.keys(previousState.skills)) {\n if (!cloudSkillNames.has(skillName)) {\n await deps.deleteLocalSkill(skillName, syncPaths);\n await deps.removeAllSymlinksForSkill(skillName, syncPaths.skillsDir);\n deleted += 1;\n }\n }\n\n const failedDownloads: SkillSyncFailure[] = [];\n const downloaded = await writePulledSkillsToScopedMirror(\n pullResponse,\n localSkills,\n previousState,\n syncPaths,\n deps,\n failedDownloads,\n );\n\n const finalLocalSkills = (await deps.scanLocalSkills(syncPaths))\n .filter((skill) => !BASE_SKILL_NAMES.has(skill.name));\n const symlinkResult = await deps.syncSymlinks(\n buildLocalSymlinkCandidates(pullResponse, finalLocalSkills, previousState),\n syncPaths.skillsDir,\n );\n const verifiedLinks = { ...(symlinkResult.verifiedAgentLinks ?? {}) };\n for (const failure of failedDownloads) delete verifiedLinks[failure.name];\n const lastSyncedAt = pullResponse.last_synced_at || new Date().toISOString();\n\n await deps.writeSyncState(\n buildSyncState(pullResponse, finalLocalSkills, lastSyncedAt, verifiedLinks, new Set(failedDownloads.map(item => item.name))),\n syncPaths,\n );\n\n return {\n syncEnabled: true,\n pushed: pushCandidates.length,\n pulled: pullResponse.skills.length,\n downloaded,\n deleted,\n deactivated,\n linked: gatheredSymlinkResult.linked + symlinkResult.linked,\n removed: foreignLinksRemoved + gatheredSymlinkResult.removed + symlinkResult.removed,\n skipped: symlinkResult.skipped,\n failedLinks: [...assignmentFailures, ...failedDownloads, ...(symlinkResult.failures ?? []).filter(\n (failure) => !failedDownloads.some((download) => download.name === failure.name),\n )],\n lastSyncedAt,\n failedPushes,\n };\n}\n", "export interface AgentTargets {\n notis: boolean;\n claude_code: boolean;\n cursor: boolean;\n codex: boolean;\n}\n\nexport interface SyncedSkill {\n cloudId: string;\n folderHash: string;\n agentTargets: AgentTargets;\n /** Actual readable managed links, never inferred from desired targets. */\n verifiedAgentLinks?: Partial<AgentTargets>;\n cloudUpdatedAt?: string;\n /** Content accepted on disk; independent of server-specific folder hash formats. */\n cloudContentHash?: string;\n syncedAt: string;\n}\n\nexport interface NotisSyncState {\n version: 1;\n lastSyncedAt: string | null;\n skills: Record<string, SyncedSkill>;\n}\n\nexport interface LocalSkill {\n name: string;\n skillMd: string;\n description: string;\n folderHash: string;\n directoryPath: string;\n sourceUrl?: string;\n bundleFiles?: BundleFile[];\n}\n\nexport interface BundleFile {\n path: string;\n content_b64: string;\n}\n\nexport interface CloudSkill {\n updated_at?: string;\n id: string;\n name: string;\n description: string | null;\n skill_md: string | null;\n agent_targets?: Partial<AgentTargets> | null;\n skill_folder_hash?: string | null;\n skill_source_url?: string | null;\n bundle_files?: BundleFile[] | null;\n bundle_hydration_failed?: boolean | null;\n source: string;\n status: string;\n}\n\nexport interface SyncSettings {\n agent_targets_conditional_updates?: boolean;\n /** Server-verified canonical Notis account id used to scope local mirrors. */\n user_id?: string;\n sync_enabled: boolean;\n last_synced_at: string | null;\n}\n\nexport interface EntitlementAccessDetail {\n status: \"error\";\n code: \"entitlement_upgrade_required\";\n upgrade_required: true;\n entitlement: string;\n required_plan: string;\n upgrade_url: string;\n message: string;\n next_action: Record<string, unknown>;\n}\n\nexport interface SyncPullResponse {\n skills: CloudSkill[];\n sync_enabled: boolean;\n last_synced_at: string | null;\n /** Legacy servers could return a successful empty denial. Current servers\n * return HTTP 403; either form must preserve existing local state. */\n entitlement_access?: EntitlementAccessDetail;\n}\n\n/** A skill the server could not sync, with a user-facing reason (e.g. an invalid\n * SKILL.md description rejected by OpenAI). One bad skill no longer fails the batch. */\nexport interface SkillSyncFailure {\n name: string;\n error: string;\n}\n\nexport interface SyncPushResponse {\n skills: unknown[];\n failed?: SkillSyncFailure[];\n}\n\nexport const DEFAULT_AGENT_TARGETS: AgentTargets = {\n notis: true,\n claude_code: true,\n cursor: true,\n codex: true,\n};\n\nexport function normalizeAgentTargets(targets?: Partial<AgentTargets> | null): AgentTargets {\n return {\n notis: Boolean(targets?.notis ?? DEFAULT_AGENT_TARGETS.notis),\n claude_code: Boolean(targets?.claude_code ?? DEFAULT_AGENT_TARGETS.claude_code),\n cursor: Boolean(targets?.cursor ?? DEFAULT_AGENT_TARGETS.cursor),\n codex: Boolean(targets?.codex ?? DEFAULT_AGENT_TARGETS.codex),\n };\n}\n", "import { createHash } from \"crypto\";\nimport { execFile } from \"child_process\";\nimport type { Dirent } from \"fs\";\nimport { promises as fs } from \"fs\";\nimport os from \"os\";\nimport path from \"path\";\nimport { promisify } from \"util\";\n\nimport type { CloudSkill, LocalSkill, NotisSyncState } from \"./types\";\n\nconst HOME_DIR = os.homedir();\nconst execFileAsync = promisify(execFile);\n\nexport const AGENTS_DIR = path.join(HOME_DIR, \".agents\");\nexport const LEGACY_AGENTS_SKILLS_DIR = path.join(AGENTS_DIR, \"skills\");\nexport const NOTIS_SKILL_SYNC_ROOT = path.join(HOME_DIR, \".notis\", \"skills\");\nexport const LEGACY_NOTIS_SYNC_STATE_PATH = path.join(\n AGENTS_DIR,\n \".notis-sync.json\",\n);\nexport const AGENTS_SKILLS_DIR = LEGACY_AGENTS_SKILLS_DIR;\nexport const NOTIS_SYNC_STATE_PATH = LEGACY_NOTIS_SYNC_STATE_PATH;\nconst SKILL_LOCK_PATH = path.join(AGENTS_DIR, \".skill-lock.json\");\n\nconst DEFAULT_SYNC_STATE: NotisSyncState = {\n version: 1,\n lastSyncedAt: null,\n skills: {},\n};\nconst EXCLUDED_TOP_LEVEL_ROOT_NAMES = new Set([\n \"backup\",\n \"backups\",\n \"builtin\",\n \"builtins\",\n \"cache\",\n \"caches\",\n \"marketplace\",\n \"marketplaces\",\n \"plugin\",\n \"plugins\",\n \"temp\",\n \"tmp\",\n \"worktree\",\n \"worktrees\",\n]);\n\nexport interface SkillSyncPaths {\n agentsDir: string;\n syncRoot: string;\n legacySkillsDir: string;\n legacyScopedSkillsDir: string;\n legacyScopedSyncStatePath: string;\n skillsDir: string;\n syncStatePath: string;\n skillLockPath: string;\n gatherMetadataPath: string;\n}\n\nconst DEFAULT_SYNC_PATHS: SkillSyncPaths = {\n agentsDir: AGENTS_DIR,\n syncRoot: NOTIS_SKILL_SYNC_ROOT,\n legacySkillsDir: LEGACY_AGENTS_SKILLS_DIR,\n legacyScopedSkillsDir: LEGACY_AGENTS_SKILLS_DIR,\n legacyScopedSyncStatePath: LEGACY_NOTIS_SYNC_STATE_PATH,\n skillsDir: LEGACY_AGENTS_SKILLS_DIR,\n syncStatePath: LEGACY_NOTIS_SYNC_STATE_PATH,\n skillLockPath: SKILL_LOCK_PATH,\n gatherMetadataPath: path.join(NOTIS_SKILL_SYNC_ROOT, \"skill-gather-metadata.json\"),\n};\n\nfunction getDefaultSyncRootForAgentsDir(resolvedAgentsDir: string): string {\n if (resolvedAgentsDir === path.resolve(AGENTS_DIR)) {\n return NOTIS_SKILL_SYNC_ROOT;\n }\n return path.join(resolvedAgentsDir, \".notis\", \"skills\");\n}\n\nfunction isResolvedChildPath(baseDir: string, candidatePath: string): boolean {\n return candidatePath.startsWith(`${baseDir}${path.sep}`);\n}\n\nfunction sanitizePathSegment(value: string): string {\n const raw = value.trim();\n const sanitized = raw\n .replace(/[^A-Za-z0-9_-]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n if (!sanitized) {\n throw new Error(\"Invalid sync user id\");\n }\n if (sanitized === raw) {\n return sanitized;\n }\n return `${sanitized}-${createHash(\"sha256\").update(raw).digest(\"hex\").slice(0, 12)}`;\n}\n\nexport function getSkillSyncPathsForUser(\n authUserId: string,\n agentsDir: string = AGENTS_DIR,\n): SkillSyncPaths {\n const safeUserId = sanitizePathSegment(authUserId);\n const resolvedAgentsDir = path.resolve(agentsDir);\n const syncRoot = getDefaultSyncRootForAgentsDir(resolvedAgentsDir);\n const userRoot = path.join(syncRoot, \"users\", safeUserId);\n const legacyUserRoot = path.join(resolvedAgentsDir, \"notis\", \"users\", safeUserId);\n\n return {\n agentsDir: resolvedAgentsDir,\n syncRoot,\n legacySkillsDir: path.join(resolvedAgentsDir, \"skills\"),\n legacyScopedSkillsDir: path.join(legacyUserRoot, \"skills\"),\n legacyScopedSyncStatePath: path.join(legacyUserRoot, \".notis-sync.json\"),\n skillsDir: path.join(userRoot, \"skills\"),\n syncStatePath: path.join(userRoot, \".notis-sync.json\"),\n skillLockPath: path.join(resolvedAgentsDir, \".skill-lock.json\"),\n gatherMetadataPath: path.join(userRoot, \".notis-gathered-skills.json\"),\n };\n}\n\nfunction assertRelativeBundlePath(relativePath: string): string {\n const normalized = relativePath.replace(/\\\\/g, \"/\").replace(/^\\/+|\\/+$/g, \"\");\n if (\n !normalized ||\n normalized.startsWith(\"../\") ||\n `/${normalized}/`.includes(\"/../\")\n ) {\n throw new Error(`Invalid bundle file path: ${relativePath}`);\n }\n return normalized;\n}\n\nfunction stripWrappingQuotes(value: string): string {\n return value.replace(/^['\"]|['\"]$/g, \"\").trim();\n}\n\nfunction parseFrontMatter(skillMd: string): { description: string } {\n const match = skillMd.match(/^---\\s*\\n([\\s\\S]*?)\\n---\\s*(?:\\n|$)/);\n if (!match) {\n return { description: \"\" };\n }\n\n let description = \"\";\n for (const line of match[1].split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue;\n }\n const descriptionMatch = trimmed.match(/^description\\s*:\\s*(.+)$/i);\n if (descriptionMatch) {\n description = stripWrappingQuotes(descriptionMatch[1]);\n break;\n }\n }\n\n return { description };\n}\n\nasync function readJsonFile<T>(filePath: string): Promise<T | null> {\n try {\n const raw = await fs.readFile(filePath, \"utf8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\nasync function listFilesRecursive(dirPath: string): Promise<string[]> {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n const nested = await Promise.all(\n entries.map(async (entry) => {\n if (entry.name === \".DS_Store\") {\n return [];\n }\n\n const fullPath = path.join(dirPath, entry.name);\n if (entry.isDirectory()) {\n return listFilesRecursive(fullPath);\n }\n\n if (entry.isFile()) {\n return [fullPath];\n }\n\n return [];\n }),\n );\n\n return nested.flat().sort();\n}\n\nasync function computeFolderHash(dirPath: string): Promise<string> {\n const hash = createHash(\"sha256\");\n const filePaths = await listFilesRecursive(dirPath);\n\n for (const filePath of filePaths) {\n const relativePath = path.relative(dirPath, filePath);\n hash.update(relativePath);\n hash.update(\"\\0\");\n hash.update(await fs.readFile(filePath));\n hash.update(\"\\0\");\n }\n\n return hash.digest(\"hex\");\n}\n\nfunction resolveSkillBundleFilePath(\n skillDir: string,\n relativePath: string,\n): string {\n const normalizedPath = assertRelativeBundlePath(relativePath);\n const resolvedSkillDir = path.resolve(skillDir);\n const candidatePath = path.resolve(resolvedSkillDir, normalizedPath);\n if (\n candidatePath === resolvedSkillDir ||\n !isResolvedChildPath(resolvedSkillDir, candidatePath)\n ) {\n throw new Error(\n `Bundle file resolves outside the expected directory: ${relativePath}`,\n );\n }\n return candidatePath;\n}\n\nasync function readSkillMdIfValid(skillDir: string): Promise<boolean> {\n try {\n await fs.readFile(path.join(skillDir, \"SKILL.md\"), \"utf8\");\n return true;\n } catch {\n return false;\n }\n}\n\nasync function moveDirectory(\n sourceDir: string,\n destinationDir: string,\n): Promise<void> {\n await fs.mkdir(path.dirname(destinationDir), { recursive: true });\n try {\n await fs.rename(sourceDir, destinationDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EXDEV\") {\n throw error;\n }\n await fs.cp(sourceDir, destinationDir, {\n recursive: true,\n errorOnExist: true,\n force: false,\n });\n await fs.rm(sourceDir, { recursive: true, force: true });\n }\n}\n\nasync function createDirectorySymlink(\n targetDir: string,\n linkPath: string,\n): Promise<void> {\n await fs.mkdir(path.dirname(linkPath), { recursive: true });\n await fs.symlink(path.relative(path.dirname(linkPath), targetDir), linkPath, \"dir\");\n}\n\nfunction backupRootFor(paths: SkillSyncPaths, timestamp: string): string {\n return path.join(paths.syncRoot, \"skill-dedupe-backups\", timestamp);\n}\n\nfunction relativeBackupPath(label: string, skillName: string): string {\n return path.join(\n label.replace(/[^A-Za-z0-9_.-]+/g, \"_\"),\n safeName(skillName),\n );\n}\n\nfunction isExcludedTopLevelSkillEntry(entryName: string): boolean {\n return (\n entryName.startsWith(\".\") ||\n isTransientSkillDirectoryName(entryName) ||\n EXCLUDED_TOP_LEVEL_ROOT_NAMES.has(entryName.toLowerCase())\n );\n}\n\nfunction isTransientSkillDirectoryName(entryName: string): boolean {\n return /\\.(?:backup|staging)-/.test(entryName);\n}\n\ninterface TopLevelSkillSource {\n label: string;\n root: string;\n priority: number;\n}\n\ninterface TopLevelSkillCandidate {\n name: string;\n root: string;\n label: string;\n path: string;\n resolvedPath: string;\n priority: number;\n isScoped: boolean;\n isSymlink: boolean;\n}\n\nexport interface GatherTopLevelLocalSkillsOptions {\n sourceRoots?: Array<{ label: string; root: string }>;\n protectedSkillNames?: ReadonlySet<string>;\n timestamp?: string;\n}\n\nexport interface GatherTopLevelLocalSkillsResult {\n gathered: number;\n backedUp: number;\n skipped: number;\n metadataPath: string;\n}\n\nfunction defaultTopLevelSkillSources(\n paths: SkillSyncPaths,\n): TopLevelSkillSource[] {\n const sources: TopLevelSkillSource[] = [];\n if (path.resolve(paths.legacyScopedSkillsDir) !== path.resolve(paths.skillsDir)) {\n sources.push({\n label: \"notis-legacy-scoped\",\n root: paths.legacyScopedSkillsDir,\n priority: 1,\n });\n }\n\n sources.push(\n { label: \"agents\", root: paths.legacySkillsDir, priority: 2 },\n {\n label: \"codex\",\n root: path.join(HOME_DIR, \".codex\", \"skills\"),\n priority: 3,\n },\n {\n label: \"cursor\",\n root: path.join(HOME_DIR, \".cursor\", \"skills\"),\n priority: 4,\n },\n {\n label: \"claude\",\n root: path.join(HOME_DIR, \".claude\", \"skills\"),\n priority: 5,\n },\n );\n\n return sources;\n}\n\nfunction isManagedTopLevelSymlinkTarget(\n resolvedPath: string,\n paths: SkillSyncPaths,\n): boolean {\n const managedRoots = [\n path.resolve(paths.skillsDir),\n path.resolve(paths.legacySkillsDir),\n path.resolve(paths.legacyScopedSkillsDir),\n path.join(path.resolve(paths.syncRoot), \"base\"),\n path.join(path.resolve(paths.syncRoot), \"users\"),\n path.join(path.resolve(paths.agentsDir), \"notis\", \"users\"),\n ];\n return managedRoots.some(\n (root) =>\n resolvedPath === root || resolvedPath.startsWith(`${root}${path.sep}`),\n );\n}\n\nasync function listTopLevelSkillCandidates(\n paths: SkillSyncPaths,\n options: GatherTopLevelLocalSkillsOptions,\n): Promise<TopLevelSkillCandidate[]> {\n const sourceRoots = options.sourceRoots\n ? options.sourceRoots.map((source, index) => ({\n ...source,\n priority: index + 1,\n }))\n : defaultTopLevelSkillSources(paths);\n const candidates: TopLevelSkillCandidate[] = [];\n\n try {\n const scopedEntries = await fs.readdir(paths.skillsDir, {\n withFileTypes: true,\n });\n for (const entry of scopedEntries) {\n if (\n (!entry.isDirectory() && !entry.isSymbolicLink()) ||\n entry.name.startsWith(\".\") ||\n isTransientSkillDirectoryName(entry.name)\n ) {\n continue;\n }\n const skillDir = path.join(paths.skillsDir, entry.name);\n let resolvedPath = path.resolve(skillDir);\n if (entry.isSymbolicLink()) {\n try {\n resolvedPath = path.resolve(path.dirname(skillDir), await fs.readlink(skillDir));\n } catch {\n continue;\n }\n }\n if (await readSkillMdIfValid(skillDir)) {\n candidates.push({\n name: safeName(entry.name, paths.skillsDir),\n root: paths.skillsDir,\n label: \"notis-managed\",\n path: skillDir,\n resolvedPath,\n priority: 0,\n isScoped: true,\n isSymlink: entry.isSymbolicLink(),\n });\n }\n }\n } catch {\n // Missing scoped mirror is expected before the first sync.\n }\n\n for (const source of sourceRoots) {\n let entries: Dirent[];\n try {\n entries = await fs.readdir(source.root, { withFileTypes: true });\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n if (\n isExcludedTopLevelSkillEntry(entry.name) ||\n (!entry.isDirectory() && !entry.isSymbolicLink())\n ) {\n continue;\n }\n\n const candidatePath = path.join(source.root, entry.name);\n let resolvedPath: string;\n try {\n resolvedPath = entry.isSymbolicLink()\n ? path.resolve(\n path.dirname(candidatePath),\n await fs.readlink(candidatePath),\n )\n : path.resolve(candidatePath);\n } catch {\n continue;\n }\n\n if (\n entry.isSymbolicLink() &&\n isManagedTopLevelSymlinkTarget(resolvedPath, paths)\n ) {\n continue;\n }\n\n if (!(await readSkillMdIfValid(resolvedPath))) {\n continue;\n }\n\n candidates.push({\n name: safeName(entry.name, paths.skillsDir),\n root: source.root,\n label: source.label,\n path: candidatePath,\n resolvedPath,\n priority: source.priority,\n isScoped: false,\n isSymlink: entry.isSymbolicLink(),\n });\n }\n }\n\n return candidates.sort(\n (a, b) => a.priority - b.priority || a.name.localeCompare(b.name),\n );\n}\n\nexport async function gatherTopLevelLocalSkills(\n paths: SkillSyncPaths,\n options: GatherTopLevelLocalSkillsOptions = {},\n): Promise<GatherTopLevelLocalSkillsResult> {\n await ensureCanonicalSkillsDir(paths);\n\n const protectedSkillNames = options.protectedSkillNames || new Set<string>();\n const timestamp =\n options.timestamp || new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupRoot = backupRootFor(paths, timestamp);\n const candidates = await listTopLevelSkillCandidates(paths, options);\n const byName = new Map<string, TopLevelSkillCandidate[]>();\n\n for (const candidate of candidates) {\n const existing = byName.get(candidate.name) || [];\n existing.push(candidate);\n byName.set(candidate.name, existing);\n }\n\n let gathered = 0;\n let backedUp = 0;\n let skipped = 0;\n const metadata: {\n version: 1;\n gatheredAt: string;\n skills: Record<\n string,\n {\n canonicalPath: string | null;\n skippedSources: string[];\n protectedFromLocalGather?: boolean;\n }\n >;\n } = {\n version: 1,\n gatheredAt: new Date().toISOString(),\n skills: {},\n };\n\n for (const [skillName, skillCandidates] of byName) {\n const protectedFromLocalGather = protectedSkillNames.has(skillName);\n const canonical = protectedFromLocalGather\n ? skillCandidates.find((candidate) => candidate.isScoped) || null\n : skillCandidates[0];\n const destinationName = safeName(skillName, paths.skillsDir);\n const destinationDir = path.join(paths.skillsDir, destinationName);\n const skippedSources: string[] = [];\n\n if (!canonical) {\n for (const candidate of skillCandidates) {\n if (candidate.isSymlink) {\n skipped += 1;\n skippedSources.push(candidate.path);\n continue;\n }\n const backupDir = path.join(\n backupRoot,\n relativeBackupPath(candidate.label, skillName),\n );\n try {\n await moveDirectory(candidate.path, backupDir);\n backedUp += 1;\n skippedSources.push(candidate.path);\n } catch {\n skipped += 1;\n }\n }\n metadata.skills[skillName] = {\n canonicalPath: null,\n skippedSources,\n protectedFromLocalGather: true,\n };\n continue;\n }\n\n if (!canonical.isScoped && !(await pathExists(destinationDir))) {\n try {\n if (canonical.isSymlink) {\n await createDirectorySymlink(canonical.resolvedPath, destinationDir);\n } else {\n await moveDirectory(canonical.path, destinationDir);\n }\n gathered += 1;\n } catch {\n skipped += 1;\n }\n }\n\n for (const candidate of skillCandidates) {\n if (candidate === canonical || candidate.isScoped) {\n continue;\n }\n if (candidate.isSymlink) {\n skippedSources.push(candidate.path);\n continue;\n }\n const backupDir = path.join(\n backupRoot,\n relativeBackupPath(candidate.label, skillName),\n );\n try {\n await moveDirectory(candidate.path, backupDir);\n backedUp += 1;\n skippedSources.push(candidate.path);\n } catch {\n skipped += 1;\n }\n }\n\n metadata.skills[skillName] = {\n canonicalPath: (await pathExists(destinationDir))\n ? destinationDir\n : canonical.path,\n skippedSources,\n ...(protectedFromLocalGather ? { protectedFromLocalGather: true } : {}),\n };\n }\n\n await fs.mkdir(path.dirname(paths.gatherMetadataPath), { recursive: true });\n await fs.writeFile(\n paths.gatherMetadataPath,\n `${JSON.stringify(metadata, null, 2)}\\n`,\n \"utf8\",\n );\n\n return {\n gathered,\n backedUp,\n skipped,\n metadataPath: paths.gatherMetadataPath,\n };\n}\n\nasync function readSkillLock(\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<Record<string, string>> {\n type SkillLock = {\n skills?: Record<string, { sourceUrl?: string }>;\n };\n\n const lockData = await readJsonFile<SkillLock>(paths.skillLockPath);\n const sourceUrls: Record<string, string> = {};\n\n for (const [skillName, entry] of Object.entries(lockData?.skills || {})) {\n if (typeof entry?.sourceUrl === \"string\" && entry.sourceUrl.trim()) {\n sourceUrls[skillName] = entry.sourceUrl.trim();\n }\n }\n\n return sourceUrls;\n}\n\nexport async function ensureCanonicalSkillsDir(\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<void> {\n await fs.mkdir(paths.skillsDir, { recursive: true });\n}\n\nexport async function initializeScopedSkillsFromLegacy(\n paths: SkillSyncPaths,\n): Promise<number> {\n const usingLegacyPath =\n path.resolve(paths.skillsDir) === path.resolve(paths.legacySkillsDir);\n if (usingLegacyPath || (await pathExists(paths.syncStatePath))) {\n await ensureCanonicalSkillsDir(paths);\n return 0;\n }\n\n await ensureCanonicalSkillsDir(paths);\n let entries: Dirent[];\n try {\n entries = await fs.readdir(paths.legacySkillsDir, { withFileTypes: true });\n } catch {\n return 0;\n }\n\n let copied = 0;\n for (const entry of entries) {\n if (!entry.isDirectory()) {\n continue;\n }\n\n const legacySkillDir = path.join(paths.legacySkillsDir, entry.name);\n const targetSkillName = safeName(entry.name, paths.skillsDir);\n const scopedSkillDir = path.join(paths.skillsDir, targetSkillName);\n try {\n await fs.readFile(path.join(legacySkillDir, \"SKILL.md\"), \"utf8\");\n if (await pathExists(scopedSkillDir)) {\n continue;\n }\n await fs.cp(legacySkillDir, scopedSkillDir, {\n recursive: true,\n errorOnExist: true,\n force: false,\n });\n copied += 1;\n } catch {\n // Ignore invalid legacy skill folders and existing scoped copies.\n }\n }\n\n return copied;\n}\n\nexport async function scanLocalSkills(\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<LocalSkill[]> {\n await ensureCanonicalSkillsDir(paths);\n\n const sourceUrls = await readSkillLock(paths);\n const entries = await fs.readdir(paths.skillsDir, { withFileTypes: true });\n const skills: Array<LocalSkill | null> = await Promise.all(\n entries.map(async (entry) => {\n if (\n (!entry.isDirectory() && !entry.isSymbolicLink()) ||\n isTransientSkillDirectoryName(entry.name)\n ) {\n return null;\n }\n\n const skillDir = path.join(paths.skillsDir, entry.name);\n const skillMdPath = path.join(skillDir, \"SKILL.md\");\n\n try {\n const skillMd = await fs.readFile(skillMdPath, \"utf8\");\n const { description } = parseFrontMatter(skillMd);\n const folderHash = await computeFolderHash(skillDir);\n const skill: LocalSkill = {\n name: entry.name,\n skillMd,\n description,\n folderHash,\n directoryPath: skillDir,\n };\n if (sourceUrls[entry.name]) {\n skill.sourceUrl = sourceUrls[entry.name];\n }\n return skill;\n } catch {\n return null;\n }\n }),\n );\n\n return skills\n .filter((skill): skill is LocalSkill => skill !== null)\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\nfunction normalizeSyncState(\n state: NotisSyncState | null,\n): NotisSyncState | null {\n if (!state || state.version !== 1 || typeof state.skills !== \"object\") {\n return null;\n }\n const normalizeStoredAgentTargets = (\n targets: Partial<NotisSyncState[\"skills\"][string][\"agentTargets\"]> | null | undefined,\n ): NotisSyncState[\"skills\"][string][\"agentTargets\"] => ({\n notis: Boolean(targets?.notis ?? true),\n claude_code: Boolean(targets?.claude_code ?? true),\n cursor: Boolean(targets?.cursor ?? true),\n codex: Boolean(targets?.codex ?? true),\n });\n\n return {\n version: 1,\n lastSyncedAt:\n typeof state.lastSyncedAt === \"string\" ? state.lastSyncedAt : null,\n skills: Object.fromEntries(\n Object.entries(state.skills || {}).map(([skillName, skillState]) => [\n skillName,\n {\n ...skillState,\n agentTargets: normalizeStoredAgentTargets(skillState.agentTargets),\n },\n ]),\n ),\n };\n}\n\nexport async function readSyncState(\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<NotisSyncState> {\n const scopedState = normalizeSyncState(\n await readJsonFile<NotisSyncState>(paths.syncStatePath),\n );\n if (scopedState) {\n return scopedState;\n }\n\n return DEFAULT_SYNC_STATE;\n}\n\nexport async function readLegacySyncState(\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<NotisSyncState | null> {\n const legacyStatePaths = [\n paths.legacyScopedSyncStatePath,\n path.resolve(paths.syncStatePath) === path.resolve(LEGACY_NOTIS_SYNC_STATE_PATH)\n ? paths.syncStatePath\n : path.join(paths.agentsDir, \".notis-sync.json\"),\n ];\n\n for (const legacyStatePath of legacyStatePaths) {\n const state = normalizeSyncState(\n await readJsonFile<NotisSyncState>(legacyStatePath),\n );\n if (state) {\n return state;\n }\n }\n\n return null;\n}\n\nexport async function writeSyncState(\n state: NotisSyncState,\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<void> {\n await fs.mkdir(path.dirname(paths.syncStatePath), { recursive: true });\n await fs.writeFile(\n paths.syncStatePath,\n `${JSON.stringify(state, null, 2)}\\n`,\n \"utf8\",\n );\n}\n\nexport function safeName(\n name: string,\n baseDir: string = AGENTS_SKILLS_DIR,\n): string {\n const sanitizedName = name\n .trim()\n .replace(/[\\\\/]+/g, \"\")\n .replace(/\\.\\./g, \"\");\n if (!sanitizedName) {\n throw new Error(\"Invalid skill name\");\n }\n\n const resolvedBaseDir = path.resolve(baseDir);\n const resolvedPath = path.resolve(resolvedBaseDir, sanitizedName);\n if (!isResolvedChildPath(resolvedBaseDir, resolvedPath)) {\n throw new Error(\"Skill name resolves outside the expected directory\");\n }\n\n return sanitizedName;\n}\n\nexport async function deleteLocalSkill(\n skillName: string,\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<boolean> {\n const skillDir = path.join(\n paths.skillsDir,\n safeName(skillName, paths.skillsDir),\n );\n try {\n await fs.rm(skillDir, { recursive: true, force: true });\n return true;\n } catch {\n return false;\n }\n}\n\nasync function createZipFromDirectory(directoryPath: string): Promise<string> {\n const zipPath = path.join(\n os.tmpdir(),\n `notis-skill-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`,\n );\n const parentDir = path.dirname(directoryPath);\n const directoryName = path.basename(directoryPath);\n\n if (process.platform === \"win32\") {\n await execFileAsync(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n `Compress-Archive -Path '${directoryPath.replace(/'/g, \"''\")}' -DestinationPath '${zipPath.replace(/'/g, \"''\")}' -Force`,\n ]);\n return zipPath;\n }\n\n await execFileAsync(\"zip\", [\"-qry\", zipPath, directoryName], {\n cwd: parentDir,\n });\n return zipPath;\n}\n\nasync function extractZipToDirectory(\n zipPath: string,\n destinationDir: string,\n): Promise<void> {\n const extractRoot = await fs.mkdtemp(\n path.join(os.tmpdir(), \"notis-skill-extract-\"),\n );\n\n try {\n if (process.platform === \"win32\") {\n await execFileAsync(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n `Expand-Archive -Path '${zipPath.replace(/'/g, \"''\")}' -DestinationPath '${extractRoot.replace(/'/g, \"''\")}' -Force`,\n ]);\n } else {\n await execFileAsync(\"unzip\", [\"-qq\", zipPath, \"-d\", extractRoot]);\n }\n\n const extractedEntries = await fs.readdir(extractRoot, {\n withFileTypes: true,\n });\n const extractedDirectory = extractedEntries.find((entry) =>\n entry.isDirectory(),\n );\n const sourceDir = extractedDirectory\n ? path.join(extractRoot, extractedDirectory.name)\n : extractRoot;\n\n await fs.rm(destinationDir, { recursive: true, force: true });\n await fs.mkdir(path.dirname(destinationDir), { recursive: true });\n await fs.cp(sourceDir, destinationDir, { recursive: true });\n } finally {\n await fs.rm(extractRoot, { recursive: true, force: true });\n }\n}\n\nasync function pathExists(targetPath: string): Promise<boolean> {\n try {\n await fs.access(targetPath);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function replaceSkillDirectoryAtomically(\n skillDir: string,\n populateDir: (stagingDir: string) => Promise<void>,\n): Promise<void> {\n const parentDir = path.dirname(skillDir);\n const skillName = path.basename(skillDir);\n await fs.mkdir(parentDir, { recursive: true });\n\n const stagingDir = await fs.mkdtemp(\n path.join(parentDir, `${skillName}.staging-`),\n );\n const backupDir = path.join(\n parentDir,\n `${skillName}.backup-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n );\n\n let movedExisting = false;\n let promotedStaging = false;\n let cleanupError: Error | null = null;\n\n try {\n await populateDir(stagingDir);\n\n if (await pathExists(skillDir)) {\n await fs.rename(skillDir, backupDir);\n movedExisting = true;\n }\n\n await fs.rename(stagingDir, skillDir);\n promotedStaging = true;\n } catch (error) {\n if (movedExisting && !promotedStaging && (await pathExists(backupDir))) {\n await fs.rename(backupDir, skillDir);\n }\n throw error;\n } finally {\n if (!promotedStaging && (await pathExists(stagingDir))) {\n await fs.rm(stagingDir, { recursive: true, force: true });\n }\n if ((promotedStaging || !movedExisting) && (await pathExists(backupDir))) {\n try {\n await fs.rm(backupDir, { recursive: true, force: true });\n } catch (error) {\n if (!cleanupError) {\n cleanupError =\n error instanceof Error ? error : new Error(String(error));\n }\n }\n }\n if (cleanupError) {\n console.warn(\n `[Notis] Failed to clean up temporary skill directory backup for \"${skillDir}\"`,\n cleanupError,\n );\n }\n }\n}\n\nfunction bundleFilesIncludeSkillMd(\n bundleFiles: Array<{ path: string }>,\n): boolean {\n return bundleFiles.some((bundleFile) => {\n const basename = path.basename(bundleFile.path).toLowerCase();\n return basename === \"skill.md\" || basename === \"skills.md\";\n });\n}\n\nexport async function createSkillBundleBase64(\n skill: LocalSkill,\n): Promise<string> {\n const zipPath = await createZipFromDirectory(skill.directoryPath);\n try {\n const zipBytes = await fs.readFile(zipPath);\n return zipBytes.toString(\"base64\");\n } finally {\n await fs.rm(zipPath, { force: true });\n }\n}\n\nexport async function writeCloudSkillToDisk(\n skill: CloudSkill,\n bundleBytes?: Buffer,\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<boolean> {\n const skillDir = path.join(\n paths.skillsDir,\n safeName(skill.name, paths.skillsDir),\n );\n\n if (bundleBytes?.length) {\n const bundlePath = path.join(\n os.tmpdir(),\n `notis-skill-download-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`,\n );\n try {\n await fs.writeFile(bundlePath, bundleBytes);\n await extractZipToDirectory(bundlePath, skillDir);\n return true;\n } finally {\n await fs.rm(bundlePath, { force: true });\n }\n }\n\n if (\n !skill.skill_md &&\n (!skill.bundle_files || skill.bundle_files.length === 0)\n ) {\n return false;\n }\n\n if (skill.bundle_files && skill.bundle_files.length > 0) {\n if (!bundleFilesIncludeSkillMd(skill.bundle_files)) {\n throw new Error(\n `Synced bundle for \"${skill.name}\" is missing SKILL.md or SKILLS.md`,\n );\n }\n await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {\n for (const bundleFile of skill.bundle_files || []) {\n const filePath = resolveSkillBundleFilePath(\n stagingDir,\n bundleFile.path,\n );\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(\n filePath,\n Buffer.from(bundleFile.content_b64, \"base64\"),\n );\n }\n });\n return true;\n }\n\n await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {\n await fs.writeFile(\n path.join(stagingDir, \"SKILL.md\"),\n skill.skill_md ?? \"\",\n \"utf8\",\n );\n });\n return true;\n}\n", "import { createSkillBundleBase64 } from './local-scanner';\nimport type { AgentTargets, LocalSkill, SyncPullResponse, SyncPushResponse, SyncSettings } from './types';\n\ntype JsonBody = Record<string, unknown> | undefined;\n\nasync function requestJson<T>(\n url: string,\n jwt: string,\n options: { method?: string; body?: JsonBody } = {},\n): Promise<T> {\n const response = await fetch(url, {\n signal: AbortSignal.timeout(90_000),\n method: options.method || 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${jwt}`,\n },\n body: options.body ? JSON.stringify(options.body) : undefined,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`${options.method || 'POST'} ${url} \u2192 ${response.status}: ${text}`);\n }\n\n return response.json() as Promise<T>;\n}\n\nexport async function fetchSyncSettings(serverUrl: string, jwt: string): Promise<SyncSettings> {\n return requestJson<SyncSettings>(`${serverUrl}/portal_skills/sync-settings`, jwt, {\n body: {},\n });\n}\n\nexport async function pullSkills(serverUrl: string, jwt: string): Promise<SyncPullResponse> {\n return requestJson<SyncPullResponse>(`${serverUrl}/portal_skills/sync-pull`, jwt, {\n body: {},\n });\n}\n\nexport async function pushChangedSkills(\n serverUrl: string,\n jwt: string,\n changedSkills: LocalSkill[],\n): Promise<SyncPushResponse> {\n const payloadSkills = await Promise.all(changedSkills.map(async (skill) => ({\n name: skill.name,\n description: skill.description,\n skill_md: skill.skillMd,\n source_url: skill.sourceUrl,\n folder_hash: skill.folderHash,\n bundle_base64: await createSkillBundleBase64(skill),\n })));\n\n return requestJson<SyncPushResponse>(`${serverUrl}/portal_skills/sync-push`, jwt, {\n body: {\n skills: payloadSkills,\n },\n });\n}\n\nexport async function downloadSkillBundle(bundleUrl: string): Promise<Buffer> {\n const response = await fetch(bundleUrl, { signal: AbortSignal.timeout(90_000) });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`GET ${bundleUrl} \u2192 ${response.status}: ${text}`);\n }\n\n return Buffer.from(await response.arrayBuffer());\n}\n\nexport async function updateAgentTargets(\n serverUrl: string,\n jwt: string,\n skillId: string,\n targets: Partial<AgentTargets>,\n expectedUpdatedAt?: string,\n): Promise<{ success: boolean; agent_targets: AgentTargets; updated_at?: string }> {\n return requestJson<{ success: boolean; agent_targets: AgentTargets; updated_at?: string }>(`${serverUrl}/portal_skills/agent-targets`, jwt, {\n method: 'PATCH',\n body: {\n skill_id: skillId,\n agent_targets: targets,\n ...(expectedUpdatedAt ? { expected_updated_at: expectedUpdatedAt } : {}),\n },\n });\n}\n\n// Install (enabled=true) or uninstall (enabled=false) the curated computer-use\n// skill for the user, matching the desktop app's \"Desktop Use\" toggle.\nexport async function setComputerUseSkill(\n serverUrl: string,\n jwt: string,\n enabled: boolean,\n): Promise<{ installed: boolean }> {\n return requestJson<{ installed: boolean }>(`${serverUrl}/portal_skills/desktop-use`, jwt, {\n body: { enabled, name: 'notis-desktop-use' },\n });\n}\n", "import { promises as fs } from 'fs';\nimport os from 'os';\nimport path from 'path';\n\nimport {\n AGENTS_DIR,\n LEGACY_AGENTS_SKILLS_DIR,\n NOTIS_SKILL_SYNC_ROOT,\n safeName,\n} from './local-scanner';\nimport { normalizeAgentTargets, type AgentTargets, type SkillSyncFailure, type CloudSkill, type NotisSyncState } from './types';\n\nconst HOME_DIR = os.homedir();\n\nconst EXTERNAL_AGENT_SKILL_DIRS = {\n claude_code: path.join(HOME_DIR, '.claude', 'skills'),\n cursor: path.join(HOME_DIR, '.cursor', 'skills'),\n codex: path.join(HOME_DIR, '.codex', 'skills'),\n} as const;\n\ntype ExternalAgent = keyof typeof EXTERNAL_AGENT_SKILL_DIRS;\n\nconst EXTERNAL_AGENTS = Object.keys(EXTERNAL_AGENT_SKILL_DIRS) as ExternalAgent[];\n\nconst AGENT_FAILURE_LABELS: Record<string, string> = {\n claude_code: 'Claude Code',\n cursor: 'Cursor',\n codex: 'Codex',\n legacy: 'legacy ~/.agents/skills',\n};\n\n/** Failures are shown next to skill names in the Portal and CLI; never leak raw agent ids. */\nfunction agentFailureLabel(agent: string): string {\n return AGENT_FAILURE_LABELS[agent] ?? agent;\n}\n\nfunction agentFolderFailureName(agent: string): string {\n return `${agentFailureLabel(agent)} skills folder`;\n}\n\nexport interface DeletedAgentSymlink {\n skillId: string;\n skillName: string;\n agent: ExternalAgent;\n}\n\ntype AgentSkillDirs = {\n notis: string;\n} & typeof EXTERNAL_AGENT_SKILL_DIRS;\n\nexport interface SymlinkSyncOptions {\n agentSkillDirs?: Partial<AgentSkillDirs>;\n legacyGlobalSkillsDir?: string;\n removeUndesired?: boolean;\n}\n\nexport interface RemoveSkillSymlinkOptions extends SymlinkSyncOptions {\n ownership?: 'any-managed' | 'exact-skill-dir';\n}\n\nexport interface SymlinkSyncResult {\n linked: number;\n removed: number;\n skipped: number;\n verifiedAgentLinks?: Record<string, Partial<AgentTargets>>;\n failures?: SkillSyncFailure[];\n}\n\n/** Remove only links into another account's managed mirror. This is safe even\n * when automatic sync is disabled: it never changes either account mirror and\n * preserves links owned by the currently authenticated account. */\nexport async function removeForeignAccountSymlinks(\n skillsDir: string,\n options: SymlinkSyncOptions = {},\n): Promise<number> {\n const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };\n const currentRoot = path.resolve(skillsDir);\n const foreignCapableRoots = [\n path.join(path.resolve(NOTIS_SKILL_SYNC_ROOT), 'users'),\n path.join(path.resolve(AGENTS_DIR), 'notis', 'users'),\n path.dirname(path.dirname(currentRoot)),\n ];\n let removed = 0;\n const legacyGlobalSkillsDir = options.legacyGlobalSkillsDir || LEGACY_AGENTS_SKILLS_DIR;\n for (const agentDir of new Set([...Object.values(agentSkillDirs), legacyGlobalSkillsDir])) {\n let entries;\n try {\n entries = await fs.readdir(agentDir, { withFileTypes: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') continue;\n throw error;\n }\n for (const entry of entries) {\n if (!entry.isSymbolicLink()) continue;\n const entryPath = path.join(agentDir, entry.name);\n try {\n const target = await fs.readlink(entryPath);\n const resolvedTarget = path.resolve(path.dirname(entryPath), target);\n const belongsToAnyAccount = foreignCapableRoots.some((root) => (\n resolvedTarget.startsWith(`${root}${path.sep}`)\n ));\n const belongsToCurrentAccount = resolvedTarget === currentRoot\n || resolvedTarget.startsWith(`${currentRoot}${path.sep}`);\n if (belongsToAnyAccount && !belongsToCurrentAccount) {\n await fs.unlink(entryPath);\n removed += 1;\n }\n } catch (error) {\n // A disappearing link is already safe. Permission and I/O failures\n // must fail closed: otherwise a previous account's link can remain\n // exposed while the repeating sync reports success.\n if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') throw error;\n }\n }\n }\n return removed;\n}\n\nfunction managedSkillRoots(\n skillsDir: string,\n legacyGlobalSkillsDir: string = LEGACY_AGENTS_SKILLS_DIR,\n): string[] {\n const resolvedSkillsDir = path.resolve(skillsDir);\n const scopedUsersRoot = path.dirname(path.dirname(resolvedSkillsDir));\n const roots = [\n resolvedSkillsDir,\n path.resolve(legacyGlobalSkillsDir),\n path.join(path.resolve(NOTIS_SKILL_SYNC_ROOT), 'users'),\n path.join(path.resolve(AGENTS_DIR), 'notis', 'users'),\n ];\n if (path.basename(scopedUsersRoot) === 'users') {\n roots.push(scopedUsersRoot);\n }\n return roots;\n}\n\nasync function isManagedSymlink(linkPath: string, managedRoots: string[]): Promise<boolean> {\n try {\n const stats = await fs.lstat(linkPath);\n if (!stats.isSymbolicLink()) {\n return false;\n }\n\n const target = await fs.readlink(linkPath);\n const resolvedTarget = path.resolve(path.dirname(linkPath), target);\n return managedRoots.some((root) => (\n resolvedTarget === root || resolvedTarget.startsWith(`${root}${path.sep}`)\n ));\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') return false;\n throw error;\n }\n}\n\nasync function ensureCorrectSymlink(linkPath: string, targetPath: string): Promise<'linked' | 'skipped' | 'blocked'> {\n try {\n const stats = await fs.lstat(linkPath);\n if (stats.isSymbolicLink()) {\n const currentTarget = await fs.readlink(linkPath);\n const resolvedTarget = path.resolve(path.dirname(linkPath), currentTarget);\n if (resolvedTarget === targetPath) {\n return 'skipped';\n }\n await fs.unlink(linkPath);\n } else {\n return 'blocked';\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') throw error;\n }\n\n const relativePath = path.relative(path.dirname(linkPath), targetPath);\n await fs.symlink(relativePath, linkPath);\n return 'linked';\n}\n\nfunction defaultAgentSkillDirs(skillsDir: string): AgentSkillDirs {\n return {\n notis: skillsDir,\n ...EXTERNAL_AGENT_SKILL_DIRS,\n };\n}\n\nasync function removeUndesiredManagedSymlinks(\n agentDir: string,\n desiredSkills: Set<string>,\n managedRoots: string[],\n failures: SkillSyncFailure[],\n agent: string,\n): Promise<number> {\n let removed = 0;\n try {\n await fs.mkdir(agentDir, { recursive: true });\n const existingEntries = await fs.readdir(agentDir, { withFileTypes: true });\n for (const entry of existingEntries) {\n const entryPath = path.join(agentDir, entry.name);\n try {\n if (!desiredSkills.has(entry.name) && await isManagedSymlink(entryPath, managedRoots)) {\n await fs.unlink(entryPath);\n removed += 1;\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') {\n failures.push({ name: entry.name, error: `${agentFailureLabel(agent)}: could not remove skill link (${(error as Error).message})` });\n }\n }\n }\n } catch (error) {\n failures.push({ name: agentFolderFailureName(agent), error: `Could not read agent skills directory (${(error as Error).message})` });\n }\n return removed;\n}\n\nexport async function removeAllSymlinksForSkill(\n skillName: string,\n skillsDir: string = LEGACY_AGENTS_SKILLS_DIR,\n options: RemoveSkillSymlinkOptions = {},\n): Promise<number> {\n let removed = 0;\n const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };\n const legacyGlobalSkillsDir = options.legacyGlobalSkillsDir || LEGACY_AGENTS_SKILLS_DIR;\n const managedRoots = managedSkillRoots(skillsDir, legacyGlobalSkillsDir);\n const agentDirs = new Set([...Object.values(agentSkillDirs), legacyGlobalSkillsDir]);\n const expectedTarget = path.resolve(skillsDir, safeName(skillName, skillsDir));\n for (const agentDir of agentDirs) {\n const linkPath = path.join(agentDir, safeName(skillName, agentDir));\n const owned = options.ownership === 'exact-skill-dir'\n ? await isManagedSymlink(linkPath, [expectedTarget])\n : await isManagedSymlink(linkPath, managedRoots);\n if (owned) {\n await fs.unlink(linkPath);\n removed += 1;\n }\n }\n return removed;\n}\n\n/**\n * Detect per-agent skill symlinks that we created on a prior sync but that the user has since\n * deleted locally (e.g. `rm ~/.claude/skills/<skill>`). The caller turns each result into a\n * per-agent deactivation so the deletion sticks instead of being recreated on the next pass.\n *\n * A pair is flagged only when the skill is cloud-active for that agent, the previous sync state\n * recorded it active for that agent (so we know we created the link), and the link is now truly\n * absent (lstat ENOENT). Stale/wrong-target links and real files blocking the path are NOT\n * deletions \u2014 those are repaired/skipped by syncSymlinks as before. Only external agents are\n * considered; `notis` has no symlink (its dir is the source skills folder).\n */\nexport async function detectDeletedAgentSymlinks(\n cloudSkills: CloudSkill[],\n previousState: NotisSyncState,\n skillsDir: string = LEGACY_AGENTS_SKILLS_DIR,\n options: SymlinkSyncOptions = {},\n): Promise<DeletedAgentSymlink[]> {\n const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };\n const deletions: DeletedAgentSymlink[] = [];\n\n for (const agent of EXTERNAL_AGENTS) {\n const agentDir = agentSkillDirs[agent];\n if (path.resolve(agentDir) === path.resolve(skillsDir)) {\n continue;\n }\n\n // A missing (or non-directory) agent skills directory is NOT a per-skill deletion: removing\n // ~/.claude/skills wholesale would otherwise make every entry's link path ENOENT and\n // deactivate every skill for that agent. syncSymlinks recreates the directory and its links,\n // so only absent entries INSIDE an existing agent directory count as explicit user deletions.\n try {\n const dirStat = await fs.stat(agentDir);\n if (!dirStat.isDirectory()) {\n continue;\n }\n } catch {\n continue;\n }\n\n for (const skill of cloudSkills) {\n if (skill.status !== 'active') {\n continue;\n }\n if (!skill.id || skill.id.startsWith('local-')) {\n continue;\n }\n const previous = previousState.skills[skill.name];\n if (!previous || previous.cloudId !== skill.id\n || previous.verifiedAgentLinks?.[agent] !== true\n || !skill.updated_at || previous.cloudUpdatedAt !== skill.updated_at) {\n continue;\n }\n\n const cloudTargets = normalizeAgentTargets(skill.agent_targets);\n const previousTargets = normalizeAgentTargets(previous.agentTargets);\n if (!cloudTargets[agent] || !previousTargets[agent]) {\n continue;\n }\n\n let safeSkillName: string;\n try {\n safeSkillName = safeName(skill.name, skillsDir);\n } catch {\n continue;\n }\n\n const linkPath = path.join(agentDir, safeName(safeSkillName, agentDir));\n try {\n await fs.lstat(linkPath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {\n deletions.push({ skillId: skill.id, skillName: skill.name, agent });\n }\n }\n }\n }\n\n return deletions;\n}\n\nexport async function syncSymlinks(\n skills: CloudSkill[],\n skillsDir: string = LEGACY_AGENTS_SKILLS_DIR,\n options: SymlinkSyncOptions = {},\n): Promise<SymlinkSyncResult> {\n await fs.mkdir(skillsDir, { recursive: true });\n\n const result: Required<SymlinkSyncResult> = {\n linked: 0,\n removed: 0,\n skipped: 0,\n verifiedAgentLinks: {},\n failures: [],\n };\n const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };\n const legacyGlobalSkillsDir = options.legacyGlobalSkillsDir || LEGACY_AGENTS_SKILLS_DIR;\n const managedRoots = managedSkillRoots(skillsDir, legacyGlobalSkillsDir);\n\n const desiredByAgent = {\n notis: new Set<string>(),\n claude_code: new Set<string>(),\n cursor: new Set<string>(),\n codex: new Set<string>(),\n };\n\n for (const skill of skills) {\n if (skill.status !== 'active') {\n continue;\n }\n\n const safeSkillName = safeName(skill.name, skillsDir);\n const targets = normalizeAgentTargets(skill.agent_targets);\n if (targets.notis) {\n desiredByAgent.notis.add(safeSkillName);\n }\n if (targets.claude_code) {\n desiredByAgent.claude_code.add(safeSkillName);\n }\n if (targets.cursor) {\n desiredByAgent.cursor.add(safeSkillName);\n }\n if (targets.codex) {\n desiredByAgent.codex.add(safeSkillName);\n }\n }\n\n for (const [agent, agentDir] of Object.entries(agentSkillDirs) as Array<[keyof AgentSkillDirs, string]>) {\n if (path.resolve(agentDir) === path.resolve(skillsDir)) {\n result.skipped += desiredByAgent[agent].size;\n continue;\n }\n\n try {\n await fs.mkdir(agentDir, { recursive: true });\n } catch (error) {\n result.failures.push({ name: agentFolderFailureName(agent), error: `Could not create agent skills directory (${(error as Error).message})` });\n continue;\n }\n\n const desiredSkills = desiredByAgent[agent];\n if (options.removeUndesired !== false) {\n result.removed += await removeUndesiredManagedSymlinks(agentDir, desiredSkills, managedRoots, result.failures, agent);\n }\n\n for (const skillName of desiredSkills) {\n const targetPath = path.join(skillsDir, skillName);\n const linkPath = path.join(agentDir, safeName(skillName, agentDir));\n try {\n if (!(await fs.stat(path.join(targetPath, \"SKILL.md\"))).isFile()) throw new Error(\"Missing SKILL.md\");\n } catch {\n result.skipped += 1;\n result.failures.push({ name: skillName, error: `${agentFailureLabel(agent)}: SKILL.md is missing or unreadable` });\n continue;\n }\n let syncOutcome;\n try {\n syncOutcome = await ensureCorrectSymlink(linkPath, targetPath);\n } catch (error) {\n result.skipped += 1;\n result.failures.push({ name: skillName, error: `${agentFailureLabel(agent)}: could not create skill link (${(error as Error).message})` });\n continue;\n }\n if (syncOutcome === 'linked') {\n result.linked += 1;\n } else if (syncOutcome === 'blocked') {\n console.warn(\n `[skill-sync] Could not link \"${skillName}\" for ${agent}: non-symlink entry blocks ${linkPath}`,\n );\n result.skipped += 1;\n result.failures.push({ name: skillName, error: `${agentFailureLabel(agent)}: an existing file or folder blocks the skill link` });\n } else {\n result.skipped += 1;\n }\n if (syncOutcome !== \"blocked\") {\n result.verifiedAgentLinks[skillName] = { ...result.verifiedAgentLinks[skillName], [agent]: true };\n }\n }\n }\n\n if (\n options.removeUndesired !== false\n && !Object.values(agentSkillDirs).some(\n (agentDir) => path.resolve(agentDir) === path.resolve(legacyGlobalSkillsDir),\n )\n ) {\n result.removed += await removeUndesiredManagedSymlinks(\n legacyGlobalSkillsDir,\n new Set(),\n managedRoots,\n result.failures,\n 'legacy',\n );\n }\n\n return result;\n}\n", "import type { LocalSkill, NotisSyncState } from \"./types\";\n\nexport function getPushCandidates(\n localSkills: LocalSkill[],\n syncState: NotisSyncState,\n cloudCuratedSkillNames: ReadonlySet<string> = new Set(),\n cloudSkillNames?: ReadonlySet<string>,\n): LocalSkill[] {\n return localSkills.filter((skill) => {\n if (cloudCuratedSkillNames.has(skill.name)) {\n return false;\n }\n const previous = syncState.skills[skill.name];\n if (!previous) {\n return true;\n }\n if (cloudSkillNames && !cloudSkillNames.has(skill.name)) {\n return false;\n }\n return previous.folderHash !== skill.folderHash;\n });\n}\n", "import type { CloudSkill } from './types';\n\ninterface WriteCloudSkillDependencies {\n downloadSkillBundle: (bundleUrl: string) => Promise<Buffer>;\n writeCloudSkillToDisk: (skill: CloudSkill, bundleBytes?: Buffer) => Promise<boolean>;\n onWarning?: (message: string, error: unknown) => void;\n}\n\nexport async function writeCloudSkillWithBundleFallback(\n skill: CloudSkill,\n dependencies: WriteCloudSkillDependencies,\n): Promise<boolean> {\n if (skill.skill_source_url) {\n try {\n const bundleBytes = await dependencies.downloadSkillBundle(skill.skill_source_url);\n const wroteBundleToDisk = await dependencies.writeCloudSkillToDisk(skill, bundleBytes);\n if (wroteBundleToDisk) {\n return true;\n }\n\n dependencies.onWarning?.(\n `Bundle sync for \"${skill.name}\" produced no local changes, falling back to SKILL.md payload.`,\n new Error('Bundle write returned false'),\n );\n } catch (error) {\n dependencies.onWarning?.(\n `Failed to apply bundle sync for \"${skill.name}\", falling back to SKILL.md payload.`,\n error,\n );\n }\n }\n\n if (skill.bundle_hydration_failed) {\n dependencies.onWarning?.(\n `Skipping markdown fallback for synced skill \"${skill.name}\" because its stored bundle could not be hydrated by the server.`,\n new Error('Bundle hydration failed'),\n );\n return false;\n }\n\n try {\n return await dependencies.writeCloudSkillToDisk(skill);\n } catch (error) {\n dependencies.onWarning?.(\n `Failed to write synced skill \"${skill.name}\" to disk.`,\n error,\n );\n return false;\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,cAAAA,mBAAkB;;;AC+FpB,IAAM,wBAAsC;AAAA,EACjD,OAAO;AAAA,EACP,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,OAAO;AACT;AAEO,SAAS,sBAAsB,SAAsD;AAC1F,SAAO;AAAA,IACL,OAAO,QAAQ,SAAS,SAAS,sBAAsB,KAAK;AAAA,IAC5D,aAAa,QAAQ,SAAS,eAAe,sBAAsB,WAAW;AAAA,IAC9E,QAAQ,QAAQ,SAAS,UAAU,sBAAsB,MAAM;AAAA,IAC/D,OAAO,QAAQ,SAAS,SAAS,sBAAsB,KAAK;AAAA,EAC9D;AACF;;;AC7GA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AAEzB,SAAS,YAAY,UAAU;AAC/B,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAI1B,IAAM,WAAW,GAAG,QAAQ;AAC5B,IAAM,gBAAgB,UAAU,QAAQ;AAEjC,IAAM,aAAa,KAAK,KAAK,UAAU,SAAS;AAChD,IAAM,2BAA2B,KAAK,KAAK,YAAY,QAAQ;AAC/D,IAAM,wBAAwB,KAAK,KAAK,UAAU,UAAU,QAAQ;AACpE,IAAM,+BAA+B,KAAK;AAAA,EAC/C;AAAA,EACA;AACF;AACO,IAAM,oBAAoB;AAEjC,IAAM,kBAAkB,KAAK,KAAK,YAAY,kBAAkB;AAEhE,IAAM,qBAAqC;AAAA,EACzC,SAAS;AAAA,EACT,cAAc;AAAA,EACd,QAAQ,CAAC;AACX;AACA,IAAM,gCAAgC,oBAAI,IAAI;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAcD,IAAM,qBAAqC;AAAA,EACzC,WAAW;AAAA,EACX,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,uBAAuB;AAAA,EACvB,2BAA2B;AAAA,EAC3B,WAAW;AAAA,EACX,eAAe;AAAA,EACf,eAAe;AAAA,EACf,oBAAoB,KAAK,KAAK,uBAAuB,4BAA4B;AACnF;AAEA,SAAS,+BAA+B,mBAAmC;AACzE,MAAI,sBAAsB,KAAK,QAAQ,UAAU,GAAG;AAClD,WAAO;AAAA,EACT;AACA,SAAO,KAAK,KAAK,mBAAmB,UAAU,QAAQ;AACxD;AAEA,SAAS,oBAAoB,SAAiB,eAAgC;AAC5E,SAAO,cAAc,WAAW,GAAG,OAAO,GAAG,KAAK,GAAG,EAAE;AACzD;AAEA,SAAS,oBAAoB,OAAuB;AAClD,QAAM,MAAM,MAAM,KAAK;AACvB,QAAM,YAAY,IACf,QAAQ,oBAAoB,GAAG,EAC/B,QAAQ,YAAY,EAAE;AACzB,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,sBAAsB;AAAA,EACxC;AACA,MAAI,cAAc,KAAK;AACrB,WAAO;AAAA,EACT;AACA,SAAO,GAAG,SAAS,IAAI,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC;AACpF;AAEO,SAAS,yBACd,YACA,YAAoB,YACJ;AAChB,QAAM,aAAa,oBAAoB,UAAU;AACjD,QAAM,oBAAoB,KAAK,QAAQ,SAAS;AAChD,QAAM,WAAW,+BAA+B,iBAAiB;AACjE,QAAM,WAAW,KAAK,KAAK,UAAU,SAAS,UAAU;AACxD,QAAM,iBAAiB,KAAK,KAAK,mBAAmB,SAAS,SAAS,UAAU;AAEhF,SAAO;AAAA,IACL,WAAW;AAAA,IACX;AAAA,IACA,iBAAiB,KAAK,KAAK,mBAAmB,QAAQ;AAAA,IACtD,uBAAuB,KAAK,KAAK,gBAAgB,QAAQ;AAAA,IACzD,2BAA2B,KAAK,KAAK,gBAAgB,kBAAkB;AAAA,IACvE,WAAW,KAAK,KAAK,UAAU,QAAQ;AAAA,IACvC,eAAe,KAAK,KAAK,UAAU,kBAAkB;AAAA,IACrD,eAAe,KAAK,KAAK,mBAAmB,kBAAkB;AAAA,IAC9D,oBAAoB,KAAK,KAAK,UAAU,6BAA6B;AAAA,EACvE;AACF;AAEA,SAAS,yBAAyB,cAA8B;AAC9D,QAAM,aAAa,aAAa,QAAQ,OAAO,GAAG,EAAE,QAAQ,cAAc,EAAE;AAC5E,MACE,CAAC,cACD,WAAW,WAAW,KAAK,KAC3B,IAAI,UAAU,IAAI,SAAS,MAAM,GACjC;AACA,UAAM,IAAI,MAAM,6BAA6B,YAAY,EAAE;AAAA,EAC7D;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAuB;AAClD,SAAO,MAAM,QAAQ,gBAAgB,EAAE,EAAE,KAAK;AAChD;AAEA,SAAS,iBAAiB,SAA0C;AAClE,QAAM,QAAQ,QAAQ,MAAM,qCAAqC;AACjE,MAAI,CAAC,OAAO;AACV,WAAO,EAAE,aAAa,GAAG;AAAA,EAC3B;AAEA,MAAI,cAAc;AAClB,aAAW,QAAQ,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG;AACvC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,GAAG;AACvC;AAAA,IACF;AACA,UAAM,mBAAmB,QAAQ,MAAM,2BAA2B;AAClE,QAAI,kBAAkB;AACpB,oBAAc,oBAAoB,iBAAiB,CAAC,CAAC;AACrD;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,YAAY;AACvB;AAEA,eAAe,aAAgB,UAAqC;AAClE,MAAI;AACF,UAAM,MAAM,MAAM,GAAG,SAAS,UAAU,MAAM;AAC9C,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,mBAAmB,SAAoC;AACpE,QAAM,UAAU,MAAM,GAAG,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC;AACjE,QAAM,SAAS,MAAM,QAAQ;AAAA,IAC3B,QAAQ,IAAI,OAAO,UAAU;AAC3B,UAAI,MAAM,SAAS,aAAa;AAC9B,eAAO,CAAC;AAAA,MACV;AAEA,YAAM,WAAW,KAAK,KAAK,SAAS,MAAM,IAAI;AAC9C,UAAI,MAAM,YAAY,GAAG;AACvB,eAAO,mBAAmB,QAAQ;AAAA,MACpC;AAEA,UAAI,MAAM,OAAO,GAAG;AAClB,eAAO,CAAC,QAAQ;AAAA,MAClB;AAEA,aAAO,CAAC;AAAA,IACV,CAAC;AAAA,EACH;AAEA,SAAO,OAAO,KAAK,EAAE,KAAK;AAC5B;AAEA,eAAe,kBAAkB,SAAkC;AACjE,QAAM,OAAO,WAAW,QAAQ;AAChC,QAAM,YAAY,MAAM,mBAAmB,OAAO;AAElD,aAAW,YAAY,WAAW;AAChC,UAAM,eAAe,KAAK,SAAS,SAAS,QAAQ;AACpD,SAAK,OAAO,YAAY;AACxB,SAAK,OAAO,IAAI;AAChB,SAAK,OAAO,MAAM,GAAG,SAAS,QAAQ,CAAC;AACvC,SAAK,OAAO,IAAI;AAAA,EAClB;AAEA,SAAO,KAAK,OAAO,KAAK;AAC1B;AAEA,SAAS,2BACP,UACA,cACQ;AACR,QAAM,iBAAiB,yBAAyB,YAAY;AAC5D,QAAM,mBAAmB,KAAK,QAAQ,QAAQ;AAC9C,QAAM,gBAAgB,KAAK,QAAQ,kBAAkB,cAAc;AACnE,MACE,kBAAkB,oBAClB,CAAC,oBAAoB,kBAAkB,aAAa,GACpD;AACA,UAAM,IAAI;AAAA,MACR,wDAAwD,YAAY;AAAA,IACtE;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,mBAAmB,UAAoC;AACpE,MAAI;AACF,UAAM,GAAG,SAAS,KAAK,KAAK,UAAU,UAAU,GAAG,MAAM;AACzD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,cACb,WACA,gBACe;AACf,QAAM,GAAG,MAAM,KAAK,QAAQ,cAAc,GAAG,EAAE,WAAW,KAAK,CAAC;AAChE,MAAI;AACF,UAAM,GAAG,OAAO,WAAW,cAAc;AAAA,EAC3C,SAAS,OAAO;AACd,QAAK,MAAgC,SAAS,SAAS;AACrD,YAAM;AAAA,IACR;AACA,UAAM,GAAG,GAAG,WAAW,gBAAgB;AAAA,MACrC,WAAW;AAAA,MACX,cAAc;AAAA,MACd,OAAO;AAAA,IACT,CAAC;AACD,UAAM,GAAG,GAAG,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACzD;AACF;AAEA,eAAe,uBACb,WACA,UACe;AACf,QAAM,GAAG,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,QAAM,GAAG,QAAQ,KAAK,SAAS,KAAK,QAAQ,QAAQ,GAAG,SAAS,GAAG,UAAU,KAAK;AACpF;AAEA,SAAS,cAAc,OAAuB,WAA2B;AACvE,SAAO,KAAK,KAAK,MAAM,UAAU,wBAAwB,SAAS;AACpE;AAEA,SAAS,mBAAmB,OAAe,WAA2B;AACpE,SAAO,KAAK;AAAA,IACV,MAAM,QAAQ,qBAAqB,GAAG;AAAA,IACtC,SAAS,SAAS;AAAA,EACpB;AACF;AAEA,SAAS,6BAA6B,WAA4B;AAChE,SACE,UAAU,WAAW,GAAG,KACxB,8BAA8B,SAAS,KACvC,8BAA8B,IAAI,UAAU,YAAY,CAAC;AAE7D;AAEA,SAAS,8BAA8B,WAA4B;AACjE,SAAO,wBAAwB,KAAK,SAAS;AAC/C;AAgCA,SAAS,4BACP,OACuB;AACvB,QAAM,UAAiC,CAAC;AACxC,MAAI,KAAK,QAAQ,MAAM,qBAAqB,MAAM,KAAK,QAAQ,MAAM,SAAS,GAAG;AAC/E,YAAQ,KAAK;AAAA,MACX,OAAO;AAAA,MACP,MAAM,MAAM;AAAA,MACZ,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAEA,UAAQ;AAAA,IACN,EAAE,OAAO,UAAU,MAAM,MAAM,iBAAiB,UAAU,EAAE;AAAA,IAC5D;AAAA,MACE,OAAO;AAAA,MACP,MAAM,KAAK,KAAK,UAAU,UAAU,QAAQ;AAAA,MAC5C,UAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM,KAAK,KAAK,UAAU,WAAW,QAAQ;AAAA,MAC7C,UAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM,KAAK,KAAK,UAAU,WAAW,QAAQ;AAAA,MAC7C,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,+BACP,cACA,OACS;AACT,QAAM,eAAe;AAAA,IACnB,KAAK,QAAQ,MAAM,SAAS;AAAA,IAC5B,KAAK,QAAQ,MAAM,eAAe;AAAA,IAClC,KAAK,QAAQ,MAAM,qBAAqB;AAAA,IACxC,KAAK,KAAK,KAAK,QAAQ,MAAM,QAAQ,GAAG,MAAM;AAAA,IAC9C,KAAK,KAAK,KAAK,QAAQ,MAAM,QAAQ,GAAG,OAAO;AAAA,IAC/C,KAAK,KAAK,KAAK,QAAQ,MAAM,SAAS,GAAG,SAAS,OAAO;AAAA,EAC3D;AACA,SAAO,aAAa;AAAA,IAClB,CAAC,SACC,iBAAiB,QAAQ,aAAa,WAAW,GAAG,IAAI,GAAG,KAAK,GAAG,EAAE;AAAA,EACzE;AACF;AAEA,eAAe,4BACb,OACA,SACmC;AACnC,QAAM,cAAc,QAAQ,cACxB,QAAQ,YAAY,IAAI,CAAC,QAAQ,WAAW;AAAA,IAC1C,GAAG;AAAA,IACH,UAAU,QAAQ;AAAA,EACpB,EAAE,IACF,4BAA4B,KAAK;AACrC,QAAM,aAAuC,CAAC;AAE9C,MAAI;AACF,UAAM,gBAAgB,MAAM,GAAG,QAAQ,MAAM,WAAW;AAAA,MACtD,eAAe;AAAA,IACjB,CAAC;AACD,eAAW,SAAS,eAAe;AACjC,UACG,CAAC,MAAM,YAAY,KAAK,CAAC,MAAM,eAAe,KAC/C,MAAM,KAAK,WAAW,GAAG,KACzB,8BAA8B,MAAM,IAAI,GACxC;AACA;AAAA,MACF;AACA,YAAM,WAAW,KAAK,KAAK,MAAM,WAAW,MAAM,IAAI;AACtD,UAAI,eAAe,KAAK,QAAQ,QAAQ;AACxC,UAAI,MAAM,eAAe,GAAG;AAC1B,YAAI;AACF,yBAAe,KAAK,QAAQ,KAAK,QAAQ,QAAQ,GAAG,MAAM,GAAG,SAAS,QAAQ,CAAC;AAAA,QACjF,QAAQ;AACN;AAAA,QACF;AAAA,MACF;AACA,UAAI,MAAM,mBAAmB,QAAQ,GAAG;AACtC,mBAAW,KAAK;AAAA,UACd,MAAM,SAAS,MAAM,MAAM,MAAM,SAAS;AAAA,UAC1C,MAAM,MAAM;AAAA,UACZ,OAAO;AAAA,UACP,MAAM;AAAA,UACN;AAAA,UACA,UAAU;AAAA,UACV,UAAU;AAAA,UACV,WAAW,MAAM,eAAe;AAAA,QAClC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,aAAW,UAAU,aAAa;AAChC,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,GAAG,QAAQ,OAAO,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,IACjE,QAAQ;AACN;AAAA,IACF;AAEA,eAAW,SAAS,SAAS;AAC3B,UACE,6BAA6B,MAAM,IAAI,KACtC,CAAC,MAAM,YAAY,KAAK,CAAC,MAAM,eAAe,GAC/C;AACA;AAAA,MACF;AAEA,YAAM,gBAAgB,KAAK,KAAK,OAAO,MAAM,MAAM,IAAI;AACvD,UAAI;AACJ,UAAI;AACF,uBAAe,MAAM,eAAe,IAChC,KAAK;AAAA,UACH,KAAK,QAAQ,aAAa;AAAA,UAC1B,MAAM,GAAG,SAAS,aAAa;AAAA,QACjC,IACA,KAAK,QAAQ,aAAa;AAAA,MAChC,QAAQ;AACN;AAAA,MACF;AAEA,UACE,MAAM,eAAe,KACrB,+BAA+B,cAAc,KAAK,GAClD;AACA;AAAA,MACF;AAEA,UAAI,CAAE,MAAM,mBAAmB,YAAY,GAAI;AAC7C;AAAA,MACF;AAEA,iBAAW,KAAK;AAAA,QACd,MAAM,SAAS,MAAM,MAAM,MAAM,SAAS;AAAA,QAC1C,MAAM,OAAO;AAAA,QACb,OAAO,OAAO;AAAA,QACd,MAAM;AAAA,QACN;AAAA,QACA,UAAU,OAAO;AAAA,QACjB,UAAU;AAAA,QACV,WAAW,MAAM,eAAe;AAAA,MAClC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,WAAW;AAAA,IAChB,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,EAClE;AACF;AAEA,eAAsB,0BACpB,OACA,UAA4C,CAAC,GACH;AAC1C,QAAM,yBAAyB,KAAK;AAEpC,QAAM,sBAAsB,QAAQ,uBAAuB,oBAAI,IAAY;AAC3E,QAAM,YACJ,QAAQ,cAAa,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,SAAS,GAAG;AACpE,QAAM,aAAa,cAAc,OAAO,SAAS;AACjD,QAAM,aAAa,MAAM,4BAA4B,OAAO,OAAO;AACnE,QAAM,SAAS,oBAAI,IAAsC;AAEzD,aAAW,aAAa,YAAY;AAClC,UAAM,WAAW,OAAO,IAAI,UAAU,IAAI,KAAK,CAAC;AAChD,aAAS,KAAK,SAAS;AACvB,WAAO,IAAI,UAAU,MAAM,QAAQ;AAAA,EACrC;AAEA,MAAI,WAAW;AACf,MAAI,WAAW;AACf,MAAI,UAAU;AACd,QAAM,WAWF;AAAA,IACF,SAAS;AAAA,IACT,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,QAAQ,CAAC;AAAA,EACX;AAEA,aAAW,CAAC,WAAW,eAAe,KAAK,QAAQ;AACjD,UAAM,2BAA2B,oBAAoB,IAAI,SAAS;AAClE,UAAM,YAAY,2BACd,gBAAgB,KAAK,CAAC,cAAc,UAAU,QAAQ,KAAK,OAC3D,gBAAgB,CAAC;AACrB,UAAM,kBAAkB,SAAS,WAAW,MAAM,SAAS;AAC3D,UAAM,iBAAiB,KAAK,KAAK,MAAM,WAAW,eAAe;AACjE,UAAM,iBAA2B,CAAC;AAElC,QAAI,CAAC,WAAW;AACd,iBAAW,aAAa,iBAAiB;AACvC,YAAI,UAAU,WAAW;AACvB,qBAAW;AACX,yBAAe,KAAK,UAAU,IAAI;AAClC;AAAA,QACF;AACA,cAAM,YAAY,KAAK;AAAA,UACrB;AAAA,UACA,mBAAmB,UAAU,OAAO,SAAS;AAAA,QAC/C;AACA,YAAI;AACF,gBAAM,cAAc,UAAU,MAAM,SAAS;AAC7C,sBAAY;AACZ,yBAAe,KAAK,UAAU,IAAI;AAAA,QACpC,QAAQ;AACN,qBAAW;AAAA,QACb;AAAA,MACF;AACA,eAAS,OAAO,SAAS,IAAI;AAAA,QAC3B,eAAe;AAAA,QACf;AAAA,QACA,0BAA0B;AAAA,MAC5B;AACA;AAAA,IACF;AAEA,QAAI,CAAC,UAAU,YAAY,CAAE,MAAM,WAAW,cAAc,GAAI;AAC9D,UAAI;AACF,YAAI,UAAU,WAAW;AACvB,gBAAM,uBAAuB,UAAU,cAAc,cAAc;AAAA,QACrE,OAAO;AACL,gBAAM,cAAc,UAAU,MAAM,cAAc;AAAA,QACpD;AACA,oBAAY;AAAA,MACd,QAAQ;AACN,mBAAW;AAAA,MACb;AAAA,IACF;AAEA,eAAW,aAAa,iBAAiB;AACvC,UAAI,cAAc,aAAa,UAAU,UAAU;AACjD;AAAA,MACF;AACA,UAAI,UAAU,WAAW;AACvB,uBAAe,KAAK,UAAU,IAAI;AAClC;AAAA,MACF;AACA,YAAM,YAAY,KAAK;AAAA,QACrB;AAAA,QACA,mBAAmB,UAAU,OAAO,SAAS;AAAA,MAC/C;AACA,UAAI;AACF,cAAM,cAAc,UAAU,MAAM,SAAS;AAC7C,oBAAY;AACZ,uBAAe,KAAK,UAAU,IAAI;AAAA,MACpC,QAAQ;AACN,mBAAW;AAAA,MACb;AAAA,IACF;AAEA,aAAS,OAAO,SAAS,IAAI;AAAA,MAC3B,eAAgB,MAAM,WAAW,cAAc,IAC3C,iBACA,UAAU;AAAA,MACd;AAAA,MACA,GAAI,2BAA2B,EAAE,0BAA0B,KAAK,IAAI,CAAC;AAAA,IACvE;AAAA,EACF;AAEA,QAAM,GAAG,MAAM,KAAK,QAAQ,MAAM,kBAAkB,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1E,QAAM,GAAG;AAAA,IACP,MAAM;AAAA,IACN,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA;AAAA,IACpC;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,MAAM;AAAA,EACtB;AACF;AAEA,eAAe,cACb,QAAwB,oBACS;AAKjC,QAAM,WAAW,MAAM,aAAwB,MAAM,aAAa;AAClE,QAAM,aAAqC,CAAC;AAE5C,aAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,UAAU,UAAU,CAAC,CAAC,GAAG;AACvE,QAAI,OAAO,OAAO,cAAc,YAAY,MAAM,UAAU,KAAK,GAAG;AAClE,iBAAW,SAAS,IAAI,MAAM,UAAU,KAAK;AAAA,IAC/C;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,yBACpB,QAAwB,oBACT;AACf,QAAM,GAAG,MAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AACrD;AAgDA,eAAsB,gBACpB,QAAwB,oBACD;AACvB,QAAM,yBAAyB,KAAK;AAEpC,QAAM,aAAa,MAAM,cAAc,KAAK;AAC5C,QAAM,UAAU,MAAM,GAAG,QAAQ,MAAM,WAAW,EAAE,eAAe,KAAK,CAAC;AACzE,QAAM,SAAmC,MAAM,QAAQ;AAAA,IACrD,QAAQ,IAAI,OAAO,UAAU;AAC3B,UACG,CAAC,MAAM,YAAY,KAAK,CAAC,MAAM,eAAe,KAC/C,8BAA8B,MAAM,IAAI,GACxC;AACA,eAAO;AAAA,MACT;AAEA,YAAM,WAAW,KAAK,KAAK,MAAM,WAAW,MAAM,IAAI;AACtD,YAAM,cAAc,KAAK,KAAK,UAAU,UAAU;AAElD,UAAI;AACF,cAAM,UAAU,MAAM,GAAG,SAAS,aAAa,MAAM;AACrD,cAAM,EAAE,YAAY,IAAI,iBAAiB,OAAO;AAChD,cAAM,aAAa,MAAM,kBAAkB,QAAQ;AACnD,cAAM,QAAoB;AAAA,UACxB,MAAM,MAAM;AAAA,UACZ;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe;AAAA,QACjB;AACA,YAAI,WAAW,MAAM,IAAI,GAAG;AAC1B,gBAAM,YAAY,WAAW,MAAM,IAAI;AAAA,QACzC;AACA,eAAO;AAAA,MACT,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,OACJ,OAAO,CAAC,UAA+B,UAAU,IAAI,EACrD,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAChD;AAEA,SAAS,mBACP,OACuB;AACvB,MAAI,CAAC,SAAS,MAAM,YAAY,KAAK,OAAO,MAAM,WAAW,UAAU;AACrE,WAAO;AAAA,EACT;AACA,QAAM,8BAA8B,CAClC,aACsD;AAAA,IACtD,OAAO,QAAQ,SAAS,SAAS,IAAI;AAAA,IACrC,aAAa,QAAQ,SAAS,eAAe,IAAI;AAAA,IACjD,QAAQ,QAAQ,SAAS,UAAU,IAAI;AAAA,IACvC,OAAO,QAAQ,SAAS,SAAS,IAAI;AAAA,EACvC;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,cACE,OAAO,MAAM,iBAAiB,WAAW,MAAM,eAAe;AAAA,IAChE,QAAQ,OAAO;AAAA,MACb,OAAO,QAAQ,MAAM,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,WAAW,UAAU,MAAM;AAAA,QAClE;AAAA,QACA;AAAA,UACE,GAAG;AAAA,UACH,cAAc,4BAA4B,WAAW,YAAY;AAAA,QACnE;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,eAAsB,cACpB,QAAwB,oBACC;AACzB,QAAM,cAAc;AAAA,IAClB,MAAM,aAA6B,MAAM,aAAa;AAAA,EACxD;AACA,MAAI,aAAa;AACf,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,eAAsB,oBACpB,QAAwB,oBACQ;AAChC,QAAM,mBAAmB;AAAA,IACvB,MAAM;AAAA,IACN,KAAK,QAAQ,MAAM,aAAa,MAAM,KAAK,QAAQ,4BAA4B,IAC3E,MAAM,gBACN,KAAK,KAAK,MAAM,WAAW,kBAAkB;AAAA,EACnD;AAEA,aAAW,mBAAmB,kBAAkB;AAC9C,UAAM,QAAQ;AAAA,MACZ,MAAM,aAA6B,eAAe;AAAA,IACpD;AACA,QAAI,OAAO;AACT,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,eACpB,OACA,QAAwB,oBACT;AACf,QAAM,GAAG,MAAM,KAAK,QAAQ,MAAM,aAAa,GAAG,EAAE,WAAW,KAAK,CAAC;AACrE,QAAM,GAAG;AAAA,IACP,MAAM;AAAA,IACN,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA;AAAA,IACjC;AAAA,EACF;AACF;AAEO,SAAS,SACd,MACA,UAAkB,mBACV;AACR,QAAM,gBAAgB,KACnB,KAAK,EACL,QAAQ,WAAW,EAAE,EACrB,QAAQ,SAAS,EAAE;AACtB,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AAEA,QAAM,kBAAkB,KAAK,QAAQ,OAAO;AAC5C,QAAM,eAAe,KAAK,QAAQ,iBAAiB,aAAa;AAChE,MAAI,CAAC,oBAAoB,iBAAiB,YAAY,GAAG;AACvD,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AAEA,SAAO;AACT;AAEA,eAAsB,iBACpB,WACA,QAAwB,oBACN;AAClB,QAAM,WAAW,KAAK;AAAA,IACpB,MAAM;AAAA,IACN,SAAS,WAAW,MAAM,SAAS;AAAA,EACrC;AACA,MAAI;AACF,UAAM,GAAG,GAAG,UAAU,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACtD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,uBAAuB,eAAwC;AAC5E,QAAM,UAAU,KAAK;AAAA,IACnB,GAAG,OAAO;AAAA,IACV,eAAe,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAAA,EAClE;AACA,QAAM,YAAY,KAAK,QAAQ,aAAa;AAC5C,QAAM,gBAAgB,KAAK,SAAS,aAAa;AAEjD,MAAI,QAAQ,aAAa,SAAS;AAChC,UAAM,cAAc,kBAAkB;AAAA,MACpC;AAAA,MACA;AAAA,MACA,2BAA2B,cAAc,QAAQ,MAAM,IAAI,CAAC,uBAAuB,QAAQ,QAAQ,MAAM,IAAI,CAAC;AAAA,IAChH,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,OAAO,CAAC,QAAQ,SAAS,aAAa,GAAG;AAAA,IAC3D,KAAK;AAAA,EACP,CAAC;AACD,SAAO;AACT;AAEA,eAAe,sBACb,SACA,gBACe;AACf,QAAM,cAAc,MAAM,GAAG;AAAA,IAC3B,KAAK,KAAK,GAAG,OAAO,GAAG,sBAAsB;AAAA,EAC/C;AAEA,MAAI;AACF,QAAI,QAAQ,aAAa,SAAS;AAChC,YAAM,cAAc,kBAAkB;AAAA,QACpC;AAAA,QACA;AAAA,QACA,yBAAyB,QAAQ,QAAQ,MAAM,IAAI,CAAC,uBAAuB,YAAY,QAAQ,MAAM,IAAI,CAAC;AAAA,MAC5G,CAAC;AAAA,IACH,OAAO;AACL,YAAM,cAAc,SAAS,CAAC,OAAO,SAAS,MAAM,WAAW,CAAC;AAAA,IAClE;AAEA,UAAM,mBAAmB,MAAM,GAAG,QAAQ,aAAa;AAAA,MACrD,eAAe;AAAA,IACjB,CAAC;AACD,UAAM,qBAAqB,iBAAiB;AAAA,MAAK,CAAC,UAChD,MAAM,YAAY;AAAA,IACpB;AACA,UAAM,YAAY,qBACd,KAAK,KAAK,aAAa,mBAAmB,IAAI,IAC9C;AAEJ,UAAM,GAAG,GAAG,gBAAgB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC5D,UAAM,GAAG,MAAM,KAAK,QAAQ,cAAc,GAAG,EAAE,WAAW,KAAK,CAAC;AAChE,UAAM,GAAG,GAAG,WAAW,gBAAgB,EAAE,WAAW,KAAK,CAAC;AAAA,EAC5D,UAAE;AACA,UAAM,GAAG,GAAG,aAAa,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC3D;AACF;AAEA,eAAe,WAAW,YAAsC;AAC9D,MAAI;AACF,UAAM,GAAG,OAAO,UAAU;AAC1B,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,gCACb,UACA,aACe;AACf,QAAM,YAAY,KAAK,QAAQ,QAAQ;AACvC,QAAM,YAAY,KAAK,SAAS,QAAQ;AACxC,QAAM,GAAG,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAE7C,QAAM,aAAa,MAAM,GAAG;AAAA,IAC1B,KAAK,KAAK,WAAW,GAAG,SAAS,WAAW;AAAA,EAC9C;AACA,QAAM,YAAY,KAAK;AAAA,IACrB;AAAA,IACA,GAAG,SAAS,WAAW,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAAA,EAC1E;AAEA,MAAI,gBAAgB;AACpB,MAAI,kBAAkB;AACtB,MAAI,eAA6B;AAEjC,MAAI;AACF,UAAM,YAAY,UAAU;AAE5B,QAAI,MAAM,WAAW,QAAQ,GAAG;AAC9B,YAAM,GAAG,OAAO,UAAU,SAAS;AACnC,sBAAgB;AAAA,IAClB;AAEA,UAAM,GAAG,OAAO,YAAY,QAAQ;AACpC,sBAAkB;AAAA,EACpB,SAAS,OAAO;AACd,QAAI,iBAAiB,CAAC,mBAAoB,MAAM,WAAW,SAAS,GAAI;AACtE,YAAM,GAAG,OAAO,WAAW,QAAQ;AAAA,IACrC;AACA,UAAM;AAAA,EACR,UAAE;AACA,QAAI,CAAC,mBAAoB,MAAM,WAAW,UAAU,GAAI;AACtD,YAAM,GAAG,GAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAC1D;AACA,SAAK,mBAAmB,CAAC,kBAAmB,MAAM,WAAW,SAAS,GAAI;AACxE,UAAI;AACF,cAAM,GAAG,GAAG,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MACzD,SAAS,OAAO;AACd,YAAI,CAAC,cAAc;AACjB,yBACE,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,QAC5D;AAAA,MACF;AAAA,IACF;AACA,QAAI,cAAc;AAChB,cAAQ;AAAA,QACN,oEAAoE,QAAQ;AAAA,QAC5E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,0BACP,aACS;AACT,SAAO,YAAY,KAAK,CAAC,eAAe;AACtC,UAAM,WAAW,KAAK,SAAS,WAAW,IAAI,EAAE,YAAY;AAC5D,WAAO,aAAa,cAAc,aAAa;AAAA,EACjD,CAAC;AACH;AAEA,eAAsB,wBACpB,OACiB;AACjB,QAAM,UAAU,MAAM,uBAAuB,MAAM,aAAa;AAChE,MAAI;AACF,UAAM,WAAW,MAAM,GAAG,SAAS,OAAO;AAC1C,WAAO,SAAS,SAAS,QAAQ;AAAA,EACnC,UAAE;AACA,UAAM,GAAG,GAAG,SAAS,EAAE,OAAO,KAAK,CAAC;AAAA,EACtC;AACF;AAEA,eAAsB,sBACpB,OACA,aACA,QAAwB,oBACN;AAClB,QAAM,WAAW,KAAK;AAAA,IACpB,MAAM;AAAA,IACN,SAAS,MAAM,MAAM,MAAM,SAAS;AAAA,EACtC;AAEA,MAAI,aAAa,QAAQ;AACvB,UAAM,aAAa,KAAK;AAAA,MACtB,GAAG,OAAO;AAAA,MACV,wBAAwB,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAAA,IAC3E;AACA,QAAI;AACF,YAAM,GAAG,UAAU,YAAY,WAAW;AAC1C,YAAM,sBAAsB,YAAY,QAAQ;AAChD,aAAO;AAAA,IACT,UAAE;AACA,YAAM,GAAG,GAAG,YAAY,EAAE,OAAO,KAAK,CAAC;AAAA,IACzC;AAAA,EACF;AAEA,MACE,CAAC,MAAM,aACN,CAAC,MAAM,gBAAgB,MAAM,aAAa,WAAW,IACtD;AACA,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,gBAAgB,MAAM,aAAa,SAAS,GAAG;AACvD,QAAI,CAAC,0BAA0B,MAAM,YAAY,GAAG;AAClD,YAAM,IAAI;AAAA,QACR,sBAAsB,MAAM,IAAI;AAAA,MAClC;AAAA,IACF;AACA,UAAM,gCAAgC,UAAU,OAAO,eAAe;AACpE,iBAAW,cAAc,MAAM,gBAAgB,CAAC,GAAG;AACjD,cAAM,WAAW;AAAA,UACf;AAAA,UACA,WAAW;AAAA,QACb;AACA,cAAM,GAAG,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,cAAM,GAAG;AAAA,UACP;AAAA,UACA,OAAO,KAAK,WAAW,aAAa,QAAQ;AAAA,QAC9C;AAAA,MACF;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,gCAAgC,UAAU,OAAO,eAAe;AACpE,UAAM,GAAG;AAAA,MACP,KAAK,KAAK,YAAY,UAAU;AAAA,MAChC,MAAM,YAAY;AAAA,MAClB;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;;;AChhCA,eAAe,YACb,KACA,KACA,UAAgD,CAAC,GACrC;AACZ,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ,YAAY,QAAQ,GAAM;AAAA,IAClC,QAAQ,QAAQ,UAAU;AAAA,IAC1B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,GAAG;AAAA,IAC9B;AAAA,IACA,MAAM,QAAQ,OAAO,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,EACtD,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,IAAI,MAAM,GAAG,QAAQ,UAAU,MAAM,IAAI,GAAG,WAAM,SAAS,MAAM,KAAK,IAAI,EAAE;AAAA,EACpF;AAEA,SAAO,SAAS,KAAK;AACvB;AAEA,eAAsB,kBAAkB,WAAmB,KAAoC;AAC7F,SAAO,YAA0B,GAAG,SAAS,gCAAgC,KAAK;AAAA,IAChF,MAAM,CAAC;AAAA,EACT,CAAC;AACH;AAEA,eAAsB,WAAW,WAAmB,KAAwC;AAC1F,SAAO,YAA8B,GAAG,SAAS,4BAA4B,KAAK;AAAA,IAChF,MAAM,CAAC;AAAA,EACT,CAAC;AACH;AAEA,eAAsB,kBACpB,WACA,KACA,eAC2B;AAC3B,QAAM,gBAAgB,MAAM,QAAQ,IAAI,cAAc,IAAI,OAAO,WAAW;AAAA,IAC1E,MAAM,MAAM;AAAA,IACZ,aAAa,MAAM;AAAA,IACnB,UAAU,MAAM;AAAA,IAChB,YAAY,MAAM;AAAA,IAClB,aAAa,MAAM;AAAA,IACnB,eAAe,MAAM,wBAAwB,KAAK;AAAA,EACpD,EAAE,CAAC;AAEH,SAAO,YAA8B,GAAG,SAAS,4BAA4B,KAAK;AAAA,IAChF,MAAM;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,oBAAoB,WAAoC;AAC5E,QAAM,WAAW,MAAM,MAAM,WAAW,EAAE,QAAQ,YAAY,QAAQ,GAAM,EAAE,CAAC;AAC/E,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,IAAI,MAAM,OAAO,SAAS,WAAM,SAAS,MAAM,KAAK,IAAI,EAAE;AAAA,EAClE;AAEA,SAAO,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC;AACjD;AAEA,eAAsB,mBACpB,WACA,KACA,SACA,SACA,mBACiF;AACjF,SAAO,YAAoF,GAAG,SAAS,gCAAgC,KAAK;AAAA,IAC1I,QAAQ;AAAA,IACR,MAAM;AAAA,MACJ,UAAU;AAAA,MACV,eAAe;AAAA,MACf,GAAI,oBAAoB,EAAE,qBAAqB,kBAAkB,IAAI,CAAC;AAAA,IACxE;AAAA,EACF,CAAC;AACH;;;ACtFA,SAAS,YAAYC,WAAU;AAC/B,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAUjB,IAAMC,YAAWC,IAAG,QAAQ;AAE5B,IAAM,4BAA4B;AAAA,EAChC,aAAaC,MAAK,KAAKF,WAAU,WAAW,QAAQ;AAAA,EACpD,QAAQE,MAAK,KAAKF,WAAU,WAAW,QAAQ;AAAA,EAC/C,OAAOE,MAAK,KAAKF,WAAU,UAAU,QAAQ;AAC/C;AAIA,IAAM,kBAAkB,OAAO,KAAK,yBAAyB;AAE7D,IAAM,uBAA+C;AAAA,EACnD,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACV;AAGA,SAAS,kBAAkB,OAAuB;AAChD,SAAO,qBAAqB,KAAK,KAAK;AACxC;AAEA,SAAS,uBAAuB,OAAuB;AACrD,SAAO,GAAG,kBAAkB,KAAK,CAAC;AACpC;AAiCA,eAAsB,6BACpB,WACA,UAA8B,CAAC,GACd;AACjB,QAAM,iBAAiB,EAAE,GAAG,sBAAsB,SAAS,GAAG,GAAG,QAAQ,eAAe;AACxF,QAAM,cAAcE,MAAK,QAAQ,SAAS;AAC1C,QAAM,sBAAsB;AAAA,IAC1BA,MAAK,KAAKA,MAAK,QAAQ,qBAAqB,GAAG,OAAO;AAAA,IACtDA,MAAK,KAAKA,MAAK,QAAQ,UAAU,GAAG,SAAS,OAAO;AAAA,IACpDA,MAAK,QAAQA,MAAK,QAAQ,WAAW,CAAC;AAAA,EACxC;AACA,MAAI,UAAU;AACd,QAAM,wBAAwB,QAAQ,yBAAyB;AAC/D,aAAW,YAAY,oBAAI,IAAI,CAAC,GAAG,OAAO,OAAO,cAAc,GAAG,qBAAqB,CAAC,GAAG;AACzF,QAAI;AACJ,QAAI;AACF,gBAAU,MAAMC,IAAG,QAAQ,UAAU,EAAE,eAAe,KAAK,CAAC;AAAA,IAC9D,SAAS,OAAO;AACd,UAAK,OAAiC,SAAS,SAAU;AACzD,YAAM;AAAA,IACR;AACA,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,MAAM,eAAe,EAAG;AAC7B,YAAM,YAAYD,MAAK,KAAK,UAAU,MAAM,IAAI;AAChD,UAAI;AACF,cAAM,SAAS,MAAMC,IAAG,SAAS,SAAS;AAC1C,cAAM,iBAAiBD,MAAK,QAAQA,MAAK,QAAQ,SAAS,GAAG,MAAM;AACnE,cAAM,sBAAsB,oBAAoB,KAAK,CAAC,SACpD,eAAe,WAAW,GAAG,IAAI,GAAGA,MAAK,GAAG,EAAE,CAC/C;AACD,cAAM,0BAA0B,mBAAmB,eAC9C,eAAe,WAAW,GAAG,WAAW,GAAGA,MAAK,GAAG,EAAE;AAC1D,YAAI,uBAAuB,CAAC,yBAAyB;AACnD,gBAAMC,IAAG,OAAO,SAAS;AACzB,qBAAW;AAAA,QACb;AAAA,MACF,SAAS,OAAO;AAId,YAAK,OAAiC,SAAS,SAAU,OAAM;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBACP,WACA,wBAAgC,0BACtB;AACV,QAAM,oBAAoBD,MAAK,QAAQ,SAAS;AAChD,QAAM,kBAAkBA,MAAK,QAAQA,MAAK,QAAQ,iBAAiB,CAAC;AACpE,QAAM,QAAQ;AAAA,IACZ;AAAA,IACAA,MAAK,QAAQ,qBAAqB;AAAA,IAClCA,MAAK,KAAKA,MAAK,QAAQ,qBAAqB,GAAG,OAAO;AAAA,IACtDA,MAAK,KAAKA,MAAK,QAAQ,UAAU,GAAG,SAAS,OAAO;AAAA,EACtD;AACA,MAAIA,MAAK,SAAS,eAAe,MAAM,SAAS;AAC9C,UAAM,KAAK,eAAe;AAAA,EAC5B;AACA,SAAO;AACT;AAEA,eAAe,iBAAiB,UAAkB,cAA0C;AAC1F,MAAI;AACF,UAAM,QAAQ,MAAMC,IAAG,MAAM,QAAQ;AACrC,QAAI,CAAC,MAAM,eAAe,GAAG;AAC3B,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,MAAMA,IAAG,SAAS,QAAQ;AACzC,UAAM,iBAAiBD,MAAK,QAAQA,MAAK,QAAQ,QAAQ,GAAG,MAAM;AAClE,WAAO,aAAa,KAAK,CAAC,SACxB,mBAAmB,QAAQ,eAAe,WAAW,GAAG,IAAI,GAAGA,MAAK,GAAG,EAAE,CAC1E;AAAA,EACH,SAAS,OAAO;AACd,QAAK,OAAiC,SAAS,SAAU,QAAO;AAChE,UAAM;AAAA,EACR;AACF;AAEA,eAAe,qBAAqB,UAAkB,YAA+D;AACnH,MAAI;AACF,UAAM,QAAQ,MAAMC,IAAG,MAAM,QAAQ;AACrC,QAAI,MAAM,eAAe,GAAG;AAC1B,YAAM,gBAAgB,MAAMA,IAAG,SAAS,QAAQ;AAChD,YAAM,iBAAiBD,MAAK,QAAQA,MAAK,QAAQ,QAAQ,GAAG,aAAa;AACzE,UAAI,mBAAmB,YAAY;AACjC,eAAO;AAAA,MACT;AACA,YAAMC,IAAG,OAAO,QAAQ;AAAA,IAC1B,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF,SAAS,OAAO;AACd,QAAK,OAAiC,SAAS,SAAU,OAAM;AAAA,EACjE;AAEA,QAAM,eAAeD,MAAK,SAASA,MAAK,QAAQ,QAAQ,GAAG,UAAU;AACrE,QAAMC,IAAG,QAAQ,cAAc,QAAQ;AACvC,SAAO;AACT;AAEA,SAAS,sBAAsB,WAAmC;AAChE,SAAO;AAAA,IACL,OAAO;AAAA,IACP,GAAG;AAAA,EACL;AACF;AAEA,eAAe,+BACb,UACA,eACA,cACA,UACA,OACiB;AACjB,MAAI,UAAU;AACd,MAAI;AACF,UAAMA,IAAG,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAC5C,UAAM,kBAAkB,MAAMA,IAAG,QAAQ,UAAU,EAAE,eAAe,KAAK,CAAC;AAC1E,eAAW,SAAS,iBAAiB;AACnC,YAAM,YAAYD,MAAK,KAAK,UAAU,MAAM,IAAI;AAChD,UAAI;AACF,YAAI,CAAC,cAAc,IAAI,MAAM,IAAI,KAAK,MAAM,iBAAiB,WAAW,YAAY,GAAG;AACrF,gBAAMC,IAAG,OAAO,SAAS;AACzB,qBAAW;AAAA,QACb;AAAA,MACF,SAAS,OAAO;AACd,YAAK,OAAiC,SAAS,UAAU;AACvD,mBAAS,KAAK,EAAE,MAAM,MAAM,MAAM,OAAO,GAAG,kBAAkB,KAAK,CAAC,kCAAmC,MAAgB,OAAO,IAAI,CAAC;AAAA,QACrI;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,aAAS,KAAK,EAAE,MAAM,uBAAuB,KAAK,GAAG,OAAO,0CAA2C,MAAgB,OAAO,IAAI,CAAC;AAAA,EACrI;AACA,SAAO;AACT;AAEA,eAAsB,0BACpB,WACA,YAAoB,0BACpB,UAAqC,CAAC,GACrB;AACjB,MAAI,UAAU;AACd,QAAM,iBAAiB,EAAE,GAAG,sBAAsB,SAAS,GAAG,GAAG,QAAQ,eAAe;AACxF,QAAM,wBAAwB,QAAQ,yBAAyB;AAC/D,QAAM,eAAe,kBAAkB,WAAW,qBAAqB;AACvE,QAAM,YAAY,oBAAI,IAAI,CAAC,GAAG,OAAO,OAAO,cAAc,GAAG,qBAAqB,CAAC;AACnF,QAAM,iBAAiBD,MAAK,QAAQ,WAAW,SAAS,WAAW,SAAS,CAAC;AAC7E,aAAW,YAAY,WAAW;AAChC,UAAM,WAAWA,MAAK,KAAK,UAAU,SAAS,WAAW,QAAQ,CAAC;AAClE,UAAM,QAAQ,QAAQ,cAAc,oBAChC,MAAM,iBAAiB,UAAU,CAAC,cAAc,CAAC,IACjD,MAAM,iBAAiB,UAAU,YAAY;AACjD,QAAI,OAAO;AACT,YAAMC,IAAG,OAAO,QAAQ;AACxB,iBAAW;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;AAaA,eAAsB,2BACpB,aACA,eACA,YAAoB,0BACpB,UAA8B,CAAC,GACC;AAChC,QAAM,iBAAiB,EAAE,GAAG,sBAAsB,SAAS,GAAG,GAAG,QAAQ,eAAe;AACxF,QAAM,YAAmC,CAAC;AAE1C,aAAW,SAAS,iBAAiB;AACnC,UAAM,WAAW,eAAe,KAAK;AACrC,QAAID,MAAK,QAAQ,QAAQ,MAAMA,MAAK,QAAQ,SAAS,GAAG;AACtD;AAAA,IACF;AAMA,QAAI;AACF,YAAM,UAAU,MAAMC,IAAG,KAAK,QAAQ;AACtC,UAAI,CAAC,QAAQ,YAAY,GAAG;AAC1B;AAAA,MACF;AAAA,IACF,QAAQ;AACN;AAAA,IACF;AAEA,eAAW,SAAS,aAAa;AAC/B,UAAI,MAAM,WAAW,UAAU;AAC7B;AAAA,MACF;AACA,UAAI,CAAC,MAAM,MAAM,MAAM,GAAG,WAAW,QAAQ,GAAG;AAC9C;AAAA,MACF;AACA,YAAM,WAAW,cAAc,OAAO,MAAM,IAAI;AAChD,UAAI,CAAC,YAAY,SAAS,YAAY,MAAM,MACvC,SAAS,qBAAqB,KAAK,MAAM,QACzC,CAAC,MAAM,cAAc,SAAS,mBAAmB,MAAM,YAAY;AACtE;AAAA,MACF;AAEA,YAAM,eAAe,sBAAsB,MAAM,aAAa;AAC9D,YAAM,kBAAkB,sBAAsB,SAAS,YAAY;AACnE,UAAI,CAAC,aAAa,KAAK,KAAK,CAAC,gBAAgB,KAAK,GAAG;AACnD;AAAA,MACF;AAEA,UAAI;AACJ,UAAI;AACF,wBAAgB,SAAS,MAAM,MAAM,SAAS;AAAA,MAChD,QAAQ;AACN;AAAA,MACF;AAEA,YAAM,WAAWD,MAAK,KAAK,UAAU,SAAS,eAAe,QAAQ,CAAC;AACtE,UAAI;AACF,cAAMC,IAAG,MAAM,QAAQ;AAAA,MACzB,SAAS,OAAO;AACd,YAAK,OAAiC,SAAS,UAAU;AACvD,oBAAU,KAAK,EAAE,SAAS,MAAM,IAAI,WAAW,MAAM,MAAM,MAAM,CAAC;AAAA,QACpE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,aACpB,QACA,YAAoB,0BACpB,UAA8B,CAAC,GACH;AAC5B,QAAMA,IAAG,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAE7C,QAAM,SAAsC;AAAA,IAC1C,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,IACT,oBAAoB,CAAC;AAAA,IACrB,UAAU,CAAC;AAAA,EACb;AACA,QAAM,iBAAiB,EAAE,GAAG,sBAAsB,SAAS,GAAG,GAAG,QAAQ,eAAe;AACxF,QAAM,wBAAwB,QAAQ,yBAAyB;AAC/D,QAAM,eAAe,kBAAkB,WAAW,qBAAqB;AAEvE,QAAM,iBAAiB;AAAA,IACrB,OAAO,oBAAI,IAAY;AAAA,IACvB,aAAa,oBAAI,IAAY;AAAA,IAC7B,QAAQ,oBAAI,IAAY;AAAA,IACxB,OAAO,oBAAI,IAAY;AAAA,EACzB;AAEA,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,WAAW,UAAU;AAC7B;AAAA,IACF;AAEA,UAAM,gBAAgB,SAAS,MAAM,MAAM,SAAS;AACpD,UAAM,UAAU,sBAAsB,MAAM,aAAa;AACzD,QAAI,QAAQ,OAAO;AACjB,qBAAe,MAAM,IAAI,aAAa;AAAA,IACxC;AACA,QAAI,QAAQ,aAAa;AACvB,qBAAe,YAAY,IAAI,aAAa;AAAA,IAC9C;AACA,QAAI,QAAQ,QAAQ;AAClB,qBAAe,OAAO,IAAI,aAAa;AAAA,IACzC;AACA,QAAI,QAAQ,OAAO;AACjB,qBAAe,MAAM,IAAI,aAAa;AAAA,IACxC;AAAA,EACF;AAEA,aAAW,CAAC,OAAO,QAAQ,KAAK,OAAO,QAAQ,cAAc,GAA4C;AACvG,QAAID,MAAK,QAAQ,QAAQ,MAAMA,MAAK,QAAQ,SAAS,GAAG;AACtD,aAAO,WAAW,eAAe,KAAK,EAAE;AACxC;AAAA,IACF;AAEA,QAAI;AACF,YAAMC,IAAG,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,IAC9C,SAAS,OAAO;AACd,aAAO,SAAS,KAAK,EAAE,MAAM,uBAAuB,KAAK,GAAG,OAAO,4CAA6C,MAAgB,OAAO,IAAI,CAAC;AAC5I;AAAA,IACF;AAEA,UAAM,gBAAgB,eAAe,KAAK;AAC1C,QAAI,QAAQ,oBAAoB,OAAO;AACrC,aAAO,WAAW,MAAM,+BAA+B,UAAU,eAAe,cAAc,OAAO,UAAU,KAAK;AAAA,IACtH;AAEA,eAAW,aAAa,eAAe;AACrC,YAAM,aAAaD,MAAK,KAAK,WAAW,SAAS;AACjD,YAAM,WAAWA,MAAK,KAAK,UAAU,SAAS,WAAW,QAAQ,CAAC;AAClE,UAAI;AACF,YAAI,EAAE,MAAMC,IAAG,KAAKD,MAAK,KAAK,YAAY,UAAU,CAAC,GAAG,OAAO,EAAG,OAAM,IAAI,MAAM,kBAAkB;AAAA,MACtG,QAAQ;AACN,eAAO,WAAW;AAClB,eAAO,SAAS,KAAK,EAAE,MAAM,WAAW,OAAO,GAAG,kBAAkB,KAAK,CAAC,sCAAsC,CAAC;AACjH;AAAA,MACF;AACA,UAAI;AACJ,UAAI;AACF,sBAAc,MAAM,qBAAqB,UAAU,UAAU;AAAA,MAC/D,SAAS,OAAO;AACd,eAAO,WAAW;AAClB,eAAO,SAAS,KAAK,EAAE,MAAM,WAAW,OAAO,GAAG,kBAAkB,KAAK,CAAC,kCAAmC,MAAgB,OAAO,IAAI,CAAC;AACzI;AAAA,MACF;AACA,UAAI,gBAAgB,UAAU;AAC5B,eAAO,UAAU;AAAA,MACnB,WAAW,gBAAgB,WAAW;AACpC,gBAAQ;AAAA,UACN,gCAAgC,SAAS,SAAS,KAAK,8BAA8B,QAAQ;AAAA,QAC/F;AACA,eAAO,WAAW;AAClB,eAAO,SAAS,KAAK,EAAE,MAAM,WAAW,OAAO,GAAG,kBAAkB,KAAK,CAAC,qDAAqD,CAAC;AAAA,MAClI,OAAO;AACL,eAAO,WAAW;AAAA,MACpB;AACA,UAAI,gBAAgB,WAAW;AAC7B,eAAO,mBAAmB,SAAS,IAAI,EAAE,GAAG,OAAO,mBAAmB,SAAS,GAAG,CAAC,KAAK,GAAG,KAAK;AAAA,MAClG;AAAA,IACF;AAAA,EACF;AAEA,MACE,QAAQ,oBAAoB,SACzB,CAAC,OAAO,OAAO,cAAc,EAAE;AAAA,IAChC,CAAC,aAAaA,MAAK,QAAQ,QAAQ,MAAMA,MAAK,QAAQ,qBAAqB;AAAA,EAC7E,GACA;AACA,WAAO,WAAW,MAAM;AAAA,MACtB;AAAA,MACA,oBAAI,IAAI;AAAA,MACR;AAAA,MACA,OAAO;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;;;AC9aO,SAAS,kBACd,aACA,WACA,yBAA8C,oBAAI,IAAI,GACtD,iBACc;AACd,SAAO,YAAY,OAAO,CAAC,UAAU;AACnC,QAAI,uBAAuB,IAAI,MAAM,IAAI,GAAG;AAC1C,aAAO;AAAA,IACT;AACA,UAAM,WAAW,UAAU,OAAO,MAAM,IAAI;AAC5C,QAAI,CAAC,UAAU;AACb,aAAO;AAAA,IACT;AACA,QAAI,mBAAmB,CAAC,gBAAgB,IAAI,MAAM,IAAI,GAAG;AACvD,aAAO;AAAA,IACT;AACA,WAAO,SAAS,eAAe,MAAM;AAAA,EACvC,CAAC;AACH;;;ACbA,eAAsB,kCACpB,OACA,cACkB;AAClB,MAAI,MAAM,kBAAkB;AAC1B,QAAI;AACF,YAAM,cAAc,MAAM,aAAa,oBAAoB,MAAM,gBAAgB;AACjF,YAAM,oBAAoB,MAAM,aAAa,sBAAsB,OAAO,WAAW;AACrF,UAAI,mBAAmB;AACrB,eAAO;AAAA,MACT;AAEA,mBAAa;AAAA,QACX,oBAAoB,MAAM,IAAI;AAAA,QAC9B,IAAI,MAAM,6BAA6B;AAAA,MACzC;AAAA,IACF,SAAS,OAAO;AACd,mBAAa;AAAA,QACX,oCAAoC,MAAM,IAAI;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,MAAM,yBAAyB;AACjC,iBAAa;AAAA,MACX,gDAAgD,MAAM,IAAI;AAAA,MAC1D,IAAI,MAAM,yBAAyB;AAAA,IACrC;AACA,WAAO;AAAA,EACT;AAEA,MAAI;AACF,WAAO,MAAM,aAAa,sBAAsB,KAAK;AAAA,EACvD,SAAS,OAAO;AACd,iBAAa;AAAA,MACX,iCAAiC,MAAM,IAAI;AAAA,MAC3C;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;;;ANcA,IAAM,mBAAmB,oBAAI,IAAI,CAAC,cAAc,eAAe,WAAW,CAAC;AAE3E,SAAS,kBAAkB,cAAkD;AAC3E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ,aAAa,OAAO,OAAO,CAAC,UAAU,CAAC,iBAAiB,IAAI,MAAM,IAAI,CAAC;AAAA,EACjF;AACF;AAEA,SAAS,sBAAsB,OAAuC;AACpE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ,OAAO;AAAA,MACb,OAAO,QAAQ,MAAM,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,iBAAiB,IAAI,IAAI,CAAC;AAAA,IAC7E;AAAA,EACF;AACF;AAuCA,IAAM,8BAAwD;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,SAAS,WAAW,QAA+C;AACjE,SAAO,IAAI,IAAI,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;AAC3D;AAEA,SAAS,iBAAiB,KAA4B;AACpD,MAAI;AACF,UAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,UAAU,KAAK;AAAA,MACnB,OAAO,KAAK,MAAM,CAAC,GAAG,WAAW,EAAE,SAAS;AAAA,IAC9C;AACA,WAAO,OAAO,QAAQ,QAAQ,YAAY,QAAQ,IAAI,KAAK,IACvD,QAAQ,IAAI,KAAK,IACjB;AAAA,EACN,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,iBAAiB,OAAmD;AAC3E,SAAOE,YAAW,QAAQ,EAAE,OAAO,KAAK,UAAU;AAAA,IAChD,IAAI,MAAM;AAAA,IACV,MAAM,MAAM;AAAA,IACZ,QAAQ,MAAM;AAAA,IACd,OAAO,MAAM,cAAc,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,IAC9E,iBAAiB,MAAM,4BAA4B;AAAA,EACrD,CAAC,CAAC,EAAE,OAAO,KAAK;AAClB;AAEO,SAAS,sBACd,YACA,aACA,eACS;AACT,QAAM,YAAY,WAAW;AAC7B,QAAM,YAAY,WAAW,qBAAqB;AAClD,QAAM,aAAa,YAAY,IAAI,SAAS;AAC5C,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,cAAc,OAAO,SAAS;AAC/C,MAAI,UAAU,eAAe,WAAW,cACnC,SAAS,qBAAqB,iBAAiB,UAAU,EAAG,QAAO;AAExE,MAAI,WAAW,WAAW,WAAW;AACnC,WAAO,YAAY,cAAc,WAAW,aAAa;AAAA,EAC3D;AAEA,QAAM,4BACJ,CAAC,YAAY,SAAS,eAAe,WAAW;AAClD,SACE,CAAC,8BACC,QAAQ,SAAS,KAAK,cAAc,WAAW,cAC5C,QAAQ,UAAU,oBAAoB,SAAS,qBAAqB,iBAAiB,UAAU,CAAC;AAEzG;AAEA,SAAS,eACP,cACA,aACA,cACA,qBAA4D,CAAC,GAC7D,qBAA0C,oBAAI,IAAI,GAClC;AAChB,QAAM,gBAAgB,WAAW,WAAW;AAC5C,QAAM,SAAS,OAAO;AAAA,IACpB,aAAa,OAAO,IAAI,CAAC,UAAU;AACjC,YAAM,aAAa,cAAc,IAAI,MAAM,IAAI;AAC/C,aAAO;AAAA,QACL,MAAM;AAAA,QACN;AAAA,UACE,SAAS,MAAM;AAAA,UACf,YAAY,YAAY,cAAc,MAAM,qBAAqB;AAAA,UACjE,cAAc,sBAAsB,MAAM,aAAa;AAAA,UACvD,oBAAoB,MAAM,WAAW,WAAW,mBAAmB,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC;AAAA,UACxF,gBAAgB,MAAM;AAAA,UACtB,GAAI,CAAC,mBAAmB,IAAI,MAAM,IAAI,KAAK,CAAC,MAAM,mBAC9C,EAAE,kBAAkB,iBAAiB,KAAK,EAAE,IAAI,CAAC;AAAA,UACrD,UAAU,iBAAgB,oBAAI,KAAK,GAAE,YAAY;AAAA,QACnD;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,4BACP,cACA,aACA,eAC4B;AAC5B,QAAM,kBAAkB,IAAI;AAAA,IAC1B,aAAa,OAAO,IAAI,CAAC,UAAU,MAAM,IAAI;AAAA,EAC/C;AACA,QAAM,kBAAkB,YACrB,OAAO,CAAC,UAAU,CAAC,gBAAgB,IAAI,MAAM,IAAI,CAAC,EAClD,IAAI,CAAC,UAAU;AACd,UAAM,WAAW,cAAc,OAAO,MAAM,IAAI;AAChD,WAAO;AAAA,MACL,IAAI,UAAU,WAAW,SAAS,MAAM,IAAI;AAAA,MAC5C,MAAM,MAAM;AAAA,MACZ,aAAa,MAAM,eAAe;AAAA,MAClC,UAAU,MAAM;AAAA,MAChB,eAAe,UAAU;AAAA,MACzB,mBAAmB,MAAM;AAAA,MACzB,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF,CAAC;AAEH,SAAO,CAAC,GAAG,aAAa,QAAQ,GAAG,eAAe;AACpD;AAEA,SAAS,iBAAiB,OAAgC;AACxD,SAAO,MAAM,iBAAiB,QAAQ,OAAO,KAAK,MAAM,MAAM,EAAE,WAAW;AAC7E;AAEA,SAAS,yBACP,aACA,aACA,aACgB;AAChB,MAAI,CAAC,iBAAiB,WAAW,KAAK,CAAC,aAAa;AAClD,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,OAAO;AAAA,IAC5B,YAAY,QAAQ,CAAC,UAAU;AAC7B,YAAM,WAAW,YAAY,OAAO,MAAM,IAAI;AAC9C,UAAI,CAAC,YAAY,SAAS,eAAe,MAAM,YAAY;AACzD,eAAO,CAAC;AAAA,MACV;AACA,aAAO,CAAC,CAAC,MAAM,MAAM,QAAQ,CAAC;AAAA,IAChC,CAAC;AAAA,EACH;AAEA,MAAI,OAAO,KAAK,cAAc,EAAE,WAAW,GAAG;AAC5C,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,cAAc,YAAY;AAAA,IAC1B,QAAQ;AAAA,EACV;AACF;AAEA,eAAe,gCACb,cACA,aACA,eACA,WACA,MAIA,WAA+B,CAAC,GAChC,oBAAiC,oBAAI,IAAI,GACxB;AACjB,QAAM,gBAAgB,WAAW,WAAW;AAC5C,QAAM,gBAAgB,CAAC,SAAiB,UAAyB;AAC/D,YAAQ,KAAK,WAAW,OAAO,IAAI,KAAK;AAAA,EAC1C;AAEA,MAAI,aAAa;AACjB,aAAW,cAAc,aAAa,QAAQ;AAC5C,QAAI,CAAC,sBAAsB,YAAY,eAAe,aAAa,GAAG;AACpE;AAAA,IACF;AAEA,QACE,MAAM,kCAAkC,YAAY;AAAA,MAClD,qBAAqB,KAAK;AAAA,MAC1B,uBAAuB,CAAC,OAAO,gBAC7B,KAAK,sBAAsB,OAAO,aAAa,SAAS;AAAA,MAC1D,WAAW;AAAA,IACb,CAAC,GACD;AACA,oBAAc;AACd,wBAAkB,IAAI,WAAW,IAAI;AAAA,IACvC,OAAO;AACL,eAAS,KAAK,EAAE,MAAM,WAAW,MAAM,OAAO,oEAAoE,CAAC;AAAA,IACrH;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,2BACP,cACM;AACN,MACE,aAAa,oBAAoB,SAAS,kCACvC,aAAa,mBAAmB,gBAAgB,UACnD;AAKA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,oCACpB,WACA,KACA,eAWK,CAAC,GACN,UAAyC,CAAC,GACH;AACvC,QAAM,OAAO;AAAA,IACX,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACA,QAAM,aAAa,iBAAiB,GAAG;AACvC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,yBAAyB,QAAQ,iBAAiB,KAAK,KAAK,UAAU;AACxF,QAAM,eAAe,MAAM,KAAK,WAAW,WAAW,GAAG;AACzD,6BAA2B,YAAY;AACvC,QAAM,gBAAgB,MAAM,KAAK,cAAc,SAAS;AAExD,QAAM,cAAc,MAAM,KAAK,gBAAgB,SAAS;AACxD,QAAM,kBAAsC,CAAC;AAC7C,QAAM,oBAAoB,oBAAI,IAAY;AAC1C,QAAM,aAAa,MAAM;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,mBAAmB,MAAM,KAAK,gBAAgB,SAAS;AAC7D,QAAM,eAAe,aAAa,mBAAkB,oBAAI,KAAK,GAAE,YAAY;AAE3E,QAAM,mBAAmB,IAAI,IAAI,QAAQ,oBAAoB,CAAC,CAAC;AAC/D,QAAM,WAAW,CAAC,GAAG,eAAe;AACpC,QAAM,gBAAuD,CAAC;AAC9D,aAAW,SAAS,aAAa,QAAQ;AACvC,UAAM,WAAW,cAAc,OAAO,MAAM,IAAI;AAChD,QAAI,MAAM,cAAc,UAAU,YAAY,MAAM,MAAM,SAAS,mBAAmB,MAAM,YAAY;AACtG,oBAAc,MAAM,IAAI,IAAI,EAAE,GAAG,SAAS,mBAAmB;AAAA,IAC/D;AAAA,EACF;AACA,MAAI,iBAAiB,OAAO,GAAG;AAC7B,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B,aAAa,OAAO,OAAO,CAAC,UAAU,iBAAiB,IAAI,MAAM,IAAI,CAAC;AAAA,MACtE,UAAU;AAAA,MACV,EAAE,iBAAiB,MAAM;AAAA,IAC3B;AACA,aAAS,KAAK,IAAI,SAAS,YAAY,CAAC,GAAG;AAAA,MACzC,CAAC,YAAY,CAAC,gBAAgB,KAAK,CAAC,aAAa,SAAS,SAAS,QAAQ,IAAI;AAAA,IACjF,CAAC;AACD,eAAW,QAAQ,kBAAkB;AACnC,oBAAc,IAAI,IAAI,SAAS,qBAAqB,IAAI,KAAK,CAAC;AAAA,IAChE;AAAA,EACF;AACA,aAAW,WAAW,gBAAiB,QAAO,cAAc,QAAQ,IAAI;AAExE,QAAM,oBAAoB;AAAA,IACxB;AAAA,IAAc;AAAA,IAAkB;AAAA,IAAc;AAAA,IAC9C,IAAI,IAAI,gBAAgB,IAAI,UAAQ,KAAK,IAAI,CAAC;AAAA,EAChD;AAIA,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,kBAAkB,MAAM,GAAG;AACpE,QAAI,kBAAkB,IAAI,IAAI,EAAG;AACjC,UAAM,WAAW,cAAc,OAAO,IAAI;AAC1C,QAAI,UAAU;AACZ,YAAM,aAAa,SAAS;AAC5B,YAAM,mBAAmB,SAAS;AAAA,IACpC,OAAO;AACL,aAAO,kBAAkB,OAAO,IAAI;AAAA,IACtC;AAAA,EACF;AACA,oBAAkB,SAAS,EAAE,GAAG,cAAc,QAAQ,GAAG,kBAAkB,OAAO;AAClF,QAAM,KAAK,eAAe,mBAAmB,SAAS;AAEtD,SAAO;AAAA,IACL,wBAAwB,aAAa,OAClC,OAAO,CAAC,UAAU,iBAAiB,KAAK,CAAC,UAAU,MAAM,SAAS,MAAM,IAAI,KACxE,CAAC,gBAAgB,KAAK,CAAC,YAAY,QAAQ,SAAS,MAAM,IAAI,CAAC,EACnE,IAAI,CAAC,UAAU,MAAM,IAAI;AAAA,IAC5B,QAAQ,aAAa,OAAO;AAAA,IAC5B;AAAA,IACA,SAAS;AAAA,IACT,SAAS;AAAA,IACT;AAAA,IACA,GAAI,SAAS,SAAS,EAAE,aAAa,SAAS,IAAI,CAAC;AAAA,EACrD;AACF;AASA,eAAe,6BACb,WACA,KACA,cACA,eACA,aACA,WACA,MAIA,UACiB;AAGjB,MAAI,iBAAiB,WAAW,GAAG;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,MAAM,KAAK;AAAA,IAC3B,aAAa;AAAA,IACb;AAAA,IACA;AAAA,EACF;AACA,MAAI,UAAU,WAAW,GAAG;AAC1B,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,oBAAI,IAA8E;AACxG,aAAW,YAAY,WAAW;AAChC,UAAM,QAAQ,cAAc,IAAI,SAAS,OAAO,KAAK;AAAA,MACnD,WAAW,SAAS;AAAA,MACpB,QAAQ,oBAAI,IAAkC;AAAA,IAChD;AACA,UAAM,OAAO,IAAI,SAAS,KAAK;AAC/B,kBAAc,IAAI,SAAS,SAAS,KAAK;AAAA,EAC3C;AAEA,QAAM,QAAQ,kBAAkB,MAAM,KAAK,WAAW,WAAW,GAAG,CAAC;AACrE,6BAA2B,KAAK;AAChC,SAAO,OAAO,cAAc,KAAK;AACjC,MAAI,eAAe;AACnB,MAAI,cAAc;AAClB,aAAW,CAAC,SAAS,EAAE,WAAW,OAAO,CAAC,KAAK,eAAe;AAC5D,UAAM,QAAQ,aAAa,OAAO,KAAK,CAAC,SAAS,KAAK,OAAO,OAAO;AACpE,UAAM,WAAW,cAAc,OAAO,SAAS;AAC/C,QAAI,CAAC,OAAO,cAAc,UAAU,mBAAmB,MAAM,WAAY;AACzE,UAAM,QAAQ,OAAO,YAAY,CAAC,GAAG,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AAC3E,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,mBAAmB,WAAW,KAAK,SAAS,OAAO,MAAM,UAAU;AAC5F,UAAI,MAAM,YAAY,QAAQ,CAAC,MAAM,YAAY,KAAK,KACjD,MAAM,eAAe,MAAM,cAC3B,CAAC,CAAC,SAAS,eAAe,UAAU,OAAO,EAAE,MAAM,CAAC,UACrD,OAAO,MAAM,gBAAgB,KAA2B,MAAM,SAAS,KACtE,CAAC,CAAC,GAAG,MAAM,EAAE,MAAM,CAAC,UAAU,MAAM,cAAc,KAAK,MAAM,KAAK,GAAG;AACxE,cAAM,IAAI,MAAM,4DAA4D;AAAA,MAC9E;AACA,YAAM,gBAAgB,MAAM;AAC5B,YAAM,aAAa,MAAM;AACzB,qBAAe,OAAO;AAAA,IACxB,SAAS,OAAO;AACd,qBAAe;AACf,eAAS,KAAK,EAAE,MAAM,WAAW,OAAO,sEAAsE,CAAC;AAC/G,cAAQ,KAAK,8DAA8D,SAAS,wCAAwC,KAAK;AAAA,IACnI;AAAA,EACF;AACA,MAAI,cAAc;AAChB,UAAM,YAAY,kBAAkB,MAAM,KAAK,WAAW,WAAW,GAAG,CAAC;AACzE,+BAA2B,SAAS;AACpC,WAAO,OAAO,cAAc,SAAS;AAAA,EACvC;AACA,SAAO;AACT;AAEA,eAAsB,aACpB,WACA,KACA,eAAkD,CAAC,GACnD,UAA+B,CAAC,GACH;AAC7B,QAAM,OAAO;AAAA,IACX,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACA,QAAM,eAA6B,MAAM,KAAK;AAAA,IAC5C;AAAA,IACA;AAAA,EACF;AAMA,QAAM,aAAa,aAAa,SAAS,KAAK,KAAK,iBAAiB,GAAG;AACvE,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,YAAY,yBAAyB,UAAU;AACrD,QAAM,sBAAsB,MAAM,KAAK;AAAA,IACrC,UAAU;AAAA,EACZ;AACA,MAAI,QAAQ,qBAAqB,SAAS,CAAC,aAAa,cAAc;AACpE,WAAO;AAAA,MACL,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,cAAc,aAAa;AAAA,MAC3B,cAAc,CAAC;AAAA,IACjB;AAAA,EACF;AAEA,MAAI,eAAe,kBAAkB,MAAM,KAAK,WAAW,WAAW,GAAG,CAAC;AAC1E,6BAA2B,YAAY;AAEvC,QAAM,yBAAyB,IAAI;AAAA,IACjC,aAAa,OACV,OAAO,CAAC,UAAU,MAAM,WAAW,SAAS,EAC5C,IAAI,CAAC,UAAU,MAAM,IAAI;AAAA,EAC9B;AACA,QAAM,sBAAsB,oBAAI,IAAI,CAAC,GAAG,wBAAwB,GAAG,gBAAgB,CAAC;AACpF,QAAM,cAAc,sBAAsB,MAAM,KAAK,cAAc,SAAS,CAAC;AAC7E,QAAM,qBAAyC,CAAC;AAChD,QAAM,cAAc,aAAa,sCAAsC,OACnE,MAAM;AAAA,IACJ;AAAA,IAAW;AAAA,IAAK;AAAA,IAAc;AAAA,IAAa;AAAA,IAAa,UAAU;AAAA,IAAW;AAAA,IAAM;AAAA,EACrF,IACA;AACJ,QAAM,aAAa,iBAAiB,GAAG;AACvC,MAAI,oBAA2C;AAC/C,MAAI,cAAc,eAAe,YAAY;AAC3C,UAAM,oBAAoB,yBAAyB,UAAU;AAC7D,wBAAoB,MAAM,KAAK,cAAc,iBAAiB;AAC9D,UAAM,KAAK,0BAA0B,WAAW;AAAA,MAC9C,aAAa,CAAC,EAAE,OAAO,uBAAuB,MAAM,kBAAkB,UAAU,CAAC;AAAA,MACjF;AAAA,IACF,CAAC;AAAA,EACH;AACA,QAAM,KAAK,0BAA0B,WAAW;AAAA,IAC9C;AAAA,EACF,CAAC;AACD,QAAM,eAAe,MAAM,KAAK,gBAAgB,SAAS,GACtD,OAAO,CAAC,UAAU,CAAC,iBAAiB,IAAI,MAAM,IAAI,CAAC;AACtD,QAAM,gBAAgB,sBAAsB;AAAA,IAC1C;AAAA,IACA;AAAA,IACA,iBAAiB,WAAW,IACvB,CAAC,qBAAqB,iBAAiB,iBAAiB,IACrD,MAAM,KAAK,oBAAoB,SAAS,IACxC,oBACJ;AAAA,EACN,CAAC;AACD,QAAM,wBAAwB,MAAM,KAAK;AAAA,IACvC,4BAA4B,cAAc,aAAa,aAAa;AAAA,IACpE,UAAU;AAAA,EACZ;AACA,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA,IAAI,IAAI,aAAa,OAAO,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC;AAAA,EACxD;AAEA,QAAM,eAAmC,CAAC;AAC1C,MAAI,eAAe,SAAS,GAAG;AAC7B,UAAM,aAAa,MAAM,KAAK,kBAAkB,WAAW,KAAK,cAAc;AAC9E,QAAI,MAAM,QAAQ,YAAY,MAAM,KAAK,WAAW,OAAO,SAAS,GAAG;AACrE,mBAAa,KAAK,GAAG,WAAW,MAAM;AACtC,cAAQ;AAAA,QACN,gBAAgB,WAAW,OAAO,MAAM,0CACtC,WAAW,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI;AAAA,MACpE;AAAA,IACF;AACA,mBAAe,kBAAkB,MAAM,KAAK,WAAW,WAAW,GAAG,CAAC;AACtE,+BAA2B,YAAY;AAAA,EACzC;AAGA,QAAM,kBAAkB,IAAI,IAAI,aAAa,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACtE,MAAI,UAAU;AACd,aAAW,aAAa,OAAO,KAAK,cAAc,MAAM,GAAG;AACzD,QAAI,CAAC,gBAAgB,IAAI,SAAS,GAAG;AACnC,YAAM,KAAK,iBAAiB,WAAW,SAAS;AAChD,YAAM,KAAK,0BAA0B,WAAW,UAAU,SAAS;AACnE,iBAAW;AAAA,IACb;AAAA,EACF;AAEA,QAAM,kBAAsC,CAAC;AAC7C,QAAM,aAAa,MAAM;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,oBAAoB,MAAM,KAAK,gBAAgB,SAAS,GAC3D,OAAO,CAAC,UAAU,CAAC,iBAAiB,IAAI,MAAM,IAAI,CAAC;AACtD,QAAM,gBAAgB,MAAM,KAAK;AAAA,IAC/B,4BAA4B,cAAc,kBAAkB,aAAa;AAAA,IACzE,UAAU;AAAA,EACZ;AACA,QAAM,gBAAgB,EAAE,GAAI,cAAc,sBAAsB,CAAC,EAAG;AACpE,aAAW,WAAW,gBAAiB,QAAO,cAAc,QAAQ,IAAI;AACxE,QAAM,eAAe,aAAa,mBAAkB,oBAAI,KAAK,GAAE,YAAY;AAE3E,QAAM,KAAK;AAAA,IACT,eAAe,cAAc,kBAAkB,cAAc,eAAe,IAAI,IAAI,gBAAgB,IAAI,UAAQ,KAAK,IAAI,CAAC,CAAC;AAAA,IAC3H;AAAA,EACF;AAEA,SAAO;AAAA,IACL,aAAa;AAAA,IACb,QAAQ,eAAe;AAAA,IACvB,QAAQ,aAAa,OAAO;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,sBAAsB,SAAS,cAAc;AAAA,IACrD,SAAS,sBAAsB,sBAAsB,UAAU,cAAc;AAAA,IAC7E,SAAS,cAAc;AAAA,IACvB,aAAa,CAAC,GAAG,oBAAoB,GAAG,iBAAiB,IAAI,cAAc,YAAY,CAAC,GAAG;AAAA,MACzF,CAAC,YAAY,CAAC,gBAAgB,KAAK,CAAC,aAAa,SAAS,SAAS,QAAQ,IAAI;AAAA,IACjF,CAAC;AAAA,IACD;AAAA,IACA;AAAA,EACF;AACF;",
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'node:crypto';\nimport type {\n AgentTargets,\n LocalSkill,\n NotisSyncState,\n SkillSyncFailure,\n SyncPullResponse,\n SyncSettings,\n} from \"./types\";\nimport { normalizeAgentTargets } from \"./types\";\nimport {\n downloadSkillBundle,\n fetchSyncSettings,\n pullSkills,\n pushChangedSkills,\n updateAgentTargets,\n} from \"./cloud-client\";\nimport {\n deleteLocalSkill,\n gatherTopLevelLocalSkills,\n getSkillSyncPathsForUser,\n readLegacySyncState,\n readSyncState,\n scanLocalSkills,\n writeCloudSkillToDisk,\n writeSyncState,\n} from \"./local-scanner\";\nimport {\n detectDeletedAgentSymlinks,\n removeForeignAccountSymlinks,\n removeAllSymlinksForSkill,\n syncSymlinks,\n type DeletedAgentSymlink,\n} from \"./symlink-manager\";\nimport { getPushCandidates, selectCloudSkillsToApply } from \"./sync-plan\";\nimport { writeCloudSkillWithBundleFallback } from \"./write-cloud-skill\";\nexport { fetchSyncSettings } from './cloud-client';\n\nexport interface RunSkillSyncResult {\n syncEnabled: boolean;\n pushed: number;\n pulled: number;\n downloaded: number;\n deleted: number;\n /** Skills deactivated for a specific agent because the user deleted that agent's local\n * symlink (e.g. `rm ~/.claude/skills/<skill>`); the deletion is honored instead of recreated. */\n deactivated: number;\n linked: number;\n removed: number;\n skipped: number;\n lastSyncedAt: string | null;\n /** Skills the server rejected (e.g. an invalid SKILL.md description). The rest of\n * the batch still syncs; these are surfaced so the user knows what to fix. */\n failedPushes: SkillSyncFailure[];\n failedLinks?: SkillSyncFailure[];\n}\n\nexport interface RunSkillSyncOptions {\n /** Electron's scheduled invocation honors the account preference. A manual\n * CLI sync always runs, even when automatic Desktop refresh is disabled. */\n honorSyncEnabled?: boolean;\n}\n\nconst BASE_SKILL_NAMES = new Set(['notis-apps', 'notis-query', 'notis-cli']);\n\nfunction withoutBaseSkills(pullResponse: SyncPullResponse): SyncPullResponse {\n return {\n ...pullResponse,\n skills: pullResponse.skills.filter((skill) => !BASE_SKILL_NAMES.has(skill.name)),\n };\n}\n\nfunction withoutBaseSkillState(state: NotisSyncState): NotisSyncState {\n return {\n ...state,\n skills: Object.fromEntries(\n Object.entries(state.skills).filter(([name]) => !BASE_SKILL_NAMES.has(name)),\n ),\n };\n}\n\nexport interface MaterializeCloudSkillsResult {\n /** Cloud skills verified present in the final scoped disk scan. */\n materializedSkillNames: string[];\n pulled: number;\n downloaded: number;\n deleted: number;\n removed: number;\n lastSyncedAt: string | null;\n failedLinks?: SkillSyncFailure[];\n}\n\nexport interface MaterializeCloudSkillsOptions {\n /** Server-verified Notis identity, when Desktop's auth subject differs. */\n canonicalUserId?: string;\n /** Re-link only these cloud skills without removing or adopting other links. */\n relinkSkillNames?: readonly string[];\n}\n\ninterface RunSkillSyncDependencies {\n fetchSyncSettings: typeof fetchSyncSettings;\n pullSkills: typeof pullSkills;\n pushChangedSkills: typeof pushChangedSkills;\n downloadSkillBundle: typeof downloadSkillBundle;\n gatherTopLevelLocalSkills: typeof gatherTopLevelLocalSkills;\n readLegacySyncState: typeof readLegacySyncState;\n readSyncState: typeof readSyncState;\n scanLocalSkills: typeof scanLocalSkills;\n deleteLocalSkill: typeof deleteLocalSkill;\n writeCloudSkillToDisk: typeof writeCloudSkillToDisk;\n writeSyncState: typeof writeSyncState;\n removeAllSymlinksForSkill: typeof removeAllSymlinksForSkill;\n syncSymlinks: typeof syncSymlinks;\n detectDeletedAgentSymlinks: typeof detectDeletedAgentSymlinks;\n removeForeignAccountSymlinks: typeof removeForeignAccountSymlinks;\n updateAgentTargets: typeof updateAgentTargets;\n}\n\nconst DEFAULT_RUN_SKILL_SYNC_DEPS: RunSkillSyncDependencies = {\n fetchSyncSettings,\n pullSkills,\n pushChangedSkills,\n downloadSkillBundle,\n gatherTopLevelLocalSkills,\n readLegacySyncState,\n readSyncState,\n scanLocalSkills,\n deleteLocalSkill,\n writeCloudSkillToDisk,\n writeSyncState,\n removeAllSymlinksForSkill,\n syncSymlinks,\n detectDeletedAgentSymlinks,\n removeForeignAccountSymlinks,\n updateAgentTargets,\n};\nfunction toSkillMap(skills: LocalSkill[]): Map<string, LocalSkill> {\n return new Map(skills.map((skill) => [skill.name, skill]));\n}\n\nfunction decodeJwtSubject(jwt: string): string | null {\n try {\n const parts = jwt.split(\".\");\n if (parts.length !== 3) return null;\n const decoded = JSON.parse(\n Buffer.from(parts[1], \"base64url\").toString(),\n ) as { sub?: unknown };\n return typeof decoded.sub === \"string\" && decoded.sub.trim()\n ? decoded.sub.trim()\n : null;\n } catch {\n return null;\n }\n}\n\nfunction cloudContentHash(skill: SyncPullResponse['skills'][number]): string {\n return createHash('sha256').update(JSON.stringify({\n md: skill.skill_md,\n hash: skill.skill_folder_hash,\n source: skill.skill_source_url,\n files: skill.bundle_files?.slice().sort((a, b) => a.path.localeCompare(b.path)),\n hydrationFailed: skill.bundle_hydration_failed === true,\n })).digest('hex');\n}\n\nexport function shouldWriteCloudSkill(\n cloudSkill: SyncPullResponse[\"skills\"][number],\n localSkills: Map<string, LocalSkill>,\n previousState: NotisSyncState,\n): boolean {\n const skillName = cloudSkill.name;\n const cloudHash = cloudSkill.skill_folder_hash || \"\";\n const localSkill = localSkills.get(skillName);\n if (!localSkill) {\n return true;\n }\n\n const previous = previousState.skills[skillName];\n if (previous?.folderHash === localSkill.folderHash\n && previous.cloudContentHash === cloudContentHash(cloudSkill)) return false;\n\n // Without a stored content baseline (skills served through a signed source URL keep\n // none, because that URL changes on every pull) the only other signal is\n // `skill_folder_hash`, which the server does NOT always compute the way the local\n // folder hash is computed. An app-published skill hashes its bundle entries, so that\n // value can never equal the hash of the extracted directory, and comparing the two\n // replaced the folder on every sync tick, deleting whatever a running skill had\n // written inside it. Compare cloud against cloud instead: rewrite when the cloud\n // revision we last applied has actually moved.\n if (previous\n && previous.cloudContentHash === undefined\n && previous.cloudId === cloudSkill.id\n && previous.appliedCloudFolderHash !== undefined\n && previous.appliedCloudFolderHash === cloudHash\n && previous.cloudUpdatedAt === cloudSkill.updated_at\n && previous.folderHash === localSkill.folderHash) {\n return false;\n }\n\n if (cloudSkill.source === \"curated\") {\n return cloudHash ? cloudHash !== localSkill.folderHash : true;\n }\n\n const localChangedSinceLastSync =\n !previous || previous.folderHash !== localSkill.folderHash;\n return (\n !localChangedSinceLastSync &&\n ((Boolean(cloudHash) && cloudHash !== localSkill.folderHash)\n || Boolean(previous?.cloudContentHash && previous.cloudContentHash !== cloudContentHash(cloudSkill)))\n );\n}\n\ntype AppliedCloudRevisions = Record<string, string | undefined>;\n\n/**\n * The cloud revision each folder's last successful write applied: this run's revision\n * for the skills we just wrote, the previously recorded one otherwise. A failed write\n * records nothing, so the next sync retries it.\n */\nfunction collectAppliedCloudRevisions(\n pullResponse: SyncPullResponse,\n previousState: NotisSyncState,\n writtenSkillNames: ReadonlySet<string>,\n): AppliedCloudRevisions {\n const applied: AppliedCloudRevisions = {};\n for (const skill of selectCloudSkillsToApply(pullResponse.skills)) {\n applied[skill.name] = writtenSkillNames.has(skill.name)\n ? skill.skill_folder_hash || \"\"\n : previousState.skills[skill.name]?.appliedCloudFolderHash;\n }\n return applied;\n}\n\nfunction buildSyncState(\n pullResponse: SyncPullResponse,\n localSkills: LocalSkill[],\n lastSyncedAt: string | null,\n verifiedAgentLinks: Record<string, Partial<AgentTargets>> = {},\n failedContentNames: ReadonlySet<string> = new Set(),\n appliedRevisions: AppliedCloudRevisions = {},\n): NotisSyncState {\n const localSkillMap = toSkillMap(localSkills);\n // The same row selection the writer used, so the state describes the revision that\n // is actually on disk rather than whichever duplicate row the server listed last.\n const skills = Object.fromEntries(\n selectCloudSkillsToApply(pullResponse.skills).map((skill) => {\n const localSkill = localSkillMap.get(skill.name);\n const appliedCloudFolderHash = appliedRevisions[skill.name];\n return [\n skill.name,\n {\n cloudId: skill.id,\n folderHash: localSkill?.folderHash || skill.skill_folder_hash || \"\",\n agentTargets: normalizeAgentTargets(skill.agent_targets),\n verifiedAgentLinks: skill.status === \"active\" ? verifiedAgentLinks[skill.name] ?? {} : {},\n cloudUpdatedAt: skill.updated_at,\n ...(!failedContentNames.has(skill.name) && !skill.skill_source_url\n ? { cloudContentHash: cloudContentHash(skill) } : {}),\n ...(appliedCloudFolderHash !== undefined ? { appliedCloudFolderHash } : {}),\n syncedAt: lastSyncedAt || new Date().toISOString(),\n },\n ];\n }),\n );\n\n return {\n version: 1,\n lastSyncedAt,\n skills,\n };\n}\n\nfunction buildLocalSymlinkCandidates(\n pullResponse: SyncPullResponse,\n localSkills: LocalSkill[],\n previousState: NotisSyncState,\n): SyncPullResponse[\"skills\"] {\n const cloudSkillNames = new Set(\n pullResponse.skills.map((skill) => skill.name),\n );\n const localOnlySkills = localSkills\n .filter((skill) => !cloudSkillNames.has(skill.name))\n .map((skill) => {\n const previous = previousState.skills[skill.name];\n return {\n id: previous?.cloudId || `local-${skill.name}`,\n name: skill.name,\n description: skill.description || null,\n skill_md: skill.skillMd,\n agent_targets: previous?.agentTargets,\n skill_folder_hash: skill.folderHash,\n source: \"local\",\n status: \"active\",\n };\n });\n\n return [...pullResponse.skills, ...localOnlySkills];\n}\n\nfunction isEmptySyncState(state: NotisSyncState): boolean {\n return state.lastSyncedAt === null && Object.keys(state.skills).length === 0;\n}\n\nfunction applyLegacyFirstRunState(\n localSkills: LocalSkill[],\n scopedState: NotisSyncState,\n legacyState: NotisSyncState | null,\n): NotisSyncState {\n if (!isEmptySyncState(scopedState) || !legacyState) {\n return scopedState;\n }\n\n const migratedSkills = Object.fromEntries(\n localSkills.flatMap((skill) => {\n const previous = legacyState.skills[skill.name];\n if (!previous || previous.folderHash !== skill.folderHash) {\n return [];\n }\n return [[skill.name, previous]];\n }),\n );\n\n if (Object.keys(migratedSkills).length === 0) {\n return scopedState;\n }\n\n return {\n version: 1,\n lastSyncedAt: legacyState.lastSyncedAt,\n skills: migratedSkills,\n };\n}\n\nasync function writePulledSkillsToScopedMirror(\n pullResponse: SyncPullResponse,\n localSkills: LocalSkill[],\n previousState: NotisSyncState,\n syncPaths: ReturnType<typeof getSkillSyncPathsForUser>,\n deps: Pick<\n RunSkillSyncDependencies,\n \"downloadSkillBundle\" | \"writeCloudSkillToDisk\"\n >,\n failures: SkillSyncFailure[] = [],\n writtenSkillNames: Set<string> = new Set(),\n): Promise<number> {\n const localSkillMap = toSkillMap(localSkills);\n const warnSkillSync = (message: string, error: unknown): void => {\n console.warn(`[Notis] ${message}`, error);\n };\n\n let downloaded = 0;\n for (const cloudSkill of selectCloudSkillsToApply(pullResponse.skills)) {\n if (!shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {\n continue;\n }\n\n if (\n await writeCloudSkillWithBundleFallback(cloudSkill, {\n downloadSkillBundle: deps.downloadSkillBundle,\n writeCloudSkillToDisk: (skill, bundleBytes) =>\n deps.writeCloudSkillToDisk(skill, bundleBytes, syncPaths),\n onWarning: warnSkillSync,\n })\n ) {\n downloaded += 1;\n writtenSkillNames.add(cloudSkill.name);\n } else {\n failures.push({ name: cloudSkill.name, error: \"Skill content could not be downloaded or written; sync will retry\" });\n }\n }\n\n return downloaded;\n}\n\nfunction assertSkillsPullAuthorized(\n pullResponse: SyncPullResponse,\n): void {\n if (\n pullResponse.entitlement_access?.code === \"entitlement_upgrade_required\"\n && pullResponse.entitlement_access.entitlement === \"skills\"\n ) {\n // Current servers return HTTP 403, so cloud-client rejects before producing\n // a SyncPullResponse. Keep this guard for an older server's successful\n // empty-denial envelope, but fail closed: it is never authorization to\n // delete a user's managed local mirror.\n throw new Error(\n \"Skill sync access was denied; preserving existing local skills.\",\n );\n }\n}\n\nexport async function materializeCloudSkillsForLocalShell(\n serverUrl: string,\n jwt: string,\n dependencies: Partial<Pick<\n RunSkillSyncDependencies,\n | \"downloadSkillBundle\"\n | \"deleteLocalSkill\"\n | \"pullSkills\"\n | \"readSyncState\"\n | \"removeAllSymlinksForSkill\"\n | \"scanLocalSkills\"\n | \"syncSymlinks\"\n | \"writeCloudSkillToDisk\"\n | \"writeSyncState\"\n >> = {},\n options: MaterializeCloudSkillsOptions = {},\n): Promise<MaterializeCloudSkillsResult> {\n const deps = {\n ...DEFAULT_RUN_SKILL_SYNC_DEPS,\n ...dependencies,\n };\n const authUserId = decodeJwtSubject(jwt);\n if (!authUserId) {\n throw new Error(\n \"Cannot materialize skills without a valid authenticated desktop session.\",\n );\n }\n\n const syncPaths = getSkillSyncPathsForUser(options.canonicalUserId?.trim() || authUserId);\n const pullResponse = await deps.pullSkills(serverUrl, jwt);\n assertSkillsPullAuthorized(pullResponse);\n const previousState = await deps.readSyncState(syncPaths);\n\n const localSkills = await deps.scanLocalSkills(syncPaths);\n const failedDownloads: SkillSyncFailure[] = [];\n const writtenSkillNames = new Set<string>();\n const downloaded = await writePulledSkillsToScopedMirror(\n pullResponse,\n localSkills,\n previousState,\n syncPaths,\n deps,\n failedDownloads,\n writtenSkillNames,\n );\n const finalLocalSkills = await deps.scanLocalSkills(syncPaths);\n const lastSyncedAt = pullResponse.last_synced_at || new Date().toISOString();\n\n const relinkSkillNames = new Set(options.relinkSkillNames || []);\n const failures = [...failedDownloads];\n const verifiedLinks: Record<string, Partial<AgentTargets>> = {};\n for (const skill of pullResponse.skills) {\n const previous = previousState.skills[skill.name];\n if (skill.updated_at && previous?.cloudId === skill.id && previous.cloudUpdatedAt === skill.updated_at) {\n verifiedLinks[skill.name] = { ...previous.verifiedAgentLinks };\n }\n }\n if (relinkSkillNames.size > 0) {\n const relinked = await deps.syncSymlinks(\n pullResponse.skills.filter((skill) => relinkSkillNames.has(skill.name)),\n syncPaths.skillsDir,\n { removeUndesired: false },\n );\n failures.push(...(relinked.failures ?? []).filter(\n (failure) => !failedDownloads.some((download) => download.name === failure.name),\n ));\n for (const name of relinkSkillNames) {\n verifiedLinks[name] = relinked.verifiedAgentLinks?.[name] ?? {};\n }\n }\n for (const failure of failedDownloads) delete verifiedLinks[failure.name];\n\n const materializedState = buildSyncState(\n pullResponse, finalLocalSkills, lastSyncedAt, verifiedLinks,\n new Set(failedDownloads.map(item => item.name)),\n collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames),\n );\n // Pull-only refresh is not an upload acknowledgement. Keep content baselines\n // unless we actually wrote cloud content, and retain cloud-missing entries so\n // the next two-way sync can delete them instead of uploading them as new.\n for (const [name, entry] of Object.entries(materializedState.skills)) {\n if (writtenSkillNames.has(name)) continue;\n const previous = previousState.skills[name];\n if (previous) {\n entry.folderHash = previous.folderHash;\n entry.cloudContentHash = previous.cloudContentHash;\n } else {\n delete materializedState.skills[name];\n }\n }\n materializedState.skills = { ...previousState.skills, ...materializedState.skills };\n await deps.writeSyncState(materializedState, syncPaths);\n\n return {\n materializedSkillNames: pullResponse.skills\n .filter((skill) => finalLocalSkills.some((local) => local.name === skill.name)\n && !failedDownloads.some((failure) => failure.name === skill.name))\n .map((skill) => skill.name),\n pulled: pullResponse.skills.length,\n downloaded,\n deleted: 0,\n removed: 0,\n lastSyncedAt,\n ...(failures.length ? { failedLinks: failures } : {}),\n };\n}\n\n/**\n * When the user deletes a skill's symlink for a single agent (e.g. `rm ~/.claude/skills/<skill>`),\n * honor that as \"remove this skill from that agent\" by deactivating it in the portal, instead of\n * recreating the symlink on the next sync. The cloud `agent_targets` are mutated in place so the\n * subsequent symlink reconciliation treats the link as undesired. Returns the number of\n * (skill, agent) pairs deactivated.\n */\nasync function deactivateDeletedAgentSkills(\n serverUrl: string,\n jwt: string,\n pullResponse: SyncPullResponse,\n previousState: NotisSyncState,\n scopedState: NotisSyncState,\n skillsDir: string,\n deps: Pick<\n RunSkillSyncDependencies,\n \"detectDeletedAgentSymlinks\" | \"updateAgentTargets\" | \"pullSkills\"\n >,\n failures: SkillSyncFailure[],\n): Promise<number> {\n // First sync (incl. legacy migration) has no reliable \"we created this link\" signal, so we\n // cannot tell a user deletion apart from a never-created link \u2014 skip detection entirely.\n if (isEmptySyncState(scopedState)) {\n return 0;\n }\n\n const deletions = await deps.detectDeletedAgentSymlinks(\n pullResponse.skills,\n previousState,\n skillsDir,\n );\n if (deletions.length === 0) {\n return 0;\n }\n\n const agentsBySkill = new Map<string, { skillName: string; agents: Set<DeletedAgentSymlink[\"agent\"]> }>();\n for (const deletion of deletions) {\n const entry = agentsBySkill.get(deletion.skillId) ?? {\n skillName: deletion.skillName,\n agents: new Set<DeletedAgentSymlink[\"agent\"]>(),\n };\n entry.agents.add(deletion.agent);\n agentsBySkill.set(deletion.skillId, entry);\n }\n\n const fresh = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));\n assertSkillsPullAuthorized(fresh);\n Object.assign(pullResponse, fresh);\n let needsRefresh = false;\n let deactivated = 0;\n for (const [skillId, { skillName, agents }] of agentsBySkill) {\n const skill = pullResponse.skills.find((item) => item.id === skillId);\n const previous = previousState.skills[skillName];\n if (!skill?.updated_at || previous?.cloudUpdatedAt !== skill.updated_at) continue;\n const patch = Object.fromEntries([...agents].map((agent) => [agent, false]));\n try {\n const saved = await deps.updateAgentTargets(serverUrl, jwt, skillId, patch, skill.updated_at);\n if (saved.success !== true || !saved.updated_at?.trim()\n || saved.updated_at === skill.updated_at\n || !['notis', 'claude_code', 'cursor', 'codex'].every((agent) =>\n typeof saved.agent_targets?.[agent as keyof AgentTargets] === 'boolean')\n || ![...agents].every((agent) => saved.agent_targets[agent] === false)) {\n throw new Error('Assignment update did not return a verified saved revision');\n }\n skill.agent_targets = saved.agent_targets;\n skill.updated_at = saved.updated_at;\n deactivated += agents.size;\n } catch (error) {\n needsRefresh = true;\n failures.push({ name: skillName, error: 'Could not save the local agent removal; refreshed saved assignments' });\n console.warn(`[skill-sync] Assignment changed or could not be saved for \"${skillName}\"; refreshing before reconciliation.`, error);\n }\n }\n if (needsRefresh) {\n const refreshed = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));\n assertSkillsPullAuthorized(refreshed);\n Object.assign(pullResponse, refreshed);\n }\n return deactivated;\n}\n\nexport async function runSkillSync(\n serverUrl: string,\n jwt: string,\n dependencies: Partial<RunSkillSyncDependencies> = {},\n options: RunSkillSyncOptions = {},\n): Promise<RunSkillSyncResult> {\n const deps = {\n ...DEFAULT_RUN_SKILL_SYNC_DEPS,\n ...dependencies,\n };\n const syncSettings: SyncSettings = await deps.fetchSyncSettings(\n serverUrl,\n jwt,\n );\n // Supabase Desktop sessions use the auth-user id as JWT `sub`, while CLI\n // OAuth credentials use the canonical Notis `users.user_id`. Trust the\n // authenticated server response so both transports share one local mirror.\n // The token fallback keeps the CLI compatible with an older server during\n // rollout, where sync-settings did not yet return user_id.\n const syncUserId = syncSettings.user_id?.trim() || decodeJwtSubject(jwt);\n if (!syncUserId) {\n throw new Error(\n \"Cannot sync skills without a server-verified account identity.\",\n );\n }\n const syncPaths = getSkillSyncPathsForUser(syncUserId);\n const foreignLinksRemoved = await deps.removeForeignAccountSymlinks(\n syncPaths.skillsDir,\n );\n if (options.honorSyncEnabled !== false && !syncSettings.sync_enabled) {\n return {\n syncEnabled: false,\n pushed: 0,\n pulled: 0,\n downloaded: 0,\n deleted: 0,\n deactivated: 0,\n linked: 0,\n removed: foreignLinksRemoved,\n skipped: 0,\n lastSyncedAt: syncSettings.last_synced_at,\n failedPushes: [],\n };\n }\n\n let pullResponse = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));\n assertSkillsPullAuthorized(pullResponse);\n\n const cloudCuratedSkillNames = new Set(\n pullResponse.skills\n .filter((skill) => skill.source === \"curated\")\n .map((skill) => skill.name),\n );\n const protectedSkillNames = new Set([...cloudCuratedSkillNames, ...BASE_SKILL_NAMES]);\n const scopedState = withoutBaseSkillState(await deps.readSyncState(syncPaths));\n const assignmentFailures: SkillSyncFailure[] = [];\n const deactivated = syncSettings.agent_targets_conditional_updates === true\n ? await deactivateDeletedAgentSkills(\n serverUrl, jwt, pullResponse, scopedState, scopedState, syncPaths.skillsDir, deps, assignmentFailures,\n )\n : 0;\n const authUserId = decodeJwtSubject(jwt);\n let previousAuthState: NotisSyncState | null = null;\n if (authUserId && authUserId !== syncUserId) {\n const previousAuthPaths = getSkillSyncPathsForUser(authUserId);\n previousAuthState = await deps.readSyncState(previousAuthPaths);\n await deps.gatherTopLevelLocalSkills(syncPaths, {\n sourceRoots: [{ label: \"previous-auth-scope\", root: previousAuthPaths.skillsDir }],\n protectedSkillNames,\n });\n }\n await deps.gatherTopLevelLocalSkills(syncPaths, {\n protectedSkillNames,\n });\n const localSkills = (await deps.scanLocalSkills(syncPaths))\n .filter((skill) => !BASE_SKILL_NAMES.has(skill.name));\n const previousState = withoutBaseSkillState(applyLegacyFirstRunState(\n localSkills,\n scopedState,\n isEmptySyncState(scopedState)\n ? (!previousAuthState || isEmptySyncState(previousAuthState)\n ? await deps.readLegacySyncState(syncPaths)\n : previousAuthState)\n : null,\n ));\n const gatheredSymlinkResult = await deps.syncSymlinks(\n buildLocalSymlinkCandidates(pullResponse, localSkills, previousState),\n syncPaths.skillsDir,\n );\n const pushCandidates = getPushCandidates(\n localSkills,\n previousState,\n cloudCuratedSkillNames,\n new Set(pullResponse.skills.map((skill) => skill.name)),\n new Set(pullResponse.skills.filter((skill) => skill.owner_app_id).map((skill) => skill.name)),\n );\n\n const failedPushes: SkillSyncFailure[] = [];\n if (pushCandidates.length > 0) {\n const pushResult = await deps.pushChangedSkills(serverUrl, jwt, pushCandidates);\n if (Array.isArray(pushResult?.failed) && pushResult.failed.length > 0) {\n failedPushes.push(...pushResult.failed);\n console.warn(\n `[skill-sync] ${pushResult.failed.length} skill(s) were rejected during push: ` +\n pushResult.failed.map((f) => `${f.name} (${f.error})`).join(\"; \"),\n );\n }\n pullResponse = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));\n assertSkillsPullAuthorized(pullResponse);\n }\n\n // Delete phase: remove skills that were previously synced but are no longer in the cloud\n const cloudSkillNames = new Set(pullResponse.skills.map((s) => s.name));\n let deleted = 0;\n for (const skillName of Object.keys(previousState.skills)) {\n if (!cloudSkillNames.has(skillName)) {\n await deps.deleteLocalSkill(skillName, syncPaths);\n await deps.removeAllSymlinksForSkill(skillName, syncPaths.skillsDir);\n deleted += 1;\n }\n }\n\n const failedDownloads: SkillSyncFailure[] = [];\n const writtenSkillNames = new Set<string>();\n const downloaded = await writePulledSkillsToScopedMirror(\n pullResponse,\n localSkills,\n previousState,\n syncPaths,\n deps,\n failedDownloads,\n writtenSkillNames,\n );\n\n const finalLocalSkills = (await deps.scanLocalSkills(syncPaths))\n .filter((skill) => !BASE_SKILL_NAMES.has(skill.name));\n const symlinkResult = await deps.syncSymlinks(\n buildLocalSymlinkCandidates(pullResponse, finalLocalSkills, previousState),\n syncPaths.skillsDir,\n );\n const verifiedLinks = { ...(symlinkResult.verifiedAgentLinks ?? {}) };\n for (const failure of failedDownloads) delete verifiedLinks[failure.name];\n const lastSyncedAt = pullResponse.last_synced_at || new Date().toISOString();\n\n await deps.writeSyncState(\n buildSyncState(\n pullResponse,\n finalLocalSkills,\n lastSyncedAt,\n verifiedLinks,\n new Set(failedDownloads.map(item => item.name)),\n collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames),\n ),\n syncPaths,\n );\n\n return {\n syncEnabled: true,\n pushed: pushCandidates.length,\n pulled: pullResponse.skills.length,\n downloaded,\n deleted,\n deactivated,\n linked: gatheredSymlinkResult.linked + symlinkResult.linked,\n removed: foreignLinksRemoved + gatheredSymlinkResult.removed + symlinkResult.removed,\n skipped: symlinkResult.skipped,\n failedLinks: [...assignmentFailures, ...failedDownloads, ...(symlinkResult.failures ?? []).filter(\n (failure) => !failedDownloads.some((download) => download.name === failure.name),\n )],\n lastSyncedAt,\n failedPushes,\n };\n}\n", "export interface AgentTargets {\n notis: boolean;\n claude_code: boolean;\n cursor: boolean;\n codex: boolean;\n}\n\nexport interface SyncedSkill {\n cloudId: string;\n folderHash: string;\n agentTargets: AgentTargets;\n /** Actual readable managed links, never inferred from desired targets. */\n verifiedAgentLinks?: Partial<AgentTargets>;\n cloudUpdatedAt?: string;\n /** Content accepted on disk; independent of server-specific folder hash formats. */\n cloudContentHash?: string;\n /** `skill_folder_hash` of the cloud revision a successful write actually applied.\n * The server may compute that hash with a different construction than the local\n * folder hash (app-published skills do), so only cloud-to-cloud comparison tells\n * \"the cloud moved\" apart from \"the two sides hash differently\". */\n appliedCloudFolderHash?: string;\n syncedAt: string;\n}\n\nexport interface NotisSyncState {\n version: 1;\n lastSyncedAt: string | null;\n skills: Record<string, SyncedSkill>;\n}\n\nexport interface LocalSkill {\n name: string;\n skillMd: string;\n description: string;\n folderHash: string;\n directoryPath: string;\n sourceUrl?: string;\n bundleFiles?: BundleFile[];\n}\n\nexport interface BundleFile {\n path: string;\n content_b64: string;\n}\n\nexport interface CloudSkill {\n updated_at?: string;\n id: string;\n name: string;\n description: string | null;\n skill_md: string | null;\n agent_targets?: Partial<AgentTargets> | null;\n skill_folder_hash?: string | null;\n skill_source_url?: string | null;\n bundle_files?: BundleFile[] | null;\n bundle_hydration_failed?: boolean | null;\n /** Set when the skill's content is owned by an installed Notis app. Its folder is\n * republished by the app, never by a local push. */\n owner_app_id?: string | null;\n source: string;\n status: string;\n}\n\nexport interface SyncSettings {\n agent_targets_conditional_updates?: boolean;\n /** Server-verified canonical Notis account id used to scope local mirrors. */\n user_id?: string;\n sync_enabled: boolean;\n last_synced_at: string | null;\n}\n\nexport interface EntitlementAccessDetail {\n status: \"error\";\n code: \"entitlement_upgrade_required\";\n upgrade_required: true;\n entitlement: string;\n required_plan: string;\n upgrade_url: string;\n message: string;\n next_action: Record<string, unknown>;\n}\n\nexport interface SyncPullResponse {\n skills: CloudSkill[];\n sync_enabled: boolean;\n last_synced_at: string | null;\n /** Legacy servers could return a successful empty denial. Current servers\n * return HTTP 403; either form must preserve existing local state. */\n entitlement_access?: EntitlementAccessDetail;\n}\n\n/** A skill the server could not sync, with a user-facing reason (e.g. an invalid\n * SKILL.md description rejected by OpenAI). One bad skill no longer fails the batch. */\nexport interface SkillSyncFailure {\n name: string;\n error: string;\n}\n\nexport interface SyncPushResponse {\n skills: unknown[];\n failed?: SkillSyncFailure[];\n}\n\nexport const DEFAULT_AGENT_TARGETS: AgentTargets = {\n notis: true,\n claude_code: true,\n cursor: true,\n codex: true,\n};\n\nexport function normalizeAgentTargets(targets?: Partial<AgentTargets> | null): AgentTargets {\n return {\n notis: Boolean(targets?.notis ?? DEFAULT_AGENT_TARGETS.notis),\n claude_code: Boolean(targets?.claude_code ?? DEFAULT_AGENT_TARGETS.claude_code),\n cursor: Boolean(targets?.cursor ?? DEFAULT_AGENT_TARGETS.cursor),\n codex: Boolean(targets?.codex ?? DEFAULT_AGENT_TARGETS.codex),\n };\n}\n", "import { createHash } from \"crypto\";\nimport { execFile } from \"child_process\";\nimport type { Dirent } from \"fs\";\nimport { promises as fs } from \"fs\";\nimport os from \"os\";\nimport path from \"path\";\nimport { promisify } from \"util\";\n\nimport type { CloudSkill, LocalSkill, NotisSyncState } from \"./types\";\n\nconst HOME_DIR = os.homedir();\nconst execFileAsync = promisify(execFile);\n\nexport const AGENTS_DIR = path.join(HOME_DIR, \".agents\");\nexport const LEGACY_AGENTS_SKILLS_DIR = path.join(AGENTS_DIR, \"skills\");\nexport const NOTIS_SKILL_SYNC_ROOT = path.join(HOME_DIR, \".notis\", \"skills\");\nexport const LEGACY_NOTIS_SYNC_STATE_PATH = path.join(\n AGENTS_DIR,\n \".notis-sync.json\",\n);\nexport const AGENTS_SKILLS_DIR = LEGACY_AGENTS_SKILLS_DIR;\nexport const NOTIS_SYNC_STATE_PATH = LEGACY_NOTIS_SYNC_STATE_PATH;\nconst SKILL_LOCK_PATH = path.join(AGENTS_DIR, \".skill-lock.json\");\n\nconst DEFAULT_SYNC_STATE: NotisSyncState = {\n version: 1,\n lastSyncedAt: null,\n skills: {},\n};\nconst EXCLUDED_TOP_LEVEL_ROOT_NAMES = new Set([\n \"backup\",\n \"backups\",\n \"builtin\",\n \"builtins\",\n \"cache\",\n \"caches\",\n \"marketplace\",\n \"marketplaces\",\n \"plugin\",\n \"plugins\",\n \"temp\",\n \"tmp\",\n \"worktree\",\n \"worktrees\",\n]);\n\nexport interface SkillSyncPaths {\n agentsDir: string;\n syncRoot: string;\n legacySkillsDir: string;\n legacyScopedSkillsDir: string;\n legacyScopedSyncStatePath: string;\n skillsDir: string;\n syncStatePath: string;\n skillLockPath: string;\n gatherMetadataPath: string;\n}\n\nconst DEFAULT_SYNC_PATHS: SkillSyncPaths = {\n agentsDir: AGENTS_DIR,\n syncRoot: NOTIS_SKILL_SYNC_ROOT,\n legacySkillsDir: LEGACY_AGENTS_SKILLS_DIR,\n legacyScopedSkillsDir: LEGACY_AGENTS_SKILLS_DIR,\n legacyScopedSyncStatePath: LEGACY_NOTIS_SYNC_STATE_PATH,\n skillsDir: LEGACY_AGENTS_SKILLS_DIR,\n syncStatePath: LEGACY_NOTIS_SYNC_STATE_PATH,\n skillLockPath: SKILL_LOCK_PATH,\n gatherMetadataPath: path.join(NOTIS_SKILL_SYNC_ROOT, \"skill-gather-metadata.json\"),\n};\n\nfunction getDefaultSyncRootForAgentsDir(resolvedAgentsDir: string): string {\n if (resolvedAgentsDir === path.resolve(AGENTS_DIR)) {\n return NOTIS_SKILL_SYNC_ROOT;\n }\n return path.join(resolvedAgentsDir, \".notis\", \"skills\");\n}\n\nfunction isResolvedChildPath(baseDir: string, candidatePath: string): boolean {\n return candidatePath.startsWith(`${baseDir}${path.sep}`);\n}\n\nfunction sanitizePathSegment(value: string): string {\n const raw = value.trim();\n const sanitized = raw\n .replace(/[^A-Za-z0-9_-]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n if (!sanitized) {\n throw new Error(\"Invalid sync user id\");\n }\n if (sanitized === raw) {\n return sanitized;\n }\n return `${sanitized}-${createHash(\"sha256\").update(raw).digest(\"hex\").slice(0, 12)}`;\n}\n\nexport function getSkillSyncPathsForUser(\n authUserId: string,\n agentsDir: string = AGENTS_DIR,\n): SkillSyncPaths {\n const safeUserId = sanitizePathSegment(authUserId);\n const resolvedAgentsDir = path.resolve(agentsDir);\n const syncRoot = getDefaultSyncRootForAgentsDir(resolvedAgentsDir);\n const userRoot = path.join(syncRoot, \"users\", safeUserId);\n const legacyUserRoot = path.join(resolvedAgentsDir, \"notis\", \"users\", safeUserId);\n\n return {\n agentsDir: resolvedAgentsDir,\n syncRoot,\n legacySkillsDir: path.join(resolvedAgentsDir, \"skills\"),\n legacyScopedSkillsDir: path.join(legacyUserRoot, \"skills\"),\n legacyScopedSyncStatePath: path.join(legacyUserRoot, \".notis-sync.json\"),\n skillsDir: path.join(userRoot, \"skills\"),\n syncStatePath: path.join(userRoot, \".notis-sync.json\"),\n skillLockPath: path.join(resolvedAgentsDir, \".skill-lock.json\"),\n gatherMetadataPath: path.join(userRoot, \".notis-gathered-skills.json\"),\n };\n}\n\nfunction assertRelativeBundlePath(relativePath: string): string {\n const normalized = relativePath.replace(/\\\\/g, \"/\").replace(/^\\/+|\\/+$/g, \"\");\n if (\n !normalized ||\n normalized.startsWith(\"../\") ||\n `/${normalized}/`.includes(\"/../\")\n ) {\n throw new Error(`Invalid bundle file path: ${relativePath}`);\n }\n return normalized;\n}\n\nfunction stripWrappingQuotes(value: string): string {\n return value.replace(/^['\"]|['\"]$/g, \"\").trim();\n}\n\nfunction parseFrontMatter(skillMd: string): { description: string } {\n const match = skillMd.match(/^---\\s*\\n([\\s\\S]*?)\\n---\\s*(?:\\n|$)/);\n if (!match) {\n return { description: \"\" };\n }\n\n let description = \"\";\n for (const line of match[1].split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue;\n }\n const descriptionMatch = trimmed.match(/^description\\s*:\\s*(.+)$/i);\n if (descriptionMatch) {\n description = stripWrappingQuotes(descriptionMatch[1]);\n break;\n }\n }\n\n return { description };\n}\n\nasync function readJsonFile<T>(filePath: string): Promise<T | null> {\n try {\n const raw = await fs.readFile(filePath, \"utf8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\nasync function listFilesRecursive(dirPath: string): Promise<string[]> {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n const nested = await Promise.all(\n entries.map(async (entry) => {\n if (entry.name === \".DS_Store\") {\n return [];\n }\n\n const fullPath = path.join(dirPath, entry.name);\n if (entry.isDirectory()) {\n return listFilesRecursive(fullPath);\n }\n\n if (entry.isFile()) {\n return [fullPath];\n }\n\n return [];\n }),\n );\n\n return nested.flat().sort();\n}\n\nasync function computeFolderHash(dirPath: string): Promise<string> {\n const hash = createHash(\"sha256\");\n const filePaths = await listFilesRecursive(dirPath);\n\n for (const filePath of filePaths) {\n const relativePath = path.relative(dirPath, filePath);\n hash.update(relativePath);\n hash.update(\"\\0\");\n hash.update(await fs.readFile(filePath));\n hash.update(\"\\0\");\n }\n\n return hash.digest(\"hex\");\n}\n\nfunction resolveSkillBundleFilePath(\n skillDir: string,\n relativePath: string,\n): string {\n const normalizedPath = assertRelativeBundlePath(relativePath);\n const resolvedSkillDir = path.resolve(skillDir);\n const candidatePath = path.resolve(resolvedSkillDir, normalizedPath);\n if (\n candidatePath === resolvedSkillDir ||\n !isResolvedChildPath(resolvedSkillDir, candidatePath)\n ) {\n throw new Error(\n `Bundle file resolves outside the expected directory: ${relativePath}`,\n );\n }\n return candidatePath;\n}\n\nasync function readSkillMdIfValid(skillDir: string): Promise<boolean> {\n try {\n await fs.readFile(path.join(skillDir, \"SKILL.md\"), \"utf8\");\n return true;\n } catch {\n return false;\n }\n}\n\nasync function moveDirectory(\n sourceDir: string,\n destinationDir: string,\n): Promise<void> {\n await fs.mkdir(path.dirname(destinationDir), { recursive: true });\n try {\n await fs.rename(sourceDir, destinationDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EXDEV\") {\n throw error;\n }\n await fs.cp(sourceDir, destinationDir, {\n recursive: true,\n errorOnExist: true,\n force: false,\n });\n await fs.rm(sourceDir, { recursive: true, force: true });\n }\n}\n\nasync function createDirectorySymlink(\n targetDir: string,\n linkPath: string,\n): Promise<void> {\n await fs.mkdir(path.dirname(linkPath), { recursive: true });\n await fs.symlink(path.relative(path.dirname(linkPath), targetDir), linkPath, \"dir\");\n}\n\nfunction backupRootFor(paths: SkillSyncPaths, timestamp: string): string {\n return path.join(paths.syncRoot, \"skill-dedupe-backups\", timestamp);\n}\n\nfunction relativeBackupPath(label: string, skillName: string): string {\n return path.join(\n label.replace(/[^A-Za-z0-9_.-]+/g, \"_\"),\n safeName(skillName),\n );\n}\n\nfunction isExcludedTopLevelSkillEntry(entryName: string): boolean {\n return (\n entryName.startsWith(\".\") ||\n isTransientSkillDirectoryName(entryName) ||\n EXCLUDED_TOP_LEVEL_ROOT_NAMES.has(entryName.toLowerCase())\n );\n}\n\nfunction isTransientSkillDirectoryName(entryName: string): boolean {\n return /\\.(?:backup|staging)-/.test(entryName);\n}\n\ninterface TopLevelSkillSource {\n label: string;\n root: string;\n priority: number;\n}\n\ninterface TopLevelSkillCandidate {\n name: string;\n root: string;\n label: string;\n path: string;\n resolvedPath: string;\n priority: number;\n isScoped: boolean;\n isSymlink: boolean;\n}\n\nexport interface GatherTopLevelLocalSkillsOptions {\n sourceRoots?: Array<{ label: string; root: string }>;\n protectedSkillNames?: ReadonlySet<string>;\n timestamp?: string;\n}\n\nexport interface GatherTopLevelLocalSkillsResult {\n gathered: number;\n backedUp: number;\n skipped: number;\n metadataPath: string;\n}\n\nfunction defaultTopLevelSkillSources(\n paths: SkillSyncPaths,\n): TopLevelSkillSource[] {\n const sources: TopLevelSkillSource[] = [];\n if (path.resolve(paths.legacyScopedSkillsDir) !== path.resolve(paths.skillsDir)) {\n sources.push({\n label: \"notis-legacy-scoped\",\n root: paths.legacyScopedSkillsDir,\n priority: 1,\n });\n }\n\n sources.push(\n { label: \"agents\", root: paths.legacySkillsDir, priority: 2 },\n {\n label: \"codex\",\n root: path.join(HOME_DIR, \".codex\", \"skills\"),\n priority: 3,\n },\n {\n label: \"cursor\",\n root: path.join(HOME_DIR, \".cursor\", \"skills\"),\n priority: 4,\n },\n {\n label: \"claude\",\n root: path.join(HOME_DIR, \".claude\", \"skills\"),\n priority: 5,\n },\n );\n\n return sources;\n}\n\nfunction isManagedTopLevelSymlinkTarget(\n resolvedPath: string,\n paths: SkillSyncPaths,\n): boolean {\n const managedRoots = [\n path.resolve(paths.skillsDir),\n path.resolve(paths.legacySkillsDir),\n path.resolve(paths.legacyScopedSkillsDir),\n path.join(path.resolve(paths.syncRoot), \"base\"),\n path.join(path.resolve(paths.syncRoot), \"users\"),\n path.join(path.resolve(paths.agentsDir), \"notis\", \"users\"),\n ];\n return managedRoots.some(\n (root) =>\n resolvedPath === root || resolvedPath.startsWith(`${root}${path.sep}`),\n );\n}\n\nasync function listTopLevelSkillCandidates(\n paths: SkillSyncPaths,\n options: GatherTopLevelLocalSkillsOptions,\n): Promise<TopLevelSkillCandidate[]> {\n const sourceRoots = options.sourceRoots\n ? options.sourceRoots.map((source, index) => ({\n ...source,\n priority: index + 1,\n }))\n : defaultTopLevelSkillSources(paths);\n const candidates: TopLevelSkillCandidate[] = [];\n\n try {\n const scopedEntries = await fs.readdir(paths.skillsDir, {\n withFileTypes: true,\n });\n for (const entry of scopedEntries) {\n if (\n (!entry.isDirectory() && !entry.isSymbolicLink()) ||\n entry.name.startsWith(\".\") ||\n isTransientSkillDirectoryName(entry.name)\n ) {\n continue;\n }\n const skillDir = path.join(paths.skillsDir, entry.name);\n let resolvedPath = path.resolve(skillDir);\n if (entry.isSymbolicLink()) {\n try {\n resolvedPath = path.resolve(path.dirname(skillDir), await fs.readlink(skillDir));\n } catch {\n continue;\n }\n }\n if (await readSkillMdIfValid(skillDir)) {\n candidates.push({\n name: safeName(entry.name, paths.skillsDir),\n root: paths.skillsDir,\n label: \"notis-managed\",\n path: skillDir,\n resolvedPath,\n priority: 0,\n isScoped: true,\n isSymlink: entry.isSymbolicLink(),\n });\n }\n }\n } catch {\n // Missing scoped mirror is expected before the first sync.\n }\n\n for (const source of sourceRoots) {\n let entries: Dirent[];\n try {\n entries = await fs.readdir(source.root, { withFileTypes: true });\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n if (\n isExcludedTopLevelSkillEntry(entry.name) ||\n (!entry.isDirectory() && !entry.isSymbolicLink())\n ) {\n continue;\n }\n\n const candidatePath = path.join(source.root, entry.name);\n let resolvedPath: string;\n try {\n resolvedPath = entry.isSymbolicLink()\n ? path.resolve(\n path.dirname(candidatePath),\n await fs.readlink(candidatePath),\n )\n : path.resolve(candidatePath);\n } catch {\n continue;\n }\n\n if (\n entry.isSymbolicLink() &&\n isManagedTopLevelSymlinkTarget(resolvedPath, paths)\n ) {\n continue;\n }\n\n if (!(await readSkillMdIfValid(resolvedPath))) {\n continue;\n }\n\n candidates.push({\n name: safeName(entry.name, paths.skillsDir),\n root: source.root,\n label: source.label,\n path: candidatePath,\n resolvedPath,\n priority: source.priority,\n isScoped: false,\n isSymlink: entry.isSymbolicLink(),\n });\n }\n }\n\n return candidates.sort(\n (a, b) => a.priority - b.priority || a.name.localeCompare(b.name),\n );\n}\n\nexport async function gatherTopLevelLocalSkills(\n paths: SkillSyncPaths,\n options: GatherTopLevelLocalSkillsOptions = {},\n): Promise<GatherTopLevelLocalSkillsResult> {\n await ensureCanonicalSkillsDir(paths);\n\n const protectedSkillNames = options.protectedSkillNames || new Set<string>();\n const timestamp =\n options.timestamp || new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupRoot = backupRootFor(paths, timestamp);\n const candidates = await listTopLevelSkillCandidates(paths, options);\n const byName = new Map<string, TopLevelSkillCandidate[]>();\n\n for (const candidate of candidates) {\n const existing = byName.get(candidate.name) || [];\n existing.push(candidate);\n byName.set(candidate.name, existing);\n }\n\n let gathered = 0;\n let backedUp = 0;\n let skipped = 0;\n const metadata: {\n version: 1;\n gatheredAt: string;\n skills: Record<\n string,\n {\n canonicalPath: string | null;\n skippedSources: string[];\n protectedFromLocalGather?: boolean;\n }\n >;\n } = {\n version: 1,\n gatheredAt: new Date().toISOString(),\n skills: {},\n };\n\n for (const [skillName, skillCandidates] of byName) {\n const protectedFromLocalGather = protectedSkillNames.has(skillName);\n const canonical = protectedFromLocalGather\n ? skillCandidates.find((candidate) => candidate.isScoped) || null\n : skillCandidates[0];\n const destinationName = safeName(skillName, paths.skillsDir);\n const destinationDir = path.join(paths.skillsDir, destinationName);\n const skippedSources: string[] = [];\n\n if (!canonical) {\n for (const candidate of skillCandidates) {\n if (candidate.isSymlink) {\n skipped += 1;\n skippedSources.push(candidate.path);\n continue;\n }\n const backupDir = path.join(\n backupRoot,\n relativeBackupPath(candidate.label, skillName),\n );\n try {\n await moveDirectory(candidate.path, backupDir);\n backedUp += 1;\n skippedSources.push(candidate.path);\n } catch {\n skipped += 1;\n }\n }\n metadata.skills[skillName] = {\n canonicalPath: null,\n skippedSources,\n protectedFromLocalGather: true,\n };\n continue;\n }\n\n if (!canonical.isScoped && !(await pathExists(destinationDir))) {\n try {\n if (canonical.isSymlink) {\n await createDirectorySymlink(canonical.resolvedPath, destinationDir);\n } else {\n await moveDirectory(canonical.path, destinationDir);\n }\n gathered += 1;\n } catch {\n skipped += 1;\n }\n }\n\n for (const candidate of skillCandidates) {\n if (candidate === canonical || candidate.isScoped) {\n continue;\n }\n if (candidate.isSymlink) {\n skippedSources.push(candidate.path);\n continue;\n }\n const backupDir = path.join(\n backupRoot,\n relativeBackupPath(candidate.label, skillName),\n );\n try {\n await moveDirectory(candidate.path, backupDir);\n backedUp += 1;\n skippedSources.push(candidate.path);\n } catch {\n skipped += 1;\n }\n }\n\n metadata.skills[skillName] = {\n canonicalPath: (await pathExists(destinationDir))\n ? destinationDir\n : canonical.path,\n skippedSources,\n ...(protectedFromLocalGather ? { protectedFromLocalGather: true } : {}),\n };\n }\n\n await fs.mkdir(path.dirname(paths.gatherMetadataPath), { recursive: true });\n await fs.writeFile(\n paths.gatherMetadataPath,\n `${JSON.stringify(metadata, null, 2)}\\n`,\n \"utf8\",\n );\n\n return {\n gathered,\n backedUp,\n skipped,\n metadataPath: paths.gatherMetadataPath,\n };\n}\n\nasync function readSkillLock(\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<Record<string, string>> {\n type SkillLock = {\n skills?: Record<string, { sourceUrl?: string }>;\n };\n\n const lockData = await readJsonFile<SkillLock>(paths.skillLockPath);\n const sourceUrls: Record<string, string> = {};\n\n for (const [skillName, entry] of Object.entries(lockData?.skills || {})) {\n if (typeof entry?.sourceUrl === \"string\" && entry.sourceUrl.trim()) {\n sourceUrls[skillName] = entry.sourceUrl.trim();\n }\n }\n\n return sourceUrls;\n}\n\nexport async function ensureCanonicalSkillsDir(\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<void> {\n await fs.mkdir(paths.skillsDir, { recursive: true });\n}\n\nexport async function initializeScopedSkillsFromLegacy(\n paths: SkillSyncPaths,\n): Promise<number> {\n const usingLegacyPath =\n path.resolve(paths.skillsDir) === path.resolve(paths.legacySkillsDir);\n if (usingLegacyPath || (await pathExists(paths.syncStatePath))) {\n await ensureCanonicalSkillsDir(paths);\n return 0;\n }\n\n await ensureCanonicalSkillsDir(paths);\n let entries: Dirent[];\n try {\n entries = await fs.readdir(paths.legacySkillsDir, { withFileTypes: true });\n } catch {\n return 0;\n }\n\n let copied = 0;\n for (const entry of entries) {\n if (!entry.isDirectory()) {\n continue;\n }\n\n const legacySkillDir = path.join(paths.legacySkillsDir, entry.name);\n const targetSkillName = safeName(entry.name, paths.skillsDir);\n const scopedSkillDir = path.join(paths.skillsDir, targetSkillName);\n try {\n await fs.readFile(path.join(legacySkillDir, \"SKILL.md\"), \"utf8\");\n if (await pathExists(scopedSkillDir)) {\n continue;\n }\n await fs.cp(legacySkillDir, scopedSkillDir, {\n recursive: true,\n errorOnExist: true,\n force: false,\n });\n copied += 1;\n } catch {\n // Ignore invalid legacy skill folders and existing scoped copies.\n }\n }\n\n return copied;\n}\n\nexport async function scanLocalSkills(\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<LocalSkill[]> {\n await ensureCanonicalSkillsDir(paths);\n\n const sourceUrls = await readSkillLock(paths);\n const entries = await fs.readdir(paths.skillsDir, { withFileTypes: true });\n const skills: Array<LocalSkill | null> = await Promise.all(\n entries.map(async (entry) => {\n if (\n (!entry.isDirectory() && !entry.isSymbolicLink()) ||\n isTransientSkillDirectoryName(entry.name)\n ) {\n return null;\n }\n\n const skillDir = path.join(paths.skillsDir, entry.name);\n const skillMdPath = path.join(skillDir, \"SKILL.md\");\n\n try {\n const skillMd = await fs.readFile(skillMdPath, \"utf8\");\n const { description } = parseFrontMatter(skillMd);\n const folderHash = await computeFolderHash(skillDir);\n const skill: LocalSkill = {\n name: entry.name,\n skillMd,\n description,\n folderHash,\n directoryPath: skillDir,\n };\n if (sourceUrls[entry.name]) {\n skill.sourceUrl = sourceUrls[entry.name];\n }\n return skill;\n } catch {\n return null;\n }\n }),\n );\n\n return skills\n .filter((skill): skill is LocalSkill => skill !== null)\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\nfunction normalizeSyncState(\n state: NotisSyncState | null,\n): NotisSyncState | null {\n if (!state || state.version !== 1 || typeof state.skills !== \"object\") {\n return null;\n }\n const normalizeStoredAgentTargets = (\n targets: Partial<NotisSyncState[\"skills\"][string][\"agentTargets\"]> | null | undefined,\n ): NotisSyncState[\"skills\"][string][\"agentTargets\"] => ({\n notis: Boolean(targets?.notis ?? true),\n claude_code: Boolean(targets?.claude_code ?? true),\n cursor: Boolean(targets?.cursor ?? true),\n codex: Boolean(targets?.codex ?? true),\n });\n\n return {\n version: 1,\n lastSyncedAt:\n typeof state.lastSyncedAt === \"string\" ? state.lastSyncedAt : null,\n skills: Object.fromEntries(\n Object.entries(state.skills || {}).map(([skillName, skillState]) => [\n skillName,\n {\n ...skillState,\n agentTargets: normalizeStoredAgentTargets(skillState.agentTargets),\n },\n ]),\n ),\n };\n}\n\nexport async function readSyncState(\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<NotisSyncState> {\n const scopedState = normalizeSyncState(\n await readJsonFile<NotisSyncState>(paths.syncStatePath),\n );\n if (scopedState) {\n return scopedState;\n }\n\n return DEFAULT_SYNC_STATE;\n}\n\nexport async function readLegacySyncState(\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<NotisSyncState | null> {\n const legacyStatePaths = [\n paths.legacyScopedSyncStatePath,\n path.resolve(paths.syncStatePath) === path.resolve(LEGACY_NOTIS_SYNC_STATE_PATH)\n ? paths.syncStatePath\n : path.join(paths.agentsDir, \".notis-sync.json\"),\n ];\n\n for (const legacyStatePath of legacyStatePaths) {\n const state = normalizeSyncState(\n await readJsonFile<NotisSyncState>(legacyStatePath),\n );\n if (state) {\n return state;\n }\n }\n\n return null;\n}\n\nexport async function writeSyncState(\n state: NotisSyncState,\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<void> {\n await fs.mkdir(path.dirname(paths.syncStatePath), { recursive: true });\n await fs.writeFile(\n paths.syncStatePath,\n `${JSON.stringify(state, null, 2)}\\n`,\n \"utf8\",\n );\n}\n\nexport function safeName(\n name: string,\n baseDir: string = AGENTS_SKILLS_DIR,\n): string {\n const sanitizedName = name\n .trim()\n .replace(/[\\\\/]+/g, \"\")\n .replace(/\\.\\./g, \"\");\n if (!sanitizedName) {\n throw new Error(\"Invalid skill name\");\n }\n\n const resolvedBaseDir = path.resolve(baseDir);\n const resolvedPath = path.resolve(resolvedBaseDir, sanitizedName);\n if (!isResolvedChildPath(resolvedBaseDir, resolvedPath)) {\n throw new Error(\"Skill name resolves outside the expected directory\");\n }\n\n return sanitizedName;\n}\n\nexport async function deleteLocalSkill(\n skillName: string,\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<boolean> {\n const skillDir = path.join(\n paths.skillsDir,\n safeName(skillName, paths.skillsDir),\n );\n try {\n await fs.rm(skillDir, { recursive: true, force: true });\n return true;\n } catch {\n return false;\n }\n}\n\nasync function createZipFromDirectory(directoryPath: string): Promise<string> {\n const zipPath = path.join(\n os.tmpdir(),\n `notis-skill-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`,\n );\n const parentDir = path.dirname(directoryPath);\n const directoryName = path.basename(directoryPath);\n\n if (process.platform === \"win32\") {\n await execFileAsync(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n `Compress-Archive -Path '${directoryPath.replace(/'/g, \"''\")}' -DestinationPath '${zipPath.replace(/'/g, \"''\")}' -Force`,\n ]);\n return zipPath;\n }\n\n await execFileAsync(\"zip\", [\"-qry\", zipPath, directoryName], {\n cwd: parentDir,\n });\n return zipPath;\n}\n\nasync function extractZipToDirectory(\n zipPath: string,\n destinationDir: string,\n): Promise<void> {\n const extractRoot = await fs.mkdtemp(\n path.join(os.tmpdir(), \"notis-skill-extract-\"),\n );\n\n try {\n if (process.platform === \"win32\") {\n await execFileAsync(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n `Expand-Archive -Path '${zipPath.replace(/'/g, \"''\")}' -DestinationPath '${extractRoot.replace(/'/g, \"''\")}' -Force`,\n ]);\n } else {\n await execFileAsync(\"unzip\", [\"-qq\", zipPath, \"-d\", extractRoot]);\n }\n\n const extractedEntries = await fs.readdir(extractRoot, {\n withFileTypes: true,\n });\n const extractedDirectory = extractedEntries.find((entry) =>\n entry.isDirectory(),\n );\n const sourceDir = extractedDirectory\n ? path.join(extractRoot, extractedDirectory.name)\n : extractRoot;\n\n await fs.rm(destinationDir, { recursive: true, force: true });\n await fs.mkdir(path.dirname(destinationDir), { recursive: true });\n await fs.cp(sourceDir, destinationDir, { recursive: true });\n } finally {\n await fs.rm(extractRoot, { recursive: true, force: true });\n }\n}\n\nasync function pathExists(targetPath: string): Promise<boolean> {\n try {\n await fs.access(targetPath);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function replaceSkillDirectoryAtomically(\n skillDir: string,\n populateDir: (stagingDir: string) => Promise<void>,\n): Promise<void> {\n const parentDir = path.dirname(skillDir);\n const skillName = path.basename(skillDir);\n await fs.mkdir(parentDir, { recursive: true });\n\n const stagingDir = await fs.mkdtemp(\n path.join(parentDir, `${skillName}.staging-`),\n );\n const backupDir = path.join(\n parentDir,\n `${skillName}.backup-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n );\n\n let movedExisting = false;\n let promotedStaging = false;\n let cleanupError: Error | null = null;\n\n try {\n await populateDir(stagingDir);\n\n if (await pathExists(skillDir)) {\n await fs.rename(skillDir, backupDir);\n movedExisting = true;\n }\n\n await fs.rename(stagingDir, skillDir);\n promotedStaging = true;\n } catch (error) {\n if (movedExisting && !promotedStaging && (await pathExists(backupDir))) {\n await fs.rename(backupDir, skillDir);\n }\n throw error;\n } finally {\n if (!promotedStaging && (await pathExists(stagingDir))) {\n await fs.rm(stagingDir, { recursive: true, force: true });\n }\n if ((promotedStaging || !movedExisting) && (await pathExists(backupDir))) {\n try {\n await fs.rm(backupDir, { recursive: true, force: true });\n } catch (error) {\n if (!cleanupError) {\n cleanupError =\n error instanceof Error ? error : new Error(String(error));\n }\n }\n }\n if (cleanupError) {\n console.warn(\n `[Notis] Failed to clean up temporary skill directory backup for \"${skillDir}\"`,\n cleanupError,\n );\n }\n }\n}\n\nfunction bundleFilesIncludeSkillMd(\n bundleFiles: Array<{ path: string }>,\n): boolean {\n return bundleFiles.some((bundleFile) => {\n const basename = path.basename(bundleFile.path).toLowerCase();\n return basename === \"skill.md\" || basename === \"skills.md\";\n });\n}\n\nexport async function createSkillBundleBase64(\n skill: LocalSkill,\n): Promise<string> {\n const zipPath = await createZipFromDirectory(skill.directoryPath);\n try {\n const zipBytes = await fs.readFile(zipPath);\n return zipBytes.toString(\"base64\");\n } finally {\n await fs.rm(zipPath, { force: true });\n }\n}\n\nexport async function writeCloudSkillToDisk(\n skill: CloudSkill,\n bundleBytes?: Buffer,\n paths: SkillSyncPaths = DEFAULT_SYNC_PATHS,\n): Promise<boolean> {\n const skillDir = path.join(\n paths.skillsDir,\n safeName(skill.name, paths.skillsDir),\n );\n\n if (bundleBytes?.length) {\n const bundlePath = path.join(\n os.tmpdir(),\n `notis-skill-download-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`,\n );\n try {\n await fs.writeFile(bundlePath, bundleBytes);\n await extractZipToDirectory(bundlePath, skillDir);\n return true;\n } finally {\n await fs.rm(bundlePath, { force: true });\n }\n }\n\n if (\n !skill.skill_md &&\n (!skill.bundle_files || skill.bundle_files.length === 0)\n ) {\n return false;\n }\n\n if (skill.bundle_files && skill.bundle_files.length > 0) {\n if (!bundleFilesIncludeSkillMd(skill.bundle_files)) {\n throw new Error(\n `Synced bundle for \"${skill.name}\" is missing SKILL.md or SKILLS.md`,\n );\n }\n await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {\n for (const bundleFile of skill.bundle_files || []) {\n const filePath = resolveSkillBundleFilePath(\n stagingDir,\n bundleFile.path,\n );\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(\n filePath,\n Buffer.from(bundleFile.content_b64, \"base64\"),\n );\n }\n });\n return true;\n }\n\n await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {\n await fs.writeFile(\n path.join(stagingDir, \"SKILL.md\"),\n skill.skill_md ?? \"\",\n \"utf8\",\n );\n });\n return true;\n}\n", "import { createSkillBundleBase64 } from './local-scanner';\nimport type { AgentTargets, LocalSkill, SyncPullResponse, SyncPushResponse, SyncSettings } from './types';\n\ntype JsonBody = Record<string, unknown> | undefined;\n\nasync function requestJson<T>(\n url: string,\n jwt: string,\n options: { method?: string; body?: JsonBody } = {},\n): Promise<T> {\n const response = await fetch(url, {\n signal: AbortSignal.timeout(90_000),\n method: options.method || 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${jwt}`,\n },\n body: options.body ? JSON.stringify(options.body) : undefined,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`${options.method || 'POST'} ${url} \u2192 ${response.status}: ${text}`);\n }\n\n return response.json() as Promise<T>;\n}\n\nexport async function fetchSyncSettings(serverUrl: string, jwt: string): Promise<SyncSettings> {\n return requestJson<SyncSettings>(`${serverUrl}/portal_skills/sync-settings`, jwt, {\n body: {},\n });\n}\n\nexport async function pullSkills(serverUrl: string, jwt: string): Promise<SyncPullResponse> {\n return requestJson<SyncPullResponse>(`${serverUrl}/portal_skills/sync-pull`, jwt, {\n body: {},\n });\n}\n\nexport async function pushChangedSkills(\n serverUrl: string,\n jwt: string,\n changedSkills: LocalSkill[],\n): Promise<SyncPushResponse> {\n const payloadSkills = await Promise.all(changedSkills.map(async (skill) => ({\n name: skill.name,\n description: skill.description,\n skill_md: skill.skillMd,\n source_url: skill.sourceUrl,\n folder_hash: skill.folderHash,\n bundle_base64: await createSkillBundleBase64(skill),\n })));\n\n return requestJson<SyncPushResponse>(`${serverUrl}/portal_skills/sync-push`, jwt, {\n body: {\n skills: payloadSkills,\n },\n });\n}\n\nexport async function downloadSkillBundle(bundleUrl: string): Promise<Buffer> {\n const response = await fetch(bundleUrl, { signal: AbortSignal.timeout(90_000) });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`GET ${bundleUrl} \u2192 ${response.status}: ${text}`);\n }\n\n return Buffer.from(await response.arrayBuffer());\n}\n\nexport async function updateAgentTargets(\n serverUrl: string,\n jwt: string,\n skillId: string,\n targets: Partial<AgentTargets>,\n expectedUpdatedAt?: string,\n): Promise<{ success: boolean; agent_targets: AgentTargets; updated_at?: string }> {\n return requestJson<{ success: boolean; agent_targets: AgentTargets; updated_at?: string }>(`${serverUrl}/portal_skills/agent-targets`, jwt, {\n method: 'PATCH',\n body: {\n skill_id: skillId,\n agent_targets: targets,\n ...(expectedUpdatedAt ? { expected_updated_at: expectedUpdatedAt } : {}),\n },\n });\n}\n\n// Install (enabled=true) or uninstall (enabled=false) the curated computer-use\n// skill for the user, matching the desktop app's \"Desktop Use\" toggle.\nexport async function setComputerUseSkill(\n serverUrl: string,\n jwt: string,\n enabled: boolean,\n): Promise<{ installed: boolean }> {\n return requestJson<{ installed: boolean }>(`${serverUrl}/portal_skills/desktop-use`, jwt, {\n body: { enabled, name: 'notis-desktop-use' },\n });\n}\n", "import { promises as fs } from 'fs';\nimport os from 'os';\nimport path from 'path';\n\nimport {\n AGENTS_DIR,\n LEGACY_AGENTS_SKILLS_DIR,\n NOTIS_SKILL_SYNC_ROOT,\n safeName,\n} from './local-scanner';\nimport { normalizeAgentTargets, type AgentTargets, type SkillSyncFailure, type CloudSkill, type NotisSyncState } from './types';\n\nconst HOME_DIR = os.homedir();\n\nconst EXTERNAL_AGENT_SKILL_DIRS = {\n claude_code: path.join(HOME_DIR, '.claude', 'skills'),\n cursor: path.join(HOME_DIR, '.cursor', 'skills'),\n codex: path.join(HOME_DIR, '.codex', 'skills'),\n} as const;\n\ntype ExternalAgent = keyof typeof EXTERNAL_AGENT_SKILL_DIRS;\n\nconst EXTERNAL_AGENTS = Object.keys(EXTERNAL_AGENT_SKILL_DIRS) as ExternalAgent[];\n\nconst AGENT_FAILURE_LABELS: Record<string, string> = {\n claude_code: 'Claude Code',\n cursor: 'Cursor',\n codex: 'Codex',\n legacy: 'legacy ~/.agents/skills',\n};\n\n/** Failures are shown next to skill names in the Portal and CLI; never leak raw agent ids. */\nfunction agentFailureLabel(agent: string): string {\n return AGENT_FAILURE_LABELS[agent] ?? agent;\n}\n\nfunction agentFolderFailureName(agent: string): string {\n return `${agentFailureLabel(agent)} skills folder`;\n}\n\nexport interface DeletedAgentSymlink {\n skillId: string;\n skillName: string;\n agent: ExternalAgent;\n}\n\ntype AgentSkillDirs = {\n notis: string;\n} & typeof EXTERNAL_AGENT_SKILL_DIRS;\n\nexport interface SymlinkSyncOptions {\n agentSkillDirs?: Partial<AgentSkillDirs>;\n legacyGlobalSkillsDir?: string;\n removeUndesired?: boolean;\n}\n\nexport interface RemoveSkillSymlinkOptions extends SymlinkSyncOptions {\n ownership?: 'any-managed' | 'exact-skill-dir';\n}\n\nexport interface SymlinkSyncResult {\n linked: number;\n removed: number;\n skipped: number;\n verifiedAgentLinks?: Record<string, Partial<AgentTargets>>;\n failures?: SkillSyncFailure[];\n}\n\n/** Remove only links into another account's managed mirror. This is safe even\n * when automatic sync is disabled: it never changes either account mirror and\n * preserves links owned by the currently authenticated account. */\nexport async function removeForeignAccountSymlinks(\n skillsDir: string,\n options: SymlinkSyncOptions = {},\n): Promise<number> {\n const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };\n const currentRoot = path.resolve(skillsDir);\n const foreignCapableRoots = [\n path.join(path.resolve(NOTIS_SKILL_SYNC_ROOT), 'users'),\n path.join(path.resolve(AGENTS_DIR), 'notis', 'users'),\n path.dirname(path.dirname(currentRoot)),\n ];\n let removed = 0;\n const legacyGlobalSkillsDir = options.legacyGlobalSkillsDir || LEGACY_AGENTS_SKILLS_DIR;\n for (const agentDir of new Set([...Object.values(agentSkillDirs), legacyGlobalSkillsDir])) {\n let entries;\n try {\n entries = await fs.readdir(agentDir, { withFileTypes: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') continue;\n throw error;\n }\n for (const entry of entries) {\n if (!entry.isSymbolicLink()) continue;\n const entryPath = path.join(agentDir, entry.name);\n try {\n const target = await fs.readlink(entryPath);\n const resolvedTarget = path.resolve(path.dirname(entryPath), target);\n const belongsToAnyAccount = foreignCapableRoots.some((root) => (\n resolvedTarget.startsWith(`${root}${path.sep}`)\n ));\n const belongsToCurrentAccount = resolvedTarget === currentRoot\n || resolvedTarget.startsWith(`${currentRoot}${path.sep}`);\n if (belongsToAnyAccount && !belongsToCurrentAccount) {\n await fs.unlink(entryPath);\n removed += 1;\n }\n } catch (error) {\n // A disappearing link is already safe. Permission and I/O failures\n // must fail closed: otherwise a previous account's link can remain\n // exposed while the repeating sync reports success.\n if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') throw error;\n }\n }\n }\n return removed;\n}\n\nfunction managedSkillRoots(\n skillsDir: string,\n legacyGlobalSkillsDir: string = LEGACY_AGENTS_SKILLS_DIR,\n): string[] {\n const resolvedSkillsDir = path.resolve(skillsDir);\n const scopedUsersRoot = path.dirname(path.dirname(resolvedSkillsDir));\n const roots = [\n resolvedSkillsDir,\n path.resolve(legacyGlobalSkillsDir),\n path.join(path.resolve(NOTIS_SKILL_SYNC_ROOT), 'users'),\n path.join(path.resolve(AGENTS_DIR), 'notis', 'users'),\n ];\n if (path.basename(scopedUsersRoot) === 'users') {\n roots.push(scopedUsersRoot);\n }\n return roots;\n}\n\nasync function isManagedSymlink(linkPath: string, managedRoots: string[]): Promise<boolean> {\n try {\n const stats = await fs.lstat(linkPath);\n if (!stats.isSymbolicLink()) {\n return false;\n }\n\n const target = await fs.readlink(linkPath);\n const resolvedTarget = path.resolve(path.dirname(linkPath), target);\n return managedRoots.some((root) => (\n resolvedTarget === root || resolvedTarget.startsWith(`${root}${path.sep}`)\n ));\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') return false;\n throw error;\n }\n}\n\nasync function ensureCorrectSymlink(linkPath: string, targetPath: string): Promise<'linked' | 'skipped' | 'blocked'> {\n try {\n const stats = await fs.lstat(linkPath);\n if (stats.isSymbolicLink()) {\n const currentTarget = await fs.readlink(linkPath);\n const resolvedTarget = path.resolve(path.dirname(linkPath), currentTarget);\n if (resolvedTarget === targetPath) {\n return 'skipped';\n }\n await fs.unlink(linkPath);\n } else {\n return 'blocked';\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') throw error;\n }\n\n const relativePath = path.relative(path.dirname(linkPath), targetPath);\n await fs.symlink(relativePath, linkPath);\n return 'linked';\n}\n\nfunction defaultAgentSkillDirs(skillsDir: string): AgentSkillDirs {\n return {\n notis: skillsDir,\n ...EXTERNAL_AGENT_SKILL_DIRS,\n };\n}\n\nasync function removeUndesiredManagedSymlinks(\n agentDir: string,\n desiredSkills: Set<string>,\n managedRoots: string[],\n failures: SkillSyncFailure[],\n agent: string,\n): Promise<number> {\n let removed = 0;\n try {\n await fs.mkdir(agentDir, { recursive: true });\n const existingEntries = await fs.readdir(agentDir, { withFileTypes: true });\n for (const entry of existingEntries) {\n const entryPath = path.join(agentDir, entry.name);\n try {\n if (!desiredSkills.has(entry.name) && await isManagedSymlink(entryPath, managedRoots)) {\n await fs.unlink(entryPath);\n removed += 1;\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') {\n failures.push({ name: entry.name, error: `${agentFailureLabel(agent)}: could not remove skill link (${(error as Error).message})` });\n }\n }\n }\n } catch (error) {\n failures.push({ name: agentFolderFailureName(agent), error: `Could not read agent skills directory (${(error as Error).message})` });\n }\n return removed;\n}\n\nexport async function removeAllSymlinksForSkill(\n skillName: string,\n skillsDir: string = LEGACY_AGENTS_SKILLS_DIR,\n options: RemoveSkillSymlinkOptions = {},\n): Promise<number> {\n let removed = 0;\n const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };\n const legacyGlobalSkillsDir = options.legacyGlobalSkillsDir || LEGACY_AGENTS_SKILLS_DIR;\n const managedRoots = managedSkillRoots(skillsDir, legacyGlobalSkillsDir);\n const agentDirs = new Set([...Object.values(agentSkillDirs), legacyGlobalSkillsDir]);\n const expectedTarget = path.resolve(skillsDir, safeName(skillName, skillsDir));\n for (const agentDir of agentDirs) {\n const linkPath = path.join(agentDir, safeName(skillName, agentDir));\n const owned = options.ownership === 'exact-skill-dir'\n ? await isManagedSymlink(linkPath, [expectedTarget])\n : await isManagedSymlink(linkPath, managedRoots);\n if (owned) {\n await fs.unlink(linkPath);\n removed += 1;\n }\n }\n return removed;\n}\n\n/**\n * Detect per-agent skill symlinks that we created on a prior sync but that the user has since\n * deleted locally (e.g. `rm ~/.claude/skills/<skill>`). The caller turns each result into a\n * per-agent deactivation so the deletion sticks instead of being recreated on the next pass.\n *\n * A pair is flagged only when the skill is cloud-active for that agent, the previous sync state\n * recorded it active for that agent (so we know we created the link), and the link is now truly\n * absent (lstat ENOENT). Stale/wrong-target links and real files blocking the path are NOT\n * deletions \u2014 those are repaired/skipped by syncSymlinks as before. Only external agents are\n * considered; `notis` has no symlink (its dir is the source skills folder).\n */\nexport async function detectDeletedAgentSymlinks(\n cloudSkills: CloudSkill[],\n previousState: NotisSyncState,\n skillsDir: string = LEGACY_AGENTS_SKILLS_DIR,\n options: SymlinkSyncOptions = {},\n): Promise<DeletedAgentSymlink[]> {\n const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };\n const deletions: DeletedAgentSymlink[] = [];\n\n for (const agent of EXTERNAL_AGENTS) {\n const agentDir = agentSkillDirs[agent];\n if (path.resolve(agentDir) === path.resolve(skillsDir)) {\n continue;\n }\n\n // A missing (or non-directory) agent skills directory is NOT a per-skill deletion: removing\n // ~/.claude/skills wholesale would otherwise make every entry's link path ENOENT and\n // deactivate every skill for that agent. syncSymlinks recreates the directory and its links,\n // so only absent entries INSIDE an existing agent directory count as explicit user deletions.\n try {\n const dirStat = await fs.stat(agentDir);\n if (!dirStat.isDirectory()) {\n continue;\n }\n } catch {\n continue;\n }\n\n for (const skill of cloudSkills) {\n if (skill.status !== 'active') {\n continue;\n }\n if (!skill.id || skill.id.startsWith('local-')) {\n continue;\n }\n const previous = previousState.skills[skill.name];\n if (!previous || previous.cloudId !== skill.id\n || previous.verifiedAgentLinks?.[agent] !== true\n || !skill.updated_at || previous.cloudUpdatedAt !== skill.updated_at) {\n continue;\n }\n\n const cloudTargets = normalizeAgentTargets(skill.agent_targets);\n const previousTargets = normalizeAgentTargets(previous.agentTargets);\n if (!cloudTargets[agent] || !previousTargets[agent]) {\n continue;\n }\n\n let safeSkillName: string;\n try {\n safeSkillName = safeName(skill.name, skillsDir);\n } catch {\n continue;\n }\n\n const linkPath = path.join(agentDir, safeName(safeSkillName, agentDir));\n try {\n await fs.lstat(linkPath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {\n deletions.push({ skillId: skill.id, skillName: skill.name, agent });\n }\n }\n }\n }\n\n return deletions;\n}\n\nexport async function syncSymlinks(\n skills: CloudSkill[],\n skillsDir: string = LEGACY_AGENTS_SKILLS_DIR,\n options: SymlinkSyncOptions = {},\n): Promise<SymlinkSyncResult> {\n await fs.mkdir(skillsDir, { recursive: true });\n\n const result: Required<SymlinkSyncResult> = {\n linked: 0,\n removed: 0,\n skipped: 0,\n verifiedAgentLinks: {},\n failures: [],\n };\n const agentSkillDirs = { ...defaultAgentSkillDirs(skillsDir), ...options.agentSkillDirs };\n const legacyGlobalSkillsDir = options.legacyGlobalSkillsDir || LEGACY_AGENTS_SKILLS_DIR;\n const managedRoots = managedSkillRoots(skillsDir, legacyGlobalSkillsDir);\n\n const desiredByAgent = {\n notis: new Set<string>(),\n claude_code: new Set<string>(),\n cursor: new Set<string>(),\n codex: new Set<string>(),\n };\n\n for (const skill of skills) {\n if (skill.status !== 'active') {\n continue;\n }\n\n const safeSkillName = safeName(skill.name, skillsDir);\n const targets = normalizeAgentTargets(skill.agent_targets);\n if (targets.notis) {\n desiredByAgent.notis.add(safeSkillName);\n }\n if (targets.claude_code) {\n desiredByAgent.claude_code.add(safeSkillName);\n }\n if (targets.cursor) {\n desiredByAgent.cursor.add(safeSkillName);\n }\n if (targets.codex) {\n desiredByAgent.codex.add(safeSkillName);\n }\n }\n\n for (const [agent, agentDir] of Object.entries(agentSkillDirs) as Array<[keyof AgentSkillDirs, string]>) {\n if (path.resolve(agentDir) === path.resolve(skillsDir)) {\n result.skipped += desiredByAgent[agent].size;\n continue;\n }\n\n try {\n await fs.mkdir(agentDir, { recursive: true });\n } catch (error) {\n result.failures.push({ name: agentFolderFailureName(agent), error: `Could not create agent skills directory (${(error as Error).message})` });\n continue;\n }\n\n const desiredSkills = desiredByAgent[agent];\n if (options.removeUndesired !== false) {\n result.removed += await removeUndesiredManagedSymlinks(agentDir, desiredSkills, managedRoots, result.failures, agent);\n }\n\n for (const skillName of desiredSkills) {\n const targetPath = path.join(skillsDir, skillName);\n const linkPath = path.join(agentDir, safeName(skillName, agentDir));\n try {\n if (!(await fs.stat(path.join(targetPath, \"SKILL.md\"))).isFile()) throw new Error(\"Missing SKILL.md\");\n } catch {\n result.skipped += 1;\n result.failures.push({ name: skillName, error: `${agentFailureLabel(agent)}: SKILL.md is missing or unreadable` });\n continue;\n }\n let syncOutcome;\n try {\n syncOutcome = await ensureCorrectSymlink(linkPath, targetPath);\n } catch (error) {\n result.skipped += 1;\n result.failures.push({ name: skillName, error: `${agentFailureLabel(agent)}: could not create skill link (${(error as Error).message})` });\n continue;\n }\n if (syncOutcome === 'linked') {\n result.linked += 1;\n } else if (syncOutcome === 'blocked') {\n console.warn(\n `[skill-sync] Could not link \"${skillName}\" for ${agent}: non-symlink entry blocks ${linkPath}`,\n );\n result.skipped += 1;\n result.failures.push({ name: skillName, error: `${agentFailureLabel(agent)}: an existing file or folder blocks the skill link` });\n } else {\n result.skipped += 1;\n }\n if (syncOutcome !== \"blocked\") {\n result.verifiedAgentLinks[skillName] = { ...result.verifiedAgentLinks[skillName], [agent]: true };\n }\n }\n }\n\n if (\n options.removeUndesired !== false\n && !Object.values(agentSkillDirs).some(\n (agentDir) => path.resolve(agentDir) === path.resolve(legacyGlobalSkillsDir),\n )\n ) {\n result.removed += await removeUndesiredManagedSymlinks(\n legacyGlobalSkillsDir,\n new Set(),\n managedRoots,\n result.failures,\n 'legacy',\n );\n }\n\n return result;\n}\n", "import type { CloudSkill, LocalSkill, NotisSyncState } from \"./types\";\n\n/**\n * One cloud row per local folder. Several active rows can carry the same skill name\n * (an app's source skill plus its installed clone, or duplicates created by earlier\n * pushes), and every one of them writes to the same directory. Applying them all in\n * one pass replaced that directory several times per sync and left whichever row came\n * last on disk. The newest revision wins, deterministically.\n */\nexport function selectCloudSkillsToApply(skills: CloudSkill[]): CloudSkill[] {\n const winners = new Map<string, CloudSkill>();\n for (const skill of skills) {\n const current = winners.get(skill.name);\n if (!current || (skill.updated_at || \"\") >= (current.updated_at || \"\")) {\n winners.set(skill.name, skill);\n }\n }\n return [...winners.values()];\n}\n\nexport function getPushCandidates(\n localSkills: LocalSkill[],\n syncState: NotisSyncState,\n cloudCuratedSkillNames: ReadonlySet<string> = new Set(),\n cloudSkillNames?: ReadonlySet<string>,\n cloudAppOwnedSkillNames: ReadonlySet<string> = new Set(),\n): LocalSkill[] {\n return localSkills.filter((skill) => {\n if (cloudCuratedSkillNames.has(skill.name)) {\n return false;\n }\n // App-owned content belongs to the installed app's release, not to this account.\n // Pushing it created a second row with the same name on every local difference,\n // and each new row then fought over the same folder.\n if (cloudAppOwnedSkillNames.has(skill.name)) {\n return false;\n }\n const previous = syncState.skills[skill.name];\n if (!previous) {\n return true;\n }\n if (cloudSkillNames && !cloudSkillNames.has(skill.name)) {\n return false;\n }\n return previous.folderHash !== skill.folderHash;\n });\n}\n", "import type { CloudSkill } from './types';\n\ninterface WriteCloudSkillDependencies {\n downloadSkillBundle: (bundleUrl: string) => Promise<Buffer>;\n writeCloudSkillToDisk: (skill: CloudSkill, bundleBytes?: Buffer) => Promise<boolean>;\n onWarning?: (message: string, error: unknown) => void;\n}\n\nexport async function writeCloudSkillWithBundleFallback(\n skill: CloudSkill,\n dependencies: WriteCloudSkillDependencies,\n): Promise<boolean> {\n if (skill.skill_source_url) {\n try {\n const bundleBytes = await dependencies.downloadSkillBundle(skill.skill_source_url);\n const wroteBundleToDisk = await dependencies.writeCloudSkillToDisk(skill, bundleBytes);\n if (wroteBundleToDisk) {\n return true;\n }\n\n dependencies.onWarning?.(\n `Bundle sync for \"${skill.name}\" produced no local changes, falling back to SKILL.md payload.`,\n new Error('Bundle write returned false'),\n );\n } catch (error) {\n dependencies.onWarning?.(\n `Failed to apply bundle sync for \"${skill.name}\", falling back to SKILL.md payload.`,\n error,\n );\n }\n }\n\n if (skill.bundle_hydration_failed) {\n dependencies.onWarning?.(\n `Skipping markdown fallback for synced skill \"${skill.name}\" because its stored bundle could not be hydrated by the server.`,\n new Error('Bundle hydration failed'),\n );\n return false;\n }\n\n try {\n return await dependencies.writeCloudSkillToDisk(skill);\n } catch (error) {\n dependencies.onWarning?.(\n `Failed to write synced skill \"${skill.name}\" to disk.`,\n error,\n );\n return false;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["createHash", "fs", "os", "path", "HOME_DIR", "os", "path", "fs", "createHash"]
|
|
7
7
|
}
|
|
@@ -2365,11 +2365,24 @@ async function syncSymlinks(skills, skillsDir = LEGACY_AGENTS_SKILLS_DIR, option
|
|
|
2365
2365
|
}
|
|
2366
2366
|
return result;
|
|
2367
2367
|
}
|
|
2368
|
-
function
|
|
2368
|
+
function selectCloudSkillsToApply(skills) {
|
|
2369
|
+
const winners = /* @__PURE__ */ new Map();
|
|
2370
|
+
for (const skill of skills) {
|
|
2371
|
+
const current = winners.get(skill.name);
|
|
2372
|
+
if (!current || (skill.updated_at || "") >= (current.updated_at || "")) {
|
|
2373
|
+
winners.set(skill.name, skill);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
return [...winners.values()];
|
|
2377
|
+
}
|
|
2378
|
+
function getPushCandidates(localSkills, syncState, cloudCuratedSkillNames = /* @__PURE__ */ new Set(), cloudSkillNames, cloudAppOwnedSkillNames = /* @__PURE__ */ new Set()) {
|
|
2369
2379
|
return localSkills.filter((skill) => {
|
|
2370
2380
|
if (cloudCuratedSkillNames.has(skill.name)) {
|
|
2371
2381
|
return false;
|
|
2372
2382
|
}
|
|
2383
|
+
if (cloudAppOwnedSkillNames.has(skill.name)) {
|
|
2384
|
+
return false;
|
|
2385
|
+
}
|
|
2373
2386
|
const previous = syncState.skills[skill.name];
|
|
2374
2387
|
if (!previous) {
|
|
2375
2388
|
return true;
|
|
@@ -2482,17 +2495,28 @@ function shouldWriteCloudSkill(cloudSkill, localSkills, previousState) {
|
|
|
2482
2495
|
}
|
|
2483
2496
|
const previous = previousState.skills[skillName];
|
|
2484
2497
|
if (previous?.folderHash === localSkill.folderHash && previous.cloudContentHash === cloudContentHash(cloudSkill)) return false;
|
|
2498
|
+
if (previous && previous.cloudContentHash === void 0 && previous.cloudId === cloudSkill.id && previous.appliedCloudFolderHash !== void 0 && previous.appliedCloudFolderHash === cloudHash && previous.cloudUpdatedAt === cloudSkill.updated_at && previous.folderHash === localSkill.folderHash) {
|
|
2499
|
+
return false;
|
|
2500
|
+
}
|
|
2485
2501
|
if (cloudSkill.source === "curated") {
|
|
2486
2502
|
return cloudHash ? cloudHash !== localSkill.folderHash : true;
|
|
2487
2503
|
}
|
|
2488
2504
|
const localChangedSinceLastSync = !previous || previous.folderHash !== localSkill.folderHash;
|
|
2489
2505
|
return !localChangedSinceLastSync && (Boolean(cloudHash) && cloudHash !== localSkill.folderHash || Boolean(previous?.cloudContentHash && previous.cloudContentHash !== cloudContentHash(cloudSkill)));
|
|
2490
2506
|
}
|
|
2491
|
-
function
|
|
2507
|
+
function collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames) {
|
|
2508
|
+
const applied = {};
|
|
2509
|
+
for (const skill of selectCloudSkillsToApply(pullResponse.skills)) {
|
|
2510
|
+
applied[skill.name] = writtenSkillNames.has(skill.name) ? skill.skill_folder_hash || "" : previousState.skills[skill.name]?.appliedCloudFolderHash;
|
|
2511
|
+
}
|
|
2512
|
+
return applied;
|
|
2513
|
+
}
|
|
2514
|
+
function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLinks = {}, failedContentNames = /* @__PURE__ */ new Set(), appliedRevisions = {}) {
|
|
2492
2515
|
const localSkillMap = toSkillMap(localSkills);
|
|
2493
2516
|
const skills = Object.fromEntries(
|
|
2494
|
-
pullResponse.skills.map((skill) => {
|
|
2517
|
+
selectCloudSkillsToApply(pullResponse.skills).map((skill) => {
|
|
2495
2518
|
const localSkill = localSkillMap.get(skill.name);
|
|
2519
|
+
const appliedCloudFolderHash = appliedRevisions[skill.name];
|
|
2496
2520
|
return [
|
|
2497
2521
|
skill.name,
|
|
2498
2522
|
{
|
|
@@ -2502,6 +2526,7 @@ function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLi
|
|
|
2502
2526
|
verifiedAgentLinks: skill.status === "active" ? verifiedAgentLinks[skill.name] ?? {} : {},
|
|
2503
2527
|
cloudUpdatedAt: skill.updated_at,
|
|
2504
2528
|
...!failedContentNames.has(skill.name) && !skill.skill_source_url ? { cloudContentHash: cloudContentHash(skill) } : {},
|
|
2529
|
+
...appliedCloudFolderHash !== void 0 ? { appliedCloudFolderHash } : {},
|
|
2505
2530
|
syncedAt: lastSyncedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
2506
2531
|
}
|
|
2507
2532
|
];
|
|
@@ -2563,7 +2588,7 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
|
|
|
2563
2588
|
console.warn(`[Notis] ${message}`, error);
|
|
2564
2589
|
};
|
|
2565
2590
|
let downloaded = 0;
|
|
2566
|
-
for (const cloudSkill of pullResponse.skills) {
|
|
2591
|
+
for (const cloudSkill of selectCloudSkillsToApply(pullResponse.skills)) {
|
|
2567
2592
|
if (!shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {
|
|
2568
2593
|
continue;
|
|
2569
2594
|
}
|
|
@@ -2718,7 +2743,8 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
2718
2743
|
localSkills,
|
|
2719
2744
|
previousState,
|
|
2720
2745
|
cloudCuratedSkillNames,
|
|
2721
|
-
new Set(pullResponse.skills.map((skill) => skill.name))
|
|
2746
|
+
new Set(pullResponse.skills.map((skill) => skill.name)),
|
|
2747
|
+
new Set(pullResponse.skills.filter((skill) => skill.owner_app_id).map((skill) => skill.name))
|
|
2722
2748
|
);
|
|
2723
2749
|
const failedPushes = [];
|
|
2724
2750
|
if (pushCandidates.length > 0) {
|
|
@@ -2742,13 +2768,15 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
2742
2768
|
}
|
|
2743
2769
|
}
|
|
2744
2770
|
const failedDownloads = [];
|
|
2771
|
+
const writtenSkillNames = /* @__PURE__ */ new Set();
|
|
2745
2772
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
2746
2773
|
pullResponse,
|
|
2747
2774
|
localSkills,
|
|
2748
2775
|
previousState,
|
|
2749
2776
|
syncPaths,
|
|
2750
2777
|
deps,
|
|
2751
|
-
failedDownloads
|
|
2778
|
+
failedDownloads,
|
|
2779
|
+
writtenSkillNames
|
|
2752
2780
|
);
|
|
2753
2781
|
const finalLocalSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES.has(skill.name));
|
|
2754
2782
|
const symlinkResult = await deps.syncSymlinks(
|
|
@@ -2759,7 +2787,14 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
|
|
|
2759
2787
|
for (const failure of failedDownloads) delete verifiedLinks[failure.name];
|
|
2760
2788
|
const lastSyncedAt = pullResponse.last_synced_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
2761
2789
|
await deps.writeSyncState(
|
|
2762
|
-
buildSyncState(
|
|
2790
|
+
buildSyncState(
|
|
2791
|
+
pullResponse,
|
|
2792
|
+
finalLocalSkills,
|
|
2793
|
+
lastSyncedAt,
|
|
2794
|
+
verifiedLinks,
|
|
2795
|
+
new Set(failedDownloads.map((item) => item.name)),
|
|
2796
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames)
|
|
2797
|
+
),
|
|
2763
2798
|
syncPaths
|
|
2764
2799
|
);
|
|
2765
2800
|
return {
|
package/package.json
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
syncSymlinks,
|
|
33
33
|
type DeletedAgentSymlink,
|
|
34
34
|
} from "./symlink-manager";
|
|
35
|
-
import { getPushCandidates } from "./sync-plan";
|
|
35
|
+
import { getPushCandidates, selectCloudSkillsToApply } from "./sync-plan";
|
|
36
36
|
import { writeCloudSkillWithBundleFallback } from "./write-cloud-skill";
|
|
37
37
|
export { fetchSyncSettings } from './cloud-client';
|
|
38
38
|
|
|
@@ -179,6 +179,24 @@ export function shouldWriteCloudSkill(
|
|
|
179
179
|
if (previous?.folderHash === localSkill.folderHash
|
|
180
180
|
&& previous.cloudContentHash === cloudContentHash(cloudSkill)) return false;
|
|
181
181
|
|
|
182
|
+
// Without a stored content baseline (skills served through a signed source URL keep
|
|
183
|
+
// none, because that URL changes on every pull) the only other signal is
|
|
184
|
+
// `skill_folder_hash`, which the server does NOT always compute the way the local
|
|
185
|
+
// folder hash is computed. An app-published skill hashes its bundle entries, so that
|
|
186
|
+
// value can never equal the hash of the extracted directory, and comparing the two
|
|
187
|
+
// replaced the folder on every sync tick, deleting whatever a running skill had
|
|
188
|
+
// written inside it. Compare cloud against cloud instead: rewrite when the cloud
|
|
189
|
+
// revision we last applied has actually moved.
|
|
190
|
+
if (previous
|
|
191
|
+
&& previous.cloudContentHash === undefined
|
|
192
|
+
&& previous.cloudId === cloudSkill.id
|
|
193
|
+
&& previous.appliedCloudFolderHash !== undefined
|
|
194
|
+
&& previous.appliedCloudFolderHash === cloudHash
|
|
195
|
+
&& previous.cloudUpdatedAt === cloudSkill.updated_at
|
|
196
|
+
&& previous.folderHash === localSkill.folderHash) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
182
200
|
if (cloudSkill.source === "curated") {
|
|
183
201
|
return cloudHash ? cloudHash !== localSkill.folderHash : true;
|
|
184
202
|
}
|
|
@@ -192,17 +210,42 @@ export function shouldWriteCloudSkill(
|
|
|
192
210
|
);
|
|
193
211
|
}
|
|
194
212
|
|
|
213
|
+
type AppliedCloudRevisions = Record<string, string | undefined>;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The cloud revision each folder's last successful write applied: this run's revision
|
|
217
|
+
* for the skills we just wrote, the previously recorded one otherwise. A failed write
|
|
218
|
+
* records nothing, so the next sync retries it.
|
|
219
|
+
*/
|
|
220
|
+
function collectAppliedCloudRevisions(
|
|
221
|
+
pullResponse: SyncPullResponse,
|
|
222
|
+
previousState: NotisSyncState,
|
|
223
|
+
writtenSkillNames: ReadonlySet<string>,
|
|
224
|
+
): AppliedCloudRevisions {
|
|
225
|
+
const applied: AppliedCloudRevisions = {};
|
|
226
|
+
for (const skill of selectCloudSkillsToApply(pullResponse.skills)) {
|
|
227
|
+
applied[skill.name] = writtenSkillNames.has(skill.name)
|
|
228
|
+
? skill.skill_folder_hash || ""
|
|
229
|
+
: previousState.skills[skill.name]?.appliedCloudFolderHash;
|
|
230
|
+
}
|
|
231
|
+
return applied;
|
|
232
|
+
}
|
|
233
|
+
|
|
195
234
|
function buildSyncState(
|
|
196
235
|
pullResponse: SyncPullResponse,
|
|
197
236
|
localSkills: LocalSkill[],
|
|
198
237
|
lastSyncedAt: string | null,
|
|
199
238
|
verifiedAgentLinks: Record<string, Partial<AgentTargets>> = {},
|
|
200
239
|
failedContentNames: ReadonlySet<string> = new Set(),
|
|
240
|
+
appliedRevisions: AppliedCloudRevisions = {},
|
|
201
241
|
): NotisSyncState {
|
|
202
242
|
const localSkillMap = toSkillMap(localSkills);
|
|
243
|
+
// The same row selection the writer used, so the state describes the revision that
|
|
244
|
+
// is actually on disk rather than whichever duplicate row the server listed last.
|
|
203
245
|
const skills = Object.fromEntries(
|
|
204
|
-
pullResponse.skills.map((skill) => {
|
|
246
|
+
selectCloudSkillsToApply(pullResponse.skills).map((skill) => {
|
|
205
247
|
const localSkill = localSkillMap.get(skill.name);
|
|
248
|
+
const appliedCloudFolderHash = appliedRevisions[skill.name];
|
|
206
249
|
return [
|
|
207
250
|
skill.name,
|
|
208
251
|
{
|
|
@@ -213,6 +256,7 @@ function buildSyncState(
|
|
|
213
256
|
cloudUpdatedAt: skill.updated_at,
|
|
214
257
|
...(!failedContentNames.has(skill.name) && !skill.skill_source_url
|
|
215
258
|
? { cloudContentHash: cloudContentHash(skill) } : {}),
|
|
259
|
+
...(appliedCloudFolderHash !== undefined ? { appliedCloudFolderHash } : {}),
|
|
216
260
|
syncedAt: lastSyncedAt || new Date().toISOString(),
|
|
217
261
|
},
|
|
218
262
|
];
|
|
@@ -305,7 +349,7 @@ async function writePulledSkillsToScopedMirror(
|
|
|
305
349
|
};
|
|
306
350
|
|
|
307
351
|
let downloaded = 0;
|
|
308
|
-
for (const cloudSkill of pullResponse.skills) {
|
|
352
|
+
for (const cloudSkill of selectCloudSkillsToApply(pullResponse.skills)) {
|
|
309
353
|
if (!shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {
|
|
310
354
|
continue;
|
|
311
355
|
}
|
|
@@ -420,6 +464,7 @@ export async function materializeCloudSkillsForLocalShell(
|
|
|
420
464
|
const materializedState = buildSyncState(
|
|
421
465
|
pullResponse, finalLocalSkills, lastSyncedAt, verifiedLinks,
|
|
422
466
|
new Set(failedDownloads.map(item => item.name)),
|
|
467
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames),
|
|
423
468
|
);
|
|
424
469
|
// Pull-only refresh is not an upload acknowledgement. Keep content baselines
|
|
425
470
|
// unless we actually wrote cloud content, and retain cloud-missing entries so
|
|
@@ -626,6 +671,7 @@ export async function runSkillSync(
|
|
|
626
671
|
previousState,
|
|
627
672
|
cloudCuratedSkillNames,
|
|
628
673
|
new Set(pullResponse.skills.map((skill) => skill.name)),
|
|
674
|
+
new Set(pullResponse.skills.filter((skill) => skill.owner_app_id).map((skill) => skill.name)),
|
|
629
675
|
);
|
|
630
676
|
|
|
631
677
|
const failedPushes: SkillSyncFailure[] = [];
|
|
@@ -654,6 +700,7 @@ export async function runSkillSync(
|
|
|
654
700
|
}
|
|
655
701
|
|
|
656
702
|
const failedDownloads: SkillSyncFailure[] = [];
|
|
703
|
+
const writtenSkillNames = new Set<string>();
|
|
657
704
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
658
705
|
pullResponse,
|
|
659
706
|
localSkills,
|
|
@@ -661,6 +708,7 @@ export async function runSkillSync(
|
|
|
661
708
|
syncPaths,
|
|
662
709
|
deps,
|
|
663
710
|
failedDownloads,
|
|
711
|
+
writtenSkillNames,
|
|
664
712
|
);
|
|
665
713
|
|
|
666
714
|
const finalLocalSkills = (await deps.scanLocalSkills(syncPaths))
|
|
@@ -674,7 +722,14 @@ export async function runSkillSync(
|
|
|
674
722
|
const lastSyncedAt = pullResponse.last_synced_at || new Date().toISOString();
|
|
675
723
|
|
|
676
724
|
await deps.writeSyncState(
|
|
677
|
-
buildSyncState(
|
|
725
|
+
buildSyncState(
|
|
726
|
+
pullResponse,
|
|
727
|
+
finalLocalSkills,
|
|
728
|
+
lastSyncedAt,
|
|
729
|
+
verifiedLinks,
|
|
730
|
+
new Set(failedDownloads.map(item => item.name)),
|
|
731
|
+
collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames),
|
|
732
|
+
),
|
|
678
733
|
syncPaths,
|
|
679
734
|
);
|
|
680
735
|
|
|
@@ -1,15 +1,40 @@
|
|
|
1
|
-
import type { LocalSkill, NotisSyncState } from "./types";
|
|
1
|
+
import type { CloudSkill, LocalSkill, NotisSyncState } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One cloud row per local folder. Several active rows can carry the same skill name
|
|
5
|
+
* (an app's source skill plus its installed clone, or duplicates created by earlier
|
|
6
|
+
* pushes), and every one of them writes to the same directory. Applying them all in
|
|
7
|
+
* one pass replaced that directory several times per sync and left whichever row came
|
|
8
|
+
* last on disk. The newest revision wins, deterministically.
|
|
9
|
+
*/
|
|
10
|
+
export function selectCloudSkillsToApply(skills: CloudSkill[]): CloudSkill[] {
|
|
11
|
+
const winners = new Map<string, CloudSkill>();
|
|
12
|
+
for (const skill of skills) {
|
|
13
|
+
const current = winners.get(skill.name);
|
|
14
|
+
if (!current || (skill.updated_at || "") >= (current.updated_at || "")) {
|
|
15
|
+
winners.set(skill.name, skill);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return [...winners.values()];
|
|
19
|
+
}
|
|
2
20
|
|
|
3
21
|
export function getPushCandidates(
|
|
4
22
|
localSkills: LocalSkill[],
|
|
5
23
|
syncState: NotisSyncState,
|
|
6
24
|
cloudCuratedSkillNames: ReadonlySet<string> = new Set(),
|
|
7
25
|
cloudSkillNames?: ReadonlySet<string>,
|
|
26
|
+
cloudAppOwnedSkillNames: ReadonlySet<string> = new Set(),
|
|
8
27
|
): LocalSkill[] {
|
|
9
28
|
return localSkills.filter((skill) => {
|
|
10
29
|
if (cloudCuratedSkillNames.has(skill.name)) {
|
|
11
30
|
return false;
|
|
12
31
|
}
|
|
32
|
+
// App-owned content belongs to the installed app's release, not to this account.
|
|
33
|
+
// Pushing it created a second row with the same name on every local difference,
|
|
34
|
+
// and each new row then fought over the same folder.
|
|
35
|
+
if (cloudAppOwnedSkillNames.has(skill.name)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
13
38
|
const previous = syncState.skills[skill.name];
|
|
14
39
|
if (!previous) {
|
|
15
40
|
return true;
|
|
@@ -14,6 +14,11 @@ export interface SyncedSkill {
|
|
|
14
14
|
cloudUpdatedAt?: string;
|
|
15
15
|
/** Content accepted on disk; independent of server-specific folder hash formats. */
|
|
16
16
|
cloudContentHash?: string;
|
|
17
|
+
/** `skill_folder_hash` of the cloud revision a successful write actually applied.
|
|
18
|
+
* The server may compute that hash with a different construction than the local
|
|
19
|
+
* folder hash (app-published skills do), so only cloud-to-cloud comparison tells
|
|
20
|
+
* "the cloud moved" apart from "the two sides hash differently". */
|
|
21
|
+
appliedCloudFolderHash?: string;
|
|
17
22
|
syncedAt: string;
|
|
18
23
|
}
|
|
19
24
|
|
|
@@ -49,6 +54,9 @@ export interface CloudSkill {
|
|
|
49
54
|
skill_source_url?: string | null;
|
|
50
55
|
bundle_files?: BundleFile[] | null;
|
|
51
56
|
bundle_hydration_failed?: boolean | null;
|
|
57
|
+
/** Set when the skill's content is owned by an installed Notis app. Its folder is
|
|
58
|
+
* republished by the app, never by a local push. */
|
|
59
|
+
owner_app_id?: string | null;
|
|
52
60
|
source: string;
|
|
53
61
|
status: string;
|
|
54
62
|
}
|