relayax-cli 0.1.97 → 0.1.99
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/init.js +25 -1
- package/dist/commands/install.js +90 -27
- package/dist/lib/api.d.ts +1 -0
- package/dist/lib/api.js +19 -0
- package/dist/lib/command-adapter.js +57 -34
- package/dist/lib/storage.js +1 -1
- package/dist/types.d.ts +15 -0
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -58,10 +58,23 @@ async function selectToolsInteractively(detectedIds) {
|
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
60
|
* 글로벌 User 커맨드를 ~/.claude/commands/relay/에 설치한다.
|
|
61
|
+
* 기존 파일 중 현재 커맨드 목록에 없는 것은 제거한다.
|
|
61
62
|
*/
|
|
62
63
|
function installGlobalUserCommands() {
|
|
63
64
|
const globalDir = (0, command_adapter_js_1.getGlobalCommandDir)();
|
|
64
65
|
fs_1.default.mkdirSync(globalDir, { recursive: true });
|
|
66
|
+
// 현재 커맨드 ID 세트
|
|
67
|
+
const currentIds = new Set(command_adapter_js_1.USER_COMMANDS.map((c) => c.id));
|
|
68
|
+
// 기존 파일 중 현재 목록에 없는 것 제거
|
|
69
|
+
if (fs_1.default.existsSync(globalDir)) {
|
|
70
|
+
for (const file of fs_1.default.readdirSync(globalDir)) {
|
|
71
|
+
const id = file.replace(/\.md$/, '');
|
|
72
|
+
if (!currentIds.has(id)) {
|
|
73
|
+
fs_1.default.unlinkSync(path_1.default.join(globalDir, file));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// 현재 커맨드 설치 (덮어쓰기)
|
|
65
78
|
const commands = [];
|
|
66
79
|
for (const cmd of command_adapter_js_1.USER_COMMANDS) {
|
|
67
80
|
const filePath = (0, command_adapter_js_1.getGlobalCommandPath)(cmd.id);
|
|
@@ -160,12 +173,23 @@ function registerInit(program) {
|
|
|
160
173
|
targetToolIds = detected.map((t) => t.value);
|
|
161
174
|
}
|
|
162
175
|
}
|
|
163
|
-
// Builder 커맨드 설치
|
|
176
|
+
// Builder 커맨드 설치 (기존 파일 중 현재 목록에 없는 것 제거)
|
|
177
|
+
const builderIds = new Set(command_adapter_js_1.BUILDER_COMMANDS.map((c) => c.id));
|
|
164
178
|
for (const toolId of targetToolIds) {
|
|
165
179
|
const tool = ai_tools_js_1.AI_TOOLS.find((t) => t.value === toolId);
|
|
166
180
|
if (!tool)
|
|
167
181
|
continue;
|
|
168
182
|
const adapter = (0, command_adapter_js_1.createAdapter)(tool);
|
|
183
|
+
const localDir = path_1.default.join(projectPath, tool.skillsDir, 'commands', 'relay');
|
|
184
|
+
// 기존 로컬 커맨드 중 Builder 목록에 없는 것 제거
|
|
185
|
+
if (fs_1.default.existsSync(localDir)) {
|
|
186
|
+
for (const file of fs_1.default.readdirSync(localDir)) {
|
|
187
|
+
const id = file.replace(/\.md$/, '');
|
|
188
|
+
if (!builderIds.has(id)) {
|
|
189
|
+
fs_1.default.unlinkSync(path_1.default.join(localDir, file));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
169
193
|
const installedCommands = [];
|
|
170
194
|
for (const cmd of command_adapter_js_1.BUILDER_COMMANDS) {
|
|
171
195
|
const filePath = path_1.default.join(projectPath, adapter.getFilePath(cmd.id));
|
package/dist/commands/install.js
CHANGED
|
@@ -1,52 +1,72 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.registerInstall = registerInstall;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
4
9
|
const api_js_1 = require("../lib/api.js");
|
|
5
10
|
const storage_js_1 = require("../lib/storage.js");
|
|
6
|
-
const installer_js_1 = require("../lib/installer.js");
|
|
7
11
|
const config_js_1 = require("../lib/config.js");
|
|
8
12
|
function registerInstall(program) {
|
|
9
13
|
program
|
|
10
14
|
.command('install <slug>')
|
|
11
|
-
.description('에이전트 팀
|
|
12
|
-
.
|
|
13
|
-
.action(async (slug, opts) => {
|
|
15
|
+
.description('에이전트 팀 패키지를 .relay/teams/에 다운로드합니다')
|
|
16
|
+
.action(async (slug) => {
|
|
14
17
|
const json = program.opts().json ?? false;
|
|
15
|
-
const
|
|
18
|
+
const projectPath = process.cwd();
|
|
19
|
+
const teamDir = path_1.default.join(projectPath, '.relay', 'teams', slug);
|
|
16
20
|
const tempDir = (0, storage_js_1.makeTempDir)();
|
|
17
21
|
try {
|
|
18
22
|
// 1. Fetch team metadata
|
|
19
23
|
const team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
20
24
|
// 2. Visibility check
|
|
21
25
|
const visibility = team.visibility ?? 'public';
|
|
22
|
-
if (visibility === 'login-only') {
|
|
26
|
+
if (visibility === 'login-only' || visibility === 'invite-only') {
|
|
23
27
|
const token = (0, config_js_1.loadToken)();
|
|
24
28
|
if (!token) {
|
|
25
|
-
console.error(JSON.stringify({
|
|
29
|
+
console.error(JSON.stringify({
|
|
30
|
+
error: 'LOGIN_REQUIRED',
|
|
31
|
+
visibility,
|
|
32
|
+
slug,
|
|
33
|
+
message: visibility === 'invite-only'
|
|
34
|
+
? '이 팀은 초대받은 사용자만 설치할 수 있습니다. 로그인이 필요합니다.'
|
|
35
|
+
: '이 팀은 로그인이 필요합니다.',
|
|
36
|
+
}));
|
|
26
37
|
process.exit(1);
|
|
27
38
|
}
|
|
28
39
|
}
|
|
29
|
-
else if (visibility === 'invite-only') {
|
|
30
|
-
const token = (0, config_js_1.loadToken)();
|
|
31
|
-
if (!token) {
|
|
32
|
-
console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', visibility: 'invite-only', slug, message: '이 팀은 초대받은 사용자만 설치할 수 있습니다. 로그인이 필요합니다.' }));
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
// 실제 초대 여부는 서버(install API)에서 체크
|
|
36
|
-
}
|
|
37
40
|
// 3. Download package
|
|
38
41
|
const tarPath = await (0, storage_js_1.downloadPackage)(team.package_url, tempDir);
|
|
39
|
-
// 4. Extract
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// 4. Extract to .relay/teams/<slug>/
|
|
43
|
+
if (fs_1.default.existsSync(teamDir)) {
|
|
44
|
+
fs_1.default.rmSync(teamDir, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
fs_1.default.mkdirSync(teamDir, { recursive: true });
|
|
47
|
+
await (0, storage_js_1.extractPackage)(tarPath, teamDir);
|
|
48
|
+
// 5. Count extracted files
|
|
49
|
+
function countFiles(dir) {
|
|
50
|
+
let count = 0;
|
|
51
|
+
if (!fs_1.default.existsSync(dir))
|
|
52
|
+
return 0;
|
|
53
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
count += countFiles(path_1.default.join(dir, entry.name));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
count++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return count;
|
|
62
|
+
}
|
|
63
|
+
const fileCount = countFiles(teamDir);
|
|
44
64
|
// 6. Record in installed.json
|
|
45
65
|
const installed = (0, config_js_1.loadInstalled)();
|
|
46
66
|
installed[slug] = {
|
|
47
67
|
version: team.version,
|
|
48
68
|
installed_at: new Date().toISOString(),
|
|
49
|
-
files,
|
|
69
|
+
files: [teamDir],
|
|
50
70
|
};
|
|
51
71
|
(0, config_js_1.saveInstalled)(installed);
|
|
52
72
|
// 7. Report install (non-blocking)
|
|
@@ -57,22 +77,65 @@ function registerInstall(program) {
|
|
|
57
77
|
slug,
|
|
58
78
|
version: team.version,
|
|
59
79
|
commands: team.commands,
|
|
60
|
-
|
|
61
|
-
install_path:
|
|
80
|
+
files: fileCount,
|
|
81
|
+
install_path: teamDir,
|
|
62
82
|
};
|
|
63
83
|
if (json) {
|
|
64
84
|
console.log(JSON.stringify(result));
|
|
65
85
|
}
|
|
66
86
|
else {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
const authorUsername = team.author?.username;
|
|
88
|
+
const authorDisplayName = team.author?.display_name ?? authorUsername ?? '';
|
|
89
|
+
const authorSuffix = authorUsername ? ` \x1b[90mby @${authorUsername}\x1b[0m` : '';
|
|
90
|
+
console.log(`\n\x1b[32m✓ ${team.name} 다운로드 완료\x1b[0m v${team.version}${authorSuffix}`);
|
|
91
|
+
console.log(` 위치: \x1b[36m${teamDir}\x1b[0m`);
|
|
92
|
+
console.log(` 파일: ${fileCount}개`);
|
|
70
93
|
if (team.commands.length > 0) {
|
|
71
|
-
console.log('\n
|
|
94
|
+
console.log('\n 포함된 커맨드:');
|
|
72
95
|
for (const cmd of team.commands) {
|
|
73
96
|
console.log(` \x1b[33m/${cmd.name}\x1b[0m - ${cmd.description}`);
|
|
74
97
|
}
|
|
75
98
|
}
|
|
99
|
+
// Builder info block
|
|
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
|
+
}
|
|
108
|
+
const contactLinks = team.author?.contact_links ?? {};
|
|
109
|
+
const contactEntries = Object.entries(contactLinks);
|
|
110
|
+
if (contactEntries.length > 0) {
|
|
111
|
+
const parts = contactEntries.map(([key, val]) => `${key}: ${val}`);
|
|
112
|
+
console.log(`\n 연락처: ${parts.join(' | ')}`);
|
|
113
|
+
}
|
|
114
|
+
if (authorUsername) {
|
|
115
|
+
console.log(`\n \x1b[90m👤 프로필: relayax.com/@${authorUsername}\x1b[0m`);
|
|
116
|
+
}
|
|
117
|
+
if (team.latest_post && authorUsername) {
|
|
118
|
+
console.log(` \x1b[90m📝 활용 팁: relayax.com/@${authorUsername}/posts/${team.latest_post.slug}\x1b[0m`);
|
|
119
|
+
}
|
|
120
|
+
// Follow prompt (only when logged in)
|
|
121
|
+
const token = (0, config_js_1.loadToken)();
|
|
122
|
+
if (authorUsername && token) {
|
|
123
|
+
try {
|
|
124
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
125
|
+
const shouldFollow = await confirm({
|
|
126
|
+
message: `@${authorUsername}을 팔로우하시겠습니까? (이메일로 새 소식을 받습니다)`,
|
|
127
|
+
default: true,
|
|
128
|
+
});
|
|
129
|
+
if (shouldFollow) {
|
|
130
|
+
await (0, api_js_1.followBuilder)(authorUsername);
|
|
131
|
+
console.log(`\x1b[32m ✓ @${authorUsername} 팔로우 완료\x1b[0m`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// non-critical: skip on error or non-interactive terminal
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
console.log('\n 에이전트가 /relay-install로 환경을 구성합니다.');
|
|
76
139
|
}
|
|
77
140
|
}
|
|
78
141
|
catch (err) {
|
package/dist/lib/api.d.ts
CHANGED
package/dist/lib/api.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.fetchTeamInfo = fetchTeamInfo;
|
|
|
4
4
|
exports.searchTeams = searchTeams;
|
|
5
5
|
exports.fetchTeamVersions = fetchTeamVersions;
|
|
6
6
|
exports.reportInstall = reportInstall;
|
|
7
|
+
exports.followBuilder = followBuilder;
|
|
7
8
|
const config_js_1 = require("./config.js");
|
|
8
9
|
async function fetchTeamInfo(slug) {
|
|
9
10
|
const url = `${config_js_1.API_URL}/api/registry/${slug}`;
|
|
@@ -42,3 +43,21 @@ async function reportInstall(slug) {
|
|
|
42
43
|
// non-critical: ignore errors
|
|
43
44
|
});
|
|
44
45
|
}
|
|
46
|
+
async function followBuilder(username) {
|
|
47
|
+
const token = (0, config_js_1.loadToken)();
|
|
48
|
+
const headers = {
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
};
|
|
51
|
+
if (token) {
|
|
52
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
53
|
+
}
|
|
54
|
+
const res = await fetch(`https://www.relayax.com/api/follows`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers,
|
|
57
|
+
body: JSON.stringify({ following_username: username, email_opt_in: true }),
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
const body = await res.text();
|
|
61
|
+
throw new Error(`팔로우 실패 (${res.status}): ${body}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -79,46 +79,52 @@ exports.USER_COMMANDS = [
|
|
|
79
79
|
{
|
|
80
80
|
id: 'relay-install',
|
|
81
81
|
description: 'relay 마켓플레이스에서 에이전트 팀을 설치합니다',
|
|
82
|
-
body: `요청된 에이전트 팀을 relay 마켓플레이스에서
|
|
82
|
+
body: `요청된 에이전트 팀을 relay 마켓플레이스에서 다운로드하고, 현재 에이전트 환경에 맞게 구성합니다.
|
|
83
83
|
|
|
84
84
|
## 실행 방법
|
|
85
85
|
|
|
86
|
-
### 1. 팀
|
|
86
|
+
### 1. 팀 패키지 다운로드
|
|
87
87
|
- \`relay install <slug>\` 명령어를 실행합니다.
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
### 2.
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
- 패키지가 \`.relay/teams/<slug>/\`에 다운로드됩니다.
|
|
89
|
+
|
|
90
|
+
### 2. 패키지 내용 확인
|
|
91
|
+
다운로드된 \`.relay/teams/<slug>/\` 디렉토리를 읽어 구조를 파악합니다:
|
|
92
|
+
- skills/ — 스킬 파일들
|
|
93
|
+
- commands/ — 슬래시 커맨드 파일들
|
|
94
|
+
- agents/ — 에이전트 설정 파일들
|
|
95
|
+
- rules/ — 룰 파일들
|
|
96
|
+
- relay.yaml — 팀 메타데이터 및 requirements
|
|
97
|
+
|
|
98
|
+
### 3. 에이전트 환경에 맞게 배치
|
|
99
|
+
현재 에이전트의 디렉토리 구조에 맞게 파일을 복사합니다:
|
|
100
|
+
- Claude Code: \`.relay/teams/<slug>/commands/\` → \`.claude/commands/\`에 복사
|
|
101
|
+
- Claude Code: \`.relay/teams/<slug>/skills/\` → \`.claude/skills/\`에 복사
|
|
102
|
+
- 다른 에이전트(Cursor, Cline 등): 해당 에이전트의 규칙에 맞는 디렉토리에 복사
|
|
103
|
+
- 에이전트 설정이나 룰은 적절한 위치에 배치
|
|
104
|
+
|
|
105
|
+
### 4. Requirements 확인 및 설치
|
|
106
|
+
\`.relay/teams/<slug>/relay.yaml\`의 \`requires\` 섹션을 읽고 처리합니다:
|
|
93
107
|
- **cli**: \`which <name>\`으로 확인 → 없으면 install 명령 실행 또는 안내
|
|
94
108
|
- **npm**: \`npm list <package>\`로 확인 → 없으면 \`npm install\`
|
|
95
|
-
- **env**: 환경변수 확인 →
|
|
96
|
-
- **mcp**: MCP 서버 설정 안내
|
|
97
|
-
- **
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- API 응답의 \`welcome\` 필드가 있으면 제작자 메시지를 보여줍니다.
|
|
101
|
-
- \`contact\` 필드가 있으면 연락처도 함께 표시합니다.
|
|
102
|
-
|
|
103
|
-
### 4. 팔로우 제안
|
|
104
|
-
- "이 팀의 제작자 @username을 팔로우할까요?"라고 제안합니다.
|
|
105
|
-
- 인증되어 있으면 POST /api/follows로 팔로우합니다.
|
|
106
|
-
- 미인증이면 \`relay login\` 후 팔로우할 수 있다고 안내합니다.
|
|
109
|
+
- **env**: 환경변수 확인 → required이면 설정 안내, optional이면 알림
|
|
110
|
+
- **mcp**: MCP 서버 설정 — 에이전트의 MCP 설정에 추가 안내
|
|
111
|
+
- **runtime**: Node.js/Python 버전 확인
|
|
112
|
+
- **teams**: 의존하는 다른 팀 → \`relay install <team>\`으로 재귀 설치
|
|
113
|
+
${LOGIN_JIT_GUIDE}
|
|
107
114
|
|
|
108
115
|
### 5. 완료 안내
|
|
109
|
-
-
|
|
116
|
+
- 배치된 파일과 활성화된 커맨드 목록을 보여줍니다.
|
|
110
117
|
- "바로 사용해볼까요?" 제안
|
|
111
|
-
${LOGIN_JIT_GUIDE}
|
|
112
118
|
|
|
113
119
|
## 예시
|
|
114
120
|
|
|
115
121
|
사용자: /relay-install contents-team
|
|
116
|
-
→ relay install contents-team 실행
|
|
117
|
-
→
|
|
118
|
-
→
|
|
119
|
-
→
|
|
120
|
-
→
|
|
121
|
-
→ "
|
|
122
|
+
→ relay install contents-team 실행 (패키지 다운로드)
|
|
123
|
+
→ .relay/teams/contents-team/ 내용 확인
|
|
124
|
+
→ commands/cardnews.md → .claude/commands/cardnews.md 복사
|
|
125
|
+
→ skills/pdf-gen.md → .claude/skills/pdf-gen.md 복사
|
|
126
|
+
→ requires 확인: ✓ playwright 설치됨, ✓ sharp 설치됨
|
|
127
|
+
→ "✓ 설치 완료! /cardnews를 사용해볼까요?"`,
|
|
122
128
|
},
|
|
123
129
|
{
|
|
124
130
|
id: 'relay-list',
|
|
@@ -294,13 +300,30 @@ requires:
|
|
|
294
300
|
- .relay/portfolio/cover.png에 저장합니다.
|
|
295
301
|
- 사용자에게 "이 cover를 사용할까요?" 확인. 직접 제공도 가능.
|
|
296
302
|
|
|
297
|
-
#### 슬롯 2: demo (
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
-
|
|
303
|
+
#### 슬롯 2: demo (자동 생성 — 동작 시연)
|
|
304
|
+
에이전트가 **직접 팀의 커맨드를 실행하여** demo를 자동 생성합니다. 사용자에게 묻기 전에 먼저 만듭니다.
|
|
305
|
+
|
|
306
|
+
**생성 절차:**
|
|
307
|
+
1. .relay/commands/ 의 커맨드 목록을 읽고, 가장 대표적인 커맨드를 선택합니다.
|
|
308
|
+
2. 해당 커맨드를 예시 주제/데이터로 실행합니다.
|
|
309
|
+
- 콘텐츠 팀: "라즈베리파이5 신제품 소개" 같은 예시 주제로 커맨드 실행
|
|
310
|
+
- 크롤링 팀: 예시 URL로 실행하며 브라우저 동작을 녹화
|
|
311
|
+
- 분석 팀: 샘플 데이터로 실행하여 결과 캡처
|
|
312
|
+
3. 실행 결과물로 demo를 생성합니다:
|
|
313
|
+
- 여러 장 이미지 (카드뉴스 등): 슬라이드쇼 GIF (각 장 2초 간격)
|
|
314
|
+
- 브라우저 동작: 동작 녹화 GIF
|
|
315
|
+
- 단일 결과물: 결과 이미지를 demo로 사용
|
|
316
|
+
4. 생성된 demo를 사용자에게 보여줍니다: "이 demo를 사용할까요?"
|
|
317
|
+
- 수정 요청 가능: "다른 주제로 다시 만들어줘", "좀 더 짧게"
|
|
318
|
+
- 확정 시 .relay/portfolio/demo.gif에 저장
|
|
319
|
+
|
|
320
|
+
**예시 주제 선택 기준:**
|
|
321
|
+
- relay.yaml의 description, tags에서 도메인 힌트 추출
|
|
322
|
+
- 기존 output/에 결과물이 있으면 같은 주제 활용
|
|
323
|
+
- 없으면 팀 설명에서 가장 그럴듯한 예시 주제를 자체 생성
|
|
324
|
+
|
|
325
|
+
**GIF 규격:** 최대 5MB, .relay/portfolio/demo.gif
|
|
326
|
+
**대안:** 외부 영상 URL (YouTube, Loom)도 가능 — relay.yaml에 기록
|
|
304
327
|
|
|
305
328
|
#### 슬롯 3: gallery (선택 — 결과물 쇼케이스, 최대 5장)
|
|
306
329
|
- 규격: 800x600px 이하, WebP, 각 500KB 이하
|
package/dist/lib/storage.js
CHANGED
|
@@ -33,7 +33,7 @@ async function extractPackage(tarPath, destDir) {
|
|
|
33
33
|
if (!fs_1.default.existsSync(destDir)) {
|
|
34
34
|
fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
35
35
|
}
|
|
36
|
-
await (0, tar_1.extract)({ file: tarPath, cwd: destDir
|
|
36
|
+
await (0, tar_1.extract)({ file: tarPath, cwd: destDir });
|
|
37
37
|
}
|
|
38
38
|
function makeTempDir() {
|
|
39
39
|
return fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'relay-'));
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface InstalledRegistry {
|
|
|
9
9
|
export interface TeamRegistryInfo {
|
|
10
10
|
slug: string;
|
|
11
11
|
name: string;
|
|
12
|
+
description?: string;
|
|
12
13
|
version: string;
|
|
13
14
|
package_url: string;
|
|
14
15
|
commands: {
|
|
@@ -20,7 +21,21 @@ export interface TeamRegistryInfo {
|
|
|
20
21
|
rules: number;
|
|
21
22
|
skills: number;
|
|
22
23
|
};
|
|
24
|
+
tags?: string[];
|
|
25
|
+
install_count?: number;
|
|
26
|
+
requires?: Record<string, unknown>;
|
|
23
27
|
visibility?: "public" | "login-only" | "invite-only";
|
|
28
|
+
welcome?: string | null;
|
|
29
|
+
contact?: Record<string, string> | null;
|
|
30
|
+
author?: {
|
|
31
|
+
username: string;
|
|
32
|
+
display_name: string | null;
|
|
33
|
+
contact_links: Record<string, string>;
|
|
34
|
+
} | null;
|
|
35
|
+
latest_post?: {
|
|
36
|
+
title: string;
|
|
37
|
+
slug: string;
|
|
38
|
+
} | null;
|
|
24
39
|
}
|
|
25
40
|
export interface SearchResult {
|
|
26
41
|
slug: string;
|