@orchagent/cli 0.3.31 → 0.3.33
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/dist/adapters/agents-md.js +14 -1
- package/dist/adapters/claude-code.js +11 -0
- package/dist/adapters/cursor.js +13 -1
- package/dist/commands/call.js +78 -2
- package/dist/commands/config.js +25 -1
- package/dist/commands/delete.js +11 -20
- package/dist/commands/init.js +139 -22
- package/dist/commands/install.js +16 -3
- package/dist/commands/llm-config.js +40 -0
- package/dist/commands/publish.test.js +475 -0
- package/dist/commands/run.test.js +330 -0
- package/dist/commands/search.js +47 -22
- package/dist/commands/skill.js +10 -7
- package/dist/commands/update.js +101 -66
- package/dist/index.js +0 -0
- package/dist/lib/api.js +68 -2
- package/dist/lib/api.test.js +230 -0
- package/dist/lib/config.js +11 -0
- package/dist/lib/config.test.js +144 -0
- package/dist/lib/output.js +19 -12
- package/dist/lib/skill-resolve.js +99 -0
- package/package.json +1 -1
package/dist/commands/update.js
CHANGED
|
@@ -53,100 +53,135 @@ function registerUpdateCommand(program) {
|
|
|
53
53
|
process.stdout.write(`Agent "${agentRef}" is not installed.\n`);
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
// Group installed entries by agent name to avoid duplicates
|
|
57
|
+
// (same agent installed to multiple formats shows once)
|
|
58
|
+
const grouped = new Map();
|
|
59
|
+
for (const item of toCheck) {
|
|
60
|
+
const existing = grouped.get(item.agent);
|
|
61
|
+
if (existing) {
|
|
62
|
+
existing.push(item);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
grouped.set(item.agent, [item]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
process.stdout.write(`Checking ${grouped.size} installed agent(s) for updates...\n\n`);
|
|
57
69
|
let updatesAvailable = 0;
|
|
58
70
|
let updatesApplied = 0;
|
|
59
71
|
let skippedModified = 0;
|
|
60
72
|
let skippedMissing = 0;
|
|
61
|
-
for (const
|
|
62
|
-
// Check
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
for (const [agentName, entries] of grouped) {
|
|
74
|
+
// Check file status for all entries in this group
|
|
75
|
+
const fileStatuses = [];
|
|
76
|
+
for (const item of entries) {
|
|
77
|
+
fileStatuses.push({ item, status: await (0, installed_1.checkModified)(item) });
|
|
78
|
+
}
|
|
79
|
+
// Fetch latest version once per agent
|
|
80
|
+
const latest = await fetchLatestAgent(resolved, agentName);
|
|
66
81
|
if (!latest) {
|
|
67
|
-
process.stdout.write(` ${chalk_1.default.yellow('?')} ${
|
|
82
|
+
process.stdout.write(` ${chalk_1.default.yellow('?')} ${agentName} - could not fetch latest\n`);
|
|
68
83
|
continue;
|
|
69
84
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
// Use the version from the first entry (all entries for the same
|
|
86
|
+
// agent should share the same version after install/update)
|
|
87
|
+
const installedVersion = entries[0].version;
|
|
88
|
+
const hasUpdate = latest.latestVersion !== installedVersion;
|
|
89
|
+
const anyMissing = fileStatuses.some(f => f.status.missing);
|
|
90
|
+
const anyModified = fileStatuses.some(f => f.status.modified);
|
|
91
|
+
if (!hasUpdate && !anyModified && !anyMissing) {
|
|
92
|
+
const formatSuffix = entries.length > 1
|
|
93
|
+
? ` ${chalk_1.default.dim(`(${entries.map(e => e.format).join(', ')})`)}`
|
|
94
|
+
: '';
|
|
95
|
+
process.stdout.write(` ${chalk_1.default.green('✓')} ${agentName}@${installedVersion} - up to date${formatSuffix}\n`);
|
|
73
96
|
continue;
|
|
74
97
|
}
|
|
75
|
-
// Handle missing
|
|
76
|
-
if (
|
|
77
|
-
|
|
98
|
+
// Handle missing files without --force
|
|
99
|
+
if (anyMissing && !hasUpdate && !options.force) {
|
|
100
|
+
const missingFormats = fileStatuses.filter(f => f.status.missing).map(f => f.item.format);
|
|
101
|
+
process.stdout.write(` ${chalk_1.default.yellow('!')} ${agentName} - file missing [${missingFormats.join(', ')}] (use --force to reinstall)\n`);
|
|
78
102
|
skippedMissing++;
|
|
79
103
|
continue;
|
|
80
104
|
}
|
|
81
|
-
// Handle modified
|
|
82
|
-
if (
|
|
83
|
-
|
|
105
|
+
// Handle modified files without --force
|
|
106
|
+
if (anyModified && !hasUpdate && !options.force) {
|
|
107
|
+
const modifiedFormats = fileStatuses.filter(f => f.status.modified).map(f => f.item.format);
|
|
108
|
+
process.stdout.write(` ${chalk_1.default.yellow('!')} ${agentName} - local modifications [${modifiedFormats.join(', ')}] (use --force to overwrite)\n`);
|
|
84
109
|
skippedModified++;
|
|
85
110
|
continue;
|
|
86
111
|
}
|
|
87
|
-
if (hasUpdate ||
|
|
112
|
+
if (hasUpdate || anyMissing) {
|
|
88
113
|
if (hasUpdate) {
|
|
89
114
|
updatesAvailable++;
|
|
90
115
|
}
|
|
91
|
-
process.stdout.write(` ${chalk_1.default.blue('↑')} ${
|
|
92
|
-
if (
|
|
116
|
+
process.stdout.write(` ${chalk_1.default.blue('↑')} ${agentName}@${installedVersion} → ${latest.latestVersion}`);
|
|
117
|
+
if (anyModified) {
|
|
93
118
|
process.stdout.write(` ${chalk_1.default.yellow('(modified)')}`);
|
|
94
119
|
}
|
|
95
|
-
if (
|
|
120
|
+
if (anyMissing) {
|
|
96
121
|
process.stdout.write(` ${chalk_1.default.yellow('(reinstalling)')}`);
|
|
97
122
|
}
|
|
98
123
|
process.stdout.write('\n');
|
|
99
124
|
if (options.check) {
|
|
100
125
|
continue;
|
|
101
126
|
}
|
|
102
|
-
// Apply update
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
// Apply update to each format entry
|
|
128
|
+
for (const { item, status: fileStatus } of fileStatuses) {
|
|
129
|
+
// Skip unmodified and non-missing entries if no version update
|
|
130
|
+
if (!hasUpdate && !fileStatus.missing)
|
|
131
|
+
continue;
|
|
132
|
+
// Skip modified entries without --force
|
|
133
|
+
if (fileStatus.modified && !options.force) {
|
|
134
|
+
process.stderr.write(` Skipped ${item.format}: local modifications (use --force)\n`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const adapter = adapters_1.adapterRegistry.get(item.format);
|
|
138
|
+
if (!adapter) {
|
|
139
|
+
process.stderr.write(` Skipped ${item.format}: unknown format\n`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const checkResult = adapter.canConvert(latest.agent);
|
|
143
|
+
if (!checkResult.canConvert) {
|
|
144
|
+
process.stderr.write(` Skipped ${item.format}: ${checkResult.errors.join(', ')}\n`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const files = adapter.convert(latest.agent);
|
|
148
|
+
if (files.length > 1) {
|
|
149
|
+
process.stderr.write(` Skipped ${item.format}: multi-file adapters not supported for update. Reinstall instead.\n`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
for (const file of files) {
|
|
153
|
+
// Use the original path from tracking
|
|
154
|
+
const fullPath = item.path;
|
|
155
|
+
try {
|
|
156
|
+
const dir = path_1.default.dirname(fullPath);
|
|
157
|
+
await promises_1.default.mkdir(dir, { recursive: true });
|
|
158
|
+
// Special handling for AGENTS.md - append/replace instead of overwrite
|
|
159
|
+
if (file.filename === 'AGENTS.md') {
|
|
160
|
+
let existingContent = '';
|
|
161
|
+
try {
|
|
162
|
+
existingContent = await promises_1.default.readFile(fullPath, 'utf-8');
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// File doesn't exist, will create new
|
|
166
|
+
}
|
|
167
|
+
file.content = (0, agents_md_utils_1.mergeAgentsMdContent)(existingContent, file.content, item.agent);
|
|
132
168
|
}
|
|
133
|
-
|
|
169
|
+
await promises_1.default.writeFile(fullPath, file.content);
|
|
170
|
+
// Update tracking
|
|
171
|
+
const updatedItem = {
|
|
172
|
+
...item,
|
|
173
|
+
version: latest.latestVersion,
|
|
174
|
+
installedAt: new Date().toISOString(),
|
|
175
|
+
adapterVersion: adapter.version,
|
|
176
|
+
contentHash: (0, installed_1.computeHash)(file.content),
|
|
177
|
+
};
|
|
178
|
+
await (0, installed_1.trackInstall)(updatedItem);
|
|
179
|
+
process.stdout.write(` Updated: ${fullPath}\n`);
|
|
180
|
+
updatesApplied++;
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
process.stderr.write(` Error (${item.format}): ${err.message}\n`);
|
|
134
184
|
}
|
|
135
|
-
await promises_1.default.writeFile(fullPath, file.content);
|
|
136
|
-
// Update tracking
|
|
137
|
-
const updatedItem = {
|
|
138
|
-
...item,
|
|
139
|
-
version: latest.latestVersion,
|
|
140
|
-
installedAt: new Date().toISOString(),
|
|
141
|
-
adapterVersion: adapter.version,
|
|
142
|
-
contentHash: (0, installed_1.computeHash)(file.content),
|
|
143
|
-
};
|
|
144
|
-
await (0, installed_1.trackInstall)(updatedItem);
|
|
145
|
-
process.stdout.write(` Updated: ${fullPath}\n`);
|
|
146
|
-
updatesApplied++;
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
process.stderr.write(` Error: ${err.message}\n`);
|
|
150
185
|
}
|
|
151
186
|
}
|
|
152
187
|
}
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/lib/api.js
CHANGED
|
@@ -48,6 +48,7 @@ exports.starAgent = starAgent;
|
|
|
48
48
|
exports.unstarAgent = unstarAgent;
|
|
49
49
|
exports.forkAgent = forkAgent;
|
|
50
50
|
exports.searchAgents = searchAgents;
|
|
51
|
+
exports.searchMyAgents = searchMyAgents;
|
|
51
52
|
exports.fetchLlmKeys = fetchLlmKeys;
|
|
52
53
|
exports.downloadCodeBundle = downloadCodeBundle;
|
|
53
54
|
exports.uploadCodeBundle = uploadCodeBundle;
|
|
@@ -233,8 +234,14 @@ async function updateOrg(config, payload) {
|
|
|
233
234
|
headers: { 'Content-Type': 'application/json' },
|
|
234
235
|
});
|
|
235
236
|
}
|
|
236
|
-
async function listPublicAgents(config) {
|
|
237
|
-
|
|
237
|
+
async function listPublicAgents(config, options) {
|
|
238
|
+
const params = new URLSearchParams();
|
|
239
|
+
if (options?.sort)
|
|
240
|
+
params.append('sort', options.sort);
|
|
241
|
+
if (options?.type)
|
|
242
|
+
params.append('type', options.type);
|
|
243
|
+
const queryStr = params.toString();
|
|
244
|
+
return publicRequest(config, `/public/agents${queryStr ? `?${queryStr}` : ''}`);
|
|
238
245
|
}
|
|
239
246
|
async function getPublicAgent(config, org, agent, version) {
|
|
240
247
|
return publicRequest(config, `/public/agents/${org}/${agent}/${version}`);
|
|
@@ -266,9 +273,68 @@ async function searchAgents(config, query, options) {
|
|
|
266
273
|
params.append('sort', options.sort);
|
|
267
274
|
if (options?.tags?.length)
|
|
268
275
|
params.append('tags', options.tags.join(','));
|
|
276
|
+
if (options?.type)
|
|
277
|
+
params.append('type', options.type);
|
|
269
278
|
const queryStr = params.toString();
|
|
270
279
|
return publicRequest(config, `/public/agents${queryStr ? `?${queryStr}` : ''}`);
|
|
271
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Search within the authenticated user's own agents (public and private).
|
|
283
|
+
* Uses the authenticated /agents endpoint with client-side filtering.
|
|
284
|
+
*/
|
|
285
|
+
async function searchMyAgents(config, query, options) {
|
|
286
|
+
let agents = await listMyAgents(config);
|
|
287
|
+
// Deduplicate: keep only latest version per agent name
|
|
288
|
+
const latestByName = new Map();
|
|
289
|
+
for (const agent of agents) {
|
|
290
|
+
const existing = latestByName.get(agent.name);
|
|
291
|
+
if (!existing || new Date(agent.created_at) > new Date(existing.created_at)) {
|
|
292
|
+
latestByName.set(agent.name, agent);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
agents = Array.from(latestByName.values());
|
|
296
|
+
// Apply type filter
|
|
297
|
+
if (options?.type) {
|
|
298
|
+
const typeFilter = options.type;
|
|
299
|
+
if (typeFilter === 'agents') {
|
|
300
|
+
agents = agents.filter(a => a.type === 'prompt' || a.type === 'code');
|
|
301
|
+
}
|
|
302
|
+
else if (typeFilter === 'skills' || typeFilter === 'skill') {
|
|
303
|
+
agents = agents.filter(a => a.type === 'skill');
|
|
304
|
+
}
|
|
305
|
+
else if (typeFilter === 'code' || typeFilter === 'prompt') {
|
|
306
|
+
agents = agents.filter(a => a.type === typeFilter);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Apply search filter (match against name and description)
|
|
310
|
+
if (query) {
|
|
311
|
+
const words = query.toLowerCase().replace(/-/g, ' ').split(/\s+/);
|
|
312
|
+
agents = agents.filter(a => {
|
|
313
|
+
const name = (a.name || '').toLowerCase().replace(/-/g, ' ');
|
|
314
|
+
const desc = (a.description || '').toLowerCase();
|
|
315
|
+
return words.every(w => name.includes(w) || desc.includes(w));
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
// Map Agent to PublicAgent-compatible objects
|
|
319
|
+
const org = await getOrg(config);
|
|
320
|
+
return agents.map(a => ({
|
|
321
|
+
id: a.id,
|
|
322
|
+
org_name: org.name || org.slug || 'unknown',
|
|
323
|
+
org_slug: a.org_slug || org.slug || 'unknown',
|
|
324
|
+
name: a.name,
|
|
325
|
+
version: a.version,
|
|
326
|
+
type: a.type,
|
|
327
|
+
description: a.description,
|
|
328
|
+
stars_count: a.stars_count ?? 0,
|
|
329
|
+
tags: a.tags ?? [],
|
|
330
|
+
default_endpoint: a.default_endpoint || 'analyze',
|
|
331
|
+
created_at: a.created_at,
|
|
332
|
+
supported_providers: a.supported_providers ?? ['any'],
|
|
333
|
+
is_public: a.is_public,
|
|
334
|
+
pricing_mode: a.pricing_mode,
|
|
335
|
+
price_per_call_cents: a.price_per_call_cents,
|
|
336
|
+
}));
|
|
337
|
+
}
|
|
272
338
|
async function fetchLlmKeys(config) {
|
|
273
339
|
const result = await request(config, 'GET', '/llm-keys/export');
|
|
274
340
|
return result.keys;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for API client functions.
|
|
4
|
+
*
|
|
5
|
+
* These tests cover the core API client that all CLI commands depend on:
|
|
6
|
+
* - Request building and authentication
|
|
7
|
+
* - Error parsing
|
|
8
|
+
* - Public vs authenticated requests
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
const vitest_1 = require("vitest");
|
|
12
|
+
const api_1 = require("./api");
|
|
13
|
+
// Mock fetch globally
|
|
14
|
+
const mockFetch = vitest_1.vi.fn();
|
|
15
|
+
global.fetch = mockFetch;
|
|
16
|
+
(0, vitest_1.describe)('ApiError', () => {
|
|
17
|
+
(0, vitest_1.it)('includes status code and message', () => {
|
|
18
|
+
const error = new api_1.ApiError('Not found', 404);
|
|
19
|
+
(0, vitest_1.expect)(error.message).toBe('Not found');
|
|
20
|
+
(0, vitest_1.expect)(error.status).toBe(404);
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)('includes optional payload', () => {
|
|
23
|
+
const payload = { error: { code: 'NOT_FOUND' } };
|
|
24
|
+
const error = new api_1.ApiError('Not found', 404, payload);
|
|
25
|
+
(0, vitest_1.expect)(error.payload).toEqual(payload);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
(0, vitest_1.describe)('request', () => {
|
|
29
|
+
const config = {
|
|
30
|
+
apiKey: 'sk_test_123',
|
|
31
|
+
apiUrl: 'https://api.test.com',
|
|
32
|
+
};
|
|
33
|
+
(0, vitest_1.beforeEach)(() => {
|
|
34
|
+
mockFetch.mockReset();
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.afterEach)(() => {
|
|
37
|
+
vitest_1.vi.restoreAllMocks();
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)('adds Authorization header with Bearer token', async () => {
|
|
40
|
+
mockFetch.mockResolvedValueOnce({
|
|
41
|
+
ok: true,
|
|
42
|
+
json: () => Promise.resolve({ data: 'test' }),
|
|
43
|
+
});
|
|
44
|
+
await (0, api_1.request)(config, 'GET', '/test');
|
|
45
|
+
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('https://api.test.com/test', vitest_1.expect.objectContaining({
|
|
46
|
+
method: 'GET',
|
|
47
|
+
headers: vitest_1.expect.objectContaining({
|
|
48
|
+
Authorization: 'Bearer sk_test_123',
|
|
49
|
+
}),
|
|
50
|
+
}));
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.it)('returns parsed JSON response', async () => {
|
|
53
|
+
const responseData = { org: 'test', slug: 'test-org' };
|
|
54
|
+
mockFetch.mockResolvedValueOnce({
|
|
55
|
+
ok: true,
|
|
56
|
+
json: () => Promise.resolve(responseData),
|
|
57
|
+
});
|
|
58
|
+
const result = await (0, api_1.request)(config, 'GET', '/org');
|
|
59
|
+
(0, vitest_1.expect)(result).toEqual(responseData);
|
|
60
|
+
});
|
|
61
|
+
(0, vitest_1.it)('throws ApiError when response not ok', async () => {
|
|
62
|
+
mockFetch.mockResolvedValueOnce({
|
|
63
|
+
ok: false,
|
|
64
|
+
status: 404,
|
|
65
|
+
statusText: 'Not Found',
|
|
66
|
+
text: () => Promise.resolve(JSON.stringify({
|
|
67
|
+
error: { message: 'Agent not found' }
|
|
68
|
+
})),
|
|
69
|
+
});
|
|
70
|
+
await (0, vitest_1.expect)((0, api_1.request)(config, 'GET', '/agents/missing'))
|
|
71
|
+
.rejects.toThrow(api_1.ApiError);
|
|
72
|
+
});
|
|
73
|
+
(0, vitest_1.it)('throws ApiError with 401 when no API key', async () => {
|
|
74
|
+
const noKeyConfig = {
|
|
75
|
+
apiKey: undefined,
|
|
76
|
+
apiUrl: 'https://api.test.com',
|
|
77
|
+
};
|
|
78
|
+
await (0, vitest_1.expect)((0, api_1.request)(noKeyConfig, 'GET', '/test'))
|
|
79
|
+
.rejects.toThrow('Missing API key');
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.it)('parses error message from JSON response', async () => {
|
|
82
|
+
mockFetch.mockResolvedValueOnce({
|
|
83
|
+
ok: false,
|
|
84
|
+
status: 403,
|
|
85
|
+
statusText: 'Forbidden',
|
|
86
|
+
text: () => Promise.resolve(JSON.stringify({
|
|
87
|
+
error: { message: 'Access denied to private agent' }
|
|
88
|
+
})),
|
|
89
|
+
});
|
|
90
|
+
try {
|
|
91
|
+
await (0, api_1.request)(config, 'GET', '/agents/private');
|
|
92
|
+
vitest_1.expect.fail('Should have thrown');
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
(0, vitest_1.expect)(error).toBeInstanceOf(api_1.ApiError);
|
|
96
|
+
(0, vitest_1.expect)(error.message).toBe('Access denied to private agent');
|
|
97
|
+
(0, vitest_1.expect)(error.status).toBe(403);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
(0, vitest_1.it)('uses statusText when no JSON message', async () => {
|
|
101
|
+
mockFetch.mockResolvedValueOnce({
|
|
102
|
+
ok: false,
|
|
103
|
+
status: 500,
|
|
104
|
+
statusText: 'Internal Server Error',
|
|
105
|
+
text: () => Promise.resolve('not json'),
|
|
106
|
+
});
|
|
107
|
+
try {
|
|
108
|
+
await (0, api_1.request)(config, 'GET', '/broken');
|
|
109
|
+
vitest_1.expect.fail('Should have thrown');
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
(0, vitest_1.expect)(error).toBeInstanceOf(api_1.ApiError);
|
|
113
|
+
(0, vitest_1.expect)(error.message).toBe('Internal Server Error');
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
(0, vitest_1.it)('includes custom headers', async () => {
|
|
117
|
+
mockFetch.mockResolvedValueOnce({
|
|
118
|
+
ok: true,
|
|
119
|
+
json: () => Promise.resolve({}),
|
|
120
|
+
});
|
|
121
|
+
await (0, api_1.request)(config, 'POST', '/agents', {
|
|
122
|
+
body: JSON.stringify({ name: 'test' }),
|
|
123
|
+
headers: { 'Content-Type': 'application/json' },
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith(vitest_1.expect.any(String), vitest_1.expect.objectContaining({
|
|
126
|
+
headers: vitest_1.expect.objectContaining({
|
|
127
|
+
'Content-Type': 'application/json',
|
|
128
|
+
Authorization: 'Bearer sk_test_123',
|
|
129
|
+
}),
|
|
130
|
+
}));
|
|
131
|
+
});
|
|
132
|
+
(0, vitest_1.it)('strips trailing slash from API URL', async () => {
|
|
133
|
+
const configWithSlash = {
|
|
134
|
+
apiKey: 'sk_test_123',
|
|
135
|
+
apiUrl: 'https://api.test.com/',
|
|
136
|
+
};
|
|
137
|
+
mockFetch.mockResolvedValueOnce({
|
|
138
|
+
ok: true,
|
|
139
|
+
json: () => Promise.resolve({}),
|
|
140
|
+
});
|
|
141
|
+
await (0, api_1.request)(configWithSlash, 'GET', '/test');
|
|
142
|
+
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('https://api.test.com/test', vitest_1.expect.any(Object));
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
(0, vitest_1.describe)('publicRequest', () => {
|
|
146
|
+
const config = {
|
|
147
|
+
apiUrl: 'https://api.test.com',
|
|
148
|
+
};
|
|
149
|
+
(0, vitest_1.beforeEach)(() => {
|
|
150
|
+
mockFetch.mockReset();
|
|
151
|
+
});
|
|
152
|
+
(0, vitest_1.it)('makes unauthenticated request', async () => {
|
|
153
|
+
mockFetch.mockResolvedValueOnce({
|
|
154
|
+
ok: true,
|
|
155
|
+
json: () => Promise.resolve([]),
|
|
156
|
+
});
|
|
157
|
+
await (0, api_1.publicRequest)(config, '/public/agents');
|
|
158
|
+
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('https://api.test.com/public/agents');
|
|
159
|
+
});
|
|
160
|
+
(0, vitest_1.it)('returns parsed JSON', async () => {
|
|
161
|
+
const agents = [{ name: 'agent1' }, { name: 'agent2' }];
|
|
162
|
+
mockFetch.mockResolvedValueOnce({
|
|
163
|
+
ok: true,
|
|
164
|
+
json: () => Promise.resolve(agents),
|
|
165
|
+
});
|
|
166
|
+
const result = await (0, api_1.publicRequest)(config, '/public/agents');
|
|
167
|
+
(0, vitest_1.expect)(result).toEqual(agents);
|
|
168
|
+
});
|
|
169
|
+
(0, vitest_1.it)('throws ApiError on failure', async () => {
|
|
170
|
+
mockFetch.mockResolvedValueOnce({
|
|
171
|
+
ok: false,
|
|
172
|
+
status: 404,
|
|
173
|
+
statusText: 'Not Found',
|
|
174
|
+
text: () => Promise.resolve('{}'),
|
|
175
|
+
});
|
|
176
|
+
await (0, vitest_1.expect)((0, api_1.publicRequest)(config, '/public/agents/missing'))
|
|
177
|
+
.rejects.toThrow(api_1.ApiError);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
(0, vitest_1.describe)('getOrg', () => {
|
|
181
|
+
const config = {
|
|
182
|
+
apiKey: 'sk_test_123',
|
|
183
|
+
apiUrl: 'https://api.test.com',
|
|
184
|
+
};
|
|
185
|
+
(0, vitest_1.beforeEach)(() => {
|
|
186
|
+
mockFetch.mockReset();
|
|
187
|
+
});
|
|
188
|
+
(0, vitest_1.it)('calls GET /org endpoint', async () => {
|
|
189
|
+
const orgData = { id: '123', slug: 'test-org', name: 'Test Org' };
|
|
190
|
+
mockFetch.mockResolvedValueOnce({
|
|
191
|
+
ok: true,
|
|
192
|
+
json: () => Promise.resolve(orgData),
|
|
193
|
+
});
|
|
194
|
+
const result = await (0, api_1.getOrg)(config);
|
|
195
|
+
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('https://api.test.com/org', vitest_1.expect.objectContaining({ method: 'GET' }));
|
|
196
|
+
(0, vitest_1.expect)(result).toEqual(orgData);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
(0, vitest_1.describe)('searchAgents', () => {
|
|
200
|
+
const config = {
|
|
201
|
+
apiUrl: 'https://api.test.com',
|
|
202
|
+
};
|
|
203
|
+
(0, vitest_1.beforeEach)(() => {
|
|
204
|
+
mockFetch.mockReset();
|
|
205
|
+
});
|
|
206
|
+
(0, vitest_1.it)('builds search query params', async () => {
|
|
207
|
+
mockFetch.mockResolvedValueOnce({
|
|
208
|
+
ok: true,
|
|
209
|
+
json: () => Promise.resolve([]),
|
|
210
|
+
});
|
|
211
|
+
await (0, api_1.searchAgents)(config, 'pdf analyzer', { sort: 'stars' });
|
|
212
|
+
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('https://api.test.com/public/agents?search=pdf+analyzer&sort=stars');
|
|
213
|
+
});
|
|
214
|
+
(0, vitest_1.it)('handles empty query', async () => {
|
|
215
|
+
mockFetch.mockResolvedValueOnce({
|
|
216
|
+
ok: true,
|
|
217
|
+
json: () => Promise.resolve([]),
|
|
218
|
+
});
|
|
219
|
+
await (0, api_1.searchAgents)(config, '');
|
|
220
|
+
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('https://api.test.com/public/agents');
|
|
221
|
+
});
|
|
222
|
+
(0, vitest_1.it)('includes tags in query', async () => {
|
|
223
|
+
mockFetch.mockResolvedValueOnce({
|
|
224
|
+
ok: true,
|
|
225
|
+
json: () => Promise.resolve([]),
|
|
226
|
+
});
|
|
227
|
+
await (0, api_1.searchAgents)(config, 'test', { tags: ['ai', 'document'] });
|
|
228
|
+
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith(vitest_1.expect.stringContaining('tags=ai%2Cdocument'));
|
|
229
|
+
});
|
|
230
|
+
});
|
package/dist/lib/config.js
CHANGED
|
@@ -43,6 +43,8 @@ exports.getResolvedConfig = getResolvedConfig;
|
|
|
43
43
|
exports.getConfigPath = getConfigPath;
|
|
44
44
|
exports.getDefaultFormats = getDefaultFormats;
|
|
45
45
|
exports.setDefaultFormats = setDefaultFormats;
|
|
46
|
+
exports.getDefaultScope = getDefaultScope;
|
|
47
|
+
exports.setDefaultScope = setDefaultScope;
|
|
46
48
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
47
49
|
const path_1 = __importDefault(require("path"));
|
|
48
50
|
const os_1 = __importDefault(require("os"));
|
|
@@ -129,3 +131,12 @@ async function setDefaultFormats(formats) {
|
|
|
129
131
|
config.default_formats = formats;
|
|
130
132
|
await saveConfig(config);
|
|
131
133
|
}
|
|
134
|
+
async function getDefaultScope() {
|
|
135
|
+
const config = await loadConfig();
|
|
136
|
+
return config.default_scope;
|
|
137
|
+
}
|
|
138
|
+
async function setDefaultScope(scope) {
|
|
139
|
+
const config = await loadConfig();
|
|
140
|
+
config.default_scope = scope;
|
|
141
|
+
await saveConfig(config);
|
|
142
|
+
}
|