genshin-persona 3.8.1 → 4.0.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/.claude-plugin/plugin.json +1 -0
- package/README.md +48 -49
- package/hooks/hooks.json +3 -2
- package/mod/CardVerbs.ts +9 -0
- package/mod/VerbDescriptionMap.ts +22 -0
- package/mod/register.ts +140 -0
- package/package.json +12 -5
- package/runtime/package-lock.json +7 -7
- package/runtime/package.json +1 -1
- package/scripts/genshin.mjs +5 -0
- package/scripts/genshin.ts +2 -2
- package/scripts/pick.mjs +5 -0
- package/scripts/pick.ts +1 -14
- package/scripts/speak.mjs +5 -0
- package/scripts/spinner.mjs +5 -0
- package/scripts/spinner.ts +8 -7
- package/scripts/status.mjs +5 -0
- package/scripts/status.ts +11 -14
- package/scripts/statusLine.mjs +5 -0
- package/scripts/statusLine.ts +13 -0
- package/scripts/voice.mjs +5 -0
- package/skills/genshin/SKILL.md +20 -20
- package/skills/genshin-author/SKILL.md +6 -6
- package/src/generated/CharacterLinesLoaderMap.ts +39 -0
- package/src/generated/characterLines/ChineseSimplified.json +4000 -0
- package/src/generated/characterLines/ChineseTraditional.json +4000 -0
- package/src/generated/characterLines/English.json +4000 -0
- package/src/generated/characterLines/French.json +4000 -0
- package/src/generated/characterLines/German.json +4000 -0
- package/src/generated/characterLines/Indonesian.json +4000 -0
- package/src/generated/characterLines/Italian.json +4000 -0
- package/src/generated/characterLines/Japanese.json +4000 -0
- package/src/generated/characterLines/Korean.json +4000 -0
- package/src/generated/characterLines/Portuguese.json +4000 -0
- package/src/generated/characterLines/Russian.json +4000 -0
- package/src/generated/characterLines/Spanish.json +4000 -0
- package/src/generated/characterLines/Thai.json +4000 -0
- package/src/generated/characterLines/Turkish.json +4000 -0
- package/src/generated/characterLines/Vietnamese.json +4000 -0
- package/src/generated/genshinText/generated/GameTextLoaderMap.ts +41 -0
- package/src/generated/genshinText/generated/text/ChineseSimplified.json +23 -0
- package/src/generated/genshinText/generated/text/ChineseTraditional.json +23 -0
- package/src/generated/genshinText/generated/text/English.json +23 -0
- package/src/generated/genshinText/generated/text/French.json +23 -0
- package/src/generated/genshinText/generated/text/German.json +23 -0
- package/src/generated/genshinText/generated/text/Indonesian.json +23 -0
- package/src/generated/genshinText/generated/text/Italian.json +23 -0
- package/src/generated/genshinText/generated/text/Japanese.json +23 -0
- package/src/generated/genshinText/generated/text/Korean.json +23 -0
- package/src/generated/genshinText/generated/text/Portuguese.json +23 -0
- package/src/generated/genshinText/generated/text/Russian.json +23 -0
- package/src/generated/genshinText/generated/text/Spanish.json +23 -0
- package/src/generated/genshinText/generated/text/Thai.json +23 -0
- package/src/generated/genshinText/generated/text/Turkish.json +23 -0
- package/src/generated/genshinText/generated/text/Vietnamese.json +23 -0
- package/src/generated/genshinText/models/GameLanguage.ts +22 -0
- package/src/generated/genshinText/models/GameText.ts +5 -0
- package/src/generated/genshinText/models/GameTextKey.ts +42 -0
- package/src/generated/genshinText/models/TravelerGender.ts +7 -0
- package/src/generated/genshinText/services/GameLanguageTagMap.ts +22 -0
- package/src/generated/genshinText/services/checkIsGameLanguage.ts +9 -0
- package/src/{services → generated/genshinText/services}/fillLinePlaceholders.ts +3 -2
- package/src/generated/genshinText/services/getCanonicalLanguage.ts +14 -0
- package/src/generated/genshinText/services/getLanguageDisplayName.ts +14 -0
- package/src/localizations/chineseSimplified.ts +258 -0
- package/src/localizations/chineseTraditional.ts +258 -0
- package/src/localizations/english.ts +10 -22
- package/src/localizations/french.ts +579 -0
- package/src/localizations/german.ts +579 -0
- package/src/localizations/indonesian.ts +582 -0
- package/src/localizations/italian.ts +614 -0
- package/src/localizations/japanese.ts +7 -20
- package/src/localizations/korean.ts +408 -0
- package/src/localizations/portuguese.ts +602 -0
- package/src/localizations/russian.ts +540 -0
- package/src/localizations/spanish.ts +605 -0
- package/src/localizations/thai.ts +444 -0
- package/src/localizations/turkish.ts +601 -0
- package/src/localizations/vietnamese.ts +612 -0
- package/src/models/Character.ts +1 -1
- package/src/models/GenshinVerb.ts +19 -24
- package/src/models/LocalizationStrings.ts +1 -3
- package/src/models/Nameplate.ts +1 -1
- package/src/models/ResolvedLocalization.ts +5 -2
- package/src/models/StatusReport.ts +2 -5
- package/src/models/TravelerTwin.ts +1 -1
- package/src/models/VoiceDeviceRung.ts +1 -1
- package/src/models/VoiceLanguage.ts +8 -5
- package/src/models/VoiceRequestType.ts +6 -9
- package/src/models/VoiceStatus.ts +6 -9
- package/src/services/CharacterColorMap.ts +5 -6
- package/src/services/ElementColorMap.ts +12 -0
- package/src/services/LanguageDubPrefixMap.ts +9 -0
- package/src/services/TravelerTwinMap.ts +10 -0
- package/src/services/VoiceLanguageNameMap.ts +11 -0
- package/src/services/checkIsVoiceLanguage.ts +1 -2
- package/src/services/cli/commands/genshinCommand.ts +0 -2
- package/src/services/cli/commands/languageCommand.ts +9 -14
- package/src/services/cli/commands/linesCommand.ts +2 -2
- package/src/services/cli/commands/pinCommand.ts +3 -7
- package/src/services/cli/commands/replyCommand.ts +3 -4
- package/src/services/cli/commands/teardownCommand.ts +0 -6
- package/src/services/cli/commands/useCommand.ts +2 -2
- package/src/services/cli/commands/voiceCommand.ts +14 -15
- package/src/services/cli/commands/volumeCommand.ts +4 -4
- package/src/services/cli/{readNamedCharacter.ts → findNamedCharacter.ts} +1 -1
- package/src/services/cli/getStatusReport.ts +1 -9
- package/src/services/cli/printQueue.ts +3 -2
- package/src/services/cli/switchSessionCharacter.ts +1 -2
- package/src/services/constants.ts +10 -91
- package/src/services/createAudioPlayer.ts +6 -5
- package/src/services/cutReferenceClip.ts +1 -1
- package/src/services/findCharacterByName.ts +1 -1
- package/src/services/formatLoreChart.ts +1 -1
- package/src/services/formatNameplate.ts +10 -13
- package/src/services/getBirthdayNote.ts +5 -4
- package/src/services/{readCharacterReference.ts → getCharacterReference.ts} +1 -1
- package/src/services/getNameplateColor.ts +10 -0
- package/src/services/getPersonaCardName.ts +6 -3
- package/src/services/getPersonaCharacter.ts +13 -0
- package/src/services/getReplyLanguageInstruction.ts +4 -0
- package/src/services/getSessionNameplate.ts +3 -3
- package/src/services/getSessionStartOutput.ts +3 -7
- package/src/services/getSpeechRequest.ts +2 -2
- package/src/services/getSpinner.ts +7 -22
- package/src/services/getSpokenLines.ts +4 -4
- package/src/services/getWikiFileTitle.ts +2 -1
- package/src/services/parseBirthday.ts +7 -3
- package/src/services/parseWikiStoryLines.ts +6 -9
- package/src/services/parseWikiTemplateLines.ts +11 -18
- package/src/services/parseWikiTravelerLines.ts +10 -13
- package/src/services/pickCharacterByLore.ts +4 -3
- package/src/services/readInterfaceLanguage.ts +9 -7
- package/src/services/readLocalization.ts +12 -5
- package/src/services/readReferenceClip.ts +2 -2
- package/src/services/readRoster.ts +3 -4
- package/src/services/readSessionNameplate.ts +24 -0
- package/src/services/readSpinner.ts +2 -2
- package/src/services/readVoiceLines.ts +29 -28
- package/src/services/readWikiFileUrls.ts +2 -2
- package/src/services/readWikiStoryLines.ts +7 -11
- package/src/services/recordSessionCharacter.ts +3 -3
- package/src/services/registerQuietExit.ts +6 -2
- package/src/util/getContrastRatio.ts +3 -4
- package/types/index.d.ts +37 -0
- package/skills/language/SKILL.md +0 -14
- package/skills/mute/SKILL.md +0 -13
- package/skills/pin/SKILL.md +0 -14
- package/skills/reply/SKILL.md +0 -14
- package/skills/roster/SKILL.md +0 -13
- package/skills/setup/SKILL.md +0 -13
- package/skills/status/SKILL.md +0 -13
- package/skills/teardown/SKILL.md +0 -13
- package/skills/today/SKILL.md +0 -13
- package/skills/unmute/SKILL.md +0 -13
- package/skills/unpin/SKILL.md +0 -13
- package/skills/use/SKILL.md +0 -14
- package/skills/voice/SKILL.md +0 -14
- package/skills/volume/SKILL.md +0 -14
- package/src/models/HookCommand.ts +0 -6
- package/src/models/HookEntry.ts +0 -7
- package/src/models/Spinner.ts +0 -11
- package/src/models/SpinnerTip.ts +0 -5
- package/src/models/SpinnerTipsOverride.ts +0 -12
- package/src/models/SpinnerVerbs.ts +0 -8
- package/src/models/SpinnerVerbsMode.ts +0 -4
- package/src/models/StatusLine.ts +0 -6
- package/src/models/TravelerGender.ts +0 -5
- package/src/models/UserHooks.ts +0 -7
- package/src/models/UserSettings.ts +0 -14
- package/src/models/WikiVoiceOversPage.ts +0 -6
- package/src/services/checkIsLanguageName.ts +0 -8
- package/src/services/checkIsPluginHookEntry.ts +0 -8
- package/src/services/checkIsPluginSpinner.ts +0 -12
- package/src/services/checkIsPluginStatusLine.ts +0 -7
- package/src/services/cli/commands/setupCommand.ts +0 -30
- package/src/services/getCanonicalLanguage.ts +0 -11
- package/src/services/getLanguageDisplayName.ts +0 -12
- package/src/services/getPluginSpeakHookEntry.ts +0 -10
- package/src/services/getPluginStatusLine.ts +0 -9
- package/src/services/getSettingsWithSpeakHook.ts +0 -13
- package/src/services/getSettingsWithSpinner.ts +0 -11
- package/src/services/getSettingsWithStatusLine.ts +0 -10
- package/src/services/getSettingsWithoutPluginEntries.ts +0 -25
- package/src/services/readLanguageNames.ts +0 -8
- package/src/services/readUserSettings.ts +0 -29
- package/src/services/spawnDetachedScript.ts +0 -7
- package/src/services/writeLauncher.ts +0 -12
- package/src/services/writeSessionSpinner.ts +0 -32
- package/src/services/writeSpeakHook.ts +0 -13
- package/src/services/writeSpinner.ts +0 -14
- package/src/services/writeUserSettings.ts +0 -9
- package/src/util/toForwardSlashes.ts +0 -2
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"repository": "https://github.com/Esposter/Esposter",
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"keywords": ["genshin", "genshin-impact", "output-style", "persona", "text-to-speech", "voice-cloning"],
|
|
11
|
+
"types": "./types/index.d.ts",
|
|
11
12
|
"userConfig": {
|
|
12
13
|
"typesafe_key": {
|
|
13
14
|
"type": "string",
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ A Claude Code plugin that gives every session a Genshin Impact character: a repl
|
|
|
10
10
|
- **A character per session** — picked by the nearest birthday, or by lore through one typed decision when you give it a TypeSafe key; `use` and `pin` override the pick.
|
|
11
11
|
- **A spoken channel, not a persona in the prose** — the character speaks in blockquote lines and nowhere else: one line opening an answer a reader will use, with everything else — the answer, the code, the commit message — a neutral assistant's, and the whole reply when the ask was the character's.
|
|
12
12
|
- **Read aloud as it is written** — a hook hands each spoken line to a resident synthesizer the moment its line lands, and the line is read in the character's cloned voice while the reply is still streaming; the engine is warmed at the session start so the first reply pays no load.
|
|
13
|
-
- **Localized** — the card, the spinner, the
|
|
13
|
+
- **Localized** — the card, the spinner, the prompt hint and every line the plugin prints in the language you set, and replies in the one you choose.
|
|
14
14
|
|
|
15
15
|
## Table of Contents
|
|
16
16
|
|
|
@@ -22,7 +22,7 @@ A Claude Code plugin that gives every session a Genshin Impact character: a repl
|
|
|
22
22
|
|
|
23
23
|
## <a name="getting-started">🚀 Getting Started</a>
|
|
24
24
|
|
|
25
|
-
This repository is a Claude Code plugin marketplace named `esposter
|
|
25
|
+
This repository is a Claude Code plugin marketplace named `esposter`:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
claude plugin marketplace add Esposter/Esposter
|
|
@@ -44,15 +44,15 @@ claude plugin disable genshin-persona@esposter
|
|
|
44
44
|
claude --plugin-dir packages/genshin-persona
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Nobody switches that on: `.agents/settings.json` declares `autoUpdate` on the marketplace it declares, and Claude Code leaves it off for every marketplace but Anthropic's own. A checkout therefore re-pins its own plugin to its own current commit on startup, and `/plugin`'s **Enable auto-update** reports the value as settings' rather than offering to change it. An update still moves a pin to a commit, so work that is not committed is not loaded, whatever the toggle says. Nothing below is repeated for an update: the
|
|
47
|
+
Nobody switches that on: `.agents/settings.json` declares `autoUpdate` on the marketplace it declares, and Claude Code leaves it off for every marketplace but Anthropic's own. A checkout therefore re-pins its own plugin to its own current commit on startup, and `/plugin`'s **Enable auto-update** reports the value as settings' rather than offering to change it. An update still moves a pin to a commit, so work that is not committed is not loaded, whatever the toggle says. Nothing below is repeated for an update: the plugin writes nothing into your settings, so every surface it draws comes from the running copy, and the voice's runtime and weights are kept until the copy carries a newer lockfile or engine, which `voice` with no argument reports and `voice <dub>` run again picks up, installing only what changed.
|
|
48
48
|
|
|
49
49
|
Spoken lines stay silent until the `voice` verb has set the engine up, once, with the dub the reference lines are taken from — `en`, `ja`, `ko` or `zh`:
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
node "<plugin root>/scripts/genshin.
|
|
52
|
+
node "<plugin root>/scripts/genshin.mjs" voice ja # or: /genshin-voice ja
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
It installs the engine's runtime and weights — about a gigabyte, once — into the plugin's own state directory under `~/.claude/genshin-persona`, ends by speaking one sentence as the session's character
|
|
55
|
+
It installs the engine's runtime and weights — about a gigabyte, once — into the plugin's own state directory under `~/.claude/genshin-persona`, and ends by speaking one sentence as the session's character; replies are read aloud from the next reply on, by the plugin's own hooks module, with nothing written into your settings. Each character is read in a clone of their own voice, conditioned on one line of their performance fetched from the community wiki the first time they are picked; nothing lifted from the game ships with the plugin. How the engine runs, which device it settles on and why, and what it does when it cannot speak is the [spoken replies](https://esposter.com/docs/infra/claude-interface/spoken-replies) page.
|
|
56
56
|
|
|
57
57
|
A [TypeSafe](https://typesafe.ai) API key, given as an option, turns the pick over to lore — one typed decision over the whole roster, weighing the date, the season's festivals and your moment — instead of the nearest birthday. The key is sensitive, and the variable the SDK itself reads, `TYPESAFE_API_KEY`, is honoured when the option is empty:
|
|
58
58
|
|
|
@@ -66,31 +66,30 @@ We highly recommend you take a look at the [documentation](https://esposter.com/
|
|
|
66
66
|
|
|
67
67
|
### Command reference
|
|
68
68
|
|
|
69
|
-
Every command is a slash command in Claude Code and the same verb of the plugin's script from any shell; the script form is what a hook, a terminal or another tool runs, and the two never differ:
|
|
69
|
+
Every command is a slash command in Claude Code, `/genshin-` and the verb, which the plugin's hooks module registers at session start, and the same verb of the plugin's script from any shell; the script form is what a hook, a terminal or another tool runs, and the two never differ:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
/genshin
|
|
73
|
-
node "<plugin root>/scripts/genshin.
|
|
72
|
+
/genshin-<verb> [argument]
|
|
73
|
+
node "<plugin root>/scripts/genshin.mjs" <verb> [argument]
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
A `<name>` is a character's, matched whole and ignoring case, in English or as the interface language spells it. A `[language]` is one the `language` verb lists, typed in English or in its own words. A verb given a name the roster has no character by, or an argument outside its set, prints why and exits 1; a verb with no argument where one is optional reports instead of changing anything.
|
|
77
77
|
|
|
78
|
-
| Verb | Argument | What it does
|
|
79
|
-
| :--------- | :----------------------- |
|
|
80
|
-
| `today` | — | Prints the card of this session's character; from a shell, of the pinned character, else of a fresh pick. A pin naming nobody in the roster is reported and ignored.
|
|
81
|
-
| `roster` | — | Every playable character, one line each: name, title, element, region, birthday and the patch that introduced them.
|
|
82
|
-
| `use` | `<name>` | Speaks as one character for this session alone, from that reply on; other sessions and the next start are untouched. From a shell, where no session is running, exits 1.
|
|
83
|
-
| `pin` | `<name>` | Fixes one character for every session until unpinned, and for this session from that reply on.
|
|
84
|
-
| `unpin` | — | Removes the pin; the pick decides again from the next session, and for this session from that reply on.
|
|
85
|
-
| `voice` | `[en \| ja \| ko \| zh]` | Sets up spoken lines in that dub — installing the engine's runtime and weights on the first run, about a gigabyte — ends by speaking one sentence
|
|
86
|
-
| `mute` | — | Stops replies' spoken lines being read aloud. The pick and the card are unaffected.
|
|
87
|
-
| `unmute` | — | Lets replies' spoken lines be read aloud again.
|
|
88
|
-
| `volume` | `<0–100>` | Sets how loud replies are spoken, as a whole number, from the next reply on; `0` is silence the engine is not woken for.
|
|
89
|
-
| `language` | `[language]` | Sets the interface language — every word the plugin writes — and carries the reply language with it. With none, reports what is set and lists the languages on offer.
|
|
90
|
-
| `reply` | `[language]` | Sets the language replies are written in, on its own. With none, reports what is set and whether it was set or cascaded from the interface language.
|
|
91
|
-
| `status` | — | Reports every setting at once and where each value came from. Changes nothing.
|
|
92
|
-
| `
|
|
93
|
-
| `teardown` | — | Removes exactly what `setup` and `voice` wrote: the three settings, and the voice's runtime, weights, cached clips and dub. |
|
|
78
|
+
| Verb | Argument | What it does |
|
|
79
|
+
| :--------- | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `today` | — | Prints the card of this session's character; from a shell, of the pinned character, else of a fresh pick. A pin naming nobody in the roster is reported and ignored. |
|
|
81
|
+
| `roster` | — | Every playable character, one line each: name, title, element, region, birthday and the patch that introduced them. |
|
|
82
|
+
| `use` | `<name>` | Speaks as one character for this session alone, from that reply on; other sessions and the next start are untouched. From a shell, where no session is running, exits 1. |
|
|
83
|
+
| `pin` | `<name>` | Fixes one character for every session until unpinned, and for this session from that reply on. |
|
|
84
|
+
| `unpin` | — | Removes the pin; the pick decides again from the next session, and for this session from that reply on. |
|
|
85
|
+
| `voice` | `[en \| ja \| ko \| zh]` | Sets up spoken lines in that dub — installing the engine's runtime and weights on the first run, about a gigabyte — and ends by speaking one sentence; replies are read from the next reply on. With no dub, reports what is set up. |
|
|
86
|
+
| `mute` | — | Stops replies' spoken lines being read aloud. The pick and the card are unaffected. |
|
|
87
|
+
| `unmute` | — | Lets replies' spoken lines be read aloud again. |
|
|
88
|
+
| `volume` | `<0–100>` | Sets how loud replies are spoken, as a whole number, from the next reply on; `0` is silence the engine is not woken for. |
|
|
89
|
+
| `language` | `[language]` | Sets the interface language — every word the plugin writes — and carries the reply language with it. With none, reports what is set and lists the languages on offer. |
|
|
90
|
+
| `reply` | `[language]` | Sets the language replies are written in, on its own. With none, reports what is set and whether it was set or cascaded from the interface language. |
|
|
91
|
+
| `status` | — | Reports every setting at once and where each value came from. Changes nothing. |
|
|
92
|
+
| `teardown` | — | Removes what `voice` installed: the runtime, the weights, the cached clips and the dub. The picks, the pin and the languages stay; `voice` brings spoken lines back. |
|
|
94
93
|
|
|
95
94
|
Four more are the authoring queues of one more skill, `genshin-author` — script-only and on no menu:
|
|
96
95
|
|
|
@@ -103,38 +102,38 @@ Four more are the authoring queues of one more skill, `genshin-author` — scrip
|
|
|
103
102
|
|
|
104
103
|
### What it ships
|
|
105
104
|
|
|
106
|
-
| Component | Role
|
|
107
|
-
| :------------------------------------- |
|
|
108
|
-
| `hooks/hooks.json` | A session-start hook that picks the character, greets you with its name, what the pick weighed and who is close, and prints its card as context
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `skills/genshin
|
|
113
|
-
| `
|
|
114
|
-
| `src/
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
105
|
+
| Component | Role |
|
|
106
|
+
| :------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
107
|
+
| `hooks/hooks.json` | A session-start hook that picks the character, greets you with its name, what the pick weighed and who is close, and prints its card as context, and the hooks module beside it. |
|
|
108
|
+
| `mod/` | The hooks module: the spinner's verbs and the prompt hint's tips of this session's character, the spoken replies, and one slash command per verb. |
|
|
109
|
+
| `types/index.d.ts` | The state the hooks module publishes — the session's character and its colour — which other plugins draw by. |
|
|
110
|
+
| `output-styles/in-character.md` | The standing rules, forced on while the plugin is enabled: a blockquote line is the character's spoken line, the ask decides how much of a reply that is, and the rest is plain. |
|
|
111
|
+
| `skills/genshin/SKILL.md` | The model's route from a request in words to one of the verbs; hidden from the menu. |
|
|
112
|
+
| `skills/genshin-author/SKILL.md` | How a persona card and its spinner verbs are written, the command that prints a character's own lines to write from, and the two queues. |
|
|
113
|
+
| `src/personaCards/` | Authored persona cards, one typed module per character, in our words: how the character speaks for the model, their spinner verbs for you, and the reference line only where an ear chose one. |
|
|
114
|
+
| `src/generated/PersonaReferenceMap.ts` | The measured reference line and its likeness per character, one generated map written by the repository's reference selection and never by hand. |
|
|
115
|
+
| `runtime/` | The manifest and lockfile of the speech engine's runtime, which the `voice` verb installs into the state directory rather than the plugin carrying it. |
|
|
116
|
+
| `src/localizations/` | One authored module per language for the words the data package does not carry: the base Teyvat verbs, each character's spinner gerunds, and every line the verbs print. |
|
|
117
|
+
| `scripts/` | The session-start hook, the commands' script, the character and spinner the hooks module reads, the status line a person can name in their settings, and the resident synthesizer, each an `.mjs` that registers `tsx` before its TypeScript. |
|
|
118
118
|
|
|
119
|
-
###
|
|
119
|
+
### Spinner and prompt hint
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
Nothing to set up: the hooks module turns the spinner's words into the session's character's verbs and the prompt hint into one of their lines under their name, a new one each turn, names the character among the labels at the right of the prompt's footer, and publishes the character's colour, which [`genshin-mods`](https://github.com/Esposter/Esposter/tree/main/packages/genshin-mods) draws its band in. Each session draws its own character, and a switch shows at once. Why each is shaped as it is: the [persona plugin](https://esposter.com/docs/infra/claude-interface/persona-plugin) page.
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
node "<plugin root>/scripts/genshin.ts" setup # or: /genshin-persona:setup
|
|
125
|
-
node "<plugin root>/scripts/genshin.ts" teardown
|
|
126
|
-
```
|
|
123
|
+
The plugin writes nothing into your settings, so your own status line stays yours. To have it show the character's nameplate, the name in their colour on a badge of that colour's tone, point it at the script the plugin ships:
|
|
127
124
|
|
|
128
|
-
|
|
125
|
+
```json
|
|
126
|
+
{ "statusLine": { "command": "node \"<plugin root>/scripts/statusLine.mjs\"", "type": "command" } }
|
|
127
|
+
```
|
|
129
128
|
|
|
130
129
|
### Languages
|
|
131
130
|
|
|
132
131
|
Three settings, and they are not one axis: the **interface language** is every word the plugin writes and the master toggle, the **reply language** is what the model writes in and follows the interface language until set on its own, and the **dub** says whose voice reads a line and is neither, because it comes from a set of four and costs an install:
|
|
133
132
|
|
|
134
133
|
```bash
|
|
135
|
-
node "<plugin root>/scripts/genshin.
|
|
136
|
-
node "<plugin root>/scripts/genshin.
|
|
137
|
-
node "<plugin root>/scripts/genshin.
|
|
134
|
+
node "<plugin root>/scripts/genshin.mjs" language japanese # or: /genshin-language japanese
|
|
135
|
+
node "<plugin root>/scripts/genshin.mjs" reply english # labels in one language, prose in another
|
|
136
|
+
node "<plugin root>/scripts/genshin.mjs" status # every setting, and where each value came from
|
|
138
137
|
```
|
|
139
138
|
|
|
140
139
|
The languages on offer are the data package's, so `language` with no argument lists them, each in its own words beside the word you type for it, and either resolves. What arrives localized for nothing and what is written by hand per language is the [persona plugin](https://esposter.com/docs/infra/claude-interface/persona-plugin) page's.
|
|
@@ -148,7 +147,7 @@ Every playable character comes from the game-data dependency — no generated ro
|
|
|
148
147
|
`use <name>` makes this session speak as one character from that reply on; `pin <name>` fixes one for every session from the next start and this one at once; `unpin` hands both back to the pick:
|
|
149
148
|
|
|
150
149
|
```bash
|
|
151
|
-
node "<plugin root>/scripts/genshin.
|
|
150
|
+
node "<plugin root>/scripts/genshin.mjs" use furina # or: /genshin-use furina
|
|
152
151
|
```
|
|
153
152
|
|
|
154
153
|
### Spoken lines
|
|
@@ -156,10 +155,10 @@ node "<plugin root>/scripts/genshin.ts" use furina # or: /genshin-persona:u
|
|
|
156
155
|
A reply to an ask of the assistant opens with one blockquote line in the character's voice and may close with one; a reply to an ask of the character — a joke, a hello, an opinion, however much was pasted to form it — is such lines and nothing else, as many as the ask deserves, a list of them included. A hook hands each piece of the reply to a resident synthesizer as it lands, which reads the lines in the order they were written, in the character's cloned voice, while the reply is still being written. `mute` and `unmute` decide whether the hook asks it at all, and so does a `volume` of zero. `volume <number>` is a whole number from 0 to 100, applied as a gain, from the next reply on:
|
|
157
156
|
|
|
158
157
|
```bash
|
|
159
|
-
node "<plugin root>/scripts/genshin.
|
|
158
|
+
node "<plugin root>/scripts/genshin.mjs" volume 60 # or: /genshin-volume 60
|
|
160
159
|
```
|
|
161
160
|
|
|
162
|
-
`teardown` removes the runtime, the weights, the cached clips
|
|
161
|
+
`teardown` removes the runtime, the weights, the cached clips and the dub, and spoken lines stay silent until `voice` sets them up again.
|
|
163
162
|
|
|
164
163
|
### Commands
|
|
165
164
|
|
package/hooks/hooks.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"hooks": {
|
|
3
|
-
"SessionStart": [{ "hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pick.
|
|
4
|
-
}
|
|
3
|
+
"SessionStart": [{ "hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pick.mjs\"" }] }]
|
|
4
|
+
},
|
|
5
|
+
"modules": ["../mod/register.ts"]
|
|
5
6
|
}
|
package/mod/CardVerbs.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GenshinVerb } from "../src/models/GenshinVerb";
|
|
2
|
+
|
|
3
|
+
// The verbs that print a card, which the model answers as from the reply that relays it
|
|
4
|
+
export const CardVerbs: readonly GenshinVerb[] = [
|
|
5
|
+
GenshinVerb.Language,
|
|
6
|
+
GenshinVerb.Pin,
|
|
7
|
+
GenshinVerb.Unpin,
|
|
8
|
+
GenshinVerb.Use,
|
|
9
|
+
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { GenshinVerb } from "../src/models/GenshinVerb";
|
|
2
|
+
|
|
3
|
+
// Each verb a person runs, as `/genshin-<verb>`, and what the command menu says of it; the authoring verbs are the
|
|
4
|
+
// `genshin-author` skill's and have no command
|
|
5
|
+
export const VerbDescriptionMap: Partial<Record<GenshinVerb, string>> = {
|
|
6
|
+
[GenshinVerb.Language]:
|
|
7
|
+
"Sets the language every word the plugin writes is in, and the reply language with it; given nothing, reports what is set.",
|
|
8
|
+
[GenshinVerb.Mute]: "Stops replies' spoken lines being read aloud. The pick and the card are unaffected.",
|
|
9
|
+
[GenshinVerb.Pin]: "Fixes one character for every session until unpinned, this one from this reply on.",
|
|
10
|
+
[GenshinVerb.Reply]: "Sets the language replies are written in, on its own; given nothing, reports what is set.",
|
|
11
|
+
[GenshinVerb.Roster]: "Every playable character, one line each.",
|
|
12
|
+
[GenshinVerb.Status]: "Reports every setting the plugin holds and where each came from. Changes nothing.",
|
|
13
|
+
[GenshinVerb.Teardown]:
|
|
14
|
+
"Removes the voice's runtime, weights, references and dub; the picks, the pin and the languages stay.",
|
|
15
|
+
[GenshinVerb.Today]: "The card of this session's character.",
|
|
16
|
+
[GenshinVerb.Unmute]: "Lets replies' spoken lines be read aloud again.",
|
|
17
|
+
[GenshinVerb.Unpin]: "Removes the pin; the pick decides again, for this session from this reply on.",
|
|
18
|
+
[GenshinVerb.Use]: "Speaks as one character for this session alone, from this reply on.",
|
|
19
|
+
[GenshinVerb.Voice]:
|
|
20
|
+
"Sets up spoken replies in the character's own cloned voice, or switches the dub (en, ja, ko or zh); given nothing, reports what is installed.",
|
|
21
|
+
[GenshinVerb.Volume]: "Sets how loud replies are spoken, as a whole number from 0 to 100.",
|
|
22
|
+
};
|
package/mod/register.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { EngineInterface, Register } from "claude-code";
|
|
2
|
+
|
|
3
|
+
import { atom, read, update } from "claude-code";
|
|
4
|
+
|
|
5
|
+
import type { PersonaSpinner, PersonaStatus } from "../types";
|
|
6
|
+
|
|
7
|
+
import { hashString } from "../src/util/hashString";
|
|
8
|
+
import { CardVerbs } from "./CardVerbs";
|
|
9
|
+
import { VerbDescriptionMap } from "./VerbDescriptionMap";
|
|
10
|
+
|
|
11
|
+
// The persona's surfaces drawn by the engine itself, so nothing is written into the person's settings: a spinner and
|
|
12
|
+
// A hint of this session's own character, its colour published for the mods, the spoken replies and one command per
|
|
13
|
+
// Verb. Every read of game data, state or audio is the plugin's node scripts, run as commands, since a hooks module
|
|
14
|
+
// Has no Node
|
|
15
|
+
const characterAtom = atom({ key: "character", plugin: "genshin-persona" } as const, {
|
|
16
|
+
color: "",
|
|
17
|
+
displayName: "",
|
|
18
|
+
name: "",
|
|
19
|
+
});
|
|
20
|
+
const isCharacterRecordedAtom = atom({ key: "isCharacterRecorded", plugin: "genshin-persona" } as const, false);
|
|
21
|
+
const spinnerAtom = atom({ key: "spinner", plugin: "genshin-persona" } as const, { label: "", tips: [], verbs: [] });
|
|
22
|
+
const tipIndexAtom = atom({ key: "tipIndex", plugin: "genshin-persona" } as const, 0);
|
|
23
|
+
// The spinner's lines cost the data package or the wiki on a first read, so its script gets the longest run allowed
|
|
24
|
+
const SPINNER_TIMEOUT_MS = Temporal.Duration.from({ minutes: 10 }).total("milliseconds");
|
|
25
|
+
|
|
26
|
+
const readStateDirectory = async ($: EngineInterface) => {
|
|
27
|
+
const home = (await $.env.get("HOME")) ?? (await $.env.get("USERPROFILE")) ?? "";
|
|
28
|
+
return `${home}/.claude/genshin-persona`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// The session's character, from the record its start hook wrote, else the pin, else the birthday pick; the spinner
|
|
32
|
+
// Is read again only when the character or the language that spells them changed
|
|
33
|
+
const refresh = async ($: EngineInterface) => {
|
|
34
|
+
const sessionId = await $.session.id();
|
|
35
|
+
const status = await $.process.run(["node", `${$.plugin.root}/scripts/status.mjs`], {
|
|
36
|
+
stdin: JSON.stringify({ session_id: sessionId }),
|
|
37
|
+
});
|
|
38
|
+
if (!status.stdout.trim()) return;
|
|
39
|
+
// oxlint-disable-next-line no-restricted-properties -- The plugin's own script prints this shape, with no date in it, and a mod cannot import the shared reviver
|
|
40
|
+
const { character, isRecorded } = JSON.parse(status.stdout) as PersonaStatus;
|
|
41
|
+
await update($, isCharacterRecordedAtom, () => isRecorded);
|
|
42
|
+
const previous = await read($, characterAtom);
|
|
43
|
+
await update($, characterAtom, () => character);
|
|
44
|
+
if (previous.name === character.name && previous.displayName === character.displayName) return;
|
|
45
|
+
|
|
46
|
+
const spinner = await $.process.run(["node", `${$.plugin.root}/scripts/spinner.mjs`, character.name], {
|
|
47
|
+
timeoutMs: SPINNER_TIMEOUT_MS,
|
|
48
|
+
});
|
|
49
|
+
// oxlint-disable-next-line no-restricted-properties -- The plugin's own script prints this shape, with no date in it, and a mod cannot import the shared reviver
|
|
50
|
+
if (spinner.stdout.trim()) await update($, spinnerAtom, () => JSON.parse(spinner.stdout) as PersonaSpinner);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Off the dispatch that asked, so neither the session's start nor a verb's answer waits on a node start
|
|
54
|
+
const refreshLater = ($: EngineInterface) => {
|
|
55
|
+
$.clock.after(0, () => {
|
|
56
|
+
// oxlint-disable-next-line typescript/no-floating-promises -- The engine's timer slot takes no promise
|
|
57
|
+
refresh($);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const register: Register = (on) => {
|
|
62
|
+
on("session.start", async ($, e, next) => {
|
|
63
|
+
await Promise.all(
|
|
64
|
+
Object.entries(VerbDescriptionMap).map(([verb, description]) =>
|
|
65
|
+
$.command.register({ argumentHint: "[name or value]", description, name: `genshin-${verb}` }),
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
refreshLater($);
|
|
69
|
+
return next(e);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Each verb runs the plugin's own script in this session, which the script reads off the environment, and the
|
|
73
|
+
// Character it may have switched is read again behind the answer
|
|
74
|
+
for (const verb of Object.keys(VerbDescriptionMap))
|
|
75
|
+
on("command.run", { command: `genshin-${verb}` }, async ($, e) => {
|
|
76
|
+
const sessionId = await $.session.id();
|
|
77
|
+
const { stderr, stdout } = await $.process.run(
|
|
78
|
+
["node", `${$.plugin.root}/scripts/genshin.mjs`, verb, ...e.args.split(" ").filter(Boolean)],
|
|
79
|
+
{ env: { CLAUDE_CODE_SESSION_ID: sessionId } },
|
|
80
|
+
);
|
|
81
|
+
refreshLater($);
|
|
82
|
+
const text = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
83
|
+
return CardVerbs.some((cardVerb) => cardVerb === verb)
|
|
84
|
+
? { context: ["Answer as the card this printed from this reply on."], text }
|
|
85
|
+
: { text };
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// A `/clear` or a resume carries on under another session id with no `session.start` for it, and the start hook
|
|
89
|
+
// Picks for that id, so the character is unsettled again until its record is read
|
|
90
|
+
on("session.end", async ($, e, next) => {
|
|
91
|
+
if (e.reason === "clear" || e.reason === "resume") await update($, isCharacterRecordedAtom, () => false);
|
|
92
|
+
return next(e);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// The character is read again at each turn's start until the start hook's record exists, since the pick may land
|
|
96
|
+
// After the session start's first read
|
|
97
|
+
on("turn.start", async ($, e, next) => {
|
|
98
|
+
if (!(await read($, isCharacterRecordedAtom))) refreshLater($);
|
|
99
|
+
return next(e);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
on("turn.complete", async ($, e, next) => {
|
|
103
|
+
const result = await next(e);
|
|
104
|
+
if (e.agentId === undefined) await update($, tipIndexAtom, (index) => index + 1);
|
|
105
|
+
return result;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// The engine samples a word per turn; the same word always maps to the same verb of this session's character
|
|
109
|
+
on("ui.render", { component: "Spinner" }, async ($, e, next) => {
|
|
110
|
+
const { verbs } = await read($, spinnerAtom);
|
|
111
|
+
if (verbs.length === 0 || e.props.message !== null) return next(e);
|
|
112
|
+
const word = verbs[hashString(e.props.word) % verbs.length] ?? e.props.word;
|
|
113
|
+
return next({ ...e, props: { ...e.props, word } });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
on("ui.render", { component: "PromptHint" }, async ($, e, next) => {
|
|
117
|
+
const { label, tips } = await read($, spinnerAtom);
|
|
118
|
+
const tip = tips[(await read($, tipIndexAtom)) % Math.max(tips.length, 1)];
|
|
119
|
+
if (!tip || e.props.isDraft) return next(e);
|
|
120
|
+
else return next({ ...e, props: { ...e.props, tail: `${label}: ${tip}` } });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// The character named among the footer's mode labels, where a standing word about the session belongs: the status
|
|
124
|
+
// Line is a settings command no plugin can set, and a label added to `modes` leaves every other plugin's in place
|
|
125
|
+
on("ui.render", { component: "SessionMode" }, async ($, e, next) => {
|
|
126
|
+
const { displayName } = await read($, characterAtom);
|
|
127
|
+
if (displayName) return next({ ...e, props: { ...e.props, modes: [...e.props.modes, displayName] } });
|
|
128
|
+
else return next(e);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Every flushed piece of a reply, handed to the speak script as the settings hook handed it; a session with no
|
|
132
|
+
// Voice set up, or muted, pays a file check and no node start
|
|
133
|
+
on("classic.MessageDisplay", async ($, e, next) => {
|
|
134
|
+
const stateDirectory = await readStateDirectory($);
|
|
135
|
+
const isSpeaking =
|
|
136
|
+
(await $.fs.exists(`${stateDirectory}/voice-language`)) && !(await $.fs.exists(`${stateDirectory}/muted`));
|
|
137
|
+
if (isSpeaking) await $.process.run(["node", `${$.plugin.root}/scripts/speak.mjs`], { stdin: JSON.stringify(e) });
|
|
138
|
+
return next(e);
|
|
139
|
+
});
|
|
140
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genshin-persona",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "A Claude Code plugin that speaks as the Genshin Impact character whose birthday is nearest: an answer opens with one spoken line in the character's voice and the rest is plain, a joke or a hello is spoken whole, and every line is read aloud in the character's own cloned voice as the reply is written.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -29,12 +29,18 @@
|
|
|
29
29
|
"runtime",
|
|
30
30
|
"scripts",
|
|
31
31
|
"skills",
|
|
32
|
+
"mod",
|
|
32
33
|
"src",
|
|
33
|
-
"
|
|
34
|
+
"types",
|
|
35
|
+
"!**/*.test.ts",
|
|
36
|
+
"!.claude-plugin/types"
|
|
34
37
|
],
|
|
35
38
|
"type": "module",
|
|
36
39
|
"imports": {
|
|
37
|
-
"#src/*": "./src/*.ts"
|
|
40
|
+
"#src/*": "./src/*.ts",
|
|
41
|
+
"#src/*.json": "./src/*.json",
|
|
42
|
+
"#scripts/*": "./scripts/*.ts",
|
|
43
|
+
"#types": "./types/index.d.ts"
|
|
38
44
|
},
|
|
39
45
|
"publishConfig": {
|
|
40
46
|
"access": "public"
|
|
@@ -51,10 +57,11 @@
|
|
|
51
57
|
"@typesafe-ai/sdk": "^0.6.0",
|
|
52
58
|
"@wasm-audio-decoders/ogg-vorbis": "^0.1.20",
|
|
53
59
|
"citty": "^0.2.2",
|
|
54
|
-
"genshin-db": "^5.2.14"
|
|
60
|
+
"genshin-db": "^5.2.14",
|
|
61
|
+
"tsx": "^4.23.15"
|
|
55
62
|
},
|
|
56
63
|
"engines": {
|
|
57
64
|
"node": ">=26.10.0"
|
|
58
65
|
},
|
|
59
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "7667bc8c9a2b4e9dcacd6b845ee8cc9951250ae7"
|
|
60
67
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"": {
|
|
7
7
|
"name": "genshin-persona-runtime",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@huggingface/transformers": "^4.3.
|
|
9
|
+
"@huggingface/transformers": "^4.3.1"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
"node_modules/@emnapi/runtime": {
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"license": "Apache-2.0"
|
|
36
36
|
},
|
|
37
37
|
"node_modules/@huggingface/transformers": {
|
|
38
|
-
"version": "4.3.
|
|
39
|
-
"resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-4.3.
|
|
40
|
-
"integrity": "sha512-
|
|
38
|
+
"version": "4.3.1",
|
|
39
|
+
"resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-4.3.1.tgz",
|
|
40
|
+
"integrity": "sha512-5+voSeUCFK6PCKlY15dPhNOKq3/zOTxDN7oC+Wy9dFb1QZ0TSQomZRRy7URRK8YRuUoE9fGMQTTPC5d/pWBJhA==",
|
|
41
41
|
"license": "Apache-2.0",
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@huggingface/jinja": "^0.5.10",
|
|
@@ -653,9 +653,9 @@
|
|
|
653
653
|
"license": "BSD-3-Clause"
|
|
654
654
|
},
|
|
655
655
|
"node_modules/@types/node": {
|
|
656
|
-
"version": "26.6.
|
|
657
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.
|
|
658
|
-
"integrity": "sha512-
|
|
656
|
+
"version": "26.6.4",
|
|
657
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.4.tgz",
|
|
658
|
+
"integrity": "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==",
|
|
659
659
|
"license": "MIT",
|
|
660
660
|
"dependencies": {
|
|
661
661
|
"undici-types": "~8.9.0"
|
package/runtime/package.json
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"private": true,
|
|
4
4
|
"description": "The persona plugin's speech engine runtime, installed by its voice verb into the plugin's state directory rather than shipped with the plugin.",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@huggingface/transformers": "^4.3.
|
|
6
|
+
"@huggingface/transformers": "^4.3.1"
|
|
7
7
|
}
|
|
8
8
|
}
|
package/scripts/genshin.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { readInterfaceLanguage } from "#src/services/readInterfaceLanguage";
|
|
|
4
4
|
import { readLocalization } from "#src/services/readLocalization";
|
|
5
5
|
import { runMain } from "citty";
|
|
6
6
|
|
|
7
|
-
// Every line the plugin prints is in the interface language, so a verb it does not know, or none, or a request for
|
|
8
|
-
//
|
|
7
|
+
// Every line the plugin prints is in the interface language, so a verb it does not know, or none, or a request for
|
|
8
|
+
// Help, is answered with its own usage line, and exits before citty would add an English one of its own
|
|
9
9
|
await runMain(genshinCommand, {
|
|
10
10
|
showUsage: async () => {
|
|
11
11
|
const { strings } = await readLocalization(readInterfaceLanguage());
|
package/scripts/pick.mjs
ADDED
package/scripts/pick.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SPEAK_LAUNCHER_PATH,
|
|
3
|
-
SPEAK_SCRIPT_PATH,
|
|
4
|
-
SPINNER_SCRIPT_PATH,
|
|
5
|
-
STATUS_LAUNCHER_PATH,
|
|
6
|
-
STATUS_SCRIPT_PATH,
|
|
7
|
-
} from "#src/services/constants";
|
|
8
1
|
import { deliverWarmRequest } from "#src/services/deliverWarmRequest";
|
|
9
2
|
import { formatLoreChart } from "#src/services/formatLoreChart";
|
|
10
3
|
import { formatUpcomingBirthdays } from "#src/services/formatUpcomingBirthdays";
|
|
@@ -20,8 +13,6 @@ import { readStdin } from "#src/services/readStdin";
|
|
|
20
13
|
import { readVoiceRemark } from "#src/services/readVoiceRemark";
|
|
21
14
|
import { registerQuietExit } from "#src/services/registerQuietExit";
|
|
22
15
|
import { resolveSessionCharacter } from "#src/services/resolveSessionCharacter";
|
|
23
|
-
import { spawnDetachedScript } from "#src/services/spawnDetachedScript";
|
|
24
|
-
import { writeLauncher } from "#src/services/writeLauncher";
|
|
25
16
|
|
|
26
17
|
// A session no character can be found for — the data unreadable, or a roster with no birthday on it — starts with
|
|
27
18
|
// No card, and the output style answers plainly
|
|
@@ -48,11 +39,7 @@ if (pick) {
|
|
|
48
39
|
: formatUpcomingBirthdays(roster, today, character.name, localization),
|
|
49
40
|
readVoiceRemark(localization.strings),
|
|
50
41
|
];
|
|
51
|
-
|
|
52
|
-
writeLauncher(SPEAK_LAUNCHER_PATH, SPEAK_SCRIPT_PATH);
|
|
53
|
-
// The spinner's lines cost the data package or the wiki, so they are read off this path; the warm waits for no
|
|
54
|
-
// More than a synthesizer's bind
|
|
55
|
-
spawnDetachedScript(SPINNER_SCRIPT_PATH, character.name);
|
|
42
|
+
// The warm waits for no more than a synthesizer's bind
|
|
56
43
|
await deliverWarmRequest(character.name, personaCard);
|
|
57
44
|
console.log(getSessionStartOutput(card, remarks, readReplyLanguage() ?? language, language));
|
|
58
45
|
}
|
package/scripts/spinner.ts
CHANGED
|
@@ -2,14 +2,14 @@ import { findCharacterByName } from "#src/services/findCharacterByName";
|
|
|
2
2
|
import { readInterfaceLanguage } from "#src/services/readInterfaceLanguage";
|
|
3
3
|
import { readPersonaCard } from "#src/services/readPersonaCard";
|
|
4
4
|
import { readRoster } from "#src/services/readRoster";
|
|
5
|
+
import { readSpinner } from "#src/services/readSpinner";
|
|
5
6
|
import { registerQuietExit } from "#src/services/registerQuietExit";
|
|
6
|
-
import { writeSessionSpinner } from "#src/services/writeSessionSpinner";
|
|
7
7
|
import { defineCommand, runMain } from "citty";
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
9
|
+
// The session's spinner for the hooks module, as JSON: its verbs and the character's lines under their name. Run off
|
|
10
|
+
// The session start's own path, since the lines cost the data package or the wiki. The character arrives as the
|
|
11
|
+
// English name, which is the identity, and the roster it is looked up in is the interface language's — so the label
|
|
12
|
+
// Is the name that language spells them by
|
|
13
13
|
registerQuietExit();
|
|
14
14
|
await runMain(
|
|
15
15
|
defineCommand({
|
|
@@ -21,11 +21,12 @@ await runMain(
|
|
|
21
21
|
type: "positional",
|
|
22
22
|
},
|
|
23
23
|
},
|
|
24
|
-
meta: { description: "
|
|
24
|
+
meta: { description: "Print the spinner for the session's character", name: "spinner" },
|
|
25
25
|
run: async ({ args }) => {
|
|
26
26
|
const language = readInterfaceLanguage();
|
|
27
27
|
const character = args.name ? findCharacterByName(readRoster(language), args.name) : undefined;
|
|
28
|
-
if (character)
|
|
28
|
+
if (character)
|
|
29
|
+
console.log(JSON.stringify(await readSpinner(character, await readPersonaCard(character.name), language)));
|
|
29
30
|
},
|
|
30
31
|
}),
|
|
31
32
|
);
|