relayax-cli 0.4.26 → 0.4.28

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.
@@ -256,7 +256,7 @@ function registerInstall(program) {
256
256
  // 4.5. Inject preamble (update check) into SKILL.md and commands
257
257
  (0, preamble_js_1.injectPreambleToAgent)(agentDir, slug);
258
258
  // 5. Deploy symlinks to detected AI tool directories
259
- const deploy = (0, installer_js_1.deploySymlinks)(agentDir, scope, projectPath);
259
+ const deploy = await (0, installer_js_1.deploySymlinks)(agentDir, scope, projectPath);
260
260
  for (const w of deploy.warnings) {
261
261
  if (!json)
262
262
  console.error(`\x1b[33m${w}\x1b[0m`);
@@ -315,18 +315,6 @@ function registerPackage(program) {
315
315
  });
316
316
  }
317
317
  }
318
- // 마운트 경로 스캔 (Cowork/sandbox 환경)
319
- for (const { tool, basePath } of (0, ai_tools_js_1.detectMountedCLIs)()) {
320
- const items = (0, ai_tools_js_1.scanMountedItems)(basePath, tool);
321
- if (items.length > 0) {
322
- sources.push({
323
- path: `${basePath}/${tool.skillsDir}`,
324
- location: 'global',
325
- name: `${tool.name} (mounted)`,
326
- items,
327
- });
328
- }
329
- }
330
318
  // ~/.relay/agents/ 에 기존 에이전트 패키지가 있는지 스캔
331
319
  const globalAgentsDir = path_1.default.join(homeDir ?? os_1.default.homedir(), '.relay', 'agents');
332
320
  const existingAgents = [];
@@ -68,6 +68,7 @@ export interface PublishMetadata {
68
68
  requires?: Requires;
69
69
  visibility?: 'public' | 'private' | 'internal';
70
70
  type?: 'command' | 'passive' | 'hybrid';
71
+ recommended_scope?: 'global' | 'local';
71
72
  cli_version?: string;
72
73
  agent_names?: string[];
73
74
  skill_names?: string[];
@@ -15,7 +15,6 @@ const paths_js_1 = require("../lib/paths.js");
15
15
  const error_report_js_1 = require("../lib/error-report.js");
16
16
  const step_tracker_js_1 = require("../lib/step-tracker.js");
17
17
  const git_operations_js_1 = require("../lib/git-operations.js");
18
- // GUIDE_INSTRUCTION removed — share text now uses npx install command directly
19
18
  // eslint-disable-next-line @typescript-eslint/no-var-requires
20
19
  const cliPkg = require('../../package.json');
21
20
  const VALID_DIRS = ['skills', 'agents', 'rules', 'commands', 'bin'];
@@ -46,6 +45,7 @@ function parseRelayYaml(content) {
46
45
  requires,
47
46
  visibility,
48
47
  type,
48
+ recommended_scope: raw.recommended_scope === 'global' ? 'global' : raw.recommended_scope === 'local' ? 'local' : undefined,
49
49
  source: raw.source ? String(raw.source) : undefined,
50
50
  org_slug: raw.org_slug ? String(raw.org_slug) : undefined,
51
51
  };
@@ -269,6 +269,7 @@ function registerPublish(program) {
269
269
  .option('--token <token>', '인증 토큰')
270
270
  .option('--space <slug>', '배포할 Space 지정')
271
271
  .option('--org <slug>', 'Organization slug 지정')
272
+ .option('--no-org', '개인 계정으로 배포 (Organization 무시)')
272
273
  .option('--version <version>', '배포 버전 지정 (relay.yaml 업데이트)')
273
274
  .option('--patch', 'patch 버전 범프')
274
275
  .option('--minor', 'minor 버전 범프')
@@ -471,10 +472,18 @@ function registerPublish(program) {
471
472
  try {
472
473
  const { fetchMyOrgs } = await import('./orgs.js');
473
474
  const orgs = await fetchMyOrgs(token);
475
+ // --no-org: skip org selection entirely (personal deployment)
476
+ const skipOrg = opts.noOrg === true;
474
477
  // Determine explicit org slug: --org > --space (legacy) > relay.yaml org_slug
475
- const explicitOrgSlug = opts.org ?? opts.space ?? config.org_slug;
478
+ const explicitOrgSlug = skipOrg ? undefined : (opts.org ?? opts.space ?? config.org_slug);
476
479
  // --org / --space / relay.yaml org_slug: resolve Org by slug
477
- if (explicitOrgSlug) {
480
+ if (skipOrg) {
481
+ // Personal deployment — no org
482
+ if (!json) {
483
+ console.error('\x1b[2m 개인 계정으로 배포합니다.\x1b[0m\n');
484
+ }
485
+ }
486
+ else if (explicitOrgSlug) {
478
487
  const matched = orgs.find((o) => o.slug === explicitOrgSlug);
479
488
  if (matched) {
480
489
  selectedOrgId = matched.id;
@@ -529,18 +538,22 @@ function registerPublish(program) {
529
538
  console.error(` → Organization: ${chosenLabel}\n`);
530
539
  }
531
540
  }
532
- else if (orgs.length > 1 && json) {
533
- // --json 모드 + 여러 Org: 에이전트가 선택할 수 있도록 에러 반환
534
- (0, error_report_js_1.reportCliError)('publish', 'MISSING_ORG', 'multiple orgs, none selected');
541
+ else if (orgs.length > 0 && json) {
542
+ // --json 모드 + Org 있음: 에이전트가 선택할 수 있도록 에러 반환
543
+ (0, error_report_js_1.reportCliError)('publish', 'MISSING_ORG', `${orgs.length} orgs, none selected`);
535
544
  console.error(JSON.stringify({
536
545
  error: 'MISSING_ORG',
537
- message: '배포할 Organization을 선택하세요.',
538
- fix: `relay publish --org <slug> --json`,
539
- options: orgs.map((o) => ({ value: o.slug, label: `${o.name} (${o.slug})` })),
546
+ message: '배포 대상을 선택하세요.',
547
+ fix: `개인 배포: relay publish --no-org --json / Org 배포: relay publish --org <slug> --json`,
548
+ options: [
549
+ { value: '__personal__', label: '개인 계정으로 배포' },
550
+ ...orgs.map((o) => ({ value: o.slug, label: `${o.name} (${o.slug})` })),
551
+ ],
540
552
  }));
541
553
  process.exit(1);
542
554
  }
543
555
  else if (orgs.length > 0) {
556
+ // non-json, non-TTY fallback (rare) — auto-select first org
544
557
  selectedOrgId = orgs[0].id;
545
558
  selectedOrgSlug = orgs[0].slug;
546
559
  }
@@ -692,6 +705,7 @@ function registerPublish(program) {
692
705
  agent_names: listDir(relayDir, 'agents'),
693
706
  skill_names: listDir(relayDir, 'skills'),
694
707
  type: config.type ?? 'hybrid',
708
+ recommended_scope: config.recommended_scope,
695
709
  agent_details: detectedAgents,
696
710
  skill_details: detectedSkills,
697
711
  ...(selectedOrgId ? { org_id: selectedOrgId } : {}),
@@ -700,12 +714,6 @@ function registerPublish(program) {
700
714
  if (!json) {
701
715
  console.error(`패키지 생성 중... (${config.name} v${config.version})`);
702
716
  }
703
- // GUIDE.html deprecation warning
704
- if (fs_1.default.existsSync(path_1.default.join(relayDir, 'GUIDE.html'))) {
705
- console.error('\x1b[33m⚠ GUIDE.html은 더 이상 지원되지 않습니다. 상세페이지가 가이드 역할을 합니다.\x1b[0m');
706
- console.error(' long_description을 활용하거나 relayax.com에서 에이전트 정보를 편집하세요.\n');
707
- }
708
- // guide.md는 웹 API route에서 동적 생성 (/api/registry/{owner}/{slug}/guide.md)
709
717
  // Generate bin/relay-preamble.sh (self-contained tracking + update check)
710
718
  (0, preamble_js_1.generatePreambleBin)(relayDir, config.slug, config_js_1.API_URL);
711
719
  // Generate entry command (commands/{author}-{name}.md)
@@ -98,7 +98,7 @@ function registerUpdate(program) {
98
98
  // Inject preamble
99
99
  (0, preamble_js_1.injectPreambleToAgent)(agentDir, slug);
100
100
  // Deploy symlinks (always — handles migration from legacy deployed_files)
101
- const deploy = (0, installer_js_1.deploySymlinks)(agentDir, currentScope, projectPath);
101
+ const deploy = await (0, installer_js_1.deploySymlinks)(agentDir, currentScope, projectPath);
102
102
  // Update installed.json
103
103
  const installRecord = {
104
104
  agent_id: agent.id,
@@ -12,18 +12,6 @@ export declare const AI_TOOLS: AITool[];
12
12
  * 프로젝트 디렉토리에서 에이전트 CLI 디렉토리를 감지한다.
13
13
  */
14
14
  export declare function detectAgentCLIs(projectPath: string): AITool[];
15
- /**
16
- * Cowork/sandbox 환경의 마운트 경로 후보를 반환한다.
17
- * /sessions/<id>/mnt/ 같은 경로에 실제 파일이 마운트됨.
18
- */
19
- export declare function detectMountPaths(): string[];
20
- /**
21
- * 마운트 경로에서 에이전트 CLI 디렉토리를 감지한다.
22
- */
23
- export declare function detectMountedCLIs(): {
24
- tool: AITool;
25
- basePath: string;
26
- }[];
27
15
  /**
28
16
  * 홈 디렉토리에서 글로벌 에이전트 CLI 디렉토리를 감지한다.
29
17
  * ~/{skillsDir}/ 가 존재하는 CLI를 반환.
@@ -44,7 +32,3 @@ export declare function scanLocalItems(projectPath: string, tool: AITool): Conte
44
32
  * 글로벌 홈 디렉토리 소스의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
45
33
  */
46
34
  export declare function scanGlobalItems(tool: AITool, home?: string): ContentItem[];
47
- /**
48
- * 마운트 경로의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
49
- */
50
- export declare function scanMountedItems(basePath: string, tool: AITool): ContentItem[];
@@ -5,12 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AI_TOOLS = void 0;
7
7
  exports.detectAgentCLIs = detectAgentCLIs;
8
- exports.detectMountPaths = detectMountPaths;
9
- exports.detectMountedCLIs = detectMountedCLIs;
10
8
  exports.detectGlobalCLIs = detectGlobalCLIs;
11
9
  exports.scanLocalItems = scanLocalItems;
12
10
  exports.scanGlobalItems = scanGlobalItems;
13
- exports.scanMountedItems = scanMountedItems;
14
11
  const fs_1 = __importDefault(require("fs"));
15
12
  const os_1 = __importDefault(require("os"));
16
13
  const path_1 = __importDefault(require("path"));
@@ -52,31 +49,6 @@ exports.AI_TOOLS = [
52
49
  function detectAgentCLIs(projectPath) {
53
50
  return exports.AI_TOOLS.filter((tool) => fs_1.default.existsSync(path_1.default.join(projectPath, tool.skillsDir)));
54
51
  }
55
- /**
56
- * Cowork/sandbox 환경의 마운트 경로 후보를 반환한다.
57
- * /sessions/<id>/mnt/ 같은 경로에 실제 파일이 마운트됨.
58
- */
59
- function detectMountPaths() {
60
- const home = os_1.default.homedir();
61
- const mntPath = path_1.default.join(home, 'mnt');
62
- if (!fs_1.default.existsSync(mntPath))
63
- return [];
64
- return [mntPath];
65
- }
66
- /**
67
- * 마운트 경로에서 에이전트 CLI 디렉토리를 감지한다.
68
- */
69
- function detectMountedCLIs() {
70
- const results = [];
71
- for (const mnt of detectMountPaths()) {
72
- for (const tool of exports.AI_TOOLS) {
73
- if (fs_1.default.existsSync(path_1.default.join(mnt, tool.skillsDir))) {
74
- results.push({ tool, basePath: mnt });
75
- }
76
- }
77
- }
78
- return results;
79
- }
80
52
  /**
81
53
  * 홈 디렉토리에서 글로벌 에이전트 CLI 디렉토리를 감지한다.
82
54
  * ~/{skillsDir}/ 가 존재하는 CLI를 반환.
@@ -130,9 +102,3 @@ function scanGlobalItems(tool, home) {
130
102
  const basePath = path_1.default.join(home ?? os_1.default.homedir(), tool.skillsDir);
131
103
  return scanItemsIn(basePath);
132
104
  }
133
- /**
134
- * 마운트 경로의 개별 스킬/에이전트/커맨드/룰 항목을 반환한다.
135
- */
136
- function scanMountedItems(basePath, tool) {
137
- return scanItemsIn(path_1.default.join(basePath, tool.skillsDir));
138
- }
@@ -11,7 +11,7 @@ export interface DeployResult {
11
11
  * @param scope 'global' | 'local'
12
12
  * @param projectPath 프로젝트 루트 경로 (local scope 시 사용)
13
13
  */
14
- export declare function deploySymlinks(agentDir: string, scope: 'global' | 'local', projectPath: string): DeployResult;
14
+ export declare function deploySymlinks(agentDir: string, scope: 'global' | 'local', projectPath: string): Promise<DeployResult>;
15
15
  /**
16
16
  * symlink 목록을 기반으로 symlink를 제거한다.
17
17
  */
@@ -26,19 +26,34 @@ const SYMLINK_DIRS = ['skills', 'commands', 'agents', 'rules'];
26
26
  * @param scope 'global' | 'local'
27
27
  * @param projectPath 프로젝트 루트 경로 (local scope 시 사용)
28
28
  */
29
- function deploySymlinks(agentDir, scope, projectPath) {
29
+ async function deploySymlinks(agentDir, scope, projectPath) {
30
30
  const result = { symlinks: [], warnings: [] };
31
31
  // 감지된 AI tool 목록
32
32
  const tools = scope === 'global'
33
33
  ? (0, ai_tools_js_1.detectGlobalCLIs)()
34
34
  : (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
35
- // Claude Code를 기본으로 포함 (글로벌에 .claude/가 없어도 생성)
35
+ // 글로벌: Claude Code를 기본으로 포함
36
36
  if (scope === 'global') {
37
37
  const hasClaudeCode = tools.some((t) => t.value === 'claude');
38
38
  if (!hasClaudeCode) {
39
39
  tools.push({ name: 'Claude Code', value: 'claude', skillsDir: '.claude' });
40
40
  }
41
41
  }
42
+ // 로컬: AI tool 디렉토리가 없으면 TTY에서 선택
43
+ if (scope === 'local' && tools.length === 0) {
44
+ if (process.stdout.isTTY) {
45
+ const { checkbox } = await import('@inquirer/prompts');
46
+ const selected = await checkbox({
47
+ message: `Select tools to set up (${ai_tools_js_1.AI_TOOLS.length} available)`,
48
+ choices: ai_tools_js_1.AI_TOOLS.map((t) => ({ name: t.name, value: t })),
49
+ });
50
+ tools.push(...selected);
51
+ }
52
+ else {
53
+ // Non-TTY (JSON 모드 등): Claude Code 기본
54
+ tools.push({ name: 'Claude Code', value: 'claude', skillsDir: '.claude' });
55
+ }
56
+ }
42
57
  for (const tool of tools) {
43
58
  const baseDir = scope === 'global'
44
59
  ? path_1.default.join(os_1.default.homedir(), tool.skillsDir)
@@ -75,7 +90,8 @@ function deploySymlinks(agentDir, scope, projectPath) {
75
90
  continue;
76
91
  }
77
92
  }
78
- fs_1.default.symlinkSync(srcPath, destPath);
93
+ const relativeSrc = path_1.default.relative(path_1.default.dirname(destPath), srcPath);
94
+ fs_1.default.symlinkSync(relativeSrc, destPath);
79
95
  result.symlinks.push(destPath);
80
96
  }
81
97
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * bin/relay-preamble.sh 스크립트를 생성한다.
3
- * relay CLI가 있으면 사용, 없으면 curl fallback.
3
+ * bash 실행 가능 환경을 전제로, relay CLI를 직접 호출한다.
4
4
  */
5
- export declare function generatePreambleScript(slug: string, apiUrl: string): string;
5
+ export declare function generatePreambleScript(slug: string, _apiUrl: string): string;
6
6
  /**
7
7
  * SKILL.md / command에 삽입할 preamble 마크다운.
8
8
  * agentDir: 설치된 에이전트의 절대 경로 (install 시점에 결정)
@@ -14,54 +14,16 @@ const PREAMBLE_START = '<!-- RELAY_PREAMBLE_START - DO NOT EDIT -->';
14
14
  const PREAMBLE_END = '<!-- RELAY_PREAMBLE_END -->';
15
15
  /**
16
16
  * bin/relay-preamble.sh 스크립트를 생성한다.
17
- * relay CLI가 있으면 사용, 없으면 curl fallback.
17
+ * bash 실행 가능 환경을 전제로, relay CLI를 직접 호출한다.
18
18
  */
19
- function generatePreambleScript(slug, apiUrl) {
20
- // slug format: @owner/agent → extract agent slug for ping
21
- const stripped = slug.startsWith('@') ? slug.slice(1) : slug;
22
- const slashIdx = stripped.indexOf('/');
23
- const agentSlug = slashIdx !== -1 ? stripped.slice(slashIdx + 1) : stripped;
19
+ function generatePreambleScript(slug, _apiUrl) {
24
20
  return `#!/usr/bin/env bash
25
21
  # relay-preamble.sh — auto-generated by relay publish
26
22
  set +e
27
23
 
28
- # Device hash (shasum → sha256sum → openssl fallback)
29
- _RAW="$(hostname):$(whoami)"
30
- if command -v shasum &>/dev/null; then
31
- DEVICE_HASH=$(printf '%s' "$_RAW" | shasum -a 256 | cut -d' ' -f1)
32
- elif command -v sha256sum &>/dev/null; then
33
- DEVICE_HASH=$(printf '%s' "$_RAW" | sha256sum | cut -d' ' -f1)
34
- elif command -v openssl &>/dev/null; then
35
- DEVICE_HASH=$(printf '%s' "$_RAW" | openssl dgst -sha256 | awk '{print $NF}')
36
- else
37
- DEVICE_HASH="unknown"
38
- fi
39
-
40
- # Read relay token (for user identification in usage ping)
41
- _RELAY_TOKEN=""
42
- if [ -f "$HOME/.relay/token.json" ]; then
43
- _RELAY_TOKEN=$(grep -o '"access_token":"[^"]*"' "$HOME/.relay/token.json" 2>/dev/null | head -1 | cut -d'"' -f4)
44
- fi
45
-
46
- # CLI version (for usage tracking)
47
- _CLI_VERSION=""
48
- if command -v relay &>/dev/null; then
49
- _CLI_VERSION=$(relay --version 2>/dev/null | head -1 | grep -o '[0-9][0-9.]*' || true)
50
- fi
51
-
52
- # Usage ping (with user identity if logged in)
24
+ # Usage ping (background, non-blocking)
53
25
  if command -v relay &>/dev/null; then
54
26
  relay ping "${slug}" --quiet 2>/dev/null &
55
- elif command -v curl &>/dev/null; then
56
- _AUTH_HEADER=""
57
- [ -n "$_RELAY_TOKEN" ] && _AUTH_HEADER="-H \\"Authorization: Bearer $_RELAY_TOKEN\\""
58
- _CLI_VER_FIELD=""
59
- [ -n "$_CLI_VERSION" ] && _CLI_VER_FIELD=",\\"cli_version\\":\\"$_CLI_VERSION\\""
60
- eval curl -sf --max-time 5 -X POST "${apiUrl}/api/agents/${agentSlug}/ping" \\
61
- -H "Content-Type: application/json" \\
62
- $_AUTH_HEADER \\
63
- -d "{\\"device_hash\\":\\"$DEVICE_HASH\\",\\"slug\\":\\"${slug}\\"$_CLI_VER_FIELD}" \\
64
- 2>/dev/null &
65
27
  fi
66
28
 
67
29
  # Update check (already 24h cached by relay CLI)
@@ -110,8 +110,17 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
110
110
  - name, slug, description, version, tags
111
111
  - requires (판단 결과)
112
112
  - org, visibility
113
+ - **recommended_scope** — 설치 시 기본 배치 범위:
114
+ - `local` — rules/ 디렉토리가 있거나 프레임워크 특화 태그(nextjs, react, vue, angular, svelte, nuxt, remix, astro, django, rails, laravel, spring, express, fastapi, flask)가 있을 때
115
+ - `global` — 그 외 범용 도구
113
116
 
114
- **사용자에게 질문하여 최종 확인** 후 `relay publish --json`으로 배포합니다.
117
+ **사용자에게 질문하여 최종 확인** 후 배포합니다.
118
+
119
+ 배포 명령어는 사용자의 선택에 따라 다릅니다:
120
+ - **개인 배포**: `relay publish --no-org --json`
121
+ - **Org 배포**: `relay publish --org {org_slug} --json`
122
+
123
+ ⚠️ `relay publish --json`만 실행하면 org 선택 에러가 발생합니다. 반드시 `--no-org` 또는 `--org`를 명시하세요.
115
124
 
116
125
  ---
117
126
 
@@ -140,7 +149,10 @@ relay.yaml이 없으면 새로 만들고, 있으면 변경사항을 반영합니
140
149
  ### 3. 배포
141
150
 
142
151
  변경 요약을 보여주고 **사용자에게 질문하여 최종 확인** 후 배포합니다.
143
- `relay publish --json`으로 배포합니다.
152
+
153
+ 배포 명령어는 사용자의 선택(또는 기존 relay.yaml 설정)에 따라:
154
+ - **개인 배포**: `relay publish --no-org --json`
155
+ - **Org 배포**: `relay publish --org {org_slug} --json`
144
156
  버전 범프가 필요하면 사용자에게 질문하여 patch/minor/major 중 확인합니다.
145
157
 
146
158
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.4.26",
3
+ "version": "0.4.28",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {