@notis_ai/cli 0.2.0-beta.191.1 → 0.2.0-beta.197.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.
Files changed (107) hide show
  1. package/README.md +485 -86
  2. package/config/portal-route-roots.json +47 -0
  3. package/dist/agent-hooks/notis-agent-hook.mjs +32663 -6357
  4. package/dist/base-skills/notis-apps/SKILL.md +88 -67
  5. package/dist/base-skills/notis-apps/references/architecture.md +114 -167
  6. package/dist/base-skills/notis-apps/references/design.md +26 -107
  7. package/dist/base-skills/notis-apps/references/memory.md +58 -0
  8. package/dist/base-skills/notis-apps/references/reading.md +52 -74
  9. package/dist/base-skills/notis-apps/references/release.md +104 -142
  10. package/dist/base-skills/notis-apps/references/sdk.md +113 -60
  11. package/dist/base-skills/notis-apps/references/troubleshooting.md +22 -21
  12. package/dist/base-skills/notis-apps/references/views.md +142 -0
  13. package/dist/base-skills/notis-cli/SKILL.md +4 -4
  14. package/dist/base-skills/notis-cli/references/app-delivery.md +12 -11
  15. package/dist/base-skills/notis-cli/references/native-databases.md +6 -6
  16. package/dist/base-skills/notis-cli/references/tool-examples.md +4 -4
  17. package/dist/base-skills/notis-query/SKILL.md +34 -60
  18. package/dist/base-skills/notis-query/references/database-discovery.md +59 -69
  19. package/dist/base-skills/notis-query/references/documents.md +63 -50
  20. package/dist/base-skills/notis-query/references/query.md +57 -524
  21. package/dist/sdk/src/bundleModule.ts +79 -0
  22. package/dist/sdk/src/components/DocumentEditor.tsx +6 -3
  23. package/dist/sdk/src/components/DocumentPage.tsx +141 -0
  24. package/dist/sdk/src/components/RecordComponents.tsx +33 -0
  25. package/dist/sdk/src/components/RenderChartData.tsx +19 -0
  26. package/dist/sdk/src/config.ts +4 -26
  27. package/dist/sdk/src/documents.ts +2 -0
  28. package/dist/sdk/src/hooks/useAction.ts +28 -0
  29. package/dist/sdk/src/hooks/useCloudComputer.ts +35 -4
  30. package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
  31. package/dist/sdk/src/hooks/useHandover.ts +5 -4
  32. package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
  33. package/dist/sdk/src/hooks/useNativeDocuments.ts +83 -0
  34. package/dist/sdk/src/hooks/useNotis.ts +3 -0
  35. package/dist/sdk/src/hooks/useNotisNavigation.ts +26 -11
  36. package/dist/sdk/src/hooks/useQuery.ts +7 -4
  37. package/dist/sdk/src/hooks/useViewParams.ts +121 -0
  38. package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
  39. package/dist/sdk/src/index.ts +36 -0
  40. package/dist/sdk/src/interactions/shortcuts.tsx +17 -3
  41. package/dist/sdk/src/nativeData.ts +39 -0
  42. package/dist/sdk/src/presentation.tsx +82 -0
  43. package/dist/sdk/src/provider.tsx +9 -4
  44. package/dist/sdk/src/queryCache.ts +5 -3
  45. package/dist/sdk/src/queryErrors.ts +51 -0
  46. package/dist/sdk/src/runtime.ts +146 -8
  47. package/dist/sdk/src/space.ts +425 -0
  48. package/dist/sdk/src/styles.css +96 -72
  49. package/dist/sdk/src/vite.ts +11 -1
  50. package/dist/skill-sync/index.js +458 -68
  51. package/dist/skill-sync/index.js.map +4 -4
  52. package/dist/skill-sync-worker.mjs +451 -77
  53. package/dist/space-harness/host.js +66 -0
  54. package/dist/view-renderer/dist/host.css +1 -0
  55. package/dist/view-renderer/dist/host.js +1042 -0
  56. package/dist/view-renderer/dist/theme.css +73 -0
  57. package/dist/view-renderer/src/dom.js +180 -0
  58. package/dist/view-renderer/src/errors.js +8 -0
  59. package/dist/view-renderer/src/host.tsx +173 -0
  60. package/dist/view-renderer/src/index.js +4 -0
  61. package/dist/view-renderer/src/pool.js +76 -0
  62. package/dist/view-renderer/src/remote-broker.js +59 -0
  63. package/dist/view-renderer/src/renderer.js +201 -0
  64. package/package.json +11 -5
  65. package/skills/notis-apps/cli.md +270 -163
  66. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +3 -3
  67. package/skills/notis-onboarding/BRIEF.md +7 -3
  68. package/src/command-specs/apps.js +1 -8
  69. package/src/command-specs/files.js +29 -0
  70. package/src/command-specs/handover.js +45 -16
  71. package/src/command-specs/helpers.js +1 -0
  72. package/src/command-specs/index.js +10 -4
  73. package/src/command-specs/skills.js +229 -0
  74. package/src/command-specs/spaces.js +572 -0
  75. package/src/command-specs/views.js +69 -0
  76. package/src/runtime/agent-browser.js +30 -7
  77. package/src/runtime/app-boundary-validator.js +7 -4
  78. package/src/runtime/app-data-literals.js +82 -0
  79. package/src/runtime/app-platform.js +321 -253
  80. package/src/runtime/confined-space-build.js +74 -0
  81. package/src/runtime/confined-space-verify.js +49 -0
  82. package/src/runtime/native-record-files.js +47 -0
  83. package/src/runtime/retired-sdk-files.js +20 -0
  84. package/src/runtime/skill-sync/cloud-client.ts +26 -4
  85. package/src/runtime/skill-sync/index.ts +154 -35
  86. package/src/runtime/skill-sync/local-scanner.ts +125 -17
  87. package/src/runtime/skill-sync/native-identity.ts +102 -0
  88. package/src/runtime/skill-sync/native-plan.ts +59 -0
  89. package/src/runtime/skill-sync/sync-plan.ts +16 -9
  90. package/src/runtime/skill-sync/types.ts +32 -6
  91. package/src/runtime/source-root.js +32 -0
  92. package/src/runtime/space-collection-scaffold.js +66 -0
  93. package/src/runtime/space-database-structures.js +229 -0
  94. package/src/runtime/space-fixture-broker.js +50 -0
  95. package/src/runtime/space-lock.js +337 -0
  96. package/src/runtime/space-platform.js +105 -0
  97. package/src/runtime/space-source.js +281 -0
  98. package/src/runtime/space-test-server.js +327 -0
  99. package/src/runtime/space-verification.js +89 -0
  100. package/src/runtime/space-verify-browser-worker.js +51 -0
  101. package/src/runtime/space-view-manifest.js +254 -0
  102. package/src/runtime/sync-skills.js +1 -1
  103. package/src/runtime/transport.js +8 -1
  104. package/src/runtime/view-render.js +49 -0
  105. package/src/space-harness-host.ts +103 -0
  106. package/template/app/globals.css +1 -0
  107. package/src/command-specs/reports.js +0 -97
@@ -1,7 +1,7 @@
1
1
  // src/skill-sync-worker.js
2
2
  import { mkdirSync as mkdirSync3, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
3
3
  import { homedir as homedir4 } from "node:os";
4
- import { join as join5 } from "node:path";
4
+ import { join as join6 } from "node:path";
5
5
 
6
6
  // src/runtime/profiles.js
7
7
  import { randomUUID } from "node:crypto";
@@ -1256,13 +1256,16 @@ async function refreshOAuthCredential(runtime, fetchImpl = fetch) {
1256
1256
  }
1257
1257
 
1258
1258
  // dist/skill-sync/index.js
1259
+ import { createHash as createHash4 } from "node:crypto";
1260
+ import { join as join3 } from "node:path";
1259
1261
  import { createHash as createHash2 } from "node:crypto";
1260
- import { createHash as createHash3 } from "crypto";
1262
+ import { createHash as createHash22 } from "crypto";
1261
1263
  import { execFile } from "child_process";
1262
1264
  import { promises as fs } from "fs";
1263
1265
  import os from "os";
1264
1266
  import path from "path";
1265
1267
  import { promisify } from "util";
1268
+ import { createHash as createHash3 } from "node:crypto";
1266
1269
  import { promises as fs2 } from "fs";
1267
1270
  import os2 from "os";
1268
1271
  import path2 from "path";
@@ -1280,8 +1283,122 @@ function normalizeAgentTargets(targets) {
1280
1283
  codex: Boolean(targets?.codex ?? DEFAULT_AGENT_TARGETS.codex)
1281
1284
  };
1282
1285
  }
1286
+ var NATIVE_IDENTITY_FILE = ".notis-native-skill.json";
1287
+ var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
1288
+ function checkedNativeReference(value, cloudId) {
1289
+ const reference = value;
1290
+ if (!reference || typeof reference !== "object" || Array.isArray(reference) || Object.keys(reference).sort().join(",") !== "access_revision,kind,skill_id,version" || reference.kind !== "native_standalone_skill" || !UUID.test(reference.skill_id) || cloudId !== void 0 && reference.skill_id !== cloudId || !Number.isSafeInteger(reference.access_revision) || reference.access_revision < 0 || !reference.version || Object.keys(reference.version).sort().join(",") !== "digest,revision" || !Number.isSafeInteger(reference.version.revision) || reference.version.revision < 1 || !/^[a-f0-9]{64}$/.test(reference.version.digest)) {
1291
+ throw new Error("Native Skill identity is unavailable. Refresh before syncing.");
1292
+ }
1293
+ return structuredClone(reference);
1294
+ }
1295
+ function nativeDirectory(reference) {
1296
+ return "native-" + checkedNativeReference(reference).skill_id;
1297
+ }
1298
+ function sameNativeReference(left, right) {
1299
+ return Boolean(left && right && left.skill_id === right.skill_id && left.access_revision === right.access_revision && left.version.revision === right.version.revision && left.version.digest === right.version.digest);
1300
+ }
1301
+ function isNativeSkill(skill) {
1302
+ return skill.source === "native" || skill.native_reference !== void 0;
1303
+ }
1304
+ function assertNativePull(response) {
1305
+ const ids = /* @__PURE__ */ new Set();
1306
+ for (const skill of response.skills) {
1307
+ if (!isNativeSkill(skill)) continue;
1308
+ const reference = checkedNativeReference(skill.native_reference, skill.id);
1309
+ if (response.skill_sync_protocol !== 2 || skill.name !== nativeDirectory(reference) || ids.has(skill.id) || !/^[a-f0-9]{64}$/.test(response.sync_scope || "") || skill.native_scope !== response.sync_scope || !Array.isArray(skill.bundle_files) || skill.bundle_hydration_failed || typeof skill.skill_md !== "string") {
1310
+ throw new Error("Native Skill inventory is incomplete. Existing local files were retained.");
1311
+ }
1312
+ const paths = /* @__PURE__ */ new Set();
1313
+ let entrypoint = false;
1314
+ for (const file of skill.bundle_files) {
1315
+ if (typeof file.path !== "string" || typeof file.content_b64 !== "string" || file.path.startsWith("/") || file.path.includes("\\") || file.path.includes("\0") || file.path.split("/").some((part) => !part || part === "." || part === "..") || paths.has(file.path.toLowerCase()) || file.path.toLowerCase() === NATIVE_IDENTITY_FILE) {
1316
+ throw new Error("Native Skill bundle has a conflicting managed path.");
1317
+ }
1318
+ paths.add(file.path.toLowerCase());
1319
+ if (file.path === "SKILL.md") {
1320
+ entrypoint = Buffer.from(file.content_b64, "base64").equals(Buffer.from(skill.skill_md, "utf8"));
1321
+ }
1322
+ }
1323
+ if (!entrypoint) throw new Error("Native Skill instructions and files do not match.");
1324
+ ids.add(skill.id);
1325
+ }
1326
+ }
1327
+ function nativeFileDigest(bytes) {
1328
+ return createHash2("sha256").update(bytes).digest("hex");
1329
+ }
1330
+ function nativeFrontmatterName(markdown) {
1331
+ const header = markdown.match(/^---\s*\n([\s\S]*?)\n---/);
1332
+ return header?.[1].match(/^name\s*:\s*(.+)$/m)?.[1].trim().replace(/^['"]|['"]$/g, "") || "";
1333
+ }
1334
+ function checkedNativeIdentity(value) {
1335
+ const identity = value;
1336
+ if (!identity || identity.schema !== 1 || typeof identity.cloudId !== "string" || typeof identity.displayName !== "string" || typeof identity.frontmatterName !== "string" || !/^[a-f0-9]{64}$/.test(identity.scope) || !/^[a-f0-9]{64}$/.test(identity.folderHash) || !identity.files || !Object.keys(identity.files).includes("SKILL.md") || Object.entries(identity.files).some(([name, hash]) => name.startsWith("/") || name.includes("\\") || name.split("/").some((part) => !part || part === "." || part === "..") || !/^[a-f0-9]{64}$/.test(hash))) {
1337
+ throw new Error("Native Skill provenance is incomplete. Existing files were retained.");
1338
+ }
1339
+ return { ...identity, reference: checkedNativeReference(identity.reference, identity.cloudId) };
1340
+ }
1341
+ function nativeState(skill) {
1342
+ if (!isNativeSkill(skill)) return {};
1343
+ return {
1344
+ nativeReference: checkedNativeReference(skill.native_reference, skill.id),
1345
+ ...skill.native_settings ? { nativeSettingsRevision: skill.native_settings.revision } : {},
1346
+ nativeDisplayName: skill.display_name || skill.name,
1347
+ nativeScope: skill.native_scope,
1348
+ nativeUnavailable: false
1349
+ };
1350
+ }
1351
+ function bindNativeFolders(response, locals, state) {
1352
+ const nativeIds = new Set(response.skills.filter(isNativeSkill).map((skill) => skill.id));
1353
+ for (const entry of Object.values(state.skills)) if (entry.nativeReference || entry.nativeUnavailable) nativeIds.add(entry.cloudId);
1354
+ const byId = /* @__PURE__ */ new Map();
1355
+ for (const local of locals) {
1356
+ const id = local.cloudId || state.skills[local.name]?.cloudId;
1357
+ if (!id || !nativeIds.has(id) && !local.nativeReference) continue;
1358
+ if (local.nativeReference) checkedNativeReference(local.nativeReference, id);
1359
+ if (byId.has(id)) throw new Error("Two local folders claim the same native Skill. Keep both copies and resolve the conflict.");
1360
+ byId.set(id, local);
1361
+ }
1362
+ const skills = response.skills.map((skill) => isNativeSkill(skill) && byId.has(skill.id) ? { ...skill, name: byId.get(skill.id).name } : skill);
1363
+ for (const skill of skills.filter(isNativeSkill)) {
1364
+ if (skills.some((other) => other.id !== skill.id && other.name === skill.name)) {
1365
+ throw new Error("A native Skill and another Skill share a local folder. Existing files were retained.");
1366
+ }
1367
+ }
1368
+ return { ...response, skills };
1369
+ }
1370
+ function previousNativeState(state, skill) {
1371
+ const named = state.skills[skill.name];
1372
+ return named?.cloudId === skill.id ? named : Object.values(state.skills).find((entry) => entry.cloudId === skill.id);
1373
+ }
1374
+ function nativeLocalReady(skill, local) {
1375
+ return Boolean(local?.nativeFilesCurrent && local.folderHash === local.nativeBaseFolderHash && local.cloudId === skill.id && local.nativeScope === skill.native_scope && sameNativeReference(local.nativeReference, skill.native_reference));
1376
+ }
1377
+ function nativeLinkRows(response, locals) {
1378
+ return response.skills.map((skill) => isNativeSkill(skill) && !nativeLocalReady(skill, locals.find((row) => row.name === skill.name)) ? { ...skill, status: "disabled" } : skill);
1379
+ }
1380
+ function retainMissingNativeState(previous, next) {
1381
+ const present = new Set(Object.values(next.skills).map((row) => row.cloudId));
1382
+ const retained = Object.fromEntries(Object.entries(previous.skills).filter(([, row]) => (row.nativeReference || row.nativeUnavailable) && !present.has(row.cloudId)).map(([name, row]) => [name, { ...row, nativeUnavailable: true, verifiedAgentLinks: {} }]));
1383
+ const history = structuredClone(previous.nativeHistory || {});
1384
+ for (const [name, row] of [...Object.entries(previous.skills), ...Object.entries(next.skills)]) {
1385
+ if (!row.nativeReference && !row.nativeUnavailable) continue;
1386
+ const names = history[row.cloudId]?.names || [];
1387
+ history[row.cloudId] = { names: [.../* @__PURE__ */ new Set([...names, name])] };
1388
+ }
1389
+ return {
1390
+ ...next,
1391
+ skills: { ...retained, ...next.skills },
1392
+ ...Object.keys(history).length ? { nativeHistory: history } : {}
1393
+ };
1394
+ }
1395
+ function hasNativeFolderHistory(state, name) {
1396
+ return Object.values(state.nativeHistory || {}).some((row) => row.names.includes(name));
1397
+ }
1283
1398
  var HOME_DIR = os.homedir();
1284
1399
  var execFileAsync = promisify(execFile);
1400
+ var NativeSkillIdentityError = class extends Error {
1401
+ };
1285
1402
  var AGENTS_DIR = path.join(HOME_DIR, ".agents");
1286
1403
  var LEGACY_AGENTS_SKILLS_DIR = path.join(AGENTS_DIR, "skills");
1287
1404
  var NOTIS_SKILL_SYNC_ROOT = path.join(HOME_DIR, ".notis", "skills");
@@ -1341,15 +1458,18 @@ function sanitizePathSegment(value) {
1341
1458
  if (sanitized === raw) {
1342
1459
  return sanitized;
1343
1460
  }
1344
- return `${sanitized}-${createHash3("sha256").update(raw).digest("hex").slice(0, 12)}`;
1461
+ return `${sanitized}-${createHash22("sha256").update(raw).digest("hex").slice(0, 12)}`;
1345
1462
  }
1346
- function getSkillSyncPathsForUser(authUserId, agentsDir = AGENTS_DIR) {
1463
+ function getSkillSyncPathsForUser(authUserId, agentsDir = AGENTS_DIR, namespace, syncHome) {
1347
1464
  const safeUserId = sanitizePathSegment(authUserId);
1348
- const resolvedAgentsDir = path.resolve(agentsDir);
1349
- const syncRoot = getDefaultSyncRootForAgentsDir(resolvedAgentsDir);
1350
- const userRoot = path.join(syncRoot, "users", safeUserId);
1465
+ const homeDir = syncHome ? path.resolve(syncHome) : void 0;
1466
+ const resolvedAgentsDir = homeDir ? path.join(homeDir, ".agents") : path.resolve(agentsDir);
1467
+ const syncRoot = homeDir ? path.join(homeDir, ".notis", "skills") : getDefaultSyncRootForAgentsDir(resolvedAgentsDir);
1468
+ const userRoot = namespace ? path.join(syncRoot, "environments", sanitizePathSegment(namespace), "users", safeUserId) : path.join(syncRoot, "users", safeUserId);
1351
1469
  const legacyUserRoot = path.join(resolvedAgentsDir, "notis", "users", safeUserId);
1352
1470
  return {
1471
+ ...homeDir ? { homeDir } : {},
1472
+ ...namespace ? { isolatedNamespace: sanitizePathSegment(namespace) } : {},
1353
1473
  agentsDir: resolvedAgentsDir,
1354
1474
  syncRoot,
1355
1475
  legacySkillsDir: path.join(resolvedAgentsDir, "skills"),
@@ -1404,16 +1524,17 @@ function isIgnoredSkillEntry(entry) {
1404
1524
  if (entry.isDirectory()) return IGNORED_SKILL_DIRECTORIES.has(entry.name);
1405
1525
  return /\.py[co]$/i.test(entry.name);
1406
1526
  }
1407
- async function listFilesRecursive(dirPath) {
1527
+ async function listFilesRecursive(dirPath, rootPath = dirPath, strictLinks = false) {
1408
1528
  const entries = await fs.readdir(dirPath, { withFileTypes: true });
1409
1529
  const nested = await Promise.all(
1410
1530
  entries.map(async (entry) => {
1411
- if (isIgnoredSkillEntry(entry)) {
1531
+ if (isIgnoredSkillEntry(entry) || dirPath === rootPath && entry.name === NATIVE_IDENTITY_FILE) {
1412
1532
  return [];
1413
1533
  }
1414
1534
  const fullPath = path.join(dirPath, entry.name);
1535
+ if (strictLinks && entry.isSymbolicLink()) throw new Error("Native Skill bundles cannot contain symbolic links.");
1415
1536
  if (entry.isDirectory()) {
1416
- return listFilesRecursive(fullPath);
1537
+ return listFilesRecursive(fullPath, rootPath, strictLinks);
1417
1538
  }
1418
1539
  if (entry.isFile()) {
1419
1540
  return [fullPath];
@@ -1424,7 +1545,7 @@ async function listFilesRecursive(dirPath) {
1424
1545
  return nested.flat().sort();
1425
1546
  }
1426
1547
  async function computeFolderHash(dirPath) {
1427
- const hash = createHash3("sha256");
1548
+ const hash = createHash22("sha256");
1428
1549
  const filePaths = await listFilesRecursive(dirPath);
1429
1550
  for (const filePath of filePaths) {
1430
1551
  const relativePath = path.relative(dirPath, filePath);
@@ -1502,17 +1623,17 @@ function defaultTopLevelSkillSources(paths) {
1502
1623
  { label: "agents", root: paths.legacySkillsDir, priority: 2 },
1503
1624
  {
1504
1625
  label: "codex",
1505
- root: path.join(HOME_DIR, ".codex", "skills"),
1626
+ root: path.join(paths.homeDir || HOME_DIR, ".codex", "skills"),
1506
1627
  priority: 3
1507
1628
  },
1508
1629
  {
1509
1630
  label: "cursor",
1510
- root: path.join(HOME_DIR, ".cursor", "skills"),
1631
+ root: path.join(paths.homeDir || HOME_DIR, ".cursor", "skills"),
1511
1632
  priority: 4
1512
1633
  },
1513
1634
  {
1514
1635
  label: "claude",
1515
- root: path.join(HOME_DIR, ".claude", "skills"),
1636
+ root: path.join(paths.homeDir || HOME_DIR, ".claude", "skills"),
1516
1637
  priority: 5
1517
1638
  }
1518
1639
  );
@@ -1732,6 +1853,7 @@ async function ensureCanonicalSkillsDir(paths = DEFAULT_SYNC_PATHS) {
1732
1853
  async function scanLocalSkills(paths = DEFAULT_SYNC_PATHS) {
1733
1854
  await ensureCanonicalSkillsDir(paths);
1734
1855
  const sourceUrls = await readSkillLock(paths);
1856
+ const previousState = await readSyncState(paths);
1735
1857
  const entries = await fs.readdir(paths.skillsDir, { withFileTypes: true });
1736
1858
  const skills = await Promise.all(
1737
1859
  entries.map(async (entry) => {
@@ -1740,6 +1862,15 @@ async function scanLocalSkills(paths = DEFAULT_SYNC_PATHS) {
1740
1862
  }
1741
1863
  const skillDir = path.join(paths.skillsDir, entry.name);
1742
1864
  const skillMdPath = path.join(skillDir, "SKILL.md");
1865
+ let marker = null;
1866
+ try {
1867
+ marker = await fs.readFile(path.join(skillDir, NATIVE_IDENTITY_FILE), "utf8");
1868
+ } catch (error) {
1869
+ if (error.code !== "ENOENT") {
1870
+ throw new NativeSkillIdentityError("Native Skill provenance could not be read; existing files were retained.");
1871
+ }
1872
+ }
1873
+ const knownNative = marker !== null || Boolean(previousState.skills[entry.name]?.nativeReference) || Object.values(previousState.nativeHistory || {}).some((row) => row.names.includes(entry.name));
1743
1874
  try {
1744
1875
  const skillMd = await fs.readFile(skillMdPath, "utf8");
1745
1876
  const { description } = parseFrontMatter(skillMd);
@@ -1751,11 +1882,38 @@ async function scanLocalSkills(paths = DEFAULT_SYNC_PATHS) {
1751
1882
  folderHash,
1752
1883
  directoryPath: skillDir
1753
1884
  };
1885
+ if (marker !== null) {
1886
+ let identity;
1887
+ try {
1888
+ identity = checkedNativeIdentity(JSON.parse(marker));
1889
+ } catch {
1890
+ throw new NativeSkillIdentityError("Native Skill provenance is unreadable; do not upload it as a new Skill.");
1891
+ }
1892
+ let current = true;
1893
+ for (const [name, expected] of Object.entries(identity.files)) {
1894
+ try {
1895
+ if (nativeFileDigest(await fs.readFile(resolveSkillBundleFilePath(skillDir, name))) !== expected) current = false;
1896
+ } catch {
1897
+ current = false;
1898
+ }
1899
+ }
1900
+ const editedName = nativeFrontmatterName(skillMd);
1901
+ Object.assign(skill, {
1902
+ cloudId: identity.cloudId,
1903
+ nativeReference: identity.reference,
1904
+ nativeScope: identity.scope,
1905
+ nativeBaseFolderHash: identity.folderHash,
1906
+ nativeFilesCurrent: current,
1907
+ displayName: editedName && editedName !== identity.frontmatterName ? editedName : identity.displayName
1908
+ });
1909
+ }
1754
1910
  if (sourceUrls[entry.name]) {
1755
1911
  skill.sourceUrl = sourceUrls[entry.name];
1756
1912
  }
1757
1913
  return skill;
1758
- } catch {
1914
+ } catch (error) {
1915
+ if (error instanceof NativeSkillIdentityError) throw error;
1916
+ if (knownNative) throw new NativeSkillIdentityError("Native Skill files could not be read; existing files were retained.");
1759
1917
  return null;
1760
1918
  }
1761
1919
  })
@@ -1766,6 +1924,9 @@ function normalizeSyncState(state) {
1766
1924
  if (!state || state.version !== 1 || typeof state.skills !== "object") {
1767
1925
  return null;
1768
1926
  }
1927
+ if (state.nativeHistory && (typeof state.nativeHistory !== "object" || Array.isArray(state.nativeHistory) || Object.values(state.nativeHistory).some((row) => !row || !Array.isArray(row.names) || row.names.some((name) => typeof name !== "string")))) {
1928
+ throw new Error("Native Skill sync provenance is unreadable; existing files were retained.");
1929
+ }
1769
1930
  const normalizeStoredAgentTargets = (targets) => ({
1770
1931
  notis: Boolean(targets?.notis ?? true),
1771
1932
  claude_code: Boolean(targets?.claude_code ?? true),
@@ -1774,6 +1935,7 @@ function normalizeSyncState(state) {
1774
1935
  });
1775
1936
  return {
1776
1937
  version: 1,
1938
+ ...state.nativeHistory ? { nativeHistory: state.nativeHistory } : {},
1777
1939
  lastSyncedAt: typeof state.lastSyncedAt === "string" ? state.lastSyncedAt : null,
1778
1940
  skills: Object.fromEntries(
1779
1941
  Object.entries(state.skills || {}).map(([skillName, skillState]) => [
@@ -1933,13 +2095,18 @@ async function listRelativeFiles(dirPath) {
1933
2095
  function toPosixRelativePath(rootDir, filePath) {
1934
2096
  return path.relative(rootDir, filePath).split(path.sep).join("/");
1935
2097
  }
1936
- async function preserveUnmanagedFiles(existingDir, stagingDir, previouslyApplied) {
2098
+ async function preserveUnmanagedFiles(existingDir, stagingDir, previouslyApplied, strictConflicts = false) {
1937
2099
  const managed = previouslyApplied ? new Set(previouslyApplied) : null;
1938
2100
  for (const existingFile of await listFilesRecursive(existingDir)) {
1939
2101
  const relativePath = toPosixRelativePath(existingDir, existingFile);
1940
2102
  if (managed?.has(relativePath)) continue;
1941
2103
  const stagedPath = path.join(stagingDir, ...relativePath.split("/"));
1942
- if (await pathExists(stagedPath)) continue;
2104
+ if (await pathExists(stagedPath)) {
2105
+ if (strictConflicts && !(await fs.readFile(existingFile)).equals(await fs.readFile(stagedPath))) {
2106
+ throw new Error("An incoming native Skill file conflicts with a local file. Local files were retained.");
2107
+ }
2108
+ continue;
2109
+ }
1943
2110
  await fs.mkdir(path.dirname(stagedPath), { recursive: true });
1944
2111
  await fs.copyFile(existingFile, stagedPath);
1945
2112
  }
@@ -1967,13 +2134,41 @@ async function replaceSkillDirectoryAtomically(skillDir, populateDir, preserve)
1967
2134
  let promotedStaging = false;
1968
2135
  let cleanupError = null;
1969
2136
  try {
2137
+ if (preserve?.native && preserve.expectedFolderHash === void 0) {
2138
+ let existing = false;
2139
+ try {
2140
+ await fs.lstat(skillDir);
2141
+ existing = true;
2142
+ } catch (error) {
2143
+ if (error.code !== "ENOENT") throw error;
2144
+ }
2145
+ if (existing) throw new Error("Existing native Skill files need a readable baseline before replacement.");
2146
+ }
2147
+ if (preserve?.expectedFolderHash !== void 0 && (!await pathExists(skillDir) || await computeFolderHash(skillDir) !== preserve.expectedFolderHash)) {
2148
+ throw new Error("Local Skill files changed before sync; the newer local edits were retained.");
2149
+ }
1970
2150
  await populateDir(stagingDir);
1971
2151
  if (preserve && await pathExists(skillDir)) {
1972
- await preserveUnmanagedFiles(skillDir, stagingDir, preserve.previouslyApplied);
2152
+ await preserveUnmanagedFiles(skillDir, stagingDir, preserve.previouslyApplied, preserve.native);
2153
+ }
2154
+ if (preserve?.native) {
2155
+ const markerPath = path.join(stagingDir, NATIVE_IDENTITY_FILE);
2156
+ const marker = checkedNativeIdentity(JSON.parse(await fs.readFile(markerPath, "utf8")));
2157
+ marker.folderHash = await computeFolderHash(stagingDir);
2158
+ await fs.writeFile(markerPath, JSON.stringify(marker), { mode: 384 });
2159
+ }
2160
+ if (preserve?.expectedFolderHash !== void 0 && (!await pathExists(skillDir) || await computeFolderHash(skillDir) !== preserve.expectedFolderHash)) {
2161
+ throw new Error("Local Skill files changed during sync; the newer local edits were retained.");
1973
2162
  }
1974
2163
  if (await pathExists(skillDir)) {
1975
2164
  await fs.rename(skillDir, backupDir);
1976
2165
  movedExisting = true;
2166
+ if (preserve?.native && preserve.expectedFolderHash === void 0) {
2167
+ throw new Error("Native Skill files appeared during sync; existing files were retained.");
2168
+ }
2169
+ if (preserve?.expectedFolderHash !== void 0 && await computeFolderHash(backupDir) !== preserve.expectedFolderHash) {
2170
+ throw new Error("Local Skill files changed before publication; the newer local edits were retained.");
2171
+ }
1977
2172
  }
1978
2173
  await fs.rename(stagingDir, skillDir);
1979
2174
  promotedStaging = true;
@@ -2018,12 +2213,41 @@ async function createSkillBundleBase64(skill) {
2018
2213
  await fs.rm(zipPath, { force: true });
2019
2214
  }
2020
2215
  }
2216
+ async function captureNativeSkillFiles(skill) {
2217
+ const hash = createHash22("sha256");
2218
+ const files = [];
2219
+ for (const filePath of await listFilesRecursive(skill.directoryPath, skill.directoryPath, true)) {
2220
+ const relative2 = path.relative(skill.directoryPath, filePath);
2221
+ const bytes = await fs.readFile(filePath);
2222
+ hash.update(relative2);
2223
+ hash.update("\0");
2224
+ hash.update(bytes);
2225
+ hash.update("\0");
2226
+ files.push({ path: relative2.split(path.sep).join("/"), content_b64: bytes.toString("base64") });
2227
+ }
2228
+ if (hash.digest("hex") !== skill.folderHash) throw new Error("Local Skill files changed before upload. Sync again with the current files.");
2229
+ return files;
2230
+ }
2021
2231
  async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PATHS, options = {}) {
2232
+ if (isNativeSkill(skill)) {
2233
+ assertNativePull({
2234
+ skills: [{ ...skill, name: "native-" + skill.id }],
2235
+ skill_sync_protocol: 2,
2236
+ sync_scope: skill.native_scope,
2237
+ sync_enabled: true,
2238
+ last_synced_at: null
2239
+ });
2240
+ if (bundleBytes?.length) throw new Error("Native Skills require their verified complete file response.");
2241
+ }
2022
2242
  const skillDir = path.join(
2023
2243
  paths.skillsDir,
2024
2244
  safeName(skill.name, paths.skillsDir)
2025
2245
  );
2026
- const preserve = { previouslyApplied: options.previouslyApplied };
2246
+ const preserve = {
2247
+ previouslyApplied: options.previouslyApplied,
2248
+ expectedFolderHash: options.expectedFolderHash,
2249
+ native: isNativeSkill(skill)
2250
+ };
2027
2251
  if (bundleBytes?.length) {
2028
2252
  const bundlePath = path.join(
2029
2253
  os.tmpdir(),
@@ -2063,6 +2287,20 @@ async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PA
2063
2287
  );
2064
2288
  appliedFiles.push(toPosixRelativePath(stagingDir, filePath));
2065
2289
  }
2290
+ if (skill.native_reference) {
2291
+ const identity = {
2292
+ schema: 1,
2293
+ cloudId: skill.id,
2294
+ reference: skill.native_reference,
2295
+ displayName: skill.display_name || skill.name,
2296
+ frontmatterName: nativeFrontmatterName(skill.skill_md || ""),
2297
+ scope: skill.native_scope,
2298
+ folderHash: await computeFolderHash(stagingDir),
2299
+ files: Object.fromEntries((skill.bundle_files || []).map((file) => [file.path, nativeFileDigest(Buffer.from(file.content_b64, "base64"))]))
2300
+ };
2301
+ await fs.writeFile(path.join(stagingDir, NATIVE_IDENTITY_FILE), JSON.stringify(identity), { mode: 384 });
2302
+ appliedFiles.push(NATIVE_IDENTITY_FILE);
2303
+ }
2066
2304
  }, preserve);
2067
2305
  return { written: true, appliedFiles: appliedFiles.sort() };
2068
2306
  }
@@ -2098,7 +2336,7 @@ async function fetchSyncSettings(serverUrl, jwt) {
2098
2336
  }
2099
2337
  async function pullSkills(serverUrl, jwt) {
2100
2338
  return requestJson(`${serverUrl}/portal_skills/sync-pull`, jwt, {
2101
- body: {}
2339
+ body: { skill_sync_protocol: 2 }
2102
2340
  });
2103
2341
  }
2104
2342
  async function pushChangedSkills(serverUrl, jwt, changedSkills) {
@@ -2108,10 +2346,17 @@ async function pushChangedSkills(serverUrl, jwt, changedSkills) {
2108
2346
  skill_md: skill.skillMd,
2109
2347
  source_url: skill.sourceUrl,
2110
2348
  folder_hash: skill.folderHash,
2111
- bundle_base64: await createSkillBundleBase64(skill)
2349
+ ...skill.nativeReference ? { bundle_files: await captureNativeSkillFiles(skill) } : { bundle_base64: await createSkillBundleBase64(skill) },
2350
+ ...skill.cloudId ? { cloud_id: skill.cloudId } : {},
2351
+ ...skill.nativeReference ? {
2352
+ native_reference: skill.nativeReference,
2353
+ display_name: skill.displayName,
2354
+ native_scope: skill.nativeScope
2355
+ } : {}
2112
2356
  })));
2113
2357
  return requestJson(`${serverUrl}/portal_skills/sync-push`, jwt, {
2114
2358
  body: {
2359
+ skill_sync_protocol: 2,
2115
2360
  skills: payloadSkills
2116
2361
  }
2117
2362
  });
@@ -2124,7 +2369,26 @@ async function downloadSkillBundle(bundleUrl) {
2124
2369
  }
2125
2370
  return Buffer.from(await response.arrayBuffer());
2126
2371
  }
2127
- async function updateAgentTargets(serverUrl, jwt, skillId, targets, expectedUpdatedAt) {
2372
+ async function updateAgentTargets(serverUrl, jwt, skillId, targets, expectedUpdatedAt, native) {
2373
+ if (native) {
2374
+ checkedNativeReference(native.reference, skillId);
2375
+ if (!Number.isSafeInteger(native.settingsRevision) || native.settingsRevision < 0) throw new Error("Refresh native Skill settings before changing assignments.");
2376
+ const payload = {
2377
+ operation: "update",
2378
+ target: { skill_id: skillId, access_revision: native.reference.access_revision },
2379
+ revision: native.settingsRevision,
2380
+ patch: { agent_targets: Object.fromEntries(Object.entries(targets).sort(([a], [b]) => a.localeCompare(b))) }
2381
+ };
2382
+ const requestId = "sync-settings-" + createHash3("sha256").update(JSON.stringify(payload)).digest("hex");
2383
+ const saved = await requestJson(
2384
+ `${serverUrl}/portal_skills/native-settings`,
2385
+ jwt,
2386
+ { body: { ...payload, request_id: requestId } }
2387
+ );
2388
+ const current = saved.current || saved;
2389
+ if (saved.skill_id !== skillId || typeof saved.receipt_id !== "string" || !saved.receipt_id || current.target?.skill_id !== skillId || !current.settings) throw new Error("Native assignment update was not confirmed.");
2390
+ return { success: true, agent_targets: current.settings.agent_targets, updated_at: current.settings.updated_at };
2391
+ }
2128
2392
  return requestJson(`${serverUrl}/portal_skills/agent-targets`, jwt, {
2129
2393
  method: "PATCH",
2130
2394
  body: {
@@ -2443,22 +2707,27 @@ function selectCloudSkillsToApply(skills) {
2443
2707
  }
2444
2708
  return [...winners.values()];
2445
2709
  }
2446
- function getPushCandidates(localSkills, syncState, cloudCuratedSkillNames = /* @__PURE__ */ new Set(), cloudSkillNames, cloudAppOwnedSkillNames = /* @__PURE__ */ new Set()) {
2710
+ function getPushCandidates(localSkills, syncState, cloudCuratedSkillNames = /* @__PURE__ */ new Set(), cloudSkillNames, cloudSkills = []) {
2447
2711
  return localSkills.filter((skill) => {
2448
2712
  if (cloudCuratedSkillNames.has(skill.name)) {
2449
2713
  return false;
2450
2714
  }
2451
- if (cloudAppOwnedSkillNames.has(skill.name)) {
2452
- return false;
2715
+ const previous = syncState.skills[skill.name] || (skill.cloudId ? Object.values(syncState.skills).find((row) => row.cloudId === skill.cloudId) : void 0);
2716
+ const native = cloudSkills.find((row) => isNativeSkill(row) && row.id === (skill.cloudId || previous?.cloudId));
2717
+ if (native || skill.nativeReference || previous?.nativeReference || previous?.nativeUnavailable) {
2718
+ return Boolean(native && skill.nativeReference && skill.nativeScope === native.native_scope && (previous ? previous.folderHash !== skill.folderHash : skill.nativeBaseFolderHash !== skill.folderHash));
2453
2719
  }
2454
- const previous = syncState.skills[skill.name];
2455
2720
  if (!previous) {
2721
+ if (hasNativeFolderHistory(syncState, skill.name) || /^native-[a-f0-9-]{36}$/.test(skill.name)) return false;
2456
2722
  return true;
2457
2723
  }
2458
2724
  if (cloudSkillNames && !cloudSkillNames.has(skill.name)) {
2459
2725
  return false;
2460
2726
  }
2461
2727
  return previous.folderHash !== skill.folderHash;
2728
+ }).map((skill) => {
2729
+ const previous = syncState.skills[skill.name] || Object.values(syncState.skills).find((row) => row.cloudId === skill.cloudId);
2730
+ return { ...skill, ...skill.cloudId || previous?.cloudId ? { cloudId: skill.cloudId || previous.cloudId } : {} };
2462
2731
  });
2463
2732
  }
2464
2733
  async function writeCloudSkillWithBundleFallback(skill, dependencies) {
@@ -2530,6 +2799,24 @@ var DEFAULT_RUN_SKILL_SYNC_DEPS = {
2530
2799
  removeForeignAccountSymlinks,
2531
2800
  updateAgentTargets
2532
2801
  };
2802
+ function isolatedLinkOptions(paths) {
2803
+ if (!paths.isolatedNamespace && !paths.homeDir) return void 0;
2804
+ const root = paths.isolatedNamespace ? join3(paths.syncRoot, "environments", paths.isolatedNamespace, "agents") : paths.homeDir;
2805
+ return {
2806
+ agentSkillDirs: {
2807
+ notis: paths.skillsDir,
2808
+ claude_code: join3(root, ".claude", "skills"),
2809
+ cursor: join3(root, ".cursor", "skills"),
2810
+ codex: join3(root, ".codex", "skills")
2811
+ },
2812
+ legacyGlobalSkillsDir: join3(root, ".agents", "skills")
2813
+ };
2814
+ }
2815
+ function assertSyncEnvironment(pull, settings) {
2816
+ if ((pull.sync_namespace || null) !== (settings.sync_namespace || null) || settings.sync_scope !== void 0 && pull.sync_scope !== settings.sync_scope) {
2817
+ throw new Error("Skill sync environment changed. Existing mirrors were retained.");
2818
+ }
2819
+ }
2533
2820
  function toSkillMap(skills) {
2534
2821
  return new Map(skills.map((skill) => [skill.name, skill]));
2535
2822
  }
@@ -2546,7 +2833,7 @@ function decodeJwtSubject(jwt) {
2546
2833
  }
2547
2834
  }
2548
2835
  function cloudContentHash(skill) {
2549
- return createHash2("sha256").update(JSON.stringify({
2836
+ return createHash4("sha256").update(JSON.stringify({
2550
2837
  md: skill.skill_md,
2551
2838
  hash: skill.skill_folder_hash,
2552
2839
  source: skill.skill_source_url,
@@ -2561,6 +2848,12 @@ function shouldWriteCloudSkill(cloudSkill, localSkills, previousState) {
2561
2848
  if (!localSkill) {
2562
2849
  return true;
2563
2850
  }
2851
+ if (isNativeSkill(cloudSkill)) {
2852
+ if (localSkill.nativeScope && localSkill.nativeScope !== cloudSkill.native_scope) return false;
2853
+ if (nativeLocalReady(cloudSkill, localSkill)) return false;
2854
+ const nativePrevious = previousNativeState(previousState, cloudSkill);
2855
+ return localSkill.nativeFilesCurrent === true && localSkill.folderHash === localSkill.nativeBaseFolderHash || !localSkill.nativeReference && nativePrevious?.cloudId === cloudSkill.id && nativePrevious.folderHash === localSkill.folderHash;
2856
+ }
2564
2857
  const previous = previousState.skills[skillName];
2565
2858
  if (previous?.folderHash === localSkill.folderHash && previous.cloudContentHash === cloudContentHash(cloudSkill)) return false;
2566
2859
  if (previous && previous.cloudContentHash === void 0 && previous.cloudId === cloudSkill.id && previous.appliedCloudFolderHash !== void 0 && previous.appliedCloudFolderHash === cloudHash && previous.cloudUpdatedAt === cloudSkill.updated_at && previous.folderHash === localSkill.folderHash) {
@@ -2586,17 +2879,28 @@ function collectAppliedFiles(pullResponse, previousState, appliedFilesByName) {
2586
2879
  }
2587
2880
  return applied;
2588
2881
  }
2589
- function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLinks = {}, failedContentNames = /* @__PURE__ */ new Set(), appliedRevisions = {}, appliedFiles = {}) {
2882
+ function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLinks = {}, failedContentNames = /* @__PURE__ */ new Set(), appliedRevisions = {}, appliedFiles = {}, previousState = { version: 1, lastSyncedAt: null, skills: {} }) {
2590
2883
  const localSkillMap = toSkillMap(localSkills);
2591
2884
  const skills = Object.fromEntries(
2592
2885
  selectCloudSkillsToApply(pullResponse.skills).map((skill) => {
2593
2886
  const localSkill = localSkillMap.get(skill.name);
2594
2887
  const appliedCloudFolderHash = appliedRevisions[skill.name];
2595
2888
  const appliedFileList = appliedFiles[skill.name];
2889
+ if (isNativeSkill(skill) && !nativeLocalReady(skill, localSkill)) {
2890
+ const previous = previousNativeState(previousState, skill);
2891
+ return [skill.name, { ...previous || {
2892
+ cloudId: skill.id,
2893
+ folderHash: localSkill?.nativeBaseFolderHash || "",
2894
+ agentTargets: normalizeAgentTargets(skill.agent_targets),
2895
+ syncedAt: lastSyncedAt || (/* @__PURE__ */ new Date()).toISOString(),
2896
+ ...localSkill?.nativeReference ? { nativeReference: localSkill.nativeReference, nativeScope: localSkill.nativeScope } : {}
2897
+ }, nativeUnavailable: true, verifiedAgentLinks: {} }];
2898
+ }
2596
2899
  return [
2597
2900
  skill.name,
2598
2901
  {
2599
2902
  cloudId: skill.id,
2903
+ ...nativeState(skill),
2600
2904
  folderHash: localSkill?.folderHash || skill.skill_folder_hash || "",
2601
2905
  agentTargets: normalizeAgentTargets(skill.agent_targets),
2602
2906
  verifiedAgentLinks: skill.status === "active" ? verifiedAgentLinks[skill.name] ?? {} : {},
@@ -2609,17 +2913,30 @@ function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLi
2609
2913
  ];
2610
2914
  })
2611
2915
  );
2612
- return {
2916
+ for (const local of localSkills) {
2917
+ if (!local.nativeReference || Object.values(skills).some((row) => row.cloudId === local.cloudId)) continue;
2918
+ skills[local.name] = {
2919
+ cloudId: local.cloudId,
2920
+ nativeReference: local.nativeReference,
2921
+ nativeScope: local.nativeScope,
2922
+ nativeUnavailable: true,
2923
+ verifiedAgentLinks: {},
2924
+ folderHash: local.nativeBaseFolderHash || "",
2925
+ agentTargets: normalizeAgentTargets(),
2926
+ syncedAt: lastSyncedAt || (/* @__PURE__ */ new Date()).toISOString()
2927
+ };
2928
+ }
2929
+ return retainMissingNativeState(previousState, {
2613
2930
  version: 1,
2614
2931
  lastSyncedAt,
2615
2932
  skills
2616
- };
2933
+ });
2617
2934
  }
2618
2935
  function buildLocalSymlinkCandidates(pullResponse, localSkills, previousState) {
2619
2936
  const cloudSkillNames = new Set(
2620
2937
  pullResponse.skills.map((skill) => skill.name)
2621
2938
  );
2622
- const localOnlySkills = localSkills.filter((skill) => !cloudSkillNames.has(skill.name)).map((skill) => {
2939
+ const localOnlySkills = localSkills.filter((skill) => !cloudSkillNames.has(skill.name)).filter((skill) => !skill.nativeReference && !previousState.skills[skill.name]?.nativeReference && !previousState.skills[skill.name]?.nativeUnavailable && !hasNativeFolderHistory(previousState, skill.name) && !/^native-[a-f0-9-]{36}$/.test(skill.name)).map((skill) => {
2623
2940
  const previous = previousState.skills[skill.name];
2624
2941
  return {
2625
2942
  id: previous?.cloudId || `local-${skill.name}`,
@@ -2632,7 +2949,7 @@ function buildLocalSymlinkCandidates(pullResponse, localSkills, previousState) {
2632
2949
  status: "active"
2633
2950
  };
2634
2951
  });
2635
- return [...pullResponse.skills, ...localOnlySkills];
2952
+ return [...nativeLinkRows(pullResponse, localSkills), ...localOnlySkills];
2636
2953
  }
2637
2954
  function isEmptySyncState(state) {
2638
2955
  return state.lastSyncedAt === null && Object.keys(state.skills).length === 0;
@@ -2659,14 +2976,19 @@ function applyLegacyFirstRunState(localSkills, scopedState, legacyState) {
2659
2976
  skills: migratedSkills
2660
2977
  };
2661
2978
  }
2662
- async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previousState, syncPaths, deps, failures = [], writtenSkillNames = /* @__PURE__ */ new Set(), appliedFilesByName = {}) {
2979
+ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previousState, syncPaths, deps, failures = [], writtenSkillNames = /* @__PURE__ */ new Set(), appliedFilesByName = {}, acknowledgedNative = /* @__PURE__ */ new Map()) {
2663
2980
  const localSkillMap = toSkillMap(localSkills);
2664
2981
  const warnSkillSync = (message, error) => {
2665
2982
  console.warn(`[Notis] ${message}`, error);
2666
2983
  };
2667
2984
  let downloaded = 0;
2668
2985
  for (const cloudSkill of selectCloudSkillsToApply(pullResponse.skills)) {
2669
- if (!shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {
2986
+ const local = localSkillMap.get(cloudSkill.name);
2987
+ const acknowledged = isNativeSkill(cloudSkill) && local && acknowledgedNative.get(cloudSkill.id) === local.folderHash;
2988
+ if (!acknowledged && !shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {
2989
+ if (isNativeSkill(cloudSkill) && !nativeLocalReady(cloudSkill, local)) {
2990
+ failures.push({ name: cloudSkill.name, error: "Local native Skill changes were retained; resolve the version or identity conflict before using them." });
2991
+ }
2670
2992
  continue;
2671
2993
  }
2672
2994
  const outcome = await writeCloudSkillWithBundleFallback(cloudSkill, {
@@ -2674,7 +2996,8 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
2674
2996
  writeCloudSkillToDisk: (skill, bundleBytes) => deps.writeCloudSkillToDisk(skill, bundleBytes, syncPaths, {
2675
2997
  // What the last write put there. Everything else in the folder was
2676
2998
  // produced locally and survives this one.
2677
- previouslyApplied: previousState.skills[skill.name]?.appliedFiles
2999
+ previouslyApplied: previousNativeState(previousState, skill)?.appliedFiles,
3000
+ ...isNativeSkill(skill) && local ? { expectedFolderHash: local.folderHash } : {}
2678
3001
  }),
2679
3002
  onWarning: warnSkillSync
2680
3003
  });
@@ -2691,20 +3014,26 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
2691
3014
  return downloaded;
2692
3015
  }
2693
3016
  function assertSkillsPullAuthorized(pullResponse) {
3017
+ if (!Array.isArray(pullResponse.skills)) throw new Error("Skill inventory is unavailable; existing files were retained.");
3018
+ assertNativePull(pullResponse);
2694
3019
  if (pullResponse.entitlement_access?.code === "entitlement_upgrade_required" && pullResponse.entitlement_access.entitlement === "skills") {
2695
3020
  throw new Error(
2696
3021
  "Skill sync access was denied; preserving existing local skills."
2697
3022
  );
2698
3023
  }
2699
3024
  }
2700
- async function deactivateDeletedAgentSkills(serverUrl, jwt, pullResponse, previousState, scopedState, skillsDir, deps, failures) {
3025
+ async function deactivateDeletedAgentSkills(serverUrl, jwt, pullResponse, previousState, scopedState, skillsDir, deps, failures, linkOptions) {
2701
3026
  if (isEmptySyncState(scopedState)) {
2702
3027
  return 0;
2703
3028
  }
2704
3029
  const deletions = await deps.detectDeletedAgentSymlinks(
2705
- pullResponse.skills,
3030
+ pullResponse.skills.map((skill) => {
3031
+ const previous = isNativeSkill(skill) ? Object.entries(previousState.skills).find(([, row]) => row.cloudId === skill.id) : void 0;
3032
+ return previous ? { ...skill, name: previous[0] } : skill;
3033
+ }),
2706
3034
  previousState,
2707
- skillsDir
3035
+ skillsDir,
3036
+ linkOptions
2708
3037
  );
2709
3038
  if (deletions.length === 0) {
2710
3039
  return 0;
@@ -2720,21 +3049,34 @@ async function deactivateDeletedAgentSkills(serverUrl, jwt, pullResponse, previo
2720
3049
  }
2721
3050
  const fresh = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));
2722
3051
  assertSkillsPullAuthorized(fresh);
3052
+ assertSyncEnvironment(fresh, pullResponse);
2723
3053
  Object.assign(pullResponse, fresh);
2724
3054
  let needsRefresh = false;
2725
3055
  let deactivated = 0;
2726
3056
  for (const [skillId, { skillName, agents }] of agentsBySkill) {
2727
3057
  const skill = pullResponse.skills.find((item) => item.id === skillId);
2728
3058
  const previous = previousState.skills[skillName];
2729
- if (!skill?.updated_at || previous?.cloudUpdatedAt !== skill.updated_at) continue;
3059
+ if (!skill) continue;
3060
+ const native = isNativeSkill(skill);
3061
+ if (native) {
3062
+ if (!skill.native_settings || previous?.nativeSettingsRevision !== skill.native_settings.revision) continue;
3063
+ } else if (!skill.updated_at || previous?.cloudUpdatedAt !== skill.updated_at) continue;
2730
3064
  const patch = Object.fromEntries([...agents].map((agent) => [agent, false]));
2731
3065
  try {
2732
- const saved = await deps.updateAgentTargets(serverUrl, jwt, skillId, patch, skill.updated_at);
2733
- if (saved.success !== true || !saved.updated_at?.trim() || saved.updated_at === skill.updated_at || !["notis", "claude_code", "cursor", "codex"].every((agent) => typeof saved.agent_targets?.[agent] === "boolean") || ![...agents].every((agent) => saved.agent_targets[agent] === false)) {
3066
+ const saved = await deps.updateAgentTargets(
3067
+ serverUrl,
3068
+ jwt,
3069
+ skillId,
3070
+ patch,
3071
+ native ? void 0 : skill.updated_at,
3072
+ native ? { reference: skill.native_reference, settingsRevision: skill.native_settings.revision } : void 0
3073
+ );
3074
+ if (saved.success !== true || !saved.updated_at?.trim() || !native && saved.updated_at === skill.updated_at || !["notis", "claude_code", "cursor", "codex"].every((agent) => typeof saved.agent_targets?.[agent] === "boolean") || ![...agents].every((agent) => saved.agent_targets[agent] === false)) {
2734
3075
  throw new Error("Assignment update did not return a verified saved revision");
2735
3076
  }
2736
3077
  skill.agent_targets = saved.agent_targets;
2737
3078
  skill.updated_at = saved.updated_at;
3079
+ if (native) needsRefresh = true;
2738
3080
  deactivated += agents.size;
2739
3081
  } catch (error) {
2740
3082
  needsRefresh = true;
@@ -2745,6 +3087,7 @@ async function deactivateDeletedAgentSkills(serverUrl, jwt, pullResponse, previo
2745
3087
  if (needsRefresh) {
2746
3088
  const refreshed = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));
2747
3089
  assertSkillsPullAuthorized(refreshed);
3090
+ assertSyncEnvironment(refreshed, pullResponse);
2748
3091
  Object.assign(pullResponse, refreshed);
2749
3092
  }
2750
3093
  return deactivated;
@@ -2764,11 +3107,12 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
2764
3107
  "Cannot sync skills without a server-verified account identity."
2765
3108
  );
2766
3109
  }
2767
- const syncPaths = getSkillSyncPathsForUser(syncUserId);
2768
- const foreignLinksRemoved = await deps.removeForeignAccountSymlinks(
2769
- syncPaths.skillsDir
2770
- );
3110
+ const syncPaths = getSkillSyncPathsForUser(syncUserId, void 0, syncSettings.sync_namespace, options.syncHome);
2771
3111
  if (options.honorSyncEnabled !== false && !syncSettings.sync_enabled) {
3112
+ const foreignLinksRemoved2 = await deps.removeForeignAccountSymlinks(
3113
+ syncPaths.skillsDir,
3114
+ isolatedLinkOptions(syncPaths)
3115
+ );
2772
3116
  return {
2773
3117
  syncEnabled: false,
2774
3118
  pushed: 0,
@@ -2777,7 +3121,7 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
2777
3121
  deleted: 0,
2778
3122
  deactivated: 0,
2779
3123
  linked: 0,
2780
- removed: foreignLinksRemoved,
3124
+ removed: foreignLinksRemoved2,
2781
3125
  skipped: 0,
2782
3126
  lastSyncedAt: syncSettings.last_synced_at,
2783
3127
  failedPushes: []
@@ -2785,13 +3129,18 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
2785
3129
  }
2786
3130
  let pullResponse = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));
2787
3131
  assertSkillsPullAuthorized(pullResponse);
3132
+ assertSyncEnvironment(pullResponse, syncSettings);
3133
+ const foreignLinksRemoved = await deps.removeForeignAccountSymlinks(
3134
+ syncPaths.skillsDir,
3135
+ isolatedLinkOptions(syncPaths)
3136
+ );
2788
3137
  const cloudCuratedSkillNames = new Set(
2789
3138
  pullResponse.skills.filter((skill) => skill.source === "curated").map((skill) => skill.name)
2790
3139
  );
2791
3140
  const protectedSkillNames = /* @__PURE__ */ new Set([...cloudCuratedSkillNames, ...BASE_SKILL_NAMES]);
2792
3141
  const scopedState = withoutBaseSkillState(await deps.readSyncState(syncPaths));
2793
3142
  const assignmentFailures = [];
2794
- const deactivated = syncSettings.agent_targets_conditional_updates === true ? await deactivateDeletedAgentSkills(
3143
+ const deactivated = !syncPaths.isolatedNamespace && syncSettings.agent_targets_conditional_updates === true ? await deactivateDeletedAgentSkills(
2795
3144
  serverUrl,
2796
3145
  jwt,
2797
3146
  pullResponse,
@@ -2799,41 +3148,55 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
2799
3148
  scopedState,
2800
3149
  syncPaths.skillsDir,
2801
3150
  deps,
2802
- assignmentFailures
3151
+ assignmentFailures,
3152
+ isolatedLinkOptions(syncPaths)
2803
3153
  ) : 0;
3154
+ assertSyncEnvironment(pullResponse, syncSettings);
2804
3155
  const authUserId = decodeJwtSubject(jwt);
2805
3156
  let previousAuthState = null;
2806
- if (authUserId && authUserId !== syncUserId) {
2807
- const previousAuthPaths = getSkillSyncPathsForUser(authUserId);
3157
+ if (!syncPaths.isolatedNamespace && authUserId && authUserId !== syncUserId) {
3158
+ const previousAuthPaths = getSkillSyncPathsForUser(authUserId, void 0, void 0, options.syncHome);
2808
3159
  previousAuthState = await deps.readSyncState(previousAuthPaths);
2809
3160
  await deps.gatherTopLevelLocalSkills(syncPaths, {
2810
3161
  sourceRoots: [{ label: "previous-auth-scope", root: previousAuthPaths.skillsDir }],
2811
3162
  protectedSkillNames
2812
3163
  });
2813
3164
  }
2814
- await deps.gatherTopLevelLocalSkills(syncPaths, {
2815
- protectedSkillNames
2816
- });
3165
+ if (!syncPaths.isolatedNamespace) {
3166
+ await deps.gatherTopLevelLocalSkills(syncPaths, { protectedSkillNames });
3167
+ }
2817
3168
  const localSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES.has(skill.name));
2818
3169
  const previousState = withoutBaseSkillState(applyLegacyFirstRunState(
2819
3170
  localSkills,
2820
3171
  scopedState,
2821
- isEmptySyncState(scopedState) ? !previousAuthState || isEmptySyncState(previousAuthState) ? await deps.readLegacySyncState(syncPaths) : previousAuthState : null
3172
+ !syncPaths.isolatedNamespace && isEmptySyncState(scopedState) ? !previousAuthState || isEmptySyncState(previousAuthState) ? await deps.readLegacySyncState(syncPaths) : previousAuthState : null
2822
3173
  ));
3174
+ pullResponse = bindNativeFolders(pullResponse, localSkills, previousState);
2823
3175
  const gatheredSymlinkResult = await deps.syncSymlinks(
2824
3176
  buildLocalSymlinkCandidates(pullResponse, localSkills, previousState),
2825
- syncPaths.skillsDir
3177
+ syncPaths.skillsDir,
3178
+ isolatedLinkOptions(syncPaths)
2826
3179
  );
2827
3180
  const pushCandidates = getPushCandidates(
2828
3181
  localSkills,
2829
3182
  previousState,
2830
3183
  cloudCuratedSkillNames,
2831
3184
  new Set(pullResponse.skills.map((skill) => skill.name)),
2832
- new Set(pullResponse.skills.filter((skill) => skill.app_owned || skill.owner_app_id).map((skill) => skill.name))
3185
+ pullResponse.skills
2833
3186
  );
2834
3187
  const failedPushes = [];
3188
+ const acknowledgedNative = /* @__PURE__ */ new Map();
3189
+ let writeLocalSkills = localSkills;
2835
3190
  if (pushCandidates.length > 0) {
2836
3191
  const pushResult = await deps.pushChangedSkills(serverUrl, jwt, pushCandidates);
3192
+ for (const result of pushResult.skills || []) {
3193
+ const saved = result;
3194
+ const candidate = pushCandidates.find((local) => local.nativeReference && local.cloudId === saved.id);
3195
+ if (candidate && saved.native_reference) {
3196
+ checkedNativeReference(saved.native_reference, candidate.cloudId);
3197
+ acknowledgedNative.set(candidate.cloudId, candidate.folderHash);
3198
+ }
3199
+ }
2837
3200
  if (Array.isArray(pushResult?.failed) && pushResult.failed.length > 0) {
2838
3201
  failedPushes.push(...pushResult.failed);
2839
3202
  console.warn(
@@ -2842,13 +3205,21 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
2842
3205
  }
2843
3206
  pullResponse = withoutBaseSkills(await deps.pullSkills(serverUrl, jwt));
2844
3207
  assertSkillsPullAuthorized(pullResponse);
3208
+ assertSyncEnvironment(pullResponse, syncSettings);
3209
+ writeLocalSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES.has(skill.name));
3210
+ pullResponse = bindNativeFolders(pullResponse, writeLocalSkills, previousState);
2845
3211
  }
2846
3212
  const cloudSkillNames = new Set(pullResponse.skills.map((s) => s.name));
2847
3213
  let deleted = 0;
2848
3214
  for (const skillName of Object.keys(previousState.skills)) {
2849
3215
  if (!cloudSkillNames.has(skillName)) {
3216
+ const previous = previousState.skills[skillName];
3217
+ if (previous.nativeReference || previous.nativeUnavailable || pullResponse.skills.some((skill) => isNativeSkill(skill) && skill.id === previous.cloudId)) {
3218
+ await deps.removeAllSymlinksForSkill(skillName, syncPaths.skillsDir, isolatedLinkOptions(syncPaths));
3219
+ continue;
3220
+ }
2850
3221
  await deps.deleteLocalSkill(skillName, syncPaths);
2851
- await deps.removeAllSymlinksForSkill(skillName, syncPaths.skillsDir);
3222
+ await deps.removeAllSymlinksForSkill(skillName, syncPaths.skillsDir, isolatedLinkOptions(syncPaths));
2852
3223
  deleted += 1;
2853
3224
  }
2854
3225
  }
@@ -2857,18 +3228,20 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
2857
3228
  const appliedFilesByName = {};
2858
3229
  const downloaded = await writePulledSkillsToScopedMirror(
2859
3230
  pullResponse,
2860
- localSkills,
3231
+ writeLocalSkills,
2861
3232
  previousState,
2862
3233
  syncPaths,
2863
3234
  deps,
2864
3235
  failedDownloads,
2865
3236
  writtenSkillNames,
2866
- appliedFilesByName
3237
+ appliedFilesByName,
3238
+ acknowledgedNative
2867
3239
  );
2868
3240
  const finalLocalSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES.has(skill.name));
2869
3241
  const symlinkResult = await deps.syncSymlinks(
2870
3242
  buildLocalSymlinkCandidates(pullResponse, finalLocalSkills, previousState),
2871
- syncPaths.skillsDir
3243
+ syncPaths.skillsDir,
3244
+ isolatedLinkOptions(syncPaths)
2872
3245
  );
2873
3246
  const verifiedLinks = { ...symlinkResult.verifiedAgentLinks ?? {} };
2874
3247
  for (const failure of failedDownloads) delete verifiedLinks[failure.name];
@@ -2881,7 +3254,8 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
2881
3254
  verifiedLinks,
2882
3255
  new Set(failedDownloads.map((item) => item.name)),
2883
3256
  collectAppliedCloudRevisions(pullResponse, previousState, writtenSkillNames),
2884
- collectAppliedFiles(pullResponse, previousState, appliedFilesByName)
3257
+ collectAppliedFiles(pullResponse, previousState, appliedFilesByName),
3258
+ previousState
2885
3259
  ),
2886
3260
  syncPaths
2887
3261
  );
@@ -2904,13 +3278,13 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
2904
3278
  }
2905
3279
 
2906
3280
  // src/runtime/sync-skills.js
2907
- import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
3281
+ import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
2908
3282
  import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
2909
3283
  import { homedir as homedir3 } from "node:os";
2910
- import { dirname as dirname4, join as join4 } from "node:path";
3284
+ import { dirname as dirname4, join as join5 } from "node:path";
2911
3285
 
2912
3286
  // src/runtime/base-skills.js
2913
- import { dirname as dirname3, join as join3, relative, resolve as resolve2 } from "node:path";
3287
+ import { dirname as dirname3, join as join4, relative, resolve as resolve2 } from "node:path";
2914
3288
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2915
3289
  var BASE_SKILL_NAMES2 = Object.freeze([
2916
3290
  "notis-apps",
@@ -2926,7 +3300,7 @@ var DEFAULT_LOCK_POLL_MS = 50;
2926
3300
  async function lockSnapshot(lockDirectory) {
2927
3301
  try {
2928
3302
  const [raw, metadata] = await Promise.all([
2929
- readFile(join4(lockDirectory, "owner"), "utf8").catch(() => ""),
3303
+ readFile(join5(lockDirectory, "owner"), "utf8").catch(() => ""),
2930
3304
  stat(lockDirectory)
2931
3305
  ]);
2932
3306
  let owner = {};
@@ -2962,11 +3336,11 @@ function delay(milliseconds) {
2962
3336
  return new Promise((resolve3) => setTimeout(resolve3, milliseconds));
2963
3337
  }
2964
3338
  async function quarantineStaleLock(lockDirectory, snapshot) {
2965
- const quarantineRoot = join4(dirname4(lockDirectory), ".stale-operation-locks");
2966
- const snapshotToken = createHash4("sha256").update(JSON.stringify(snapshot)).digest("hex");
3339
+ const quarantineRoot = join5(dirname4(lockDirectory), ".stale-operation-locks");
3340
+ const snapshotToken = createHash5("sha256").update(JSON.stringify(snapshot)).digest("hex");
2967
3341
  await mkdir(quarantineRoot, { recursive: true, mode: 448 });
2968
3342
  try {
2969
- await rename(lockDirectory, join4(quarantineRoot, snapshotToken));
3343
+ await rename(lockDirectory, join5(quarantineRoot, snapshotToken));
2970
3344
  return true;
2971
3345
  } catch (error) {
2972
3346
  if (["EEXIST", "ENOTEMPTY", "ENOENT"].includes(error?.code)) return false;
@@ -2974,16 +3348,16 @@ async function quarantineStaleLock(lockDirectory, snapshot) {
2974
3348
  }
2975
3349
  }
2976
3350
  async function writeLockOwnerAtomically(lockDirectory, owner) {
2977
- const temporaryOwnerPath = join4(lockDirectory, `.owner.${owner.id}.tmp`);
2978
- const ownerPath = join4(lockDirectory, "owner");
3351
+ const temporaryOwnerPath = join5(lockDirectory, `.owner.${owner.id}.tmp`);
3352
+ const ownerPath = join5(lockDirectory, "owner");
2979
3353
  await writeFile(temporaryOwnerPath, JSON.stringify(owner), { mode: 384 });
2980
3354
  await rename(temporaryOwnerPath, ownerPath);
2981
3355
  }
2982
3356
  async function releaseOwnedLock(lockDirectory, ownerId) {
2983
3357
  const owner = await lockSnapshot(lockDirectory);
2984
3358
  if (owner?.id !== ownerId) return;
2985
- const quarantineRoot = join4(dirname4(lockDirectory), ".stale-operation-locks");
2986
- const releasedDirectory = join4(quarantineRoot, `released.${ownerId}`);
3359
+ const quarantineRoot = join5(dirname4(lockDirectory), ".stale-operation-locks");
3360
+ const releasedDirectory = join5(quarantineRoot, `released.${ownerId}`);
2987
3361
  await mkdir(quarantineRoot, { recursive: true, mode: 448 });
2988
3362
  try {
2989
3363
  await rename(lockDirectory, releasedDirectory);
@@ -3000,7 +3374,7 @@ async function withSkillSyncLock(callback, {
3000
3374
  pollMs = DEFAULT_LOCK_POLL_MS,
3001
3375
  now = () => Date.now()
3002
3376
  } = {}) {
3003
- const lockDirectory = join4(home, ".notis", "skills", ".operation-lock");
3377
+ const lockDirectory = join5(home, ".notis", "skills", ".operation-lock");
3004
3378
  const ownerId = `${process.pid}.${randomUUID2()}`;
3005
3379
  const deadline = now() + timeoutMs;
3006
3380
  await mkdir(dirname4(lockDirectory), { recursive: true, mode: 448 });
@@ -3039,7 +3413,7 @@ async function withSkillSyncLock(callback, {
3039
3413
  let heartbeatInFlight = Promise.resolve();
3040
3414
  const refreshHeartbeat = async () => {
3041
3415
  if (heartbeatStopped) return;
3042
- const temporaryOwnerPath = join4(lockDirectory, `.owner.${ownerId}.tmp`);
3416
+ const temporaryOwnerPath = join5(lockDirectory, `.owner.${ownerId}.tmp`);
3043
3417
  await writeFile(
3044
3418
  temporaryOwnerPath,
3045
3419
  JSON.stringify({ id: ownerId, pid: process.pid, at: now() }),
@@ -3050,7 +3424,7 @@ async function withSkillSyncLock(callback, {
3050
3424
  await rm(temporaryOwnerPath, { force: true });
3051
3425
  return;
3052
3426
  }
3053
- await rename(temporaryOwnerPath, join4(lockDirectory, "owner"));
3427
+ await rename(temporaryOwnerPath, join5(lockDirectory, "owner"));
3054
3428
  };
3055
3429
  const heartbeat = setInterval(() => {
3056
3430
  heartbeatInFlight = heartbeatInFlight.then(refreshHeartbeat).catch(() => void 0);
@@ -3087,10 +3461,10 @@ async function runAutomaticSkillSync({ profile, apiBase, userId }, {
3087
3461
  }
3088
3462
  async function main(args = process.argv.slice(2)) {
3089
3463
  const [profile, apiBase, userId] = args;
3090
- const root = join5(homedir4(), ".notis", "skills", "service");
3464
+ const root = join6(homedir4(), ".notis", "skills", "service");
3091
3465
  const record = (value) => {
3092
3466
  mkdirSync3(root, { recursive: true, mode: 448 });
3093
- const target = join5(root, "status.json");
3467
+ const target = join6(root, "status.json");
3094
3468
  const temporary = `${target}.${process.pid}.tmp`;
3095
3469
  writeFileSync3(temporary, JSON.stringify({ ...value, profile, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), { mode: 384 });
3096
3470
  renameSync3(temporary, target);