relayax-cli 0.4.27 → 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`);
@@ -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[];
@@ -45,6 +45,7 @@ function parseRelayYaml(content) {
45
45
  requires,
46
46
  visibility,
47
47
  type,
48
+ recommended_scope: raw.recommended_scope === 'global' ? 'global' : raw.recommended_scope === 'local' ? 'local' : undefined,
48
49
  source: raw.source ? String(raw.source) : undefined,
49
50
  org_slug: raw.org_slug ? String(raw.org_slug) : undefined,
50
51
  };
@@ -268,6 +269,7 @@ function registerPublish(program) {
268
269
  .option('--token <token>', '인증 토큰')
269
270
  .option('--space <slug>', '배포할 Space 지정')
270
271
  .option('--org <slug>', 'Organization slug 지정')
272
+ .option('--no-org', '개인 계정으로 배포 (Organization 무시)')
271
273
  .option('--version <version>', '배포 버전 지정 (relay.yaml 업데이트)')
272
274
  .option('--patch', 'patch 버전 범프')
273
275
  .option('--minor', 'minor 버전 범프')
@@ -470,10 +472,18 @@ function registerPublish(program) {
470
472
  try {
471
473
  const { fetchMyOrgs } = await import('./orgs.js');
472
474
  const orgs = await fetchMyOrgs(token);
475
+ // --no-org: skip org selection entirely (personal deployment)
476
+ const skipOrg = opts.noOrg === true;
473
477
  // Determine explicit org slug: --org > --space (legacy) > relay.yaml org_slug
474
- const explicitOrgSlug = opts.org ?? opts.space ?? config.org_slug;
478
+ const explicitOrgSlug = skipOrg ? undefined : (opts.org ?? opts.space ?? config.org_slug);
475
479
  // --org / --space / relay.yaml org_slug: resolve Org by slug
476
- 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) {
477
487
  const matched = orgs.find((o) => o.slug === explicitOrgSlug);
478
488
  if (matched) {
479
489
  selectedOrgId = matched.id;
@@ -528,18 +538,22 @@ function registerPublish(program) {
528
538
  console.error(` → Organization: ${chosenLabel}\n`);
529
539
  }
530
540
  }
531
- else if (orgs.length > 1 && json) {
532
- // --json 모드 + 여러 Org: 에이전트가 선택할 수 있도록 에러 반환
533
- (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`);
534
544
  console.error(JSON.stringify({
535
545
  error: 'MISSING_ORG',
536
- message: '배포할 Organization을 선택하세요.',
537
- fix: `relay publish --org <slug> --json`,
538
- 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
+ ],
539
552
  }));
540
553
  process.exit(1);
541
554
  }
542
555
  else if (orgs.length > 0) {
556
+ // non-json, non-TTY fallback (rare) — auto-select first org
543
557
  selectedOrgId = orgs[0].id;
544
558
  selectedOrgSlug = orgs[0].slug;
545
559
  }
@@ -691,6 +705,7 @@ function registerPublish(program) {
691
705
  agent_names: listDir(relayDir, 'agents'),
692
706
  skill_names: listDir(relayDir, 'skills'),
693
707
  type: config.type ?? 'hybrid',
708
+ recommended_scope: config.recommended_scope,
694
709
  agent_details: detectedAgents,
695
710
  skill_details: detectedSkills,
696
711
  ...(selectedOrgId ? { org_id: selectedOrgId } : {}),
@@ -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,
@@ -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
  }
@@ -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.27",
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": {