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.
- package/README.md +174 -0
- package/dist/commands/access.d.ts +2 -0
- package/dist/commands/access.js +90 -0
- package/dist/commands/adopt.d.ts +2 -0
- package/dist/commands/adopt.js +177 -0
- package/dist/commands/changelog.d.ts +2 -0
- package/dist/commands/changelog.js +67 -0
- package/dist/commands/check-update.d.ts +2 -0
- package/dist/commands/check-update.js +76 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +84 -0
- package/dist/commands/create.d.ts +2 -0
- package/dist/commands/create.js +227 -0
- package/dist/commands/deploy-record.d.ts +2 -0
- package/dist/commands/deploy-record.js +93 -0
- package/dist/commands/deploy.d.ts +2 -0
- package/dist/commands/deploy.js +284 -0
- package/dist/commands/diff.d.ts +2 -0
- package/dist/commands/diff.js +92 -0
- package/dist/commands/feedback.d.ts +2 -0
- package/dist/commands/feedback.js +71 -0
- package/dist/commands/grant.d.ts +33 -0
- package/dist/commands/grant.js +190 -0
- package/dist/commands/hub.d.ts +2 -0
- package/dist/commands/hub.js +171 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.js +172 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.js +626 -0
- package/dist/commands/join.d.ts +6 -0
- package/dist/commands/join.js +90 -0
- package/dist/commands/link.d.ts +2 -0
- package/dist/commands/link.js +112 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.js +144 -0
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.js +235 -0
- package/dist/commands/orgs.d.ts +10 -0
- package/dist/commands/orgs.js +128 -0
- package/dist/commands/outdated.d.ts +2 -0
- package/dist/commands/outdated.js +70 -0
- package/dist/commands/package.d.ts +57 -0
- package/dist/commands/package.js +569 -0
- package/dist/commands/ping.d.ts +2 -0
- package/dist/commands/ping.js +40 -0
- package/dist/commands/publish.d.ts +98 -0
- package/dist/commands/publish.js +899 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +249 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +57 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +159 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.js +132 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +171 -0
- package/dist/commands/versions.d.ts +2 -0
- package/dist/commands/versions.js +44 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +91 -0
- package/dist/lib/agent-status.d.ts +23 -0
- package/dist/lib/agent-status.js +127 -0
- package/dist/lib/ai-tools.d.ts +34 -0
- package/dist/lib/ai-tools.js +104 -0
- package/dist/lib/anpm-config.d.ts +39 -0
- package/dist/lib/anpm-config.js +112 -0
- package/dist/lib/api.d.ts +24 -0
- package/dist/lib/api.js +151 -0
- package/dist/lib/auto-detect.d.ts +30 -0
- package/dist/lib/auto-detect.js +112 -0
- package/dist/lib/cloud-providers/anthropic.d.ts +19 -0
- package/dist/lib/cloud-providers/anthropic.js +200 -0
- package/dist/lib/cloud-providers/package-mapper.d.ts +9 -0
- package/dist/lib/cloud-providers/package-mapper.js +34 -0
- package/dist/lib/cloud-providers/provider.d.ts +60 -0
- package/dist/lib/cloud-providers/provider.js +7 -0
- package/dist/lib/command-adapter.d.ts +41 -0
- package/dist/lib/command-adapter.js +188 -0
- package/dist/lib/config.d.ts +50 -0
- package/dist/lib/config.js +274 -0
- package/dist/lib/contact-format.d.ts +7 -0
- package/dist/lib/contact-format.js +23 -0
- package/dist/lib/device-hash.d.ts +1 -0
- package/dist/lib/device-hash.js +16 -0
- package/dist/lib/error-report.d.ts +5 -0
- package/dist/lib/error-report.js +28 -0
- package/dist/lib/git-installer.d.ts +16 -0
- package/dist/lib/git-installer.js +97 -0
- package/dist/lib/git-operations.d.ts +38 -0
- package/dist/lib/git-operations.js +183 -0
- package/dist/lib/hub-notify.d.ts +9 -0
- package/dist/lib/hub-notify.js +66 -0
- package/dist/lib/install-source.d.ts +33 -0
- package/dist/lib/install-source.js +98 -0
- package/dist/lib/installer.d.ts +40 -0
- package/dist/lib/installer.js +358 -0
- package/dist/lib/local-installer.d.ts +15 -0
- package/dist/lib/local-installer.js +73 -0
- package/dist/lib/lockfile.d.ts +13 -0
- package/dist/lib/lockfile.js +42 -0
- package/dist/lib/manifest.d.ts +65 -0
- package/dist/lib/manifest.js +113 -0
- package/dist/lib/migration.d.ts +10 -0
- package/dist/lib/migration.js +91 -0
- package/dist/lib/paths.d.ts +10 -0
- package/dist/lib/paths.js +22 -0
- package/dist/lib/preamble.d.ts +22 -0
- package/dist/lib/preamble.js +133 -0
- package/dist/lib/relay-config.d.ts +13 -0
- package/dist/lib/relay-config.js +46 -0
- package/dist/lib/requires-suggest.d.ts +23 -0
- package/dist/lib/requires-suggest.js +295 -0
- package/dist/lib/setup-command.d.ts +6 -0
- package/dist/lib/setup-command.js +72 -0
- package/dist/lib/slug.d.ts +24 -0
- package/dist/lib/slug.js +100 -0
- package/dist/lib/step-tracker.d.ts +8 -0
- package/dist/lib/step-tracker.js +28 -0
- package/dist/lib/storage.d.ts +6 -0
- package/dist/lib/storage.js +23 -0
- package/dist/lib/update-cache.d.ts +2 -0
- package/dist/lib/update-cache.js +51 -0
- package/dist/lib/version-check.d.ts +10 -0
- package/dist/lib/version-check.js +75 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +112 -0
- package/dist/postinstall.d.ts +8 -0
- package/dist/postinstall.js +41 -0
- package/dist/prompts/_error-handling.md +38 -0
- package/dist/prompts/create.md +170 -0
- package/dist/prompts/explore.md +30 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.js +22 -0
- package/dist/relay-compat.d.ts +2 -0
- package/dist/relay-compat.js +7 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.js +2 -0
- package/package.json +51 -0
|
@@ -0,0 +1,626 @@
|
|
|
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.registerInstall = registerInstall;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const api_js_1 = require("../lib/api.js");
|
|
11
|
+
const storage_js_1 = require("../lib/storage.js");
|
|
12
|
+
const git_operations_js_1 = require("../lib/git-operations.js");
|
|
13
|
+
const config_js_1 = require("../lib/config.js");
|
|
14
|
+
const slug_js_1 = require("../lib/slug.js");
|
|
15
|
+
const preamble_js_1 = require("../lib/preamble.js");
|
|
16
|
+
const init_js_1 = require("./init.js");
|
|
17
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
18
|
+
const error_report_js_1 = require("../lib/error-report.js");
|
|
19
|
+
const step_tracker_js_1 = require("../lib/step-tracker.js");
|
|
20
|
+
const installer_js_1 = require("../lib/installer.js");
|
|
21
|
+
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
22
|
+
const install_source_js_1 = require("../lib/install-source.js");
|
|
23
|
+
const local_installer_js_1 = require("../lib/local-installer.js");
|
|
24
|
+
const git_installer_js_1 = require("../lib/git-installer.js");
|
|
25
|
+
const auto_detect_js_1 = require("../lib/auto-detect.js");
|
|
26
|
+
const manifest_js_1 = require("../lib/manifest.js");
|
|
27
|
+
const lockfile_js_1 = require("../lib/lockfile.js");
|
|
28
|
+
function registerInstall(program) {
|
|
29
|
+
program
|
|
30
|
+
.command('install [slug]')
|
|
31
|
+
.description('Install an agent package to .relay/agents/')
|
|
32
|
+
.option('--code <code>', 'Access code (for private/internal agents)')
|
|
33
|
+
.option('--global', 'Global install (home directory)')
|
|
34
|
+
.option('--local', 'Local install (project directory)')
|
|
35
|
+
.option('--project <dir>', 'Project root path (default: cwd, env: RELAY_PROJECT_PATH)')
|
|
36
|
+
.option('--path <subpath>', 'Subpath within a monorepo (for git installs)')
|
|
37
|
+
.option('--yes', 'Skip confirmation prompts')
|
|
38
|
+
.option('--save', 'Add agent to relay.yaml agents')
|
|
39
|
+
.option('--prune', 'Remove agents not in relay.yaml')
|
|
40
|
+
.action(async (slugInput, _opts) => {
|
|
41
|
+
const json = program.opts().json ?? false;
|
|
42
|
+
const projectPath = (0, paths_js_1.resolveProjectPath)(_opts.project);
|
|
43
|
+
// ── Manifest mode (no args) ──
|
|
44
|
+
if (!slugInput) {
|
|
45
|
+
const { manifest, filePath: manifestPath } = (0, manifest_js_1.loadManifest)(projectPath);
|
|
46
|
+
if (!manifest?.agents || Object.keys(manifest.agents).length === 0) {
|
|
47
|
+
if (json) {
|
|
48
|
+
console.log(JSON.stringify({ error: 'NO_MANIFEST', message: 'No anpm.yaml with agents found.' }));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.error('No anpm.yaml with agents found. Run `anpm install <slug>` to install an agent.');
|
|
52
|
+
}
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const agents = manifest.agents;
|
|
56
|
+
const _lock = (0, lockfile_js_1.loadLockfile)(projectPath); // used in future for pinned versions
|
|
57
|
+
const localInstalled = (0, config_js_1.loadInstalled)();
|
|
58
|
+
let installed = 0;
|
|
59
|
+
let skipped = 0;
|
|
60
|
+
if (!json)
|
|
61
|
+
console.error(`\x1b[2mInstalling from ${manifestPath}...\x1b[0m`);
|
|
62
|
+
for (const [slug, range] of Object.entries(agents)) {
|
|
63
|
+
// Check if already installed with compatible version
|
|
64
|
+
const existing = localInstalled[slug];
|
|
65
|
+
if (existing && (0, manifest_js_1.satisfiesRange)(existing.version, range)) {
|
|
66
|
+
skipped++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const source = (0, install_source_js_1.parseInstallSource)(slug);
|
|
71
|
+
if (source.type === 'local') {
|
|
72
|
+
(0, local_installer_js_1.installFromLocal)(source.absolutePath, { scope: 'local', projectPath });
|
|
73
|
+
}
|
|
74
|
+
else if (source.type === 'git') {
|
|
75
|
+
(0, git_installer_js_1.installFromGit)(source, { scope: 'local', projectPath });
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Registry install — reuse the existing flow by recursing
|
|
79
|
+
// For now, use a simplified approach: call the single-agent install logic
|
|
80
|
+
if (!json)
|
|
81
|
+
console.error(` Installing ${slug}...`);
|
|
82
|
+
// We'll let the user run `relay install <slug>` for registry agents
|
|
83
|
+
// Full manifest registry install requires the same token/fetch flow
|
|
84
|
+
if (!json)
|
|
85
|
+
console.error(` \x1b[33m⚠ Registry agent ${slug} — run: anpm install ${slug} --save\x1b[0m`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// Update lock
|
|
89
|
+
(0, lockfile_js_1.updateLockEntry)(projectPath, slug, {
|
|
90
|
+
version: existing?.version ?? '0.0.0',
|
|
91
|
+
resolved: source.type === 'local' ? `local:${source.absolutePath}` : `git:${slug}`,
|
|
92
|
+
});
|
|
93
|
+
installed++;
|
|
94
|
+
if (!json)
|
|
95
|
+
console.error(` \x1b[32m✓\x1b[0m ${slug}`);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
99
|
+
if (!json)
|
|
100
|
+
console.error(` \x1b[31m✖\x1b[0m ${slug}: ${msg}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (json) {
|
|
104
|
+
console.log(JSON.stringify({ status: 'ok', installed, skipped, total: Object.keys(agents).length }));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
console.log(`\n\x1b[32m✓\x1b[0m Manifest install complete: ${installed} installed, ${skipped} skipped`);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
(0, step_tracker_js_1.trackCommand)('install', { slug: slugInput });
|
|
112
|
+
// ── Source type branching ──
|
|
113
|
+
const source = (0, install_source_js_1.parseInstallSource)(slugInput);
|
|
114
|
+
if (source.type === 'local' || source.type === 'git') {
|
|
115
|
+
try {
|
|
116
|
+
const interactive = Boolean(process.stdin.isTTY) && !json;
|
|
117
|
+
const scope = _opts.global ? 'global' : 'local';
|
|
118
|
+
let agentDir;
|
|
119
|
+
let installSlug;
|
|
120
|
+
let sourceTag;
|
|
121
|
+
if (source.type === 'local') {
|
|
122
|
+
if (!json)
|
|
123
|
+
console.error(`\x1b[2mInstalling from local path: ${source.absolutePath}\x1b[0m`);
|
|
124
|
+
const result = (0, local_installer_js_1.installFromLocal)(source.absolutePath, { scope, projectPath });
|
|
125
|
+
// Confirm auto-detected structure if no relay.yaml
|
|
126
|
+
if (result.detected.method !== 'relay-yaml' && interactive && !_opts.yes) {
|
|
127
|
+
console.error(`\n Detected structure (${result.detected.method}):`);
|
|
128
|
+
console.error((0, auto_detect_js_1.formatDetectedStructure)(result.detected));
|
|
129
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
130
|
+
const ok = await confirm({ message: 'Install these?', default: true });
|
|
131
|
+
if (!ok) {
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
agentDir = result.agentDir;
|
|
136
|
+
installSlug = `local/${result.name}`;
|
|
137
|
+
sourceTag = `local:${source.absolutePath}`;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
if (!json)
|
|
141
|
+
console.error(`\x1b[2mInstalling from git: ${source.url}${source.ref ? `#${source.ref}` : ''}\x1b[0m`);
|
|
142
|
+
const result = (0, git_installer_js_1.installFromGit)(source, { scope, projectPath, subpath: _opts.path });
|
|
143
|
+
if (result.detected.method !== 'relay-yaml' && interactive && !_opts.yes) {
|
|
144
|
+
console.error(`\n Detected structure (${result.detected.method}):`);
|
|
145
|
+
console.error((0, auto_detect_js_1.formatDetectedStructure)(result.detected));
|
|
146
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
147
|
+
const ok = await confirm({ message: 'Install these?', default: true });
|
|
148
|
+
if (!ok) {
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
agentDir = result.agentDir;
|
|
153
|
+
installSlug = result.slug;
|
|
154
|
+
sourceTag = `git:${source.host}:${source.user}/${source.repo}${source.ref ? `#${source.ref}` : ''}`;
|
|
155
|
+
}
|
|
156
|
+
// Deploy symlinks to detected harnesses
|
|
157
|
+
const deploy = await (0, installer_js_1.deploySymlinks)(agentDir, scope, projectPath);
|
|
158
|
+
for (const w of deploy.warnings) {
|
|
159
|
+
if (!json)
|
|
160
|
+
console.error(`\x1b[33m${w}\x1b[0m`);
|
|
161
|
+
}
|
|
162
|
+
// Record in installed.json
|
|
163
|
+
const installRecord = {
|
|
164
|
+
version: '0.0.0',
|
|
165
|
+
installed_at: new Date().toISOString(),
|
|
166
|
+
files: [agentDir],
|
|
167
|
+
deploy_scope: scope,
|
|
168
|
+
deployed_symlinks: deploy.symlinks,
|
|
169
|
+
source: sourceTag,
|
|
170
|
+
};
|
|
171
|
+
if (scope === 'global') {
|
|
172
|
+
const globalInstalled = (0, config_js_1.loadGlobalInstalled)();
|
|
173
|
+
globalInstalled[installSlug] = installRecord;
|
|
174
|
+
(0, config_js_1.saveGlobalInstalled)(globalInstalled);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const installed = (0, config_js_1.loadInstalled)();
|
|
178
|
+
installed[installSlug] = installRecord;
|
|
179
|
+
(0, config_js_1.saveInstalled)(installed);
|
|
180
|
+
}
|
|
181
|
+
// Count files
|
|
182
|
+
function countFilesInDir(dir) {
|
|
183
|
+
let count = 0;
|
|
184
|
+
if (!fs_1.default.existsSync(dir))
|
|
185
|
+
return 0;
|
|
186
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
187
|
+
if (entry.isDirectory())
|
|
188
|
+
count += countFilesInDir(path_1.default.join(dir, entry.name));
|
|
189
|
+
else
|
|
190
|
+
count++;
|
|
191
|
+
}
|
|
192
|
+
return count;
|
|
193
|
+
}
|
|
194
|
+
const fileCount = countFilesInDir(agentDir);
|
|
195
|
+
const scopeLabel = scope === 'global' ? 'global' : 'local';
|
|
196
|
+
if (json) {
|
|
197
|
+
console.log(JSON.stringify({
|
|
198
|
+
status: 'ok',
|
|
199
|
+
slug: installSlug,
|
|
200
|
+
source: sourceTag,
|
|
201
|
+
files: fileCount,
|
|
202
|
+
install_path: agentDir,
|
|
203
|
+
scope,
|
|
204
|
+
symlinks: deploy.symlinks,
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
console.log(`\n\x1b[32m✓ Installed ${installSlug}\x1b[0m`);
|
|
209
|
+
console.log(` path: \x1b[36m${agentDir}\x1b[0m`);
|
|
210
|
+
console.log(` scope: ${scopeLabel}`);
|
|
211
|
+
console.log(` files: ${fileCount}, symlinks: ${deploy.symlinks.length}`);
|
|
212
|
+
}
|
|
213
|
+
// --save: add to relay.yaml
|
|
214
|
+
if (_opts.save) {
|
|
215
|
+
(0, manifest_js_1.addAgentToManifest)(projectPath, installSlug, '*');
|
|
216
|
+
if (!json)
|
|
217
|
+
console.log(` \x1b[32m✓\x1b[0m Added to anpm.yaml`);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
223
|
+
(0, error_report_js_1.reportCliError)('install', 'INSTALL_FAILED', message);
|
|
224
|
+
if (json) {
|
|
225
|
+
console.error(JSON.stringify({ error: 'INSTALL_FAILED', message }));
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
console.error(`\x1b[31m✖ ${message}\x1b[0m`);
|
|
229
|
+
}
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// ── Registry install flow (existing) ──
|
|
234
|
+
const tempDir = (0, storage_js_1.makeTempDir)();
|
|
235
|
+
try {
|
|
236
|
+
// Resolve scoped slug and fetch agent metadata
|
|
237
|
+
let agent;
|
|
238
|
+
let slug;
|
|
239
|
+
let parsed;
|
|
240
|
+
// Extract version from @owner/agent@version syntax (e.g. acme/writer@1.2.0)
|
|
241
|
+
// Version-specific install is not yet supported by the registry API;
|
|
242
|
+
// the match is kept for future use when per-version package URLs are available.
|
|
243
|
+
const versionMatch = slugInput.match(/^(.+)@(\d+\.\d+\.\d+.*)$/);
|
|
244
|
+
const actualSlugInput = versionMatch ? versionMatch[1] : slugInput;
|
|
245
|
+
parsed = await (0, slug_js_1.resolveSlug)(actualSlugInput);
|
|
246
|
+
slug = parsed.full;
|
|
247
|
+
// Helper: ensure a valid token exists, triggering auto-login in TTY if needed.
|
|
248
|
+
// Returns the token string or null if login failed / not available.
|
|
249
|
+
async function ensureToken() {
|
|
250
|
+
let token = await (0, config_js_1.getValidToken)();
|
|
251
|
+
if (!token) {
|
|
252
|
+
const isTTY = Boolean(process.stdin.isTTY);
|
|
253
|
+
if (isTTY && !json) {
|
|
254
|
+
console.error('\x1b[33m⚙ 이 에이전트는 로그인이 필요합니다. 로그인을 시작합니다...\x1b[0m');
|
|
255
|
+
const { runLogin } = await import('./login.js');
|
|
256
|
+
await runLogin();
|
|
257
|
+
token = await (0, config_js_1.getValidToken)();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return token ?? null;
|
|
261
|
+
}
|
|
262
|
+
// Pre-fetch auto-login: --code always requires auth.
|
|
263
|
+
if (_opts.code) {
|
|
264
|
+
const token = await ensureToken();
|
|
265
|
+
if (!token) {
|
|
266
|
+
if (json) {
|
|
267
|
+
console.error(JSON.stringify({
|
|
268
|
+
error: 'LOGIN_REQUIRED',
|
|
269
|
+
slug,
|
|
270
|
+
message: '이 에이전트는 로그인이 필요합니다. anpm login을 먼저 실행하세요.',
|
|
271
|
+
fix: 'anpm login 실행 후 재시도하세요.',
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
console.error('\x1b[31m이 에이전트는 로그인이 필요합니다. anpm login 을 먼저 실행하세요.\x1b[0m');
|
|
276
|
+
}
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
agent = await (0, api_js_1.fetchAgentInfo)(slug);
|
|
282
|
+
}
|
|
283
|
+
catch (fetchErr) {
|
|
284
|
+
const fetchMsg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
|
|
285
|
+
if (fetchMsg.includes('403')) {
|
|
286
|
+
// Parse error body for membership_status, visibility, purchase_info
|
|
287
|
+
let membershipStatus;
|
|
288
|
+
let errorVisibility;
|
|
289
|
+
let purchaseInfo;
|
|
290
|
+
try {
|
|
291
|
+
const errBody = JSON.parse(fetchMsg.replace(/^.*?(\{)/, '{'));
|
|
292
|
+
membershipStatus = typeof errBody.membership_status === 'string' ? errBody.membership_status : undefined;
|
|
293
|
+
errorVisibility = typeof errBody.visibility === 'string' ? errBody.visibility : undefined;
|
|
294
|
+
if (errBody.purchase_info && typeof errBody.purchase_info === 'object') {
|
|
295
|
+
purchaseInfo = errBody.purchase_info;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch { /* ignore parse errors */ }
|
|
299
|
+
// --code provided → use unified access-codes API (handles both org join and agent grant)
|
|
300
|
+
if (_opts.code) {
|
|
301
|
+
if (!json) {
|
|
302
|
+
console.error('\x1b[33m⚙ 접근 코드로 권한을 요청합니다...\x1b[0m');
|
|
303
|
+
}
|
|
304
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
305
|
+
if (!token) {
|
|
306
|
+
if (json) {
|
|
307
|
+
console.error(JSON.stringify({
|
|
308
|
+
error: 'LOGIN_REQUIRED',
|
|
309
|
+
slug,
|
|
310
|
+
message: '이 에이전트는 로그인이 필요합니다. anpm login을 먼저 실행하세요.',
|
|
311
|
+
fix: 'anpm login 실행 후 재시도하세요.',
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
console.error('\x1b[31m이 에이전트는 로그인이 필요합니다. anpm login 을 먼저 실행하세요.\x1b[0m');
|
|
316
|
+
}
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
const codeRes = await fetch(`${config_js_1.API_URL}/api/access-codes/${_opts.code}/use`, {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
headers: {
|
|
322
|
+
'Content-Type': 'application/json',
|
|
323
|
+
Authorization: `Bearer ${token}`,
|
|
324
|
+
},
|
|
325
|
+
signal: AbortSignal.timeout(10000),
|
|
326
|
+
});
|
|
327
|
+
if (!codeRes.ok) {
|
|
328
|
+
const codeBody = (await codeRes.json().catch(() => ({})));
|
|
329
|
+
const codeErrCode = codeBody.error ?? String(codeRes.status);
|
|
330
|
+
if (codeErrCode === 'INVALID_LINK')
|
|
331
|
+
throw new Error('접근 코드가 유효하지 않거나 만료되었습니다.');
|
|
332
|
+
throw new Error(codeBody.message ?? `접근 권한 요청 실패 (${codeRes.status})`);
|
|
333
|
+
}
|
|
334
|
+
agent = await (0, api_js_1.fetchAgentInfo)(slug);
|
|
335
|
+
}
|
|
336
|
+
// No code provided: show appropriate error messages
|
|
337
|
+
else if (errorVisibility === 'private' || purchaseInfo) {
|
|
338
|
+
// Private agent: show purchase info + relay access hint
|
|
339
|
+
if (json) {
|
|
340
|
+
console.error(JSON.stringify({
|
|
341
|
+
error: 'ACCESS_REQUIRED',
|
|
342
|
+
message: '이 에이전트는 접근 권한이 필요합니다.',
|
|
343
|
+
slug,
|
|
344
|
+
purchase_info: purchaseInfo ?? null,
|
|
345
|
+
fix: '접근 링크 코드가 있으면: anpm install ' + slugInput + ' --code <코드>',
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
console.error('\x1b[31m이 에이전트는 접근 권한이 필요합니다.\x1b[0m');
|
|
350
|
+
if (purchaseInfo?.message) {
|
|
351
|
+
console.error(`\n \x1b[36m${purchaseInfo.message}\x1b[0m`);
|
|
352
|
+
}
|
|
353
|
+
if (purchaseInfo?.url) {
|
|
354
|
+
console.error(` \x1b[36m${purchaseInfo.url}\x1b[0m`);
|
|
355
|
+
}
|
|
356
|
+
console.error(`\n\x1b[33m접근 링크 코드가 있으면: anpm install ${slugInput} --code <코드>\x1b[0m`);
|
|
357
|
+
}
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
else if (membershipStatus === 'member') {
|
|
361
|
+
// Member but no access to this specific agent
|
|
362
|
+
if (json) {
|
|
363
|
+
console.error(JSON.stringify({
|
|
364
|
+
error: 'NO_ACCESS',
|
|
365
|
+
message: '이 에이전트에 대한 접근 권한이 없습니다.',
|
|
366
|
+
slug,
|
|
367
|
+
fix: '이 에이전트의 접근 링크 코드가 있으면 `anpm install ' + slugInput + ' --code <코드>`로 접근 권한을 얻으세요. 없으면 에이전트 제작자에게 문의하세요.',
|
|
368
|
+
}));
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
console.error('\x1b[31m이 에이전트에 대한 접근 권한이 없습니다.\x1b[0m');
|
|
372
|
+
}
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
if (json) {
|
|
377
|
+
console.error(JSON.stringify({
|
|
378
|
+
error: 'ACCESS_REQUIRED',
|
|
379
|
+
message: '이 에이전트는 접근 권한이 필요합니다.',
|
|
380
|
+
slug,
|
|
381
|
+
fix: '접근 코드가 있으면 `anpm install ' + slugInput + ' --code <코드>`로 설치하세요.',
|
|
382
|
+
}));
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
console.error('\x1b[31m이 에이전트는 접근 권한이 필요합니다.\x1b[0m');
|
|
386
|
+
console.error('\x1b[33m접근 코드가 있으면 `anpm install ' + slugInput + ' --code <코드>`로 설치하세요.\x1b[0m');
|
|
387
|
+
}
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
throw fetchErr;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (!agent)
|
|
396
|
+
throw new Error('에이전트 정보를 가져오지 못했습니다.');
|
|
397
|
+
// Re-bind as non-optional so TypeScript tracks the narrowing through nested scopes
|
|
398
|
+
let resolvedAgent = agent;
|
|
399
|
+
const isTTY = Boolean(process.stdin.isTTY);
|
|
400
|
+
const interactive = isTTY && !json;
|
|
401
|
+
const defaultScope = resolvedAgent.recommended_scope ?? (resolvedAgent.type === 'passive' ? 'local' : 'global');
|
|
402
|
+
// ── 1. AI tools 선택 (scope 무관, 항상 홈 디렉토리에서 감지) ──
|
|
403
|
+
let selectedTools;
|
|
404
|
+
if (interactive) {
|
|
405
|
+
const detected = (0, ai_tools_js_1.detectGlobalCLIs)();
|
|
406
|
+
if (!detected.some((t) => t.value === 'claude')) {
|
|
407
|
+
detected.push({ name: 'Claude Code', value: 'claude', skillsDir: '.claude' });
|
|
408
|
+
}
|
|
409
|
+
const detectedValues = new Set(detected.map((t) => t.value));
|
|
410
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
411
|
+
selectedTools = await checkbox({
|
|
412
|
+
message: '설치할 AI 도구를 선택하세요 (감지된 도구는 자동 선택됨)',
|
|
413
|
+
choices: ai_tools_js_1.AI_TOOLS.map((t) => ({
|
|
414
|
+
name: t.name,
|
|
415
|
+
value: t,
|
|
416
|
+
checked: detectedValues.has(t.value),
|
|
417
|
+
})),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
// ── 2. 글로벌 slash commands 설치/업데이트 ──
|
|
421
|
+
if (selectedTools) {
|
|
422
|
+
(0, init_js_1.installGlobalUserCommands)(selectedTools);
|
|
423
|
+
}
|
|
424
|
+
else if (!(0, init_js_1.hasGlobalUserCommands)()) {
|
|
425
|
+
if (!json) {
|
|
426
|
+
console.error('\x1b[33m⚙ 글로벌 커맨드를 자동 설치합니다...\x1b[0m');
|
|
427
|
+
}
|
|
428
|
+
(0, init_js_1.installGlobalUserCommands)();
|
|
429
|
+
}
|
|
430
|
+
// ── 3. Scope 결정: 플래그 > TTY prompt > 자동결정 ──
|
|
431
|
+
let scope;
|
|
432
|
+
if (_opts.global) {
|
|
433
|
+
scope = 'global';
|
|
434
|
+
}
|
|
435
|
+
else if (_opts.local) {
|
|
436
|
+
scope = 'local';
|
|
437
|
+
}
|
|
438
|
+
else if (interactive) {
|
|
439
|
+
const { select } = await import('@inquirer/prompts');
|
|
440
|
+
const recommendLabel = defaultScope === 'global' ? '글로벌' : '로컬';
|
|
441
|
+
scope = await select({
|
|
442
|
+
message: `설치 범위를 선택하세요 (제작자 권장: ${recommendLabel})`,
|
|
443
|
+
choices: [
|
|
444
|
+
{ name: '글로벌 (~/.relay/agents/) — 모든 프로젝트에서 사용', value: 'global' },
|
|
445
|
+
{ name: '로컬 (./.relay/agents/) — 이 프로젝트에서만 사용', value: 'local' },
|
|
446
|
+
],
|
|
447
|
+
default: defaultScope,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
scope = defaultScope;
|
|
452
|
+
}
|
|
453
|
+
const agentDir = scope === 'global'
|
|
454
|
+
? path_1.default.join(os_1.default.homedir(), '.relay', 'agents', parsed.owner, parsed.name)
|
|
455
|
+
: path_1.default.join(projectPath, '.relay', 'agents', parsed.owner, parsed.name);
|
|
456
|
+
// 2. 로그인 필수 (git clone에 relay token 필요)
|
|
457
|
+
const token = await ensureToken();
|
|
458
|
+
if (!token) {
|
|
459
|
+
if (json) {
|
|
460
|
+
console.error(JSON.stringify({
|
|
461
|
+
error: 'LOGIN_REQUIRED',
|
|
462
|
+
slug,
|
|
463
|
+
message: '로그인이 필요합니다. anpm login을 먼저 실행하세요.',
|
|
464
|
+
fix: 'anpm login 실행 후 재시도하세요.',
|
|
465
|
+
}));
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
console.error('\x1b[31m로그인이 필요합니다. anpm login 을 먼저 실행하세요.\x1b[0m');
|
|
469
|
+
}
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
// 3. Download package via git clone
|
|
473
|
+
const requestedVersion = versionMatch ? versionMatch[2] : undefined;
|
|
474
|
+
if (!resolvedAgent.git_url) {
|
|
475
|
+
const errMsg = '이 에이전트는 재publish가 필요합니다. 빌더에게 문의하세요.';
|
|
476
|
+
if (json) {
|
|
477
|
+
console.log(JSON.stringify({ error: 'NO_GIT_URL', message: errMsg }));
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
console.error(`\x1b[31m✖ ${errMsg}\x1b[0m`);
|
|
481
|
+
}
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
(0, git_operations_js_1.checkGitInstalled)();
|
|
485
|
+
const gitUrl = (0, git_operations_js_1.buildGitUrl)(resolvedAgent.git_url, { token });
|
|
486
|
+
await (0, storage_js_1.clonePackage)(gitUrl, agentDir, requestedVersion);
|
|
487
|
+
// Verify clone has actual files (not just .git)
|
|
488
|
+
const clonedEntries = fs_1.default.readdirSync(agentDir).filter((f) => f !== '.git');
|
|
489
|
+
if (clonedEntries.length === 0) {
|
|
490
|
+
fs_1.default.rmSync(agentDir, { recursive: true, force: true });
|
|
491
|
+
const errMsg = '에이전트 패키지가 비어있습니다. 빌더에게 재publish를 요청하세요.';
|
|
492
|
+
if (json) {
|
|
493
|
+
console.log(JSON.stringify({ error: 'EMPTY_PACKAGE', message: errMsg }));
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
console.error(`\x1b[31m✖ ${errMsg}\x1b[0m`);
|
|
497
|
+
}
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
// 4.5. Inject preamble (update check) into SKILL.md and commands
|
|
501
|
+
(0, preamble_js_1.injectPreambleToAgent)(agentDir, slug);
|
|
502
|
+
// 5. Deploy symlinks to detected AI tool directories
|
|
503
|
+
const deploy = await (0, installer_js_1.deploySymlinks)(agentDir, scope, projectPath, selectedTools);
|
|
504
|
+
for (const w of deploy.warnings) {
|
|
505
|
+
if (!json)
|
|
506
|
+
console.error(`\x1b[33m${w}\x1b[0m`);
|
|
507
|
+
}
|
|
508
|
+
// 6. Count extracted files
|
|
509
|
+
function countFiles(dir) {
|
|
510
|
+
let count = 0;
|
|
511
|
+
if (!fs_1.default.existsSync(dir))
|
|
512
|
+
return 0;
|
|
513
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
514
|
+
if (entry.isDirectory()) {
|
|
515
|
+
count += countFiles(path_1.default.join(dir, entry.name));
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
count++;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return count;
|
|
522
|
+
}
|
|
523
|
+
const fileCount = countFiles(agentDir);
|
|
524
|
+
// 7. Record in installed.json (scope-aware)
|
|
525
|
+
const installRecord = {
|
|
526
|
+
agent_id: resolvedAgent.id,
|
|
527
|
+
version: resolvedAgent.version,
|
|
528
|
+
installed_at: new Date().toISOString(),
|
|
529
|
+
files: [agentDir],
|
|
530
|
+
deploy_scope: scope,
|
|
531
|
+
deployed_symlinks: deploy.symlinks,
|
|
532
|
+
};
|
|
533
|
+
if (scope === 'global') {
|
|
534
|
+
const globalInstalled = (0, config_js_1.loadGlobalInstalled)();
|
|
535
|
+
globalInstalled[slug] = installRecord;
|
|
536
|
+
(0, config_js_1.saveGlobalInstalled)(globalInstalled);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
const installed = (0, config_js_1.loadInstalled)();
|
|
540
|
+
installed[slug] = installRecord;
|
|
541
|
+
(0, config_js_1.saveInstalled)(installed);
|
|
542
|
+
}
|
|
543
|
+
// 8. Report install + usage ping (non-blocking, agent_id 기반)
|
|
544
|
+
await (0, api_js_1.reportInstall)(resolvedAgent.id, slug, resolvedAgent.version);
|
|
545
|
+
(0, api_js_1.sendUsagePing)(resolvedAgent.id, slug, resolvedAgent.version);
|
|
546
|
+
const result = {
|
|
547
|
+
status: 'ok',
|
|
548
|
+
agent: resolvedAgent.name,
|
|
549
|
+
slug,
|
|
550
|
+
version: resolvedAgent.version,
|
|
551
|
+
commands: resolvedAgent.commands,
|
|
552
|
+
files: fileCount,
|
|
553
|
+
install_path: agentDir,
|
|
554
|
+
scope,
|
|
555
|
+
symlinks: deploy.symlinks,
|
|
556
|
+
author: resolvedAgent.author ? {
|
|
557
|
+
username: resolvedAgent.author.username,
|
|
558
|
+
display_name: resolvedAgent.author.display_name ?? null,
|
|
559
|
+
contact_links: resolvedAgent.author.contact_links ?? [],
|
|
560
|
+
} : null,
|
|
561
|
+
welcome: resolvedAgent.welcome ?? null,
|
|
562
|
+
};
|
|
563
|
+
if (json) {
|
|
564
|
+
console.log(JSON.stringify(result));
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
const authorUsername = resolvedAgent.author?.username;
|
|
568
|
+
const authorSuffix = authorUsername ? ` \x1b[90mby @${authorUsername}\x1b[0m` : '';
|
|
569
|
+
const scopeLabel = scope === 'global' ? '글로벌' : '로컬';
|
|
570
|
+
console.log(`\n\x1b[32m✓ ${resolvedAgent.name} 설치 완료\x1b[0m v${resolvedAgent.version}${authorSuffix}`);
|
|
571
|
+
console.log(` 위치: \x1b[36m${agentDir}\x1b[0m`);
|
|
572
|
+
console.log(` 범위: ${scopeLabel}`);
|
|
573
|
+
console.log(` 파일: ${fileCount}개, symlink: ${deploy.symlinks.length}개`);
|
|
574
|
+
const userCommands = resolvedAgent.commands.filter((c) => !c.name.startsWith('setup-'));
|
|
575
|
+
if (userCommands.length > 0) {
|
|
576
|
+
console.log('\n 포함된 커맨드:');
|
|
577
|
+
for (const cmd of userCommands) {
|
|
578
|
+
console.log(` \x1b[33m/${cmd.name}\x1b[0m - ${cmd.description}`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Usage hint (type-aware, setup command 제외)
|
|
582
|
+
const agentType = resolvedAgent.type;
|
|
583
|
+
const mainCommand = userCommands[0];
|
|
584
|
+
if (agentType === 'passive') {
|
|
585
|
+
console.log(`\n\x1b[33m💡 자동 적용됩니다. 별도 실행 없이 동작합니다.\x1b[0m`);
|
|
586
|
+
}
|
|
587
|
+
else if (agentType === 'hybrid' && mainCommand) {
|
|
588
|
+
console.log(`\n\x1b[33m💡 자동 적용 + \x1b[1m/${mainCommand.name}\x1b[0m\x1b[33m 으로 추가 기능을 사용할 수 있습니다.\x1b[0m`);
|
|
589
|
+
}
|
|
590
|
+
else if (mainCommand) {
|
|
591
|
+
console.log(`\n\x1b[33m💡 사용법: \x1b[1m/${mainCommand.name}\x1b[0m`);
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
console.log(`\n\x1b[33m💡 설치 완료! AI 에이전트에서 사용할 수 있습니다.\x1b[0m`);
|
|
595
|
+
}
|
|
596
|
+
// Requires check + setup CTA
|
|
597
|
+
const requiresResults = (0, installer_js_1.checkRequires)(agentDir);
|
|
598
|
+
(0, installer_js_1.printRequiresCheck)(requiresResults);
|
|
599
|
+
const setupCmd = resolvedAgent.commands.find((c) => c.name.startsWith('setup-'));
|
|
600
|
+
if (setupCmd && requiresResults.some((r) => r.status === 'missing' || r.status === 'warn')) {
|
|
601
|
+
const toolNames = selectedTools
|
|
602
|
+
? selectedTools.map((t) => t.name).slice(0, 2).join(' 또는 ')
|
|
603
|
+
: 'Claude Code';
|
|
604
|
+
console.log(`\n \x1b[36m👉 설정이 필요합니다.\x1b[0m`);
|
|
605
|
+
console.log(` \x1b[36m ${toolNames}를 열고 \x1b[1m/${setupCmd.name}\x1b[0m\x1b[36m 을 입력하세요\x1b[0m`);
|
|
606
|
+
}
|
|
607
|
+
// Cloud deploy hint
|
|
608
|
+
const cloudConfig = resolvedAgent.cloud_config;
|
|
609
|
+
if (cloudConfig) {
|
|
610
|
+
const providers = cloudConfig.supported_providers;
|
|
611
|
+
const providerStr = providers?.join(', ') ?? 'anthropic';
|
|
612
|
+
console.log(`\n ☁️ Cloud deploy available. Run: \x1b[36manpm deploy ${slugInput} --to ${providerStr}\x1b[0m`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
catch (err) {
|
|
617
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
618
|
+
(0, error_report_js_1.reportCliError)('install', 'INSTALL_FAILED', message);
|
|
619
|
+
console.error(JSON.stringify({ error: 'INSTALL_FAILED', message, fix: message }));
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
finally {
|
|
623
|
+
(0, storage_js_1.removeTempDir)(tempDir);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|