@mercury-fw/cli 0.53.1 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +3 -3
- package/dist/src/catalog.d.ts +4 -1
- package/package.json +11 -11
- package/src/catalog.ts +6 -3
- package/src/program.ts +1 -1
- package/src/render.ts +55 -19
- package/template/Dockerfile.tpl +2 -2
- package/template/src/repl.ts.tpl +1 -0
- package/template/markdown.d.ts.tpl +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @mercury-fw/cli
|
|
2
2
|
|
|
3
|
+
## 0.54.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ebde9e2: The texts a person sees can be in their language. The core's own fixed texts (the refusal of someone the agent doesn't know, the account-linking replies, the login errors, the tool status lines, the terminal's own lines) are English, each under a key (`core.access.unknown`, …), and an app adds a language with a file of its own: `i18n: { defaultLocale, locales: { it } }` in `mercury.config.ts`, each file mapping keys to texts. A person picks their language by asking the agent, which saves it with `set_preferred_language` (only the languages the app ships; not offered when it ships English alone) and answers in it from then on. Whoever hasn't picked one gets `defaultLocale`, then English. A key a language's file lacks shows as the key itself, and the log names the missing keys at startup. `GET /me` on the HTTP channel also says the caller's language.
|
|
8
|
+
|
|
9
|
+
The texts that were in Italian (tool status lines, the terminal's lines) are now English by default, and the language is no longer one of the facts inferred from conversations.
|
|
10
|
+
|
|
11
|
+
An existing app keeps working, with these changes:
|
|
12
|
+
|
|
13
|
+
1. `access.unknownMessage` is gone. To keep a custom refusal, put it in `locales/en.json` as `{ "core.access.unknown": "…" }`, and in `mercury.config.ts` import it (`import en from "./locales/en.json";`) and set `i18n: { locales: { en } }`.
|
|
14
|
+
2. To keep the status and terminal lines in Italian, add `locales/it.json` with the `core.` keys translated (start it as `{}`: the log lists the missing keys at startup), and in `mercury.config.ts` import it (`import it from "./locales/it.json";`) and set `i18n: { defaultLocale: "it", locales: { it } }` (`locales: { en, it }` when you did step 1 too).
|
|
15
|
+
3. Whenever the config imports a file from `locales/` (steps 1 and 2), the image needs the folder: add `COPY --chown=mercury:mercury locales ./locales` after the `mercury.config.ts` line of the `Dockerfile`, or the app fails at startup.
|
|
16
|
+
4. For the terminal (`mfw repl`) to follow the language too, add `texts: app.texts,` to `createTerminalProvider({ … })` in `src/repl.ts`; without it the terminal's own lines stay English.
|
|
17
|
+
|
|
18
|
+
- 5015642: The plugins' texts a person sees come out in their language, like the core's. Plugin contract version 5:
|
|
19
|
+
|
|
20
|
+
- A plugin declares its texts in English in `messages`, every key under its own name (`web-search.choice.once`); the core refuses a plugin with a key outside it (and a plugin named `core` or `app`, whose keys are the core's and the app's), and the app's language files translate them. The startup log names the missing keys of the plugins too.
|
|
21
|
+
- While a turn runs, a plugin gets the texts of its person as `t`: in `SessionToolContext.t`, as the second argument of a status describer (`toolStatusDescribers`, `StatusDescriber`), the third of a post-processor (`CliPostProcessor`), and in a post-turn guard, whose `statusLabel` is now `(t) => string` and whose `run` gets `t` as its third argument. `Messages`, `Params` and `Translate` are part of the contract.
|
|
22
|
+
- The default status line of a CLI command is the core's `core.status.command` ("Running jira issue search"), `core.status.command-bare` without a subcommand ("Running jira"), and an unreadable command's is `core.status.command-unknown`. `createCliTool` (`@mercury-fw/cli-engine`) takes the turn's `t` among its options, handed to the post-processor: a CLI plugin passes `sctx.t`; `createCliStatusDescriber`'s describer takes it as its second argument.
|
|
23
|
+
- The formatter hands a rule `t` as its second argument, and an `empty` text can be `(t) => string`; existing rules keep working as they are. An app's own texts go under `app.`, in its `locales/en.json`, which declares them (the startup log no longer lists them as unknown); `mfw create` writes the Jira rule's empty text as `app.jira.no-issues`.
|
|
24
|
+
- Web search: the two choices ("Only this time", "Always for \<host\>"), the status lines and the sources heading ("Sources") are `web-search.*` texts, English by default; a list of sources the model writes under the heading in the person's language, or the English one, is still replaced by the plugin's.
|
|
25
|
+
|
|
26
|
+
The first-party plugins released with this change are written for contract 5 and need the core released with it: an older core skips them and says why in the log. A plugin written for contract 4 isn't loaded by this core either, and the log says why: rebuild it against `PLUGIN_API_VERSION` 5 (a status describer, a guard's `statusLabel` and `run`, a post-processor take the new `t`). An app that wants the web search and status lines back in Italian adds their keys to `locales/it.json` (the log lists the missing ones at startup).
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Updated dependencies [e00ff73]
|
|
31
|
+
- Updated dependencies [ebde9e2]
|
|
32
|
+
- Updated dependencies [5015642]
|
|
33
|
+
- @mercury-fw/core@0.54.0
|
|
34
|
+
- @mercury-fw/utils@0.54.0
|
|
35
|
+
|
|
36
|
+
## 0.53.2
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- 4a9a528: `mfw create` writes the whole persona inside `mercury.config.ts`: the assistant's name once, as a `const`, referred to by the identity and by the tone, which is written out in full, one string per line. There are no `persona/identity.md` and `persona/tone.md` files anymore, no `markdown.d.ts`, and the Dockerfile no longer copies them.
|
|
41
|
+
|
|
42
|
+
An existing app keeps working as it is. To move it to the new layout:
|
|
43
|
+
|
|
44
|
+
1. In `mercury.config.ts`, drop the two `import … from "./persona/…md"` lines, add `const name = "<the assistant's name>";` and set `persona: { name, identity: "…", tone: "…" }` with the text of the two files (`${name}` in a template literal where they say `{{assistant}}`, or keep `{{assistant}}`: the core still replaces it).
|
|
45
|
+
2. Delete `persona/` and `markdown.d.ts`.
|
|
46
|
+
3. In the `Dockerfile`, change `COPY --chown=mercury:mercury mercury.config.ts markdown.d.ts ./` to `COPY --chown=mercury:mercury mercury.config.ts ./` and delete `COPY --chown=mercury:mercury persona ./persona`.
|
|
47
|
+
|
|
48
|
+
- @mercury-fw/core@0.53.2
|
|
49
|
+
- @mercury-fw/utils@0.53.2
|
|
50
|
+
|
|
3
51
|
## 0.53.1
|
|
4
52
|
|
|
5
53
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -47,11 +47,11 @@ Without a global install: `bun create mercury-agent my-agent` creates an app, an
|
|
|
47
47
|
Writes a new Mercury app into `<folder>`, which has to be missing or empty; the folder's own name is turned into kebab case (`My Agent` becomes `my-agent`, the path above it stays as typed). Without flags it asks:
|
|
48
48
|
|
|
49
49
|
- the app name (the `name` in `package.json`, defaulting to the folder's);
|
|
50
|
-
- the assistant's name and role: the
|
|
50
|
+
- the assistant's name and role: the whole persona goes in the config, the name written once as a `const` and referred to by the identity (`` `You are ${name}, the platform team's release assistant.` ``) and by the tone, written out in full to edit;
|
|
51
51
|
- which channels and which tool plugins to include (none is fine: the REPL always works);
|
|
52
52
|
- the git remote for `origin`, which you can leave empty (most of the time it doesn't exist yet when you scaffold).
|
|
53
53
|
|
|
54
|
-
It writes the `mercury.config.ts` for that selection, the persona, the service and REPL entrypoints, a Dockerfile, a compose file with Qdrant, and an env example listing every variable the chosen pieces read. A plugin that hands over lists comes wrapped in the formatter with a starting rule, yours to change.
|
|
54
|
+
It writes the `mercury.config.ts` for that selection, the persona, a `locales/` folder with an `en.json` for the texts people see (empty, or the starting formatter rules' own texts: `app.jira.no-issues` with Jira), the service and REPL entrypoints, a Dockerfile, a compose file with Qdrant, and an env example listing every variable the chosen pieces read. A plugin that hands over lists comes wrapped in the formatter with a starting rule, yours to change.
|
|
55
55
|
|
|
56
56
|
Then it runs `bun install` in the app and creates a git repository on `main` with a first commit (`bun.lock` included), adding `origin` when you gave one; nothing is pushed. Each step is best effort and never undoes the ones before it: an install that fails still leaves the app committed, without the lockfile, and the closing message says what to run. The repository is skipped when the folder is already inside one (an app created in a monorepo belongs to it). Whether git can commit is git's call: when it refuses (no identity, a hook) or isn't installed, the message reports why and lists the commands to finish by hand. `bun install` prints its own output as it runs.
|
|
57
57
|
|
|
@@ -341,7 +341,7 @@ export default e2e({
|
|
|
341
341
|
"Delete the issue SUP-1 on Jira",
|
|
342
342
|
{ text: (previous) => (previous.calls.find((c) => c.pending)?.output as { token: string }).token, as: "bob" },
|
|
343
343
|
],
|
|
344
|
-
check: (run, expect) => expect.answerNot("
|
|
344
|
+
check: (run, expect) => expect.answerNot("Confirmed"),
|
|
345
345
|
},
|
|
346
346
|
],
|
|
347
347
|
});
|
package/dist/src/catalog.d.ts
CHANGED
|
@@ -49,11 +49,14 @@ export type CatalogEntry = {
|
|
|
49
49
|
* rule a kind of list isn't shown, so the scaffolded config wraps the plugin
|
|
50
50
|
* with these. `displaysType` is the type the plugin exports for its kinds,
|
|
51
51
|
* `helpers` the code the rules use (written above the config), `rules` the
|
|
52
|
-
* `kind: rule` lines
|
|
52
|
+
* `kind: rule` lines, `messages` the English texts they show the person
|
|
53
|
+
* (`app.` keys, written into the app's `locales/en.json`). Once written,
|
|
54
|
+
* they're the app's to change. */
|
|
53
55
|
export type FormatterExample = {
|
|
54
56
|
displaysType: string;
|
|
55
57
|
helpers: string;
|
|
56
58
|
rules: string[];
|
|
59
|
+
messages?: Record<string, string>;
|
|
57
60
|
};
|
|
58
61
|
export declare const CATALOG: CatalogEntry[];
|
|
59
62
|
/** The catalog entry of `kind` with `id`, or undefined when there is none. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,22 +41,22 @@
|
|
|
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.54.0",
|
|
45
|
+
"@mercury-fw/utils": "0.54.0",
|
|
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.
|
|
52
|
-
"@mercury-fw/channel-http": "0.
|
|
51
|
+
"@mercury-fw/channel-google-chat": "0.9.0",
|
|
52
|
+
"@mercury-fw/channel-http": "0.11.0",
|
|
53
53
|
"@mercury-fw/directory-static": "0.1.1",
|
|
54
|
-
"@mercury-fw/formatter": "0.
|
|
55
|
-
"@mercury-fw/plugin-atlassian-admin": "0.
|
|
56
|
-
"@mercury-fw/plugin-bitbucket": "0.
|
|
57
|
-
"@mercury-fw/plugin-jira": "0.
|
|
58
|
-
"@mercury-fw/plugin-web-search": "0.
|
|
59
|
-
"@mercury-fw/plugin-zitadel": "0.
|
|
54
|
+
"@mercury-fw/formatter": "0.54.0",
|
|
55
|
+
"@mercury-fw/plugin-atlassian-admin": "0.4.0",
|
|
56
|
+
"@mercury-fw/plugin-bitbucket": "0.5.0",
|
|
57
|
+
"@mercury-fw/plugin-jira": "0.7.0",
|
|
58
|
+
"@mercury-fw/plugin-web-search": "0.2.0",
|
|
59
|
+
"@mercury-fw/plugin-zitadel": "0.4.0",
|
|
60
60
|
"@mercury-fw/typescript-config": "*",
|
|
61
61
|
"@mercury-fw/web-search-searxng": "0.1.0",
|
|
62
62
|
"@types/bun": "^1.4.2",
|
package/src/catalog.ts
CHANGED
|
@@ -55,8 +55,10 @@ search:
|
|
|
55
55
|
* rule a kind of list isn't shown, so the scaffolded config wraps the plugin
|
|
56
56
|
* with these. `displaysType` is the type the plugin exports for its kinds,
|
|
57
57
|
* `helpers` the code the rules use (written above the config), `rules` the
|
|
58
|
-
* `kind: rule` lines
|
|
59
|
-
|
|
58
|
+
* `kind: rule` lines, `messages` the English texts they show the person
|
|
59
|
+
* (`app.` keys, written into the app's `locales/en.json`). Once written,
|
|
60
|
+
* they're the app's to change. */
|
|
61
|
+
export type FormatterExample = { displaysType: string; helpers: string; rules: string[]; messages?: Record<string, string> };
|
|
60
62
|
|
|
61
63
|
export const CATALOG: CatalogEntry[] = [
|
|
62
64
|
{
|
|
@@ -125,7 +127,8 @@ export const CATALOG: CatalogEntry[] = [
|
|
|
125
127
|
'const jiraIssueLine = (issue: JiraDisplays["issue-list"]) =>',
|
|
126
128
|
' `${issue.key} ${issue.status ? `[${issue.status}] ` : ""}${issue.summary}\\n${issue.url}`;',
|
|
127
129
|
].join("\n"),
|
|
128
|
-
rules: ['"issue-list": { item: jiraIssueLine, empty:
|
|
130
|
+
rules: ['"issue-list": { item: jiraIssueLine, empty: (t) => t("app.jira.no-issues") },'],
|
|
131
|
+
messages: { "app.jira.no-issues": "No matching issues." },
|
|
129
132
|
},
|
|
130
133
|
},
|
|
131
134
|
{
|
package/src/program.ts
CHANGED
|
@@ -52,7 +52,7 @@ function buildProgram(handlers: ProgramHandlers, result: { code: number }): Comm
|
|
|
52
52
|
.command("create")
|
|
53
53
|
.summary("writes a new Mercury app")
|
|
54
54
|
.description(
|
|
55
|
-
"Writes a new Mercury app into <folder>, which has to be missing or empty (its own name is turned into kebab case). Without options it asks for the app name, the assistant's name and role, and which channels (with the HTTP channel's auth provider) and tool plugins to include; then it writes mercury.config.ts for that selection, with the assistant's name as
|
|
55
|
+
"Writes a new Mercury app into <folder>, which has to be missing or empty (its own name is turned into kebab case). Without options it asks for the app name, the assistant's name and role, and which channels (with the HTTP channel's auth provider) and tool plugins to include; then it writes mercury.config.ts for that selection, with the whole persona in it (the assistant's name, its role as the identity, and the default tone written out to edit), the service and REPL entrypoints, a Dockerfile, a compose file with Qdrant, and an env example listing every variable the app reads. Then it runs bun install, and creates a git repository on main with a first commit, adding the origin when given (nothing is pushed).",
|
|
56
56
|
)
|
|
57
57
|
.argument("<folder>", "where to write the app")
|
|
58
58
|
.option("--name <name>", "app name, as in package.json (default: the folder's name)")
|
package/src/render.ts
CHANGED
|
@@ -3,16 +3,15 @@
|
|
|
3
3
|
* Turns the wizard's answers into the new app's files, as a `path → content`
|
|
4
4
|
* map, without touching the disk (`write.ts` does that). The static files come
|
|
5
5
|
* from `template/` imported as text, so they are bundled with the CLI; the
|
|
6
|
-
* config, the manifest, the env example, the compose file
|
|
7
|
-
* README are generated from the selection. Channels and plugins are always
|
|
6
|
+
* config (with the persona), the manifest, the env example, the compose file
|
|
7
|
+
* and the README are generated from the selection. Channels and plugins are always
|
|
8
8
|
* written in catalog order, whatever order they were chosen in; the auth
|
|
9
9
|
* provider, when there is one, after the channels.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { DEFAULT_PERSONA_TONE, assistantNameOf } from "@mercury-fw/core";
|
|
12
12
|
import { CATALOG, type CatalogEntry, type EnvVar } from "./catalog.ts";
|
|
13
13
|
import indexTs from "../template/src/index.ts.tpl" with { type: "text" };
|
|
14
14
|
import replTs from "../template/src/repl.ts.tpl" with { type: "text" };
|
|
15
|
-
import markdownDts from "../template/markdown.d.ts.tpl" with { type: "text" };
|
|
16
15
|
import tsconfigJson from "../template/tsconfig.json.tpl" with { type: "text" };
|
|
17
16
|
import gitignore from "../template/gitignore.tpl" with { type: "text" };
|
|
18
17
|
import dockerignore from "../template/dockerignore.tpl" with { type: "text" };
|
|
@@ -59,7 +58,8 @@ const CORE_ENV: EnvVar[] = [
|
|
|
59
58
|
const CONFIG_HEADER = `/**
|
|
60
59
|
* This app's composition: the tool plugins and channels it runs, the auth
|
|
61
60
|
* provider when a channel needs one, how the lists its plugins hand over read,
|
|
62
|
-
*
|
|
61
|
+
* the assistant's persona and the languages it speaks (\`i18n\`: a file per
|
|
62
|
+
* language in \`locales/\`, \`en.json\` overriding the English texts). The entrypoints
|
|
63
63
|
* (\`src/index.ts\`, \`src/repl.ts\`) hand this config to \`composeMercury\`.
|
|
64
64
|
*
|
|
65
65
|
* Every tool plugin and channel declared here is active.
|
|
@@ -86,12 +86,9 @@ export function renderApp(input: RenderInput): Map<string, string> {
|
|
|
86
86
|
["Dockerfile", dockerfile],
|
|
87
87
|
["README.md", renderReadme(input.name, channels, auth, tools, directory)],
|
|
88
88
|
["docker-compose.yml", renderCompose(input.name, channels.some((c) => c.id === "http"), services)],
|
|
89
|
-
["
|
|
90
|
-
["mercury.config.ts", renderConfig(assistantName, channels, auth, tools, directory)],
|
|
89
|
+
["locales/en.json", renderEnglish(tools)],
|
|
90
|
+
["mercury.config.ts", renderConfig(assistantName, input.role, channels, auth, tools, directory)],
|
|
91
91
|
["package.json", renderPackageJson(input.name, channels, auth, tools, input.versions, directory)],
|
|
92
|
-
// The name is persona.name in the config; the persona files refer to it.
|
|
93
|
-
["persona/identity.md", `You are {{assistant}}, ${input.role.trim().replace(/\.+$/, "")}.\n`],
|
|
94
|
-
["persona/tone.md", `${DEFAULT_PERSONA_TONE}\n`],
|
|
95
92
|
["src/index.ts", indexTs],
|
|
96
93
|
["src/repl.ts", replTs],
|
|
97
94
|
["tsconfig.json", tsconfigJson],
|
|
@@ -169,18 +166,55 @@ function selected(kind: CatalogEntry["kind"], ids: string[]): CatalogEntry[] {
|
|
|
169
166
|
return CATALOG.filter((e) => e.kind === kind && ids.includes(e.id));
|
|
170
167
|
}
|
|
171
168
|
|
|
172
|
-
/**
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
/** What the core's persona texts write where the assistant's name goes. */
|
|
170
|
+
const ASSISTANT = "{{assistant}}";
|
|
171
|
+
|
|
172
|
+
/** `text` as the inside of a template literal: backslashes, backticks and `${` stay text. */
|
|
173
|
+
function templateText(text: string): string {
|
|
174
|
+
return text.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** One line of the persona as config source: a template literal referring to
|
|
178
|
+
* the config's `name` where the line names the assistant, a string otherwise. */
|
|
179
|
+
function personaLine(line: string): string {
|
|
180
|
+
if (!line.includes(ASSISTANT)) return JSON.stringify(line);
|
|
181
|
+
return `\`${line.split(ASSISTANT).map(templateText).join("${name}")}\``;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The config's `persona`: the name, the role as the identity, and the core's
|
|
185
|
+
* default tone written out line by line, so the whole persona is there to edit. */
|
|
186
|
+
function renderPersona(role: string): string[] {
|
|
187
|
+
return [
|
|
188
|
+
" persona: {",
|
|
189
|
+
" name,",
|
|
190
|
+
` identity: ${personaLine(`You are ${ASSISTANT}, ${role.trim().replace(/\.+$/, "")}.`)},`,
|
|
191
|
+
" tone: [",
|
|
192
|
+
...DEFAULT_PERSONA_TONE.split("\n").map((line) => ` ${personaLine(line)},`),
|
|
193
|
+
' ].join("\\n"),',
|
|
194
|
+
" },",
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** `locales/en.json`: the English texts of the chosen plugins' starting
|
|
199
|
+
* formatter rules (the app's own `app.` keys), `{}` when there are none. */
|
|
200
|
+
function renderEnglish(tools: CatalogEntry[]): string {
|
|
201
|
+
const messages = Object.assign({}, ...tools.map((t) => t.formatter?.messages ?? {})) as Record<string, string>;
|
|
202
|
+
return Object.keys(messages).length === 0 ? "{}\n" : `${JSON.stringify(messages, null, 2)}\n`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** `mercury.config.ts`: imports, the assistant's name, the formatter helpers of
|
|
206
|
+
* the plugins that have any, then the config with the persona, each plugin
|
|
207
|
+
* (wrapped in the formatter when it has starting rules) and channel. */
|
|
175
208
|
function renderConfig(
|
|
176
209
|
assistantName: string,
|
|
210
|
+
role: string,
|
|
177
211
|
channels: CatalogEntry[],
|
|
178
212
|
auth: CatalogEntry | undefined,
|
|
179
213
|
tools: CatalogEntry[],
|
|
180
214
|
directory: CatalogEntry | undefined,
|
|
181
215
|
): string {
|
|
182
216
|
const withRules = tools.filter((t) => t.formatter);
|
|
183
|
-
const imports = ['import { defineMercuryConfig } from "@mercury-fw/core";'];
|
|
217
|
+
const imports = ['import { defineMercuryConfig } from "@mercury-fw/core";', 'import en from "./locales/en.json";'];
|
|
184
218
|
if (withRules.length > 0) {
|
|
185
219
|
imports.push('import { formatterPlugin, formatter } from "@mercury-fw/formatter";');
|
|
186
220
|
}
|
|
@@ -192,8 +226,6 @@ function renderConfig(
|
|
|
192
226
|
for (const c of [...channels, ...(auth ? [auth] : []), ...(directory ? [directory] : [])]) {
|
|
193
227
|
imports.push(`import { ${c.exportName} } from "${c.package}";`);
|
|
194
228
|
}
|
|
195
|
-
imports.push('import identity from "./persona/identity.md" with { type: "text" };');
|
|
196
|
-
imports.push('import tone from "./persona/tone.md" with { type: "text" };');
|
|
197
229
|
|
|
198
230
|
const helpers = withRules.map((t) => `${t.formatter?.helpers}\n\n`).join("");
|
|
199
231
|
|
|
@@ -207,12 +239,16 @@ function renderConfig(
|
|
|
207
239
|
CONFIG_HEADER,
|
|
208
240
|
...imports,
|
|
209
241
|
"",
|
|
242
|
+
"/** The assistant's name: what the persona calls it, and every text the model reads. */",
|
|
243
|
+
`const name = ${JSON.stringify(assistantName)};`,
|
|
244
|
+
"",
|
|
210
245
|
`${helpers}export default defineMercuryConfig({`,
|
|
211
|
-
|
|
246
|
+
...renderPersona(role),
|
|
212
247
|
plugins,
|
|
213
248
|
channelList,
|
|
214
249
|
...(auth ? [` auth: ${auth.exportName},`] : []),
|
|
215
250
|
...(directory ? [` directory: ${directory.exportName},`] : []),
|
|
251
|
+
" i18n: { locales: { en } },",
|
|
216
252
|
"});",
|
|
217
253
|
"",
|
|
218
254
|
].join("\n");
|
|
@@ -379,8 +415,8 @@ ${auth ? `- Auth: ${auth.id}\n` : ""}- Tool plugins: ${list(tools)}
|
|
|
379
415
|
${directory ? `- Directory: ${directory.id}\n` : ""}
|
|
380
416
|
## Layout
|
|
381
417
|
|
|
382
|
-
- \`mercury.config.ts\`: what the app is made of (tool plugins, channels, how their lists read) and the assistant's persona.
|
|
383
|
-
- \`
|
|
418
|
+
- \`mercury.config.ts\`: what the app is made of (tool plugins, channels, how their lists read) and the assistant's persona: its name, who it is and how it answers. Edit them freely.
|
|
419
|
+
- \`locales/\`: the texts people see, one file per language the assistant speaks. \`en.json\` overrides the English ones; add \`it.json\` (and \`it\` to \`i18n.locales\` in the config) for Italian, \`i18n.defaultLocale\` for the language people get when they haven't picked one. The app's own texts (a formatter rule's) are \`app.\` keys, written in English in \`en.json\` and translated in every other file. Mercury logs at startup the keys a file is missing.
|
|
384
420
|
- \`src/index.ts\`: the service (channels, crons). \`src/repl.ts\`: an interactive terminal for trying things out.
|
|
385
421
|
- \`.env.example\`: every variable the app reads. Copy it to \`.env\` and fill it in.
|
|
386
422
|
|
package/template/Dockerfile.tpl
CHANGED
|
@@ -20,8 +20,8 @@ COPY --chown=mercury:mercury .pack[s] ./.packs/
|
|
|
20
20
|
RUN bun install --production && chown -R mercury:mercury node_modules
|
|
21
21
|
RUN find /app/node_modules -path '*/@mercury-fw/*/bin/*' -type f -exec ln -sf {} /usr/local/bin/ \;
|
|
22
22
|
|
|
23
|
-
COPY --chown=mercury:mercury mercury.config.ts
|
|
24
|
-
COPY --chown=mercury:mercury
|
|
23
|
+
COPY --chown=mercury:mercury mercury.config.ts ./
|
|
24
|
+
COPY --chown=mercury:mercury locales ./locales
|
|
25
25
|
COPY --chown=mercury:mercury src ./src
|
|
26
26
|
|
|
27
27
|
# Mount points of the named volumes, owned by the runtime user before a fresh
|
package/template/src/repl.ts.tpl
CHANGED