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,249 @@
|
|
|
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.registerRun = registerRun;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
12
|
+
const manifest_js_1 = require("../lib/manifest.js");
|
|
13
|
+
const config_js_1 = require("../lib/config.js");
|
|
14
|
+
const HARNESS_MAP = {
|
|
15
|
+
claude: { name: 'Claude Code', command: 'claude', skillsDir: '.claude', type: 'cli' },
|
|
16
|
+
codex: { name: 'Codex', command: 'codex', skillsDir: '.codex', type: 'cli' },
|
|
17
|
+
gemini: { name: 'Gemini CLI', command: 'gemini', skillsDir: '.gemini', type: 'cli' },
|
|
18
|
+
cursor: { name: 'Cursor', command: 'cursor', skillsDir: '.cursor', type: 'gui' },
|
|
19
|
+
windsurf: { name: 'Windsurf', command: 'windsurf', skillsDir: '.windsurf', type: 'gui' },
|
|
20
|
+
cline: { name: 'Cline', command: 'cline', skillsDir: '.cline', type: 'cli' },
|
|
21
|
+
roocode: { name: 'RooCode', command: 'roocode', skillsDir: '.roo', type: 'cli' },
|
|
22
|
+
};
|
|
23
|
+
// Dotfiles to inherit from real HOME
|
|
24
|
+
const INHERITED_DOTFILES = [
|
|
25
|
+
'.ssh', '.gitconfig', '.git-credentials', '.npmrc', '.config',
|
|
26
|
+
'.zshrc', '.bashrc', '.profile', '.env',
|
|
27
|
+
];
|
|
28
|
+
function registerRun(program) {
|
|
29
|
+
program
|
|
30
|
+
.command('run <harness>')
|
|
31
|
+
.description('Run a harness with isolated agent environment')
|
|
32
|
+
.option('--with <agents>', 'Comma-separated agent slugs to activate (overrides relay.yaml)')
|
|
33
|
+
.option('--profile <name>', 'Use a named profile from ~/.relay/profiles/')
|
|
34
|
+
.option('--project <dir>', 'Project root path')
|
|
35
|
+
.allowUnknownOption(true)
|
|
36
|
+
.action(async (harness, opts, cmd) => {
|
|
37
|
+
const json = program.opts().json ?? false;
|
|
38
|
+
const projectPath = (0, paths_js_1.resolveProjectPath)(opts.project);
|
|
39
|
+
// 1. Resolve harness
|
|
40
|
+
const mapping = HARNESS_MAP[harness.toLowerCase()];
|
|
41
|
+
if (!mapping) {
|
|
42
|
+
const available = Object.keys(HARNESS_MAP).join(', ');
|
|
43
|
+
if (json) {
|
|
44
|
+
console.error(JSON.stringify({ error: 'UNKNOWN_HARNESS', message: `Harness '${harness}' not found. Available: ${available}` }));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.error(`\x1b[31m✖ Harness '${harness}' not found.\x1b[0m\n Available: ${available}`);
|
|
48
|
+
}
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
// 2. Determine which agents to activate
|
|
52
|
+
let agentSlugs;
|
|
53
|
+
if (opts.with) {
|
|
54
|
+
agentSlugs = opts.with.split(',').map((s) => s.trim());
|
|
55
|
+
}
|
|
56
|
+
else if (opts.profile) {
|
|
57
|
+
agentSlugs = loadProfile(opts.profile);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const { manifest } = (0, manifest_js_1.loadManifest)(projectPath);
|
|
61
|
+
if (!manifest?.agents || Object.keys(manifest.agents).length === 0) {
|
|
62
|
+
if (json) {
|
|
63
|
+
console.error(JSON.stringify({ error: 'NO_AGENTS', message: 'No agents specified. Use --with, --profile, or add agents to relay.yaml.' }));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.error('\x1b[31m✖ No agents specified.\x1b[0m');
|
|
67
|
+
console.error(' Use --with <agents>, --profile <name>, or add agents to relay.yaml.');
|
|
68
|
+
}
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
agentSlugs = Object.keys(manifest.agents);
|
|
72
|
+
}
|
|
73
|
+
// 3. Resolve agent directories from installed.json
|
|
74
|
+
const { global: globalInstalled, local: localInstalled } = (0, config_js_1.loadMergedInstalled)();
|
|
75
|
+
const allInstalled = { ...globalInstalled, ...localInstalled };
|
|
76
|
+
const agentDirs = [];
|
|
77
|
+
for (const slug of agentSlugs) {
|
|
78
|
+
const entry = allInstalled[slug];
|
|
79
|
+
if (entry?.files?.[0]) {
|
|
80
|
+
agentDirs.push({ slug, dir: entry.files[0] });
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
if (!json)
|
|
84
|
+
console.error(`\x1b[33m⚠ Agent ${slug} not installed, skipping\x1b[0m`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (agentDirs.length === 0) {
|
|
88
|
+
if (json)
|
|
89
|
+
console.error(JSON.stringify({ error: 'NO_AGENTS_INSTALLED', message: 'None of the specified agents are installed.' }));
|
|
90
|
+
else
|
|
91
|
+
console.error('\x1b[31m✖ None of the specified agents are installed.\x1b[0m');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
// 4. Extract passthrough args (everything after --)
|
|
95
|
+
const passthroughArgs = cmd.args.filter((a) => a !== harness);
|
|
96
|
+
// 5. Launch based on harness type
|
|
97
|
+
if (mapping.type === 'cli') {
|
|
98
|
+
await launchCliHarness(mapping, agentDirs, projectPath, passthroughArgs, json);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
await launchGuiHarness(mapping, agentDirs, projectPath, passthroughArgs, json);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async function launchCliHarness(mapping, agentDirs, projectPath, passthroughArgs, json) {
|
|
106
|
+
const realHome = os_1.default.homedir();
|
|
107
|
+
const tempHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'relay-env-'));
|
|
108
|
+
try {
|
|
109
|
+
// Inherit dotfiles from real HOME
|
|
110
|
+
for (const dotfile of INHERITED_DOTFILES) {
|
|
111
|
+
const src = path_1.default.join(realHome, dotfile);
|
|
112
|
+
if (fs_1.default.existsSync(src)) {
|
|
113
|
+
const dest = path_1.default.join(tempHome, dotfile);
|
|
114
|
+
try {
|
|
115
|
+
fs_1.default.symlinkSync(src, dest);
|
|
116
|
+
}
|
|
117
|
+
catch { /* skip if fails */ }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Inherit .relay directory (for token, config, etc.)
|
|
121
|
+
const relaySrc = path_1.default.join(realHome, '.relay');
|
|
122
|
+
if (fs_1.default.existsSync(relaySrc)) {
|
|
123
|
+
fs_1.default.symlinkSync(relaySrc, path_1.default.join(tempHome, '.relay'));
|
|
124
|
+
}
|
|
125
|
+
// Inherit harness settings (credentials, settings.json, etc.)
|
|
126
|
+
const harnessDir = path_1.default.join(realHome, mapping.skillsDir);
|
|
127
|
+
const tempHarnessDir = path_1.default.join(tempHome, mapping.skillsDir);
|
|
128
|
+
fs_1.default.mkdirSync(tempHarnessDir, { recursive: true });
|
|
129
|
+
// Symlink settings files (not skills/)
|
|
130
|
+
if (fs_1.default.existsSync(harnessDir)) {
|
|
131
|
+
for (const entry of fs_1.default.readdirSync(harnessDir, { withFileTypes: true })) {
|
|
132
|
+
if (['skills', 'commands', 'rules', 'agents'].includes(entry.name))
|
|
133
|
+
continue;
|
|
134
|
+
try {
|
|
135
|
+
fs_1.default.symlinkSync(path_1.default.join(harnessDir, entry.name), path_1.default.join(tempHarnessDir, entry.name));
|
|
136
|
+
}
|
|
137
|
+
catch { /* skip */ }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Create skills/commands/rules/agents directories with only specified agents
|
|
141
|
+
const contentDirs = ['skills', 'commands', 'rules', 'agents'];
|
|
142
|
+
for (const dir of contentDirs) {
|
|
143
|
+
const targetDir = path_1.default.join(tempHarnessDir, dir);
|
|
144
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
145
|
+
for (const { dir: agentDir } of agentDirs) {
|
|
146
|
+
const srcDir = path_1.default.join(agentDir, dir);
|
|
147
|
+
if (!fs_1.default.existsSync(srcDir))
|
|
148
|
+
continue;
|
|
149
|
+
for (const entry of fs_1.default.readdirSync(srcDir, { withFileTypes: true })) {
|
|
150
|
+
if (entry.name.startsWith('.'))
|
|
151
|
+
continue;
|
|
152
|
+
const srcPath = path_1.default.join(srcDir, entry.name);
|
|
153
|
+
const destPath = path_1.default.join(targetDir, entry.name);
|
|
154
|
+
try {
|
|
155
|
+
fs_1.default.symlinkSync(srcPath, destPath);
|
|
156
|
+
}
|
|
157
|
+
catch { /* skip duplicates */ }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!json) {
|
|
162
|
+
console.error(`\x1b[2manpm run: launching ${mapping.name} with ${agentDirs.length} agent(s)\x1b[0m`);
|
|
163
|
+
for (const { slug } of agentDirs) {
|
|
164
|
+
console.error(` \x1b[36m${slug}\x1b[0m`);
|
|
165
|
+
}
|
|
166
|
+
console.error('');
|
|
167
|
+
}
|
|
168
|
+
// Launch harness with HOME override
|
|
169
|
+
const child = (0, child_process_1.spawn)(mapping.command, passthroughArgs, {
|
|
170
|
+
cwd: projectPath,
|
|
171
|
+
env: { ...process.env, HOME: tempHome },
|
|
172
|
+
stdio: 'inherit',
|
|
173
|
+
});
|
|
174
|
+
await new Promise((resolve, reject) => {
|
|
175
|
+
child.on('close', (code) => {
|
|
176
|
+
if (code && code !== 0)
|
|
177
|
+
reject(new Error(`${mapping.command} exited with code ${code}`));
|
|
178
|
+
else
|
|
179
|
+
resolve();
|
|
180
|
+
});
|
|
181
|
+
child.on('error', (err) => {
|
|
182
|
+
reject(new Error(`Failed to launch ${mapping.command}: ${err.message}`));
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
// Cleanup temp directory
|
|
188
|
+
try {
|
|
189
|
+
fs_1.default.rmSync(tempHome, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
catch { /* best effort */ }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function launchGuiHarness(mapping, agentDirs, projectPath, _passthroughArgs, json) {
|
|
195
|
+
// GUI harnesses: manipulate project-local harness directory
|
|
196
|
+
const localHarnessDir = path_1.default.join(projectPath, mapping.skillsDir);
|
|
197
|
+
if (!json) {
|
|
198
|
+
console.error(`\x1b[33m⚠ GUI harness (${mapping.name}): full isolation not supported.\x1b[0m`);
|
|
199
|
+
console.error(` Setting up project-local ${mapping.skillsDir}/ with specified agents.`);
|
|
200
|
+
console.error(` Global agents may still be visible.\n`);
|
|
201
|
+
}
|
|
202
|
+
const contentDirs = ['skills', 'commands', 'rules', 'agents'];
|
|
203
|
+
const created = [];
|
|
204
|
+
for (const dir of contentDirs) {
|
|
205
|
+
const targetDir = path_1.default.join(localHarnessDir, dir);
|
|
206
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
207
|
+
for (const { dir: agentDir } of agentDirs) {
|
|
208
|
+
const srcDir = path_1.default.join(agentDir, dir);
|
|
209
|
+
if (!fs_1.default.existsSync(srcDir))
|
|
210
|
+
continue;
|
|
211
|
+
for (const entry of fs_1.default.readdirSync(srcDir, { withFileTypes: true })) {
|
|
212
|
+
if (entry.name.startsWith('.'))
|
|
213
|
+
continue;
|
|
214
|
+
const srcPath = path_1.default.join(srcDir, entry.name);
|
|
215
|
+
const destPath = path_1.default.join(targetDir, entry.name);
|
|
216
|
+
if (fs_1.default.existsSync(destPath))
|
|
217
|
+
continue;
|
|
218
|
+
try {
|
|
219
|
+
const rel = path_1.default.relative(path_1.default.dirname(destPath), srcPath);
|
|
220
|
+
fs_1.default.symlinkSync(rel, destPath);
|
|
221
|
+
created.push(destPath);
|
|
222
|
+
}
|
|
223
|
+
catch { /* skip */ }
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (json) {
|
|
228
|
+
console.log(JSON.stringify({ status: 'ok', harness: mapping.name, symlinks: created.length, note: 'GUI harness - partial isolation' }));
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
console.log(`\x1b[32m✓\x1b[0m Set up ${created.length} symlinks in ${mapping.skillsDir}/`);
|
|
232
|
+
console.log(` Open ${mapping.name} to use the configured agents.`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function loadProfile(name) {
|
|
236
|
+
const profilePath = path_1.default.join(os_1.default.homedir(), '.relay', 'profiles', `${name}.yaml`);
|
|
237
|
+
if (!fs_1.default.existsSync(profilePath)) {
|
|
238
|
+
throw new Error(`Profile not found: ${profilePath}`);
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
242
|
+
const yaml = require('js-yaml');
|
|
243
|
+
const raw = yaml.load(fs_1.default.readFileSync(profilePath, 'utf-8'));
|
|
244
|
+
return raw?.agents ?? [];
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
throw new Error(`Failed to parse profile: ${profilePath}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerSearch = registerSearch;
|
|
4
|
+
const api_js_1 = require("../lib/api.js");
|
|
5
|
+
const step_tracker_js_1 = require("../lib/step-tracker.js");
|
|
6
|
+
function formatTable(results) {
|
|
7
|
+
if (results.length === 0)
|
|
8
|
+
return '검색 결과가 없습니다.';
|
|
9
|
+
const rows = results.map((r) => ({
|
|
10
|
+
slug: r.slug,
|
|
11
|
+
name: r.name,
|
|
12
|
+
description: r.description.length > 50
|
|
13
|
+
? r.description.slice(0, 47) + '...'
|
|
14
|
+
: r.description,
|
|
15
|
+
installs: String(r.install_count),
|
|
16
|
+
commands: r.commands.map((c) => typeof c === 'string' ? c : c.name).join(', ') || '-',
|
|
17
|
+
}));
|
|
18
|
+
const cols = ['slug', 'name', 'description', 'installs', 'commands'];
|
|
19
|
+
const widths = cols.map((col) => Math.max(col.length, ...rows.map((r) => r[col].length)));
|
|
20
|
+
const header = cols
|
|
21
|
+
.map((col, i) => col.padEnd(widths[i]))
|
|
22
|
+
.join(' ');
|
|
23
|
+
const separator = widths.map((w) => '-'.repeat(w)).join(' ');
|
|
24
|
+
const lines = rows.map((row) => cols.map((col, i) => row[col].padEnd(widths[i])).join(' '));
|
|
25
|
+
return ['\x1b[1m' + header + '\x1b[0m', separator, ...lines].join('\n');
|
|
26
|
+
}
|
|
27
|
+
function registerSearch(program) {
|
|
28
|
+
program
|
|
29
|
+
.command('search <keyword>')
|
|
30
|
+
.description('Space에서 에이전트 검색 (공개 에이전트 + 내 Space 에이전트)')
|
|
31
|
+
.option('--tag <tag>', '태그로 필터링')
|
|
32
|
+
.option('--space <space>', '특정 Space 내에서 검색')
|
|
33
|
+
.action(async (keyword, opts) => {
|
|
34
|
+
const json = program.opts().json ?? false;
|
|
35
|
+
(0, step_tracker_js_1.trackCommand)('search', { slug: keyword });
|
|
36
|
+
try {
|
|
37
|
+
const results = await (0, api_js_1.searchAgents)(keyword, opts.tag);
|
|
38
|
+
if (json) {
|
|
39
|
+
console.log(JSON.stringify({ results }));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const spaceSuffix = opts.space ? ` Space: \x1b[35m@${opts.space}\x1b[0m` : '';
|
|
43
|
+
console.log(`\n검색어: \x1b[36m${keyword}\x1b[0m${opts.tag ? ` 태그: \x1b[33m${opts.tag}\x1b[0m` : ''}${spaceSuffix}\n`);
|
|
44
|
+
console.log(formatTable(results));
|
|
45
|
+
console.log(`\n총 ${results.length}건`);
|
|
46
|
+
if (!opts.space && results.length === 0) {
|
|
47
|
+
console.log('\x1b[33m💡 내 Space에서 검색하려면: anpm search <keyword> --space <space-slug>\x1b[0m');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53
|
+
console.error(JSON.stringify({ error: 'SEARCH_FAILED', message, fix: '검색어를 변경하거나 잠시 후 재시도하세요.' }));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
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.registerStatus = registerStatus;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
10
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
11
|
+
const config_js_1 = require("../lib/config.js");
|
|
12
|
+
const command_adapter_js_1 = require("../lib/command-adapter.js");
|
|
13
|
+
const agent_status_js_1 = require("../lib/agent-status.js");
|
|
14
|
+
async function resolveUsername(token) {
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(`${config_js_1.API_URL}/api/auth/me`, {
|
|
17
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
18
|
+
});
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
return undefined;
|
|
21
|
+
const body = await res.json();
|
|
22
|
+
return body.username;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function registerStatus(program) {
|
|
29
|
+
program
|
|
30
|
+
.command('status')
|
|
31
|
+
.description('현재 anpm 환경 상태를 표시합니다')
|
|
32
|
+
.option('--project <dir>', '프로젝트 루트 경로 (기본: cwd, 환경변수: RELAY_PROJECT_PATH)')
|
|
33
|
+
.action(async (opts) => {
|
|
34
|
+
const json = program.opts().json ?? false;
|
|
35
|
+
const projectPath = (0, paths_js_1.resolveProjectPath)(opts.project);
|
|
36
|
+
// 1. 로그인 상태
|
|
37
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
38
|
+
let username;
|
|
39
|
+
if (token) {
|
|
40
|
+
username = await resolveUsername(token);
|
|
41
|
+
}
|
|
42
|
+
// 2. 에이전트 감지
|
|
43
|
+
const detected = (0, ai_tools_js_1.detectAgentCLIs)(projectPath);
|
|
44
|
+
const primaryAgent = detected.length > 0 ? detected[0] : null;
|
|
45
|
+
// 글로벌 커맨드 상태
|
|
46
|
+
const hasGlobal = command_adapter_js_1.USER_COMMANDS.every((cmd) => fs_1.default.existsSync((0, command_adapter_js_1.getGlobalCommandPath)(cmd.id)));
|
|
47
|
+
// 로컬 Builder 커맨드 상태
|
|
48
|
+
let hasLocal = false;
|
|
49
|
+
if (primaryAgent) {
|
|
50
|
+
const localDir = path_1.default.join(projectPath, primaryAgent.skillsDir, 'commands', 'relay');
|
|
51
|
+
hasLocal = command_adapter_js_1.BUILDER_COMMANDS.some((cmd) => fs_1.default.existsSync(path_1.default.join(localDir, `${cmd.id}.md`)));
|
|
52
|
+
}
|
|
53
|
+
// 3. 에이전트 프로젝트 정보
|
|
54
|
+
const relayYamlPath = path_1.default.join(projectPath, '.relay', 'relay.yaml');
|
|
55
|
+
let project = null;
|
|
56
|
+
if (fs_1.default.existsSync(relayYamlPath)) {
|
|
57
|
+
try {
|
|
58
|
+
const yaml = await import('js-yaml');
|
|
59
|
+
const content = fs_1.default.readFileSync(relayYamlPath, 'utf-8');
|
|
60
|
+
const raw = yaml.load(content);
|
|
61
|
+
project = {
|
|
62
|
+
is_agent: true,
|
|
63
|
+
name: String(raw.name ?? ''),
|
|
64
|
+
slug: String(raw.slug ?? ''),
|
|
65
|
+
version: String(raw.version ?? ''),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
project = { is_agent: true };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
project = { is_agent: false };
|
|
74
|
+
}
|
|
75
|
+
// 4. Installed agents status
|
|
76
|
+
const agentEntries = (0, agent_status_js_1.getAgentStatusEntries)();
|
|
77
|
+
const unmanagedItems = (0, agent_status_js_1.findUnmanagedContent)(projectPath);
|
|
78
|
+
// 5. 출력
|
|
79
|
+
if (json) {
|
|
80
|
+
const result = {
|
|
81
|
+
login: { authenticated: !!token, username },
|
|
82
|
+
agent: {
|
|
83
|
+
detected: primaryAgent?.name ?? null,
|
|
84
|
+
global_commands: hasGlobal,
|
|
85
|
+
local_commands: hasLocal,
|
|
86
|
+
},
|
|
87
|
+
project,
|
|
88
|
+
installed_agents: agentEntries,
|
|
89
|
+
unmanaged: unmanagedItems,
|
|
90
|
+
};
|
|
91
|
+
console.log(JSON.stringify(result));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.log('');
|
|
95
|
+
// Login
|
|
96
|
+
if (token && username) {
|
|
97
|
+
console.log(` \x1b[32m✓\x1b[0m Login: \x1b[36m${username}\x1b[0m`);
|
|
98
|
+
}
|
|
99
|
+
else if (token) {
|
|
100
|
+
console.log(` \x1b[32m✓\x1b[0m Login: authenticated`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.log(` \x1b[31m✗\x1b[0m Login: not authenticated (\x1b[33manpm login\x1b[0m)`);
|
|
104
|
+
}
|
|
105
|
+
// Harness detection
|
|
106
|
+
if (primaryAgent) {
|
|
107
|
+
const globalLabel = hasGlobal ? '\x1b[32mglobal ✓\x1b[0m' : '\x1b[31mglobal ✗\x1b[0m';
|
|
108
|
+
const localLabel = hasLocal ? '\x1b[32mlocal ✓\x1b[0m' : '\x1b[2mlocal —\x1b[0m';
|
|
109
|
+
console.log(` \x1b[32m✓\x1b[0m Harness: \x1b[36m${primaryAgent.name}\x1b[0m (${globalLabel} ${localLabel})`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.log(` \x1b[31m✗\x1b[0m Harness: not detected`);
|
|
113
|
+
}
|
|
114
|
+
// Agent project
|
|
115
|
+
if (project?.is_agent && project.name) {
|
|
116
|
+
console.log(` \x1b[32m✓\x1b[0m Project: \x1b[36m${project.name}\x1b[0m v${project.version}`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(` \x1b[2m—\x1b[0m Project: not an agent`);
|
|
120
|
+
}
|
|
121
|
+
// Installed agents table
|
|
122
|
+
if (agentEntries.length > 0) {
|
|
123
|
+
console.log(`\n \x1b[1mInstalled agents (${agentEntries.length}):\x1b[0m`);
|
|
124
|
+
for (const entry of agentEntries) {
|
|
125
|
+
const statusIcon = entry.status === 'active' ? '✅' : entry.status === 'broken' ? '⚠️' : '—';
|
|
126
|
+
const sourceLabel = entry.source.startsWith('registry') ? 'registry'
|
|
127
|
+
: entry.source.startsWith('local:') ? 'local'
|
|
128
|
+
: entry.source.startsWith('git:') ? 'git'
|
|
129
|
+
: entry.source.startsWith('link:') ? 'link'
|
|
130
|
+
: entry.source.startsWith('adopted:') ? 'adopted'
|
|
131
|
+
: entry.source;
|
|
132
|
+
const harnessNames = entry.harnesses.length > 0 ? entry.harnesses.join(', ') : '—';
|
|
133
|
+
console.log(` ${statusIcon} \x1b[36m${entry.slug}\x1b[0m \x1b[90m${sourceLabel}\x1b[0m v${entry.version} → ${harnessNames}`);
|
|
134
|
+
}
|
|
135
|
+
// Broken symlink warnings
|
|
136
|
+
const brokenEntries = agentEntries.filter((e) => e.brokenSymlinks.length > 0);
|
|
137
|
+
if (brokenEntries.length > 0) {
|
|
138
|
+
console.log('');
|
|
139
|
+
for (const entry of brokenEntries) {
|
|
140
|
+
console.log(` \x1b[33m⚠️ ${entry.slug}: ${entry.brokenSymlinks.length} broken symlink(s)\x1b[0m`);
|
|
141
|
+
console.log(` Run: anpm uninstall ${entry.slug}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Unmanaged content
|
|
146
|
+
if (unmanagedItems.length > 0) {
|
|
147
|
+
console.log(`\n \x1b[1mUnmanaged content (${unmanagedItems.length}):\x1b[0m`);
|
|
148
|
+
for (const item of unmanagedItems.slice(0, 10)) {
|
|
149
|
+
console.log(` \x1b[33m⚠️\x1b[0m ${item.type}/${item.name} \x1b[90m(${item.harness})\x1b[0m`);
|
|
150
|
+
}
|
|
151
|
+
if (unmanagedItems.length > 10) {
|
|
152
|
+
console.log(` \x1b[90m...and ${unmanagedItems.length - 10} more\x1b[0m`);
|
|
153
|
+
}
|
|
154
|
+
console.log(`\n \x1b[90mTip: anpm adopt <path> to manage with anpm\x1b[0m`);
|
|
155
|
+
}
|
|
156
|
+
console.log('');
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
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.registerUninstall = registerUninstall;
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const config_js_1 = require("../lib/config.js");
|
|
10
|
+
const installer_js_1 = require("../lib/installer.js");
|
|
11
|
+
const slug_js_1 = require("../lib/slug.js");
|
|
12
|
+
const ai_tools_js_1 = require("../lib/ai-tools.js");
|
|
13
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
14
|
+
/**
|
|
15
|
+
* deployed_files에서 에이전트 설정 디렉토리(skillsDir) 기반 boundary를 추론한다.
|
|
16
|
+
* 예: deployed_files에 '~/.cursor/commands/relay/x.md'가 있으면 boundary는 basePath/.cursor
|
|
17
|
+
*/
|
|
18
|
+
function inferBoundary(deployedFiles, basePath) {
|
|
19
|
+
const skillsDirs = ai_tools_js_1.AI_TOOLS.map((t) => t.skillsDir);
|
|
20
|
+
for (const f of deployedFiles) {
|
|
21
|
+
for (const sd of skillsDirs) {
|
|
22
|
+
const prefix = path_1.default.join(basePath, sd);
|
|
23
|
+
if (f.startsWith(prefix)) {
|
|
24
|
+
return prefix;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// fallback: 첫 번째 파일의 상위 디렉토리 중 basePath 직속 디렉토리
|
|
29
|
+
return path_1.default.join(basePath, '.claude');
|
|
30
|
+
}
|
|
31
|
+
function registerUninstall(program) {
|
|
32
|
+
program
|
|
33
|
+
.command('uninstall <slug>')
|
|
34
|
+
.description('에이전트 제거')
|
|
35
|
+
.option('--project <dir>', '프로젝트 루트 경로 (기본: cwd, 환경변수: RELAY_PROJECT_PATH)')
|
|
36
|
+
.action((slugInput, _opts) => {
|
|
37
|
+
const json = program.opts().json ?? false;
|
|
38
|
+
const localInstalled = (0, config_js_1.loadInstalled)();
|
|
39
|
+
const globalInstalled = (0, config_js_1.loadGlobalInstalled)();
|
|
40
|
+
// Resolve slug — support short names like "cardnews-agent"
|
|
41
|
+
let slug;
|
|
42
|
+
if ((0, slug_js_1.isScopedSlug)(slugInput)) {
|
|
43
|
+
slug = slugInput;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const allKeys = [...Object.keys(localInstalled), ...Object.keys(globalInstalled)];
|
|
47
|
+
const match = allKeys.find((key) => {
|
|
48
|
+
const parsed = (0, slug_js_1.parseSlug)(key);
|
|
49
|
+
return parsed && parsed.name === slugInput;
|
|
50
|
+
});
|
|
51
|
+
slug = match ?? slugInput;
|
|
52
|
+
}
|
|
53
|
+
const localEntry = localInstalled[slug];
|
|
54
|
+
const globalEntry = globalInstalled[slug];
|
|
55
|
+
if (!localEntry && !globalEntry) {
|
|
56
|
+
const msg = { error: 'NOT_INSTALLED', message: `'${slugInput}'는 설치되어 있지 않습니다.` };
|
|
57
|
+
if (json) {
|
|
58
|
+
console.error(JSON.stringify(msg));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.error(`\x1b[31m오류:\x1b[0m ${msg.message}`);
|
|
62
|
+
}
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
let totalRemoved = 0;
|
|
66
|
+
// Remove from local registry
|
|
67
|
+
if (localEntry) {
|
|
68
|
+
const removed = (0, installer_js_1.uninstallAgent)(localEntry.files);
|
|
69
|
+
totalRemoved += removed.length;
|
|
70
|
+
// Remove deployed symlinks (new)
|
|
71
|
+
if (localEntry.deployed_symlinks && localEntry.deployed_symlinks.length > 0) {
|
|
72
|
+
const symlinkRemoved = (0, installer_js_1.removeSymlinks)(localEntry.deployed_symlinks);
|
|
73
|
+
totalRemoved += symlinkRemoved.length;
|
|
74
|
+
const boundary = inferBoundary(localEntry.deployed_symlinks, (0, paths_js_1.resolveProjectPath)(_opts.project));
|
|
75
|
+
for (const f of symlinkRemoved) {
|
|
76
|
+
(0, installer_js_1.cleanEmptyParents)(f, boundary);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Remove deployed files (legacy)
|
|
80
|
+
if (localEntry.deployed_files && localEntry.deployed_files.length > 0) {
|
|
81
|
+
const deployedRemoved = (0, installer_js_1.uninstallAgent)(localEntry.deployed_files);
|
|
82
|
+
totalRemoved += deployedRemoved.length;
|
|
83
|
+
const boundary = inferBoundary(localEntry.deployed_files, (0, paths_js_1.resolveProjectPath)(_opts.project));
|
|
84
|
+
for (const f of deployedRemoved) {
|
|
85
|
+
(0, installer_js_1.cleanEmptyParents)(f, boundary);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
delete localInstalled[slug];
|
|
89
|
+
(0, config_js_1.saveInstalled)(localInstalled);
|
|
90
|
+
}
|
|
91
|
+
// Remove from global registry
|
|
92
|
+
if (globalEntry) {
|
|
93
|
+
// Only remove files if not already handled by local entry
|
|
94
|
+
if (!localEntry) {
|
|
95
|
+
const removed = (0, installer_js_1.uninstallAgent)(globalEntry.files);
|
|
96
|
+
totalRemoved += removed.length;
|
|
97
|
+
}
|
|
98
|
+
// Remove deployed symlinks (new)
|
|
99
|
+
if (globalEntry.deployed_symlinks && globalEntry.deployed_symlinks.length > 0) {
|
|
100
|
+
const symlinkRemoved = (0, installer_js_1.removeSymlinks)(globalEntry.deployed_symlinks);
|
|
101
|
+
totalRemoved += symlinkRemoved.length;
|
|
102
|
+
const boundary = inferBoundary(globalEntry.deployed_symlinks, os_1.default.homedir());
|
|
103
|
+
for (const f of symlinkRemoved) {
|
|
104
|
+
(0, installer_js_1.cleanEmptyParents)(f, boundary);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Remove globally deployed files (legacy)
|
|
108
|
+
if (globalEntry.deployed_files && globalEntry.deployed_files.length > 0) {
|
|
109
|
+
const deployedRemoved = (0, installer_js_1.uninstallAgent)(globalEntry.deployed_files);
|
|
110
|
+
totalRemoved += deployedRemoved.length;
|
|
111
|
+
const boundary = inferBoundary(globalEntry.deployed_files, os_1.default.homedir());
|
|
112
|
+
for (const f of deployedRemoved) {
|
|
113
|
+
(0, installer_js_1.cleanEmptyParents)(f, boundary);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
delete globalInstalled[slug];
|
|
117
|
+
(0, config_js_1.saveGlobalInstalled)(globalInstalled);
|
|
118
|
+
}
|
|
119
|
+
const result = {
|
|
120
|
+
status: 'ok',
|
|
121
|
+
agent: slug,
|
|
122
|
+
files_removed: totalRemoved,
|
|
123
|
+
};
|
|
124
|
+
if (json) {
|
|
125
|
+
console.log(JSON.stringify(result));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.log(`\n\x1b[32m✓ ${slug} 제거 완료\x1b[0m`);
|
|
129
|
+
console.log(` 삭제된 파일: ${totalRemoved}개`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|