@notis_ai/cli 0.2.0-beta.197.1 → 0.2.0-beta.198.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.
@@ -1,21 +1,18 @@
1
1
  // src/skill-sync-worker.js
2
- import { mkdirSync as mkdirSync3, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
3
- import { homedir as homedir4 } from "node:os";
4
- import { join as join6 } from "node:path";
2
+ import { mkdirSync as mkdirSync5, renameSync as renameSync5, writeFileSync as writeFileSync5 } from "node:fs";
3
+ import { homedir as homedir5 } from "node:os";
4
+ import { join as join8 } from "node:path";
5
5
 
6
6
  // src/runtime/profiles.js
7
- import { randomUUID } from "node:crypto";
8
7
  import {
9
- existsSync,
10
- mkdirSync,
11
- readFileSync,
12
- renameSync,
13
- rmSync,
14
- statSync,
15
- writeFileSync
8
+ existsSync as existsSync2,
9
+ mkdirSync as mkdirSync2,
10
+ readFileSync as readFileSync2,
11
+ renameSync as renameSync2,
12
+ writeFileSync as writeFileSync2
16
13
  } from "node:fs";
17
- import { homedir } from "node:os";
18
- import { dirname, join, parse, resolve } from "node:path";
14
+ import { homedir as homedir2 } from "node:os";
15
+ import { dirname as dirname2, join as join2, parse, resolve as resolve2 } from "node:path";
19
16
 
20
17
  // src/runtime/errors.js
21
18
  var EXIT_CODES = {
@@ -51,6 +48,109 @@ var CliError = class extends Error {
51
48
  }
52
49
  };
53
50
 
51
+ // src/runtime/config-lock.js
52
+ import { randomUUID } from "node:crypto";
53
+ import {
54
+ existsSync,
55
+ mkdirSync,
56
+ readFileSync,
57
+ renameSync,
58
+ rmSync,
59
+ statSync,
60
+ writeFileSync
61
+ } from "node:fs";
62
+ import { homedir } from "node:os";
63
+ import { dirname, join, resolve } from "node:path";
64
+ var CONFIG_WRITE_LOCK_TIMEOUT_MS = 5e3;
65
+ var CONFIG_WRITE_LOCK_STALE_MS = 2e3;
66
+ var CONFIG_WRITE_LOCK_POLL_MS = 10;
67
+ function resolveCliConfigFile() {
68
+ const envConfigFile = process.env.NOTIS_CLI_CONFIG_FILE;
69
+ if (envConfigFile) {
70
+ return resolve(envConfigFile);
71
+ }
72
+ return join(homedir(), ".notis", "config.json");
73
+ }
74
+ function readLockOwner(lockDirectory) {
75
+ try {
76
+ return JSON.parse(readFileSync(join(lockDirectory, "owner"), "utf-8"));
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ function lockDirectoryMtime(lockDirectory) {
82
+ try {
83
+ return statSync(lockDirectory).mtimeMs;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+ function publishConfigWriteLock(lockDirectory, ownerId) {
89
+ const candidateDirectory = `${lockDirectory}.candidate-${ownerId}`;
90
+ try {
91
+ mkdirSync(candidateDirectory);
92
+ writeFileSync(
93
+ join(candidateDirectory, "owner"),
94
+ JSON.stringify({ id: ownerId, at: Date.now() }),
95
+ { mode: 384 }
96
+ );
97
+ try {
98
+ renameSync(candidateDirectory, lockDirectory);
99
+ return true;
100
+ } catch (error) {
101
+ if (!existsSync(lockDirectory)) throw error;
102
+ return false;
103
+ }
104
+ } finally {
105
+ rmSync(candidateDirectory, { recursive: true, force: true });
106
+ }
107
+ }
108
+ function withConfigFileWriteLock(configFile, callback) {
109
+ const lockDirectory = `${configFile}.write-lock`;
110
+ const ownerId = `${process.pid}.${randomUUID()}`;
111
+ const deadline = Date.now() + CONFIG_WRITE_LOCK_TIMEOUT_MS;
112
+ mkdirSync(dirname(configFile), { recursive: true });
113
+ for (; ; ) {
114
+ if (!existsSync(lockDirectory) && publishConfigWriteLock(lockDirectory, ownerId)) {
115
+ break;
116
+ }
117
+ const owner = readLockOwner(lockDirectory);
118
+ const observedMtime = lockDirectoryMtime(lockDirectory);
119
+ const ownerIsStale = owner?.id && Date.now() - Number(owner.at) > CONFIG_WRITE_LOCK_STALE_MS;
120
+ const ownerlessIsStale = !owner && observedMtime !== null && Date.now() - observedMtime > CONFIG_WRITE_LOCK_STALE_MS;
121
+ if (ownerIsStale || ownerlessIsStale) {
122
+ try {
123
+ const currentOwner = readLockOwner(lockDirectory);
124
+ const ownerUnchanged = owner?.id ? currentOwner?.id === owner.id : !currentOwner && lockDirectoryMtime(lockDirectory) === observedMtime;
125
+ if (ownerUnchanged) {
126
+ rmSync(lockDirectory, { recursive: true, force: true });
127
+ continue;
128
+ }
129
+ } catch {
130
+ }
131
+ }
132
+ if (Date.now() >= deadline) {
133
+ throw new Error(`Timed out waiting to update ${configFile}`);
134
+ }
135
+ Atomics.wait(
136
+ new Int32Array(new SharedArrayBuffer(4)),
137
+ 0,
138
+ 0,
139
+ CONFIG_WRITE_LOCK_POLL_MS
140
+ );
141
+ }
142
+ try {
143
+ return callback(configFile);
144
+ } finally {
145
+ try {
146
+ if (readLockOwner(lockDirectory)?.id === ownerId) {
147
+ rmSync(lockDirectory, { recursive: true, force: true });
148
+ }
149
+ } catch {
150
+ }
151
+ }
152
+ }
153
+
54
154
  // src/runtime/channel.js
55
155
  var RELEASE_CHANNELS = ["stable", "beta"];
56
156
  var CHANNEL_TAGS = { stable: "latest", beta: "beta" };
@@ -115,9 +215,9 @@ function getAuthRecovery(runtime = {}, { mode = "expired" } = {}) {
115
215
  }
116
216
 
117
217
  // src/runtime/profiles.js
118
- var CONFIG_DIR = join(homedir(), ".notis");
119
- var CONFIG_FILE = join(CONFIG_DIR, "config.json");
120
- var WORKSPACE_DIR = join(CONFIG_DIR, "workspace");
218
+ var CONFIG_DIR = join2(homedir2(), ".notis");
219
+ var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
220
+ var WORKSPACE_DIR = join2(CONFIG_DIR, "workspace");
121
221
  var DEFAULT_API_BASE = "https://api.notis.ai";
122
222
  var BETA_API_BASE = "https://api-beta.notis.ai";
123
223
  var DEFAULT_PROFILE = "default";
@@ -131,13 +231,10 @@ var LEGACY_DESKTOP_PROFILE_KEYS = [
131
231
  "desktop_app_name",
132
232
  "desktop_pid"
133
233
  ];
134
- var WORKTREE_RUNTIME_FILENAME = join(".context", "notis-runtime.json");
135
- var WORKTREE_ROUTING_FILENAME = join(".context", "notis-routing.json");
234
+ var WORKTREE_RUNTIME_FILENAME = join2(".context", "notis-runtime.json");
235
+ var WORKTREE_ROUTING_FILENAME = join2(".context", "notis-routing.json");
136
236
  var LOCAL_API_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
137
237
  var LIVE_API_HOSTS = /* @__PURE__ */ new Set(["api.notis.ai", "api-beta.notis.ai"]);
138
- var CONFIG_WRITE_LOCK_TIMEOUT_MS = 5e3;
139
- var CONFIG_WRITE_LOCK_STALE_MS = 2e3;
140
- var CONFIG_WRITE_LOCK_POLL_MS = 10;
141
238
  function clone(value) {
142
239
  return JSON.parse(JSON.stringify(value));
143
240
  }
@@ -211,23 +308,23 @@ function normalizeConfig(rawConfig = {}) {
211
308
  }
212
309
  function readJsonFile(path3) {
213
310
  try {
214
- return JSON.parse(readFileSync(path3, "utf-8"));
311
+ return JSON.parse(readFileSync2(path3, "utf-8"));
215
312
  } catch {
216
313
  return null;
217
314
  }
218
315
  }
219
316
  function findUp(filename, startDir = process.cwd()) {
220
- let current = resolve(startDir);
317
+ let current = resolve2(startDir);
221
318
  const root = parse(current).root;
222
319
  while (true) {
223
- const candidate = join(current, filename);
224
- if (existsSync(candidate)) {
320
+ const candidate = join2(current, filename);
321
+ if (existsSync2(candidate)) {
225
322
  return candidate;
226
323
  }
227
324
  if (current === root) {
228
325
  return null;
229
326
  }
230
- current = dirname(current);
327
+ current = dirname2(current);
231
328
  }
232
329
  }
233
330
  function processIsAlive(pid) {
@@ -298,17 +395,17 @@ function resolveWorktreeRuntime(startDir = process.cwd()) {
298
395
  function resolveConfigFile() {
299
396
  const envConfigFile = process.env.NOTIS_CLI_CONFIG_FILE;
300
397
  if (envConfigFile) {
301
- return resolve(envConfigFile);
398
+ return resolve2(envConfigFile);
302
399
  }
303
400
  return CONFIG_FILE;
304
401
  }
305
402
  function loadConfig() {
306
403
  const configFile = resolveConfigFile();
307
- if (!existsSync(configFile)) {
404
+ if (!existsSync2(configFile)) {
308
405
  return normalizeConfig({});
309
406
  }
310
407
  try {
311
- return normalizeConfig(JSON.parse(readFileSync(configFile, "utf-8")));
408
+ return normalizeConfig(JSON.parse(readFileSync2(configFile, "utf-8")));
312
409
  } catch {
313
410
  return normalizeConfig({});
314
411
  }
@@ -316,7 +413,7 @@ function loadConfig() {
316
413
  function writeConfig(configFile, config) {
317
414
  let rawConfig = null;
318
415
  try {
319
- rawConfig = JSON.parse(readFileSync(configFile, "utf-8"));
416
+ rawConfig = JSON.parse(readFileSync2(configFile, "utf-8"));
320
417
  } catch {
321
418
  }
322
419
  const normalized = normalizeConfig(config);
@@ -334,90 +431,13 @@ function writeConfig(configFile, config) {
334
431
  writeRawConfig(configFile, persisted);
335
432
  }
336
433
  function writeRawConfig(configFile, config) {
337
- mkdirSync(dirname(configFile), { recursive: true });
434
+ mkdirSync2(dirname2(configFile), { recursive: true });
338
435
  const temporaryFile = `${configFile}.${process.pid}.${Date.now()}.tmp`;
339
- writeFileSync(temporaryFile, JSON.stringify(config, null, 2), { mode: 384 });
340
- renameSync(temporaryFile, configFile);
341
- }
342
- function readLockOwner(lockDirectory) {
343
- try {
344
- return JSON.parse(readFileSync(join(lockDirectory, "owner"), "utf-8"));
345
- } catch {
346
- return null;
347
- }
348
- }
349
- function lockDirectoryMtime(lockDirectory) {
350
- try {
351
- return statSync(lockDirectory).mtimeMs;
352
- } catch {
353
- return null;
354
- }
355
- }
356
- function publishConfigWriteLock(lockDirectory, ownerId) {
357
- const candidateDirectory = `${lockDirectory}.candidate-${ownerId}`;
358
- try {
359
- mkdirSync(candidateDirectory);
360
- writeFileSync(
361
- join(candidateDirectory, "owner"),
362
- JSON.stringify({ id: ownerId, at: Date.now() }),
363
- { mode: 384 }
364
- );
365
- try {
366
- renameSync(candidateDirectory, lockDirectory);
367
- return true;
368
- } catch (error) {
369
- if (!existsSync(lockDirectory)) throw error;
370
- return false;
371
- }
372
- } finally {
373
- rmSync(candidateDirectory, { recursive: true, force: true });
374
- }
436
+ writeFileSync2(temporaryFile, JSON.stringify(config, null, 2), { mode: 384 });
437
+ renameSync2(temporaryFile, configFile);
375
438
  }
376
439
  function withConfigWriteLock(callback) {
377
- const configFile = resolveConfigFile();
378
- const lockDirectory = `${configFile}.write-lock`;
379
- const ownerId = `${process.pid}.${randomUUID()}`;
380
- const deadline = Date.now() + CONFIG_WRITE_LOCK_TIMEOUT_MS;
381
- mkdirSync(dirname(configFile), { recursive: true });
382
- for (; ; ) {
383
- if (!existsSync(lockDirectory) && publishConfigWriteLock(lockDirectory, ownerId)) {
384
- break;
385
- }
386
- const owner = readLockOwner(lockDirectory);
387
- const observedMtime = lockDirectoryMtime(lockDirectory);
388
- const ownerIsStale = owner?.id && Date.now() - Number(owner.at) > CONFIG_WRITE_LOCK_STALE_MS;
389
- const ownerlessIsStale = !owner && observedMtime !== null && Date.now() - observedMtime > CONFIG_WRITE_LOCK_STALE_MS;
390
- if (ownerIsStale || ownerlessIsStale) {
391
- try {
392
- const currentOwner = readLockOwner(lockDirectory);
393
- const ownerUnchanged = owner?.id ? currentOwner?.id === owner.id : !currentOwner && lockDirectoryMtime(lockDirectory) === observedMtime;
394
- if (ownerUnchanged) {
395
- rmSync(lockDirectory, { recursive: true, force: true });
396
- continue;
397
- }
398
- } catch {
399
- }
400
- }
401
- if (Date.now() >= deadline) {
402
- throw new Error(`Timed out waiting to update ${configFile}`);
403
- }
404
- Atomics.wait(
405
- new Int32Array(new SharedArrayBuffer(4)),
406
- 0,
407
- 0,
408
- CONFIG_WRITE_LOCK_POLL_MS
409
- );
410
- }
411
- try {
412
- return callback(configFile);
413
- } finally {
414
- try {
415
- if (readLockOwner(lockDirectory)?.id === ownerId) {
416
- rmSync(lockDirectory, { recursive: true, force: true });
417
- }
418
- } catch {
419
- }
420
- }
440
+ return withConfigFileWriteLock(resolveConfigFile(), callback);
421
441
  }
422
442
  function updateConfig(updater) {
423
443
  return withConfigWriteLock((configFile) => {
@@ -432,6 +452,13 @@ function getProfile(config, profileName) {
432
452
  const normalized = normalizeConfig(config);
433
453
  return Object.hasOwn(normalized.profiles, profileName) ? normalized.profiles[profileName] : {};
434
454
  }
455
+ function getCurrentProfileName(config, preferredName) {
456
+ const normalized = normalizeConfig(config);
457
+ if (preferredName && Object.hasOwn(normalized.profiles, preferredName)) {
458
+ return preferredName;
459
+ }
460
+ return normalized.current_profile || DEFAULT_PROFILE;
461
+ }
435
462
  function resolveProfileSelection(globalOptions = {}, worktreeRuntime = null, config, { allowUnknownProfile = false } = {}) {
436
463
  const normalized = normalizeConfig(config);
437
464
  const requested = globalOptions.profile || process.env.NOTIS_PROFILE || "";
@@ -813,20 +840,126 @@ import {
813
840
  timingSafeEqual
814
841
  } from "node:crypto";
815
842
  import {
816
- mkdirSync as mkdirSync2,
843
+ mkdirSync as mkdirSync4,
817
844
  readdirSync,
818
- readFileSync as readFileSync2,
819
- renameSync as renameSync2,
845
+ readFileSync as readFileSync4,
846
+ renameSync as renameSync4,
820
847
  rmdirSync,
821
848
  rmSync as rmSync2,
822
849
  statSync as statSync2,
823
- writeFileSync as writeFileSync2
850
+ writeFileSync as writeFileSync4
824
851
  } from "node:fs";
825
- import { homedir as homedir2 } from "node:os";
826
- import { basename, dirname as dirname2, join as join2 } from "node:path";
852
+ import { homedir as homedir3 } from "node:os";
853
+ import { basename, dirname as dirname4, join as join4 } from "node:path";
827
854
  import { fileURLToPath } from "node:url";
828
855
  import { createInterface } from "node:readline/promises";
829
- var OAUTH_LOCK_DIR = join2(homedir2(), ".notis", "oauth.lock");
856
+
857
+ // src/runtime/computer-identity.js
858
+ import { randomUUID as randomUUID2 } from "node:crypto";
859
+ import { execFileSync } from "node:child_process";
860
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
861
+ import { hostname as osHostname } from "node:os";
862
+ import { dirname as dirname3, join as join3 } from "node:path";
863
+ var COMPUTER_IDENTITY_VERSION = 1;
864
+ var COMPUTER_NAME_MAX_LENGTH = 64;
865
+ var COMPUTER_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
866
+ var FALLBACK_COMPUTER_NAME = "Computer";
867
+ function computerIdentityFile(configFile = resolveCliConfigFile()) {
868
+ return join3(dirname3(configFile), "computer.json");
869
+ }
870
+ function normalizeComputerName(value) {
871
+ if (typeof value !== "string") return null;
872
+ const collapsed = value.replace(/\s+/gu, " ").trim();
873
+ if (!collapsed) return null;
874
+ const characters = Array.from(collapsed);
875
+ if (characters.length <= COMPUTER_NAME_MAX_LENGTH) return collapsed;
876
+ return characters.slice(0, COMPUTER_NAME_MAX_LENGTH).join("").trim();
877
+ }
878
+ function readMacComputerName() {
879
+ try {
880
+ return execFileSync("/usr/sbin/scutil", ["--get", "ComputerName"], {
881
+ encoding: "utf8",
882
+ stdio: ["ignore", "pipe", "ignore"],
883
+ timeout: 2e3
884
+ });
885
+ } catch {
886
+ return null;
887
+ }
888
+ }
889
+ function defaultComputerName(options = {}) {
890
+ const platform = options.platform ?? process.platform;
891
+ if (platform === "darwin") {
892
+ const macName = normalizeComputerName((options.readMacComputerName ?? readMacComputerName)());
893
+ if (macName) return macName;
894
+ }
895
+ let host = "";
896
+ try {
897
+ host = String((options.hostname ?? osHostname)() || "");
898
+ } catch {
899
+ host = "";
900
+ }
901
+ return normalizeComputerName(host.replace(/\.local\.?$/iu, "")) || FALLBACK_COMPUTER_NAME;
902
+ }
903
+ function parseIdentityFile(file) {
904
+ let raw;
905
+ try {
906
+ raw = JSON.parse(readFileSync3(file, "utf8"));
907
+ } catch {
908
+ return null;
909
+ }
910
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
911
+ return raw;
912
+ }
913
+ function validStored(raw) {
914
+ if (!raw || raw.version !== COMPUTER_IDENTITY_VERSION) return null;
915
+ if (typeof raw.computer_id !== "string" || !COMPUTER_ID_PATTERN.test(raw.computer_id)) return null;
916
+ return {
917
+ computerId: raw.computer_id.toLowerCase(),
918
+ name: normalizeComputerName(raw.name)
919
+ };
920
+ }
921
+ function writeIdentityFile(file, stored) {
922
+ mkdirSync3(dirname3(file), { recursive: true });
923
+ const contents = {
924
+ version: COMPUTER_IDENTITY_VERSION,
925
+ computer_id: stored.computerId,
926
+ ...stored.name ? { name: stored.name } : {}
927
+ };
928
+ const temporaryFile = `${file}.${process.pid}.${Date.now()}.tmp`;
929
+ writeFileSync3(temporaryFile, `${JSON.stringify(contents, null, 2)}
930
+ `, { mode: 384 });
931
+ renameSync3(temporaryFile, file);
932
+ }
933
+ function withIdentity(stored, options) {
934
+ const defaultName = defaultComputerName(options);
935
+ return {
936
+ computerId: stored.computerId,
937
+ name: stored.name,
938
+ defaultName,
939
+ displayName: stored.name ?? defaultName
940
+ };
941
+ }
942
+ function readOrCreateLocked(configFile, file) {
943
+ const raw = parseIdentityFile(file);
944
+ const existing = validStored(raw);
945
+ if (existing) return existing;
946
+ const created = {
947
+ computerId: randomUUID2(),
948
+ // A file with a broken id keeps the name the user chose.
949
+ name: normalizeComputerName(raw?.name)
950
+ };
951
+ writeIdentityFile(file, created);
952
+ return created;
953
+ }
954
+ function readComputerIdentity(options = {}) {
955
+ const configFile = options.configFile ?? resolveCliConfigFile();
956
+ const file = computerIdentityFile(configFile);
957
+ const stored = validStored(parseIdentityFile(file)) ?? withConfigFileWriteLock(configFile, () => readOrCreateLocked(configFile, file));
958
+ return withIdentity(stored, options);
959
+ }
960
+
961
+ // src/runtime/oauth.js
962
+ var OAUTH_LOCK_DIR = join4(homedir3(), ".notis", "oauth.lock");
830
963
  var DEFAULT_REFRESH_EXPIRES_IN = 30 * 24 * 60 * 60;
831
964
  var PENDING_LOGIN_TTL_SECONDS = 30 * 60;
832
965
  var OAUTH_HTTP_TIMEOUT_MS = 1e4;
@@ -903,6 +1036,14 @@ function storedOAuthMetadata(runtime, profile) {
903
1036
  revocationEndpoint: `${issuer}/oauth/revoke`
904
1037
  };
905
1038
  }
1039
+ function cliDeviceParams() {
1040
+ try {
1041
+ const identity = readComputerIdentity({ configFile: resolveConfigFile() });
1042
+ return { device_id: identity.computerId, device_name: identity.displayName };
1043
+ } catch {
1044
+ return {};
1045
+ }
1046
+ }
906
1047
  function persistOAuthTokenResponse(runtime, metadata, tokenResponse) {
907
1048
  const now = Math.floor(Date.now() / 1e3);
908
1049
  const payload = decodeJwtPayload(tokenResponse.access_token);
@@ -959,11 +1100,11 @@ function listenerGlobalLockDir() {
959
1100
  return `${resolveConfigFile()}.oauth-listener-global.lock`;
960
1101
  }
961
1102
  function listenerGlobalLockOwnerFile(lockDir) {
962
- return join2(lockDir, "owner.json");
1103
+ return join4(lockDir, "owner.json");
963
1104
  }
964
1105
  function readListenerGlobalLockOwner(lockDir) {
965
1106
  try {
966
- return JSON.parse(readFileSync2(listenerGlobalLockOwnerFile(lockDir), "utf-8"));
1107
+ return JSON.parse(readFileSync4(listenerGlobalLockOwnerFile(lockDir), "utf-8"));
967
1108
  } catch {
968
1109
  return null;
969
1110
  }
@@ -971,7 +1112,7 @@ function readListenerGlobalLockOwner(lockDir) {
971
1112
  function writeListenerGlobalLockOwner(lock) {
972
1113
  const current = readListenerGlobalLockOwner(lock.lockDir);
973
1114
  if (current && current.owner_token !== lock.ownerToken) return false;
974
- writeFileSync2(listenerGlobalLockOwnerFile(lock.lockDir), JSON.stringify({
1115
+ writeFileSync4(listenerGlobalLockOwnerFile(lock.lockDir), JSON.stringify({
975
1116
  owner_token: lock.ownerToken,
976
1117
  owner_pid: process.pid,
977
1118
  updated_at: Date.now()
@@ -992,7 +1133,7 @@ function listenerGlobalLockIsStale(lockDir, staleMs) {
992
1133
  function retireStaleListenerGlobalLock(lockDir) {
993
1134
  const retiredDir = `${lockDir}.stale-${process.pid}-${base64url(randomBytes(8))}`;
994
1135
  try {
995
- renameSync2(lockDir, retiredDir);
1136
+ renameSync4(lockDir, retiredDir);
996
1137
  } catch {
997
1138
  return false;
998
1139
  }
@@ -1001,11 +1142,11 @@ function retireStaleListenerGlobalLock(lockDir) {
1001
1142
  }
1002
1143
  async function acquireListenerStartLock(runtime) {
1003
1144
  const lockDir = listenerStartLockDir(runtime);
1004
- mkdirSync2(dirname2(lockDir), { recursive: true });
1145
+ mkdirSync4(dirname4(lockDir), { recursive: true });
1005
1146
  const deadline = Date.now() + LISTENER_START_LOCK_STALE_MS * 2;
1006
1147
  for (; ; ) {
1007
1148
  try {
1008
- mkdirSync2(lockDir);
1149
+ mkdirSync4(lockDir);
1009
1150
  return lockDir;
1010
1151
  } catch (error) {
1011
1152
  if (error?.code !== "EEXIST") throw error;
@@ -1022,7 +1163,7 @@ async function acquireListenerStartLock(runtime) {
1022
1163
  "Timed out waiting for another CLI process to start browser authorization."
1023
1164
  );
1024
1165
  }
1025
- await new Promise((resolve3) => setTimeout(resolve3, 50));
1166
+ await new Promise((resolve4) => setTimeout(resolve4, 50));
1026
1167
  }
1027
1168
  }
1028
1169
  }
@@ -1039,11 +1180,11 @@ async function acquireListenerGlobalLock({
1039
1180
  heartbeatMs = LISTENER_GLOBAL_LOCK_HEARTBEAT_MS
1040
1181
  } = {}) {
1041
1182
  const lockDir = listenerGlobalLockDir();
1042
- mkdirSync2(dirname2(lockDir), { recursive: true });
1183
+ mkdirSync4(dirname4(lockDir), { recursive: true });
1043
1184
  const deadline = Date.now() + waitMs;
1044
1185
  for (; ; ) {
1045
1186
  try {
1046
- mkdirSync2(lockDir);
1187
+ mkdirSync4(lockDir);
1047
1188
  const lock = {
1048
1189
  lockDir,
1049
1190
  ownerToken: base64url(randomBytes(24)),
@@ -1069,7 +1210,7 @@ async function acquireListenerGlobalLock({
1069
1210
  "Timed out waiting for another CLI process to finish OAuth account changes."
1070
1211
  );
1071
1212
  }
1072
- await new Promise((resolve3) => setTimeout(resolve3, 50));
1213
+ await new Promise((resolve4) => setTimeout(resolve4, 50));
1073
1214
  }
1074
1215
  }
1075
1216
  }
@@ -1080,14 +1221,14 @@ function releaseListenerGlobalLock(lock) {
1080
1221
  if (owner?.owner_token !== lock.ownerToken) return;
1081
1222
  const releasedDir = `${lock.lockDir}.released-${process.pid}-${lock.ownerToken}`;
1082
1223
  try {
1083
- renameSync2(lock.lockDir, releasedDir);
1224
+ renameSync4(lock.lockDir, releasedDir);
1084
1225
  } catch {
1085
1226
  return;
1086
1227
  }
1087
1228
  const movedOwner = readListenerGlobalLockOwner(releasedDir);
1088
1229
  if (movedOwner?.owner_token !== lock.ownerToken) {
1089
1230
  try {
1090
- renameSync2(releasedDir, lock.lockDir);
1231
+ renameSync4(releasedDir, lock.lockDir);
1091
1232
  } catch {
1092
1233
  }
1093
1234
  return;
@@ -1159,11 +1300,11 @@ function lockIsStale() {
1159
1300
  }
1160
1301
  }
1161
1302
  async function acquireRefreshLock(runtime, waitMs = 6e4) {
1162
- mkdirSync2(join2(homedir2(), ".notis"), { recursive: true });
1303
+ mkdirSync4(join4(homedir3(), ".notis"), { recursive: true });
1163
1304
  const deadline = Date.now() + waitMs;
1164
1305
  for (; ; ) {
1165
1306
  try {
1166
- mkdirSync2(OAUTH_LOCK_DIR);
1307
+ mkdirSync4(OAUTH_LOCK_DIR);
1167
1308
  return true;
1168
1309
  } catch (error) {
1169
1310
  if (error?.code !== "EEXIST") throw error;
@@ -1182,7 +1323,7 @@ async function acquireRefreshLock(runtime, waitMs = 6e4) {
1182
1323
  if (Date.now() >= deadline) {
1183
1324
  throw oauthError("oauth_refresh_lock_timeout", "Timed out waiting for another CLI process to refresh OAuth.");
1184
1325
  }
1185
- await new Promise((resolve3) => setTimeout(resolve3, 100));
1326
+ await new Promise((resolve4) => setTimeout(resolve4, 100));
1186
1327
  }
1187
1328
  }
1188
1329
  }
@@ -1217,7 +1358,8 @@ async function refreshOAuthCredential(runtime, fetchImpl = fetch) {
1217
1358
  grant_type: "refresh_token",
1218
1359
  refresh_token: profile.oauth_refresh_token,
1219
1360
  client_id: profile.oauth_client_id,
1220
- resource: metadata.resource
1361
+ resource: metadata.resource,
1362
+ ...cliDeviceParams()
1221
1363
  })
1222
1364
  },
1223
1365
  fetchImpl
@@ -1257,7 +1399,8 @@ async function refreshOAuthCredential(runtime, fetchImpl = fetch) {
1257
1399
 
1258
1400
  // dist/skill-sync/index.js
1259
1401
  import { createHash as createHash4 } from "node:crypto";
1260
- import { join as join3 } from "node:path";
1402
+ import { promises as fs3 } from "node:fs";
1403
+ import { dirname as dirname5, join as join5 } from "node:path";
1261
1404
  import { createHash as createHash2 } from "node:crypto";
1262
1405
  import { createHash as createHash22 } from "crypto";
1263
1406
  import { execFile } from "child_process";
@@ -1374,8 +1517,16 @@ function previousNativeState(state, skill) {
1374
1517
  function nativeLocalReady(skill, local) {
1375
1518
  return Boolean(local?.nativeFilesCurrent && local.folderHash === local.nativeBaseFolderHash && local.cloudId === skill.id && local.nativeScope === skill.native_scope && sameNativeReference(local.nativeReference, skill.native_reference));
1376
1519
  }
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);
1520
+ function pendingLegacyUpgrade(skill, local, state) {
1521
+ if (!isNativeSkill(skill) || !local || local.nativeReference || local.cloudId) return false;
1522
+ const previous = previousNativeState(state, skill);
1523
+ return Boolean(previous && !previous.nativeReference);
1524
+ }
1525
+ function nativeLinkRows(response, locals, state) {
1526
+ return response.skills.map((skill) => {
1527
+ const local = locals.find((row) => row.name === skill.name);
1528
+ return isNativeSkill(skill) && !nativeLocalReady(skill, local) && !(state && pendingLegacyUpgrade(skill, local, state)) ? { ...skill, status: "disabled" } : skill;
1529
+ });
1379
1530
  }
1380
1531
  function retainMissingNativeState(previous, next) {
1381
1532
  const present = new Set(Object.values(next.skills).map((row) => row.cloudId));
@@ -2149,7 +2300,12 @@ async function replaceSkillDirectoryAtomically(skillDir, populateDir, preserve)
2149
2300
  }
2150
2301
  await populateDir(stagingDir);
2151
2302
  if (preserve && await pathExists(skillDir)) {
2152
- await preserveUnmanagedFiles(skillDir, stagingDir, preserve.previouslyApplied, preserve.native);
2303
+ await preserveUnmanagedFiles(
2304
+ skillDir,
2305
+ stagingDir,
2306
+ preserve.previouslyApplied,
2307
+ preserve.native && !preserve.legacyBaseline
2308
+ );
2153
2309
  }
2154
2310
  if (preserve?.native) {
2155
2311
  const markerPath = path.join(stagingDir, NATIVE_IDENTITY_FILE);
@@ -2246,7 +2402,9 @@ async function writeCloudSkillToDisk(skill, bundleBytes, paths = DEFAULT_SYNC_PA
2246
2402
  const preserve = {
2247
2403
  previouslyApplied: options.previouslyApplied,
2248
2404
  expectedFolderHash: options.expectedFolderHash,
2249
- native: isNativeSkill(skill)
2405
+ native: isNativeSkill(skill),
2406
+ // An unchanged legacy folder upgrading in place: shared files take the cloud version.
2407
+ legacyBaseline: isNativeSkill(skill) && options.legacyBaseline === true && options.expectedFolderHash !== void 0
2250
2408
  };
2251
2409
  if (bundleBytes?.length) {
2252
2410
  const bundlePath = path.join(
@@ -2799,17 +2957,55 @@ var DEFAULT_RUN_SKILL_SYNC_DEPS = {
2799
2957
  removeForeignAccountSymlinks,
2800
2958
  updateAgentTargets
2801
2959
  };
2960
+ var MIRROR_ONLY_DEPS = {
2961
+ removeForeignAccountSymlinks: async () => 0,
2962
+ removeAllSymlinksForSkill: async () => 0,
2963
+ syncSymlinks: async () => ({ linked: 0, removed: 0, skipped: 0, verifiedAgentLinks: {} })
2964
+ };
2965
+ async function forgetOtherAccountsAgentLinks(paths) {
2966
+ const usersDir = dirname5(dirname5(paths.syncStatePath));
2967
+ let entries;
2968
+ try {
2969
+ entries = await fs3.readdir(usersDir, { withFileTypes: true });
2970
+ } catch {
2971
+ return;
2972
+ }
2973
+ for (const entry of entries) {
2974
+ if (!entry.isDirectory()) continue;
2975
+ const statePath = join5(usersDir, entry.name, ".notis-sync.json");
2976
+ if (statePath === paths.syncStatePath) continue;
2977
+ let state = null;
2978
+ try {
2979
+ state = JSON.parse(await fs3.readFile(statePath, "utf8"));
2980
+ } catch {
2981
+ continue;
2982
+ }
2983
+ if (!state || typeof state !== "object" || !state.skills || typeof state.skills !== "object") continue;
2984
+ let changed = false;
2985
+ for (const skill of Object.values(state.skills)) {
2986
+ if (skill && typeof skill === "object" && "verifiedAgentLinks" in skill) {
2987
+ delete skill.verifiedAgentLinks;
2988
+ changed = true;
2989
+ }
2990
+ }
2991
+ if (!changed) continue;
2992
+ const temporaryPath = `${statePath}.${process.pid}.${Date.now()}.tmp`;
2993
+ await fs3.writeFile(temporaryPath, `${JSON.stringify(state, null, 2)}
2994
+ `, "utf8");
2995
+ await fs3.rename(temporaryPath, statePath);
2996
+ }
2997
+ }
2802
2998
  function isolatedLinkOptions(paths) {
2803
2999
  if (!paths.isolatedNamespace && !paths.homeDir) return void 0;
2804
- const root = paths.isolatedNamespace ? join3(paths.syncRoot, "environments", paths.isolatedNamespace, "agents") : paths.homeDir;
3000
+ const root = paths.isolatedNamespace ? join5(paths.syncRoot, "environments", paths.isolatedNamespace, "agents") : paths.homeDir;
2805
3001
  return {
2806
3002
  agentSkillDirs: {
2807
3003
  notis: paths.skillsDir,
2808
- claude_code: join3(root, ".claude", "skills"),
2809
- cursor: join3(root, ".cursor", "skills"),
2810
- codex: join3(root, ".codex", "skills")
3004
+ claude_code: join5(root, ".claude", "skills"),
3005
+ cursor: join5(root, ".cursor", "skills"),
3006
+ codex: join5(root, ".codex", "skills")
2811
3007
  },
2812
- legacyGlobalSkillsDir: join3(root, ".agents", "skills")
3008
+ legacyGlobalSkillsDir: join5(root, ".agents", "skills")
2813
3009
  };
2814
3010
  }
2815
3011
  function assertSyncEnvironment(pull, settings) {
@@ -2886,6 +3082,10 @@ function buildSyncState(pullResponse, localSkills, lastSyncedAt, verifiedAgentLi
2886
3082
  const localSkill = localSkillMap.get(skill.name);
2887
3083
  const appliedCloudFolderHash = appliedRevisions[skill.name];
2888
3084
  const appliedFileList = appliedFiles[skill.name];
3085
+ if (pendingLegacyUpgrade(skill, localSkill, previousState)) {
3086
+ const { nativeUnavailable: _unavailable, ...previous } = previousNativeState(previousState, skill);
3087
+ return [skill.name, { ...previous, verifiedAgentLinks: verifiedAgentLinks[skill.name] ?? {} }];
3088
+ }
2889
3089
  if (isNativeSkill(skill) && !nativeLocalReady(skill, localSkill)) {
2890
3090
  const previous = previousNativeState(previousState, skill);
2891
3091
  return [skill.name, { ...previous || {
@@ -2949,7 +3149,7 @@ function buildLocalSymlinkCandidates(pullResponse, localSkills, previousState) {
2949
3149
  status: "active"
2950
3150
  };
2951
3151
  });
2952
- return [...nativeLinkRows(pullResponse, localSkills), ...localOnlySkills];
3152
+ return [...nativeLinkRows(pullResponse, localSkills, previousState), ...localOnlySkills];
2953
3153
  }
2954
3154
  function isEmptySyncState(state) {
2955
3155
  return state.lastSyncedAt === null && Object.keys(state.skills).length === 0;
@@ -2985,6 +3185,7 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
2985
3185
  for (const cloudSkill of selectCloudSkillsToApply(pullResponse.skills)) {
2986
3186
  const local = localSkillMap.get(cloudSkill.name);
2987
3187
  const acknowledged = isNativeSkill(cloudSkill) && local && acknowledgedNative.get(cloudSkill.id) === local.folderHash;
3188
+ const legacyBaseline = Boolean(local && pendingLegacyUpgrade(cloudSkill, local, previousState) && previousNativeState(previousState, cloudSkill)?.folderHash === local.folderHash);
2988
3189
  if (!acknowledged && !shouldWriteCloudSkill(cloudSkill, localSkillMap, previousState)) {
2989
3190
  if (isNativeSkill(cloudSkill) && !nativeLocalReady(cloudSkill, local)) {
2990
3191
  failures.push({ name: cloudSkill.name, error: "Local native Skill changes were retained; resolve the version or identity conflict before using them." });
@@ -2997,7 +3198,8 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
2997
3198
  // What the last write put there. Everything else in the folder was
2998
3199
  // produced locally and survives this one.
2999
3200
  previouslyApplied: previousNativeState(previousState, skill)?.appliedFiles,
3000
- ...isNativeSkill(skill) && local ? { expectedFolderHash: local.folderHash } : {}
3201
+ ...isNativeSkill(skill) && local ? { expectedFolderHash: local.folderHash } : {},
3202
+ ...legacyBaseline ? { legacyBaseline } : {}
3001
3203
  }),
3002
3204
  onWarning: warnSkillSync
3003
3205
  });
@@ -3093,9 +3295,11 @@ async function deactivateDeletedAgentSkills(serverUrl, jwt, pullResponse, previo
3093
3295
  return deactivated;
3094
3296
  }
3095
3297
  async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
3298
+ const linkAgentFolders = options.linkAgentFolders !== false;
3096
3299
  const deps = {
3097
3300
  ...DEFAULT_RUN_SKILL_SYNC_DEPS,
3098
- ...dependencies
3301
+ ...dependencies,
3302
+ ...linkAgentFolders ? {} : MIRROR_ONLY_DEPS
3099
3303
  };
3100
3304
  const syncSettings = await deps.fetchSyncSettings(
3101
3305
  serverUrl,
@@ -3134,13 +3338,16 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
3134
3338
  syncPaths.skillsDir,
3135
3339
  isolatedLinkOptions(syncPaths)
3136
3340
  );
3341
+ if (linkAgentFolders && !syncPaths.isolatedNamespace) {
3342
+ await forgetOtherAccountsAgentLinks(syncPaths);
3343
+ }
3137
3344
  const cloudCuratedSkillNames = new Set(
3138
3345
  pullResponse.skills.filter((skill) => skill.source === "curated").map((skill) => skill.name)
3139
3346
  );
3140
3347
  const protectedSkillNames = /* @__PURE__ */ new Set([...cloudCuratedSkillNames, ...BASE_SKILL_NAMES]);
3141
3348
  const scopedState = withoutBaseSkillState(await deps.readSyncState(syncPaths));
3142
3349
  const assignmentFailures = [];
3143
- const deactivated = !syncPaths.isolatedNamespace && syncSettings.agent_targets_conditional_updates === true ? await deactivateDeletedAgentSkills(
3350
+ const deactivated = linkAgentFolders && !syncPaths.isolatedNamespace && syncSettings.agent_targets_conditional_updates === true ? await deactivateDeletedAgentSkills(
3144
3351
  serverUrl,
3145
3352
  jwt,
3146
3353
  pullResponse,
@@ -3162,7 +3369,7 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
3162
3369
  protectedSkillNames
3163
3370
  });
3164
3371
  }
3165
- if (!syncPaths.isolatedNamespace) {
3372
+ if (linkAgentFolders && !syncPaths.isolatedNamespace) {
3166
3373
  await deps.gatherTopLevelLocalSkills(syncPaths, { protectedSkillNames });
3167
3374
  }
3168
3375
  const localSkills = (await deps.scanLocalSkills(syncPaths)).filter((skill) => !BASE_SKILL_NAMES.has(skill.name));
@@ -3278,20 +3485,20 @@ async function runSkillSync(serverUrl, jwt, dependencies = {}, options = {}) {
3278
3485
  }
3279
3486
 
3280
3487
  // src/runtime/sync-skills.js
3281
- import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
3488
+ import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
3282
3489
  import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
3283
- import { homedir as homedir3 } from "node:os";
3284
- import { dirname as dirname4, join as join5 } from "node:path";
3490
+ import { homedir as homedir4 } from "node:os";
3491
+ import { dirname as dirname7, join as join7 } from "node:path";
3285
3492
 
3286
3493
  // src/runtime/base-skills.js
3287
- import { dirname as dirname3, join as join4, relative, resolve as resolve2 } from "node:path";
3494
+ import { dirname as dirname6, join as join6, relative, resolve as resolve3 } from "node:path";
3288
3495
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3289
3496
  var BASE_SKILL_NAMES2 = Object.freeze([
3290
3497
  "notis-apps",
3291
3498
  "notis-query",
3292
3499
  "notis-cli"
3293
3500
  ]);
3294
- var HERE = dirname3(fileURLToPath2(import.meta.url));
3501
+ var HERE = dirname6(fileURLToPath2(import.meta.url));
3295
3502
 
3296
3503
  // src/runtime/sync-skills.js
3297
3504
  var DEFAULT_LOCK_TIMEOUT_MS = 3e4;
@@ -3300,7 +3507,7 @@ var DEFAULT_LOCK_POLL_MS = 50;
3300
3507
  async function lockSnapshot(lockDirectory) {
3301
3508
  try {
3302
3509
  const [raw, metadata] = await Promise.all([
3303
- readFile(join5(lockDirectory, "owner"), "utf8").catch(() => ""),
3510
+ readFile(join7(lockDirectory, "owner"), "utf8").catch(() => ""),
3304
3511
  stat(lockDirectory)
3305
3512
  ]);
3306
3513
  let owner = {};
@@ -3333,14 +3540,14 @@ function processIsAlive2(pid) {
3333
3540
  }
3334
3541
  }
3335
3542
  function delay(milliseconds) {
3336
- return new Promise((resolve3) => setTimeout(resolve3, milliseconds));
3543
+ return new Promise((resolve4) => setTimeout(resolve4, milliseconds));
3337
3544
  }
3338
3545
  async function quarantineStaleLock(lockDirectory, snapshot) {
3339
- const quarantineRoot = join5(dirname4(lockDirectory), ".stale-operation-locks");
3546
+ const quarantineRoot = join7(dirname7(lockDirectory), ".stale-operation-locks");
3340
3547
  const snapshotToken = createHash5("sha256").update(JSON.stringify(snapshot)).digest("hex");
3341
3548
  await mkdir(quarantineRoot, { recursive: true, mode: 448 });
3342
3549
  try {
3343
- await rename(lockDirectory, join5(quarantineRoot, snapshotToken));
3550
+ await rename(lockDirectory, join7(quarantineRoot, snapshotToken));
3344
3551
  return true;
3345
3552
  } catch (error) {
3346
3553
  if (["EEXIST", "ENOTEMPTY", "ENOENT"].includes(error?.code)) return false;
@@ -3348,16 +3555,16 @@ async function quarantineStaleLock(lockDirectory, snapshot) {
3348
3555
  }
3349
3556
  }
3350
3557
  async function writeLockOwnerAtomically(lockDirectory, owner) {
3351
- const temporaryOwnerPath = join5(lockDirectory, `.owner.${owner.id}.tmp`);
3352
- const ownerPath = join5(lockDirectory, "owner");
3558
+ const temporaryOwnerPath = join7(lockDirectory, `.owner.${owner.id}.tmp`);
3559
+ const ownerPath = join7(lockDirectory, "owner");
3353
3560
  await writeFile(temporaryOwnerPath, JSON.stringify(owner), { mode: 384 });
3354
3561
  await rename(temporaryOwnerPath, ownerPath);
3355
3562
  }
3356
3563
  async function releaseOwnedLock(lockDirectory, ownerId) {
3357
3564
  const owner = await lockSnapshot(lockDirectory);
3358
3565
  if (owner?.id !== ownerId) return;
3359
- const quarantineRoot = join5(dirname4(lockDirectory), ".stale-operation-locks");
3360
- const releasedDirectory = join5(quarantineRoot, `released.${ownerId}`);
3566
+ const quarantineRoot = join7(dirname7(lockDirectory), ".stale-operation-locks");
3567
+ const releasedDirectory = join7(quarantineRoot, `released.${ownerId}`);
3361
3568
  await mkdir(quarantineRoot, { recursive: true, mode: 448 });
3362
3569
  try {
3363
3570
  await rename(lockDirectory, releasedDirectory);
@@ -3368,16 +3575,16 @@ async function releaseOwnedLock(lockDirectory, ownerId) {
3368
3575
  await rm(releasedDirectory, { recursive: true, force: true });
3369
3576
  }
3370
3577
  async function withSkillSyncLock(callback, {
3371
- home = homedir3(),
3578
+ home = homedir4(),
3372
3579
  timeoutMs = DEFAULT_LOCK_TIMEOUT_MS,
3373
3580
  staleMs = DEFAULT_LOCK_STALE_MS,
3374
3581
  pollMs = DEFAULT_LOCK_POLL_MS,
3375
3582
  now = () => Date.now()
3376
3583
  } = {}) {
3377
- const lockDirectory = join5(home, ".notis", "skills", ".operation-lock");
3378
- const ownerId = `${process.pid}.${randomUUID2()}`;
3584
+ const lockDirectory = join7(home, ".notis", "skills", ".operation-lock");
3585
+ const ownerId = `${process.pid}.${randomUUID3()}`;
3379
3586
  const deadline = now() + timeoutMs;
3380
- await mkdir(dirname4(lockDirectory), { recursive: true, mode: 448 });
3587
+ await mkdir(dirname7(lockDirectory), { recursive: true, mode: 448 });
3381
3588
  for (; ; ) {
3382
3589
  try {
3383
3590
  await mkdir(lockDirectory, { mode: 448 });
@@ -3413,7 +3620,7 @@ async function withSkillSyncLock(callback, {
3413
3620
  let heartbeatInFlight = Promise.resolve();
3414
3621
  const refreshHeartbeat = async () => {
3415
3622
  if (heartbeatStopped) return;
3416
- const temporaryOwnerPath = join5(lockDirectory, `.owner.${ownerId}.tmp`);
3623
+ const temporaryOwnerPath = join7(lockDirectory, `.owner.${ownerId}.tmp`);
3417
3624
  await writeFile(
3418
3625
  temporaryOwnerPath,
3419
3626
  JSON.stringify({ id: ownerId, pid: process.pid, at: now() }),
@@ -3424,7 +3631,7 @@ async function withSkillSyncLock(callback, {
3424
3631
  await rm(temporaryOwnerPath, { force: true });
3425
3632
  return;
3426
3633
  }
3427
- await rename(temporaryOwnerPath, join5(lockDirectory, "owner"));
3634
+ await rename(temporaryOwnerPath, join7(lockDirectory, "owner"));
3428
3635
  };
3429
3636
  const heartbeat = setInterval(() => {
3430
3637
  heartbeatInFlight = heartbeatInFlight.then(refreshHeartbeat).catch(() => void 0);
@@ -3441,12 +3648,20 @@ async function withSkillSyncLock(callback, {
3441
3648
  }
3442
3649
 
3443
3650
  // src/skill-sync-worker.js
3651
+ function currentCliProfile() {
3652
+ try {
3653
+ return getCurrentProfileName(loadConfig());
3654
+ } catch {
3655
+ return null;
3656
+ }
3657
+ }
3444
3658
  async function runAutomaticSkillSync({ profile, apiBase, userId }, {
3445
3659
  resolveRuntime = resolveRuntimeProfile,
3446
3660
  refresh = ensureFreshOAuthCredential,
3447
3661
  settings = fetchSyncSettings,
3448
3662
  sync = runSkillSync,
3449
- lock = withSkillSyncLock
3663
+ lock = withSkillSyncLock,
3664
+ currentProfile = currentCliProfile
3450
3665
  } = {}) {
3451
3666
  const runtime = resolveRuntime({ profile }, { requireAuth: true });
3452
3667
  if (runtime.credentialKind !== "oauth" || runtime.apiBase !== apiBase || runtime.oauthUserId !== userId) throw new Error("Automatic skill sync account changed; run notis skills sync to rebind");
@@ -3454,20 +3669,21 @@ async function runAutomaticSkillSync({ profile, apiBase, userId }, {
3454
3669
  const saved = await settings(runtime.apiBase, runtime.jwt);
3455
3670
  if (saved.user_id !== userId) throw new Error("Automatic skill sync identity mismatch");
3456
3671
  if (!saved.sync_enabled) return { status: "disabled" };
3672
+ const linkAgentFolders = (currentProfile() ?? profile) === profile;
3457
3673
  const result = await lock(() => sync(runtime.apiBase, runtime.jwt, {
3458
3674
  fetchSyncSettings: async () => saved
3459
- }, { honorSyncEnabled: true }));
3675
+ }, { honorSyncEnabled: true, ...linkAgentFolders ? {} : { linkAgentFolders: false } }));
3460
3676
  return { status: result.failedLinks?.length || result.failedPushes?.length ? "partial" : "synced", ...result };
3461
3677
  }
3462
3678
  async function main(args = process.argv.slice(2)) {
3463
3679
  const [profile, apiBase, userId] = args;
3464
- const root = join6(homedir4(), ".notis", "skills", "service");
3680
+ const root = join8(homedir5(), ".notis", "skills", "service");
3465
3681
  const record = (value) => {
3466
- mkdirSync3(root, { recursive: true, mode: 448 });
3467
- const target = join6(root, "status.json");
3682
+ mkdirSync5(root, { recursive: true, mode: 448 });
3683
+ const target = join8(root, "status.json");
3468
3684
  const temporary = `${target}.${process.pid}.tmp`;
3469
- writeFileSync3(temporary, JSON.stringify({ ...value, profile, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), { mode: 384 });
3470
- renameSync3(temporary, target);
3685
+ writeFileSync5(temporary, JSON.stringify({ ...value, profile, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), { mode: 384 });
3686
+ renameSync5(temporary, target);
3471
3687
  };
3472
3688
  const deadline = setTimeout(() => {
3473
3689
  record({ status: "error", code: "sync_timeout" });