ui-chan-mcp 0.5.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.
Files changed (151) hide show
  1. package/.claude-plugin/marketplace.json +13 -0
  2. package/.claude-plugin/plugin.json +6 -0
  3. package/.env.example +13 -0
  4. package/LICENSE +36 -0
  5. package/README.md +214 -0
  6. package/agents/mode.md +47 -0
  7. package/agents/talk.md +23 -0
  8. package/bin/ui-chan-mcp.mjs +11 -0
  9. package/bin/ui-chan-node +35 -0
  10. package/bin/ui-chan.mjs +350 -0
  11. package/context/AFFINITY.md +94 -0
  12. package/context/SOUL.md +79 -0
  13. package/context/VOCABULARY.md +108 -0
  14. package/cue.schema.json +109 -0
  15. package/cues/default.json +21 -0
  16. package/cues/emo_anger.json +14 -0
  17. package/cues/emo_anger_hi.json +19 -0
  18. package/cues/emo_anger_lo.json +16 -0
  19. package/cues/emo_antic.json +18 -0
  20. package/cues/emo_antic_hi.json +16 -0
  21. package/cues/emo_antic_lo.json +6 -0
  22. package/cues/emo_disgust.json +19 -0
  23. package/cues/emo_disgust_hi.json +20 -0
  24. package/cues/emo_disgust_lo.json +6 -0
  25. package/cues/emo_fear.json +19 -0
  26. package/cues/emo_fear_hi.json +19 -0
  27. package/cues/emo_fear_lo.json +19 -0
  28. package/cues/emo_joy.json +17 -0
  29. package/cues/emo_joy_hi.json +15 -0
  30. package/cues/emo_joy_lo.json +13 -0
  31. package/cues/emo_sad.json +15 -0
  32. package/cues/emo_sad_hi.json +18 -0
  33. package/cues/emo_sad_lo.json +14 -0
  34. package/cues/emo_surprise.json +15 -0
  35. package/cues/emo_surprise_hi.json +14 -0
  36. package/cues/emo_surprise_lo.json +11 -0
  37. package/cues/emo_trust.json +16 -0
  38. package/cues/emo_trust_hi.json +20 -0
  39. package/cues/emo_trust_lo.json +6 -0
  40. package/cues/idling_doze_1.json +9 -0
  41. package/cues/idling_doze_2.json +9 -0
  42. package/cues/idling_doze_3.json +9 -0
  43. package/cues/idling_giggle_1.json +9 -0
  44. package/cues/idling_giggle_2.json +9 -0
  45. package/cues/idling_lookaround_1.json +10 -0
  46. package/cues/idling_lookaround_2.json +10 -0
  47. package/cues/idling_lookaround_3.json +10 -0
  48. package/cues/idling_ponder.json +9 -0
  49. package/cues/idling_sigh_1.json +11 -0
  50. package/cues/idling_sigh_2.json +11 -0
  51. package/cues/idling_yawn_1.json +9 -0
  52. package/cues/idling_yawn_2.json +12 -0
  53. package/cues/idling_yawn_3.json +12 -0
  54. package/cues/mix_anger_antic.json +16 -0
  55. package/cues/mix_antic_joy.json +16 -0
  56. package/cues/mix_disgust_anger.json +17 -0
  57. package/cues/mix_fear_surprise.json +18 -0
  58. package/cues/mix_joy_trust.json +19 -0
  59. package/cues/mix_joy_trust_hi.json +18 -0
  60. package/cues/mix_sad_disgust.json +12 -0
  61. package/cues/mix_surprise_sad.json +21 -0
  62. package/cues/mix_surprise_sad_hi.json +14 -0
  63. package/cues/mix_trust_fear.json +18 -0
  64. package/cues/pose_arms_crossed.json +9 -0
  65. package/cues/pose_banzai.json +17 -0
  66. package/cues/pose_smug_arms.json +11 -0
  67. package/cues/pose_smug_hips.json +11 -0
  68. package/cues/pose_think.json +8 -0
  69. package/cues/pose_umbrella.json +9 -0
  70. package/cues/self_guilt.json +20 -0
  71. package/cues/self_pride.json +10 -0
  72. package/cues/self_shame.json +19 -0
  73. package/cues/self_shy.json +16 -0
  74. package/cues/self_shy_hi.json +17 -0
  75. package/cues/sys_address.json +9 -0
  76. package/cues/sys_awkward.json +18 -0
  77. package/cues/sys_beam.json +18 -0
  78. package/cues/sys_blank.json +11 -0
  79. package/cues/sys_care.json +18 -0
  80. package/cues/sys_confused.json +12 -0
  81. package/cues/sys_dazed.json +10 -0
  82. package/cues/sys_dread.json +21 -0
  83. package/cues/sys_fluster.json +20 -0
  84. package/cues/sys_intro.json +15 -0
  85. package/cues/sys_laugh.json +20 -0
  86. package/cues/sys_neutral.json +5 -0
  87. package/cues/sys_present.json +9 -0
  88. package/cues/sys_rain.json +15 -0
  89. package/cues/sys_refuse.json +18 -0
  90. package/cues/sys_relief.json +15 -0
  91. package/cues/sys_sleepy.json +8 -0
  92. package/cues/sys_smirk.json +10 -0
  93. package/cues/sys_stream.json +15 -0
  94. package/cues/sys_success.json +15 -0
  95. package/cues/sys_think.json +9 -0
  96. package/dist/app/assets.js +54 -0
  97. package/dist/app/cues.js +130 -0
  98. package/dist/app/editor-main.js +199 -0
  99. package/dist/app/editor-preload.js +17 -0
  100. package/dist/app/main.js +527 -0
  101. package/dist/app/persona.js +136 -0
  102. package/dist/app/preload.js +14 -0
  103. package/dist/app/state.js +850 -0
  104. package/dist/app/tts.js +257 -0
  105. package/dist/mcp-server.js +408 -0
  106. package/dist/renderer/bundle.js +17983 -0
  107. package/dist/renderer/editor.html +150 -0
  108. package/dist/renderer/editor.js +17917 -0
  109. package/dist/renderer/index.html +425 -0
  110. package/dist/shared/paths.js +169 -0
  111. package/dist/shared/set-cue-schema.js +66 -0
  112. package/dist/shared/types.js +14 -0
  113. package/docs/CLIENTS.md +110 -0
  114. package/docs/CUE_AUTHORING.md +76 -0
  115. package/docs/DEVELOPMENT.md +129 -0
  116. package/docs/PERSONA.md +61 -0
  117. package/docs/PSD_LAYERS.md +123 -0
  118. package/docs/README.md +43 -0
  119. package/docs/SETUP.html +684 -0
  120. package/docs/STYLE.md +107 -0
  121. package/docs/TOOLS.md +33 -0
  122. package/docs/TROUBLESHOOTING.md +122 -0
  123. package/docs/TTS.md +61 -0
  124. package/docs/design/CUE_CATALOG.md +259 -0
  125. package/docs/images/faces.png +0 -0
  126. package/docs/images/panel.png +0 -0
  127. package/hooks/fire-event.js +14 -0
  128. package/hooks/hooks.json +77 -0
  129. package/hooks/lib/mascot.js +76 -0
  130. package/hooks/notify.js +15 -0
  131. package/hooks/reaction.js +46 -0
  132. package/hooks/session-start.js +80 -0
  133. package/package.json +96 -0
  134. package/persona/ui-chan.md +111 -0
  135. package/plugins/hermes/ui-chan/__init__.py +98 -0
  136. package/plugins/hermes/ui-chan/plugin.yaml +7 -0
  137. package/plugins/opencode/ui-chan.mjs +105 -0
  138. package/skills/beam/SKILL.md +37 -0
  139. package/skills/eli14/SKILL.md +139 -0
  140. package/skills/eli14/references/base.html +495 -0
  141. package/skills/mode/SKILL.md +67 -0
  142. package/skills/talk/SKILL.md +27 -0
  143. package/tools/setup/check-package.mjs +47 -0
  144. package/tools/setup/clients.mjs +399 -0
  145. package/tools/setup/doctor.mjs +105 -0
  146. package/tools/setup/home.mjs +126 -0
  147. package/tools/setup/prompt.mjs +134 -0
  148. package/tools/setup/update-check.mjs +8 -0
  149. package/tools/setup/update.mjs +350 -0
  150. package/tools/stop-app.mjs +22 -0
  151. package/ui-chan.config.json +973 -0
@@ -0,0 +1,15 @@
1
+ {
2
+ "label": "配信",
3
+ "description": "配信・実況・発表",
4
+ "select": [
5
+ "!目/*にっこり1",
6
+ "!口/*ほほえみ",
7
+ "!奥の腕/*マイク",
8
+ "!手前の腕/*基本"
9
+ ],
10
+ "voice": {
11
+ "style_weights": {
12
+ "Happy": 1
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "label": "作業成功",
3
+ "description": "作業完了・ビルド通った・成功報告",
4
+ "select": [
5
+ "!奥の腕/*指さし横",
6
+ "!手前の腕/*腰",
7
+ "!口/*ほほえみ",
8
+ "!目/*にっこり1"
9
+ ],
10
+ "voice": {
11
+ "style_weights": {
12
+ "Happy": 1
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "label": "思考",
3
+ "description": "考え中・思案",
4
+ "select": [
5
+ "!手前の腕/*考える",
6
+ "!口/*んー",
7
+ "!目/*上向き1"
8
+ ]
9
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.findPsd = findPsd;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ /** First `.psd` (alphabetical) in the first assets dir that has one, or null.
40
+ * Shared by the mascot app (main.ts) and the Cue editor (editor-main.ts) so
41
+ * both discover the mascot PSD the same way. */
42
+ function findPsd(assetsDirs) {
43
+ for (const dir of Array.isArray(assetsDirs) ? assetsDirs : [assetsDirs]) {
44
+ if (!fs.existsSync(dir))
45
+ continue;
46
+ const psd = fs
47
+ .readdirSync(dir)
48
+ .filter((f) => f.toLowerCase().endsWith('.psd'))
49
+ .sort();
50
+ if (psd.length > 0)
51
+ return path.join(dir, psd[0]);
52
+ }
53
+ return null;
54
+ }
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.loadCues = loadCues;
40
+ exports.validateCueObject = validateCueObject;
41
+ exports.watchCues = watchCues;
42
+ exports.extractCueVoice = extractCueVoice;
43
+ const fs = __importStar(require("node:fs"));
44
+ const path = __importStar(require("node:path"));
45
+ const _2020_js_1 = __importDefault(require("ajv/dist/2020.js"));
46
+ const types_1 = require("../shared/types");
47
+ let validateCue = null;
48
+ function getValidator(schemaPath) {
49
+ if (!validateCue) {
50
+ const ajv = new _2020_js_1.default({ allErrors: true, strict: false });
51
+ validateCue = ajv.compile(JSON.parse(fs.readFileSync(schemaPath, 'utf-8')));
52
+ }
53
+ return validateCue;
54
+ }
55
+ /** One `<name>.json` = one Cue: a flat select/show/hide/blink/voice file,
56
+ * fully self-contained and validated against cue.schema.json — the schema
57
+ * file itself is the source of truth, not a hand-duplicated set of TS
58
+ * constraints. No inheritance, no bundling. */
59
+ function loadCues(dirs, schemaPath) {
60
+ const set = { cues: {}, errors: [] };
61
+ const validate = getValidator(schemaPath);
62
+ // Several dirs may contribute (packaged cues + the user's `~/.ui-chan/cues`).
63
+ // Later dirs win on a shared name, so a user override never has to fork the
64
+ // whole catalog — see src/shared/paths.ts.
65
+ const list = Array.isArray(dirs) ? dirs : [dirs];
66
+ if (list.length === 0)
67
+ set.errors.push('no cues directory configured');
68
+ for (const dir of list) {
69
+ if (!fs.existsSync(dir)) {
70
+ set.errors.push(`cues directory not found: ${dir}`);
71
+ continue;
72
+ }
73
+ for (const file of fs
74
+ .readdirSync(dir)
75
+ .filter((f) => f.endsWith('.json'))
76
+ .sort()) {
77
+ const name = path.basename(file, '.json');
78
+ try {
79
+ const raw = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf-8'));
80
+ if (!validate(raw)) {
81
+ const detail = (validate.errors ?? [])
82
+ .map((e) => `${e.instancePath || '/'} ${e.message}`)
83
+ .join('; ');
84
+ set.errors.push(`${file}: ${detail}`);
85
+ continue;
86
+ }
87
+ set.cues[name] = raw;
88
+ }
89
+ catch (e) {
90
+ set.errors.push(`${file}: ${e instanceof Error ? e.message : e}`);
91
+ }
92
+ }
93
+ }
94
+ if (!set.cues[types_1.DEFAULT_CUE_NAME]) {
95
+ set.errors.push(`no "${types_1.DEFAULT_CUE_NAME}" cue found in ${list.join(', ')} — falling back to an empty default (no select/show/hide, blink off)`);
96
+ set.cues[types_1.DEFAULT_CUE_NAME] = {};
97
+ }
98
+ return set;
99
+ }
100
+ /** Validate one Cue object against cue.schema.json (used by the editor before
101
+ * writing a file). Returns null on success or a joined error string. */
102
+ function validateCueObject(obj, schemaPath) {
103
+ const validate = getValidator(schemaPath);
104
+ if (validate(obj))
105
+ return null;
106
+ return (validate.errors ?? []).map((e) => `${e.instancePath || '/'} ${e.message}`).join('; ');
107
+ }
108
+ function watchCues(dirs, onChange) {
109
+ let timer = null;
110
+ const fire = () => {
111
+ if (timer)
112
+ clearTimeout(timer);
113
+ timer = setTimeout(() => {
114
+ timer = null;
115
+ onChange();
116
+ }, 300);
117
+ };
118
+ for (const dir of Array.isArray(dirs) ? dirs : [dirs]) {
119
+ if (fs.existsSync(dir))
120
+ fs.watch(dir, fire);
121
+ }
122
+ }
123
+ function extractCueVoice(set) {
124
+ const out = {};
125
+ for (const [name, cue] of Object.entries(set.cues)) {
126
+ if (cue.voice)
127
+ out[name] = cue.voice;
128
+ }
129
+ return out;
130
+ }
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fs = __importStar(require("node:fs"));
37
+ const path = __importStar(require("node:path"));
38
+ const electron_1 = require("electron");
39
+ const paths_1 = require("../shared/paths");
40
+ const types_1 = require("../shared/types");
41
+ const assets_1 = require("./assets");
42
+ const cues_1 = require("./cues");
43
+ const tts_1 = require("./tts");
44
+ // Standalone Cue editor window. A separate Electron entry from the mascot app
45
+ // (main.ts): opaque + framed, self-renders the PSD for preview, reads/writes
46
+ // cues/*.json directly, and previews voice via its own TTS client. Runs
47
+ // independently of the mascot — saving a cue is picked up live by a running
48
+ // mascot through its watchCues() hot-reload.
49
+ const projectRoot = path.resolve(__dirname, '..', '..');
50
+ // TTS credentials for "試し喋り" live in .env / env vars (never in the config).
51
+ (0, paths_1.loadEnvFiles)(projectRoot);
52
+ const paths = (0, paths_1.resolvePaths)(projectRoot);
53
+ const config = paths.config;
54
+ const assetsDirs = paths.assetsDirs;
55
+ const cueDirs = paths.cueDirs;
56
+ const cueSchemaPath = paths.cueSchemaFile;
57
+ const tts = config.tts?.enabled ? new tts_1.VoiSonaTalkClient(config.tts) : null;
58
+ const CUE_NAME_RE = /^[A-Za-z0-9_-]+$/;
59
+ /** Resolve `<name>.json` inside one cue dir, rejecting bad names / escapes. */
60
+ function cueFileIn(dir, name) {
61
+ if (!CUE_NAME_RE.test(name) || name === types_1.DEFAULT_CUE_NAME)
62
+ return null;
63
+ const p = path.join(dir, `${name}.json`);
64
+ if (p !== path.join(dir, path.basename(p)) || !p.startsWith(dir + path.sep))
65
+ return null;
66
+ return p;
67
+ }
68
+ /** Where an edit is saved — the user's dir when there is one, so an update of
69
+ * the package never overwrites hand-authored cues (shared/paths.ts). */
70
+ function cueWritePath(name) {
71
+ return cueFileIn(paths.cueWriteDir, name);
72
+ }
73
+ /** The file a cue currently lives in, searched with the same precedence
74
+ * loadCues() uses (last dir wins). */
75
+ function cueExistingPath(name) {
76
+ for (const dir of [...cueDirs].reverse()) {
77
+ const p = cueFileIn(dir, name);
78
+ if (p && fs.existsSync(p))
79
+ return p;
80
+ }
81
+ return null;
82
+ }
83
+ function createWindow() {
84
+ const win = new electron_1.BrowserWindow({
85
+ width: 1180,
86
+ height: 800,
87
+ minWidth: 900,
88
+ minHeight: 600,
89
+ title: '雨衣ちゃんのデバッグルーム',
90
+ webPreferences: {
91
+ preload: path.join(__dirname, 'editor-preload.js'),
92
+ contextIsolation: true,
93
+ },
94
+ });
95
+ win.loadFile(path.join(projectRoot, 'dist', 'renderer', 'editor.html'));
96
+ }
97
+ electron_1.app.whenReady().then(() => {
98
+ createWindow();
99
+ electron_1.app.on('activate', () => {
100
+ if (electron_1.BrowserWindow.getAllWindows().length === 0)
101
+ createWindow();
102
+ });
103
+ });
104
+ electron_1.app.on('window-all-closed', () => electron_1.app.quit());
105
+ electron_1.ipcMain.handle('editor:get-init', () => ({
106
+ psdAvailable: (0, assets_1.findPsd)(assetsDirs) !== null,
107
+ lipSync: config.lipSync ?? null,
108
+ }));
109
+ electron_1.ipcMain.handle('editor:read-psd', () => {
110
+ const psd = (0, assets_1.findPsd)(assetsDirs);
111
+ return psd ? fs.readFileSync(psd) : null;
112
+ });
113
+ electron_1.ipcMain.handle('editor:list-cues', () => {
114
+ const { cues } = (0, cues_1.loadCues)(cueDirs, cueSchemaPath);
115
+ return Object.entries(cues)
116
+ .filter(([name]) => name !== types_1.DEFAULT_CUE_NAME)
117
+ .map(([name, cue]) => ({
118
+ name,
119
+ label: cue.label,
120
+ internal: cue.internal ?? false,
121
+ description: cue.description,
122
+ }))
123
+ .sort((a, b) => Number(a.internal) - Number(b.internal) || a.name.localeCompare(b.name));
124
+ });
125
+ electron_1.ipcMain.handle('editor:read-cue', (_ev, name) => {
126
+ const p = cueExistingPath(name);
127
+ if (!p)
128
+ return null;
129
+ try {
130
+ return JSON.parse(fs.readFileSync(p, 'utf-8'));
131
+ }
132
+ catch {
133
+ return null;
134
+ }
135
+ });
136
+ electron_1.ipcMain.handle('editor:read-default', () => {
137
+ const p = [...cueDirs]
138
+ .reverse()
139
+ .map((d) => path.join(d, `${types_1.DEFAULT_CUE_NAME}.json`))
140
+ .find((f) => fs.existsSync(f));
141
+ if (!p)
142
+ return {};
143
+ try {
144
+ return JSON.parse(fs.readFileSync(p, 'utf-8'));
145
+ }
146
+ catch {
147
+ return {};
148
+ }
149
+ });
150
+ electron_1.ipcMain.handle('editor:write-cue', (_ev, name, cue) => {
151
+ const p = cueWritePath(name);
152
+ if (!p)
153
+ return { ok: false, error: `不正なCue名: "${name}"` };
154
+ fs.mkdirSync(path.dirname(p), { recursive: true });
155
+ const err = (0, cues_1.validateCueObject)(cue, cueSchemaPath);
156
+ if (err)
157
+ return { ok: false, error: `スキーマ検証エラー: ${err}` };
158
+ try {
159
+ fs.writeFileSync(p, `${JSON.stringify(cue, null, 2)}\n`, 'utf-8');
160
+ return { ok: true };
161
+ }
162
+ catch (e) {
163
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
164
+ }
165
+ });
166
+ electron_1.ipcMain.handle('editor:delete-cue', (_ev, name) => {
167
+ if (!CUE_NAME_RE.test(name) || name === types_1.DEFAULT_CUE_NAME)
168
+ return { ok: false, error: `不正なCue名: "${name}"` };
169
+ const p = cueExistingPath(name);
170
+ if (!p)
171
+ return { ok: false, error: `存在しません: ${name}` };
172
+ try {
173
+ fs.unlinkSync(p);
174
+ return { ok: true };
175
+ }
176
+ catch (e) {
177
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
178
+ }
179
+ });
180
+ // CueSequence names (IdlingCues + FidgetCues) whose steps reference this
181
+ // cue — a delete-safety warning so removing a cue used by a sequence doesn't
182
+ // silently break it.
183
+ electron_1.ipcMain.handle('editor:cue-refs', (_ev, name) => {
184
+ const pools = [
185
+ { label: 'idling', items: config.idle?.idlingCues?.items },
186
+ { label: 'poke', items: config.interactions?.poke },
187
+ ];
188
+ const refs = [];
189
+ for (const { label, items } of pools) {
190
+ (items ?? []).forEach((item, i) => {
191
+ if ((item.steps ?? []).some((s) => s.cue === name)) {
192
+ refs.push(`${label}:${item.name ?? `#${i}`}`);
193
+ }
194
+ });
195
+ }
196
+ return refs;
197
+ });
198
+ electron_1.ipcMain.handle('editor:list-styles', () => (tts ? tts.listStyles() : null));
199
+ electron_1.ipcMain.handle('editor:synthesize', (_ev, text, voice) => tts ? tts.synthesizeWithVoice(text, voice) : null);
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const electron_1 = require("electron");
4
+ // Bridge for the Cue editor renderer (editor.ts). Mirrors preload.ts's shape
5
+ // but exposes editor-only, file-mutating operations under window.uiEditor.
6
+ electron_1.contextBridge.exposeInMainWorld('uiEditor', {
7
+ getInit: () => electron_1.ipcRenderer.invoke('editor:get-init'),
8
+ readPsd: () => electron_1.ipcRenderer.invoke('editor:read-psd'),
9
+ listCues: () => electron_1.ipcRenderer.invoke('editor:list-cues'),
10
+ readCue: (name) => electron_1.ipcRenderer.invoke('editor:read-cue', name),
11
+ readDefault: () => electron_1.ipcRenderer.invoke('editor:read-default'),
12
+ writeCue: (name, cue) => electron_1.ipcRenderer.invoke('editor:write-cue', name, cue),
13
+ deleteCue: (name) => electron_1.ipcRenderer.invoke('editor:delete-cue', name),
14
+ cueRefs: (name) => electron_1.ipcRenderer.invoke('editor:cue-refs', name),
15
+ listStyles: () => electron_1.ipcRenderer.invoke('editor:list-styles'),
16
+ synthesize: (text, voice) => electron_1.ipcRenderer.invoke('editor:synthesize', text, voice),
17
+ });