opencode-agora 0.2.2
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/LICENSE +21 -0
- package/README.md +309 -0
- package/dist/api.d.ts +69 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +109 -0
- package/dist/api.js.map +1 -0
- package/dist/cli/app.d.ts +22 -0
- package/dist/cli/app.d.ts.map +1 -0
- package/dist/cli/app.js +1111 -0
- package/dist/cli/app.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-files.d.ts +27 -0
- package/dist/config-files.d.ts.map +1 -0
- package/dist/config-files.js +78 -0
- package/dist/config-files.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/data.d.ts +8 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +1123 -0
- package/dist/data.js.map +1 -0
- package/dist/format.d.ts +40 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +127 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +430 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +22 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +199 -0
- package/dist/init.js.map +1 -0
- package/dist/live.d.ts +100 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +443 -0
- package/dist/live.js.map +1 -0
- package/dist/logger.d.ts +20 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +59 -0
- package/dist/logger.js.map +1 -0
- package/dist/marketplace.d.ts +51 -0
- package/dist/marketplace.d.ts.map +1 -0
- package/dist/marketplace.js +219 -0
- package/dist/marketplace.js.map +1 -0
- package/dist/state.d.ts +46 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +169 -0
- package/dist/state.js.map +1 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
package/dist/cli/app.js
ADDED
|
@@ -0,0 +1,1111 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import pkg from '../../package.json';
|
|
5
|
+
import { formatConfigJson } from '../config.js';
|
|
6
|
+
import { detectOpenCodeConfigPath, doctorOpenCodeConfig, loadOpenCodeConfig, writeOpenCodeConfig } from '../config-files.js';
|
|
7
|
+
import { createInstallPlan, getInstallKind, getMarketplaceItems, getTrendingTags } from '../marketplace.js';
|
|
8
|
+
import { scanProject, generateInitPlan, applyInitPlan, runCommands } from '../init.js';
|
|
9
|
+
import { sampleWorkflows, dataRefreshedAt } from '../data.js';
|
|
10
|
+
import { createDiscussionSource, discussionsSource, findMarketplaceSource, createReviewSource, findTutorialSource, listReviewsSource, profileSource, publishPackageSource, publishWorkflowSource, searchMarketplaceSource, trendingMarketplaceSource, tutorialsSource } from '../live.js';
|
|
11
|
+
import { clearAuthState, detectAgoraDataDir, getAuthState, getAgoraStatePath, loadAgoraState, removeItemFromState, resolveSavedItems, saveItemToState, setAuthState, writeAgoraState } from '../state.js';
|
|
12
|
+
const VERSION = pkg.version;
|
|
13
|
+
const booleanFlags = new Set(['api', 'help', 'json', 'live', 'offline', 'version', 'verbose', 'write']);
|
|
14
|
+
export async function runCli(argv, io) {
|
|
15
|
+
const parsed = parseArgs(argv);
|
|
16
|
+
if (parsed.flags.version) {
|
|
17
|
+
writeLine(io.stdout, VERSION);
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
if (!parsed.command || parsed.flags.help) {
|
|
21
|
+
writeLine(io.stdout, usage());
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
switch (parsed.command) {
|
|
26
|
+
case 'search':
|
|
27
|
+
return await commandSearch(parsed, io);
|
|
28
|
+
case 'browse':
|
|
29
|
+
return await commandBrowse(parsed, io);
|
|
30
|
+
case 'trending':
|
|
31
|
+
return await commandTrending(parsed, io);
|
|
32
|
+
case 'workflows':
|
|
33
|
+
return await commandWorkflows(parsed, io);
|
|
34
|
+
case 'tutorials':
|
|
35
|
+
return await commandTutorials(parsed, io);
|
|
36
|
+
case 'tutorial':
|
|
37
|
+
return await commandTutorial(parsed, io);
|
|
38
|
+
case 'discussions':
|
|
39
|
+
return await commandDiscussions(parsed, io);
|
|
40
|
+
case 'discuss':
|
|
41
|
+
return await commandDiscuss(parsed, io);
|
|
42
|
+
case 'install':
|
|
43
|
+
return await commandInstall(parsed, io);
|
|
44
|
+
case 'save':
|
|
45
|
+
return await commandSave(parsed, io);
|
|
46
|
+
case 'saved':
|
|
47
|
+
return await commandSaved(parsed, io);
|
|
48
|
+
case 'remove':
|
|
49
|
+
return await commandRemove(parsed, io);
|
|
50
|
+
case 'publish':
|
|
51
|
+
return await commandPublish(parsed, io);
|
|
52
|
+
case 'review':
|
|
53
|
+
return await commandReview(parsed, io);
|
|
54
|
+
case 'reviews':
|
|
55
|
+
return await commandReviews(parsed, io);
|
|
56
|
+
case 'profile':
|
|
57
|
+
return await commandProfile(parsed, io);
|
|
58
|
+
case 'auth':
|
|
59
|
+
return commandAuth(parsed, io);
|
|
60
|
+
case 'config':
|
|
61
|
+
return await commandConfig(parsed, io);
|
|
62
|
+
case 'init':
|
|
63
|
+
return await commandInit(parsed, io);
|
|
64
|
+
case 'use':
|
|
65
|
+
return await commandUse(parsed, io);
|
|
66
|
+
case 'help':
|
|
67
|
+
writeLine(io.stdout, usage());
|
|
68
|
+
return 0;
|
|
69
|
+
default:
|
|
70
|
+
writeLine(io.stderr, `Unknown command: ${parsed.command}`);
|
|
71
|
+
writeLine(io.stderr, 'Run agora help for usage.');
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
writeLine(io.stderr, error instanceof Error ? error.message : String(error));
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function parseArgs(argv) {
|
|
81
|
+
const flags = {};
|
|
82
|
+
const positionals = [];
|
|
83
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
84
|
+
const arg = argv[index];
|
|
85
|
+
if (arg === '--') {
|
|
86
|
+
positionals.push(...argv.slice(index + 1));
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (arg.startsWith('--')) {
|
|
90
|
+
const [rawKey, inlineValue] = arg.slice(2).split(/=(.*)/s, 2);
|
|
91
|
+
const key = normalizeFlag(rawKey);
|
|
92
|
+
if (inlineValue !== undefined) {
|
|
93
|
+
flags[key] = inlineValue;
|
|
94
|
+
}
|
|
95
|
+
else if (!booleanFlags.has(key) && argv[index + 1] && !argv[index + 1].startsWith('-')) {
|
|
96
|
+
flags[key] = argv[index + 1];
|
|
97
|
+
index += 1;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
flags[key] = true;
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (arg.startsWith('-') && arg.length > 1) {
|
|
105
|
+
const key = shortFlag(arg);
|
|
106
|
+
if (!booleanFlags.has(key) && argv[index + 1] && !argv[index + 1].startsWith('-')) {
|
|
107
|
+
flags[key] = argv[index + 1];
|
|
108
|
+
index += 1;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
flags[key] = true;
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
positionals.push(arg);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
command: positionals[0],
|
|
119
|
+
args: positionals.slice(1),
|
|
120
|
+
flags
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function commandSearch(parsed, io) {
|
|
124
|
+
const query = parsed.args.join(' ');
|
|
125
|
+
const category = stringFlag(parsed, 'category', 'c') || 'all';
|
|
126
|
+
const limit = numberFlag(parsed, 'limit', 'n') || 10;
|
|
127
|
+
const result = await searchMarketplaceSource({ ...sourceOptions(parsed, io), query, category, limit });
|
|
128
|
+
const results = result.data;
|
|
129
|
+
warnFallback(result, io);
|
|
130
|
+
if (parsed.flags.json) {
|
|
131
|
+
writeJson(io.stdout, sourcePayload(result, { query, category, count: results.length, items: results }));
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
if (results.length === 0) {
|
|
135
|
+
writeLine(io.stdout, `No results found for "${query}".`);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
const sourceLabel = result.source === 'offline'
|
|
139
|
+
? `source: offline, refreshed ${dataRefreshedAt}`
|
|
140
|
+
: `source: ${result.source}`;
|
|
141
|
+
writeLine(io.stdout, `Agora search: ${query || 'all'} (${results.length} shown, ${sourceLabel})`);
|
|
142
|
+
writeLine(io.stdout, formatItemList(results));
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
async function commandBrowse(parsed, io) {
|
|
146
|
+
const id = parsed.args[0];
|
|
147
|
+
if (!id)
|
|
148
|
+
return usageError(io, 'browse requires an item id');
|
|
149
|
+
const result = await findMarketplaceSource({ ...sourceOptions(parsed, io), id, type: stringFlag(parsed, 'type', 't') });
|
|
150
|
+
const item = result.data;
|
|
151
|
+
warnFallback(result, io);
|
|
152
|
+
if (!item)
|
|
153
|
+
return usageError(io, `Item not found: ${id}`);
|
|
154
|
+
if (parsed.flags.json) {
|
|
155
|
+
writeJson(io.stdout, sourcePayload(result, { item }));
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
writeLine(io.stdout, formatItemDetail(item));
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
async function commandTrending(parsed, io) {
|
|
162
|
+
const category = stringFlag(parsed, 'category', 'c') || parsed.args[0] || 'all';
|
|
163
|
+
const limit = numberFlag(parsed, 'limit', 'n') || 5;
|
|
164
|
+
const result = await trendingMarketplaceSource({ ...sourceOptions(parsed, io), category, limit });
|
|
165
|
+
const items = result.data;
|
|
166
|
+
warnFallback(result, io);
|
|
167
|
+
if (parsed.flags.json) {
|
|
168
|
+
writeJson(io.stdout, sourcePayload(result, { category, count: items.length, tags: getTrendingTags(), items }));
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
writeLine(io.stdout, `Trending in Agora (${category}, source: ${result.source})`);
|
|
172
|
+
writeLine(io.stdout, formatItemList(items));
|
|
173
|
+
writeLine(io.stdout, `Tags: ${getTrendingTags().join(', ')}`);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
async function commandWorkflows(parsed, io) {
|
|
177
|
+
const query = parsed.args.join(' ');
|
|
178
|
+
const limit = numberFlag(parsed, 'limit', 'n') || 10;
|
|
179
|
+
const result = await searchMarketplaceSource({ ...sourceOptions(parsed, io), query, category: 'workflow', limit });
|
|
180
|
+
const workflows = result.data.filter((item) => item.kind === 'workflow');
|
|
181
|
+
warnFallback(result, io);
|
|
182
|
+
if (parsed.flags.json) {
|
|
183
|
+
writeJson(io.stdout, sourcePayload(result, { query, count: workflows.length, workflows }));
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
writeLine(io.stdout, `Agora workflows (${workflows.length} shown, source: ${result.source})`);
|
|
187
|
+
writeLine(io.stdout, formatItemList(workflows));
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
async function commandTutorials(parsed, io) {
|
|
191
|
+
const query = parsed.args.join(' ');
|
|
192
|
+
const level = tutorialLevelFlag(parsed);
|
|
193
|
+
if (!level.ok)
|
|
194
|
+
return usageError(io, level.error);
|
|
195
|
+
const limit = numberFlag(parsed, 'limit', 'n') || 20;
|
|
196
|
+
const result = await tutorialsSource({ ...sourceOptions(parsed, io), query, level: level.value, limit });
|
|
197
|
+
const tutorials = result.data;
|
|
198
|
+
warnFallback(result, io);
|
|
199
|
+
if (parsed.flags.json) {
|
|
200
|
+
writeJson(io.stdout, sourcePayload(result, { query, level: level.value, count: tutorials.length, tutorials }));
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
if (tutorials.length === 0) {
|
|
204
|
+
writeLine(io.stdout, query ? `No tutorials match "${query}".` : 'No tutorials found.');
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
writeLine(io.stdout, `Agora tutorials (${tutorials.length} shown, source: ${result.source})`);
|
|
208
|
+
writeLine(io.stdout, formatTutorialList(tutorials));
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
async function commandTutorial(parsed, io) {
|
|
212
|
+
const id = parsed.args[0];
|
|
213
|
+
if (!id)
|
|
214
|
+
return usageError(io, 'tutorial requires a tutorial id');
|
|
215
|
+
const step = tutorialStepNumber(parsed);
|
|
216
|
+
if (!step.ok)
|
|
217
|
+
return usageError(io, step.error);
|
|
218
|
+
const result = await findTutorialSource({ ...sourceOptions(parsed, io), id });
|
|
219
|
+
const tutorial = result.data;
|
|
220
|
+
warnFallback(result, io);
|
|
221
|
+
if (!tutorial)
|
|
222
|
+
return usageError(io, `Tutorial not found: ${id}`);
|
|
223
|
+
if (parsed.flags.json) {
|
|
224
|
+
writeJson(io.stdout, sourcePayload(result, {
|
|
225
|
+
tutorial,
|
|
226
|
+
step: tutorialStepPayload(tutorial, step.value)
|
|
227
|
+
}));
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
writeLine(io.stdout, formatTutorialStep(tutorial, step.value));
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
async function commandDiscussions(parsed, io) {
|
|
234
|
+
const category = stringFlag(parsed, 'category', 'c') || 'all';
|
|
235
|
+
const query = parsed.args.join(' ');
|
|
236
|
+
const result = await discussionsSource({ ...sourceOptions(parsed, io), category, query });
|
|
237
|
+
const discussions = result.data;
|
|
238
|
+
warnFallback(result, io);
|
|
239
|
+
if (parsed.flags.json) {
|
|
240
|
+
writeJson(io.stdout, sourcePayload(result, { category, query, count: discussions.length, discussions }));
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
if (discussions.length === 0) {
|
|
244
|
+
writeLine(io.stdout, 'No discussions found.');
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
writeLine(io.stdout, `Agora discussions (${discussions.length}, source: ${result.source})`);
|
|
248
|
+
writeLine(io.stdout, discussions.map((discussion, index) => {
|
|
249
|
+
return [
|
|
250
|
+
`${index + 1}. ${discussion.title} [${discussion.category}]`,
|
|
251
|
+
` ${truncate(discussion.content, 88)}`,
|
|
252
|
+
` replies ${discussion.replies} | stars ${discussion.stars} | by ${discussion.author}`
|
|
253
|
+
].join('\n');
|
|
254
|
+
}).join('\n\n'));
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
async function commandDiscuss(parsed, io) {
|
|
258
|
+
const source = writeSourceOptions(parsed, io);
|
|
259
|
+
if (!source.ok)
|
|
260
|
+
return usageError(io, source.error);
|
|
261
|
+
const title = requiredStringFlag(parsed, 'title');
|
|
262
|
+
const content = contentInput(parsed);
|
|
263
|
+
if (!title || !content) {
|
|
264
|
+
return usageError(io, 'discuss requires --title and --content or --content-file');
|
|
265
|
+
}
|
|
266
|
+
const category = discussionCategoryFlag(parsed);
|
|
267
|
+
if (!category.ok)
|
|
268
|
+
return usageError(io, category.error);
|
|
269
|
+
const result = await createDiscussionSource(source.options, {
|
|
270
|
+
title,
|
|
271
|
+
content,
|
|
272
|
+
category: category.value
|
|
273
|
+
});
|
|
274
|
+
if (parsed.flags.json) {
|
|
275
|
+
writeJson(io.stdout, sourcePayload(result, { discussion: result.data }));
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
writeLine(io.stdout, `Created discussion ${result.data.id}`);
|
|
279
|
+
writeLine(io.stdout, `${result.data.title} (${result.source})`);
|
|
280
|
+
return 0;
|
|
281
|
+
}
|
|
282
|
+
async function commandInstall(parsed, io) {
|
|
283
|
+
const id = parsed.args[0];
|
|
284
|
+
if (!id)
|
|
285
|
+
return usageError(io, 'install requires an item id');
|
|
286
|
+
const source = await findMarketplaceSource({ ...sourceOptions(parsed, io), id, type: stringFlag(parsed, 'type', 't') });
|
|
287
|
+
const item = source.data;
|
|
288
|
+
warnFallback(source, io);
|
|
289
|
+
if (!item)
|
|
290
|
+
return usageError(io, `Item not found: ${id}`);
|
|
291
|
+
const configPath = detectOpenCodeConfigPath({
|
|
292
|
+
explicitPath: stringFlag(parsed, 'config'),
|
|
293
|
+
cwd: io.cwd,
|
|
294
|
+
env: io.env
|
|
295
|
+
});
|
|
296
|
+
const loaded = loadOpenCodeConfig(configPath);
|
|
297
|
+
if (loaded.error)
|
|
298
|
+
return usageError(io, `${loaded.path}: ${loaded.error}`);
|
|
299
|
+
const plan = createInstallPlan(item, loaded.config);
|
|
300
|
+
if (!plan.installable)
|
|
301
|
+
return usageError(io, plan.reason || `${item.name} is not installable`);
|
|
302
|
+
if (parsed.flags.json) {
|
|
303
|
+
writeJson(io.stdout, {
|
|
304
|
+
source: source.source,
|
|
305
|
+
apiUrl: source.apiUrl,
|
|
306
|
+
fallbackReason: source.fallbackReason,
|
|
307
|
+
item,
|
|
308
|
+
configPath,
|
|
309
|
+
write: Boolean(parsed.flags.write),
|
|
310
|
+
commands: plan.commands,
|
|
311
|
+
notes: plan.notes,
|
|
312
|
+
config: plan.config
|
|
313
|
+
});
|
|
314
|
+
return 0;
|
|
315
|
+
}
|
|
316
|
+
if (parsed.flags.write) {
|
|
317
|
+
writeOpenCodeConfig(configPath, plan.config);
|
|
318
|
+
writeLine(io.stdout, `Installed ${item.name}`);
|
|
319
|
+
writeLine(io.stdout, `Updated ${configPath}`);
|
|
320
|
+
if (plan.commands.length) {
|
|
321
|
+
writeLine(io.stdout, 'Installing packages...');
|
|
322
|
+
for (const cmd of plan.commands) {
|
|
323
|
+
try {
|
|
324
|
+
execSync(cmd, { stdio: 'pipe', timeout: 120000 });
|
|
325
|
+
writeLine(io.stdout, ` ✓ ${cmd}`);
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
writeLine(io.stdout, ` ! Failed: ${cmd} (may already be installed)`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return 0;
|
|
333
|
+
}
|
|
334
|
+
writeLine(io.stdout, `Install preview: ${item.name}`);
|
|
335
|
+
writeLine(io.stdout, `Target config: ${configPath}`);
|
|
336
|
+
if (plan.commands.length) {
|
|
337
|
+
writeLine(io.stdout, '\nCommands:');
|
|
338
|
+
writeLine(io.stdout, plan.commands.join('\n'));
|
|
339
|
+
}
|
|
340
|
+
writeLine(io.stdout, '\nopencode.json preview:');
|
|
341
|
+
writeLine(io.stdout, formatConfigJson(plan.config));
|
|
342
|
+
writeLine(io.stdout, '\nRun with --write to update the config file and install packages.');
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
async function commandInit(parsed, io) {
|
|
346
|
+
const cwd = io.cwd || process.cwd();
|
|
347
|
+
const scan = scanProject(cwd);
|
|
348
|
+
if (parsed.flags.json) {
|
|
349
|
+
writeJson(io.stdout, { projectType: scan.type, frameworks: scan.frameworks });
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
writeLine(io.stdout, `Scanning ${cwd}...`);
|
|
353
|
+
writeLine(io.stdout, ` Project type: ${scan.type}`);
|
|
354
|
+
if (scan.frameworks.length)
|
|
355
|
+
writeLine(io.stdout, ` Frameworks: ${scan.frameworks.join(', ')}`);
|
|
356
|
+
if (scan.hasDocker)
|
|
357
|
+
writeLine(io.stdout, ' Docker: detected');
|
|
358
|
+
if (scan.hasTests)
|
|
359
|
+
writeLine(io.stdout, ' Tests: detected');
|
|
360
|
+
if (scan.hasDatabase)
|
|
361
|
+
writeLine(io.stdout, ' Database: detected');
|
|
362
|
+
const plan = generateInitPlan(scan);
|
|
363
|
+
const configPath = detectOpenCodeConfigPath({ cwd, env: io.env });
|
|
364
|
+
if (!parsed.flags.dryRun) {
|
|
365
|
+
applyInitPlan(plan, configPath);
|
|
366
|
+
writeLine(io.stdout, `\nWrote config to ${configPath}`);
|
|
367
|
+
if (plan.commands.length) {
|
|
368
|
+
writeLine(io.stdout, '\nInstalling MCP server packages...');
|
|
369
|
+
runCommands(plan.commands);
|
|
370
|
+
writeLine(io.stdout, ` Done (${plan.commands.length} packages)`);
|
|
371
|
+
}
|
|
372
|
+
writeLine(io.stdout, '\n✓ Agora initialized! Restart OpenCode to pick up the changes.');
|
|
373
|
+
writeLine(io.stdout, ' Plugin "opencode-agora" is now registered in your config.');
|
|
374
|
+
writeLine(io.stdout, ` ${plan.servers.length} MCP servers configured.`);
|
|
375
|
+
if (plan.workflows.length)
|
|
376
|
+
writeLine(io.stdout, ` ${plan.workflows.length} workflows available via \`agora use\`.`);
|
|
377
|
+
for (const note of plan.notes)
|
|
378
|
+
writeLine(io.stdout, ` ${note}`);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
writeLine(io.stdout, '\n--- Dry run ---');
|
|
382
|
+
writeLine(io.stdout, `Target config: ${configPath}`);
|
|
383
|
+
writeLine(io.stdout, formatConfigJson(plan.config));
|
|
384
|
+
writeLine(io.stdout, '\nPackages to install:');
|
|
385
|
+
for (const cmd of plan.commands)
|
|
386
|
+
writeLine(io.stdout, ` ${cmd}`);
|
|
387
|
+
writeLine(io.stdout, '\nRun without --dry-run to apply.');
|
|
388
|
+
}
|
|
389
|
+
return 0;
|
|
390
|
+
}
|
|
391
|
+
async function commandUse(parsed, io) {
|
|
392
|
+
const id = parsed.args[0];
|
|
393
|
+
if (!id)
|
|
394
|
+
return usageError(io, 'use requires a workflow id');
|
|
395
|
+
const workflow = sampleWorkflows.find((w) => w.id === id || w.name.toLowerCase() === id.toLowerCase());
|
|
396
|
+
if (!workflow)
|
|
397
|
+
return usageError(io, `Workflow not found: ${id}. Run \`agora workflows\` to see available workflows.`);
|
|
398
|
+
const cwd = io.cwd || process.cwd();
|
|
399
|
+
const skillsDir = join(cwd, '.opencode', 'skills');
|
|
400
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
401
|
+
const skillId = workflow.id.replace(/^wf-/, 'skill-');
|
|
402
|
+
const skillPath = join(skillsDir, `${skillId}.md`);
|
|
403
|
+
const skillContent = `---
|
|
404
|
+
name: ${workflow.name}
|
|
405
|
+
description: ${workflow.description}
|
|
406
|
+
model: ${workflow.model || ''}
|
|
407
|
+
tags: [${workflow.tags.map((t) => `"${t}"`).join(', ')}]
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
${workflow.prompt}
|
|
411
|
+
`;
|
|
412
|
+
writeFileSync(skillPath, skillContent, 'utf8');
|
|
413
|
+
const configPath = detectOpenCodeConfigPath({ cwd, env: io.env });
|
|
414
|
+
const loaded = loadOpenCodeConfig(configPath);
|
|
415
|
+
const plugins = new Set(loaded.config.plugins || []);
|
|
416
|
+
plugins.add(skillId);
|
|
417
|
+
const updatedConfig = {
|
|
418
|
+
...loaded.config,
|
|
419
|
+
plugins: Array.from(plugins),
|
|
420
|
+
};
|
|
421
|
+
writeOpenCodeConfig(configPath, updatedConfig);
|
|
422
|
+
if (parsed.flags.json) {
|
|
423
|
+
writeJson(io.stdout, { workflow: workflow.id, skillPath, registered: true });
|
|
424
|
+
return 0;
|
|
425
|
+
}
|
|
426
|
+
writeLine(io.stdout, `✓ Applied "${workflow.name}" as an OpenCode skill.`);
|
|
427
|
+
writeLine(io.stdout, ` Skill file: ${skillPath}`);
|
|
428
|
+
writeLine(io.stdout, ` Registered in: ${configPath}`);
|
|
429
|
+
writeLine(io.stdout, ' Restart OpenCode to start using it.');
|
|
430
|
+
return 0;
|
|
431
|
+
}
|
|
432
|
+
function commandConfig(parsed, io) {
|
|
433
|
+
const subcommand = parsed.args[0] || 'doctor';
|
|
434
|
+
if (subcommand !== 'doctor') {
|
|
435
|
+
return usageError(io, `Unknown config command: ${subcommand}`);
|
|
436
|
+
}
|
|
437
|
+
const configPath = detectOpenCodeConfigPath({
|
|
438
|
+
explicitPath: stringFlag(parsed, 'config'),
|
|
439
|
+
cwd: io.cwd,
|
|
440
|
+
env: io.env
|
|
441
|
+
});
|
|
442
|
+
const report = doctorOpenCodeConfig(configPath);
|
|
443
|
+
if (parsed.flags.json) {
|
|
444
|
+
writeJson(io.stdout, report);
|
|
445
|
+
return report.valid ? 0 : 1;
|
|
446
|
+
}
|
|
447
|
+
writeLine(io.stdout, `Config path: ${report.path}`);
|
|
448
|
+
writeLine(io.stdout, `Exists: ${report.exists ? 'yes' : 'no'}`);
|
|
449
|
+
writeLine(io.stdout, `Valid: ${report.valid ? 'yes' : 'no'}`);
|
|
450
|
+
if (report.error)
|
|
451
|
+
writeLine(io.stdout, `Error: ${report.error}`);
|
|
452
|
+
writeLine(io.stdout, `MCP servers: ${report.mcpServers}`);
|
|
453
|
+
writeLine(io.stdout, `Plugins: ${report.plugins}`);
|
|
454
|
+
writeLine(io.stdout, `Packages: ${report.packages.length ? report.packages.join(', ') : 'none'}`);
|
|
455
|
+
return report.valid ? 0 : 1;
|
|
456
|
+
}
|
|
457
|
+
function commandAuth(parsed, io) {
|
|
458
|
+
const subcommand = parsed.args[0] || 'status';
|
|
459
|
+
const dataDir = detectDataDir(parsed, io);
|
|
460
|
+
const state = loadAgoraState(dataDir);
|
|
461
|
+
const existingAuth = getAuthState(state);
|
|
462
|
+
if (subcommand === 'login') {
|
|
463
|
+
const token = authTokenInput(parsed, io);
|
|
464
|
+
if (!token) {
|
|
465
|
+
return usageError(io, 'auth login requires --token, AGORA_TOKEN, or AGORA_API_TOKEN');
|
|
466
|
+
}
|
|
467
|
+
const apiUrl = stringFlag(parsed, 'apiUrl') || envString(io, 'AGORA_API_URL') || existingAuth?.apiUrl;
|
|
468
|
+
const nextState = setAuthState(state, { token, apiUrl });
|
|
469
|
+
const auth = getAuthState(nextState);
|
|
470
|
+
writeAgoraState(dataDir, nextState);
|
|
471
|
+
if (parsed.flags.json) {
|
|
472
|
+
writeJson(io.stdout, authStatusPayload(dataDir, auth));
|
|
473
|
+
return 0;
|
|
474
|
+
}
|
|
475
|
+
writeLine(io.stdout, 'Stored Agora API token');
|
|
476
|
+
writeLine(io.stdout, `API URL: ${auth?.apiUrl || 'not stored'}`);
|
|
477
|
+
writeLine(io.stdout, `State: ${getAgoraStatePath(dataDir)}`);
|
|
478
|
+
return 0;
|
|
479
|
+
}
|
|
480
|
+
if (subcommand === 'status') {
|
|
481
|
+
if (parsed.flags.json) {
|
|
482
|
+
writeJson(io.stdout, authStatusPayload(dataDir, existingAuth));
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
|
485
|
+
writeLine(io.stdout, `Authenticated: ${existingAuth ? 'yes' : 'no'}`);
|
|
486
|
+
if (existingAuth) {
|
|
487
|
+
writeLine(io.stdout, `Token: ${maskToken(existingAuth.token)}`);
|
|
488
|
+
writeLine(io.stdout, `API URL: ${existingAuth.apiUrl || 'not stored'}`);
|
|
489
|
+
writeLine(io.stdout, `Saved: ${formatDate(existingAuth.savedAt)}`);
|
|
490
|
+
}
|
|
491
|
+
writeLine(io.stdout, `State: ${getAgoraStatePath(dataDir)}`);
|
|
492
|
+
return 0;
|
|
493
|
+
}
|
|
494
|
+
if (subcommand === 'logout') {
|
|
495
|
+
if (!existingAuth) {
|
|
496
|
+
if (parsed.flags.json) {
|
|
497
|
+
writeJson(io.stdout, authStatusPayload(dataDir, undefined));
|
|
498
|
+
return 0;
|
|
499
|
+
}
|
|
500
|
+
writeLine(io.stdout, 'No stored Agora API token');
|
|
501
|
+
return 0;
|
|
502
|
+
}
|
|
503
|
+
writeAgoraState(dataDir, clearAuthState(state));
|
|
504
|
+
if (parsed.flags.json) {
|
|
505
|
+
writeJson(io.stdout, authStatusPayload(dataDir, undefined));
|
|
506
|
+
return 0;
|
|
507
|
+
}
|
|
508
|
+
writeLine(io.stdout, 'Removed stored Agora API token');
|
|
509
|
+
return 0;
|
|
510
|
+
}
|
|
511
|
+
return usageError(io, `Unknown auth command: ${subcommand}`);
|
|
512
|
+
}
|
|
513
|
+
async function commandSave(parsed, io) {
|
|
514
|
+
const id = parsed.args[0];
|
|
515
|
+
if (!id)
|
|
516
|
+
return usageError(io, 'save requires an item id');
|
|
517
|
+
const source = await findMarketplaceSource({ ...sourceOptions(parsed, io), id, type: stringFlag(parsed, 'type', 't') });
|
|
518
|
+
const item = source.data;
|
|
519
|
+
warnFallback(source, io);
|
|
520
|
+
if (!item)
|
|
521
|
+
return usageError(io, `Item not found: ${id}`);
|
|
522
|
+
const dataDir = detectDataDir(parsed, io);
|
|
523
|
+
const state = loadAgoraState(dataDir);
|
|
524
|
+
const result = saveItemToState(state, item);
|
|
525
|
+
writeAgoraState(dataDir, result.state);
|
|
526
|
+
if (parsed.flags.json) {
|
|
527
|
+
writeJson(io.stdout, {
|
|
528
|
+
source: source.source,
|
|
529
|
+
apiUrl: source.apiUrl,
|
|
530
|
+
fallbackReason: source.fallbackReason,
|
|
531
|
+
dataDir,
|
|
532
|
+
statePath: getAgoraStatePath(dataDir),
|
|
533
|
+
added: result.added,
|
|
534
|
+
item
|
|
535
|
+
});
|
|
536
|
+
return 0;
|
|
537
|
+
}
|
|
538
|
+
writeLine(io.stdout, result.added ? `Saved ${item.id}` : `${item.id} is already saved`);
|
|
539
|
+
writeLine(io.stdout, `State: ${getAgoraStatePath(dataDir)}`);
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
function commandSaved(parsed, io) {
|
|
543
|
+
const query = parsed.args.join(' ').trim().toLowerCase();
|
|
544
|
+
const dataDir = detectDataDir(parsed, io);
|
|
545
|
+
const state = loadAgoraState(dataDir);
|
|
546
|
+
const saved = resolveSavedItems(state).filter((entry) => matchesSavedQuery(entry, query));
|
|
547
|
+
if (parsed.flags.json) {
|
|
548
|
+
writeJson(io.stdout, {
|
|
549
|
+
dataDir,
|
|
550
|
+
statePath: getAgoraStatePath(dataDir),
|
|
551
|
+
count: saved.length,
|
|
552
|
+
items: saved
|
|
553
|
+
});
|
|
554
|
+
return 0;
|
|
555
|
+
}
|
|
556
|
+
if (saved.length === 0) {
|
|
557
|
+
writeLine(io.stdout, query ? `No saved items match "${query}".` : 'No saved items yet.');
|
|
558
|
+
writeLine(io.stdout, 'Run agora save <id> to save a package or workflow.');
|
|
559
|
+
return 0;
|
|
560
|
+
}
|
|
561
|
+
writeLine(io.stdout, `Saved Agora items (${saved.length})`);
|
|
562
|
+
writeLine(io.stdout, formatSavedList(saved));
|
|
563
|
+
return 0;
|
|
564
|
+
}
|
|
565
|
+
function commandRemove(parsed, io) {
|
|
566
|
+
const id = parsed.args[0];
|
|
567
|
+
if (!id)
|
|
568
|
+
return usageError(io, 'remove requires an item id');
|
|
569
|
+
const dataDir = detectDataDir(parsed, io);
|
|
570
|
+
const state = loadAgoraState(dataDir);
|
|
571
|
+
const targetId = resolveSavedItems(state).find((entry) => {
|
|
572
|
+
return entry.saved.id === id || entry.item?.id === id || entry.item?.name === id;
|
|
573
|
+
})?.saved.id || id;
|
|
574
|
+
const result = removeItemFromState(state, targetId);
|
|
575
|
+
writeAgoraState(dataDir, result.state);
|
|
576
|
+
if (parsed.flags.json) {
|
|
577
|
+
writeJson(io.stdout, {
|
|
578
|
+
dataDir,
|
|
579
|
+
statePath: getAgoraStatePath(dataDir),
|
|
580
|
+
removed: result.removed,
|
|
581
|
+
id: targetId
|
|
582
|
+
});
|
|
583
|
+
return result.removed ? 0 : 1;
|
|
584
|
+
}
|
|
585
|
+
if (!result.removed) {
|
|
586
|
+
return usageError(io, `Saved item not found: ${id}`);
|
|
587
|
+
}
|
|
588
|
+
writeLine(io.stdout, `Removed ${targetId}`);
|
|
589
|
+
return 0;
|
|
590
|
+
}
|
|
591
|
+
async function commandPublish(parsed, io) {
|
|
592
|
+
const kind = parsed.args[0];
|
|
593
|
+
if (kind !== 'package' && kind !== 'workflow') {
|
|
594
|
+
return usageError(io, 'publish requires "package" or "workflow"');
|
|
595
|
+
}
|
|
596
|
+
const source = writeSourceOptions(parsed, io);
|
|
597
|
+
if (!source.ok)
|
|
598
|
+
return usageError(io, source.error);
|
|
599
|
+
const name = requiredStringFlag(parsed, 'name');
|
|
600
|
+
const description = requiredStringFlag(parsed, 'description', 'd');
|
|
601
|
+
if (!name || !description) {
|
|
602
|
+
return usageError(io, 'publish requires --name and --description');
|
|
603
|
+
}
|
|
604
|
+
if (kind === 'package') {
|
|
605
|
+
const npmPackage = stringFlag(parsed, 'npm') || stringFlag(parsed, 'npmPackage');
|
|
606
|
+
const category = stringFlag(parsed, 'category', 'c') || 'mcp';
|
|
607
|
+
if (category === 'mcp' && !npmPackage) {
|
|
608
|
+
return usageError(io, 'publish package requires --npm for MCP packages');
|
|
609
|
+
}
|
|
610
|
+
const result = await publishPackageSource(source.options, {
|
|
611
|
+
id: stringFlag(parsed, 'id'),
|
|
612
|
+
name,
|
|
613
|
+
description,
|
|
614
|
+
version: stringFlag(parsed, 'version') || '1.0.0',
|
|
615
|
+
category,
|
|
616
|
+
tags: tagsFlag(parsed),
|
|
617
|
+
repository: stringFlag(parsed, 'repo') || stringFlag(parsed, 'repository'),
|
|
618
|
+
npmPackage
|
|
619
|
+
});
|
|
620
|
+
if (parsed.flags.json) {
|
|
621
|
+
writeJson(io.stdout, sourcePayload(result, { item: result.data }));
|
|
622
|
+
return 0;
|
|
623
|
+
}
|
|
624
|
+
writeLine(io.stdout, `Published package ${result.data.id}`);
|
|
625
|
+
writeLine(io.stdout, `${result.data.name} (${result.source})`);
|
|
626
|
+
return 0;
|
|
627
|
+
}
|
|
628
|
+
const prompt = promptInput(parsed);
|
|
629
|
+
if (!prompt) {
|
|
630
|
+
return usageError(io, 'publish workflow requires --prompt or --prompt-file');
|
|
631
|
+
}
|
|
632
|
+
const result = await publishWorkflowSource(source.options, {
|
|
633
|
+
id: stringFlag(parsed, 'id'),
|
|
634
|
+
name,
|
|
635
|
+
description,
|
|
636
|
+
prompt,
|
|
637
|
+
model: stringFlag(parsed, 'model'),
|
|
638
|
+
tags: tagsFlag(parsed)
|
|
639
|
+
});
|
|
640
|
+
if (parsed.flags.json) {
|
|
641
|
+
writeJson(io.stdout, sourcePayload(result, { item: result.data }));
|
|
642
|
+
return 0;
|
|
643
|
+
}
|
|
644
|
+
writeLine(io.stdout, `Published workflow ${result.data.id}`);
|
|
645
|
+
writeLine(io.stdout, `${result.data.name} (${result.source})`);
|
|
646
|
+
return 0;
|
|
647
|
+
}
|
|
648
|
+
async function commandReview(parsed, io) {
|
|
649
|
+
const itemId = parsed.args[0];
|
|
650
|
+
if (!itemId)
|
|
651
|
+
return usageError(io, 'review requires an item id');
|
|
652
|
+
const rating = numberFlag(parsed, 'rating', 'r');
|
|
653
|
+
const content = requiredStringFlag(parsed, 'content');
|
|
654
|
+
if (!rating || rating < 1 || rating > 5 || !content) {
|
|
655
|
+
return usageError(io, 'review requires --rating 1-5 and --content');
|
|
656
|
+
}
|
|
657
|
+
const source = writeSourceOptions(parsed, io);
|
|
658
|
+
if (!source.ok)
|
|
659
|
+
return usageError(io, source.error);
|
|
660
|
+
const result = await createReviewSource(source.options, {
|
|
661
|
+
itemId,
|
|
662
|
+
itemType: itemTypeFlag(parsed, itemId),
|
|
663
|
+
rating,
|
|
664
|
+
content
|
|
665
|
+
});
|
|
666
|
+
if (parsed.flags.json) {
|
|
667
|
+
writeJson(io.stdout, sourcePayload(result, { review: result.data }));
|
|
668
|
+
return 0;
|
|
669
|
+
}
|
|
670
|
+
writeLine(io.stdout, `Reviewed ${result.data.itemId}`);
|
|
671
|
+
writeLine(io.stdout, `${result.data.rating}/5 by ${result.data.author}`);
|
|
672
|
+
return 0;
|
|
673
|
+
}
|
|
674
|
+
async function commandReviews(parsed, io) {
|
|
675
|
+
const itemId = parsed.args[0];
|
|
676
|
+
const source = readSourceOptions(parsed, io);
|
|
677
|
+
if (!source.ok)
|
|
678
|
+
return usageError(io, source.error);
|
|
679
|
+
const result = await listReviewsSource(source.options, itemId, stringFlag(parsed, 'type', 't'));
|
|
680
|
+
if (parsed.flags.json) {
|
|
681
|
+
writeJson(io.stdout, sourcePayload(result, { count: result.data.length, reviews: result.data }));
|
|
682
|
+
return 0;
|
|
683
|
+
}
|
|
684
|
+
if (result.data.length === 0) {
|
|
685
|
+
writeLine(io.stdout, itemId ? `No reviews found for ${itemId}.` : 'No reviews found.');
|
|
686
|
+
return 0;
|
|
687
|
+
}
|
|
688
|
+
writeLine(io.stdout, `Agora reviews (${result.data.length}, source: ${result.source})`);
|
|
689
|
+
writeLine(io.stdout, formatReviewList(result.data));
|
|
690
|
+
return 0;
|
|
691
|
+
}
|
|
692
|
+
async function commandProfile(parsed, io) {
|
|
693
|
+
const username = parsed.args[0] || stringFlag(parsed, 'username');
|
|
694
|
+
if (!username)
|
|
695
|
+
return usageError(io, 'profile requires a username');
|
|
696
|
+
const source = readSourceOptions(parsed, io);
|
|
697
|
+
if (!source.ok)
|
|
698
|
+
return usageError(io, source.error);
|
|
699
|
+
const result = await profileSource(source.options, username);
|
|
700
|
+
if (!result.data)
|
|
701
|
+
return usageError(io, `Profile not found: ${username}`);
|
|
702
|
+
if (parsed.flags.json) {
|
|
703
|
+
writeJson(io.stdout, sourcePayload(result, { profile: result.data }));
|
|
704
|
+
return 0;
|
|
705
|
+
}
|
|
706
|
+
writeLine(io.stdout, formatProfileDetail(result.data));
|
|
707
|
+
return 0;
|
|
708
|
+
}
|
|
709
|
+
function formatItemList(items) {
|
|
710
|
+
return items.map((item, index) => {
|
|
711
|
+
const installs = item.kind === 'package' ? ` | installs ${formatCount(item.installs)}` : '';
|
|
712
|
+
return [
|
|
713
|
+
`${index + 1}. ${item.id} [${item.category}]`,
|
|
714
|
+
` ${item.name}`,
|
|
715
|
+
` ${truncate(item.description, 88)}`,
|
|
716
|
+
` stars ${formatCount(item.stars)}${installs} | by ${item.author}`
|
|
717
|
+
].join('\n');
|
|
718
|
+
}).join('\n\n');
|
|
719
|
+
}
|
|
720
|
+
function formatItemDetail(item) {
|
|
721
|
+
const lines = [
|
|
722
|
+
`${item.name}`,
|
|
723
|
+
`id: ${item.id}`,
|
|
724
|
+
`type: ${item.kind}`,
|
|
725
|
+
`category: ${item.category}`,
|
|
726
|
+
`author: ${item.author}`,
|
|
727
|
+
`stars: ${formatCount(item.stars)}`,
|
|
728
|
+
`install: ${getInstallKind(item)}`,
|
|
729
|
+
'',
|
|
730
|
+
item.description,
|
|
731
|
+
'',
|
|
732
|
+
`tags: ${item.tags.join(', ')}`
|
|
733
|
+
];
|
|
734
|
+
if (item.kind === 'package') {
|
|
735
|
+
lines.splice(5, 0, `version: ${item.version}`);
|
|
736
|
+
lines.push(`installs: ${formatCount(item.installs)}`);
|
|
737
|
+
if (item.repository)
|
|
738
|
+
lines.push(`repository: ${item.repository}`);
|
|
739
|
+
if (item.npmPackage)
|
|
740
|
+
lines.push(`npm: ${item.npmPackage}`);
|
|
741
|
+
}
|
|
742
|
+
if (item.kind === 'workflow') {
|
|
743
|
+
lines.push(`forks: ${item.forks}`);
|
|
744
|
+
if (item.model)
|
|
745
|
+
lines.push(`model: ${item.model}`);
|
|
746
|
+
lines.push('', 'prompt:', item.prompt);
|
|
747
|
+
}
|
|
748
|
+
return lines.join('\n');
|
|
749
|
+
}
|
|
750
|
+
function formatSavedList(items) {
|
|
751
|
+
return items.map((entry, index) => {
|
|
752
|
+
if (!entry.item) {
|
|
753
|
+
return [
|
|
754
|
+
`${index + 1}. ${entry.saved.id} [missing]`,
|
|
755
|
+
` saved ${formatDate(entry.saved.savedAt)}`
|
|
756
|
+
].join('\n');
|
|
757
|
+
}
|
|
758
|
+
return [
|
|
759
|
+
`${index + 1}. ${entry.item.id} [${entry.item.category}]`,
|
|
760
|
+
` ${entry.item.name}`,
|
|
761
|
+
` ${truncate(entry.item.description, 88)}`,
|
|
762
|
+
` saved ${formatDate(entry.saved.savedAt)}`
|
|
763
|
+
].join('\n');
|
|
764
|
+
}).join('\n\n');
|
|
765
|
+
}
|
|
766
|
+
function formatReviewList(reviews) {
|
|
767
|
+
return reviews.map((review, index) => {
|
|
768
|
+
return [
|
|
769
|
+
`${index + 1}. ${review.itemId} [${review.itemType}]`,
|
|
770
|
+
` rating ${review.rating}/5 by ${review.author}`,
|
|
771
|
+
` ${truncate(review.content, 88)}`
|
|
772
|
+
].join('\n');
|
|
773
|
+
}).join('\n\n');
|
|
774
|
+
}
|
|
775
|
+
function formatProfileDetail(profile) {
|
|
776
|
+
const lines = [
|
|
777
|
+
profile.displayName,
|
|
778
|
+
`username: ${profile.username}`,
|
|
779
|
+
`packages: ${formatCount(profile.packages)}`,
|
|
780
|
+
`workflows: ${formatCount(profile.workflows)}`,
|
|
781
|
+
`discussions: ${formatCount(profile.discussions)}`
|
|
782
|
+
];
|
|
783
|
+
if (profile.bio)
|
|
784
|
+
lines.splice(2, 0, `bio: ${profile.bio}`);
|
|
785
|
+
if (profile.avatarUrl)
|
|
786
|
+
lines.push(`avatar: ${profile.avatarUrl}`);
|
|
787
|
+
if (profile.joinedAt)
|
|
788
|
+
lines.push(`joined: ${formatDate(profile.joinedAt)}`);
|
|
789
|
+
return lines.join('\n');
|
|
790
|
+
}
|
|
791
|
+
function formatTutorialList(tutorials) {
|
|
792
|
+
return tutorials.map((tutorial, index) => {
|
|
793
|
+
return [
|
|
794
|
+
`${index + 1}. ${tutorial.id} [${tutorial.level}]`,
|
|
795
|
+
` ${tutorial.title}`,
|
|
796
|
+
` ${truncate(tutorial.description, 88)}`,
|
|
797
|
+
` ${tutorial.duration} | ${tutorial.steps.length} steps`
|
|
798
|
+
].join('\n');
|
|
799
|
+
}).join('\n\n');
|
|
800
|
+
}
|
|
801
|
+
function formatTutorialStep(tutorial, stepNumber) {
|
|
802
|
+
const payload = tutorialStepPayload(tutorial, stepNumber);
|
|
803
|
+
if (payload.completed) {
|
|
804
|
+
return [
|
|
805
|
+
`${tutorial.title}`,
|
|
806
|
+
`Completed ${tutorial.steps.length}/${tutorial.steps.length} steps.`,
|
|
807
|
+
'Run agora tutorials for more tutorials.'
|
|
808
|
+
].join('\n');
|
|
809
|
+
}
|
|
810
|
+
const lines = [
|
|
811
|
+
`${tutorial.title}`,
|
|
812
|
+
`id: ${tutorial.id}`,
|
|
813
|
+
`level: ${tutorial.level}`,
|
|
814
|
+
`duration: ${tutorial.duration}`,
|
|
815
|
+
`step: ${payload.stepNumber}/${tutorial.steps.length}`,
|
|
816
|
+
'',
|
|
817
|
+
payload.title || '',
|
|
818
|
+
payload.content || ''
|
|
819
|
+
];
|
|
820
|
+
if (payload.code) {
|
|
821
|
+
lines.push('', 'code:', payload.code);
|
|
822
|
+
}
|
|
823
|
+
return lines.join('\n');
|
|
824
|
+
}
|
|
825
|
+
function usage() {
|
|
826
|
+
return [
|
|
827
|
+
'Agora CLI',
|
|
828
|
+
'',
|
|
829
|
+
'Usage:',
|
|
830
|
+
' agora init [--dry-run] [--json]',
|
|
831
|
+
' agora use <workflow-id> [--json]',
|
|
832
|
+
' agora search <query> [--category mcp|prompt|workflow|skill] [--limit 10] [--json]',
|
|
833
|
+
' agora browse <id> [--type package|workflow] [--json]',
|
|
834
|
+
' agora trending [all|packages|workflows] [--limit 5] [--json]',
|
|
835
|
+
' agora workflows [query] [--limit 10] [--json]',
|
|
836
|
+
' agora tutorials [query] [--level beginner|intermediate|advanced] [--limit 20] [--json]',
|
|
837
|
+
' agora tutorial <id> [step] [--json]',
|
|
838
|
+
' agora discussions [query] [--category question|idea|showcase|discussion] [--json]',
|
|
839
|
+
' agora discuss --title <title> (--content <text>|--content-file path) [--category question|idea|showcase|discussion]',
|
|
840
|
+
' agora install <id> [--write] [--config path] [--json]',
|
|
841
|
+
' agora save <id> [--data-dir path] [--json]',
|
|
842
|
+
' agora saved [query] [--data-dir path] [--json]',
|
|
843
|
+
' agora remove <id> [--data-dir path] [--json]',
|
|
844
|
+
' agora auth login --token <token> [--api-url url] [--data-dir path]',
|
|
845
|
+
' agora auth status [--data-dir path] [--json]',
|
|
846
|
+
' agora auth logout [--data-dir path]',
|
|
847
|
+
' agora publish package --name <name> --description <text> --npm <package> [--token token]',
|
|
848
|
+
' agora publish workflow --name <name> --description <text> --prompt-file <path> [--token token]',
|
|
849
|
+
' agora review <id> --rating 5 --content <text> [--token token]',
|
|
850
|
+
' agora reviews [id] [--type package|workflow]',
|
|
851
|
+
' agora profile <username> [--json]',
|
|
852
|
+
' agora config doctor [--config path] [--json]',
|
|
853
|
+
'',
|
|
854
|
+
'Data source:',
|
|
855
|
+
' --api Use the live Agora API',
|
|
856
|
+
' --api-url <url> Override AGORA_API_URL',
|
|
857
|
+
' --api-timeout <ms> API timeout before offline fallback',
|
|
858
|
+
' --token <token> API auth token, defaults to env vars or agora auth login',
|
|
859
|
+
' --offline Force local bundled marketplace data',
|
|
860
|
+
'',
|
|
861
|
+
'Examples:',
|
|
862
|
+
' agora init',
|
|
863
|
+
' agora init --dry-run',
|
|
864
|
+
' agora use wf-tdd-cycle',
|
|
865
|
+
' agora search filesystem',
|
|
866
|
+
' agora search filesystem --api',
|
|
867
|
+
' agora browse mcp-github',
|
|
868
|
+
' agora tutorials mcp',
|
|
869
|
+
' agora tutorial tut-mcp-basics 2',
|
|
870
|
+
' agora install mcp-github',
|
|
871
|
+
' agora install mcp-github --write',
|
|
872
|
+
' agora save wf-security-audit',
|
|
873
|
+
' agora saved',
|
|
874
|
+
' agora auth login --token $AGORA_TOKEN --api-url https://agora.example.com',
|
|
875
|
+
' agora discuss --title "MCP question" --content "How are you composing servers?" --category question',
|
|
876
|
+
' agora profile alice',
|
|
877
|
+
' agora publish package --name @you/server --description "MCP server" --npm @you/server',
|
|
878
|
+
' agora review mcp-github --rating 5 --content "Works well"'
|
|
879
|
+
].join('\n');
|
|
880
|
+
}
|
|
881
|
+
function usageError(io, message) {
|
|
882
|
+
writeLine(io.stderr, message);
|
|
883
|
+
return 1;
|
|
884
|
+
}
|
|
885
|
+
function stringFlag(parsed, longName, shortName) {
|
|
886
|
+
const value = parsed.flags[longName] ?? (shortName ? parsed.flags[shortName] : undefined);
|
|
887
|
+
return typeof value === 'string' ? value : undefined;
|
|
888
|
+
}
|
|
889
|
+
function requiredStringFlag(parsed, longName, shortName) {
|
|
890
|
+
const value = stringFlag(parsed, longName, shortName);
|
|
891
|
+
return value?.trim() || undefined;
|
|
892
|
+
}
|
|
893
|
+
function numberFlag(parsed, longName, shortName) {
|
|
894
|
+
const value = stringFlag(parsed, longName, shortName);
|
|
895
|
+
if (!value)
|
|
896
|
+
return undefined;
|
|
897
|
+
const parsedValue = Number(value);
|
|
898
|
+
return Number.isFinite(parsedValue) ? parsedValue : undefined;
|
|
899
|
+
}
|
|
900
|
+
function authTokenInput(parsed, io) {
|
|
901
|
+
return requiredStringFlag(parsed, 'token') ||
|
|
902
|
+
envString(io, 'AGORA_TOKEN') ||
|
|
903
|
+
envString(io, 'AGORA_API_TOKEN');
|
|
904
|
+
}
|
|
905
|
+
function envString(io, name) {
|
|
906
|
+
const value = io.env?.[name];
|
|
907
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
908
|
+
}
|
|
909
|
+
function shortFlag(arg) {
|
|
910
|
+
const flag = arg.slice(1);
|
|
911
|
+
if (flag === 'h')
|
|
912
|
+
return 'help';
|
|
913
|
+
if (flag === 'j')
|
|
914
|
+
return 'json';
|
|
915
|
+
if (flag === 'c')
|
|
916
|
+
return 'c';
|
|
917
|
+
if (flag === 'n')
|
|
918
|
+
return 'n';
|
|
919
|
+
if (flag === 't')
|
|
920
|
+
return 't';
|
|
921
|
+
return flag;
|
|
922
|
+
}
|
|
923
|
+
function detectDataDir(parsed, io) {
|
|
924
|
+
return detectAgoraDataDir({
|
|
925
|
+
explicitDir: stringFlag(parsed, 'dataDir'),
|
|
926
|
+
cwd: io.cwd,
|
|
927
|
+
env: io.env
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
function sourceOptions(parsed, io) {
|
|
931
|
+
const explicitApiUrl = stringFlag(parsed, 'apiUrl');
|
|
932
|
+
const envApiUrl = envString(io, 'AGORA_API_URL');
|
|
933
|
+
const storedAuth = getAuthState(loadAgoraState(detectDataDir(parsed, io)));
|
|
934
|
+
const storedApiUrl = storedAuth?.apiUrl;
|
|
935
|
+
const apiUrl = explicitApiUrl || envApiUrl || storedApiUrl || '';
|
|
936
|
+
const useApi = !parsed.flags.offline && Boolean(parsed.flags.api ||
|
|
937
|
+
parsed.flags.live ||
|
|
938
|
+
explicitApiUrl ||
|
|
939
|
+
envApiUrl ||
|
|
940
|
+
storedApiUrl ||
|
|
941
|
+
io.env?.AGORA_USE_API === 'true');
|
|
942
|
+
return {
|
|
943
|
+
useApi,
|
|
944
|
+
apiUrl,
|
|
945
|
+
token: authTokenInput(parsed, io) || storedAuth?.token,
|
|
946
|
+
fetcher: io.fetcher,
|
|
947
|
+
timeoutMs: numberFlag(parsed, 'apiTimeout')
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
function writeSourceOptions(parsed, io) {
|
|
951
|
+
const options = sourceOptions(parsed, io);
|
|
952
|
+
if (!options.apiUrl) {
|
|
953
|
+
return { ok: false, error: 'This command requires --api-url, AGORA_API_URL, or an auth login API URL' };
|
|
954
|
+
}
|
|
955
|
+
if (!options.token) {
|
|
956
|
+
return { ok: false, error: 'This command requires --token, AGORA_TOKEN, AGORA_API_TOKEN, or agora auth login' };
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
ok: true,
|
|
960
|
+
options: {
|
|
961
|
+
...options,
|
|
962
|
+
useApi: true
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
function readSourceOptions(parsed, io) {
|
|
967
|
+
const options = sourceOptions(parsed, io);
|
|
968
|
+
if (!options.apiUrl) {
|
|
969
|
+
return { ok: false, error: 'This command requires --api-url, AGORA_API_URL, or an auth login API URL' };
|
|
970
|
+
}
|
|
971
|
+
return { ok: true, options: { ...options, useApi: true } };
|
|
972
|
+
}
|
|
973
|
+
function tagsFlag(parsed) {
|
|
974
|
+
const value = stringFlag(parsed, 'tags');
|
|
975
|
+
if (!value)
|
|
976
|
+
return [];
|
|
977
|
+
return value.split(',').map((tag) => tag.trim()).filter(Boolean);
|
|
978
|
+
}
|
|
979
|
+
function promptInput(parsed) {
|
|
980
|
+
const prompt = stringFlag(parsed, 'prompt');
|
|
981
|
+
if (prompt)
|
|
982
|
+
return prompt;
|
|
983
|
+
const promptFile = stringFlag(parsed, 'promptFile');
|
|
984
|
+
if (!promptFile)
|
|
985
|
+
return undefined;
|
|
986
|
+
return readFileSync(promptFile, 'utf8');
|
|
987
|
+
}
|
|
988
|
+
function contentInput(parsed) {
|
|
989
|
+
const content = requiredStringFlag(parsed, 'content');
|
|
990
|
+
if (content)
|
|
991
|
+
return content;
|
|
992
|
+
const contentFile = stringFlag(parsed, 'contentFile');
|
|
993
|
+
if (!contentFile)
|
|
994
|
+
return undefined;
|
|
995
|
+
return readFileSync(contentFile, 'utf8').trim();
|
|
996
|
+
}
|
|
997
|
+
function itemTypeFlag(parsed, itemId) {
|
|
998
|
+
const type = stringFlag(parsed, 'type', 't');
|
|
999
|
+
if (type === 'workflow' || itemId.startsWith('wf-'))
|
|
1000
|
+
return 'workflow';
|
|
1001
|
+
return 'package';
|
|
1002
|
+
}
|
|
1003
|
+
function discussionCategoryFlag(parsed) {
|
|
1004
|
+
const category = stringFlag(parsed, 'category', 'c') || 'discussion';
|
|
1005
|
+
if (category === 'question' || category === 'idea' || category === 'showcase' || category === 'discussion') {
|
|
1006
|
+
return { ok: true, value: category };
|
|
1007
|
+
}
|
|
1008
|
+
return { ok: false, error: 'discussion category must be question, idea, showcase, or discussion' };
|
|
1009
|
+
}
|
|
1010
|
+
function tutorialLevelFlag(parsed) {
|
|
1011
|
+
const level = stringFlag(parsed, 'level') || 'all';
|
|
1012
|
+
if (level === 'all' || level === 'beginner' || level === 'intermediate' || level === 'advanced') {
|
|
1013
|
+
return { ok: true, value: level };
|
|
1014
|
+
}
|
|
1015
|
+
return { ok: false, error: 'tutorial level must be beginner, intermediate, advanced, or all' };
|
|
1016
|
+
}
|
|
1017
|
+
function tutorialStepNumber(parsed) {
|
|
1018
|
+
const rawStep = parsed.args[1] || stringFlag(parsed, 'step');
|
|
1019
|
+
if (!rawStep)
|
|
1020
|
+
return { ok: true, value: 1 };
|
|
1021
|
+
const step = Number(rawStep);
|
|
1022
|
+
if (!Number.isInteger(step) || step < 1) {
|
|
1023
|
+
return { ok: false, error: 'tutorial step must be a positive integer' };
|
|
1024
|
+
}
|
|
1025
|
+
return { ok: true, value: step };
|
|
1026
|
+
}
|
|
1027
|
+
function tutorialStepPayload(tutorial, stepNumber) {
|
|
1028
|
+
const step = tutorial.steps[stepNumber - 1];
|
|
1029
|
+
return {
|
|
1030
|
+
stepNumber,
|
|
1031
|
+
totalSteps: tutorial.steps.length,
|
|
1032
|
+
completed: !step,
|
|
1033
|
+
title: step?.title,
|
|
1034
|
+
content: step?.content,
|
|
1035
|
+
code: step?.code
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
function warnFallback(result, io) {
|
|
1039
|
+
if (result.fallbackReason) {
|
|
1040
|
+
writeLine(io.stderr, `API unavailable, using offline data (refreshed ${dataRefreshedAt}): ${result.fallbackReason}`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function sourcePayload(result, payload) {
|
|
1044
|
+
return {
|
|
1045
|
+
source: result.source,
|
|
1046
|
+
apiUrl: result.apiUrl,
|
|
1047
|
+
fallbackReason: result.fallbackReason,
|
|
1048
|
+
...payload
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function authStatusPayload(dataDir, auth) {
|
|
1052
|
+
return {
|
|
1053
|
+
dataDir,
|
|
1054
|
+
statePath: getAgoraStatePath(dataDir),
|
|
1055
|
+
authenticated: Boolean(auth),
|
|
1056
|
+
apiUrl: auth?.apiUrl,
|
|
1057
|
+
tokenPreview: auth ? maskToken(auth.token) : undefined,
|
|
1058
|
+
savedAt: auth?.savedAt
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function maskToken(token) {
|
|
1062
|
+
const value = token.trim();
|
|
1063
|
+
if (value.length <= 4)
|
|
1064
|
+
return '****';
|
|
1065
|
+
if (value.length <= 8)
|
|
1066
|
+
return `${'*'.repeat(value.length - 4)}${value.slice(-4)}`;
|
|
1067
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
1068
|
+
}
|
|
1069
|
+
function matchesSavedQuery(entry, query) {
|
|
1070
|
+
if (!query)
|
|
1071
|
+
return true;
|
|
1072
|
+
const searchable = entry.item
|
|
1073
|
+
? [
|
|
1074
|
+
entry.item.id,
|
|
1075
|
+
entry.item.name,
|
|
1076
|
+
entry.item.description,
|
|
1077
|
+
entry.item.author,
|
|
1078
|
+
entry.item.category,
|
|
1079
|
+
...entry.item.tags
|
|
1080
|
+
].join(' ')
|
|
1081
|
+
: entry.saved.id;
|
|
1082
|
+
return searchable.toLowerCase().includes(query);
|
|
1083
|
+
}
|
|
1084
|
+
function normalizeFlag(flag) {
|
|
1085
|
+
return flag.trim().replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
1086
|
+
}
|
|
1087
|
+
function writeJson(stream, value) {
|
|
1088
|
+
writeLine(stream, JSON.stringify(value, null, 2));
|
|
1089
|
+
}
|
|
1090
|
+
function writeLine(stream, value = '') {
|
|
1091
|
+
stream.write(value.endsWith('\n') ? value : `${value}\n`);
|
|
1092
|
+
}
|
|
1093
|
+
function truncate(value, max) {
|
|
1094
|
+
if (value.length <= max)
|
|
1095
|
+
return value;
|
|
1096
|
+
return `${value.slice(0, max - 3)}...`;
|
|
1097
|
+
}
|
|
1098
|
+
function formatCount(value) {
|
|
1099
|
+
if (value >= 1000000)
|
|
1100
|
+
return `${(value / 1000000).toFixed(1)}M`;
|
|
1101
|
+
if (value >= 1000)
|
|
1102
|
+
return `${(value / 1000).toFixed(1)}K`;
|
|
1103
|
+
return String(value);
|
|
1104
|
+
}
|
|
1105
|
+
function formatDate(value) {
|
|
1106
|
+
return value.slice(0, 10);
|
|
1107
|
+
}
|
|
1108
|
+
export function listKnownItems() {
|
|
1109
|
+
return getMarketplaceItems();
|
|
1110
|
+
}
|
|
1111
|
+
//# sourceMappingURL=app.js.map
|