@mercury-fw/formatter 0.53.2 → 0.55.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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # @mercury-fw/formatter
2
2
 
3
+ ## 0.55.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [eac72fa]
8
+ - @mercury-fw/plugin-types@0.55.0
9
+
10
+ ## 0.54.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 5015642: The plugins' texts a person sees come out in their language, like the core's. Plugin contract version 5:
15
+
16
+ - 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.
17
+ - 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.
18
+ - 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.
19
+ - 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`.
20
+ - 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.
21
+
22
+ 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).
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies [5015642]
27
+ - @mercury-fw/plugin-types@0.54.0
28
+
3
29
  ## 0.53.2
4
30
 
5
31
  ### Patch Changes
package/README.md CHANGED
@@ -21,7 +21,7 @@ export default defineMercuryConfig({
21
21
  });
22
22
  ```
23
23
 
24
- - `formatter<D>(rules)` takes one rule per kind of list, keyed by the kinds the plugin declares (`D`, e.g. `JiraDisplays`), so a kind it doesn't emit or a rule for the wrong item shape fails the typecheck. A rule is the line for each item, or `{ item, empty }` when an empty list needs a text of its own. Items are joined with a blank line.
24
+ - `formatter<D>(rules)` takes one rule per kind of list, keyed by the kinds the plugin declares (`D`, e.g. `JiraDisplays`), so a kind it doesn't emit or a rule for the wrong item shape fails the typecheck. A rule is the line for each item, or `{ item, empty }` when an empty list needs a text of its own. Items are joined with a blank line. A rule also gets `t`, the texts of the person the turn is for, as its second argument (`(issue, t) => …`), and `empty` can be a function of it (`(t) => t("app.jira.no-issues")`, the key in the app's `locales/en.json` and its other language files), so an app that speaks several languages writes its lists in each person's.
25
25
  - `formatterPlugin(plugin, handler)` wraps a plugin so every list it hands over goes through the rules; everything else the plugin contributes passes through untouched.
26
26
 
27
27
  MIT
package/dist/index.d.ts CHANGED
@@ -11,16 +11,17 @@
11
11
  * its post-processor emits goes through the handler; everything else the
12
12
  * plugin contributes passes through untouched.
13
13
  */
14
- import type { Plugin, ToolDisplay } from "@mercury-fw/plugin-types";
15
- /** Renders one display into the single text block shown to the user, or
16
- * returns `undefined` when there is nothing to show (no rule for its kind, or
17
- * an empty list with no empty text). */
18
- export type DisplayHandler = (display: ToolDisplay) => string | undefined;
14
+ import type { Plugin, ToolDisplay, Translate } from "@mercury-fw/plugin-types";
15
+ /** Renders one display into the single text block shown to the user, in the
16
+ * turn's language (`t`), or returns `undefined` when there is nothing to show
17
+ * (no rule for its kind, or an empty list with no empty text). */
18
+ export type DisplayHandler = (display: ToolDisplay, t: Translate) => string | undefined;
19
19
  /** How one kind of list is rendered: the line for each item, or that plus the
20
- * text to show when the list is empty. */
21
- export type FormatterRule<T> = ((item: T) => string) | {
22
- item: (item: T) => string;
23
- empty?: string;
20
+ * text to show when the list is empty. Both may use `t`, the texts of the
21
+ * person the turn is for (an app's own keys in its language files). */
22
+ export type FormatterRule<T> = ((item: T, t: Translate) => string) | {
23
+ item: (item: T, t: Translate) => string;
24
+ empty?: string | ((t: Translate) => string);
24
25
  };
25
26
  /**
26
27
  * Builds a handler from the instance's rules. `D` is the data plugin's map of
package/index.ts CHANGED
@@ -11,16 +11,19 @@
11
11
  * its post-processor emits goes through the handler; everything else the
12
12
  * plugin contributes passes through untouched.
13
13
  */
14
- import type { Plugin, CliPostProcessor, CliResult, ToolDisplay } from "@mercury-fw/plugin-types";
14
+ import type { Plugin, CliPostProcessor, CliResult, ToolDisplay, Translate } from "@mercury-fw/plugin-types";
15
15
 
16
- /** Renders one display into the single text block shown to the user, or
17
- * returns `undefined` when there is nothing to show (no rule for its kind, or
18
- * an empty list with no empty text). */
19
- export type DisplayHandler = (display: ToolDisplay) => string | undefined;
16
+ /** Renders one display into the single text block shown to the user, in the
17
+ * turn's language (`t`), or returns `undefined` when there is nothing to show
18
+ * (no rule for its kind, or an empty list with no empty text). */
19
+ export type DisplayHandler = (display: ToolDisplay, t: Translate) => string | undefined;
20
20
 
21
21
  /** How one kind of list is rendered: the line for each item, or that plus the
22
- * text to show when the list is empty. */
23
- export type FormatterRule<T> = ((item: T) => string) | { item: (item: T) => string; empty?: string };
22
+ * text to show when the list is empty. Both may use `t`, the texts of the
23
+ * person the turn is for (an app's own keys in its language files). */
24
+ export type FormatterRule<T> =
25
+ | ((item: T, t: Translate) => string)
26
+ | { item: (item: T, t: Translate) => string; empty?: string | ((t: Translate) => string) };
24
27
 
25
28
  /** Items are rendered one per block, separated by a blank line. */
26
29
  const ITEM_SEPARATOR = "\n\n";
@@ -33,16 +36,16 @@ const ITEM_SEPARATOR = "\n\n";
33
36
  */
34
37
  export function formatter<D>(rules: { [K in keyof D]?: FormatterRule<D[K]> }): DisplayHandler {
35
38
  const byKind = rules as Record<string, FormatterRule<unknown> | undefined>;
36
- return (display) => {
39
+ return (display, t) => {
37
40
  const rule = byKind[display.type];
38
41
  if (rule === undefined) {
39
42
  return undefined;
40
43
  }
41
44
  const { item, empty } = typeof rule === "function" ? { item: rule, empty: undefined } : rule;
42
45
  if (display.items.length === 0) {
43
- return empty;
46
+ return typeof empty === "function" ? empty(t) : empty;
44
47
  }
45
- return display.items.map((i) => item(i)).join(ITEM_SEPARATOR);
48
+ return display.items.map((i) => item(i, t)).join(ITEM_SEPARATOR);
46
49
  };
47
50
  }
48
51
 
@@ -51,11 +54,17 @@ export function formatter<D>(rules: { [K in keyof D]?: FormatterRule<D[K]> }): D
51
54
  * non-empty list with no rule stays structured (so it isn't shown) and is
52
55
  * reported through `log`. Non-ok results and results with no display are
53
56
  * returned as they are, without calling the handler. */
54
- function renderDisplay(result: CliResult, handler: DisplayHandler, log: (m: string) => void, pluginName: string): CliResult {
57
+ function renderDisplay(
58
+ result: CliResult,
59
+ handler: DisplayHandler,
60
+ t: Translate,
61
+ log: (m: string) => void,
62
+ pluginName: string,
63
+ ): CliResult {
55
64
  if (!result.ok || !result.display) {
56
65
  return result;
57
66
  }
58
- const text = handler(result.display);
67
+ const text = handler(result.display, t);
59
68
  if (text !== undefined) {
60
69
  return { ...result, display: { ...result.display, items: [text] } };
61
70
  }
@@ -81,7 +90,7 @@ export function formatterPlugin(plugin: Plugin, handler: DisplayHandler): Plugin
81
90
  const inner = plugin.build ? plugin.build(ctx) : {};
82
91
  const proc = inner.postProcess;
83
92
  const wrapped: CliPostProcessor | undefined = proc
84
- ? (cmd, result) => renderDisplay(proc(cmd, result), handler, ctx.log, plugin.name)
93
+ ? (cmd, result, t) => renderDisplay(proc(cmd, result, t), handler, t, ctx.log, plugin.name)
85
94
  : undefined;
86
95
  return { ...inner, postProcess: wrapped };
87
96
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mercury-fw/formatter",
3
- "version": "0.53.2",
3
+ "version": "0.55.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "typecheck": "tsc --noEmit"
30
30
  },
31
31
  "dependencies": {
32
- "@mercury-fw/plugin-types": "0.53.2"
32
+ "@mercury-fw/plugin-types": "0.55.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@mercury-fw/typescript-config": "*",