ui-chan-mcp 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/LICENSE +0 -15
- package/NOTICE.md +31 -0
- package/README.md +4 -1
- package/dist/app/main.js +109 -50
- package/dist/renderer/bundle.js +1 -1
- package/dist/renderer/index.html +85 -73
- package/docs/DEVELOPMENT.md +23 -2
- package/docs/images/panel.png +0 -0
- package/package.json +3 -3
- package/plugins/hermes/ui-chan/plugin.yaml +1 -1
- package/tools/setup/check-package.mjs +43 -2
- package/.env.example +0 -13
package/LICENSE
CHANGED
|
@@ -19,18 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
このライセンスは **このリポジトリのソフトウェアにのみ** 適用されます。
|
|
26
|
-
|
|
27
|
-
本プロジェクトは**非公式のファン制作物**であり、下記の権利者とは一切関係がありません。
|
|
28
|
-
キャラクターを扱う以上、**二次創作のガイドラインおよび各利用規約は、この MIT ライセンスより
|
|
29
|
-
優先されます**。公序良俗に反する利用、誹謗中傷、権利者の名誉・ブランドを毀損する利用、
|
|
30
|
-
公式を騙る行為は固くお断りします。
|
|
31
|
-
|
|
32
|
-
- 立ち絵素材「雨衣(うい)」(坂本アヒル様)はこのリポジトリに含まれておらず、
|
|
33
|
-
MIT ライセンスの対象ではありません。利用は雨衣キャラクターガイドライン
|
|
34
|
-
(https://www.ui-roid.com/guidelines/) の範囲で、各自が入手してください。
|
|
35
|
-
- VoiSona Talk(テクノスピーチ社)も同梱していません。各自でインストールし、
|
|
36
|
-
同社の利用規約に従ってください。
|
package/NOTICE.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 権利表記(NOTICE)
|
|
2
|
+
|
|
3
|
+
> **本ソフトウェアが依存している、第三者の素材・製品についての表記です。**
|
|
4
|
+
> MIT ライセンス([LICENSE](LICENSE))が適用されるのは、このリポジトリのソフトウェアだけです。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
このライセンスは **このリポジトリのソフトウェアにのみ** 適用されます。
|
|
9
|
+
|
|
10
|
+
本プロジェクトは**非公式のファン制作物**であり、下記の権利者とは一切関係がありません。
|
|
11
|
+
キャラクターを扱う以上、**二次創作のガイドラインおよび各利用規約は、この MIT ライセンスより
|
|
12
|
+
優先されます**。公序良俗に反する利用、誹謗中傷、権利者の名誉・ブランドを毀損する利用、
|
|
13
|
+
公式を騙る行為は固くお断りします。
|
|
14
|
+
|
|
15
|
+
- 立ち絵素材「雨衣(うい)」(坂本アヒル様)はこのリポジトリに含まれておらず、
|
|
16
|
+
MIT ライセンスの対象ではありません。利用は雨衣キャラクターガイドライン
|
|
17
|
+
(https://www.ui-roid.com/guidelines/) の範囲で、各自が入手してください。
|
|
18
|
+
- VoiSona Talk(テクノスピーチ社)も同梱していません。各自でインストールし、
|
|
19
|
+
同社の利用規約に従ってください。
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 本プロジェクトの位置づけ
|
|
24
|
+
|
|
25
|
+
本プロジェクトは**非公式のファン制作物**であり、上記の権利者とは一切関係がありません。
|
|
26
|
+
キャラクターを扱う以上、**二次創作のガイドラインおよび各利用規約は MIT ライセンスより優先されます**。
|
|
27
|
+
|
|
28
|
+
公序良俗に反する利用、特定の個人・団体への誹謗中傷、権利者の名誉・ブランドを毀損する利用、
|
|
29
|
+
公式・公認を騙る行為は固くお断りします。
|
|
30
|
+
|
|
31
|
+
詳細は [README.md](README.md#ライセンスと権利表記) を参照してください。
|
package/README.md
CHANGED
|
@@ -108,7 +108,8 @@ ui-chan
|
|
|
108
108
|
| 🔊 | 声だけ止める(ミュート) |
|
|
109
109
|
| ⊘ | いま喋ってるのを止める |
|
|
110
110
|
| ♡ | どのくらい仲良しか見る・変える |
|
|
111
|
-
|
|
|
111
|
+
| ⟳ | 起動しなおす(定位置にも戻る) |
|
|
112
|
+
| ⬇ | 更新する(**更新があるときだけ出る**) |
|
|
112
113
|
| ⏻ | 終了する(呼ばれても起きない) |
|
|
113
114
|
|
|
114
115
|
<sub>複数のセッションから繋がってるときは、そこに一覧が出るよ(どのツールの、どのプロジェクトか)。
|
|
@@ -162,6 +163,7 @@ VoiSona Talk が起きてないか、REST API が有効になってないか、
|
|
|
162
163
|
| 新しい表情を作りたい | [docs/CUE_AUTHORING.md](docs/CUE_AUTHORING.md) |
|
|
163
164
|
| わたしの性格を変えたい | [docs/PERSONA.md](docs/PERSONA.md) |
|
|
164
165
|
| **中のコードを直したい** | [docs/](docs/README.md) — 開発者向けの資料はこっちに全部ある |
|
|
166
|
+
| 手伝ってくれるなら | [CONTRIBUTING.md](CONTRIBUTING.md) — 歓迎するもの/お断りするもの |
|
|
165
167
|
|
|
166
168
|
---
|
|
167
169
|
|
|
@@ -173,6 +175,7 @@ VoiSona Talk が起きてないか、REST API が有効になってないか、
|
|
|
173
175
|
### 本ソフトウェア
|
|
174
176
|
|
|
175
177
|
MIT License([LICENSE](LICENSE))。Copyright (c) 2026 Uncle-Peke。
|
|
178
|
+
第三者の素材・製品についての表記は [NOTICE.md](NOTICE.md) にもまとめてあります。
|
|
176
179
|
|
|
177
180
|
MIT が適用されるのは**このリポジトリのコードと、同梱の設定・Cue 定義・ドキュメントのみ**です。
|
|
178
181
|
以下の2つは同梱しておらず、MIT の対象外です。
|
package/dist/app/main.js
CHANGED
|
@@ -90,7 +90,10 @@ let nextAgentId = 1;
|
|
|
90
90
|
* every failure — "can't tell" and "up to date" look the same on screen, and
|
|
91
91
|
* neither is worth a warning. */
|
|
92
92
|
function checkForUpdate() {
|
|
93
|
-
|
|
93
|
+
// `process.execPath` は Electron 本体を指す(ここは Electron のメインプロセス)。
|
|
94
|
+
// それで .mjs を起動すると Electron がアプリとして立ち上がろうとして、
|
|
95
|
+
// 更新チェックは一度も成功しない。node を探すランチャ経由で起動する。
|
|
96
|
+
const child = (0, node_child_process_1.spawn)(path.join(projectRoot, 'bin', 'ui-chan-node'), [path.join(projectRoot, 'tools', 'setup', 'update-check.mjs'), projectRoot], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
94
97
|
let out = '';
|
|
95
98
|
child.stdout?.on('data', (d) => {
|
|
96
99
|
out += d;
|
|
@@ -215,6 +218,18 @@ function handleDebug(_ws, req) {
|
|
|
215
218
|
state.onInteraction(action.kind ?? 'poke');
|
|
216
219
|
return { id: req.id, ok: true, result: { ok: true } };
|
|
217
220
|
}
|
|
221
|
+
case 'panel': {
|
|
222
|
+
return { id: req.id, ok: true, result: panelAction(action.kind, action.value) };
|
|
223
|
+
}
|
|
224
|
+
case 'fake_update': {
|
|
225
|
+
sendToRenderer({
|
|
226
|
+
type: 'update',
|
|
227
|
+
available: action.available,
|
|
228
|
+
behind: action.behind ?? 3,
|
|
229
|
+
blocked: null,
|
|
230
|
+
});
|
|
231
|
+
return { id: req.id, ok: true, result: { ok: true } };
|
|
232
|
+
}
|
|
218
233
|
default: {
|
|
219
234
|
return { id: req.id, ok: false, error: `unknown debug action` };
|
|
220
235
|
}
|
|
@@ -368,14 +383,103 @@ function scheduleExitIfIdle() {
|
|
|
368
383
|
electron_1.app.quit();
|
|
369
384
|
}, sec * 1000);
|
|
370
385
|
}
|
|
371
|
-
|
|
386
|
+
/** 定位置=主ディスプレイの作業領域の右下。起動時とリセット時の両方が
|
|
387
|
+
* ここを見るので、「起動し直さないと位置が戻らない」ということはない。 */
|
|
388
|
+
function homePosition() {
|
|
372
389
|
const { width, height, margin } = config.window;
|
|
373
390
|
const wa = electron_1.screen.getPrimaryDisplay().workArea;
|
|
391
|
+
return { x: wa.x + wa.width - width - margin, y: wa.y + wa.height - height - margin };
|
|
392
|
+
}
|
|
393
|
+
/** パネルのボタンの実体。IPC からも、デバッグ用の WS アクションからも同じ
|
|
394
|
+
* ものを呼ぶ——押した結果を確かめる方法が無いと、今回のように
|
|
395
|
+
* 「押したのに何も起きない」不具合を見つけられない。 */
|
|
396
|
+
function panelAction(kind, value) {
|
|
397
|
+
switch (kind) {
|
|
398
|
+
case 'affinity':
|
|
399
|
+
// The panel is the only place a human can move affinity directly; the
|
|
400
|
+
// agent's own adjust_affinity stays direction+magnitude, so this can't
|
|
401
|
+
// be used to sneak past the asymmetric curve on her behalf.
|
|
402
|
+
if (typeof value === 'number')
|
|
403
|
+
state.setAffinity(value);
|
|
404
|
+
return state.affinitySnapshot();
|
|
405
|
+
case 'affinity:get':
|
|
406
|
+
return state.affinitySnapshot();
|
|
407
|
+
case 'mute':
|
|
408
|
+
muted = true;
|
|
409
|
+
return { muted };
|
|
410
|
+
case 'unmute':
|
|
411
|
+
muted = false;
|
|
412
|
+
return { muted };
|
|
413
|
+
case 'clear':
|
|
414
|
+
state.clear();
|
|
415
|
+
return { ok: true };
|
|
416
|
+
case 'restart':
|
|
417
|
+
electron_1.app.relaunch();
|
|
418
|
+
electron_1.app.quit();
|
|
419
|
+
return { ok: true };
|
|
420
|
+
case 'update': {
|
|
421
|
+
// 更新はこのプロセスが読んでいるファイルそのものを書き換えるので、
|
|
422
|
+
// 自分より長生きする子プロセスにやらせる(取得 → npm install →
|
|
423
|
+
// build → 起動しなおし)。
|
|
424
|
+
//
|
|
425
|
+
// ただし **投げっぱなしにはしない**。更新が無かった場合や失敗した
|
|
426
|
+
// 場合、子は何もせず終わるので、「着替えてくる」と言ったまま彼女が
|
|
427
|
+
// 戻ってこないように見える。結果を受け取って必ず言い直す。
|
|
428
|
+
const child = (0, node_child_process_1.spawn)(path.join(projectRoot, 'bin', 'ui-chan-node'), [path.join(projectRoot, 'bin', 'ui-chan.mjs'), 'update'], { detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
429
|
+
let out = '';
|
|
430
|
+
child.stdout?.on('data', (d) => {
|
|
431
|
+
out += d;
|
|
432
|
+
});
|
|
433
|
+
child.stderr?.on('data', (d) => {
|
|
434
|
+
out += d;
|
|
435
|
+
});
|
|
436
|
+
child.on('close', (code) => {
|
|
437
|
+
// 更新できた場合、子が stop → start するのでこの行には来ない
|
|
438
|
+
// (来たとしても、そのときは何も起きていない)。
|
|
439
|
+
// 理由は言わない。git が汚れているとか追跡先が無いとかは、使う人には
|
|
440
|
+
// 意味の無い話で、npm で入れた人には git の概念すら無い。**結果だけ**
|
|
441
|
+
// 言い、詳細は stderr へ落とす。
|
|
442
|
+
if (code === 0) {
|
|
443
|
+
state.setCue({
|
|
444
|
+
cue: 'emo_joy_lo',
|
|
445
|
+
text: '更新するものなかったよ。',
|
|
446
|
+
reading: 'こうしんするものなかったよ。',
|
|
447
|
+
}, 'panel');
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
console.error(`[ui-chan] update failed:\n${out}`);
|
|
451
|
+
state.setCue({
|
|
452
|
+
cue: 'sys_awkward',
|
|
453
|
+
text: 'うまく更新できなかった。',
|
|
454
|
+
reading: 'うまくこうしんできなかった。',
|
|
455
|
+
}, 'panel');
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
child.unref();
|
|
459
|
+
state.setCue({
|
|
460
|
+
cue: 'sys_think',
|
|
461
|
+
text: '着替えてくる。ちょっと待ってて。',
|
|
462
|
+
reading: 'きがえてくる。ちょっとまってて。',
|
|
463
|
+
}, 'panel');
|
|
464
|
+
return { ok: true };
|
|
465
|
+
}
|
|
466
|
+
case 'quit':
|
|
467
|
+
// Nothing to coordinate: a bridge only launches the app at its own
|
|
468
|
+
// startup, so quitting stays quit until a person starts her again.
|
|
469
|
+
electron_1.app.quit();
|
|
470
|
+
return { ok: true };
|
|
471
|
+
default:
|
|
472
|
+
return { ok: false };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function createWindow() {
|
|
476
|
+
const { width, height } = config.window;
|
|
477
|
+
const home = homePosition();
|
|
374
478
|
win = new electron_1.BrowserWindow({
|
|
375
479
|
width,
|
|
376
480
|
height,
|
|
377
|
-
x:
|
|
378
|
-
y:
|
|
481
|
+
x: home.x,
|
|
482
|
+
y: home.y,
|
|
379
483
|
transparent: true,
|
|
380
484
|
frame: false,
|
|
381
485
|
resizable: false,
|
|
@@ -417,52 +521,7 @@ else {
|
|
|
417
521
|
/** The panel's buttons. Deliberately few and all reversible-or-obvious:
|
|
418
522
|
* anything destructive belongs in the CLI, not in a window that pops open
|
|
419
523
|
* on its own. */
|
|
420
|
-
electron_1.ipcMain.handle('ui-chan:panel-action', (_ev, kind, value) =>
|
|
421
|
-
switch (kind) {
|
|
422
|
-
case 'affinity':
|
|
423
|
-
// The panel is the only place a human can move affinity directly; the
|
|
424
|
-
// agent's own adjust_affinity stays direction+magnitude, so this can't
|
|
425
|
-
// be used to sneak past the asymmetric curve on her behalf.
|
|
426
|
-
if (typeof value === 'number')
|
|
427
|
-
state.setAffinity(value);
|
|
428
|
-
return state.affinitySnapshot();
|
|
429
|
-
case 'affinity:get':
|
|
430
|
-
return state.affinitySnapshot();
|
|
431
|
-
case 'mute':
|
|
432
|
-
muted = true;
|
|
433
|
-
return { muted };
|
|
434
|
-
case 'unmute':
|
|
435
|
-
muted = false;
|
|
436
|
-
return { muted };
|
|
437
|
-
case 'clear':
|
|
438
|
-
state.clear();
|
|
439
|
-
return { ok: true };
|
|
440
|
-
case 'restart':
|
|
441
|
-
electron_1.app.relaunch();
|
|
442
|
-
electron_1.app.quit();
|
|
443
|
-
return { ok: true };
|
|
444
|
-
case 'update': {
|
|
445
|
-
// The update rewrites the very files this process is running from, so
|
|
446
|
-
// it happens in a detached child that outlives us: pull, npm install,
|
|
447
|
-
// build, then start the app again on the new build.
|
|
448
|
-
const child = (0, node_child_process_1.spawn)(path.join(projectRoot, 'bin', 'ui-chan-node'), [path.join(projectRoot, 'bin', 'ui-chan.mjs'), 'update'], { detached: true, stdio: 'ignore' });
|
|
449
|
-
child.unref();
|
|
450
|
-
state.setCue({
|
|
451
|
-
cue: 'sys_think',
|
|
452
|
-
text: '着替えてくる。ちょっと待ってて。',
|
|
453
|
-
reading: 'きがえてくる。ちょっとまってて。',
|
|
454
|
-
}, 'panel');
|
|
455
|
-
return { ok: true };
|
|
456
|
-
}
|
|
457
|
-
case 'quit':
|
|
458
|
-
// Nothing to coordinate: a bridge only launches the app at its own
|
|
459
|
-
// startup, so quitting stays quit until a person starts her again.
|
|
460
|
-
electron_1.app.quit();
|
|
461
|
-
return { ok: true };
|
|
462
|
-
default:
|
|
463
|
-
return { ok: false };
|
|
464
|
-
}
|
|
465
|
-
});
|
|
524
|
+
electron_1.ipcMain.handle('ui-chan:panel-action', (_ev, kind, value) => panelAction(kind, value));
|
|
466
525
|
electron_1.ipcMain.handle('ui-chan:get-init', () => {
|
|
467
526
|
const psdFile = findPsd();
|
|
468
527
|
return { config, psdAvailable: psdFile !== null, psdFile };
|
package/dist/renderer/bundle.js
CHANGED
|
@@ -17896,7 +17896,7 @@
|
|
|
17896
17896
|
if (typeof res?.muted === "boolean") {
|
|
17897
17897
|
muted = res.muted;
|
|
17898
17898
|
btn.classList.toggle("on", muted);
|
|
17899
|
-
btn.title = muted ? "\u58F0\u3092\u3082\u3069\u3059" : "\
|
|
17899
|
+
btn.title = muted ? "\u58F0\u3092\u3082\u3069\u3059" : "\u58F0\u3060\u3051\u6B62\u3081\u308B";
|
|
17900
17900
|
}
|
|
17901
17901
|
});
|
|
17902
17902
|
}
|
package/dist/renderer/index.html
CHANGED
|
@@ -235,44 +235,12 @@
|
|
|
235
235
|
margin-top: 2px;
|
|
236
236
|
letter-spacing: 0.02em;
|
|
237
237
|
}
|
|
238
|
-
/* 操作はアイコン1
|
|
239
|
-
|
|
240
|
-
意味はすべて title(ツールチップ)で補う。 */
|
|
241
|
-
/* 好感度スライダー。歯車で開くまで存在しない(普段のパネルは「誰がいるか」
|
|
242
|
-
だけを答える場所で、設定はそのついでに触れればいい)。 */
|
|
243
|
-
#panel-affinity {
|
|
244
|
-
margin-top: 6px;
|
|
245
|
-
border-top: 1px solid rgba(78, 91, 181, 0.2);
|
|
246
|
-
padding: 6px 4px 2px;
|
|
247
|
-
}
|
|
248
|
-
#panel-affinity[hidden] {
|
|
249
|
-
display: none;
|
|
250
|
-
}
|
|
251
|
-
.aff-head {
|
|
252
|
-
display: flex;
|
|
253
|
-
align-items: baseline;
|
|
254
|
-
gap: 6px;
|
|
255
|
-
color: #6b6577;
|
|
256
|
-
font-size: 11px;
|
|
257
|
-
}
|
|
258
|
-
.aff-head b {
|
|
259
|
-
color: #2e2a35;
|
|
260
|
-
font-size: 13px;
|
|
261
|
-
margin-left: auto;
|
|
262
|
-
}
|
|
263
|
-
.aff-head small {
|
|
264
|
-
font-size: 10.5px;
|
|
265
|
-
color: #4e5bb5;
|
|
266
|
-
}
|
|
267
|
-
#aff-range {
|
|
268
|
-
width: 100%;
|
|
269
|
-
margin: 4px 0 0;
|
|
270
|
-
accent-color: #4e5bb5;
|
|
271
|
-
}
|
|
238
|
+
/* 操作はアイコン1行。文字を置かないぶん「アプリのUI」に見えにくく、
|
|
239
|
+
高さも固定なので、接続が増えて縦に伸びる見え方を邪魔しない。 */
|
|
272
240
|
#panel-actions {
|
|
273
241
|
display: flex;
|
|
242
|
+
justify-content: space-between;
|
|
274
243
|
align-items: center;
|
|
275
|
-
gap: 2px;
|
|
276
244
|
margin-top: 6px;
|
|
277
245
|
border-top: 1px solid rgba(78, 91, 181, 0.2);
|
|
278
246
|
padding-top: 6px;
|
|
@@ -283,8 +251,13 @@
|
|
|
283
251
|
background: transparent;
|
|
284
252
|
border: none;
|
|
285
253
|
border-radius: 8px;
|
|
286
|
-
|
|
287
|
-
|
|
254
|
+
/* 当たり判定を全部そろえる。図形の大小で押しやすさが変わらないように */
|
|
255
|
+
width: 30px;
|
|
256
|
+
height: 26px;
|
|
257
|
+
padding: 0;
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
justify-content: center;
|
|
288
261
|
color: #4a4560;
|
|
289
262
|
}
|
|
290
263
|
#panel-actions button:hover {
|
|
@@ -296,31 +269,74 @@
|
|
|
296
269
|
height: 17px;
|
|
297
270
|
fill: none;
|
|
298
271
|
stroke: currentColor;
|
|
299
|
-
stroke-width: 1.
|
|
272
|
+
stroke-width: 1.5;
|
|
300
273
|
stroke-linecap: round;
|
|
301
274
|
stroke-linejoin: round;
|
|
302
275
|
}
|
|
303
|
-
/* ミュート中は「音が消えている」形(波→斜線)に変わり、色でも分かる。
|
|
276
|
+
/* ミュート中は「音が消えている」形(波→斜線)に変わり、色でも分かる。
|
|
277
|
+
詳細度が上がる順に並べる(下がる順だと、あとの規則が効かない事故を招く)。 */
|
|
304
278
|
#panel-actions .i-slash {
|
|
305
279
|
display: none;
|
|
306
280
|
}
|
|
307
|
-
#panel-actions .i-fill {
|
|
308
|
-
fill: currentColor;
|
|
309
|
-
stroke: none;
|
|
310
|
-
}
|
|
311
281
|
#panel-actions button.on {
|
|
312
282
|
color: #4e5bb5;
|
|
313
283
|
background: rgba(78, 91, 181, 0.16);
|
|
314
284
|
}
|
|
315
|
-
#panel-gear.on svg path {
|
|
316
|
-
fill: currentColor;
|
|
317
|
-
}
|
|
318
285
|
#panel-actions button.on .i-wave {
|
|
319
286
|
display: none;
|
|
320
287
|
}
|
|
321
288
|
#panel-actions button.on .i-slash {
|
|
322
289
|
display: inline;
|
|
323
290
|
}
|
|
291
|
+
#panel-gear.on svg path {
|
|
292
|
+
fill: currentColor;
|
|
293
|
+
}
|
|
294
|
+
/* 終了だけは押し間違えると困るので、触れたときだけ赤くして手を止めさせる。 */
|
|
295
|
+
#panel-actions button[data-action='quit'] {
|
|
296
|
+
color: #8a8497;
|
|
297
|
+
}
|
|
298
|
+
#panel-actions button[data-action='quit']:hover {
|
|
299
|
+
color: #b4515e;
|
|
300
|
+
background: rgba(180, 81, 94, 0.12);
|
|
301
|
+
}
|
|
302
|
+
/* 更新は「あるときだけ」出す。常設すると平常時の情報がひとつ増える。 */
|
|
303
|
+
#panel-update[hidden] {
|
|
304
|
+
display: none;
|
|
305
|
+
}
|
|
306
|
+
#panel-update {
|
|
307
|
+
color: #4e5bb5;
|
|
308
|
+
}
|
|
309
|
+
/* 好感度スライダー。歯車で開くまで存在しない(普段のパネルは「誰がいるか」
|
|
310
|
+
だけを答える場所で、設定はそのついでに触れればいい)。 */
|
|
311
|
+
#panel-affinity {
|
|
312
|
+
margin-top: 6px;
|
|
313
|
+
border-top: 1px solid rgba(78, 91, 181, 0.2);
|
|
314
|
+
padding: 6px 4px 2px;
|
|
315
|
+
}
|
|
316
|
+
#panel-affinity[hidden] {
|
|
317
|
+
display: none;
|
|
318
|
+
}
|
|
319
|
+
.aff-head {
|
|
320
|
+
display: flex;
|
|
321
|
+
align-items: baseline;
|
|
322
|
+
gap: 6px;
|
|
323
|
+
color: #6b6577;
|
|
324
|
+
font-size: 11px;
|
|
325
|
+
}
|
|
326
|
+
.aff-head b {
|
|
327
|
+
color: #2e2a35;
|
|
328
|
+
font-size: 13px;
|
|
329
|
+
margin-left: auto;
|
|
330
|
+
}
|
|
331
|
+
.aff-head small {
|
|
332
|
+
font-size: 10.5px;
|
|
333
|
+
color: #4e5bb5;
|
|
334
|
+
}
|
|
335
|
+
#aff-range {
|
|
336
|
+
width: 100%;
|
|
337
|
+
margin: 4px 0 0;
|
|
338
|
+
accent-color: #4e5bb5;
|
|
339
|
+
}
|
|
324
340
|
/* 更新の入口は「あるときだけ」出す。常設すると平常時の情報がひとつ増える。 */
|
|
325
341
|
#panel-update[hidden] {
|
|
326
342
|
display: none;
|
|
@@ -354,50 +370,46 @@
|
|
|
354
370
|
<input id="aff-range" type="range" min="0" max="100" step="1" value="0" />
|
|
355
371
|
</div>
|
|
356
372
|
<div id="panel-actions">
|
|
357
|
-
|
|
373
|
+
<!-- 並びは左から「よく押す → 設定 → 起き直す → 終了」。手が滑って隣を
|
|
374
|
+
押しても軽い操作にしか当たらないよう、終了は必ず右端。
|
|
375
|
+
「定位置に戻す」は独立したボタンにしない——再起動すれば位置は戻るし、
|
|
376
|
+
好感度は持ち越すので、再起動に副作用が無いから。
|
|
377
|
+
説明はブラウザ標準のツールチップ(title)に任せる。 -->
|
|
378
|
+
<button type="button" data-action="mute" title="声だけ止める" aria-label="声だけ止める">
|
|
358
379
|
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
359
|
-
<path d="M4
|
|
360
|
-
<path class="i-wave" d="M13.
|
|
361
|
-
<path class="i-slash" d="M13.
|
|
380
|
+
<path d="M4.4 8h2.8L11 4.8v10.4L7.2 12H4.4z" />
|
|
381
|
+
<path class="i-wave" d="M13.6 7.4a3.4 3.4 0 0 1 0 5.2" />
|
|
382
|
+
<path class="i-slash" d="M13.4 7.6l4 4.8M17.4 7.6l-4 4.8" />
|
|
362
383
|
</svg>
|
|
363
384
|
</button>
|
|
364
|
-
<button type="button" data-action="clear" title="
|
|
385
|
+
<button type="button" data-action="clear" title="いま喋っているのを止める" aria-label="喋るのをやめる">
|
|
365
386
|
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
<circle cx="10" cy="10" r="6.4" />
|
|
369
|
-
<path d="M5.5 14.5L14.5 5.5" />
|
|
387
|
+
<circle cx="10" cy="10" r="6.2" />
|
|
388
|
+
<path d="M5.6 14.4L14.4 5.6" />
|
|
370
389
|
</svg>
|
|
371
390
|
</button>
|
|
372
391
|
<button type="button" id="panel-gear" title="好感度" aria-label="好感度">
|
|
373
392
|
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
374
|
-
|
|
375
|
-
歯車より、中身をそのまま指すほうが正直(設定が増えたら歯車に
|
|
376
|
-
戻す)。 -->
|
|
377
|
-
<path d="M10 16.2S3.4 12.3 3.4 7.9a3.5 3.5 0 0 1 6.6-1.6 3.5 3.5 0 0 1 6.6 1.6c0 4.4-6.6 8.3-6.6 8.3z" />
|
|
393
|
+
<path d="M10 15.8S3.8 12.2 3.8 8.1a3.3 3.3 0 0 1 6.2-1.5 3.3 3.3 0 0 1 6.2 1.5c0 4.1-6.2 7.7-6.2 7.7z" />
|
|
378
394
|
</svg>
|
|
379
395
|
</button>
|
|
380
|
-
<button type="button" data-action="restart" title="
|
|
396
|
+
<button type="button" data-action="restart" title="起動しなおす(定位置に戻る)" aria-label="起動しなおす">
|
|
381
397
|
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
<path d="M3.4 9.3L10 3.6l6.6 5.7" />
|
|
385
|
-
<path d="M5.2 8.4v7.4h9.6V8.4" />
|
|
386
|
-
<path d="M8.4 15.8v-4.1h3.2v4.1" />
|
|
398
|
+
<path d="M16 7.6A6.4 6.4 0 1 0 16.3 12" />
|
|
399
|
+
<path d="M16.6 3.6v4.2h-4.2" />
|
|
387
400
|
</svg>
|
|
388
401
|
</button>
|
|
389
|
-
<button type="button" data-action="update" id="panel-update" title="
|
|
402
|
+
<button type="button" data-action="update" id="panel-update" title="更新があります" aria-label="更新する" hidden>
|
|
390
403
|
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
391
|
-
|
|
392
|
-
<path d="
|
|
393
|
-
<path d="
|
|
394
|
-
<path d="M4.2 14.2v1.4a1 1 0 0 0 1 1h9.6a1 1 0 0 0 1-1v-1.4" />
|
|
404
|
+
<path d="M10 4v7.6" />
|
|
405
|
+
<path d="M6.8 8.6L10 11.8l3.2-3.2" />
|
|
406
|
+
<path d="M4.6 14v1.2a1 1 0 0 0 1 1h8.8a1 1 0 0 0 1-1V14" />
|
|
395
407
|
</svg>
|
|
396
408
|
</button>
|
|
397
|
-
<button type="button" data-action="quit" title="
|
|
409
|
+
<button type="button" data-action="quit" title="終了する(呼ばれても起きない)" aria-label="終了">
|
|
398
410
|
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
399
|
-
<path d="M10
|
|
400
|
-
<path d="M6.
|
|
411
|
+
<path d="M10 4v6" />
|
|
412
|
+
<path d="M6.4 6.2a5.6 5.6 0 1 0 7.2 0" />
|
|
401
413
|
</svg>
|
|
402
414
|
</button>
|
|
403
415
|
</div>
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -75,7 +75,28 @@ README に載せている画像もこれで撮っています。
|
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
|
-
## `05`
|
|
78
|
+
## `05` 環境変数
|
|
79
|
+
|
|
80
|
+
すべて省略可能です。設定ファイル(`~/.ui-chan/config.json`)より**環境変数が優先**されます。
|
|
81
|
+
|
|
82
|
+
| 変数 | 効果 |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `UI_CHAN_TTS_USERNAME` / `UI_CHAN_TTS_PASSWORD` | VoiSona Talk の資格情報。通常は `~/.ui-chan/.env` に置きます |
|
|
85
|
+
| `UI_CHAN_HOME` | ユーザーデータの場所(既定 `~/.ui-chan`) |
|
|
86
|
+
| `UI_CHAN_PORT` | アプリが待ち受ける WebSocket ポート(既定 8123) |
|
|
87
|
+
| `UI_CHAN_AGENT_NAME` | `get_state` に出るエージェント名。既定は MCP クライアントが名乗る名前 |
|
|
88
|
+
| `UI_CHAN_NO_PERSONA_INSTRUCTIONS` | `1` で、MCP ハンドシェイクでの人格注入をやめる |
|
|
89
|
+
| `UI_CHAN_NO_PERSONA_HOOK` | `1` で、SessionStart フックでの人格注入をやめる(アプリ起動はする) |
|
|
90
|
+
| `UI_CHAN_OPENCODE_CONFIG` / `UI_CHAN_HERMES_CONFIG` | それぞれの設定ファイルの場所を上書き |
|
|
91
|
+
| `HERMES_HOME` | Hermes Agent のホーム(既定 `~/.hermes`) |
|
|
92
|
+
| `UI_CHAN_ROOT` | Hermes プラグインが ui-chan の場所を見つけるための上書き |
|
|
93
|
+
|
|
94
|
+
<sub>以前は `.env.example` を同梱していましたが、`ui-chan` の対話セットアップが
|
|
95
|
+
`~/.ui-chan/.env` を作って中身も書くようになったため、削除しました。</sub>
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## `06` 変更が反映されるタイミング
|
|
79
100
|
|
|
80
101
|
| 直したもの | 反映 |
|
|
81
102
|
|---|---|
|
|
@@ -88,7 +109,7 @@ README に載せている画像もこれで撮っています。
|
|
|
88
109
|
|
|
89
110
|
---
|
|
90
111
|
|
|
91
|
-
## `
|
|
112
|
+
## `07` アーキテクチャ
|
|
92
113
|
|
|
93
114
|
MCP サーバは薄いブリッジで、**状態はすべて Electron アプリ側に一元化**されています。
|
|
94
115
|
複数のエージェントが同時に繋いでも状態が食い違いません。
|
package/docs/images/panel.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui-chan-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "雨衣ちゃんMCP — デスクトップの隅に住む、AI の体になるマスコット(PSD立ち絵+吹き出し+音声。MCP で操作)",
|
|
5
5
|
"main": "dist/app/main.js",
|
|
6
6
|
"private": false,
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
".claude-plugin",
|
|
60
60
|
"ui-chan.config.json",
|
|
61
61
|
"cue.schema.json",
|
|
62
|
-
".env.example",
|
|
63
62
|
"docs",
|
|
64
63
|
"README.md",
|
|
65
|
-
"LICENSE"
|
|
64
|
+
"LICENSE",
|
|
65
|
+
"NOTICE.md"
|
|
66
66
|
],
|
|
67
67
|
"repository": {
|
|
68
68
|
"type": "git",
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
// This is now the *only* thing standing between a mistake and the registry:
|
|
17
17
|
// the package is public, so `npm publish` will succeed unless this fails.
|
|
18
18
|
import { execFileSync } from 'node:child_process';
|
|
19
|
+
import * as fs from 'node:fs';
|
|
20
|
+
import * as os from 'node:os';
|
|
21
|
+
import * as path from 'node:path';
|
|
19
22
|
|
|
20
23
|
const FORBIDDEN = [
|
|
21
24
|
{ re: /\.psd$/i, why: '立ち絵PSD(二次配布禁止)' },
|
|
@@ -29,8 +32,46 @@ const FORBIDDEN = [
|
|
|
29
32
|
{ re: /voisona/i, why: 'VoiSona Talk 由来のファイル' },
|
|
30
33
|
];
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
// **実際に tarball を作って、その中身を見る。**
|
|
36
|
+
//
|
|
37
|
+
// 以前は `npm pack --json` の出力を解釈していたが、環境差で二度こけた:
|
|
38
|
+
// (1) `prepare`(ビルド)の出力が JSON の前後に混ざり、しかも npm の版で
|
|
39
|
+
// stdout か stderr かが変わる。(2) npm 11 は `[{...}]`、npm 12 は
|
|
40
|
+
// `{"名前": {...}}` と形そのものが変わる。どちらも「手元では通って CI で
|
|
41
|
+
// だけ落ちる」壊れ方をした。
|
|
42
|
+
//
|
|
43
|
+
// この検査はライセンス素材の混入を止める最後の砦なので、npm の表示仕様に
|
|
44
|
+
// 依存させない。tar が読める実体だけを見る。
|
|
45
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ui-chan-pack-'));
|
|
46
|
+
let files;
|
|
47
|
+
try {
|
|
48
|
+
// --ignore-scripts: ビルドし直す必要はない(見たいのは、いまディスクに
|
|
49
|
+
// あるもののうち何が詰められるか)。--pack-destination: 作業ツリーを汚さない。
|
|
50
|
+
const packed = execFileSync('npm', ['pack', '--ignore-scripts', '--pack-destination', tmp], {
|
|
51
|
+
encoding: 'utf-8',
|
|
52
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
53
|
+
});
|
|
54
|
+
const tgz = fs
|
|
55
|
+
.readdirSync(tmp)
|
|
56
|
+
.filter((f) => f.endsWith('.tgz'))
|
|
57
|
+
.map((f) => path.join(tmp, f))[0];
|
|
58
|
+
if (!tgz) {
|
|
59
|
+
console.error(`❌ tarball が作られませんでした:\n${packed}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
// tar の一覧は "package/<パス>" 形式。ディレクトリ行(末尾 /)は捨てる。
|
|
63
|
+
files = execFileSync('tar', ['-tzf', tgz], { encoding: 'utf-8', maxBuffer: 32 * 1024 * 1024 })
|
|
64
|
+
.split('\n')
|
|
65
|
+
.filter((l) => l && !l.endsWith('/'))
|
|
66
|
+
.map((l) => l.replace(/^package\//, ''));
|
|
67
|
+
} finally {
|
|
68
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (files.length === 0) {
|
|
72
|
+
console.error('❌ tarball の中身を読めませんでした。');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
34
75
|
|
|
35
76
|
const hits = files.flatMap((path) => {
|
|
36
77
|
const rule = FORBIDDEN.find((r) => r.re.test(path));
|
package/.env.example
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# Example environment variables for local development/debugging.
|
|
2
|
-
# Copy this file to `.env` and fill in your VoiSona Talk credentials.
|
|
3
|
-
# `.env` is gitignored so credentials never get committed.
|
|
4
|
-
|
|
5
|
-
# VoiSona Talk REST API credentials (used by TTS synthesis)
|
|
6
|
-
UI_CHAN_TTS_USERNAME=your_voisona_username
|
|
7
|
-
UI_CHAN_TTS_PASSWORD=your_voisona_api_password
|
|
8
|
-
|
|
9
|
-
# Optional: override the WebSocket port the display app listens on
|
|
10
|
-
# UI_CHAN_PORT=8123
|
|
11
|
-
|
|
12
|
-
# Optional: override the agent name reported in get_state
|
|
13
|
-
# UI_CHAN_AGENT_NAME=debug
|