opencode-agora 0.3.0 → 0.4.1

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