@mercury-fw/cli 0.52.0 → 0.53.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.
- package/CHANGELOG.md +21 -0
- package/README.md +1 -1
- package/dist/src/catalog.d.ts +18 -0
- package/dist/src/render.d.ts +3 -0
- package/package.json +7 -5
- package/src/catalog.ts +60 -0
- package/src/main.ts +2 -2
- package/src/render.ts +24 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @mercury-fw/cli
|
|
2
2
|
|
|
3
|
+
## 0.53.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @mercury-fw/core@0.53.1
|
|
8
|
+
- @mercury-fw/utils@0.53.1
|
|
9
|
+
|
|
10
|
+
## 0.53.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- 79a232e: - New package `@mercury-fw/web-search-searxng`: a SearXNG instance as the web search plugin's backend, through its JSON format.
|
|
15
|
+
- `mfw create --plugins web-search` adds the web search plugin with the SearXNG backend, a `searxng` service in the compose file (on the compose network only), its `searxng/settings.yml` with the JSON format on, and `SEARXNG_URL` and `SEARXNG_SECRET` in the env example.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [56e4ed5]
|
|
20
|
+
- Updated dependencies [c83b129]
|
|
21
|
+
- @mercury-fw/core@0.53.0
|
|
22
|
+
- @mercury-fw/utils@0.53.0
|
|
23
|
+
|
|
3
24
|
## 0.52.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ Then it runs `bun install` in the app and creates a git repository on `main` wit
|
|
|
61
61
|
| `--assistant-name <name>` | The assistant's name (default `Mercury`). |
|
|
62
62
|
| `--role <text>` | Completes "You are <name>, …" (default `an internal assistant`). |
|
|
63
63
|
| `--channels <ids>` | Comma-separated: `google-chat`, `http`. |
|
|
64
|
-
| `--plugins <ids>` | Comma-separated: `jira`, `bitbucket`, `atlassian-admin`, `zitadel
|
|
64
|
+
| `--plugins <ids>` | Comma-separated: `jira`, `bitbucket`, `atlassian-admin`, `zitadel`, `web-search` (with a SearXNG service next to the app: set `SEARXNG_SECRET` in the env file). |
|
|
65
65
|
| `--auth <id>` | The HTTP channel's auth provider, `oidc` or `static`: required with `http`, refused without it, since the channel doesn't start without one. The wizard asks for it once `http` is chosen. |
|
|
66
66
|
| `--directory <id>` | The user directory, `static` or `zitadel`: who the people are and their roles. Without one, the person is whoever the channel says, with no roles. |
|
|
67
67
|
| `--local-packages <folder>` | Installs the packages packed in `<folder>` (`bun pm pack`) instead of the registry's, as [`mfw local-packages`](#mfw-local-packages-folder--mfw-local-packages---off) does on an existing app: their versions come from the tarballs, so a package that isn't published yet works too, and no newer `mfw` is looked up on the registry. For trying unreleased packages in a new app; the test bed makes its apps this way. |
|
package/dist/src/catalog.d.ts
CHANGED
|
@@ -26,6 +26,24 @@ export type CatalogEntry = {
|
|
|
26
26
|
/** Dependencies whose install scripts Bun must run for this entry (Bun runs
|
|
27
27
|
* none it isn't told to trust), on top of a tool plugin's own package. */
|
|
28
28
|
trusts?: string[];
|
|
29
|
+
/** For a plugin built by calling its export: what the config writes in
|
|
30
|
+
* `plugins` (indented as an element of it) and the other imports that needs,
|
|
31
|
+
* whose packages the app depends on too. */
|
|
32
|
+
build?: {
|
|
33
|
+
expression: string;
|
|
34
|
+
imports: Array<{
|
|
35
|
+
names: string;
|
|
36
|
+
package: string;
|
|
37
|
+
}>;
|
|
38
|
+
};
|
|
39
|
+
/** A compose service the entry needs next to the app (its lines under
|
|
40
|
+
* `services:`), which the app then depends on. */
|
|
41
|
+
service?: {
|
|
42
|
+
name: string;
|
|
43
|
+
lines: string[];
|
|
44
|
+
};
|
|
45
|
+
/** Files the entry adds to the app, by path. */
|
|
46
|
+
files?: Record<string, string>;
|
|
29
47
|
};
|
|
30
48
|
/** Starting formatter rules for a plugin that hands the user lists: without a
|
|
31
49
|
* rule a kind of list isn't shown, so the scaffolded config wraps the plugin
|
package/dist/src/render.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CatalogEntry } from "./catalog.ts";
|
|
1
2
|
/** What the new app is made of. `versions` maps each package the app depends
|
|
2
3
|
* on to the version its range is written against. */
|
|
3
4
|
export type RenderInput = {
|
|
@@ -28,3 +29,5 @@ export declare function selectionError(channels: string[], plugins: string[], au
|
|
|
28
29
|
* Undefined when they go together. Shared with the command, which checks it
|
|
29
30
|
* before writing when nothing will ask (`--yes`). */
|
|
30
31
|
export declare function pairingError(channels: string[], auth?: string): string | undefined;
|
|
32
|
+
/** The packages an entry brings into the app: its own, and those its config imports. */
|
|
33
|
+
export declare function entryPackages(e: CatalogEntry): string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,22 +41,24 @@
|
|
|
41
41
|
"comment:shape": "The Mercury CLI (`mfw`). `mfw create <dir>` writes a new Mercury app from the template. The framework packages move in lockstep with it, so a new app gets them at the CLI's own version; plugins and channels at the registry's latest. The plugin and channel packages are devDependencies only for the catalog test. `main(argv)` is exported for create-mercury-agent.",
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@clack/prompts": "^1.8.1",
|
|
44
|
-
"@mercury-fw/core": "0.
|
|
45
|
-
"@mercury-fw/utils": "0.
|
|
44
|
+
"@mercury-fw/core": "0.53.1",
|
|
45
|
+
"@mercury-fw/utils": "0.53.1",
|
|
46
46
|
"commander": "^15.0.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@mercury-fw/auth-oidc": "0.2.0",
|
|
50
50
|
"@mercury-fw/auth-static": "0.2.1",
|
|
51
|
-
"@mercury-fw/channel-google-chat": "0.8.
|
|
51
|
+
"@mercury-fw/channel-google-chat": "0.8.2",
|
|
52
52
|
"@mercury-fw/channel-http": "0.10.1",
|
|
53
53
|
"@mercury-fw/directory-static": "0.1.1",
|
|
54
|
-
"@mercury-fw/formatter": "0.
|
|
54
|
+
"@mercury-fw/formatter": "0.53.1",
|
|
55
55
|
"@mercury-fw/plugin-atlassian-admin": "0.3.2",
|
|
56
56
|
"@mercury-fw/plugin-bitbucket": "0.4.1",
|
|
57
57
|
"@mercury-fw/plugin-jira": "0.6.5",
|
|
58
|
+
"@mercury-fw/plugin-web-search": "0.1.2",
|
|
58
59
|
"@mercury-fw/plugin-zitadel": "0.3.2",
|
|
59
60
|
"@mercury-fw/typescript-config": "*",
|
|
61
|
+
"@mercury-fw/web-search-searxng": "0.1.0",
|
|
60
62
|
"@types/bun": "^1.4.2",
|
|
61
63
|
"typescript": "^6.0.3"
|
|
62
64
|
}
|
package/src/catalog.ts
CHANGED
|
@@ -24,8 +24,33 @@ export type CatalogEntry = {
|
|
|
24
24
|
/** Dependencies whose install scripts Bun must run for this entry (Bun runs
|
|
25
25
|
* none it isn't told to trust), on top of a tool plugin's own package. */
|
|
26
26
|
trusts?: string[];
|
|
27
|
+
/** For a plugin built by calling its export: what the config writes in
|
|
28
|
+
* `plugins` (indented as an element of it) and the other imports that needs,
|
|
29
|
+
* whose packages the app depends on too. */
|
|
30
|
+
build?: { expression: string; imports: Array<{ names: string; package: string }> };
|
|
31
|
+
/** A compose service the entry needs next to the app (its lines under
|
|
32
|
+
* `services:`), which the app then depends on. */
|
|
33
|
+
service?: { name: string; lines: string[] };
|
|
34
|
+
/** Files the entry adds to the app, by path. */
|
|
35
|
+
files?: Record<string, string>;
|
|
27
36
|
};
|
|
28
37
|
|
|
38
|
+
/** SearXNG's settings for the web search plugin, mounted into its container. */
|
|
39
|
+
const SEARXNG_SETTINGS = `# SearXNG for the web search plugin. Only the app reaches it, on the compose
|
|
40
|
+
# network: nothing of it is published on the host. The secret comes from
|
|
41
|
+
# SEARXNG_SECRET in the app's env file.
|
|
42
|
+
use_default_settings: true
|
|
43
|
+
server:
|
|
44
|
+
# Only Mercury asks: no rate limiting, no image proxy.
|
|
45
|
+
limiter: false
|
|
46
|
+
image_proxy: false
|
|
47
|
+
search:
|
|
48
|
+
# The plugin reads the results as JSON, which is off by default.
|
|
49
|
+
formats:
|
|
50
|
+
- html
|
|
51
|
+
- json
|
|
52
|
+
`;
|
|
53
|
+
|
|
29
54
|
/** Starting formatter rules for a plugin that hands the user lists: without a
|
|
30
55
|
* rule a kind of list isn't shown, so the scaffolded config wraps the plugin
|
|
31
56
|
* with these. `displaysType` is the type the plugin exports for its kinds,
|
|
@@ -124,6 +149,41 @@ export const CATALOG: CatalogEntry[] = [
|
|
|
124
149
|
exportName: "zitadelPlugin",
|
|
125
150
|
env: [],
|
|
126
151
|
},
|
|
152
|
+
{
|
|
153
|
+
id: "web-search",
|
|
154
|
+
kind: "tool",
|
|
155
|
+
package: "@mercury-fw/plugin-web-search",
|
|
156
|
+
exportName: "webSearchPlugin",
|
|
157
|
+
env: [
|
|
158
|
+
{ name: "SEARXNG_URL", comment: "SearXNG, the compose service the web search asks", value: "http://searxng:8080" },
|
|
159
|
+
{ name: "SEARXNG_SECRET", comment: "Required: SearXNG's secret key, any long random string (openssl rand -hex 32)" },
|
|
160
|
+
],
|
|
161
|
+
build: {
|
|
162
|
+
expression: [
|
|
163
|
+
" webSearchPlugin({",
|
|
164
|
+
' backend: searxngBackend({ url: process.env.SEARXNG_URL || "http://searxng:8080" }),',
|
|
165
|
+
" // Hosts whose pages anyone may read without being asked; each covers its subdomains.",
|
|
166
|
+
" allowedHosts: [],",
|
|
167
|
+
" })",
|
|
168
|
+
].join("\n"),
|
|
169
|
+
imports: [{ names: "searxngBackend", package: "@mercury-fw/web-search-searxng" }],
|
|
170
|
+
},
|
|
171
|
+
service: {
|
|
172
|
+
name: "searxng",
|
|
173
|
+
lines: [
|
|
174
|
+
" searxng:",
|
|
175
|
+
" # SearXNG publishes no major version tags, only latest and dated ones.",
|
|
176
|
+
" image: searxng/searxng:latest",
|
|
177
|
+
" restart: unless-stopped",
|
|
178
|
+
" environment:",
|
|
179
|
+
" # SearXNG exits on its default key: without one, compose says so instead.",
|
|
180
|
+
" SEARXNG_SECRET: ${SEARXNG_SECRET:?set SEARXNG_SECRET in .env (see .env.example)}",
|
|
181
|
+
" volumes:",
|
|
182
|
+
" - ./searxng/settings.yml:/etc/searxng/settings.yml:ro",
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
files: { "searxng/settings.yml": SEARXNG_SETTINGS },
|
|
186
|
+
},
|
|
127
187
|
{
|
|
128
188
|
id: "static",
|
|
129
189
|
kind: "directory",
|
package/src/main.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { CreateArgs } from "./args.ts";
|
|
|
11
11
|
import { CATALOG } from "./catalog.ts";
|
|
12
12
|
import { kebabCase } from "./naming.ts";
|
|
13
13
|
import { runProgram } from "./program.ts";
|
|
14
|
-
import { pairingError, renderApp, selectionError } from "./render.ts";
|
|
14
|
+
import { entryPackages, pairingError, renderApp, selectionError } from "./render.ts";
|
|
15
15
|
import { appVersions, cliVersion, newerCli, registryFrom } from "./versions.ts";
|
|
16
16
|
import { appCommands, terminalDeps, type AppDeps } from "./app/commands.ts";
|
|
17
17
|
import { copyPacks, readPacks, withLocalOverrides } from "./app/local-packages.ts";
|
|
@@ -147,7 +147,7 @@ async function create(args: CreateArgs, rawArgs: string[], relaunch: Relaunch, g
|
|
|
147
147
|
: e.kind === "directory"
|
|
148
148
|
? e.id === answers.directory
|
|
149
149
|
: (e.kind === "channel" ? answers.channels : answers.plugins).includes(e.id),
|
|
150
|
-
).
|
|
150
|
+
).flatMap(entryPackages),
|
|
151
151
|
),
|
|
152
152
|
];
|
|
153
153
|
const versions = await appVersions(chosen, {
|
package/src/render.ts
CHANGED
|
@@ -76,13 +76,16 @@ export function renderApp(input: RenderInput): Map<string, string> {
|
|
|
76
76
|
const directory = selected("directory", input.directory === undefined ? [] : [input.directory])[0];
|
|
77
77
|
const assistantName = input.assistantName.trim();
|
|
78
78
|
|
|
79
|
+
const extraFiles = [...channels, ...tools].flatMap((e) => Object.entries(e.files ?? {}));
|
|
80
|
+
const services = tools.flatMap((t) => (t.service ? [t.service] : []));
|
|
79
81
|
return new Map([
|
|
82
|
+
...extraFiles,
|
|
80
83
|
[".dockerignore", dockerignore],
|
|
81
84
|
[".env.example", renderEnv(channels, auth, tools, directory)],
|
|
82
85
|
[".gitignore", gitignore],
|
|
83
86
|
["Dockerfile", dockerfile],
|
|
84
87
|
["README.md", renderReadme(input.name, channels, auth, tools, directory)],
|
|
85
|
-
["docker-compose.yml", renderCompose(input.name, channels.some((c) => c.id === "http"))],
|
|
88
|
+
["docker-compose.yml", renderCompose(input.name, channels.some((c) => c.id === "http"), services)],
|
|
86
89
|
["markdown.d.ts", markdownDts],
|
|
87
90
|
["mercury.config.ts", renderConfig(assistantName, channels, auth, tools, directory)],
|
|
88
91
|
["package.json", renderPackageJson(input.name, channels, auth, tools, input.versions, directory)],
|
|
@@ -184,6 +187,7 @@ function renderConfig(
|
|
|
184
187
|
for (const t of tools) {
|
|
185
188
|
const names = t.formatter ? `${t.exportName}, type ${t.formatter.displaysType}` : t.exportName;
|
|
186
189
|
imports.push(`import { ${names} } from "${t.package}";`);
|
|
190
|
+
for (const extra of t.build?.imports ?? []) imports.push(`import { ${extra.names} } from "${extra.package}";`);
|
|
187
191
|
}
|
|
188
192
|
for (const c of [...channels, ...(auth ? [auth] : []), ...(directory ? [directory] : [])]) {
|
|
189
193
|
imports.push(`import { ${c.exportName} } from "${c.package}";`);
|
|
@@ -216,6 +220,7 @@ function renderConfig(
|
|
|
216
220
|
|
|
217
221
|
/** One element of the config's `plugins` array. */
|
|
218
222
|
function renderPluginEntry(t: CatalogEntry): string {
|
|
223
|
+
if (t.build) return `${t.build.expression},`;
|
|
219
224
|
if (!t.formatter) {
|
|
220
225
|
return ` ${t.exportName},`;
|
|
221
226
|
}
|
|
@@ -229,6 +234,11 @@ function renderPluginEntry(t: CatalogEntry): string {
|
|
|
229
234
|
].join("\n");
|
|
230
235
|
}
|
|
231
236
|
|
|
237
|
+
/** The packages an entry brings into the app: its own, and those its config imports. */
|
|
238
|
+
export function entryPackages(e: CatalogEntry): string[] {
|
|
239
|
+
return [e.package, ...(e.build?.imports ?? []).map((i) => i.package)];
|
|
240
|
+
}
|
|
241
|
+
|
|
232
242
|
/** `package.json`: the core, the formatter when a plugin is wrapped in it, the
|
|
233
243
|
* chosen packages, and every tool plugin trusted to run its postinstall (it
|
|
234
244
|
* downloads the plugin's CLI), plus what a chosen entry says it needs trusted. */
|
|
@@ -241,7 +251,7 @@ function renderPackageJson(
|
|
|
241
251
|
directory: CatalogEntry | undefined,
|
|
242
252
|
): string {
|
|
243
253
|
const chosen = [...channels, ...(auth ? [auth] : []), ...tools, ...(directory ? [directory] : [])];
|
|
244
|
-
const packages = [...new Set(["@mercury-fw/core", ...chosen.
|
|
254
|
+
const packages = [...new Set(["@mercury-fw/core", ...chosen.flatMap(entryPackages)])];
|
|
245
255
|
if (tools.some((t) => t.formatter)) {
|
|
246
256
|
packages.push("@mercury-fw/formatter");
|
|
247
257
|
}
|
|
@@ -297,8 +307,8 @@ function renderEnv(
|
|
|
297
307
|
/** `docker-compose.yml`: the app and Qdrant, with named volumes prefixed by the
|
|
298
308
|
* app's name, the CLI credentials one included whatever was chosen (a plugin
|
|
299
309
|
* with a CLI can come later); the HTTP surface's port published on the host
|
|
300
|
-
* only with the HTTP channel. */
|
|
301
|
-
function renderCompose(name: string, hasHttp: boolean): string {
|
|
310
|
+
* only with the HTTP channel; and the services chosen plugins need. */
|
|
311
|
+
function renderCompose(name: string, hasHttp: boolean, services: Array<{ name: string; lines: string[] }>): string {
|
|
302
312
|
const httpPort = hasHttp
|
|
303
313
|
? [
|
|
304
314
|
" # The HTTP surface, on the host: every route but its OpenAPI document needs a token.",
|
|
@@ -325,12 +335,14 @@ function renderCompose(name: string, hasHttp: boolean): string {
|
|
|
325
335
|
' - "host.docker.internal:host-gateway"',
|
|
326
336
|
" depends_on:",
|
|
327
337
|
" - qdrant",
|
|
338
|
+
...services.map((s) => ` - ${s.name}`),
|
|
328
339
|
"",
|
|
329
340
|
" qdrant:",
|
|
330
341
|
" image: qdrant/qdrant:v1",
|
|
331
342
|
" volumes:",
|
|
332
343
|
" - qdrant-data:/qdrant/storage",
|
|
333
344
|
"",
|
|
345
|
+
...services.flatMap((s) => [...s.lines, ""]),
|
|
334
346
|
"volumes:",
|
|
335
347
|
" wiki-vault:",
|
|
336
348
|
` name: ${name}_wiki-vault`,
|
|
@@ -351,6 +363,13 @@ function renderReadme(
|
|
|
351
363
|
directory: CatalogEntry | undefined,
|
|
352
364
|
): string {
|
|
353
365
|
const list = (entries: CatalogEntry[]) => (entries.length > 0 ? entries.map((e) => e.id).join(", ") : "none");
|
|
366
|
+
// What the app can't start without, named where the reader is about to start it.
|
|
367
|
+
const required = [...channels, ...(auth ? [auth] : []), ...tools, ...(directory ? [directory] : [])]
|
|
368
|
+
.flatMap((e) => e.env)
|
|
369
|
+
.filter((v) => v.comment.startsWith("Required:"))
|
|
370
|
+
.map((v) => `\`${v.name}\``);
|
|
371
|
+
const requiredNote =
|
|
372
|
+
required.length > 0 ? `\nBefore \`mfw start\`, fill in the variables the chosen pieces require: ${required.join(", ")}.\n` : "";
|
|
354
373
|
return `# ${name}
|
|
355
374
|
|
|
356
375
|
A Mercury app, scaffolded by \`mfw create\`.
|
|
@@ -381,7 +400,7 @@ cp .env.example .env
|
|
|
381
400
|
mfw start
|
|
382
401
|
mfw repl
|
|
383
402
|
\`\`\`
|
|
384
|
-
|
|
403
|
+
${requiredNote}
|
|
385
404
|
\`bun install\` here gives your editor, \`bun run typecheck\` and the app's own \`mfw\` (the one a global \`mfw\` runs inside the app) the packages (tool plugins download their CLI binary as they install); the image installs its own copy when it builds. \`mfw start\` builds the image and starts the app with Qdrant in the background, \`mfw repl\` opens a terminal conversation with the assistant. \`mfw --help\` lists the rest: stopping and restarting, logs, a shell in the container, the wiki and the memory, and resetting them.
|
|
386
405
|
${renderHttpSection(channels, auth)}${CREDENTIALS_SECTION}`;
|
|
387
406
|
}
|