anpm-io 1.0.0

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.
Files changed (139) hide show
  1. package/README.md +174 -0
  2. package/dist/commands/access.d.ts +2 -0
  3. package/dist/commands/access.js +90 -0
  4. package/dist/commands/adopt.d.ts +2 -0
  5. package/dist/commands/adopt.js +177 -0
  6. package/dist/commands/changelog.d.ts +2 -0
  7. package/dist/commands/changelog.js +67 -0
  8. package/dist/commands/check-update.d.ts +2 -0
  9. package/dist/commands/check-update.js +76 -0
  10. package/dist/commands/config.d.ts +2 -0
  11. package/dist/commands/config.js +84 -0
  12. package/dist/commands/create.d.ts +2 -0
  13. package/dist/commands/create.js +227 -0
  14. package/dist/commands/deploy-record.d.ts +2 -0
  15. package/dist/commands/deploy-record.js +93 -0
  16. package/dist/commands/deploy.d.ts +2 -0
  17. package/dist/commands/deploy.js +284 -0
  18. package/dist/commands/diff.d.ts +2 -0
  19. package/dist/commands/diff.js +92 -0
  20. package/dist/commands/feedback.d.ts +2 -0
  21. package/dist/commands/feedback.js +71 -0
  22. package/dist/commands/grant.d.ts +33 -0
  23. package/dist/commands/grant.js +190 -0
  24. package/dist/commands/hub.d.ts +2 -0
  25. package/dist/commands/hub.js +171 -0
  26. package/dist/commands/init.d.ts +13 -0
  27. package/dist/commands/init.js +172 -0
  28. package/dist/commands/install.d.ts +2 -0
  29. package/dist/commands/install.js +626 -0
  30. package/dist/commands/join.d.ts +6 -0
  31. package/dist/commands/join.js +90 -0
  32. package/dist/commands/link.d.ts +2 -0
  33. package/dist/commands/link.js +112 -0
  34. package/dist/commands/list.d.ts +2 -0
  35. package/dist/commands/list.js +144 -0
  36. package/dist/commands/login.d.ts +7 -0
  37. package/dist/commands/login.js +235 -0
  38. package/dist/commands/orgs.d.ts +10 -0
  39. package/dist/commands/orgs.js +128 -0
  40. package/dist/commands/outdated.d.ts +2 -0
  41. package/dist/commands/outdated.js +70 -0
  42. package/dist/commands/package.d.ts +57 -0
  43. package/dist/commands/package.js +569 -0
  44. package/dist/commands/ping.d.ts +2 -0
  45. package/dist/commands/ping.js +40 -0
  46. package/dist/commands/publish.d.ts +98 -0
  47. package/dist/commands/publish.js +899 -0
  48. package/dist/commands/run.d.ts +2 -0
  49. package/dist/commands/run.js +249 -0
  50. package/dist/commands/search.d.ts +2 -0
  51. package/dist/commands/search.js +57 -0
  52. package/dist/commands/status.d.ts +2 -0
  53. package/dist/commands/status.js +159 -0
  54. package/dist/commands/uninstall.d.ts +2 -0
  55. package/dist/commands/uninstall.js +132 -0
  56. package/dist/commands/update.d.ts +2 -0
  57. package/dist/commands/update.js +171 -0
  58. package/dist/commands/versions.d.ts +2 -0
  59. package/dist/commands/versions.js +44 -0
  60. package/dist/index.d.ts +2 -0
  61. package/dist/index.js +91 -0
  62. package/dist/lib/agent-status.d.ts +23 -0
  63. package/dist/lib/agent-status.js +127 -0
  64. package/dist/lib/ai-tools.d.ts +34 -0
  65. package/dist/lib/ai-tools.js +104 -0
  66. package/dist/lib/anpm-config.d.ts +39 -0
  67. package/dist/lib/anpm-config.js +112 -0
  68. package/dist/lib/api.d.ts +24 -0
  69. package/dist/lib/api.js +151 -0
  70. package/dist/lib/auto-detect.d.ts +30 -0
  71. package/dist/lib/auto-detect.js +112 -0
  72. package/dist/lib/cloud-providers/anthropic.d.ts +19 -0
  73. package/dist/lib/cloud-providers/anthropic.js +200 -0
  74. package/dist/lib/cloud-providers/package-mapper.d.ts +9 -0
  75. package/dist/lib/cloud-providers/package-mapper.js +34 -0
  76. package/dist/lib/cloud-providers/provider.d.ts +60 -0
  77. package/dist/lib/cloud-providers/provider.js +7 -0
  78. package/dist/lib/command-adapter.d.ts +41 -0
  79. package/dist/lib/command-adapter.js +188 -0
  80. package/dist/lib/config.d.ts +50 -0
  81. package/dist/lib/config.js +274 -0
  82. package/dist/lib/contact-format.d.ts +7 -0
  83. package/dist/lib/contact-format.js +23 -0
  84. package/dist/lib/device-hash.d.ts +1 -0
  85. package/dist/lib/device-hash.js +16 -0
  86. package/dist/lib/error-report.d.ts +5 -0
  87. package/dist/lib/error-report.js +28 -0
  88. package/dist/lib/git-installer.d.ts +16 -0
  89. package/dist/lib/git-installer.js +97 -0
  90. package/dist/lib/git-operations.d.ts +38 -0
  91. package/dist/lib/git-operations.js +183 -0
  92. package/dist/lib/hub-notify.d.ts +9 -0
  93. package/dist/lib/hub-notify.js +66 -0
  94. package/dist/lib/install-source.d.ts +33 -0
  95. package/dist/lib/install-source.js +98 -0
  96. package/dist/lib/installer.d.ts +40 -0
  97. package/dist/lib/installer.js +358 -0
  98. package/dist/lib/local-installer.d.ts +15 -0
  99. package/dist/lib/local-installer.js +73 -0
  100. package/dist/lib/lockfile.d.ts +13 -0
  101. package/dist/lib/lockfile.js +42 -0
  102. package/dist/lib/manifest.d.ts +65 -0
  103. package/dist/lib/manifest.js +113 -0
  104. package/dist/lib/migration.d.ts +10 -0
  105. package/dist/lib/migration.js +91 -0
  106. package/dist/lib/paths.d.ts +10 -0
  107. package/dist/lib/paths.js +22 -0
  108. package/dist/lib/preamble.d.ts +22 -0
  109. package/dist/lib/preamble.js +133 -0
  110. package/dist/lib/relay-config.d.ts +13 -0
  111. package/dist/lib/relay-config.js +46 -0
  112. package/dist/lib/requires-suggest.d.ts +23 -0
  113. package/dist/lib/requires-suggest.js +295 -0
  114. package/dist/lib/setup-command.d.ts +6 -0
  115. package/dist/lib/setup-command.js +72 -0
  116. package/dist/lib/slug.d.ts +24 -0
  117. package/dist/lib/slug.js +100 -0
  118. package/dist/lib/step-tracker.d.ts +8 -0
  119. package/dist/lib/step-tracker.js +28 -0
  120. package/dist/lib/storage.d.ts +6 -0
  121. package/dist/lib/storage.js +23 -0
  122. package/dist/lib/update-cache.d.ts +2 -0
  123. package/dist/lib/update-cache.js +51 -0
  124. package/dist/lib/version-check.d.ts +10 -0
  125. package/dist/lib/version-check.js +75 -0
  126. package/dist/mcp/server.d.ts +3 -0
  127. package/dist/mcp/server.js +112 -0
  128. package/dist/postinstall.d.ts +8 -0
  129. package/dist/postinstall.js +41 -0
  130. package/dist/prompts/_error-handling.md +38 -0
  131. package/dist/prompts/create.md +170 -0
  132. package/dist/prompts/explore.md +30 -0
  133. package/dist/prompts/index.d.ts +3 -0
  134. package/dist/prompts/index.js +22 -0
  135. package/dist/relay-compat.d.ts +2 -0
  136. package/dist/relay-compat.js +7 -0
  137. package/dist/types.d.ts +118 -0
  138. package/dist/types.js +2 -0
  139. package/package.json +51 -0
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.joinOrg = joinOrg;
4
+ exports.registerJoin = registerJoin;
5
+ const config_js_1 = require("../lib/config.js");
6
+ const step_tracker_js_1 = require("../lib/step-tracker.js");
7
+ const init_js_1 = require("./init.js");
8
+ async function joinOrg(orgSlug, code) {
9
+ const token = await (0, config_js_1.getValidToken)();
10
+ if (!token) {
11
+ throw new Error('LOGIN_REQUIRED');
12
+ }
13
+ // Use the access code via API
14
+ const res = await fetch(`${config_js_1.API_URL}/api/access-codes/${code}/use`, {
15
+ method: 'POST',
16
+ headers: {
17
+ 'Content-Type': 'application/json',
18
+ Authorization: `Bearer ${token}`,
19
+ },
20
+ });
21
+ if (!res.ok) {
22
+ const body = await res.json().catch(() => ({}));
23
+ const errCode = body.error ?? String(res.status);
24
+ switch (errCode) {
25
+ case 'INVALID_LINK':
26
+ throw new Error('초대 코드가 유효하지 않거나 만료되었습니다.');
27
+ default:
28
+ throw new Error(body.message ?? `가입 실패 (${res.status})`);
29
+ }
30
+ }
31
+ return res.json();
32
+ }
33
+ function registerJoin(program) {
34
+ program
35
+ .command('join <slug>')
36
+ .description('Organization에 초대 코드로 가입합니다')
37
+ .requiredOption('--code <code>', '초대 코드 (UUID)')
38
+ .action(async (slug, opts) => {
39
+ const json = program.opts().json ?? false;
40
+ (0, step_tracker_js_1.trackCommand)('join', { slug });
41
+ if (!(0, init_js_1.hasGlobalUserCommands)()) {
42
+ if (!json) {
43
+ console.error('\x1b[33m⚠ anpm init이 실행되지 않았습니다. 먼저 anpm init을 실행하세요.\x1b[0m');
44
+ }
45
+ else {
46
+ console.error(JSON.stringify({ error: 'NOT_INITIALIZED', message: 'anpm init을 먼저 실행하세요.', fix: 'anpm init 실행하세요.' }));
47
+ }
48
+ process.exit(1);
49
+ }
50
+ try {
51
+ const result = await joinOrg(slug, opts.code);
52
+ if (json) {
53
+ console.log(JSON.stringify({ status: 'ok', ...result }));
54
+ }
55
+ else {
56
+ if (result.type === 'org') {
57
+ console.log(`\x1b[32m✅ @${slug} Organization에 가입했습니다 (역할: ${result.role ?? 'member'})\x1b[0m`);
58
+ console.log(`\n\x1b[33m 대시보드: www.relayax.com/orgs/${slug}\x1b[0m`);
59
+ }
60
+ else {
61
+ console.log(`\x1b[32m✅ 에이전트 접근 권한이 부여되었습니다\x1b[0m`);
62
+ }
63
+ }
64
+ }
65
+ catch (err) {
66
+ const message = err instanceof Error ? err.message : String(err);
67
+ if (message === 'LOGIN_REQUIRED') {
68
+ if (json) {
69
+ console.error(JSON.stringify({
70
+ error: 'LOGIN_REQUIRED',
71
+ message: '로그인이 필요합니다. anpm login 을 먼저 실행하세요.',
72
+ fix: 'anpm login 실행 후 재시도하세요.',
73
+ }));
74
+ }
75
+ else {
76
+ console.error('\x1b[31m오류: 로그인이 필요합니다.\x1b[0m');
77
+ console.error(' anpm login 을 먼저 실행하세요.');
78
+ }
79
+ process.exit(1);
80
+ }
81
+ if (json) {
82
+ console.error(JSON.stringify({ error: 'JOIN_FAILED', message, fix: 'slug와 초대 코드를 확인 후 재시도하세요.' }));
83
+ }
84
+ else {
85
+ console.error(`\x1b[31m오류: ${message}\x1b[0m`);
86
+ }
87
+ process.exit(1);
88
+ }
89
+ });
90
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerLink(program: Command): void;
@@ -0,0 +1,112 @@
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.registerLink = registerLink;
7
+ const path_1 = __importDefault(require("path"));
8
+ const config_js_1 = require("../lib/config.js");
9
+ const paths_js_1 = require("../lib/paths.js");
10
+ const installer_js_1 = require("../lib/installer.js");
11
+ const auto_detect_js_1 = require("../lib/auto-detect.js");
12
+ function registerLink(program) {
13
+ program
14
+ .command('link [path]')
15
+ .description('Link a local agent directory to harnesses for development')
16
+ .option('--global', 'Link globally (home directory)')
17
+ .option('--project <dir>', 'Project root path')
18
+ .action(async (inputPath, opts) => {
19
+ const json = program.opts().json ?? false;
20
+ const projectPath = (0, paths_js_1.resolveProjectPath)(opts.project);
21
+ const sourcePath = inputPath ? path_1.default.resolve(inputPath) : process.cwd();
22
+ const scope = opts.global ? 'global' : 'local';
23
+ try {
24
+ const detected = (0, auto_detect_js_1.detectAgentStructure)(sourcePath);
25
+ if (!(0, auto_detect_js_1.hasDetectedContent)(detected)) {
26
+ const msg = `No agent structure detected in ${sourcePath}`;
27
+ if (json)
28
+ console.error(JSON.stringify({ error: 'NO_STRUCTURE', message: msg }));
29
+ else
30
+ console.error(`\x1b[31m✖ ${msg}\x1b[0m`);
31
+ process.exit(1);
32
+ }
33
+ const name = path_1.default.basename(sourcePath);
34
+ // Link directly — deploySymlinks creates symlinks pointing to sourcePath
35
+ const deploy = await (0, installer_js_1.deploySymlinks)(sourcePath, scope, projectPath);
36
+ const slug = `link/${name}`;
37
+ const installRecord = {
38
+ version: '0.0.0',
39
+ installed_at: new Date().toISOString(),
40
+ files: [sourcePath],
41
+ deploy_scope: scope,
42
+ deployed_symlinks: deploy.symlinks,
43
+ source: `link:${sourcePath}`,
44
+ };
45
+ if (scope === 'global') {
46
+ const installed = (0, config_js_1.loadGlobalInstalled)();
47
+ installed[slug] = installRecord;
48
+ (0, config_js_1.saveGlobalInstalled)(installed);
49
+ }
50
+ else {
51
+ const installed = (0, config_js_1.loadInstalled)();
52
+ installed[slug] = installRecord;
53
+ (0, config_js_1.saveInstalled)(installed);
54
+ }
55
+ if (json) {
56
+ console.log(JSON.stringify({ status: 'ok', slug, source: sourcePath, symlinks: deploy.symlinks }));
57
+ }
58
+ else {
59
+ console.log(`\n\x1b[32m✓ Linked ${name}\x1b[0m`);
60
+ console.log(` source: \x1b[36m${sourcePath}\x1b[0m`);
61
+ console.log(` symlinks: ${deploy.symlinks.length}`);
62
+ if (detected.method !== 'relay-yaml') {
63
+ console.error(` detected (${detected.method}):`);
64
+ console.error((0, auto_detect_js_1.formatDetectedStructure)(detected));
65
+ }
66
+ console.log(`\n\x1b[33mChanges to source files will be reflected immediately.\x1b[0m`);
67
+ }
68
+ }
69
+ catch (err) {
70
+ const message = err instanceof Error ? err.message : String(err);
71
+ if (json)
72
+ console.error(JSON.stringify({ error: 'LINK_FAILED', message }));
73
+ else
74
+ console.error(`\x1b[31m✖ ${message}\x1b[0m`);
75
+ process.exit(1);
76
+ }
77
+ });
78
+ program
79
+ .command('unlink <name>')
80
+ .description('Remove a linked agent from harnesses')
81
+ .action(async (name) => {
82
+ const json = program.opts().json ?? false;
83
+ const slug = name.startsWith('link/') ? name : `link/${name}`;
84
+ // Check both global and local
85
+ const globalInstalled = (0, config_js_1.loadGlobalInstalled)();
86
+ const localInstalled = (0, config_js_1.loadInstalled)();
87
+ const entry = globalInstalled[slug] ?? localInstalled[slug];
88
+ if (!entry) {
89
+ const msg = `No linked agent found: ${name}`;
90
+ if (json)
91
+ console.error(JSON.stringify({ error: 'NOT_FOUND', message: msg }));
92
+ else
93
+ console.error(`\x1b[31m✖ ${msg}\x1b[0m`);
94
+ process.exit(1);
95
+ }
96
+ const removed = (0, installer_js_1.removeSymlinks)(entry.deployed_symlinks ?? []);
97
+ if (globalInstalled[slug]) {
98
+ delete globalInstalled[slug];
99
+ (0, config_js_1.saveGlobalInstalled)(globalInstalled);
100
+ }
101
+ if (localInstalled[slug]) {
102
+ delete localInstalled[slug];
103
+ (0, config_js_1.saveInstalled)(localInstalled);
104
+ }
105
+ if (json) {
106
+ console.log(JSON.stringify({ status: 'ok', slug, removed: removed.length }));
107
+ }
108
+ else {
109
+ console.log(`\x1b[32m✓ Unlinked ${name}\x1b[0m (${removed.length} symlinks removed)`);
110
+ }
111
+ });
112
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerList(program: Command): void;
@@ -0,0 +1,144 @@
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.registerList = registerList;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const config_js_1 = require("../lib/config.js");
9
+ const ai_tools_js_1 = require("../lib/ai-tools.js");
10
+ async function fetchOrgAgentList(orgSlug, token) {
11
+ const res = await fetch(`${config_js_1.API_URL}/api/orgs/${orgSlug}/agents`, {
12
+ headers: { Authorization: `Bearer ${token}` },
13
+ signal: AbortSignal.timeout(8000),
14
+ });
15
+ if (!res.ok) {
16
+ const body = await res.text();
17
+ throw new Error(`Org 에이전트 목록 조회 실패 (${res.status}): ${body}`);
18
+ }
19
+ return (await res.json());
20
+ }
21
+ function registerList(program) {
22
+ program
23
+ .command('list')
24
+ .description('List installed agents')
25
+ .option('--org <slug>', 'List organization agents')
26
+ .option('--detail', 'Show file-level symlink mapping per agent')
27
+ .action(async (opts) => {
28
+ const json = program.opts().json ?? false;
29
+ // --org 옵션: Org 에이전트 목록
30
+ if (opts.org) {
31
+ const orgSlug = opts.org;
32
+ const token = await (0, config_js_1.getValidToken)();
33
+ if (!token) {
34
+ if (json) {
35
+ console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', message: '로그인이 필요합니다. anpm login을 먼저 실행하세요.', fix: 'anpm login 실행 후 재시도하세요.' }));
36
+ }
37
+ else {
38
+ console.error('\x1b[31m오류: 로그인이 필요합니다.\x1b[0m');
39
+ console.error(' anpm login을 먼저 실행하세요.');
40
+ }
41
+ process.exit(1);
42
+ }
43
+ try {
44
+ const agents = await fetchOrgAgentList(orgSlug, token);
45
+ if (json) {
46
+ console.log(JSON.stringify({ org: orgSlug, agents }));
47
+ return;
48
+ }
49
+ if (agents.length === 0) {
50
+ console.log(`\n@${orgSlug} Organization에 에이전트가 없습니다.`);
51
+ return;
52
+ }
53
+ console.log(`\n\x1b[1m@${orgSlug} 에이전트 목록\x1b[0m (${agents.length}개):\n`);
54
+ for (const t of agents) {
55
+ const desc = t.description
56
+ ? ` \x1b[90m${t.description.length > 50 ? t.description.slice(0, 50) + '...' : t.description}\x1b[0m`
57
+ : '';
58
+ console.log(` \x1b[36m@${t.owner}/${t.slug}\x1b[0m \x1b[1m${t.name}\x1b[0m${desc}`);
59
+ }
60
+ console.log(`\n\x1b[33m 설치: anpm install @${orgSlug}/<에이전트슬러그>\x1b[0m`);
61
+ }
62
+ catch (err) {
63
+ const message = err instanceof Error ? err.message : String(err);
64
+ if (json) {
65
+ console.error(JSON.stringify({ error: 'FETCH_FAILED', message, fix: '네트워크 연결을 확인하거나 잠시 후 재시도하세요.' }));
66
+ }
67
+ else {
68
+ console.error(`\x1b[31m오류: ${message}\x1b[0m`);
69
+ }
70
+ process.exit(1);
71
+ }
72
+ return;
73
+ }
74
+ // 기본 동작: 글로벌 + 로컬 통합 목록
75
+ const { global: globalInstalled, local: localInstalled } = (0, config_js_1.loadMergedInstalled)();
76
+ const allEntries = [];
77
+ const seen = new Set();
78
+ // 글로벌 먼저
79
+ for (const [slug, info] of Object.entries(globalInstalled)) {
80
+ allEntries.push({
81
+ slug,
82
+ version: info.version,
83
+ installed_at: info.installed_at,
84
+ scope: 'global',
85
+ deploy_scope: info.deploy_scope,
86
+ org_slug: info.org_slug,
87
+ source: info.source,
88
+ symlinks: info.deployed_symlinks,
89
+ });
90
+ seen.add(slug);
91
+ }
92
+ // Local (not already in global)
93
+ for (const [slug, info] of Object.entries(localInstalled)) {
94
+ if (seen.has(slug))
95
+ continue;
96
+ allEntries.push({
97
+ slug,
98
+ version: info.version,
99
+ installed_at: info.installed_at,
100
+ scope: 'local',
101
+ deploy_scope: info.deploy_scope,
102
+ org_slug: info.org_slug,
103
+ source: info.source,
104
+ symlinks: info.deployed_symlinks,
105
+ });
106
+ }
107
+ if (json) {
108
+ console.log(JSON.stringify({ installed: allEntries }));
109
+ }
110
+ else {
111
+ if (allEntries.length === 0) {
112
+ console.log('\nNo agents installed. Run `anpm install <slug>` to install one.');
113
+ return;
114
+ }
115
+ console.log(`\nInstalled agents (${allEntries.length}):\n`);
116
+ for (const item of allEntries) {
117
+ const date = new Date(item.installed_at).toLocaleDateString('en-US');
118
+ const scopeLabel = item.deploy_scope === 'global'
119
+ ? '\x1b[32mglobal\x1b[0m'
120
+ : item.deploy_scope === 'local'
121
+ ? '\x1b[33mlocal\x1b[0m'
122
+ : '\x1b[90m—\x1b[0m';
123
+ const sourceLabel = item.source
124
+ ? ` \x1b[90m${item.source.split(':')[0]}\x1b[0m`
125
+ : '';
126
+ const orgLabel = item.org_slug ? ` \x1b[90m[Org: ${item.org_slug}]\x1b[0m` : '';
127
+ console.log(` \x1b[36m${item.slug}\x1b[0m v${item.version} ${scopeLabel}${sourceLabel} (${date})${orgLabel}`);
128
+ // --detail: show per-file symlink mapping
129
+ if (opts.detail && item.symlinks && item.symlinks.length > 0) {
130
+ for (const link of item.symlinks) {
131
+ const exists = fs_1.default.existsSync(link);
132
+ const icon = exists ? '✅' : '❌';
133
+ // Extract harness + content type from path
134
+ const harnessName = ai_tools_js_1.AI_TOOLS.find((t) => link.includes(t.skillsDir))?.name ?? '?';
135
+ const parts = link.split('/');
136
+ const contentIdx = parts.findIndex((p) => ['skills', 'commands', 'rules', 'agents'].includes(p));
137
+ const contentLabel = contentIdx >= 0 ? parts.slice(contentIdx).join('/') : link;
138
+ console.log(` ${icon} ${contentLabel} \x1b[90m→ ${harnessName}\x1b[0m`);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ });
144
+ }
@@ -0,0 +1,7 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * 대화형 로그인 플로우 실행 (auto-login에서 호출).
4
+ * 브라우저에서 로그인 페이지를 열고 토큰을 받아 저장.
5
+ */
6
+ export declare function runLogin(): Promise<void>;
7
+ export declare function registerLogin(program: Command): void;
@@ -0,0 +1,235 @@
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.runLogin = runLogin;
7
+ exports.registerLogin = registerLogin;
8
+ const http_1 = __importDefault(require("http"));
9
+ const child_process_1 = require("child_process");
10
+ const config_js_1 = require("../lib/config.js");
11
+ const error_report_js_1 = require("../lib/error-report.js");
12
+ const step_tracker_js_1 = require("../lib/step-tracker.js");
13
+ function openBrowser(url) {
14
+ const platform = process.platform;
15
+ try {
16
+ if (platform === 'darwin') {
17
+ (0, child_process_1.execSync)(`open "${url}"`, { stdio: 'ignore' });
18
+ }
19
+ else if (platform === 'win32') {
20
+ (0, child_process_1.execSync)(`start "" "${url}"`, { stdio: 'ignore' });
21
+ }
22
+ else {
23
+ (0, child_process_1.execSync)(`xdg-open "${url}"`, { stdio: 'ignore' });
24
+ }
25
+ return true;
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
31
+ async function verifyToken(token) {
32
+ try {
33
+ const res = await fetch(`${config_js_1.API_URL}/api/auth/me`, {
34
+ headers: { Authorization: `Bearer ${token}` },
35
+ });
36
+ if (!res.ok)
37
+ return null;
38
+ return (await res.json());
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ function collectBody(req) {
45
+ return new Promise((resolve) => {
46
+ const chunks = [];
47
+ req.on('data', (chunk) => chunks.push(chunk));
48
+ req.on('end', () => resolve(Buffer.concat(chunks).toString()));
49
+ });
50
+ }
51
+ const SUCCESS_HTML = `<!DOCTYPE html>
52
+ <html><head><title>RelayAX</title></head>
53
+ <body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#f6f5f2;color:#111318">
54
+ <div style="text-align:center">
55
+ <h2>로그인 완료!</h2>
56
+ <p>터미널로 돌아가세요. 이 창은 닫아도 됩니다.</p>
57
+ <script>setTimeout(()=>window.close(),2000)</script>
58
+ </div>
59
+ </body></html>`;
60
+ function waitForToken(port) {
61
+ return new Promise((resolve, reject) => {
62
+ const timeout = setTimeout(() => {
63
+ server.close();
64
+ reject(new Error('로그인 시간이 초과되었습니다 (5분)'));
65
+ }, 5 * 60 * 1000);
66
+ const server = http_1.default.createServer(async (req, res) => {
67
+ const url = new URL(req.url ?? '/', `http://localhost:${port}`);
68
+ if (url.pathname === '/callback' && req.method === 'POST') {
69
+ const body = await collectBody(req);
70
+ const params = new URLSearchParams(body);
71
+ const token = params.get('token');
72
+ const refresh_token = params.get('refresh_token') ?? undefined;
73
+ const expires_at_raw = params.get('expires_at');
74
+ const expires_at = expires_at_raw ? Number(expires_at_raw) : undefined;
75
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
76
+ res.end(SUCCESS_HTML);
77
+ clearTimeout(timeout);
78
+ server.close();
79
+ if (token) {
80
+ resolve({ token, refresh_token, expires_at });
81
+ }
82
+ else {
83
+ reject(new Error('토큰이 전달되지 않았습니다'));
84
+ }
85
+ }
86
+ else {
87
+ res.writeHead(404);
88
+ res.end('Not found');
89
+ }
90
+ });
91
+ server.listen(port, '127.0.0.1');
92
+ });
93
+ }
94
+ function findAvailablePort() {
95
+ return new Promise((resolve, reject) => {
96
+ const server = http_1.default.createServer();
97
+ server.listen(0, '127.0.0.1', () => {
98
+ const addr = server.address();
99
+ if (addr && typeof addr !== 'string') {
100
+ const port = addr.port;
101
+ server.close(() => resolve(port));
102
+ }
103
+ else {
104
+ server.close(() => reject(new Error('포트를 찾을 수 없습니다')));
105
+ }
106
+ });
107
+ });
108
+ }
109
+ async function loginWithBrowser(json) {
110
+ const port = await findAvailablePort();
111
+ const loginUrl = `${config_js_1.API_URL}/auth/cli-login?port=${port}`;
112
+ const opened = openBrowser(loginUrl);
113
+ if (!json) {
114
+ if (opened) {
115
+ console.error(`브라우저에서 로그인 페이지를 엽니다...`);
116
+ }
117
+ else {
118
+ console.error(`브라우저를 자동으로 열 수 없습니다. 아래 URL을 브라우저에서 직접 열어주세요:\n`);
119
+ console.error(` ${loginUrl}\n`);
120
+ }
121
+ }
122
+ return waitForToken(port);
123
+ }
124
+ async function loginWithDevice(json) {
125
+ const res = await fetch(`${config_js_1.API_URL}/api/auth/device/request`, { method: 'POST' });
126
+ if (!res.ok) {
127
+ throw new Error('Device code 발급에 실패했습니다');
128
+ }
129
+ const { device_code, user_code, verification_url, expires_in } = await res.json();
130
+ if (json) {
131
+ console.error(JSON.stringify({ status: 'waiting', verification_url, user_code, expires_in }));
132
+ }
133
+ else {
134
+ console.error(`\n아래 URL에서 코드를 입력하세요:\n`);
135
+ console.error(` ${verification_url}`);
136
+ console.error(`\n 코드: \x1b[1m${user_code}\x1b[0m\n`);
137
+ }
138
+ openBrowser(`${verification_url}?user_code=${user_code}`);
139
+ const deadline = Date.now() + expires_in * 1000;
140
+ while (Date.now() < deadline) {
141
+ await new Promise((r) => setTimeout(r, 5000));
142
+ const pollRes = await fetch(`${config_js_1.API_URL}/api/auth/device/poll`, {
143
+ method: 'POST',
144
+ headers: { 'Content-Type': 'application/json' },
145
+ body: JSON.stringify({ device_code }),
146
+ });
147
+ if (!pollRes.ok)
148
+ continue;
149
+ const data = await pollRes.json();
150
+ if (data.status === 'approved' && data.token) {
151
+ return {
152
+ token: data.token,
153
+ refresh_token: data.refresh_token,
154
+ expires_at: data.expires_at ? Number(data.expires_at) : undefined,
155
+ };
156
+ }
157
+ if (data.status === 'expired') {
158
+ throw new Error('코드가 만료되었습니다. 다시 시도하세요.');
159
+ }
160
+ // pending — continue polling
161
+ }
162
+ throw new Error('로그인 시간이 초과되었습니다 (5분)');
163
+ }
164
+ /**
165
+ * 대화형 로그인 플로우 실행 (auto-login에서 호출).
166
+ * 브라우저에서 로그인 페이지를 열고 토큰을 받아 저장.
167
+ */
168
+ async function runLogin() {
169
+ (0, config_js_1.ensureGlobalAnpmDir)();
170
+ const loginResult = await loginWithBrowser(false);
171
+ await verifyToken(loginResult.token);
172
+ (0, config_js_1.saveTokenData)({
173
+ access_token: loginResult.token,
174
+ ...(loginResult.refresh_token ? { refresh_token: loginResult.refresh_token } : {}),
175
+ ...(loginResult.expires_at ? { expires_at: loginResult.expires_at } : {}),
176
+ });
177
+ console.log(`\x1b[32m✓ 로그인 완료\x1b[0m`);
178
+ }
179
+ function registerLogin(program) {
180
+ program
181
+ .command('login')
182
+ .description('RelayAX 계정에 로그인합니다')
183
+ .option('--token <token>', '직접 토큰 입력 (브라우저 없이)')
184
+ .option('--device', 'Device code 방식으로 로그인 (샌드박스/원격 환경용)')
185
+ .action(async (opts) => {
186
+ const json = program.opts().json ?? false;
187
+ (0, config_js_1.ensureGlobalAnpmDir)();
188
+ (0, step_tracker_js_1.trackCommand)('login');
189
+ let accessToken = opts.token;
190
+ let refreshToken;
191
+ let expiresAt;
192
+ if (!accessToken) {
193
+ const loginFn = opts.device ? loginWithDevice : loginWithBrowser;
194
+ try {
195
+ const loginResult = await loginFn(json);
196
+ accessToken = loginResult.token;
197
+ refreshToken = loginResult.refresh_token;
198
+ expiresAt = loginResult.expires_at;
199
+ }
200
+ catch (err) {
201
+ const msg = err instanceof Error ? err.message : '로그인 실패';
202
+ (0, error_report_js_1.reportCliError)('login', 'LOGIN_FAILED', msg);
203
+ if (json) {
204
+ console.error(JSON.stringify({ error: 'LOGIN_FAILED', message: msg, fix: opts.device ? '다시 시도하세요.' : 'anpm login --device를 시도하세요.' }));
205
+ }
206
+ else {
207
+ console.error(`\x1b[31m오류: ${msg}\x1b[0m`);
208
+ if (!opts.device) {
209
+ console.error(`\n\x1b[33m팁: 브라우저 콜백이 안 되는 환경이라면 anpm login --device를 시도하세요.\x1b[0m`);
210
+ }
211
+ }
212
+ process.exit(1);
213
+ }
214
+ }
215
+ const user = await verifyToken(accessToken);
216
+ (0, config_js_1.saveTokenData)({
217
+ access_token: accessToken,
218
+ ...(refreshToken ? { refresh_token: refreshToken } : {}),
219
+ ...(expiresAt ? { expires_at: expiresAt } : {}),
220
+ });
221
+ const result = {
222
+ status: 'ok',
223
+ message: '로그인 성공',
224
+ ...(user ? { email: user.email } : {}),
225
+ };
226
+ if (json) {
227
+ console.log(JSON.stringify(result));
228
+ }
229
+ else {
230
+ console.log(`\x1b[32m✓ 로그인 완료\x1b[0m`);
231
+ if (user?.email)
232
+ console.log(` 계정: \x1b[36m${user.email}\x1b[0m`);
233
+ }
234
+ });
235
+ }
@@ -0,0 +1,10 @@
1
+ import { Command } from 'commander';
2
+ export interface OrgInfo {
3
+ id: string;
4
+ slug: string;
5
+ name: string;
6
+ description: string | null;
7
+ role: string;
8
+ }
9
+ export declare function fetchMyOrgs(token: string): Promise<OrgInfo[]>;
10
+ export declare function registerOrgs(program: Command): void;