relayax-cli 0.3.41 → 0.3.42
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/access.js +12 -12
- package/dist/commands/changelog.js +2 -2
- package/dist/commands/check-update.js +12 -12
- package/dist/commands/create.js +46 -19
- package/dist/commands/deploy-record.js +2 -2
- package/dist/commands/diff.js +2 -2
- package/dist/commands/grant.d.ts +33 -0
- package/dist/commands/grant.js +190 -0
- package/dist/commands/init.js +10 -10
- package/dist/commands/install.js +69 -68
- package/dist/commands/join.js +3 -3
- package/dist/commands/list.js +15 -15
- package/dist/commands/login.js +10 -3
- package/dist/commands/orgs.js +1 -1
- package/dist/commands/outdated.js +7 -7
- package/dist/commands/package.d.ts +18 -0
- package/dist/commands/package.js +355 -146
- package/dist/commands/ping.js +5 -5
- package/dist/commands/publish.d.ts +1 -1
- package/dist/commands/publish.js +56 -48
- package/dist/commands/search.js +2 -2
- package/dist/commands/status.js +11 -11
- package/dist/commands/uninstall.js +7 -7
- package/dist/commands/update.js +22 -22
- package/dist/commands/versions.js +2 -2
- package/dist/index.js +2 -0
- package/dist/lib/ai-tools.d.ts +15 -0
- package/dist/lib/ai-tools.js +48 -1
- package/dist/lib/api.d.ts +7 -7
- package/dist/lib/api.js +11 -11
- package/dist/lib/command-adapter.js +30 -682
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.js +2 -2
- package/dist/lib/guide.js +34 -79
- package/dist/lib/installer.d.ts +2 -2
- package/dist/lib/installer.js +4 -4
- package/dist/lib/preamble.d.ts +4 -4
- package/dist/lib/preamble.js +14 -14
- package/dist/lib/slug.d.ts +5 -0
- package/dist/lib/slug.js +49 -2
- package/dist/lib/update-cache.js +4 -4
- package/dist/lib/version-check.d.ts +3 -3
- package/dist/lib/version-check.js +13 -13
- package/dist/prompts/_business-card.md +41 -0
- package/dist/prompts/_error-handling.md +38 -0
- package/dist/prompts/_requirements-check.md +59 -0
- package/dist/prompts/_setup-cli.md +19 -0
- package/dist/prompts/_setup-login.md +7 -0
- package/dist/prompts/_setup-org.md +27 -0
- package/dist/prompts/business-card.md +41 -0
- package/dist/prompts/error-handling.md +38 -0
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/index.js +28 -0
- package/dist/prompts/install.md +187 -0
- package/dist/prompts/publish.md +444 -0
- package/dist/prompts/requirements-check.md +59 -0
- package/dist/types.d.ts +9 -9
- package/package.json +3 -3
package/dist/commands/publish.js
CHANGED
|
@@ -23,8 +23,8 @@ function parseRelayYaml(content) {
|
|
|
23
23
|
: [];
|
|
24
24
|
const requires = raw.requires;
|
|
25
25
|
const rawVisibility = String(raw.visibility ?? '');
|
|
26
|
-
const visibility = rawVisibility === '
|
|
27
|
-
: rawVisibility === '
|
|
26
|
+
const visibility = rawVisibility === 'internal' ? 'internal'
|
|
27
|
+
: rawVisibility === 'private' ? 'private'
|
|
28
28
|
: rawVisibility === 'public' ? 'public'
|
|
29
29
|
: undefined;
|
|
30
30
|
const rawType = String(raw.type ?? '');
|
|
@@ -46,8 +46,8 @@ function parseRelayYaml(content) {
|
|
|
46
46
|
source: raw.source ? String(raw.source) : undefined,
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
-
function detectCommands(
|
|
50
|
-
const cmdDir = path_1.default.join(
|
|
49
|
+
function detectCommands(agentDir) {
|
|
50
|
+
const cmdDir = path_1.default.join(agentDir, 'commands');
|
|
51
51
|
if (!fs_1.default.existsSync(cmdDir))
|
|
52
52
|
return [];
|
|
53
53
|
const entries = [];
|
|
@@ -78,8 +78,8 @@ function detectCommands(teamDir) {
|
|
|
78
78
|
}
|
|
79
79
|
return entries;
|
|
80
80
|
}
|
|
81
|
-
function detectSkills(
|
|
82
|
-
const skillsDir = path_1.default.join(
|
|
81
|
+
function detectSkills(agentDir) {
|
|
82
|
+
const skillsDir = path_1.default.join(agentDir, 'skills');
|
|
83
83
|
if (!fs_1.default.existsSync(skillsDir))
|
|
84
84
|
return [];
|
|
85
85
|
const entries = [];
|
|
@@ -118,8 +118,8 @@ function detectSkills(teamDir) {
|
|
|
118
118
|
return entries;
|
|
119
119
|
}
|
|
120
120
|
const MCP_KEYWORDS = ['mcp', 'supabase', 'github', 'slack', 'notion', 'linear', 'jira', 'figma', 'stripe', 'openai', 'anthropic', 'postgres', 'mysql', 'redis', 'mongodb', 'firebase', 'aws', 'gcp', 'azure', 'vercel', 'netlify', 'docker', 'kubernetes'];
|
|
121
|
-
function detectAgentDetails(
|
|
122
|
-
const agentsDir = path_1.default.join(
|
|
121
|
+
function detectAgentDetails(agentDir, requires) {
|
|
122
|
+
const agentsDir = path_1.default.join(agentDir, 'agents');
|
|
123
123
|
if (!fs_1.default.existsSync(agentsDir))
|
|
124
124
|
return [];
|
|
125
125
|
const mcpNames = new Set((requires?.mcp ?? []).map((m) => m.name.toLowerCase()));
|
|
@@ -168,8 +168,8 @@ function detectAgentDetails(teamDir, requires) {
|
|
|
168
168
|
return entries;
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
171
|
-
*
|
|
172
|
-
* root SKILL.md를 대체하여
|
|
171
|
+
* 에이전트 진입점 커맨드(commands/{author}-{name}.md)를 생성한다.
|
|
172
|
+
* root SKILL.md를 대체하여 에이전트의 얼굴 역할을 한다.
|
|
173
173
|
*/
|
|
174
174
|
function generateEntryCommand(config, commands, skills, scopedSlug) {
|
|
175
175
|
const lines = [];
|
|
@@ -181,7 +181,7 @@ function generateEntryCommand(config, commands, skills, scopedSlug) {
|
|
|
181
181
|
// Preamble
|
|
182
182
|
lines.push((0, preamble_js_1.generatePreamble)(scopedSlug));
|
|
183
183
|
lines.push('');
|
|
184
|
-
//
|
|
184
|
+
// Agent header
|
|
185
185
|
lines.push(`## ${config.name}`);
|
|
186
186
|
lines.push('');
|
|
187
187
|
lines.push(`v${config.version} — ${scopedSlug}`);
|
|
@@ -210,14 +210,14 @@ function generateEntryCommand(config, commands, skills, scopedSlug) {
|
|
|
210
210
|
lines.push('');
|
|
211
211
|
return lines.join('\n');
|
|
212
212
|
}
|
|
213
|
-
function countDir(
|
|
214
|
-
const dirPath = path_1.default.join(
|
|
213
|
+
function countDir(agentDir, dirName) {
|
|
214
|
+
const dirPath = path_1.default.join(agentDir, dirName);
|
|
215
215
|
if (!fs_1.default.existsSync(dirPath))
|
|
216
216
|
return 0;
|
|
217
217
|
return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.')).length;
|
|
218
218
|
}
|
|
219
|
-
function listDir(
|
|
220
|
-
const dirPath = path_1.default.join(
|
|
219
|
+
function listDir(agentDir, dirName) {
|
|
220
|
+
const dirPath = path_1.default.join(agentDir, dirName);
|
|
221
221
|
if (!fs_1.default.existsSync(dirPath))
|
|
222
222
|
return [];
|
|
223
223
|
return fs_1.default.readdirSync(dirPath).filter((f) => !f.startsWith('.'));
|
|
@@ -227,10 +227,10 @@ function listDir(teamDir, dirName) {
|
|
|
227
227
|
* 1. relay.yaml에 있으면 사용
|
|
228
228
|
* 2. README.md가 있으면 fallback
|
|
229
229
|
*/
|
|
230
|
-
function resolveLongDescription(
|
|
230
|
+
function resolveLongDescription(agentDir, yamlValue) {
|
|
231
231
|
if (yamlValue)
|
|
232
232
|
return yamlValue;
|
|
233
|
-
const readmePath = path_1.default.join(
|
|
233
|
+
const readmePath = path_1.default.join(agentDir, 'README.md');
|
|
234
234
|
if (fs_1.default.existsSync(readmePath)) {
|
|
235
235
|
try {
|
|
236
236
|
return fs_1.default.readFileSync(readmePath, 'utf-8').trim() || undefined;
|
|
@@ -241,21 +241,21 @@ function resolveLongDescription(teamDir, yamlValue) {
|
|
|
241
241
|
}
|
|
242
242
|
return undefined;
|
|
243
243
|
}
|
|
244
|
-
async function createTarball(
|
|
244
|
+
async function createTarball(agentDir) {
|
|
245
245
|
const tmpFile = path_1.default.join(os_1.default.tmpdir(), `relay-publish-${Date.now()}.tar.gz`);
|
|
246
|
-
const dirsToInclude = VALID_DIRS.filter((d) => fs_1.default.existsSync(path_1.default.join(
|
|
246
|
+
const dirsToInclude = VALID_DIRS.filter((d) => fs_1.default.existsSync(path_1.default.join(agentDir, d)));
|
|
247
247
|
// Include root-level files if they exist
|
|
248
248
|
const entries = [...dirsToInclude];
|
|
249
249
|
const rootFiles = ['relay.yaml', 'SKILL.md', 'guide.md'];
|
|
250
250
|
for (const file of rootFiles) {
|
|
251
|
-
if (fs_1.default.existsSync(path_1.default.join(
|
|
251
|
+
if (fs_1.default.existsSync(path_1.default.join(agentDir, file))) {
|
|
252
252
|
entries.push(file);
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
await (0, tar_1.create)({
|
|
256
256
|
gzip: true,
|
|
257
257
|
file: tmpFile,
|
|
258
|
-
cwd:
|
|
258
|
+
cwd: agentDir,
|
|
259
259
|
}, entries);
|
|
260
260
|
return tmpFile;
|
|
261
261
|
}
|
|
@@ -281,14 +281,14 @@ async function publishToApi(token, tarPath, metadata) {
|
|
|
281
281
|
function registerPublish(program) {
|
|
282
282
|
program
|
|
283
283
|
.command('publish')
|
|
284
|
-
.description('현재
|
|
284
|
+
.description('현재 에이전트 패키지를 Space에 배포합니다 (relay.yaml 필요)')
|
|
285
285
|
.option('--token <token>', '인증 토큰')
|
|
286
286
|
.option('--space <slug>', '배포할 Space 지정')
|
|
287
287
|
.option('--version <version>', '배포 버전 지정 (relay.yaml 업데이트)')
|
|
288
288
|
.action(async (opts) => {
|
|
289
289
|
const json = program.opts().json ?? false;
|
|
290
|
-
const
|
|
291
|
-
const relayDir = path_1.default.join(
|
|
290
|
+
const agentDir = process.cwd();
|
|
291
|
+
const relayDir = path_1.default.join(agentDir, '.relay');
|
|
292
292
|
const relayYamlPath = path_1.default.join(relayDir, 'relay.yaml');
|
|
293
293
|
const isTTY = Boolean(process.stdin.isTTY) && !json;
|
|
294
294
|
// CLI update check before publish
|
|
@@ -327,12 +327,12 @@ function registerPublish(program) {
|
|
|
327
327
|
}
|
|
328
328
|
// Interactive onboarding: create relay.yaml
|
|
329
329
|
const { input: promptInput, select: promptSelect } = await import('@inquirer/prompts');
|
|
330
|
-
const dirName = path_1.default.basename(
|
|
330
|
+
const dirName = path_1.default.basename(agentDir);
|
|
331
331
|
const defaultSlug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
332
|
-
console.error('\n\x1b[36m릴레이
|
|
332
|
+
console.error('\n\x1b[36m릴레이 에이전트 패키지를 초기화합니다.\x1b[0m');
|
|
333
333
|
console.error('.relay/relay.yaml을 생성하기 위해 몇 가지 정보를 입력해주세요.\n');
|
|
334
334
|
const name = await promptInput({
|
|
335
|
-
message: '
|
|
335
|
+
message: '에이전트 이름:',
|
|
336
336
|
default: dirName,
|
|
337
337
|
});
|
|
338
338
|
const slug = await promptInput({
|
|
@@ -340,7 +340,7 @@ function registerPublish(program) {
|
|
|
340
340
|
default: defaultSlug,
|
|
341
341
|
});
|
|
342
342
|
const description = await promptInput({
|
|
343
|
-
message: '
|
|
343
|
+
message: '에이전트 설명 (필수):',
|
|
344
344
|
validate: (v) => v.trim().length > 0 ? true : '설명을 입력해주세요.',
|
|
345
345
|
});
|
|
346
346
|
const tagsRaw = await promptInput({
|
|
@@ -351,16 +351,16 @@ function registerPublish(program) {
|
|
|
351
351
|
message: '공개 범위:',
|
|
352
352
|
choices: [
|
|
353
353
|
{ name: '공개 — 누구나 설치', value: 'public' },
|
|
354
|
-
{ name: '링크 공유 — 접근 링크가 있는 사람만 설치', value: '
|
|
355
|
-
{ name: '비공개 —
|
|
354
|
+
{ name: '링크 공유 — 접근 링크가 있는 사람만 설치', value: 'private' },
|
|
355
|
+
{ name: '비공개 — Org 멤버만', value: 'internal' },
|
|
356
356
|
],
|
|
357
357
|
});
|
|
358
358
|
console.error('\n\x1b[2m💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: www.relayax.com/dashboard/profile\x1b[0m');
|
|
359
|
-
if (visibility === '
|
|
360
|
-
console.error('\x1b[2m💡 링크 공유
|
|
359
|
+
if (visibility === 'private') {
|
|
360
|
+
console.error('\x1b[2m💡 링크 공유 에이전트는 웹 대시보드에서 접근 링크와 구매 안내를 설정하세요: www.relayax.com/dashboard\x1b[0m');
|
|
361
361
|
}
|
|
362
|
-
else if (visibility === '
|
|
363
|
-
console.error('\x1b[2m💡 비공개
|
|
362
|
+
else if (visibility === 'internal') {
|
|
363
|
+
console.error('\x1b[2m💡 비공개 에이전트는 Org를 통해 멤버를 관리하세요: www.relayax.com/dashboard/agents\x1b[0m');
|
|
364
364
|
}
|
|
365
365
|
console.error('');
|
|
366
366
|
const tags = tagsRaw
|
|
@@ -537,11 +537,11 @@ function registerPublish(program) {
|
|
|
537
537
|
},
|
|
538
538
|
{
|
|
539
539
|
name: '링크 공유 — 접근 링크가 있는 사람만 설치',
|
|
540
|
-
value: '
|
|
540
|
+
value: 'private',
|
|
541
541
|
},
|
|
542
542
|
{
|
|
543
|
-
name: `비공개 —
|
|
544
|
-
value: '
|
|
543
|
+
name: `비공개 — Org 멤버만 접근`,
|
|
544
|
+
value: 'internal',
|
|
545
545
|
},
|
|
546
546
|
],
|
|
547
547
|
default: defaultVisibility,
|
|
@@ -558,8 +558,8 @@ function registerPublish(program) {
|
|
|
558
558
|
message: 'relay.yaml에 visibility를 설정해주세요.',
|
|
559
559
|
options: [
|
|
560
560
|
{ value: 'public', label: '공개 — 누구나 설치' },
|
|
561
|
-
{ value: '
|
|
562
|
-
{ value: '
|
|
561
|
+
{ value: 'private', label: '링크 공유 — 접근 링크가 있는 사람만 설치' },
|
|
562
|
+
{ value: 'internal', label: '비공개 — Org 멤버만 접근' },
|
|
563
563
|
],
|
|
564
564
|
fix: 'relay.yaml의 visibility 필드를 위 옵션 중 하나로 설정하세요.',
|
|
565
565
|
}));
|
|
@@ -571,8 +571,8 @@ function registerPublish(program) {
|
|
|
571
571
|
const { select: promptConfirmVis } = await import('@inquirer/prompts');
|
|
572
572
|
const visLabelMap = {
|
|
573
573
|
public: '공개',
|
|
574
|
-
|
|
575
|
-
|
|
574
|
+
private: '링크공유',
|
|
575
|
+
internal: '비공개',
|
|
576
576
|
};
|
|
577
577
|
const currentVisLabel = visLabelMap[config.visibility ?? 'public'] ?? config.visibility;
|
|
578
578
|
const newVisibility = await promptConfirmVis({
|
|
@@ -584,11 +584,11 @@ function registerPublish(program) {
|
|
|
584
584
|
},
|
|
585
585
|
{
|
|
586
586
|
name: '링크공유 — 접근 링크가 있는 사람만 설치',
|
|
587
|
-
value: '
|
|
587
|
+
value: 'private',
|
|
588
588
|
},
|
|
589
589
|
{
|
|
590
|
-
name: `비공개 —
|
|
591
|
-
value: '
|
|
590
|
+
name: `비공개 — Org 멤버만 접근`,
|
|
591
|
+
value: 'internal',
|
|
592
592
|
},
|
|
593
593
|
],
|
|
594
594
|
default: config.visibility ?? defaultVisibility,
|
|
@@ -641,7 +641,7 @@ function registerPublish(program) {
|
|
|
641
641
|
// GUIDE.html deprecation warning
|
|
642
642
|
if (fs_1.default.existsSync(path_1.default.join(relayDir, 'GUIDE.html'))) {
|
|
643
643
|
console.error('\x1b[33m⚠ GUIDE.html은 더 이상 지원되지 않습니다. 상세페이지가 가이드 역할을 합니다.\x1b[0m');
|
|
644
|
-
console.error(' long_description을 활용하거나 relayax.com에서
|
|
644
|
+
console.error(' long_description을 활용하거나 relayax.com에서 에이전트 정보를 편집하세요.\n');
|
|
645
645
|
}
|
|
646
646
|
// Generate guide.md (consumer install guide)
|
|
647
647
|
const { generateGuide } = await import('../lib/guide.js');
|
|
@@ -711,7 +711,10 @@ function registerPublish(program) {
|
|
|
711
711
|
// Show shareable onboarding guide as a plain copyable block
|
|
712
712
|
if (isTTY) {
|
|
713
713
|
const detailSlug = result.slug.startsWith('@') ? result.slug.slice(1) : result.slug;
|
|
714
|
-
const
|
|
714
|
+
const accessCode = result.access_code;
|
|
715
|
+
const guideUrl = accessCode
|
|
716
|
+
? `https://relayax.com/api/registry/${detailSlug}/guide.md?code=${accessCode}`
|
|
717
|
+
: `https://relayax.com/api/registry/${detailSlug}/guide.md`;
|
|
715
718
|
console.log(`\n \x1b[90m주변인에게 공유하세요:\x1b[0m\n`);
|
|
716
719
|
console.log('```');
|
|
717
720
|
console.log('아래 가이드를 읽고 그대로 따라해줘:');
|
|
@@ -719,9 +722,14 @@ function registerPublish(program) {
|
|
|
719
722
|
console.log('```');
|
|
720
723
|
console.log(`\n \x1b[90mCLI 설치된 사용자용 (짧은 버전):\x1b[0m`);
|
|
721
724
|
console.log(` /relay:relay-install ${result.slug}`);
|
|
722
|
-
if (config.visibility
|
|
725
|
+
if (config.visibility === 'private') {
|
|
726
|
+
console.log(`\n \x1b[90mprivate 에이전트:\x1b[0m`);
|
|
727
|
+
console.log(` 접근 링크를 생성한 뒤 guide.md?code={agent_code}로 공유하세요.`);
|
|
728
|
+
console.log(` 접근 링크 관리: \x1b[36mrelayax.com/dashboard/agent-access/${config.slug}\x1b[0m`);
|
|
729
|
+
}
|
|
730
|
+
else if (config.visibility !== 'internal') {
|
|
723
731
|
console.log(`\n \x1b[90m유료 판매하려면:\x1b[0m`);
|
|
724
|
-
console.log(` 1. 가시성을 "
|
|
732
|
+
console.log(` 1. 가시성을 "private"로 변경: \x1b[36mrelayax.com/dashboard\x1b[0m`);
|
|
725
733
|
console.log(` 2. API 키 발급: \x1b[36mrelayax.com/dashboard/keys\x1b[0m`);
|
|
726
734
|
console.log(` 3. 웹훅 연동 가이드: \x1b[36mrelayax.com/docs/webhook-guide.md\x1b[0m`);
|
|
727
735
|
}
|
package/dist/commands/search.js
CHANGED
|
@@ -26,13 +26,13 @@ function formatTable(results) {
|
|
|
26
26
|
function registerSearch(program) {
|
|
27
27
|
program
|
|
28
28
|
.command('search <keyword>')
|
|
29
|
-
.description('Space에서 에이전트
|
|
29
|
+
.description('Space에서 에이전트 검색 (공개 에이전트 + 내 Space 에이전트)')
|
|
30
30
|
.option('--tag <tag>', '태그로 필터링')
|
|
31
31
|
.option('--space <space>', '특정 Space 내에서 검색')
|
|
32
32
|
.action(async (keyword, opts) => {
|
|
33
33
|
const json = program.opts().json ?? false;
|
|
34
34
|
try {
|
|
35
|
-
const results = await (0, api_js_1.
|
|
35
|
+
const results = await (0, api_js_1.searchAgents)(keyword, opts.tag);
|
|
36
36
|
if (json) {
|
|
37
37
|
console.log(JSON.stringify({ results }));
|
|
38
38
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -47,27 +47,27 @@ function registerStatus(program) {
|
|
|
47
47
|
const localDir = path_1.default.join(projectPath, primaryAgent.skillsDir, 'commands', 'relay');
|
|
48
48
|
hasLocal = command_adapter_js_1.BUILDER_COMMANDS.some((cmd) => fs_1.default.existsSync(path_1.default.join(localDir, `${cmd.id}.md`)));
|
|
49
49
|
}
|
|
50
|
-
// 3.
|
|
50
|
+
// 3. 에이전트 프로젝트 정보
|
|
51
51
|
const relayYamlPath = path_1.default.join(projectPath, '.relay', 'relay.yaml');
|
|
52
|
-
let
|
|
52
|
+
let project = null;
|
|
53
53
|
if (fs_1.default.existsSync(relayYamlPath)) {
|
|
54
54
|
try {
|
|
55
55
|
const yaml = await import('js-yaml');
|
|
56
56
|
const content = fs_1.default.readFileSync(relayYamlPath, 'utf-8');
|
|
57
57
|
const raw = yaml.load(content);
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
project = {
|
|
59
|
+
is_agent: true,
|
|
60
60
|
name: String(raw.name ?? ''),
|
|
61
61
|
slug: String(raw.slug ?? ''),
|
|
62
62
|
version: String(raw.version ?? ''),
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
catch {
|
|
66
|
-
|
|
66
|
+
project = { is_agent: true };
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
else {
|
|
70
|
-
|
|
70
|
+
project = { is_agent: false };
|
|
71
71
|
}
|
|
72
72
|
// 4. 출력
|
|
73
73
|
if (json) {
|
|
@@ -78,7 +78,7 @@ function registerStatus(program) {
|
|
|
78
78
|
global_commands: hasGlobal,
|
|
79
79
|
local_commands: hasLocal,
|
|
80
80
|
},
|
|
81
|
-
|
|
81
|
+
project,
|
|
82
82
|
};
|
|
83
83
|
console.log(JSON.stringify(result));
|
|
84
84
|
}
|
|
@@ -103,12 +103,12 @@ function registerStatus(program) {
|
|
|
103
103
|
else {
|
|
104
104
|
console.log(` \x1b[31m✗\x1b[0m 에이전트: 감지 안 됨`);
|
|
105
105
|
}
|
|
106
|
-
//
|
|
107
|
-
if (
|
|
108
|
-
console.log(` \x1b[32m✓\x1b[0m 현재
|
|
106
|
+
// 에이전트 프로젝트
|
|
107
|
+
if (project?.is_agent && project.name) {
|
|
108
|
+
console.log(` \x1b[32m✓\x1b[0m 현재 에이전트: \x1b[36m${project.name}\x1b[0m v${project.version}`);
|
|
109
109
|
}
|
|
110
110
|
else {
|
|
111
|
-
console.log(` \x1b[2m—\x1b[0m 현재 프로젝트:
|
|
111
|
+
console.log(` \x1b[2m—\x1b[0m 현재 프로젝트: 에이전트 아님`);
|
|
112
112
|
}
|
|
113
113
|
console.log('');
|
|
114
114
|
}
|
|
@@ -12,12 +12,12 @@ const slug_js_1 = require("../lib/slug.js");
|
|
|
12
12
|
function registerUninstall(program) {
|
|
13
13
|
program
|
|
14
14
|
.command('uninstall <slug>')
|
|
15
|
-
.description('에이전트
|
|
15
|
+
.description('에이전트 제거')
|
|
16
16
|
.action((slugInput) => {
|
|
17
17
|
const json = program.opts().json ?? false;
|
|
18
18
|
const localInstalled = (0, config_js_1.loadInstalled)();
|
|
19
19
|
const globalInstalled = (0, config_js_1.loadGlobalInstalled)();
|
|
20
|
-
// Resolve slug — support short names like "cardnews-
|
|
20
|
+
// Resolve slug — support short names like "cardnews-agent"
|
|
21
21
|
let slug;
|
|
22
22
|
if ((0, slug_js_1.isScopedSlug)(slugInput)) {
|
|
23
23
|
slug = slugInput;
|
|
@@ -45,11 +45,11 @@ function registerUninstall(program) {
|
|
|
45
45
|
let totalRemoved = 0;
|
|
46
46
|
// Remove from local registry
|
|
47
47
|
if (localEntry) {
|
|
48
|
-
const removed = (0, installer_js_1.
|
|
48
|
+
const removed = (0, installer_js_1.uninstallAgent)(localEntry.files);
|
|
49
49
|
totalRemoved += removed.length;
|
|
50
50
|
// Remove deployed files
|
|
51
51
|
if (localEntry.deployed_files && localEntry.deployed_files.length > 0) {
|
|
52
|
-
const deployedRemoved = (0, installer_js_1.
|
|
52
|
+
const deployedRemoved = (0, installer_js_1.uninstallAgent)(localEntry.deployed_files);
|
|
53
53
|
totalRemoved += deployedRemoved.length;
|
|
54
54
|
// Clean empty parent directories
|
|
55
55
|
const boundary = path_1.default.join(process.cwd(), '.claude');
|
|
@@ -64,12 +64,12 @@ function registerUninstall(program) {
|
|
|
64
64
|
if (globalEntry) {
|
|
65
65
|
// Only remove files if not already handled by local entry
|
|
66
66
|
if (!localEntry) {
|
|
67
|
-
const removed = (0, installer_js_1.
|
|
67
|
+
const removed = (0, installer_js_1.uninstallAgent)(globalEntry.files);
|
|
68
68
|
totalRemoved += removed.length;
|
|
69
69
|
}
|
|
70
70
|
// Remove globally deployed files
|
|
71
71
|
if (globalEntry.deployed_files && globalEntry.deployed_files.length > 0) {
|
|
72
|
-
const deployedRemoved = (0, installer_js_1.
|
|
72
|
+
const deployedRemoved = (0, installer_js_1.uninstallAgent)(globalEntry.deployed_files);
|
|
73
73
|
totalRemoved += deployedRemoved.length;
|
|
74
74
|
// Clean empty parent directories
|
|
75
75
|
const boundary = path_1.default.join(os_1.default.homedir(), '.claude');
|
|
@@ -82,7 +82,7 @@ function registerUninstall(program) {
|
|
|
82
82
|
}
|
|
83
83
|
const result = {
|
|
84
84
|
status: 'ok',
|
|
85
|
-
|
|
85
|
+
agent: slug,
|
|
86
86
|
files_removed: totalRemoved,
|
|
87
87
|
};
|
|
88
88
|
if (json) {
|
package/dist/commands/update.js
CHANGED
|
@@ -11,9 +11,9 @@ const preamble_js_1 = require("../lib/preamble.js");
|
|
|
11
11
|
function registerUpdate(program) {
|
|
12
12
|
program
|
|
13
13
|
.command('update <slug>')
|
|
14
|
-
.description('설치된
|
|
14
|
+
.description('설치된 에이전트를 최신 버전으로 업데이트합니다')
|
|
15
15
|
.option('--path <install_path>', '설치 경로 지정 (기본: ./.claude)')
|
|
16
|
-
.option('--code <code>', '초대 코드 (비공개
|
|
16
|
+
.option('--code <code>', '초대 코드 (비공개 에이전트 업데이트 시 필요)')
|
|
17
17
|
.action(async (slugInput, opts) => {
|
|
18
18
|
const json = program.opts().json ?? false;
|
|
19
19
|
const installPath = (0, config_js_1.getInstallPath)(opts.path);
|
|
@@ -32,9 +32,9 @@ function registerUpdate(program) {
|
|
|
32
32
|
// Check installed.json for current version
|
|
33
33
|
const currentEntry = installed[slug];
|
|
34
34
|
const currentVersion = currentEntry?.version ?? null;
|
|
35
|
-
// Fetch latest
|
|
36
|
-
const
|
|
37
|
-
const latestVersion =
|
|
35
|
+
// Fetch latest agent metadata
|
|
36
|
+
const agent = await (0, api_js_1.fetchAgentInfo)(slug);
|
|
37
|
+
const latestVersion = agent.version;
|
|
38
38
|
if (currentVersion && currentVersion === latestVersion) {
|
|
39
39
|
if (json) {
|
|
40
40
|
console.log(JSON.stringify({ status: 'up-to-date', slug, version: latestVersion }));
|
|
@@ -45,29 +45,29 @@ function registerUpdate(program) {
|
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
// Visibility check
|
|
48
|
-
const visibility =
|
|
49
|
-
if (visibility === '
|
|
48
|
+
const visibility = agent.visibility ?? 'public';
|
|
49
|
+
if (visibility === 'internal') {
|
|
50
50
|
const token = await (0, config_js_1.getValidToken)();
|
|
51
51
|
if (!token) {
|
|
52
|
-
console.error('이
|
|
52
|
+
console.error('이 에이전트는 Org 멤버만 업데이트할 수 있습니다. `relay login`을 먼저 실행하세요.');
|
|
53
53
|
process.exit(1);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
// Download package
|
|
57
|
-
const tarPath = await (0, storage_js_1.downloadPackage)(
|
|
57
|
+
const tarPath = await (0, storage_js_1.downloadPackage)(agent.package_url, tempDir);
|
|
58
58
|
// Extract
|
|
59
59
|
const extractDir = `${tempDir}/extracted`;
|
|
60
60
|
await (0, storage_js_1.extractPackage)(tarPath, extractDir);
|
|
61
61
|
// Inject preamble (update check) before copying
|
|
62
|
-
(0, preamble_js_1.
|
|
62
|
+
(0, preamble_js_1.injectPreambleToAgent)(extractDir, slug);
|
|
63
63
|
// Copy files to install_path
|
|
64
|
-
const files = (0, installer_js_1.
|
|
64
|
+
const files = (0, installer_js_1.installAgent)(extractDir, installPath);
|
|
65
65
|
// Preserve deploy info but clear deployed_files (agent needs to re-deploy)
|
|
66
66
|
const previousDeployScope = currentEntry?.deploy_scope;
|
|
67
67
|
const hadDeployedFiles = (currentEntry?.deployed_files?.length ?? 0) > 0;
|
|
68
68
|
// Update installed.json with new version
|
|
69
69
|
installed[slug] = {
|
|
70
|
-
|
|
70
|
+
agent_id: agent.id,
|
|
71
71
|
version: latestVersion,
|
|
72
72
|
installed_at: new Date().toISOString(),
|
|
73
73
|
files,
|
|
@@ -76,8 +76,8 @@ function registerUpdate(program) {
|
|
|
76
76
|
// Clear deployed_files — agent must re-deploy and call deploy-record
|
|
77
77
|
};
|
|
78
78
|
(0, config_js_1.saveInstalled)(installed);
|
|
79
|
-
// Report install (non-blocking,
|
|
80
|
-
await (0, api_js_1.reportInstall)(
|
|
79
|
+
// Report install (non-blocking, agent_id 기반)
|
|
80
|
+
await (0, api_js_1.reportInstall)(agent.id, slug, latestVersion);
|
|
81
81
|
const result = {
|
|
82
82
|
status: 'updated',
|
|
83
83
|
slug,
|
|
@@ -92,17 +92,17 @@ function registerUpdate(program) {
|
|
|
92
92
|
}
|
|
93
93
|
else {
|
|
94
94
|
const fromLabel = currentVersion ? `v${currentVersion} → ` : '';
|
|
95
|
-
console.log(`\n\x1b[32m✓ ${
|
|
95
|
+
console.log(`\n\x1b[32m✓ ${agent.name} ${fromLabel}v${latestVersion} 업데이트 완료\x1b[0m`);
|
|
96
96
|
console.log(` 설치 위치: \x1b[36m${installPath}\x1b[0m`);
|
|
97
97
|
console.log(` 파일 수: ${files.length}개`);
|
|
98
98
|
// Builder business card
|
|
99
|
-
const authorUsername =
|
|
100
|
-
const authorDisplayName =
|
|
101
|
-
const contactParts = (0, contact_format_js_1.formatContactParts)(
|
|
102
|
-
const hasCard =
|
|
99
|
+
const authorUsername = agent.author?.username;
|
|
100
|
+
const authorDisplayName = agent.author?.display_name ?? authorUsername ?? '';
|
|
101
|
+
const contactParts = (0, contact_format_js_1.formatContactParts)(agent.author?.contact_links);
|
|
102
|
+
const hasCard = agent.welcome || contactParts.length > 0 || authorUsername;
|
|
103
103
|
// Show changelog for this version
|
|
104
104
|
try {
|
|
105
|
-
const versions = await (0, api_js_1.
|
|
105
|
+
const versions = await (0, api_js_1.fetchAgentVersions)(slug);
|
|
106
106
|
const thisVersion = versions.find((v) => v.version === latestVersion);
|
|
107
107
|
if (thisVersion?.changelog) {
|
|
108
108
|
console.log(`\n \x1b[90m── Changelog ──────────────────────────────\x1b[0m`);
|
|
@@ -117,8 +117,8 @@ function registerUpdate(program) {
|
|
|
117
117
|
}
|
|
118
118
|
if (hasCard) {
|
|
119
119
|
console.log(`\n \x1b[90m┌─ ${authorDisplayName || '빌더'}의 명함 ${'─'.repeat(Math.max(0, 34 - (authorDisplayName || '빌더').length))}┐\x1b[0m`);
|
|
120
|
-
if (
|
|
121
|
-
const truncated =
|
|
120
|
+
if (agent.welcome) {
|
|
121
|
+
const truncated = agent.welcome.length > 45 ? agent.welcome.slice(0, 45) + '...' : agent.welcome;
|
|
122
122
|
console.log(` \x1b[90m│\x1b[0m 💬 "${truncated}"`);
|
|
123
123
|
}
|
|
124
124
|
if (contactParts.length > 0) {
|
|
@@ -6,12 +6,12 @@ const slug_js_1 = require("../lib/slug.js");
|
|
|
6
6
|
function registerVersions(program) {
|
|
7
7
|
program
|
|
8
8
|
.command('versions <slug>')
|
|
9
|
-
.description('
|
|
9
|
+
.description('에이전트 버전 목록과 릴리즈 노트를 확인합니다')
|
|
10
10
|
.action(async (slugInput) => {
|
|
11
11
|
const json = program.opts().json ?? false;
|
|
12
12
|
try {
|
|
13
13
|
const resolved = await (0, slug_js_1.resolveSlug)(slugInput);
|
|
14
|
-
const versions = await (0, api_js_1.
|
|
14
|
+
const versions = await (0, api_js_1.fetchAgentVersions)(resolved.full);
|
|
15
15
|
if (json) {
|
|
16
16
|
console.log(JSON.stringify({ slug: resolved.full, versions }));
|
|
17
17
|
return;
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ const orgs_js_1 = require("./commands/orgs.js");
|
|
|
22
22
|
const deploy_record_js_1 = require("./commands/deploy-record.js");
|
|
23
23
|
const ping_js_1 = require("./commands/ping.js");
|
|
24
24
|
const access_js_1 = require("./commands/access.js");
|
|
25
|
+
const grant_js_1 = require("./commands/grant.js");
|
|
25
26
|
const versions_js_1 = require("./commands/versions.js");
|
|
26
27
|
const diff_js_1 = require("./commands/diff.js");
|
|
27
28
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
@@ -52,6 +53,7 @@ program
|
|
|
52
53
|
(0, deploy_record_js_1.registerDeployRecord)(program);
|
|
53
54
|
(0, ping_js_1.registerPing)(program);
|
|
54
55
|
(0, access_js_1.registerAccess)(program);
|
|
56
|
+
(0, grant_js_1.registerGrant)(program);
|
|
55
57
|
(0, versions_js_1.registerVersions)(program);
|
|
56
58
|
(0, diff_js_1.registerDiff)(program);
|
|
57
59
|
program.parse();
|
package/dist/lib/ai-tools.d.ts
CHANGED
|
@@ -17,3 +17,18 @@ export declare function detectAgentCLIs(projectPath: string): AITool[];
|
|
|
17
17
|
* ~/{skillsDir}/ 가 존재하는 CLI를 반환.
|
|
18
18
|
*/
|
|
19
19
|
export declare function detectGlobalCLIs(): AITool[];
|
|
20
|
+
export type ContentType = 'skill' | 'agent' | 'command' | 'rule';
|
|
21
|
+
export interface ContentItem {
|
|
22
|
+
name: string;
|
|
23
|
+
type: ContentType;
|
|
24
|
+
/** 소스 디렉토리 기준 상대 경로 (예: skills/code-review) */
|
|
25
|
+
relativePath: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 프로젝트 로컬 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
|
|
29
|
+
*/
|
|
30
|
+
export declare function scanLocalItems(projectPath: string, tool: AITool): ContentItem[];
|
|
31
|
+
/**
|
|
32
|
+
* 글로벌 홈 디렉토리 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
|
|
33
|
+
*/
|
|
34
|
+
export declare function scanGlobalItems(tool: AITool): ContentItem[];
|
package/dist/lib/ai-tools.js
CHANGED
|
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.AI_TOOLS = void 0;
|
|
7
7
|
exports.detectAgentCLIs = detectAgentCLIs;
|
|
8
8
|
exports.detectGlobalCLIs = detectGlobalCLIs;
|
|
9
|
+
exports.scanLocalItems = scanLocalItems;
|
|
10
|
+
exports.scanGlobalItems = scanGlobalItems;
|
|
9
11
|
const fs_1 = __importDefault(require("fs"));
|
|
10
12
|
const os_1 = __importDefault(require("os"));
|
|
11
13
|
const path_1 = __importDefault(require("path"));
|
|
@@ -50,6 +52,51 @@ function detectAgentCLIs(projectPath) {
|
|
|
50
52
|
* ~/{skillsDir}/ 가 존재하는 CLI를 반환.
|
|
51
53
|
*/
|
|
52
54
|
function detectGlobalCLIs() {
|
|
53
|
-
const home =
|
|
55
|
+
const home = os_1.default.homedir();
|
|
54
56
|
return exports.AI_TOOLS.filter((tool) => fs_1.default.existsSync(path_1.default.join(home, tool.skillsDir)));
|
|
55
57
|
}
|
|
58
|
+
const CONTENT_DIRS = [
|
|
59
|
+
{ dir: 'skills', type: 'skill' },
|
|
60
|
+
{ dir: 'agents', type: 'agent' },
|
|
61
|
+
{ dir: 'commands', type: 'command' },
|
|
62
|
+
{ dir: 'rules', type: 'rule' },
|
|
63
|
+
];
|
|
64
|
+
const EXCLUDE_SUBDIRS = ['relay'];
|
|
65
|
+
/**
|
|
66
|
+
* 소스 디렉토리(basePath) 안의 skills/, agents/, commands/, rules/에서
|
|
67
|
+
* 개별 항목을 스캔하여 반환한다.
|
|
68
|
+
*/
|
|
69
|
+
function scanItemsIn(basePath) {
|
|
70
|
+
const items = [];
|
|
71
|
+
for (const { dir, type } of CONTENT_DIRS) {
|
|
72
|
+
const fullDir = path_1.default.join(basePath, dir);
|
|
73
|
+
if (!fs_1.default.existsSync(fullDir))
|
|
74
|
+
continue;
|
|
75
|
+
for (const entry of fs_1.default.readdirSync(fullDir, { withFileTypes: true })) {
|
|
76
|
+
if (entry.name.startsWith('.'))
|
|
77
|
+
continue;
|
|
78
|
+
if (entry.isDirectory() && EXCLUDE_SUBDIRS.includes(entry.name))
|
|
79
|
+
continue;
|
|
80
|
+
items.push({
|
|
81
|
+
name: entry.name.replace(/\.\w+$/, ''), // 파일이면 확장자 제거
|
|
82
|
+
type,
|
|
83
|
+
relativePath: path_1.default.join(dir, entry.name),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return items;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 프로젝트 로컬 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
|
|
91
|
+
*/
|
|
92
|
+
function scanLocalItems(projectPath, tool) {
|
|
93
|
+
const basePath = path_1.default.join(projectPath, tool.skillsDir);
|
|
94
|
+
return scanItemsIn(basePath);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 글로벌 홈 디렉토리 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
|
|
98
|
+
*/
|
|
99
|
+
function scanGlobalItems(tool) {
|
|
100
|
+
const basePath = path_1.default.join(os_1.default.homedir(), tool.skillsDir);
|
|
101
|
+
return scanItemsIn(basePath);
|
|
102
|
+
}
|