@horizon_works/banto 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.
- package/README.md +109 -0
- package/env.js +83 -0
- package/gate_core.js +629 -0
- package/gtasks.js +233 -0
- package/license/client.js +138 -0
- package/package.json +45 -0
- package/payload/compliance/SKILL.md +113 -0
- package/payload/compliance/dict/00_/345/213/225/344/275/234/347/242/272/350/252/215.json +14 -0
- package/payload/compliance/dict/06_house_rules.json +16 -0
- package/payload/compliance/dict/_sample/04_common_keihyo.json +270 -0
- package/payload/compliance/dict/_sample/README.md +72 -0
- package/payload/compliance/lint.js +265 -0
- package/payload/disciplines//347/225/252/351/240/255/343/201/256/344/275/234/346/263/225.md +64 -0
- package/payload/disciplines//350/207/252/345/267/261/345/201/245/350/250/272.md +74 -0
- package/payload/disciplines//351/200/261/346/254/241/343/203/241/343/203/263/343/203/206.md +60 -0
- package/payload/migration/protocol.md +35 -0
- package/payload/skill-maker/SKILL_base_anthropic.md +357 -0
- package/payload/skill-maker/skill-maker-v1.0.md +305 -0
- package/payload/skills/_/343/201/202/343/201/250/343/201/247/350/266/263/343/201/233/343/202/213/343/202/202/343/201/256.md +47 -0
- package/payload/skills/image-gen/SKILL.md +147 -0
- package/payload/skills/image-gen/gen.js +225 -0
- package/payload/skills/remember/SETUP_Google/351/200/243/346/220/272.md +106 -0
- package/payload/skills/remember/SKILL.md +158 -0
- package/payload/skills/remember/handoff.js +149 -0
- package/payload/skills/remember/tasks.js +320 -0
- package/payload/skills/slide-deck/SKILL.md +160 -0
- package/payload/skills/transcribe/README.md +182 -0
- package/payload/skills/transcribe/dict.json +10 -0
- package/payload/skills/transcribe/enroll_speaker.py +76 -0
- package/payload/skills/transcribe/identify_speakers.py +153 -0
- package/payload/skills/transcribe/transcribe.py +126 -0
- package/privacy.js +234 -0
- package/server.js +1671 -0
- package/shelf.js +172 -0
- package/subconscious.js +150 -0
- package/supplements.json +192 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* やることリスト(Google Tasks)
|
|
3
|
+
*
|
|
4
|
+
* 使い方:
|
|
5
|
+
* node kit/skills/remember/tasks.js setup # 初回の接続・リスト作成
|
|
6
|
+
* node kit/skills/remember/tasks.js lists # リスト一覧
|
|
7
|
+
* node kit/skills/remember/tasks.js list [リスト名] # やること一覧(既定:全リスト)
|
|
8
|
+
* node kit/skills/remember/tasks.js add "やること" [リスト名] [--due YYYY-MM-DD] [--note "メモ"]
|
|
9
|
+
* node kit/skills/remember/tasks.js done <taskId> # 完了
|
|
10
|
+
* node kit/skills/remember/tasks.js delete <taskId> # 削除
|
|
11
|
+
* node kit/skills/remember/tasks.js rename "旧名" "新名" # リスト名を変える
|
|
12
|
+
*
|
|
13
|
+
* ★Googleとの接続が済んでいないと動きません(→ SETUP_Google連携.md)。
|
|
14
|
+
* 接続前でも、引き継ぎメモ(handoff.js)はそのまま使えます。
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs').promises;
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
// ★部品が入っていないときに、Nodeの生のエラーを見せない。
|
|
21
|
+
// 何をすればよいかが分からない画面は、そこで人を止めてしまうので。
|
|
22
|
+
let authenticate, google, format;
|
|
23
|
+
try {
|
|
24
|
+
({ authenticate } = require('@google-cloud/local-auth'));
|
|
25
|
+
({ google } = require('googleapis'));
|
|
26
|
+
({ format } = require('date-fns'));
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log(' やることリスト(Google Tasks)は、まだ使えません。');
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log(' Googleとの接続が済んでいないためです。');
|
|
32
|
+
console.log(' やり方は kit/skills/remember/SETUP_Google連携.md にあります。');
|
|
33
|
+
console.log(' 番頭に「Google連携をやりたい」と言えば、順番に案内します。');
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(' ★接続しなくても、引き継ぎメモは使えます:');
|
|
36
|
+
console.log(' node kit/skills/remember/handoff.js list');
|
|
37
|
+
console.log('');
|
|
38
|
+
console.log(' やることは second-brain/state.md の「決まっていないこと」に書いておけます。');
|
|
39
|
+
console.log('');
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const SCOPES = [
|
|
44
|
+
'https://www.googleapis.com/auth/calendar',
|
|
45
|
+
'https://www.googleapis.com/auth/tasks',
|
|
46
|
+
'https://www.googleapis.com/auth/drive.readonly'
|
|
47
|
+
];
|
|
48
|
+
const TOKEN_PATH = path.join(__dirname, 'token.json');
|
|
49
|
+
const CREDENTIALS_PATH = path.join(__dirname, 'credentials.json');
|
|
50
|
+
|
|
51
|
+
// タスクリスト定義
|
|
52
|
+
// ★名前は変えて構いません。その人の暮らしに合う分け方が正解です。
|
|
53
|
+
// ただし変えたら kit/skills/remember/SKILL.md の表も直してください。
|
|
54
|
+
const TASK_LISTS = [
|
|
55
|
+
'仕事', // 仕事・勉強・活動
|
|
56
|
+
'暮らし', // 家のこと・手続き・買うもの
|
|
57
|
+
'やりたいこと', // 期限は無いが忘れたくないもの
|
|
58
|
+
'確認まち', // 人の返事を待っているもの
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// ─── 認証 ───
|
|
62
|
+
async function loadSavedCredentialsIfExist() {
|
|
63
|
+
try {
|
|
64
|
+
const content = await fs.readFile(TOKEN_PATH);
|
|
65
|
+
const credentials = JSON.parse(content);
|
|
66
|
+
return google.auth.fromJSON(credentials);
|
|
67
|
+
} catch { return null; }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function saveCredentials(client) {
|
|
71
|
+
const content = await fs.readFile(CREDENTIALS_PATH);
|
|
72
|
+
const keys = JSON.parse(content);
|
|
73
|
+
const key = keys.installed || keys.web;
|
|
74
|
+
await fs.writeFile(TOKEN_PATH, JSON.stringify({
|
|
75
|
+
type: 'authorized_user',
|
|
76
|
+
client_id: key.client_id,
|
|
77
|
+
client_secret: key.client_secret,
|
|
78
|
+
refresh_token: client.credentials.refresh_token,
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function authorize() {
|
|
83
|
+
let client = await loadSavedCredentialsIfExist();
|
|
84
|
+
if (client) return client;
|
|
85
|
+
client = await authenticate({ scopes: SCOPES, keyfilePath: CREDENTIALS_PATH });
|
|
86
|
+
if (client.credentials) await saveCredentials(client);
|
|
87
|
+
return client;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── ヘルパー ───
|
|
91
|
+
async function getTaskLists(service) {
|
|
92
|
+
const res = await service.tasklists.list({ maxResults: 100 });
|
|
93
|
+
return res.data.items || [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function findListByName(service, name) {
|
|
97
|
+
const lists = await getTaskLists(service);
|
|
98
|
+
return lists.find(l => l.title === name) || null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── setup: タスクリスト作成 ───
|
|
102
|
+
async function setup(service) {
|
|
103
|
+
console.log('\n📋 タスクリストセットアップ...\n');
|
|
104
|
+
const existing = await getTaskLists(service);
|
|
105
|
+
for (const name of TASK_LISTS) {
|
|
106
|
+
const found = existing.find(l => l.title === name);
|
|
107
|
+
if (found) {
|
|
108
|
+
console.log(` ⏭️ 「${name}」は既に存在 (ID: ${found.id})`);
|
|
109
|
+
} else {
|
|
110
|
+
const res = await service.tasklists.insert({ resource: { title: name } });
|
|
111
|
+
console.log(` ✅ 「${name}」を作成 (ID: ${res.data.id})`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
console.log('\n🎉 セットアップ完了!');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── lists: タスクリスト一覧 ───
|
|
118
|
+
async function listTaskLists(service) {
|
|
119
|
+
const lists = await getTaskLists(service);
|
|
120
|
+
console.log('\n📋 タスクリスト一覧:\n');
|
|
121
|
+
for (const l of lists) {
|
|
122
|
+
console.log(` ${l.title} (${l.id})`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─── list: タスク一覧 ───
|
|
127
|
+
async function listTasks(service, listName) {
|
|
128
|
+
const lists = await getTaskLists(service);
|
|
129
|
+
let targetLists;
|
|
130
|
+
if (listName) {
|
|
131
|
+
const found = lists.find(l => l.title === listName);
|
|
132
|
+
if (!found) { console.error(`❌ リスト「${listName}」が見つかりません`); return; }
|
|
133
|
+
targetLists = [found];
|
|
134
|
+
} else {
|
|
135
|
+
targetLists = lists.filter(l => TASK_LISTS.includes(l.title));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let totalCount = 0;
|
|
139
|
+
for (const tl of targetLists) {
|
|
140
|
+
const res = await service.tasks.list({
|
|
141
|
+
tasklist: tl.id,
|
|
142
|
+
showCompleted: false,
|
|
143
|
+
showHidden: false,
|
|
144
|
+
maxResults: 100,
|
|
145
|
+
});
|
|
146
|
+
const tasks = res.data.items || [];
|
|
147
|
+
if (tasks.length === 0) continue;
|
|
148
|
+
|
|
149
|
+
console.log(`\n📂 ${tl.title}`);
|
|
150
|
+
for (const t of tasks) {
|
|
151
|
+
const due = t.due ? ` | 期限: ${format(new Date(t.due), 'yyyy-MM-dd')}` : '';
|
|
152
|
+
const note = t.notes ? ` | メモ: ${t.notes.substring(0, 50)}` : '';
|
|
153
|
+
console.log(` ☐ ${t.title}${due}${note}`);
|
|
154
|
+
console.log(` ID: ${t.id}`);
|
|
155
|
+
totalCount++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (totalCount === 0) {
|
|
160
|
+
console.log('\n📭 未完了タスクはありません。');
|
|
161
|
+
} else {
|
|
162
|
+
console.log(`\n合計: ${totalCount}件`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ─── list-json: タスクをJSONで出力(task-graph依存レイヤー用・完了も含む) ───
|
|
167
|
+
async function listTasksJson(service) {
|
|
168
|
+
const lists = await getTaskLists(service);
|
|
169
|
+
const targetLists = lists.filter(l => TASK_LISTS.includes(l.title));
|
|
170
|
+
const out = [];
|
|
171
|
+
for (const tl of targetLists) {
|
|
172
|
+
const res = await service.tasks.list({
|
|
173
|
+
tasklist: tl.id,
|
|
174
|
+
showCompleted: true, // blocker解決のため完了タスクも取得
|
|
175
|
+
showHidden: true,
|
|
176
|
+
maxResults: 100,
|
|
177
|
+
});
|
|
178
|
+
for (const t of (res.data.items || [])) {
|
|
179
|
+
out.push({
|
|
180
|
+
id: t.id,
|
|
181
|
+
title: t.title,
|
|
182
|
+
status: t.status, // 'needsAction' | 'completed'
|
|
183
|
+
due: t.due || null,
|
|
184
|
+
updated: t.updated || null,
|
|
185
|
+
list: tl.title,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
console.log(JSON.stringify(out));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── add: タスク追加 ───
|
|
193
|
+
async function addTask(service, title, listName, opts = {}) {
|
|
194
|
+
const targetListName = listName || TASK_LISTS[0];
|
|
195
|
+
const list = await findListByName(service, targetListName);
|
|
196
|
+
if (!list) {
|
|
197
|
+
console.error(`❌ リスト「${targetListName}」が見つかりません。先に setup を実行してください。`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const resource = { title };
|
|
202
|
+
if (opts.due) resource.due = new Date(opts.due + 'T00:00:00Z').toISOString();
|
|
203
|
+
if (opts.note) resource.notes = opts.note;
|
|
204
|
+
|
|
205
|
+
const res = await service.tasks.insert({ tasklist: list.id, resource });
|
|
206
|
+
console.log(`✅ タスク追加: 「${title}」→ ${targetListName} (ID: ${res.data.id})`);
|
|
207
|
+
return res.data;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ─── done: タスク完了 ───
|
|
211
|
+
async function completeTask(service, taskId) {
|
|
212
|
+
// タスクIDからリストを探す
|
|
213
|
+
const lists = await getTaskLists(service);
|
|
214
|
+
for (const tl of lists) {
|
|
215
|
+
try {
|
|
216
|
+
const task = await service.tasks.get({ tasklist: tl.id, task: taskId });
|
|
217
|
+
if (task.data) {
|
|
218
|
+
await service.tasks.update({
|
|
219
|
+
tasklist: tl.id,
|
|
220
|
+
task: taskId,
|
|
221
|
+
resource: { ...task.data, status: 'completed' }
|
|
222
|
+
});
|
|
223
|
+
console.log(`✅ 「${task.data.title}」を完了にしました。`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
} catch {}
|
|
227
|
+
}
|
|
228
|
+
console.error(`❌ タスクID ${taskId} が見つかりません`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ─── delete: タスク削除 ───
|
|
232
|
+
async function deleteTask(service, taskId) {
|
|
233
|
+
const lists = await getTaskLists(service);
|
|
234
|
+
for (const tl of lists) {
|
|
235
|
+
try {
|
|
236
|
+
await service.tasks.delete({ tasklist: tl.id, task: taskId });
|
|
237
|
+
console.log(`🗑️ タスク ${taskId} を削除しました。`);
|
|
238
|
+
return;
|
|
239
|
+
} catch {}
|
|
240
|
+
}
|
|
241
|
+
console.error(`❌ タスクID ${taskId} が見つかりません`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ─── rename: タスクリスト名変更 ───
|
|
245
|
+
async function renameList(service, oldName, newName) {
|
|
246
|
+
const list = await findListByName(service, oldName);
|
|
247
|
+
if (!list) {
|
|
248
|
+
console.error(`❌ リスト「${oldName}」が見つかりません`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
await service.tasklists.patch({
|
|
252
|
+
tasklist: list.id,
|
|
253
|
+
resource: { title: newName }
|
|
254
|
+
});
|
|
255
|
+
console.log(`✅ リスト名を変更: 「${oldName}」→「${newName}」`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ─── Main CLI ───
|
|
259
|
+
(async () => {
|
|
260
|
+
const args = process.argv.slice(2);
|
|
261
|
+
const cmd = args[0];
|
|
262
|
+
|
|
263
|
+
if (!cmd || !['setup', 'lists', 'list', 'list-json', 'add', 'done', 'delete', 'rename'].includes(cmd)) {
|
|
264
|
+
console.log(`
|
|
265
|
+
📋 Google Tasks 管理
|
|
266
|
+
|
|
267
|
+
使い方:
|
|
268
|
+
node kit/skills/remember/tasks.js setup # タスクリスト作成
|
|
269
|
+
node kit/skills/remember/tasks.js lists # タスクリスト一覧
|
|
270
|
+
node kit/skills/remember/tasks.js list [リスト名] # タスク一覧
|
|
271
|
+
node kit/skills/remember/tasks.js add "タスク名" ["リスト名"] [--due YYYY-MM-DD] [--note "メモ"]
|
|
272
|
+
node kit/skills/remember/tasks.js done <taskId> # 完了
|
|
273
|
+
node kit/skills/remember/tasks.js delete <taskId> # 削除
|
|
274
|
+
node kit/skills/remember/tasks.js rename "旧名" "新名" # リスト名変更
|
|
275
|
+
`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const auth = await authorize();
|
|
280
|
+
const service = google.tasks({ version: 'v1', auth });
|
|
281
|
+
|
|
282
|
+
switch (cmd) {
|
|
283
|
+
case 'setup':
|
|
284
|
+
await setup(service);
|
|
285
|
+
break;
|
|
286
|
+
case 'lists':
|
|
287
|
+
await listTaskLists(service);
|
|
288
|
+
break;
|
|
289
|
+
case 'list':
|
|
290
|
+
await listTasks(service, args[1] || null);
|
|
291
|
+
break;
|
|
292
|
+
case 'list-json':
|
|
293
|
+
await listTasksJson(service);
|
|
294
|
+
break;
|
|
295
|
+
case 'add': {
|
|
296
|
+
const title = args[1];
|
|
297
|
+
if (!title) { console.error('❌ タスク名を指定してください'); return; }
|
|
298
|
+
const listName = args[2] && !args[2].startsWith('--') ? args[2] : null;
|
|
299
|
+
const opts = {};
|
|
300
|
+
for (let i = 2; i < args.length; i++) {
|
|
301
|
+
if (args[i] === '--due' && args[i+1]) { opts.due = args[i+1]; i++; }
|
|
302
|
+
if (args[i] === '--note' && args[i+1]) { opts.note = args[i+1]; i++; }
|
|
303
|
+
}
|
|
304
|
+
await addTask(service, title, listName, opts);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
case 'done':
|
|
308
|
+
if (!args[1]) { console.error('❌ タスクIDを指定してください'); return; }
|
|
309
|
+
await completeTask(service, args[1]);
|
|
310
|
+
break;
|
|
311
|
+
case 'delete':
|
|
312
|
+
if (!args[1]) { console.error('❌ タスクIDを指定してください'); return; }
|
|
313
|
+
await deleteTask(service, args[1]);
|
|
314
|
+
break;
|
|
315
|
+
case 'rename':
|
|
316
|
+
if (!args[1] || !args[2]) { console.error('❌ 旧名と新名を指定してください'); return; }
|
|
317
|
+
await renameList(service, args[1], args[2]);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
})();
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slide-deck
|
|
3
|
+
description: セミナー・研修・商談のスライド資料を作る。番頭が中身をMarkdownで書き、パワポ(pptx)への変換は Claude のチャットに渡す。「資料作って」「スライド」「セミナーの資料」「研修の資料」と言われたとき。
|
|
4
|
+
layer: L3
|
|
5
|
+
depends_on: [compliance]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# スライド資料を作る(slide-deck)
|
|
9
|
+
|
|
10
|
+
> **分担がはっきりしています。**
|
|
11
|
+
> 番頭=中身を考えて Markdown で書く。Claudeのチャット=それをパワポの形にする。
|
|
12
|
+
> 番頭がパワポのファイルを直接作ろうとしない(作れても崩れます)。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 手順
|
|
17
|
+
|
|
18
|
+
1. **誰に何を話すのかを1行で確認する**(ここを飛ばさない)
|
|
19
|
+
2. 構成を先に出して、本人に見せる(本文を書く前に)
|
|
20
|
+
3. OKが出たら本文を書く(`資料_<テーマ>_<日付>.md`)
|
|
21
|
+
4. ★**商売・宣伝の資料なら** `node kit/compliance/lint.js` を通す(→ `kit/compliance/SKILL.md`)
|
|
22
|
+
5. **Claudeに渡す文を添えて**、本人に渡す(下の受け渡し文)
|
|
23
|
+
6. 出来上がったパワポを見て、崩れがないか一緒に確認する
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 最初に確認する4つ
|
|
28
|
+
|
|
29
|
+
| 項目 | 例 |
|
|
30
|
+
|---|---|
|
|
31
|
+
| 誰に | 同僚/お客さん/これから始める人/家族・地域の集まり |
|
|
32
|
+
| 何のために | 知ってもらう/やり方を覚えて帰ってもらう/決めてもらう |
|
|
33
|
+
| 時間 | 30分/60分/90分 |
|
|
34
|
+
| 持ち帰ってほしいこと | ★**1つだけ**。3つ書きたくなったら、それは絞れていない |
|
|
35
|
+
|
|
36
|
+
★**時間からスライドの枚数が決まります**。目安は「**1枚あたり1〜2分**」。
|
|
37
|
+
60分なら30〜40枚。これより多いと、必ず時間内に終わりません。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 中身の作り方
|
|
42
|
+
|
|
43
|
+
### 1枚1メッセージ
|
|
44
|
+
|
|
45
|
+
1枚のスライドで言うことは1つだけ。2つ言いたくなったら、スライドを2枚に割ります。
|
|
46
|
+
|
|
47
|
+
### 型(迷ったらこれ)
|
|
48
|
+
|
|
49
|
+
| 順 | 何を置くか |
|
|
50
|
+
|---|---|
|
|
51
|
+
| 1 | 表紙(テーマ・誰向けか・日付) |
|
|
52
|
+
| 2 | 今日持ち帰ってほしいこと(1つ) |
|
|
53
|
+
| 3 | いまどうなっているか(相手の現実。ここで共感を作る) |
|
|
54
|
+
| 4〜 | 本題(1枚1メッセージ) |
|
|
55
|
+
| 終 | 今日から何をするか(**3歩まで**。多いと誰もやりません) |
|
|
56
|
+
|
|
57
|
+
### 話し方の設計
|
|
58
|
+
|
|
59
|
+
- **数字と具体を先に、説明を後に**。「多くの店で」より「3店中2店で」
|
|
60
|
+
- **相手の言葉で書く**。業界の内輪の言葉を、そのまま資料に載せない
|
|
61
|
+
- 抽象語(「最適化」「構造的」「価値提供」)で1枚埋めない。**何がどうなるのかを書く**
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Markdownの書き方(Claudeがパワポにしやすい形)
|
|
66
|
+
|
|
67
|
+
```markdown
|
|
68
|
+
---
|
|
69
|
+
title: 資料のタイトル
|
|
70
|
+
subtitle: 誰向けか
|
|
71
|
+
date: 2026-08-06
|
|
72
|
+
audience: 誰向けか
|
|
73
|
+
duration: 60分
|
|
74
|
+
takeaway: 今日持ち帰ってほしいこと(1つ)
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
# 1. 大きな区切り(章)
|
|
78
|
+
|
|
79
|
+
## 1-1. スライドの見出し(=1枚)
|
|
80
|
+
|
|
81
|
+
- 箇条書きは1枚に3〜5個まで
|
|
82
|
+
- 1行は短く。読み上げる文章を書かない
|
|
83
|
+
|
|
84
|
+
> 話すこと:ここにスピーカーノートを書く。スライドには出さず、口で言うこと。
|
|
85
|
+
|
|
86
|
+
## 1-2. 次のスライド
|
|
87
|
+
|
|
88
|
+
| 表を使うとき | こう書く |
|
|
89
|
+
|---|---|
|
|
90
|
+
| 比較 | 表が一番伝わる |
|
|
91
|
+
|
|
92
|
+
> 話すこと:表は読み上げない。1行だけ指して話す。
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**決まりごと**
|
|
96
|
+
|
|
97
|
+
- `##` の見出し=**スライド1枚**(これがそのまま分かれ目になります)
|
|
98
|
+
- `#` =章の区切り(章扉になります)
|
|
99
|
+
- `> 話すこと:` =スピーカーノート(スライドには出ません)
|
|
100
|
+
- **画像は入れず、`[画像:何を入れるか]` と書いておく**(画像は後から差し込みます)
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## ★Claudeに渡すときの文(そのままコピーする)
|
|
105
|
+
|
|
106
|
+
資料のMarkdownができたら、この文を添えて渡します。
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
このMarkdownをPowerPoint(.pptx)にしてください。
|
|
110
|
+
|
|
111
|
+
【決まり】
|
|
112
|
+
- ## の見出し1つ=スライド1枚
|
|
113
|
+
- # は章扉
|
|
114
|
+
- 「> 話すこと:」の行はスライド本文に出さず、スピーカーノートに入れる
|
|
115
|
+
- 16:9
|
|
116
|
+
- 文字は本文24pt以上(後ろの席から読めるように)
|
|
117
|
+
- 1枚に文字を詰め込みすぎない。入り切らなければスライドを分ける
|
|
118
|
+
- 装飾は控えめに。アニメーションは付けない
|
|
119
|
+
- 「[画像:〜]」と書いてあるところは、空の枠を置いてメモを残す
|
|
120
|
+
|
|
121
|
+
【やらないでほしいこと】
|
|
122
|
+
- 中身の文言を勝手に書き換えない(確認を通した文なので、言い回しを変えないでください)
|
|
123
|
+
- 数字を足さない・丸めない
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
★**最後の2行が重要です。** 確認して通した文言が、パワポにする過程で書き換わると、
|
|
127
|
+
**確かめたはずのものが別物になります**。
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 出す前のチェック
|
|
132
|
+
|
|
133
|
+
- [ ] 持ち帰ってほしいことは**1つ**か
|
|
134
|
+
- [ ] 1枚1メッセージになっているか
|
|
135
|
+
- [ ] 枚数は時間に合っているか(1枚1〜2分)
|
|
136
|
+
- [ ] ★商売・宣伝の資料なら、関所を通したか(`kit/compliance/`)
|
|
137
|
+
- [ ] 数字に根拠があるか。無いものは `■要確認` になっているか
|
|
138
|
+
- [ ] 他社の実名を出して下げていないか
|
|
139
|
+
- [ ] 最後の「今日から何をするか」は3歩以内か
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## パワポが出来上がったら
|
|
144
|
+
|
|
145
|
+
**必ず開いて見ます。** 見るところ:
|
|
146
|
+
|
|
147
|
+
- 文字が枠からはみ出していないか
|
|
148
|
+
- 日本語が変なところで改行されていないか(カタカナの途中で切れていないか)
|
|
149
|
+
- スピーカーノートが本文に混ざっていないか
|
|
150
|
+
- **文言が勝手に書き換わっていないか**(★ここを一番に見る)
|
|
151
|
+
|
|
152
|
+
崩れていたら、そのスライドだけ作り直します。全部やり直さない。
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## やってはいけないこと
|
|
157
|
+
|
|
158
|
+
- ❌ 番頭がパワポのファイルを直接作ろうとする(崩れます。Markdownまでが持ち場)
|
|
159
|
+
- ❌ 構成を見せずに全部書き上げる(方向が違ったら全部無駄になります)
|
|
160
|
+
- ❌ 出来上がったパワポを開かずに「できました」と言う
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# ローカル文字起こしキット(Whisper+辞書+話者識別)
|
|
2
|
+
|
|
3
|
+
Zoom録画などの音声・動画ファイルを、**PCの中だけで**文字起こしするための道具一式です。
|
|
4
|
+
外部サービスに音声を送らないので、機密性の高い会議でも安心して使えます。
|
|
5
|
+
|
|
6
|
+
3つの道具が入っています。
|
|
7
|
+
|
|
8
|
+
| ファイル | 役割 |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `transcribe.py` | 音声・動画 → タイムスタンプ付きテキスト(Whisperローカル版) |
|
|
11
|
+
| `dict.json` | 誤変換の補正辞書。使うほど育つ |
|
|
12
|
+
| `enroll_speaker.py` / `identify_speakers.py` | 話者識別。「誰が話したか」を声紋で判定 |
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## ★Windows で使うとき(初回のみ)
|
|
17
|
+
|
|
18
|
+
このREADMEの下の方は Mac 向けに書かれています。Windows では次の順で入れます。
|
|
19
|
+
|
|
20
|
+
```powershell
|
|
21
|
+
# 1. Python と ffmpeg を入れる(→ 必要なプログラム.md の 5・6)
|
|
22
|
+
# Python は「Add python.exe to PATH」に必ずチェック
|
|
23
|
+
|
|
24
|
+
# 2. 専用の環境を作る(キットのフォルダで。1回だけ)
|
|
25
|
+
python -m venv .venv
|
|
26
|
+
|
|
27
|
+
# 3. その環境に入る(PCを再起動したら毎回)
|
|
28
|
+
.venv\Scripts\activate
|
|
29
|
+
|
|
30
|
+
# 4. 部品を入れる
|
|
31
|
+
pip install faster-whisper
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
★話者識別(誰が話したか)を使うときだけ、追加で `pip install resemblyzer` を入れます。
|
|
35
|
+
うまく入らないときは `pip install webrtcvad-wheels` を先に実行してください。
|
|
36
|
+
|
|
37
|
+
★**手順が多い部分です。** 詰まったら、画面のメッセージをそのまま番頭に見せてください。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## セットアップ(Mac・初回のみ)
|
|
42
|
+
|
|
43
|
+
このREADMEごとAIに渡して「セットアップして」と伝えるのが一番早いです。
|
|
44
|
+
手動でやる場合は以下の3ステップです。
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 1. ffmpeg(動画・音声の変換に使う。導入済みならスキップ)
|
|
48
|
+
brew install ffmpeg
|
|
49
|
+
|
|
50
|
+
# 2. Python仮想環境(このフォルダの中に作る)
|
|
51
|
+
cd transcribe-kit
|
|
52
|
+
python3 -m venv venv
|
|
53
|
+
source venv/bin/activate
|
|
54
|
+
|
|
55
|
+
# 3. ライブラリ
|
|
56
|
+
pip install faster-whisper resemblyzer
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
※ `resemblyzer` の依存 `webrtcvad` のインストールに失敗したら
|
|
60
|
+
`pip install webrtcvad-wheels` を先に実行してから再度試してください。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 1. 文字起こし
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python transcribe.py 会議録画.mp4
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- 初回だけ認識モデル(約500MB)を自動ダウンロードします
|
|
71
|
+
- 出力は入力と同じ場所に2つ:
|
|
72
|
+
- `会議録画.txt` … タイムスタンプ付き文字起こし(人が読む用)
|
|
73
|
+
- `会議録画.json` … 詳細データ(話者識別の入力になる)
|
|
74
|
+
|
|
75
|
+
精度を上げたいときはモデルを指定します(大きいほど正確・遅い):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python transcribe.py 会議録画.mp4 --model medium
|
|
79
|
+
python transcribe.py 会議録画.mp4 --model large-v3
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
目安:60分の会議で small=10〜20分、medium=30分前後(Apple Silicon想定)。
|
|
83
|
+
まずは small で試して、固有名詞の誤りが気になったら medium に上げるのがおすすめです。
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 2. 辞書の育て方(ここが一番効きます)
|
|
88
|
+
|
|
89
|
+
Whisperは会社名・商品名・業界用語をよく間違えます。
|
|
90
|
+
間違いに気づいたら `dict.json` の `entries` に1行足すだけで、**次回から自動で直ります**。
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"entries": {
|
|
95
|
+
"誤変換された表記": "正しい表記",
|
|
96
|
+
"クロード": "Claude"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
運用のコツ:
|
|
102
|
+
|
|
103
|
+
- 文字起こしが終わったらAIに「この文字起こしを校正して。繰り返し出てくる誤変換があれば dict.json に追記して」と頼むと、辞書が勝手に育ちます
|
|
104
|
+
- **固有名詞・専門用語を中心に**登録してください。短い一般語(「はい」「時」など)を登録すると、正しい文章まで置換される事故が起きます
|
|
105
|
+
- 置換は長い表記から先に適用されるので、似た誤変換は両方登録してOKです
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 3. 話者識別(誰が話したか)
|
|
110
|
+
|
|
111
|
+
Whisper単体は「何を話したか」しか分かりません。
|
|
112
|
+
そこで**声紋(声の特徴を数値化したもの)**を使って「誰が話したか」を判定します。
|
|
113
|
+
|
|
114
|
+
### 仕組み
|
|
115
|
+
|
|
116
|
+
1. 各参加者の「その人だけが話している音声」から声紋を登録する(初回のみ)
|
|
117
|
+
2. 会議の発話ひとつひとつを声紋と照合し、一番似ている人の名前を付ける
|
|
118
|
+
|
|
119
|
+
### 手順1:声紋の登録(人ごとに初回のみ)
|
|
120
|
+
|
|
121
|
+
その人だけが話している10秒〜1分程度の音声を用意します。
|
|
122
|
+
過去のZoom録画から切り出すのが手軽です:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# 例:録画の5分10秒から60秒間を切り出す
|
|
126
|
+
ffmpeg -i 過去の会議.mp4 -ss 00:05:10 -t 60 sample_yamada.m4a
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
切り出した音声で登録:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
python enroll_speaker.py 山田 sample_yamada.m4a
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
→ `voiceprints/山田_voiceprint.npy` が作られます。参加者の人数分やってください。
|
|
136
|
+
一度登録すれば、以降の会議で使い回せます。
|
|
137
|
+
|
|
138
|
+
### 手順2:識別の実行
|
|
139
|
+
|
|
140
|
+
文字起こし(手順1の `.json`)ができている状態で:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
python identify_speakers.py 会議録画.mp4 会議録画.json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
→ `会議録画_speakers.txt` が出ます。こんな形式です:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
山田 0:12
|
|
150
|
+
おはようございます。今日はよろしくお願いします。
|
|
151
|
+
|
|
152
|
+
参加者A 0:45
|
|
153
|
+
こちらこそ。早速ですが先週の件から…
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- 声紋との類似度が低い発話は「不明」になります(登録していない人・音質が悪い区間)
|
|
157
|
+
- 「不明」が多いときは、その人の声紋をもっと長い音源で登録し直すと改善します
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## よくあるつまずき
|
|
162
|
+
|
|
163
|
+
| 症状 | 対処 |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `ffmpeg: command not found` | `brew install ffmpeg` |
|
|
166
|
+
| webrtcvadのビルドエラー | `pip install webrtcvad-wheels` |
|
|
167
|
+
| 文字起こしが遅い | まず `--model small`(既定)で。会議が長い場合は分割も有効 |
|
|
168
|
+
| 話者識別で「不明」が多い | 声紋登録の音源を長く・クリアなものに差し替えて再登録 |
|
|
169
|
+
| 同じ人が別人判定される | マイク・環境が大きく違う録音は声紋がズレます。実際の会議と近い環境の音源で登録 |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## この文字起こしの使い道(商談・社内面談)
|
|
174
|
+
|
|
175
|
+
タイムスタンプ+話者名付きのテキストは、そのまま
|
|
176
|
+
**関係性管理スキル(relationship-manager)の入力**として使えます。
|
|
177
|
+
|
|
178
|
+
- **商談**:終わった直後に「この文字起こしを読んで、○○社の担当者のプロファイルを更新して。次回の商談で押さえるべき点も整理して」とAIに渡す
|
|
179
|
+
- **社内面談**:「この面談の文字起こしから、本人の関心事と気がかりを整理して」と渡せば、1on1の記録と次回準備が一度にできます
|
|
180
|
+
|
|
181
|
+
商談相手・社員それぞれの声紋を一度登録しておけば、
|
|
182
|
+
以降は「録画を置いて2コマンド」で話者名付きの記録が積み上がっていきます。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_readme": "Whisperの誤変換補正辞書。entries に「誤変換された表記: 正しい表記」を追記すると、次回の文字起こしから自動で直ります。固有名詞(会社名・商品名・人名・業界用語)を中心に登録してください。短い一般語の登録は誤爆のもとになるので避けてください。",
|
|
3
|
+
"entries": {
|
|
4
|
+
"クロード": "Claude",
|
|
5
|
+
"ジェミニ": "Gemini",
|
|
6
|
+
"チャットGPT": "ChatGPT",
|
|
7
|
+
"ノーションLM": "NotebookLM",
|
|
8
|
+
"ノートブックLM": "NotebookLM"
|
|
9
|
+
}
|
|
10
|
+
}
|