@notis_ai/cli 0.2.0-beta.179.1 → 0.2.0-beta.185.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.
@@ -3186,11 +3186,16 @@ async function readJsonFile3(filePath) {
3186
3186
  return null;
3187
3187
  }
3188
3188
  }
3189
+ function isIgnoredSkillEntry(entry) {
3190
+ if (entry.name === ".DS_Store") return true;
3191
+ if (entry.isDirectory()) return IGNORED_SKILL_DIRECTORIES.has(entry.name);
3192
+ return /\.py[co]$/i.test(entry.name);
3193
+ }
3189
3194
  async function listFilesRecursive2(dirPath) {
3190
3195
  const entries = await fs2.readdir(dirPath, { withFileTypes: true });
3191
3196
  const nested = await Promise.all(
3192
3197
  entries.map(async (entry) => {
3193
- if (entry.name === ".DS_Store") {
3198
+ if (isIgnoredSkillEntry(entry)) {
3194
3199
  return [];
3195
3200
  }
3196
3201
  const fullPath = path2.join(dirPath, entry.name);
@@ -3630,20 +3635,31 @@ async function createZipFromDirectory(directoryPath) {
3630
3635
  os.tmpdir(),
3631
3636
  `notis-skill-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`
3632
3637
  );
3633
- const parentDir = path2.dirname(directoryPath);
3634
3638
  const directoryName = path2.basename(directoryPath);
3635
- if (process.platform === "win32") {
3636
- await execFileAsync("powershell.exe", [
3637
- "-NoProfile",
3638
- "-Command",
3639
- `Compress-Archive -Path '${directoryPath.replace(/'/g, "''")}' -DestinationPath '${zipPath.replace(/'/g, "''")}' -Force`
3640
- ]);
3639
+ const stagingRoot = await fs2.mkdtemp(path2.join(os.tmpdir(), "notis-skill-bundle-"));
3640
+ try {
3641
+ const stagedDir = path2.join(stagingRoot, directoryName);
3642
+ for (const filePath of await listFilesRecursive2(directoryPath)) {
3643
+ const target = path2.join(stagedDir, path2.relative(directoryPath, filePath));
3644
+ await fs2.mkdir(path2.dirname(target), { recursive: true });
3645
+ await fs2.copyFile(filePath, target);
3646
+ }
3647
+ await fs2.mkdir(stagedDir, { recursive: true });
3648
+ if (process.platform === "win32") {
3649
+ await execFileAsync("powershell.exe", [
3650
+ "-NoProfile",
3651
+ "-Command",
3652
+ `Compress-Archive -Path '${stagedDir.replace(/'/g, "''")}' -DestinationPath '${zipPath.replace(/'/g, "''")}' -Force`
3653
+ ]);
3654
+ return zipPath;
3655
+ }
3656
+ await execFileAsync("zip", ["-qry", zipPath, directoryName], {
3657
+ cwd: stagingRoot
3658
+ });
3641
3659
  return zipPath;
3660
+ } finally {
3661
+ await fs2.rm(stagingRoot, { recursive: true, force: true });
3642
3662
  }
3643
- await execFileAsync("zip", ["-qry", zipPath, directoryName], {
3644
- cwd: parentDir
3645
- });
3646
- return zipPath;
3647
3663
  }
3648
3664
  async function extractZipToDirectory(zipPath, destinationDir) {
3649
3665
  const extractRoot = await fs2.mkdtemp(
@@ -4708,7 +4724,7 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
4708
4724
  failedPushes
4709
4725
  };
4710
4726
  }
4711
- var DEFAULT_AGENT_TARGETS, HOME_DIR, execFileAsync, AGENTS_DIR, LEGACY_AGENTS_SKILLS_DIR, NOTIS_SKILL_SYNC_ROOT, LEGACY_NOTIS_SYNC_STATE_PATH, AGENTS_SKILLS_DIR, SKILL_LOCK_PATH, DEFAULT_SYNC_STATE, EXCLUDED_TOP_LEVEL_ROOT_NAMES, DEFAULT_SYNC_PATHS, HOME_DIR2, EXTERNAL_AGENT_SKILL_DIRS, EXTERNAL_AGENTS, AGENT_FAILURE_LABELS, BASE_SKILL_NAMES2, DEFAULT_RUN_SKILL_SYNC_DEPS;
4727
+ var DEFAULT_AGENT_TARGETS, HOME_DIR, execFileAsync, AGENTS_DIR, LEGACY_AGENTS_SKILLS_DIR, NOTIS_SKILL_SYNC_ROOT, LEGACY_NOTIS_SYNC_STATE_PATH, AGENTS_SKILLS_DIR, SKILL_LOCK_PATH, DEFAULT_SYNC_STATE, EXCLUDED_TOP_LEVEL_ROOT_NAMES, DEFAULT_SYNC_PATHS, IGNORED_SKILL_DIRECTORIES, HOME_DIR2, EXTERNAL_AGENT_SKILL_DIRS, EXTERNAL_AGENTS, AGENT_FAILURE_LABELS, BASE_SKILL_NAMES2, DEFAULT_RUN_SKILL_SYNC_DEPS;
4712
4728
  var init_skill_sync = __esm({
4713
4729
  "dist/skill-sync/index.js"() {
4714
4730
  DEFAULT_AGENT_TARGETS = {
@@ -4760,6 +4776,7 @@ var init_skill_sync = __esm({
4760
4776
  skillLockPath: SKILL_LOCK_PATH,
4761
4777
  gatherMetadataPath: path2.join(NOTIS_SKILL_SYNC_ROOT, "skill-gather-metadata.json")
4762
4778
  };
4779
+ IGNORED_SKILL_DIRECTORIES = /* @__PURE__ */ new Set(["__pycache__", ".pytest_cache"]);
4763
4780
  HOME_DIR2 = os2.homedir();
4764
4781
  EXTERNAL_AGENT_SKILL_DIRS = {
4765
4782
  claude_code: path22.join(HOME_DIR2, ".claude", "skills"),
@@ -11820,7 +11837,7 @@ var SAFE_VIEWPORT_HEIGHT_SCRIPT = `(() => {
11820
11837
  })()`;
11821
11838
 
11822
11839
  // src/runtime/agent-browser.js
11823
- var PREPARE_CAPTURE_SCRIPT = "(() => { const s = document.getElementById('harness-status'); if (s) s.style.display = 'none'; const r = document.getElementById('root'); if (r) { r.style.paddingTop = '0'; r.style.minHeight = '0'; } document.body.style.minHeight = '0'; document.documentElement.style.minHeight = '0'; return true; })()";
11840
+ var PREPARE_CAPTURE_SCRIPT = "(() => { const r = document.getElementById('root'); if (r) r.style.minHeight = '0'; document.body.style.minHeight = '0'; document.documentElement.style.minHeight = '0'; return true; })()";
11824
11841
  var FONTS_STATUS_SCRIPT = "(() => (document.fonts ? document.fonts.status : 'loaded'))()";
11825
11842
  var CONTENT_HEIGHT_SCRIPT = "(() => { const r = document.getElementById('root'); if (!r) return Math.ceil(document.documentElement.scrollHeight); return Math.ceil(Math.max(r.scrollHeight, r.getBoundingClientRect().height)); })()";
11826
11843
  var MIN_CSS_VIEWPORT_HEIGHT = 720;
@@ -12300,7 +12317,7 @@ var DESIGN_ASSERTIONS_SCRIPT = `(() => {
12300
12317
  };
12301
12318
  const all = Array.from(root.querySelectorAll('*'));
12302
12319
  for (const el of all) {
12303
- if (el.id === 'harness-status' || !visible(el) || isControl(el)) continue;
12320
+ if (!visible(el) || isControl(el)) continue;
12304
12321
  if (isBox(el)) {
12305
12322
  let p = el.parentElement; let depth = 0;
12306
12323
  while (p && p !== root && depth < 6) { if (!isControl(p) && isBox(p)) { push('nested_border_box', el); break; } p = p.parentElement; depth += 1; }
@@ -12317,7 +12334,7 @@ var DESIGN_ASSERTIONS_SCRIPT = `(() => {
12317
12334
  const text = (node.textContent || '').trim();
12318
12335
  if (!text) continue;
12319
12336
  const el = node.parentElement;
12320
- if (!el || el.closest('#harness-status') || !visible(el)) continue;
12337
+ if (!el || !visible(el)) continue;
12321
12338
  const size = parseFloat(getComputedStyle(el).fontSize);
12322
12339
  if (size > 0 && size < 12) push('text_below_12px', el, { font_size: size });
12323
12340
  if (/^Loading(\\.{3}|\\u2026)?(\\s|$)/.test(text)) push('loading_placeholder_after_mount', el);
@@ -12956,7 +12973,8 @@ var NOTIS_DATABASE_CORE_NAMES = /* @__PURE__ */ new Set([
12956
12973
  "get_database",
12957
12974
  "get_document",
12958
12975
  "list_databases",
12959
- "upsert_database"
12976
+ "upsert_database",
12977
+ "delete_database"
12960
12978
  ]);
12961
12979
  function isNotisDatabaseCoreName(coreName) {
12962
12980
  return NOTIS_DATABASE_CORE_NAMES.has(coreName) || coreName.startsWith("upsert_");
@@ -14201,15 +14219,20 @@ async function appsDeployHandler(ctx) {
14201
14219
  } catch {
14202
14220
  warnings.push("The app was updated, but the temporary release directory needs local cleanup.");
14203
14221
  }
14222
+ const skillReviews = (Array.isArray(result.payload.skill_reviews) ? result.payload.skill_reviews : []).filter((review) => review && typeof review.review_url === "string");
14204
14223
  return ctx.output.emitSuccess({
14205
14224
  command: ctx.spec.command_path.join(" "),
14206
14225
  data: {
14207
14226
  app_id: appId,
14208
14227
  version: deployedVersion,
14209
- idempotency_key: idempotencyKey
14228
+ idempotency_key: idempotencyKey,
14229
+ ...skillReviews.length ? { skill_reviews: skillReviews } : {}
14210
14230
  },
14211
14231
  warnings,
14212
- humanSummary: `Deployed to app ${appId} (version ${deployedVersion})`,
14232
+ humanSummary: [
14233
+ `Deployed to app ${appId} (version ${deployedVersion})`,
14234
+ ...skillReviews.map((review) => `Review ${review.skill} changes: ${review.review_url}`)
14235
+ ].join("\n"),
14213
14236
  meta: { mutating: true, idempotency_key: idempotencyKey }
14214
14237
  });
14215
14238
  } finally {
@@ -97,14 +97,16 @@ Before submitting or updating a Store listing:
97
97
  5. Test onboarding as an independent harness-native proof agent using an account
98
98
  isolated from the publisher, then rerun to prove no duplicates. Exercise each
99
99
  route and its interactions with fictional data, including empty/error states.
100
- Record exact identities, versions, results and run-created resource cleanup;
101
- never use owner records as writable test fixtures.
100
+ Record exact identities, versions, results and run-created resource cleanup in
101
+ a private Notis document owned by the app, never only in local files; never use
102
+ owner records as writable test fixtures.
102
103
  For bundled starter-data claims, install the actual published listing into an
103
104
  empty test account and read back its rows before onboarding or any manual data
104
105
  writes. Confirm the installed listing version and remapped relations; do not
105
106
  substitute an editable-source deployment for this Store-install test.
106
107
  6. Inspect the final submitted snapshot and media after packaging. Record the
107
- privacy audit and verification against that exact source version. Any unknown
108
+ privacy audit and verification against that exact source version in the same
109
+ private Notis document. Any unknown
108
110
  provenance, missing dependency, untested onboarding or suspected personal data
109
111
  blocks submission until resolved. Never equate submission with review approval
110
112
  or Store publication.
@@ -6,6 +6,7 @@ Native Notis databases are accessed through the generic tool workflow, not a fir
6
6
  - `LOCAL_NOTIS_DATABASE_GET_DATABASE` -- inspect read-only metadata and schema detail
7
7
  - `LOCAL_NOTIS_DATABASE_QUERY` -- query documents from a database
8
8
  - `LOCAL_NOTIS_DATABASE_UPSERT_DATABASE` -- create or update a database schema. Every database belongs to a Notis app: creation requires the owning app's slug or id in the `app` argument (create the app first with `LOCAL_NOTIS_CREATE_APP` if needed)
9
+ - `LOCAL_NOTIS_DATABASE_DELETE_DATABASE` -- permanently delete a database and all its rows by `database_id`. It is refused for a database its app declares, and while another database relates to it or an automation is triggered by it; the error says what to remove first
9
10
 
10
11
  Example workflow before building an app:
11
12
 
@@ -57,3 +57,14 @@ Example relation update:
57
57
  ]
58
58
  }
59
59
  ```
60
+
61
+ ### Deleting databases
62
+
63
+ Use `LOCAL_NOTIS_DATABASE_DELETE_DATABASE` only when the user asked for a database
64
+ to be removed: it permanently deletes the database and every row, and cannot be
65
+ undone. Pass the exact `database_id` from `LOCAL_NOTIS_DATABASE_LIST_DATABASES`.
66
+ The tool refuses while another database has a relation property pointing to it or
67
+ an automation is triggered by it, and lists them; remove those first (delete the
68
+ relation property with `LOCAL_NOTIS_DATABASE_UPSERT_DATABASE`, action `remove`).
69
+ It also refuses a database its app declares, because the app recreates it empty the
70
+ next time it loads: remove it from the app's databases and deploy, or delete the app.
@@ -513,7 +513,9 @@ export function useCollectionInteractions<T>(
513
513
  const extendDown = filterShortcutKeys(keyboard.extendNext, (key) => !key.toLowerCase().endsWith('arrowright'));
514
514
  const extendLeft = filterShortcutKeys(keyboard.extendPrevious, (key) => key.toLowerCase().endsWith('arrowleft'));
515
515
  const extendUp = filterShortcutKeys(keyboard.extendPrevious, (key) => !key.toLowerCase().endsWith('arrowleft'));
516
- add('collection.clear', 'Clear selection', keyboard.clear, clear);
516
+ // An empty collection has nothing to dismiss. Let the enclosing surface
517
+ // own Escape (for example Desktop's final Inbox navigation layer).
518
+ add('collection.clear', 'Clear selection', selectedIds.size > 0 ? keyboard.clear : false, clear);
517
519
  add('collection.select-all', 'Select all visible items', selectionMode === 'none' ? false : keyboard.selectAll, selectAll);
518
520
  add('collection.toggle', 'Toggle active item', selectionMode === 'none' ? false : keyboard.toggle, () => {
519
521
  const id = activeIdRef.current ?? anchorIdRef.current;
@@ -534,7 +536,7 @@ export function useCollectionInteractions<T>(
534
536
  if (id) activate(id);
535
537
  });
536
538
  return definitions;
537
- }, [activate, clear, keyboard, moveActive, selectAll, selectionMode, toggle]);
539
+ }, [activate, clear, keyboard, moveActive, selectAll, selectedIds.size, selectionMode, toggle]);
538
540
  const isAvailable = useCallback(() => isInteractionElementVisible(containerRef.current), []);
539
541
  useEffect(() => {
540
542
  const owner = shortcutCollectionOwnerRef.current!;
@@ -368,6 +368,8 @@ export interface RuntimeResource {
368
368
  kind: 'app' | 'report';
369
369
  /** Host-provided adoption eligibility, never report-authored authority. */
370
370
  analytics_eligible?: boolean;
371
+ /** Host-derived report family for adoption analytics, e.g. skill review reports. */
372
+ report_kind?: 'skill_review' | 'custom';
371
373
  id: string;
372
374
  revision: number;
373
375
  name?: string;
@@ -142,11 +142,17 @@ async function readJsonFile(filePath) {
142
142
  return null;
143
143
  }
144
144
  }
145
+ var IGNORED_SKILL_DIRECTORIES = /* @__PURE__ */ new Set(["__pycache__", ".pytest_cache"]);
146
+ function isIgnoredSkillEntry(entry) {
147
+ if (entry.name === ".DS_Store") return true;
148
+ if (entry.isDirectory()) return IGNORED_SKILL_DIRECTORIES.has(entry.name);
149
+ return /\.py[co]$/i.test(entry.name);
150
+ }
145
151
  async function listFilesRecursive(dirPath) {
146
152
  const entries = await fs.readdir(dirPath, { withFileTypes: true });
147
153
  const nested = await Promise.all(
148
154
  entries.map(async (entry) => {
149
- if (entry.name === ".DS_Store") {
155
+ if (isIgnoredSkillEntry(entry)) {
150
156
  return [];
151
157
  }
152
158
  const fullPath = path.join(dirPath, entry.name);
@@ -586,20 +592,31 @@ async function createZipFromDirectory(directoryPath) {
586
592
  os.tmpdir(),
587
593
  `notis-skill-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`
588
594
  );
589
- const parentDir = path.dirname(directoryPath);
590
595
  const directoryName = path.basename(directoryPath);
591
- if (process.platform === "win32") {
592
- await execFileAsync("powershell.exe", [
593
- "-NoProfile",
594
- "-Command",
595
- `Compress-Archive -Path '${directoryPath.replace(/'/g, "''")}' -DestinationPath '${zipPath.replace(/'/g, "''")}' -Force`
596
- ]);
596
+ const stagingRoot = await fs.mkdtemp(path.join(os.tmpdir(), "notis-skill-bundle-"));
597
+ try {
598
+ const stagedDir = path.join(stagingRoot, directoryName);
599
+ for (const filePath of await listFilesRecursive(directoryPath)) {
600
+ const target = path.join(stagedDir, path.relative(directoryPath, filePath));
601
+ await fs.mkdir(path.dirname(target), { recursive: true });
602
+ await fs.copyFile(filePath, target);
603
+ }
604
+ await fs.mkdir(stagedDir, { recursive: true });
605
+ if (process.platform === "win32") {
606
+ await execFileAsync("powershell.exe", [
607
+ "-NoProfile",
608
+ "-Command",
609
+ `Compress-Archive -Path '${stagedDir.replace(/'/g, "''")}' -DestinationPath '${zipPath.replace(/'/g, "''")}' -Force`
610
+ ]);
611
+ return zipPath;
612
+ }
613
+ await execFileAsync("zip", ["-qry", zipPath, directoryName], {
614
+ cwd: stagingRoot
615
+ });
597
616
  return zipPath;
617
+ } finally {
618
+ await fs.rm(stagingRoot, { recursive: true, force: true });
598
619
  }
599
- await execFileAsync("zip", ["-qry", zipPath, directoryName], {
600
- cwd: parentDir
601
- });
602
- return zipPath;
603
620
  }
604
621
  async function extractZipToDirectory(zipPath, destinationDir) {
605
622
  const extractRoot = await fs.mkdtemp(
@@ -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, 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\n/**\n * The files each folder's last successful write put on disk: this run's list for the\n * skills we just wrote, the previously recorded one otherwise. Missing means \"no\n * record\", which keeps local files rather than deleting what cannot be attributed.\n */\nfunction collectAppliedFiles(\n pullResponse: SyncPullResponse,\n previousState: NotisSyncState,\n appliedFilesByName: Record<string, string[]>,\n): Record<string, string[] | undefined> {\n const applied: Record<string, string[] | undefined> = {};\n for (const skill of selectCloudSkillsToApply(pullResponse.skills)) {\n applied[skill.name] = appliedFilesByName[skill.name]\n ?? previousState.skills[skill.name]?.appliedFiles;\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 appliedFiles: Record<string, string[] | undefined> = {},\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 const appliedFileList = appliedFiles[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 ...(appliedFileList !== undefined ? { appliedFiles: appliedFileList } : {}),\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 appliedFilesByName: Record<string, string[]> = {},\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 const outcome = await writeCloudSkillWithBundleFallback(cloudSkill, {\n downloadSkillBundle: deps.downloadSkillBundle,\n writeCloudSkillToDisk: (skill, bundleBytes) =>\n deps.writeCloudSkillToDisk(skill, bundleBytes, syncPaths, {\n // What the last write put there. Everything else in the folder was\n // produced locally and survives this one.\n previouslyApplied: previousState.skills[skill.name]?.appliedFiles,\n }),\n onWarning: warnSkillSync,\n });\n if (outcome) {\n downloaded += 1;\n writtenSkillNames.add(cloudSkill.name);\n if (typeof outcome === \"object\" && Array.isArray(outcome.appliedFiles)) {\n appliedFilesByName[cloudSkill.name] = outcome.appliedFiles;\n }\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 appliedFilesByName: Record<string, string[]> = {};\n const downloaded = await writePulledSkillsToScopedMirror(\n pullResponse,\n localSkills,\n previousState,\n syncPaths,\n deps,\n failedDownloads,\n writtenSkillNames,\n appliedFilesByName,\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 collectAppliedFiles(pullResponse, previousState, appliedFilesByName),\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.app_owned || 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 appliedFilesByName: Record<string, string[]> = {};\n const downloaded = await writePulledSkillsToScopedMirror(\n pullResponse,\n localSkills,\n previousState,\n syncPaths,\n deps,\n failedDownloads,\n writtenSkillNames,\n appliedFilesByName,\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 collectAppliedFiles(pullResponse, previousState, appliedFilesByName),\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 /** Relative paths the last successful write put in this folder. Anything else in\n * the folder was created locally (a running skill's output) and survives the next\n * cloud write; anything listed here that the cloud later drops is deleted. */\n appliedFiles?: 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\n/** A successful write, plus the paths it put on disk so the next one can tell\n * cloud-managed files apart from whatever ran inside the folder since. */\nexport interface SkillWriteOutcome {\n written: true;\n appliedFiles: string[];\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 a Notis app. Its folder is republished\n * by the app, never by a local push. `app_owned` is what the server sends; the raw\n * association id stays server-side. */\n app_owned?: boolean | null;\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, SkillWriteOutcome } 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<string[]> {\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 return await listRelativeFiles(destinationDir);\n } finally {\n await fs.rm(extractRoot, { recursive: true, force: true });\n }\n}\n\nasync function listRelativeFiles(dirPath: string): Promise<string[]> {\n try {\n const files = await listFilesRecursive(dirPath);\n return files.map((filePath) => toPosixRelativePath(dirPath, filePath)).sort();\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === \"ENOENT\") return [];\n throw error;\n }\n}\n\nfunction toPosixRelativePath(rootDir: string, filePath: string): string {\n return path.relative(rootDir, filePath).split(path.sep).join(\"/\");\n}\n\n/**\n * Carry local-only files across a cloud write. A skill that writes while it runs\n * (a run ledger under its own folder) would otherwise lose that work every time the\n * cloud content changed, because the write replaces the whole directory.\n *\n * Selective, not a blanket merge:\n * - a path the incoming content provides always wins;\n * - a path the previous write applied and the cloud has now dropped stays deleted,\n * so upstream removals still propagate;\n * - anything else is local and is kept.\n *\n * With no record of the previous write (state written before this existed) nothing\n * can be attributed to the cloud, so local files are kept rather than destroyed.\n */\nasync function preserveUnmanagedFiles(\n existingDir: string,\n stagingDir: string,\n previouslyApplied?: readonly string[],\n): Promise<void> {\n const managed = previouslyApplied ? new Set(previouslyApplied) : null;\n for (const existingFile of await listFilesRecursive(existingDir)) {\n const relativePath = toPosixRelativePath(existingDir, existingFile);\n if (managed?.has(relativePath)) continue;\n const stagedPath = path.join(stagingDir, ...relativePath.split(\"/\"));\n if (await pathExists(stagedPath)) continue;\n await fs.mkdir(path.dirname(stagedPath), { recursive: true });\n await fs.copyFile(existingFile, stagedPath);\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 preserve?: { previouslyApplied?: readonly string[] },\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 (preserve && (await pathExists(skillDir))) {\n await preserveUnmanagedFiles(skillDir, stagingDir, preserve.previouslyApplied);\n }\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 options: { previouslyApplied?: readonly string[] } = {},\n): Promise<SkillWriteOutcome | false> {\n const skillDir = path.join(\n paths.skillsDir,\n safeName(skill.name, paths.skillsDir),\n );\n const preserve = { previouslyApplied: options.previouslyApplied };\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 let appliedFiles: string[] = [];\n await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {\n appliedFiles = await extractZipToDirectory(bundlePath, stagingDir);\n }, preserve);\n return { written: true, appliedFiles };\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 const appliedFiles: string[] = [];\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 appliedFiles.push(toPosixRelativePath(stagingDir, filePath));\n }\n }, preserve);\n return { written: true, appliedFiles: appliedFiles.sort() };\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 }, preserve);\n return { written: true, appliedFiles: [\"SKILL.md\"] };\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, SkillWriteOutcome } from './types';\n\ninterface WriteCloudSkillDependencies {\n downloadSkillBundle: (bundleUrl: string) => Promise<Buffer>;\n writeCloudSkillToDisk: (skill: CloudSkill, bundleBytes?: Buffer) => Promise<SkillWriteOutcome | boolean>;\n onWarning?: (message: string, error: unknown) => void;\n}\n\nexport async function writeCloudSkillWithBundleFallback(\n skill: CloudSkill,\n dependencies: WriteCloudSkillDependencies,\n): Promise<SkillWriteOutcome | 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 wroteBundleToDisk;\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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",
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\n/**\n * The files each folder's last successful write put on disk: this run's list for the\n * skills we just wrote, the previously recorded one otherwise. Missing means \"no\n * record\", which keeps local files rather than deleting what cannot be attributed.\n */\nfunction collectAppliedFiles(\n pullResponse: SyncPullResponse,\n previousState: NotisSyncState,\n appliedFilesByName: Record<string, string[]>,\n): Record<string, string[] | undefined> {\n const applied: Record<string, string[] | undefined> = {};\n for (const skill of selectCloudSkillsToApply(pullResponse.skills)) {\n applied[skill.name] = appliedFilesByName[skill.name]\n ?? previousState.skills[skill.name]?.appliedFiles;\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 appliedFiles: Record<string, string[] | undefined> = {},\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 const appliedFileList = appliedFiles[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 ...(appliedFileList !== undefined ? { appliedFiles: appliedFileList } : {}),\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 appliedFilesByName: Record<string, string[]> = {},\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 const outcome = await writeCloudSkillWithBundleFallback(cloudSkill, {\n downloadSkillBundle: deps.downloadSkillBundle,\n writeCloudSkillToDisk: (skill, bundleBytes) =>\n deps.writeCloudSkillToDisk(skill, bundleBytes, syncPaths, {\n // What the last write put there. Everything else in the folder was\n // produced locally and survives this one.\n previouslyApplied: previousState.skills[skill.name]?.appliedFiles,\n }),\n onWarning: warnSkillSync,\n });\n if (outcome) {\n downloaded += 1;\n writtenSkillNames.add(cloudSkill.name);\n if (typeof outcome === \"object\" && Array.isArray(outcome.appliedFiles)) {\n appliedFilesByName[cloudSkill.name] = outcome.appliedFiles;\n }\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 appliedFilesByName: Record<string, string[]> = {};\n const downloaded = await writePulledSkillsToScopedMirror(\n pullResponse,\n localSkills,\n previousState,\n syncPaths,\n deps,\n failedDownloads,\n writtenSkillNames,\n appliedFilesByName,\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 collectAppliedFiles(pullResponse, previousState, appliedFilesByName),\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.app_owned || 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 appliedFilesByName: Record<string, string[]> = {};\n const downloaded = await writePulledSkillsToScopedMirror(\n pullResponse,\n localSkills,\n previousState,\n syncPaths,\n deps,\n failedDownloads,\n writtenSkillNames,\n appliedFilesByName,\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 collectAppliedFiles(pullResponse, previousState, appliedFilesByName),\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 /** Relative paths the last successful write put in this folder. Anything else in\n * the folder was created locally (a running skill's output) and survives the next\n * cloud write; anything listed here that the cloud later drops is deleted. */\n appliedFiles?: 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\n/** A successful write, plus the paths it put on disk so the next one can tell\n * cloud-managed files apart from whatever ran inside the folder since. */\nexport interface SkillWriteOutcome {\n written: true;\n appliedFiles: string[];\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 a Notis app. Its folder is republished\n * by the app, never by a local push. `app_owned` is what the server sends; the raw\n * association id stays server-side. */\n app_owned?: boolean | null;\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, SkillWriteOutcome } 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\n// Interpreter caches a script run leaves beside itself are not skill content: they\n// must neither mark a skill as changed nor ship in its bundle (app bundling applies\n// the same rule).\nconst IGNORED_SKILL_DIRECTORIES = new Set([\"__pycache__\", \".pytest_cache\"]);\n\nfunction isIgnoredSkillEntry(entry: Dirent): boolean {\n if (entry.name === \".DS_Store\") return true;\n if (entry.isDirectory()) return IGNORED_SKILL_DIRECTORIES.has(entry.name);\n return /\\.py[co]$/i.test(entry.name);\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 (isIgnoredSkillEntry(entry)) {\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 directoryName = path.basename(directoryPath);\n // Zip a copy holding exactly the files the folder hash covers, so ignored\n // caches never ship.\n const stagingRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"notis-skill-bundle-\"));\n try {\n const stagedDir = path.join(stagingRoot, directoryName);\n for (const filePath of await listFilesRecursive(directoryPath)) {\n const target = path.join(stagedDir, path.relative(directoryPath, filePath));\n await fs.mkdir(path.dirname(target), { recursive: true });\n await fs.copyFile(filePath, target);\n }\n await fs.mkdir(stagedDir, { recursive: true });\n\n if (process.platform === \"win32\") {\n await execFileAsync(\"powershell.exe\", [\n \"-NoProfile\",\n \"-Command\",\n `Compress-Archive -Path '${stagedDir.replace(/'/g, \"''\")}' -DestinationPath '${zipPath.replace(/'/g, \"''\")}' -Force`,\n ]);\n return zipPath;\n }\n\n await execFileAsync(\"zip\", [\"-qry\", zipPath, directoryName], {\n cwd: stagingRoot,\n });\n return zipPath;\n } finally {\n await fs.rm(stagingRoot, { recursive: true, force: true });\n }\n}\n\nasync function extractZipToDirectory(\n zipPath: string,\n destinationDir: string,\n): Promise<string[]> {\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 return await listRelativeFiles(destinationDir);\n } finally {\n await fs.rm(extractRoot, { recursive: true, force: true });\n }\n}\n\nasync function listRelativeFiles(dirPath: string): Promise<string[]> {\n try {\n const files = await listFilesRecursive(dirPath);\n return files.map((filePath) => toPosixRelativePath(dirPath, filePath)).sort();\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === \"ENOENT\") return [];\n throw error;\n }\n}\n\nfunction toPosixRelativePath(rootDir: string, filePath: string): string {\n return path.relative(rootDir, filePath).split(path.sep).join(\"/\");\n}\n\n/**\n * Carry local-only files across a cloud write. A skill that writes while it runs\n * (a run ledger under its own folder) would otherwise lose that work every time the\n * cloud content changed, because the write replaces the whole directory.\n *\n * Selective, not a blanket merge:\n * - a path the incoming content provides always wins;\n * - a path the previous write applied and the cloud has now dropped stays deleted,\n * so upstream removals still propagate;\n * - anything else is local and is kept.\n *\n * With no record of the previous write (state written before this existed) nothing\n * can be attributed to the cloud, so local files are kept rather than destroyed.\n */\nasync function preserveUnmanagedFiles(\n existingDir: string,\n stagingDir: string,\n previouslyApplied?: readonly string[],\n): Promise<void> {\n const managed = previouslyApplied ? new Set(previouslyApplied) : null;\n for (const existingFile of await listFilesRecursive(existingDir)) {\n const relativePath = toPosixRelativePath(existingDir, existingFile);\n if (managed?.has(relativePath)) continue;\n const stagedPath = path.join(stagingDir, ...relativePath.split(\"/\"));\n if (await pathExists(stagedPath)) continue;\n await fs.mkdir(path.dirname(stagedPath), { recursive: true });\n await fs.copyFile(existingFile, stagedPath);\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 preserve?: { previouslyApplied?: readonly string[] },\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 (preserve && (await pathExists(skillDir))) {\n await preserveUnmanagedFiles(skillDir, stagingDir, preserve.previouslyApplied);\n }\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 options: { previouslyApplied?: readonly string[] } = {},\n): Promise<SkillWriteOutcome | false> {\n const skillDir = path.join(\n paths.skillsDir,\n safeName(skill.name, paths.skillsDir),\n );\n const preserve = { previouslyApplied: options.previouslyApplied };\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 let appliedFiles: string[] = [];\n await replaceSkillDirectoryAtomically(skillDir, async (stagingDir) => {\n appliedFiles = await extractZipToDirectory(bundlePath, stagingDir);\n }, preserve);\n return { written: true, appliedFiles };\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 const appliedFiles: string[] = [];\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 appliedFiles.push(toPosixRelativePath(stagingDir, filePath));\n }\n }, preserve);\n return { written: true, appliedFiles: appliedFiles.sort() };\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 }, preserve);\n return { written: true, appliedFiles: [\"SKILL.md\"] };\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, SkillWriteOutcome } from './types';\n\ninterface WriteCloudSkillDependencies {\n downloadSkillBundle: (bundleUrl: string) => Promise<Buffer>;\n writeCloudSkillToDisk: (skill: CloudSkill, bundleBytes?: Buffer) => Promise<SkillWriteOutcome | boolean>;\n onWarning?: (message: string, error: unknown) => void;\n}\n\nexport async function writeCloudSkillWithBundleFallback(\n skill: CloudSkill,\n dependencies: WriteCloudSkillDependencies,\n): Promise<SkillWriteOutcome | 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 wroteBundleToDisk;\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
  }
@@ -1398,11 +1398,17 @@ async function readJsonFile2(filePath) {
1398
1398
  return null;
1399
1399
  }
1400
1400
  }
1401
+ var IGNORED_SKILL_DIRECTORIES = /* @__PURE__ */ new Set(["__pycache__", ".pytest_cache"]);
1402
+ function isIgnoredSkillEntry(entry) {
1403
+ if (entry.name === ".DS_Store") return true;
1404
+ if (entry.isDirectory()) return IGNORED_SKILL_DIRECTORIES.has(entry.name);
1405
+ return /\.py[co]$/i.test(entry.name);
1406
+ }
1401
1407
  async function listFilesRecursive(dirPath) {
1402
1408
  const entries = await fs.readdir(dirPath, { withFileTypes: true });
1403
1409
  const nested = await Promise.all(
1404
1410
  entries.map(async (entry) => {
1405
- if (entry.name === ".DS_Store") {
1411
+ if (isIgnoredSkillEntry(entry)) {
1406
1412
  return [];
1407
1413
  }
1408
1414
  const fullPath = path.join(dirPath, entry.name);
@@ -1842,20 +1848,31 @@ async function createZipFromDirectory(directoryPath) {
1842
1848
  os.tmpdir(),
1843
1849
  `notis-skill-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`
1844
1850
  );
1845
- const parentDir = path.dirname(directoryPath);
1846
1851
  const directoryName = path.basename(directoryPath);
1847
- if (process.platform === "win32") {
1848
- await execFileAsync("powershell.exe", [
1849
- "-NoProfile",
1850
- "-Command",
1851
- `Compress-Archive -Path '${directoryPath.replace(/'/g, "''")}' -DestinationPath '${zipPath.replace(/'/g, "''")}' -Force`
1852
- ]);
1852
+ const stagingRoot = await fs.mkdtemp(path.join(os.tmpdir(), "notis-skill-bundle-"));
1853
+ try {
1854
+ const stagedDir = path.join(stagingRoot, directoryName);
1855
+ for (const filePath of await listFilesRecursive(directoryPath)) {
1856
+ const target = path.join(stagedDir, path.relative(directoryPath, filePath));
1857
+ await fs.mkdir(path.dirname(target), { recursive: true });
1858
+ await fs.copyFile(filePath, target);
1859
+ }
1860
+ await fs.mkdir(stagedDir, { recursive: true });
1861
+ if (process.platform === "win32") {
1862
+ await execFileAsync("powershell.exe", [
1863
+ "-NoProfile",
1864
+ "-Command",
1865
+ `Compress-Archive -Path '${stagedDir.replace(/'/g, "''")}' -DestinationPath '${zipPath.replace(/'/g, "''")}' -Force`
1866
+ ]);
1867
+ return zipPath;
1868
+ }
1869
+ await execFileAsync("zip", ["-qry", zipPath, directoryName], {
1870
+ cwd: stagingRoot
1871
+ });
1853
1872
  return zipPath;
1873
+ } finally {
1874
+ await fs.rm(stagingRoot, { recursive: true, force: true });
1854
1875
  }
1855
- await execFileAsync("zip", ["-qry", zipPath, directoryName], {
1856
- cwd: parentDir
1857
- });
1858
- return zipPath;
1859
1876
  }
1860
1877
  async function extractZipToDirectory(zipPath, destinationDir) {
1861
1878
  const extractRoot = await fs.mkdtemp(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notis_ai/cli",
3
- "version": "0.2.0-beta.179.1",
3
+ "version": "0.2.0-beta.185.1",
4
4
  "description": "Agent-first Notis CLI for apps and generic tool execution",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1257,15 +1257,22 @@ async function appsDeployHandler(ctx) {
1257
1257
  try { release.close(); }
1258
1258
  catch { warnings.push('The app was updated, but the temporary release directory needs local cleanup.'); }
1259
1259
 
1260
+ // One review report per skill this release changed; share the link when the user should review it.
1261
+ const skillReviews = (Array.isArray(result.payload.skill_reviews) ? result.payload.skill_reviews : [])
1262
+ .filter((review) => review && typeof review.review_url === 'string');
1260
1263
  return ctx.output.emitSuccess({
1261
1264
  command: ctx.spec.command_path.join(' '),
1262
1265
  data: {
1263
1266
  app_id: appId,
1264
1267
  version: deployedVersion,
1265
1268
  idempotency_key: idempotencyKey,
1269
+ ...(skillReviews.length ? { skill_reviews: skillReviews } : {}),
1266
1270
  },
1267
1271
  warnings,
1268
- humanSummary: `Deployed to app ${appId} (version ${deployedVersion})`,
1272
+ humanSummary: [
1273
+ `Deployed to app ${appId} (version ${deployedVersion})`,
1274
+ ...skillReviews.map((review) => `Review ${review.skill} changes: ${review.review_url}`),
1275
+ ].join('\n'),
1269
1276
  meta: { mutating: true, idempotency_key: idempotencyKey },
1270
1277
  });
1271
1278
  } finally {
@@ -12,6 +12,7 @@ const NOTIS_DATABASE_CORE_NAMES = new Set([
12
12
  'get_document',
13
13
  'list_databases',
14
14
  'upsert_database',
15
+ 'delete_database',
15
16
  ]);
16
17
 
17
18
  function isNotisDatabaseCoreName(coreName) {
@@ -3,18 +3,13 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { dirname } from 'node:path';
4
4
  import { contentCapturePlan, MEASURE_SCREENSHOT_CONTENT_SCRIPT, SAFE_VIEWPORT_HEIGHT_SCRIPT } from './screenshot-framing.js';
5
5
 
6
- export const HIDE_HARNESS_STATUS_SCRIPT =
7
- "(() => { const el = document.getElementById('harness-status'); if (el) el.style.opacity = '0'; return true; })()";
8
-
9
6
  /**
10
- * Prepare the mounted harness page for a listing capture: remove the debug
11
- * banner and the layout the harness adds around the app (top padding for the
12
- * banner, 100vh min-height) so content height can be measured and framed
13
- * without artificial whitespace.
7
+ * Prepare the mounted harness page for a listing capture: remove the 100vh
8
+ * min-height the harness adds around the app so content height can be
9
+ * measured and framed without artificial whitespace.
14
10
  */
15
11
  const PREPARE_CAPTURE_SCRIPT =
16
- "(() => { const s = document.getElementById('harness-status'); if (s) s.style.display = 'none';" +
17
- " const r = document.getElementById('root'); if (r) { r.style.paddingTop = '0'; r.style.minHeight = '0'; }" +
12
+ "(() => { const r = document.getElementById('root'); if (r) r.style.minHeight = '0';" +
18
13
  " document.body.style.minHeight = '0'; document.documentElement.style.minHeight = '0'; return true; })()";
19
14
 
20
15
  const FONTS_STATUS_SCRIPT =
@@ -583,7 +578,7 @@ export const DESIGN_ASSERTIONS_SCRIPT = `(() => {
583
578
  };
584
579
  const all = Array.from(root.querySelectorAll('*'));
585
580
  for (const el of all) {
586
- if (el.id === 'harness-status' || !visible(el) || isControl(el)) continue;
581
+ if (!visible(el) || isControl(el)) continue;
587
582
  if (isBox(el)) {
588
583
  let p = el.parentElement; let depth = 0;
589
584
  while (p && p !== root && depth < 6) { if (!isControl(p) && isBox(p)) { push('nested_border_box', el); break; } p = p.parentElement; depth += 1; }
@@ -600,7 +595,7 @@ export const DESIGN_ASSERTIONS_SCRIPT = `(() => {
600
595
  const text = (node.textContent || '').trim();
601
596
  if (!text) continue;
602
597
  const el = node.parentElement;
603
- if (!el || el.closest('#harness-status') || !visible(el)) continue;
598
+ if (!el || !visible(el)) continue;
604
599
  const size = parseFloat(getComputedStyle(el).fontSize);
605
600
  if (size > 0 && size < 12) push('text_below_12px', el, { font_size: size });
606
601
  if (/^Loading(\\.{3}|\\u2026)?(\\s|$)/.test(text)) push('loading_placeholder_after_mount', el);
@@ -163,11 +163,22 @@ async function readJsonFile<T>(filePath: string): Promise<T | null> {
163
163
  }
164
164
  }
165
165
 
166
+ // Interpreter caches a script run leaves beside itself are not skill content: they
167
+ // must neither mark a skill as changed nor ship in its bundle (app bundling applies
168
+ // the same rule).
169
+ const IGNORED_SKILL_DIRECTORIES = new Set(["__pycache__", ".pytest_cache"]);
170
+
171
+ function isIgnoredSkillEntry(entry: Dirent): boolean {
172
+ if (entry.name === ".DS_Store") return true;
173
+ if (entry.isDirectory()) return IGNORED_SKILL_DIRECTORIES.has(entry.name);
174
+ return /\.py[co]$/i.test(entry.name);
175
+ }
176
+
166
177
  async function listFilesRecursive(dirPath: string): Promise<string[]> {
167
178
  const entries = await fs.readdir(dirPath, { withFileTypes: true });
168
179
  const nested = await Promise.all(
169
180
  entries.map(async (entry) => {
170
- if (entry.name === ".DS_Store") {
181
+ if (isIgnoredSkillEntry(entry)) {
171
182
  return [];
172
183
  }
173
184
 
@@ -839,22 +850,35 @@ async function createZipFromDirectory(directoryPath: string): Promise<string> {
839
850
  os.tmpdir(),
840
851
  `notis-skill-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`,
841
852
  );
842
- const parentDir = path.dirname(directoryPath);
843
853
  const directoryName = path.basename(directoryPath);
854
+ // Zip a copy holding exactly the files the folder hash covers, so ignored
855
+ // caches never ship.
856
+ const stagingRoot = await fs.mkdtemp(path.join(os.tmpdir(), "notis-skill-bundle-"));
857
+ try {
858
+ const stagedDir = path.join(stagingRoot, directoryName);
859
+ for (const filePath of await listFilesRecursive(directoryPath)) {
860
+ const target = path.join(stagedDir, path.relative(directoryPath, filePath));
861
+ await fs.mkdir(path.dirname(target), { recursive: true });
862
+ await fs.copyFile(filePath, target);
863
+ }
864
+ await fs.mkdir(stagedDir, { recursive: true });
844
865
 
845
- if (process.platform === "win32") {
846
- await execFileAsync("powershell.exe", [
847
- "-NoProfile",
848
- "-Command",
849
- `Compress-Archive -Path '${directoryPath.replace(/'/g, "''")}' -DestinationPath '${zipPath.replace(/'/g, "''")}' -Force`,
850
- ]);
866
+ if (process.platform === "win32") {
867
+ await execFileAsync("powershell.exe", [
868
+ "-NoProfile",
869
+ "-Command",
870
+ `Compress-Archive -Path '${stagedDir.replace(/'/g, "''")}' -DestinationPath '${zipPath.replace(/'/g, "''")}' -Force`,
871
+ ]);
872
+ return zipPath;
873
+ }
874
+
875
+ await execFileAsync("zip", ["-qry", zipPath, directoryName], {
876
+ cwd: stagingRoot,
877
+ });
851
878
  return zipPath;
879
+ } finally {
880
+ await fs.rm(stagingRoot, { recursive: true, force: true });
852
881
  }
853
-
854
- await execFileAsync("zip", ["-qry", zipPath, directoryName], {
855
- cwd: parentDir,
856
- });
857
- return zipPath;
858
882
  }
859
883
 
860
884
  async function extractZipToDirectory(
@@ -93,12 +93,10 @@
93
93
  color: hsl(var(--foreground));
94
94
  -webkit-font-smoothing: antialiased;
95
95
  }
96
- #harness-status { position: fixed; top: 0; left: 0; right: 0; padding: 4px 8px; font-size: 11px; background: #111; color: #0f0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; z-index: 9999; }
97
- #root { padding-top: 24px; min-height: 100vh; }
96
+ #root { min-height: 100vh; }
98
97
  </style>
99
98
  </head>
100
99
  <body>
101
- <div id="harness-status">harness: booting</div>
102
100
  <div id="root" data-notis-app-root></div>
103
101
  <script type="module">
104
102
  const routeExport = {{ROUTE_EXPORT}};
@@ -134,10 +132,9 @@
134
132
  return Object.prototype.hasOwnProperty.call(tools, name) ? tools[name] : undefined;
135
133
  }
136
134
 
137
- const status = document.getElementById('harness-status');
138
- const setStatus = (msg, color = '#0f0') => {
139
- status.textContent = `harness: ${msg}`;
140
- status.style.color = color;
135
+ // Boot progress goes to the console so the rendered page stays identical to the app.
136
+ const setStatus = (msg, failed = false) => {
137
+ (failed ? console.error : console.info)(`[notis harness] ${msg}`);
141
138
  };
142
139
 
143
140
  window.__harness = {
@@ -416,13 +413,13 @@
416
413
  window.__harness.mounted = true;
417
414
  const errors = window.__harness.errors.length;
418
415
  const calls = window.__harness.runtimeCalls.length;
419
- setStatus(`mounted (errors=${errors}, runtimeCalls=${calls})`, errors ? '#f55' : '#0f0');
416
+ setStatus(`mounted (errors=${errors}, runtimeCalls=${calls})`, errors > 0);
420
417
  } catch (error) {
421
418
  window.__harness.errors.push({
422
419
  type: 'scenario',
423
420
  message: error && error.message ? error.message : String(error),
424
421
  });
425
- setStatus(`scenario failed: ${error && error.message ? error.message : String(error)}`, '#f55');
422
+ setStatus(`scenario failed: ${error && error.message ? error.message : String(error)}`, true);
426
423
  }
427
424
  }));
428
425
  } catch (error) {
@@ -431,7 +428,7 @@
431
428
  message: error && error.message ? error.message : String(error),
432
429
  stack: error && error.stack,
433
430
  });
434
- setStatus(`boot failed: ${error && error.message ? error.message : String(error)}`, '#f55');
431
+ setStatus(`boot failed: ${error && error.message ? error.message : String(error)}`, true);
435
432
  }
436
433
  </script>
437
434
  </body>