@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.
- package/README.md +5 -5
- package/dist/agent-hooks/notis-agent-hook.mjs +729 -460
- package/dist/base-skills/notis-apps/SKILL.md +4 -3
- package/dist/base-skills/notis-apps/references/sdk.md +3 -1
- package/dist/base-skills/notis-apps/references/views.md +1 -1
- package/dist/base-skills/notis-cli/SKILL.md +7 -5
- package/dist/base-skills/notis-query/references/documents.md +4 -3
- package/dist/sdk/src/components/DocumentPage.tsx +6 -3
- package/dist/sdk/src/runtime.ts +10 -2
- package/dist/skill-sync/index.js +78 -12
- package/dist/skill-sync/index.js.map +3 -3
- package/dist/skill-sync-worker.mjs +391 -175
- package/dist/view-renderer/dist/host.js +86 -86
- package/dist/view-renderer/src/host.tsx +4 -2
- package/package.json +1 -1
- package/skills/notis-apps/cli.md +1 -1
- package/src/command-specs/auth.js +2 -2
- package/src/command-specs/profile.js +42 -1
- package/src/command-specs/skills.js +13 -1
- package/src/runtime/computer-identity.d.ts +30 -0
- package/src/runtime/computer-identity.js +151 -0
- package/src/runtime/config-lock.js +135 -0
- package/src/runtime/oauth.js +18 -1
- package/src/runtime/profiles.js +5 -107
- package/src/runtime/skill-sync/index.ts +83 -7
- package/src/runtime/skill-sync/local-scanner.ts +7 -4
- package/src/runtime/skill-sync/native-plan.ts +14 -3
- package/src/runtime/sync-skills.d.ts +3 -1
- package/src/runtime/sync-skills.js +10 -2
- package/src/skill-sync-worker.js +14 -2
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
// src/skill-sync-worker.js
|
|
2
|
-
import { mkdirSync as
|
|
3
|
-
import { homedir as
|
|
4
|
-
import { join as
|
|
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
|
-
|
|
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 =
|
|
119
|
-
var CONFIG_FILE =
|
|
120
|
-
var WORKSPACE_DIR =
|
|
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 =
|
|
135
|
-
var WORKTREE_ROUTING_FILENAME =
|
|
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(
|
|
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 =
|
|
317
|
+
let current = resolve2(startDir);
|
|
221
318
|
const root = parse(current).root;
|
|
222
319
|
while (true) {
|
|
223
|
-
const candidate =
|
|
224
|
-
if (
|
|
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 =
|
|
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
|
|
398
|
+
return resolve2(envConfigFile);
|
|
302
399
|
}
|
|
303
400
|
return CONFIG_FILE;
|
|
304
401
|
}
|
|
305
402
|
function loadConfig() {
|
|
306
403
|
const configFile = resolveConfigFile();
|
|
307
|
-
if (!
|
|
404
|
+
if (!existsSync2(configFile)) {
|
|
308
405
|
return normalizeConfig({});
|
|
309
406
|
}
|
|
310
407
|
try {
|
|
311
|
-
return normalizeConfig(JSON.parse(
|
|
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(
|
|
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
|
-
|
|
434
|
+
mkdirSync2(dirname2(configFile), { recursive: true });
|
|
338
435
|
const temporaryFile = `${configFile}.${process.pid}.${Date.now()}.tmp`;
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
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
|
|
843
|
+
mkdirSync as mkdirSync4,
|
|
817
844
|
readdirSync,
|
|
818
|
-
readFileSync as
|
|
819
|
-
renameSync as
|
|
845
|
+
readFileSync as readFileSync4,
|
|
846
|
+
renameSync as renameSync4,
|
|
820
847
|
rmdirSync,
|
|
821
848
|
rmSync as rmSync2,
|
|
822
849
|
statSync as statSync2,
|
|
823
|
-
writeFileSync as
|
|
850
|
+
writeFileSync as writeFileSync4
|
|
824
851
|
} from "node:fs";
|
|
825
|
-
import { homedir as
|
|
826
|
-
import { basename, dirname as
|
|
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
|
-
|
|
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
|
|
1103
|
+
return join4(lockDir, "owner.json");
|
|
963
1104
|
}
|
|
964
1105
|
function readListenerGlobalLockOwner(lockDir) {
|
|
965
1106
|
try {
|
|
966
|
-
return JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
1183
|
+
mkdirSync4(dirname4(lockDir), { recursive: true });
|
|
1043
1184
|
const deadline = Date.now() + waitMs;
|
|
1044
1185
|
for (; ; ) {
|
|
1045
1186
|
try {
|
|
1046
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1303
|
+
mkdirSync4(join4(homedir3(), ".notis"), { recursive: true });
|
|
1163
1304
|
const deadline = Date.now() + waitMs;
|
|
1164
1305
|
for (; ; ) {
|
|
1165
1306
|
try {
|
|
1166
|
-
|
|
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((
|
|
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 {
|
|
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
|
|
1378
|
-
|
|
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(
|
|
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 ?
|
|
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:
|
|
2809
|
-
cursor:
|
|
2810
|
-
codex:
|
|
3004
|
+
claude_code: join5(root, ".claude", "skills"),
|
|
3005
|
+
cursor: join5(root, ".cursor", "skills"),
|
|
3006
|
+
codex: join5(root, ".codex", "skills")
|
|
2811
3007
|
},
|
|
2812
|
-
legacyGlobalSkillsDir:
|
|
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
|
|
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
|
|
3284
|
-
import { dirname as
|
|
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
|
|
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 =
|
|
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(
|
|
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((
|
|
3543
|
+
return new Promise((resolve4) => setTimeout(resolve4, milliseconds));
|
|
3337
3544
|
}
|
|
3338
3545
|
async function quarantineStaleLock(lockDirectory, snapshot) {
|
|
3339
|
-
const quarantineRoot =
|
|
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,
|
|
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 =
|
|
3352
|
-
const ownerPath =
|
|
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 =
|
|
3360
|
-
const releasedDirectory =
|
|
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 =
|
|
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 =
|
|
3378
|
-
const ownerId = `${process.pid}.${
|
|
3584
|
+
const lockDirectory = join7(home, ".notis", "skills", ".operation-lock");
|
|
3585
|
+
const ownerId = `${process.pid}.${randomUUID3()}`;
|
|
3379
3586
|
const deadline = now() + timeoutMs;
|
|
3380
|
-
await mkdir(
|
|
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 =
|
|
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,
|
|
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 =
|
|
3680
|
+
const root = join8(homedir5(), ".notis", "skills", "service");
|
|
3465
3681
|
const record = (value) => {
|
|
3466
|
-
|
|
3467
|
-
const target =
|
|
3682
|
+
mkdirSync5(root, { recursive: true, mode: 448 });
|
|
3683
|
+
const target = join8(root, "status.json");
|
|
3468
3684
|
const temporary = `${target}.${process.pid}.tmp`;
|
|
3469
|
-
|
|
3470
|
-
|
|
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" });
|