relayax-cli 0.1.99 → 0.1.992
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/check-update.d.ts +2 -0
- package/dist/commands/check-update.js +61 -0
- package/dist/commands/install.js +22 -18
- package/dist/commands/publish.js +54 -3
- package/dist/commands/update.js +24 -0
- package/dist/index.js +2 -0
- package/dist/lib/device-hash.d.ts +1 -0
- package/dist/lib/device-hash.js +16 -0
- package/dist/lib/preamble.d.ts +3 -0
- package/dist/lib/preamble.js +63 -0
- package/dist/lib/update-cache.d.ts +2 -0
- package/dist/lib/update-cache.js +51 -0
- package/dist/lib/version-check.d.ts +10 -0
- package/dist/lib/version-check.js +74 -0
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCheckUpdate = registerCheckUpdate;
|
|
4
|
+
const version_check_js_1 = require("../lib/version-check.js");
|
|
5
|
+
function registerCheckUpdate(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('check-update [slug]')
|
|
8
|
+
.description('CLI 및 설치된 팀의 업데이트를 확인합니다')
|
|
9
|
+
.option('--quiet', '업데이트가 있을 때만 머신 리더블 출력')
|
|
10
|
+
.option('--force', '캐시를 무시하고 강제 체크')
|
|
11
|
+
.action(async (slug, opts) => {
|
|
12
|
+
const quiet = opts.quiet ?? false;
|
|
13
|
+
const force = opts.force ?? false;
|
|
14
|
+
// CLI version check
|
|
15
|
+
const cliResult = await (0, version_check_js_1.checkCliVersion)(force);
|
|
16
|
+
if (cliResult) {
|
|
17
|
+
if (quiet) {
|
|
18
|
+
console.log(`CLI_UPGRADE_AVAILABLE ${cliResult.current} ${cliResult.latest}`);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
console.log(`\n\x1b[33m⚠ relay v${cliResult.latest} available\x1b[0m (현재 v${cliResult.current})`);
|
|
22
|
+
console.log(` 실행: npm update -g relayax-cli\n`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Team version check
|
|
26
|
+
if (slug) {
|
|
27
|
+
const teamResult = await (0, version_check_js_1.checkTeamVersion)(slug, force);
|
|
28
|
+
if (teamResult) {
|
|
29
|
+
if (quiet) {
|
|
30
|
+
const byAuthor = teamResult.author ? ` ${teamResult.author}` : '';
|
|
31
|
+
console.log(`TEAM_UPGRADE_AVAILABLE ${slug} ${teamResult.current} ${teamResult.latest}${byAuthor}`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const byAuthor = teamResult.author ? ` \x1b[90m(by @${teamResult.author})\x1b[0m` : '';
|
|
35
|
+
console.log(`\x1b[33m⚠ ${slug} v${teamResult.latest} available\x1b[0m${byAuthor} (현재 v${teamResult.current})`);
|
|
36
|
+
console.log(` 실행: relay update ${slug}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (!quiet && !cliResult) {
|
|
40
|
+
console.log('모든 것이 최신 상태입니다.');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const teamResults = await (0, version_check_js_1.checkAllTeams)(force);
|
|
45
|
+
for (const result of teamResults) {
|
|
46
|
+
if (quiet) {
|
|
47
|
+
const byAuthor = result.author ? ` ${result.author}` : '';
|
|
48
|
+
console.log(`TEAM_UPGRADE_AVAILABLE ${result.slug} ${result.current} ${result.latest}${byAuthor}`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const byAuthor = result.author ? ` \x1b[90m(by @${result.author})\x1b[0m` : '';
|
|
52
|
+
console.log(`\x1b[33m⚠ ${result.slug} v${result.latest} available\x1b[0m${byAuthor} (현재 v${result.current})`);
|
|
53
|
+
console.log(` 실행: relay update ${result.slug}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!quiet && !cliResult && teamResults.length === 0) {
|
|
57
|
+
console.log('모든 것이 최신 상태입니다.');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
package/dist/commands/install.js
CHANGED
|
@@ -9,6 +9,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const api_js_1 = require("../lib/api.js");
|
|
10
10
|
const storage_js_1 = require("../lib/storage.js");
|
|
11
11
|
const config_js_1 = require("../lib/config.js");
|
|
12
|
+
const preamble_js_1 = require("../lib/preamble.js");
|
|
12
13
|
function registerInstall(program) {
|
|
13
14
|
program
|
|
14
15
|
.command('install <slug>')
|
|
@@ -61,6 +62,8 @@ function registerInstall(program) {
|
|
|
61
62
|
return count;
|
|
62
63
|
}
|
|
63
64
|
const fileCount = countFiles(teamDir);
|
|
65
|
+
// 5.5. Inject update-check preamble into SKILL.md files
|
|
66
|
+
(0, preamble_js_1.injectPreambleToTeam)(teamDir, slug);
|
|
64
67
|
// 6. Record in installed.json
|
|
65
68
|
const installed = (0, config_js_1.loadInstalled)();
|
|
66
69
|
installed[slug] = {
|
|
@@ -96,26 +99,27 @@ function registerInstall(program) {
|
|
|
96
99
|
console.log(` \x1b[33m/${cmd.name}\x1b[0m - ${cmd.description}`);
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
|
-
// Builder
|
|
100
|
-
if (team.welcome) {
|
|
101
|
-
console.log(`\n ┌─ 💬 ${authorDisplayName}님의 메시지 ${'─'.repeat(Math.max(0, 38 - authorDisplayName.length))}┐`);
|
|
102
|
-
const lines = team.welcome.match(/.{1,50}/g) ?? [team.welcome];
|
|
103
|
-
for (const line of lines) {
|
|
104
|
-
console.log(` │ "${line}"`);
|
|
105
|
-
}
|
|
106
|
-
console.log(` └${'─'.repeat(44)}┘`);
|
|
107
|
-
}
|
|
102
|
+
// Builder business card
|
|
108
103
|
const contactLinks = team.author?.contact_links ?? {};
|
|
109
104
|
const contactEntries = Object.entries(contactLinks);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
console.log(`\n
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
105
|
+
const hasCard = team.welcome || contactEntries.length > 0 || authorUsername;
|
|
106
|
+
if (hasCard) {
|
|
107
|
+
console.log(`\n \x1b[90m┌─ ${authorDisplayName || authorUsername || '빌더'}의 명함 ${'─'.repeat(Math.max(0, 34 - (authorDisplayName || authorUsername || '빌더').length))}┐\x1b[0m`);
|
|
108
|
+
if (team.welcome) {
|
|
109
|
+
const truncated = team.welcome.length > 45 ? team.welcome.slice(0, 45) + '...' : team.welcome;
|
|
110
|
+
console.log(` \x1b[90m│\x1b[0m 💬 "${truncated}"`);
|
|
111
|
+
}
|
|
112
|
+
if (contactEntries.length > 0) {
|
|
113
|
+
const parts = contactEntries.map(([k, v]) => `${k}: ${v}`).join(' ');
|
|
114
|
+
console.log(` \x1b[90m│\x1b[0m 📇 ${parts}`);
|
|
115
|
+
}
|
|
116
|
+
if (authorUsername) {
|
|
117
|
+
console.log(` \x1b[90m│\x1b[0m 👤 relayax.com/@${authorUsername}`);
|
|
118
|
+
}
|
|
119
|
+
if (team.latest_post && authorUsername) {
|
|
120
|
+
console.log(` \x1b[90m│\x1b[0m 📝 relayax.com/@${authorUsername}/posts/${team.latest_post.slug}`);
|
|
121
|
+
}
|
|
122
|
+
console.log(` \x1b[90m└${'─'.repeat(44)}┘\x1b[0m`);
|
|
119
123
|
}
|
|
120
124
|
// Follow prompt (only when logged in)
|
|
121
125
|
const token = (0, config_js_1.loadToken)();
|
package/dist/commands/publish.js
CHANGED
|
@@ -10,6 +10,9 @@ const os_1 = __importDefault(require("os"));
|
|
|
10
10
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
11
11
|
const tar_1 = require("tar");
|
|
12
12
|
const config_js_1 = require("../lib/config.js");
|
|
13
|
+
const version_check_js_1 = require("../lib/version-check.js");
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
15
|
+
const cliPkg = require('../../package.json');
|
|
13
16
|
const VALID_DIRS = ['skills', 'agents', 'rules', 'commands'];
|
|
14
17
|
const IMAGE_EXTS = ['.png', '.jpg', '.jpeg', '.webp'];
|
|
15
18
|
/** 개별 포트폴리오 이미지 최대 크기 (2 MB) */
|
|
@@ -263,6 +266,30 @@ function registerPublish(program) {
|
|
|
263
266
|
const relayDir = path_1.default.join(teamDir, '.relay');
|
|
264
267
|
const relayYamlPath = path_1.default.join(relayDir, 'relay.yaml');
|
|
265
268
|
const isTTY = Boolean(process.stdin.isTTY) && !json;
|
|
269
|
+
// CLI update check before publish
|
|
270
|
+
if (isTTY) {
|
|
271
|
+
const cliUpdate = await (0, version_check_js_1.checkCliVersion)(true);
|
|
272
|
+
if (cliUpdate) {
|
|
273
|
+
console.error(`\n\x1b[33m⚠ relay v${cliUpdate.latest}이 있습니다\x1b[0m (현재 v${cliUpdate.current})`);
|
|
274
|
+
console.error(' 최신 버전에서는 설치자에게 자동 업데이트 알림이 지원됩니다.');
|
|
275
|
+
console.error(` 업데이트: \x1b[36mnpm update -g relayax-cli\x1b[0m\n`);
|
|
276
|
+
try {
|
|
277
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
278
|
+
const shouldContinue = await confirm({
|
|
279
|
+
message: '현재 버전으로 계속 배포할까요?',
|
|
280
|
+
default: true,
|
|
281
|
+
});
|
|
282
|
+
if (!shouldContinue) {
|
|
283
|
+
console.error('\n배포를 취소했습니다. CLI를 업데이트한 후 다시 시도하세요.');
|
|
284
|
+
process.exit(0);
|
|
285
|
+
}
|
|
286
|
+
console.error('');
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// non-interactive fallback: continue
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
266
293
|
// Check .relay/relay.yaml exists
|
|
267
294
|
if (!fs_1.default.existsSync(relayYamlPath)) {
|
|
268
295
|
if (!isTTY) {
|
|
@@ -302,9 +329,9 @@ function registerPublish(program) {
|
|
|
302
329
|
{ name: '초대 코드 필요', value: 'invite-only' },
|
|
303
330
|
],
|
|
304
331
|
});
|
|
305
|
-
console.error('\n\x1b[2m💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: www.relayax.com/dashboard/
|
|
332
|
+
console.error('\n\x1b[2m💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: www.relayax.com/dashboard/profile\x1b[0m');
|
|
306
333
|
if (visibility === 'invite-only') {
|
|
307
|
-
console.error('\x1b[2m💡 invite-only 팀은 웹 대시보드에서 사용자를 초대하세요: www.relayax.com/dashboard\x1b[0m');
|
|
334
|
+
console.error('\x1b[2m💡 invite-only 팀은 웹 대시보드에서 사용자를 초대하세요: www.relayax.com/dashboard/teams\x1b[0m');
|
|
308
335
|
}
|
|
309
336
|
console.error('');
|
|
310
337
|
const tags = tagsRaw
|
|
@@ -335,7 +362,7 @@ function registerPublish(program) {
|
|
|
335
362
|
}
|
|
336
363
|
// Profile hint
|
|
337
364
|
if (isTTY) {
|
|
338
|
-
console.error('💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: www.relayax.com/dashboard/
|
|
365
|
+
console.error('💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: www.relayax.com/dashboard/profile');
|
|
339
366
|
}
|
|
340
367
|
// Validate structure (콘텐츠는 .relay/ 안에 있음)
|
|
341
368
|
const hasDirs = VALID_DIRS.some((d) => {
|
|
@@ -381,6 +408,7 @@ function registerPublish(program) {
|
|
|
381
408
|
changelog: config.changelog,
|
|
382
409
|
requires: config.requires,
|
|
383
410
|
visibility: config.visibility,
|
|
411
|
+
cli_version: cliPkg.version,
|
|
384
412
|
};
|
|
385
413
|
if (!json) {
|
|
386
414
|
console.error(`패키지 생성 중... (${config.name} v${config.version})`);
|
|
@@ -405,6 +433,29 @@ function registerPublish(program) {
|
|
|
405
433
|
if (result.portfolio_count && result.portfolio_count > 0) {
|
|
406
434
|
console.log(` 포트폴리오: ${result.portfolio_count}개 이미지 업로드됨`);
|
|
407
435
|
}
|
|
436
|
+
// Show business card preview
|
|
437
|
+
const profile = result.profile;
|
|
438
|
+
if (profile) {
|
|
439
|
+
const contacts = profile.contact_links ?? {};
|
|
440
|
+
const contactEntries = Object.entries(contacts);
|
|
441
|
+
const welcome = profile.default_welcome ?? '';
|
|
442
|
+
console.log(`\n \x1b[90m┌─ 설치자에게 보이는 명함 ${'─'.repeat(24)}┐\x1b[0m`);
|
|
443
|
+
if (welcome) {
|
|
444
|
+
console.log(` \x1b[90m│\x1b[0m 💬 "${welcome.length > 45 ? welcome.slice(0, 45) + '...' : welcome}"`);
|
|
445
|
+
}
|
|
446
|
+
if (contactEntries.length > 0) {
|
|
447
|
+
const parts = contactEntries.map(([k, v]) => `${k}: ${v}`).join(' ');
|
|
448
|
+
console.log(` \x1b[90m│\x1b[0m 📇 ${parts}`);
|
|
449
|
+
}
|
|
450
|
+
if (profile.username) {
|
|
451
|
+
console.log(` \x1b[90m│\x1b[0m 👤 relayax.com/@${profile.username}`);
|
|
452
|
+
}
|
|
453
|
+
if (!welcome && contactEntries.length === 0) {
|
|
454
|
+
console.log(` \x1b[90m│\x1b[0m \x1b[2m명함이 비어있습니다\x1b[0m`);
|
|
455
|
+
}
|
|
456
|
+
console.log(` \x1b[90m└${'─'.repeat(44)}┘\x1b[0m`);
|
|
457
|
+
console.log(`\n \x1b[90m명함 수정: \x1b[36mwww.relayax.com/dashboard/profile\x1b[0m`);
|
|
458
|
+
}
|
|
408
459
|
}
|
|
409
460
|
}
|
|
410
461
|
catch (err) {
|
package/dist/commands/update.js
CHANGED
|
@@ -5,6 +5,7 @@ const api_js_1 = require("../lib/api.js");
|
|
|
5
5
|
const storage_js_1 = require("../lib/storage.js");
|
|
6
6
|
const installer_js_1 = require("../lib/installer.js");
|
|
7
7
|
const config_js_1 = require("../lib/config.js");
|
|
8
|
+
const preamble_js_1 = require("../lib/preamble.js");
|
|
8
9
|
function registerUpdate(program) {
|
|
9
10
|
program
|
|
10
11
|
.command('update <slug>')
|
|
@@ -54,6 +55,8 @@ function registerUpdate(program) {
|
|
|
54
55
|
await (0, storage_js_1.extractPackage)(tarPath, extractDir);
|
|
55
56
|
// Copy files to install_path
|
|
56
57
|
const files = (0, installer_js_1.installTeam)(extractDir, installPath);
|
|
58
|
+
// Inject update-check preamble into SKILL.md files
|
|
59
|
+
(0, preamble_js_1.injectPreambleToTeam)(installPath, slug);
|
|
57
60
|
// Update installed.json with new version
|
|
58
61
|
installed[slug] = {
|
|
59
62
|
version: latestVersion,
|
|
@@ -79,6 +82,27 @@ function registerUpdate(program) {
|
|
|
79
82
|
console.log(`\n\x1b[32m✓ ${team.name} ${fromLabel}v${latestVersion} 업데이트 완료\x1b[0m`);
|
|
80
83
|
console.log(` 설치 위치: \x1b[36m${installPath}\x1b[0m`);
|
|
81
84
|
console.log(` 파일 수: ${files.length}개`);
|
|
85
|
+
// Builder business card
|
|
86
|
+
const authorUsername = team.author?.username;
|
|
87
|
+
const authorDisplayName = team.author?.display_name ?? authorUsername ?? '';
|
|
88
|
+
const contactLinks = team.author?.contact_links ?? {};
|
|
89
|
+
const contactEntries = Object.entries(contactLinks);
|
|
90
|
+
const hasCard = team.welcome || contactEntries.length > 0 || authorUsername;
|
|
91
|
+
if (hasCard) {
|
|
92
|
+
console.log(`\n \x1b[90m┌─ ${authorDisplayName || '빌더'}의 명함 ${'─'.repeat(Math.max(0, 34 - (authorDisplayName || '빌더').length))}┐\x1b[0m`);
|
|
93
|
+
if (team.welcome) {
|
|
94
|
+
const truncated = team.welcome.length > 45 ? team.welcome.slice(0, 45) + '...' : team.welcome;
|
|
95
|
+
console.log(` \x1b[90m│\x1b[0m 💬 "${truncated}"`);
|
|
96
|
+
}
|
|
97
|
+
if (contactEntries.length > 0) {
|
|
98
|
+
const parts = contactEntries.map(([k, v]) => `${k}: ${v}`).join(' ');
|
|
99
|
+
console.log(` \x1b[90m│\x1b[0m 📇 ${parts}`);
|
|
100
|
+
}
|
|
101
|
+
if (authorUsername) {
|
|
102
|
+
console.log(` \x1b[90m│\x1b[0m 👤 relayax.com/@${authorUsername}`);
|
|
103
|
+
}
|
|
104
|
+
console.log(` \x1b[90m└${'─'.repeat(44)}┘\x1b[0m`);
|
|
105
|
+
}
|
|
82
106
|
}
|
|
83
107
|
}
|
|
84
108
|
catch (err) {
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const publish_js_1 = require("./commands/publish.js");
|
|
|
13
13
|
const login_js_1 = require("./commands/login.js");
|
|
14
14
|
const update_js_1 = require("./commands/update.js");
|
|
15
15
|
const outdated_js_1 = require("./commands/outdated.js");
|
|
16
|
+
const check_update_js_1 = require("./commands/check-update.js");
|
|
16
17
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
17
18
|
const pkg = require('../package.json');
|
|
18
19
|
const program = new commander_1.Command();
|
|
@@ -32,4 +33,5 @@ program
|
|
|
32
33
|
(0, login_js_1.registerLogin)(program);
|
|
33
34
|
(0, update_js_1.registerUpdate)(program);
|
|
34
35
|
(0, outdated_js_1.registerOutdated)(program);
|
|
36
|
+
(0, check_update_js_1.registerCheckUpdate)(program);
|
|
35
37
|
program.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getDeviceHash(): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getDeviceHash = getDeviceHash;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
let cachedHash = null;
|
|
10
|
+
function getDeviceHash() {
|
|
11
|
+
if (cachedHash)
|
|
12
|
+
return cachedHash;
|
|
13
|
+
const raw = `${os_1.default.hostname()}:${os_1.default.userInfo().username}`;
|
|
14
|
+
cachedHash = (0, crypto_1.createHash)('sha256').update(raw).digest('hex');
|
|
15
|
+
return cachedHash;
|
|
16
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generatePreamble = generatePreamble;
|
|
7
|
+
exports.injectPreamble = injectPreamble;
|
|
8
|
+
exports.injectPreambleToTeam = injectPreambleToTeam;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const PREAMBLE_START = '<!-- RELAY_PREAMBLE_START - DO NOT EDIT -->';
|
|
12
|
+
const PREAMBLE_END = '<!-- RELAY_PREAMBLE_END -->';
|
|
13
|
+
function generatePreamble(slug) {
|
|
14
|
+
return `${PREAMBLE_START}
|
|
15
|
+
## Preamble (auto-generated by relay)
|
|
16
|
+
|
|
17
|
+
\`\`\`bash
|
|
18
|
+
relay check-update ${slug} --quiet 2>/dev/null
|
|
19
|
+
\`\`\`
|
|
20
|
+
|
|
21
|
+
If output shows \`CLI_UPGRADE_AVAILABLE <old> <new>\`: tell user "relay v{new} available. Run: npm update -g relayax-cli"
|
|
22
|
+
If output shows \`TEAM_UPGRADE_AVAILABLE\`: tell user "${slug} v{new} available (by @{author}). Run: relay update ${slug}"
|
|
23
|
+
${PREAMBLE_END}`;
|
|
24
|
+
}
|
|
25
|
+
function injectPreamble(filePath, slug) {
|
|
26
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
27
|
+
const preamble = generatePreamble(slug);
|
|
28
|
+
// Replace existing preamble or prepend
|
|
29
|
+
const startIdx = content.indexOf(PREAMBLE_START);
|
|
30
|
+
const endIdx = content.indexOf(PREAMBLE_END);
|
|
31
|
+
let newContent;
|
|
32
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
33
|
+
// Replace existing
|
|
34
|
+
newContent =
|
|
35
|
+
content.slice(0, startIdx) +
|
|
36
|
+
preamble +
|
|
37
|
+
content.slice(endIdx + PREAMBLE_END.length);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Prepend
|
|
41
|
+
newContent = preamble + '\n\n' + content;
|
|
42
|
+
}
|
|
43
|
+
fs_1.default.writeFileSync(filePath, newContent);
|
|
44
|
+
}
|
|
45
|
+
function injectPreambleToTeam(teamDir, slug) {
|
|
46
|
+
let count = 0;
|
|
47
|
+
function walk(dir) {
|
|
48
|
+
if (!fs_1.default.existsSync(dir))
|
|
49
|
+
return;
|
|
50
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
51
|
+
const fullPath = path_1.default.join(dir, entry.name);
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
walk(fullPath);
|
|
54
|
+
}
|
|
55
|
+
else if (entry.name === 'SKILL.md') {
|
|
56
|
+
injectPreamble(fullPath, slug);
|
|
57
|
+
count++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
walk(teamDir);
|
|
62
|
+
return count;
|
|
63
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isCacheValid = isCacheValid;
|
|
7
|
+
exports.updateCacheTimestamp = updateCacheTimestamp;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const RELAY_DIR = path_1.default.join(os_1.default.homedir(), '.relay');
|
|
12
|
+
const CACHE_FILE = path_1.default.join(RELAY_DIR, 'last-update-check');
|
|
13
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
14
|
+
function loadCache() {
|
|
15
|
+
try {
|
|
16
|
+
if (!fs_1.default.existsSync(CACHE_FILE))
|
|
17
|
+
return {};
|
|
18
|
+
return JSON.parse(fs_1.default.readFileSync(CACHE_FILE, 'utf-8'));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function saveCache(data) {
|
|
25
|
+
if (!fs_1.default.existsSync(RELAY_DIR)) {
|
|
26
|
+
fs_1.default.mkdirSync(RELAY_DIR, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
fs_1.default.writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2));
|
|
29
|
+
}
|
|
30
|
+
function isCacheValid(key, force) {
|
|
31
|
+
if (force)
|
|
32
|
+
return false;
|
|
33
|
+
const cache = loadCache();
|
|
34
|
+
const timestamp = key === 'cli' ? cache.cli : cache.teams?.[key];
|
|
35
|
+
if (!timestamp)
|
|
36
|
+
return false;
|
|
37
|
+
return Date.now() - new Date(timestamp).getTime() < CACHE_TTL_MS;
|
|
38
|
+
}
|
|
39
|
+
function updateCacheTimestamp(key) {
|
|
40
|
+
const cache = loadCache();
|
|
41
|
+
const now = new Date().toISOString();
|
|
42
|
+
if (key === 'cli') {
|
|
43
|
+
cache.cli = now;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
if (!cache.teams)
|
|
47
|
+
cache.teams = {};
|
|
48
|
+
cache.teams[key] = now;
|
|
49
|
+
}
|
|
50
|
+
saveCache(cache);
|
|
51
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface UpdateResult {
|
|
2
|
+
type: 'cli' | 'team';
|
|
3
|
+
slug?: string;
|
|
4
|
+
current: string;
|
|
5
|
+
latest: string;
|
|
6
|
+
author?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function checkCliVersion(force?: boolean): Promise<UpdateResult | null>;
|
|
9
|
+
export declare function checkTeamVersion(slug: string, force?: boolean): Promise<UpdateResult | null>;
|
|
10
|
+
export declare function checkAllTeams(force?: boolean): Promise<UpdateResult[]>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkCliVersion = checkCliVersion;
|
|
4
|
+
exports.checkTeamVersion = checkTeamVersion;
|
|
5
|
+
exports.checkAllTeams = checkAllTeams;
|
|
6
|
+
const config_js_1 = require("./config.js");
|
|
7
|
+
const api_js_1 = require("./api.js");
|
|
8
|
+
const update_cache_js_1 = require("./update-cache.js");
|
|
9
|
+
const device_hash_js_1 = require("./device-hash.js");
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
11
|
+
const pkg = require('../../package.json');
|
|
12
|
+
async function checkCliVersion(force) {
|
|
13
|
+
if ((0, update_cache_js_1.isCacheValid)('cli', force))
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch('https://registry.npmjs.org/relayax-cli/latest', {
|
|
17
|
+
signal: AbortSignal.timeout(3000),
|
|
18
|
+
});
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
return null;
|
|
21
|
+
const data = (await res.json());
|
|
22
|
+
(0, update_cache_js_1.updateCacheTimestamp)('cli');
|
|
23
|
+
if (data.version !== pkg.version) {
|
|
24
|
+
return { type: 'cli', current: pkg.version, latest: data.version };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// network error — silently skip
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
async function checkTeamVersion(slug, force) {
|
|
33
|
+
if ((0, update_cache_js_1.isCacheValid)(slug, force))
|
|
34
|
+
return null;
|
|
35
|
+
try {
|
|
36
|
+
const installed = (0, config_js_1.loadInstalled)();
|
|
37
|
+
const entry = installed[slug];
|
|
38
|
+
if (!entry?.version)
|
|
39
|
+
return null;
|
|
40
|
+
const team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
41
|
+
(0, update_cache_js_1.updateCacheTimestamp)(slug);
|
|
42
|
+
// Send usage ping (fire-and-forget)
|
|
43
|
+
fetch(`${config_js_1.API_URL}/api/registry/${slug}/ping`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify({ device_hash: (0, device_hash_js_1.getDeviceHash)() }),
|
|
47
|
+
signal: AbortSignal.timeout(3000),
|
|
48
|
+
}).catch(() => { });
|
|
49
|
+
if (team.version !== entry.version) {
|
|
50
|
+
return {
|
|
51
|
+
type: 'team',
|
|
52
|
+
slug,
|
|
53
|
+
current: entry.version,
|
|
54
|
+
latest: team.version,
|
|
55
|
+
author: team.author?.username,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// network error — silently skip
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
async function checkAllTeams(force) {
|
|
65
|
+
const installed = (0, config_js_1.loadInstalled)();
|
|
66
|
+
const slugs = Object.keys(installed);
|
|
67
|
+
const results = [];
|
|
68
|
+
for (const slug of slugs) {
|
|
69
|
+
const result = await checkTeamVersion(slug, force);
|
|
70
|
+
if (result)
|
|
71
|
+
results.push(result);
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
74
|
+
}
|