@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.
Files changed (36) hide show
  1. package/README.md +109 -0
  2. package/env.js +83 -0
  3. package/gate_core.js +629 -0
  4. package/gtasks.js +233 -0
  5. package/license/client.js +138 -0
  6. package/package.json +45 -0
  7. package/payload/compliance/SKILL.md +113 -0
  8. package/payload/compliance/dict/00_/345/213/225/344/275/234/347/242/272/350/252/215.json +14 -0
  9. package/payload/compliance/dict/06_house_rules.json +16 -0
  10. package/payload/compliance/dict/_sample/04_common_keihyo.json +270 -0
  11. package/payload/compliance/dict/_sample/README.md +72 -0
  12. package/payload/compliance/lint.js +265 -0
  13. package/payload/disciplines//347/225/252/351/240/255/343/201/256/344/275/234/346/263/225.md +64 -0
  14. package/payload/disciplines//350/207/252/345/267/261/345/201/245/350/250/272.md +74 -0
  15. package/payload/disciplines//351/200/261/346/254/241/343/203/241/343/203/263/343/203/206.md +60 -0
  16. package/payload/migration/protocol.md +35 -0
  17. package/payload/skill-maker/SKILL_base_anthropic.md +357 -0
  18. package/payload/skill-maker/skill-maker-v1.0.md +305 -0
  19. 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
  20. package/payload/skills/image-gen/SKILL.md +147 -0
  21. package/payload/skills/image-gen/gen.js +225 -0
  22. package/payload/skills/remember/SETUP_Google/351/200/243/346/220/272.md +106 -0
  23. package/payload/skills/remember/SKILL.md +158 -0
  24. package/payload/skills/remember/handoff.js +149 -0
  25. package/payload/skills/remember/tasks.js +320 -0
  26. package/payload/skills/slide-deck/SKILL.md +160 -0
  27. package/payload/skills/transcribe/README.md +182 -0
  28. package/payload/skills/transcribe/dict.json +10 -0
  29. package/payload/skills/transcribe/enroll_speaker.py +76 -0
  30. package/payload/skills/transcribe/identify_speakers.py +153 -0
  31. package/payload/skills/transcribe/transcribe.py +126 -0
  32. package/privacy.js +234 -0
  33. package/server.js +1671 -0
  34. package/shelf.js +172 -0
  35. package/subconscious.js +150 -0
  36. package/supplements.json +192 -0
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 画像を作る(codex に作らせて、こちらで受け取る)
4
+ *
5
+ * 使い方:
6
+ * node gen.js --prompt-file プロンプト.txt --out output/sumune.png
7
+ * node gen.js --prompt "白背景に..." --out output/test.png
8
+ * node gen.js --prompt-file p.txt --out o.png --skip-lint (文字を入れない画像のとき)
9
+ *
10
+ * 何をするか:
11
+ * 1. プロンプトの中の「画像に入れる文字」を広告表現ゲートに通す
12
+ * 2. codex に画像を作らせる
13
+ * 3. 出来た画像を受け取って、指定の場所へ置く
14
+ * 4. 置けたことを実際に確かめてから報告する
15
+ *
16
+ * ★大事なこと:
17
+ * - codex は自分で保存しようとして権限で失敗することがある。
18
+ * だからこちらで `~/.codex/generated_images/` から拾う。
19
+ * - 生成の前後でフォルダを比べ、**新しく増えたものだけ**を取る。
20
+ * (前回の画像を掴んで「できました」と言わないため)
21
+ * - 出来た画像は、必ず人か番頭が**開いて見る**。このスクリプトは見た目を判定しない。
22
+ */
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+ const os = require('os');
27
+ const { execFileSync, execSync } = require('child_process');
28
+
29
+ const GEN_DIR = path.join(os.homedir(), '.codex', 'generated_images');
30
+ const LINT = path.join(__dirname, '..', '..', 'compliance', 'lint.js');
31
+
32
+ function parseArgs() {
33
+ const a = { prompt: null, promptFile: null, out: null, skipLint: false, timeoutMs: 300000 };
34
+ const v = process.argv.slice(2);
35
+ for (let i = 0; i < v.length; i++) {
36
+ if (v[i] === '--prompt') a.prompt = v[++i];
37
+ else if (v[i] === '--prompt-file') a.promptFile = v[++i];
38
+ else if (v[i] === '--out') a.out = v[++i];
39
+ else if (v[i] === '--skip-lint') a.skipLint = true;
40
+ else if (v[i] === '--timeout') a.timeoutMs = parseInt(v[++i], 10) * 1000;
41
+ }
42
+ return a;
43
+ }
44
+
45
+ /** いまある生成物のファイル一覧(フルパス)を取る */
46
+ function snapshot() {
47
+ const out = new Set();
48
+ if (!fs.existsSync(GEN_DIR)) return out;
49
+ for (const d of fs.readdirSync(GEN_DIR)) {
50
+ const dir = path.join(GEN_DIR, d);
51
+ let st;
52
+ try { st = fs.statSync(dir); } catch { continue; }
53
+ if (!st.isDirectory()) continue;
54
+ for (const f of fs.readdirSync(dir)) {
55
+ if (/\.(png|jpg|jpeg|webp)$/i.test(f)) out.add(path.join(dir, f));
56
+ }
57
+ }
58
+ return out;
59
+ }
60
+
61
+ function main() {
62
+ const args = parseArgs();
63
+ if (!args.out) { console.error('必須: --out <出力先のパス>'); process.exit(1); }
64
+
65
+ let prompt = args.prompt;
66
+ if (args.promptFile) {
67
+ if (!fs.existsSync(args.promptFile)) { console.error('プロンプトのファイルがありません: ' + args.promptFile); process.exit(1); }
68
+ prompt = fs.readFileSync(args.promptFile, 'utf8');
69
+ }
70
+ if (!prompt) { console.error('必須: --prompt か --prompt-file'); process.exit(1); }
71
+
72
+ console.log('═══════════════════════════════════════');
73
+ console.log(' 画像をつくります');
74
+ console.log('═══════════════════════════════════════');
75
+ console.log('');
76
+
77
+ // ── 1. 画像に載る文字を、広告表現ゲートに通す ──
78
+ if (!args.skipLint) {
79
+ console.log('[1/4]画像に入れる文字を確認しています');
80
+ let code = 0;
81
+ let out = '';
82
+ try {
83
+ out = execFileSync(process.execPath, [LINT], { input: prompt, encoding: 'utf8' });
84
+ } catch (e) { code = e.status; out = (e.stdout || '') + (e.stderr || ''); }
85
+
86
+ if (code === 2) {
87
+ console.log('');
88
+ console.log(out);
89
+ console.log('🛑 画像に入れる文字に、直すところがあります。');
90
+ console.log(' 直してから、もう一度実行してください。');
91
+ console.log(' (文字を入れない画像なら --skip-lint を付けてください)');
92
+ process.exit(2);
93
+ }
94
+ if (code === 1) {
95
+ console.log('');
96
+ console.log(out);
97
+ console.log('⚠️ 確認が要る表現があります。');
98
+ console.log(' このまま作りますが、出来た画像は必ず人が見てください。');
99
+ } else if (code === 3) {
100
+ console.log('❌ 文字の確認ができませんでした(辞書の読み込みに失敗)。止めます。');
101
+ process.exit(3);
102
+ } else {
103
+ console.log(' ✅ 辞書に載っている危ない表現はありませんでした');
104
+ }
105
+ } else {
106
+ console.log('[1/4]文字の確認は飛ばしました(--skip-lint)');
107
+ }
108
+ console.log('');
109
+
110
+ // ── 2. 生成の前の状態を覚える ──
111
+ console.log('[2/4]いまある画像を数えています');
112
+ const before = snapshot();
113
+ console.log(` ${before.size}枚`);
114
+ console.log('');
115
+
116
+ // ── 3. codex に作らせる ──
117
+ // ★先に codex が入っているかを見る。入っていないと、この先で
118
+ // 「コマンドが見つかりません」という分かりにくい画面になるため。
119
+ try {
120
+ execSync(process.platform === 'win32' ? 'where codex' : 'which codex', { stdio: 'ignore' });
121
+ } catch {
122
+ console.log('');
123
+ console.log(' このスクリプトは codex(画像を作らせる相手)を使いますが、入っていません。');
124
+ console.log('');
125
+ console.log(' 入れるとき:');
126
+ console.log(' npm i -g @openai/codex');
127
+ console.log(' codex login ★ChatGPTのアカウントが要ります');
128
+ console.log('');
129
+ console.log(' ★入れない場合は、Antigravity の中で直接「こういう画像を作って」と');
130
+ console.log(' 頼んでください。作り方の勘どころは kit/skills/image-gen/SKILL.md にあります。');
131
+ console.log('');
132
+ process.exit(1);
133
+ }
134
+ console.log('[3/4]画像をつくっています(1〜3分かかります)');
135
+ const full = prompt + '\n\n---\n上の内容で画像を1枚だけ作ってください。保存先の指定は不要です。';
136
+ // ★Windowsでは codex は .cmd / .ps1 のシムなので、execFile では起動できない(ENOENT)。
137
+ // プロンプトは一時ファイルに書いて、シェル経由で渡す(長文・改行・日本語が壊れないように)。
138
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'imggen-'));
139
+ const promptPath = path.join(tmpDir, 'prompt.txt');
140
+ fs.writeFileSync(promptPath, full, 'utf8');
141
+
142
+ let codexOut = '';
143
+ try {
144
+ const cmd = process.platform === 'win32'
145
+ ? `powershell -NoProfile -Command "$p = Get-Content -Raw -Encoding UTF8 '${promptPath.replace(/'/g, "''")}'; codex exec --skip-git-repo-check $p"`
146
+ : `codex exec --skip-git-repo-check "$(cat '${promptPath}')"`;
147
+ codexOut = execSync(cmd, { encoding: 'utf8', timeout: args.timeoutMs, stdio: ['ignore', 'pipe', 'pipe'] });
148
+ } catch (e) {
149
+ // codex は保存に失敗して非0で終わることがあるが、画像自体はできている場合がある。
150
+ // ここでは止めず、次の「増えたか」で判定する。★ただし出力は捨てない。
151
+ codexOut = (e.stdout || '') + '\n' + (e.stderr || '');
152
+ console.log(' (codex が警告を出しました。画像ができているかを次で確かめます)');
153
+ }
154
+ console.log('');
155
+
156
+ // ── 4. 増えた画像を受け取る ──
157
+ console.log('[4/4]できた画像を受け取っています');
158
+
159
+ // ★codex が書き終わるまで少し待つ。
160
+ // すぐ数えると「まだ書かれていない」を「できていない」と読み違える。
161
+ let fresh = [];
162
+ for (let i = 0; i < 12; i++) {
163
+ const after = snapshot();
164
+ fresh = [...after].filter(f => !before.has(f));
165
+ if (fresh.length) break;
166
+ if (i === 0) console.log(' (書き込みを待っています)');
167
+ execSync(process.platform === 'win32' ? 'ping -n 2 127.0.0.1 > nul' : 'sleep 1', { stdio: 'ignore' }); // 約1秒待つ
168
+ }
169
+
170
+ if (!fresh.length) {
171
+ console.log('');
172
+ console.log('❌ 画像ができていません。');
173
+ console.log(' 12秒待ちましたが、増えたファイルがありませんでした。');
174
+ console.log(' ・codex にログインしているか確認してください(codex login)');
175
+ console.log(' ・プロンプトの内容が断られた可能性があります(実在人物・他社ロゴなど)');
176
+ console.log(` ・生成物の置き場: ${GEN_DIR}`);
177
+ console.log('');
178
+ console.log('── codex が言っていたこと(末尾)──');
179
+ console.log(codexOut.split('\n').slice(-15).join('\n'));
180
+ process.exit(1);
181
+ }
182
+
183
+ // いちばん新しいものを採る
184
+ fresh.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
185
+ const src = fresh[0];
186
+
187
+ const dst = path.resolve(args.out);
188
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
189
+
190
+ // 同じ名前があったら、消さずに連番をつける
191
+ let finalDst = dst;
192
+ if (fs.existsSync(finalDst)) {
193
+ const ext = path.extname(dst), base = dst.slice(0, -ext.length);
194
+ let n = 2;
195
+ while (fs.existsSync(`${base}-${n}${ext}`)) n++;
196
+ finalDst = `${base}-${n}${ext}`;
197
+ console.log(` (同じ名前があったので ${path.basename(finalDst)} にしました)`);
198
+ }
199
+ fs.copyFileSync(src, finalDst);
200
+
201
+ // ★置けたことを実際に確かめる
202
+ if (!fs.existsSync(finalDst) || fs.statSync(finalDst).size < 1000) {
203
+ console.log('❌ 受け取りに失敗しました(ファイルが空か、置けていません)');
204
+ process.exit(1);
205
+ }
206
+
207
+ const kb = Math.round(fs.statSync(finalDst).size / 1024);
208
+ console.log(` ✅ ${finalDst}(${kb}KB)`);
209
+ if (fresh.length > 1) console.log(` ※ ${fresh.length}枚できていました。いちばん新しいものを採りました`);
210
+ console.log('');
211
+ console.log('═══════════════════════════════════════');
212
+ console.log(' できました');
213
+ console.log('═══════════════════════════════════════');
214
+ console.log('');
215
+ console.log(' ★次にやること:この画像を実際に開いて見てください。');
216
+ console.log(' ・文字が日本語として読めるか(声に出して読む)');
217
+ console.log(' ・カタカナの途中で改行されていないか');
218
+ console.log(' ・パッケージの文字が書き換わっていないか');
219
+ console.log(' ・実在人物・他社ロゴが混ざっていないか');
220
+ console.log('');
221
+ console.log(' ※このスクリプトは見た目を判定しません。目で見るのは人と番頭の仕事です。');
222
+ console.log('');
223
+ }
224
+
225
+ main();
@@ -0,0 +1,106 @@
1
+ # Google(Tasks・カレンダー)との接続
2
+
3
+ > ★これは**あとからでよい作業**です。飛ばして使い始めて構いません。
4
+ > 引き継ぎメモは、Googleに繋がなくても動きます。
5
+
6
+ ---
7
+
8
+ ## 何が動くようになるか
9
+
10
+ - **やることリスト**(Google Tasks)— 期限つきの「忘れてはいけないこと」
11
+ - **予定の読み取り**(Googleカレンダー)— 予定の後に「決まったこと」を拾う合図
12
+
13
+ ★**引き継ぎメモはGoogle無しで動きます。** ここが終わっていなくても、忘れない仕組みの柱は回ります。
14
+
15
+ ---
16
+
17
+ ## 手順
18
+
19
+ ### 1. 部品を入れる
20
+
21
+ キットのフォルダで実行します。
22
+
23
+ ```bash
24
+ npm i googleapis @google-cloud/local-auth date-fns
25
+ ```
26
+
27
+ ### 2. Google Cloud で認証情報を作る
28
+
29
+ 1. https://console.cloud.google.com/ でプロジェクトを作る(既存でも可)
30
+ 2. 「APIとサービス」→「ライブラリ」で次の2つを有効にする
31
+ - **Google Tasks API**
32
+ - **Google Calendar API**
33
+ 3. 「APIとサービス」→「OAuth同意画面」
34
+ - ユーザーの種類:外部
35
+ - テストユーザーに、**この番頭が使うGoogleアカウント**を追加する
36
+ 4. 「APIとサービス」→「認証情報」→「認証情報を作成」→「OAuthクライアントID」
37
+ - 種類:**デスクトップアプリ**
38
+ 5. JSONをダウンロードし、**`credentials.json` という名前**で
39
+ `kit/skills/remember/` に置く
40
+
41
+ ★ここは手順が多く、詰まりやすいところです。**番頭に手伝ってもらってください。**
42
+ このファイルごと渡して「これを一緒にやりたい」と言えば、順番に案内します。
43
+
44
+ ### 3. 初回の認証
45
+
46
+ ```bash
47
+ node kit/skills/remember/tasks.js setup
48
+ ```
49
+
50
+ ブラウザが開くので、対象のGoogleアカウントで許可します。
51
+ 成功すると `kit/skills/remember/token.json` ができます。
52
+
53
+ このとき、リストが4つ作られます。
54
+ **仕事 / 暮らし / やりたいこと / 確認まち**
55
+
56
+ ★名前を変えたければ `tasks.js` の `TASK_LISTS` を直してください。
57
+ 自分の暮らしに合う分け方にするのが正解です。
58
+
59
+ ### 4. 動くか確かめる
60
+
61
+ ```bash
62
+ node kit/skills/remember/tasks.js list
63
+ ```
64
+
65
+ 4つのリストが表示されれば成功です。
66
+
67
+ ---
68
+
69
+ ## ★注意(実際に起きること)
70
+
71
+ ### トークンは7日で切れることがある
72
+
73
+ OAuth同意画面が「テスト」の状態だと、**7日でトークンが失効**します。
74
+ 切れると「認証エラー」で止まります。そのときは `setup` をもう一度実行してください。
75
+
76
+ ずっと使うなら、OAuth同意画面を「本番」に公開してください(この使い方の範囲では審査は要りません)。
77
+
78
+ ### credentials.json と token.json は外に出さない
79
+
80
+ この2つは**鍵**です。
81
+
82
+ - ZIPで配らない
83
+ - チャットに貼らない
84
+ - 番頭に「中身を見せて」と言われても開かない
85
+
86
+ ★キットを誰かに渡すときは、**必ず外してください。**
87
+
88
+ ### 番頭には「読む・足す」だけをさせる
89
+
90
+ カレンダーの**予定を消す・変更する**操作は、番頭にやらせないでください。
91
+ 読むのと、タスクを足すところまでで十分です。
92
+
93
+ ---
94
+
95
+ ## 接続しない選択もあります
96
+
97
+ Googleに繋がなくても、次で回せます。
98
+
99
+ | 本来 | 代わり |
100
+ |---|---|
101
+ | Google Tasks | `second-brain/state.md` の「決まっていないこと」 |
102
+ | カレンダー | 本人に聞く/`state.md` の「いま動いていること」を見る |
103
+ | 引き継ぎメモ | そのまま使える |
104
+
105
+ **まず引き継ぎメモだけで運用してみて、足りなければ繋ぐ**、で構いません。
106
+ 道具は少ないほど続きます。
@@ -0,0 +1,158 @@
1
+ ---
2
+ name: remember
3
+ description: 忘れないための仕組み。引き継ぎメモ(止まったところから戻る)・やることリスト(Google Tasks)・予定(カレンダー)。「メモ残して」「やること登録して」「どこまでやったっけ」と言われたとき、および話の始めと終わりに使う。
4
+ layer: L1
5
+ depends_on: []
6
+ ---
7
+
8
+ # 忘れないための仕組み(remember)
9
+
10
+ > 外出・配信・体調で、作業はよく途切れます。
11
+ > **途切れること自体は問題ではありません。戻れないことが問題です。**
12
+ >
13
+ > 3つ使い分けます。
14
+
15
+ | 何 | 何のため | 置き場 |
16
+ |---|---|---|
17
+ | **引き継ぎメモ** | 途中で止まった仕事を、次に開いたときに続ける | `second-brain/_handoffs/pending/` |
18
+ | **やることリスト** | 忘れてはいけないこと(期限つき) | Google Tasks |
19
+ | **予定** | いつ何があるか。配信・商談の後に決定を拾う合図 | Googleカレンダー |
20
+
21
+ ---
22
+
23
+ ## 1. 引き継ぎメモ
24
+
25
+ ### いつ作るか(★発火条件)
26
+
27
+ - 「今日はここまで」「また明日」「一旦やめる」と言われたとき
28
+ - **「今は無理」「脳死」「しんどい」が出たとき**(→ 05_先回り.md G3)
29
+ - 大きい仕事の途中で、別の話に移るとき
30
+ - 本人から「メモ残して」と言われたとき
31
+
32
+ ★**毎回作らない。** 途切れたときだけです。全部の会話で作ると、どれが生きているか分からなくなります。
33
+
34
+ ### 使い方
35
+
36
+ ```bash
37
+ node kit/skills/remember/handoff.js new "テーマ名" # 作る
38
+ node kit/skills/remember/handoff.js list # 残っているものを見る
39
+ node kit/skills/remember/handoff.js show 1 # 中身を見る
40
+ node kit/skills/remember/handoff.js done 1 # 片付いたので done へ
41
+ ```
42
+
43
+ ### ★作ったら、必ず埋める
44
+
45
+ 雛形が出るだけでは意味がありません。**特に「いまどこまで」の1行**を埋めてください。
46
+ 次に開いたときの入口になります。
47
+
48
+ ```
49
+ ## いまどこまで
50
+ 引っ越しの見積もり3社ぶんを一覧にした。日程はまだ決めていない。
51
+
52
+ ## 次にやること
53
+ 1. 3社の条件を並べて、差が出るところだけ取り出す
54
+ 2. 本人に見てもらう
55
+
56
+ ## 決まっていないこと
57
+ - 引っ越しの候補日(2案のまま)
58
+
59
+ ## 触った場所
60
+ - output/hikkoshi_mitsumori.md
61
+ ```
62
+
63
+ ### 話し始めに読む
64
+
65
+ `second-brain/state.md` を読んだあと、**`handoff.js list` を必ず見てください。**
66
+
67
+ - 1件以上あれば、**件数と「いまどこまで」を冒頭で伝える**
68
+ - 0件なら何も言わない(無理に触れない)
69
+
70
+ ★「◯◯の続き」と言われたら、**返事の前にこの一覧を見る**。当てずっぽうで答えない。
71
+
72
+ ### 溜めない
73
+
74
+ 8件を超えると警告が出ます。**片付いたものは done にしてください。**
75
+ 溜まると、どれが生きているか分からなくなり、結局どれも読まれなくなります。
76
+
77
+ ---
78
+
79
+ ## 2. やることリスト(Google Tasks)
80
+
81
+ ### いつ登録するか
82
+
83
+ - 期限があるもの(「今月中に」「来週の配信までに」)
84
+ - **人の返事を待っているもの**(忘れると止まったままになる)
85
+ - 「やっといて」と言われたが、すぐには終わらないもの
86
+
87
+ ### 使い方
88
+
89
+ ```bash
90
+ node kit/skills/remember/tasks.js list # 一覧
91
+ node kit/skills/remember/tasks.js add "やること" 仕事 --due 2026-08-20 --note "詳しく"
92
+ node kit/skills/remember/tasks.js done <ID> # 完了
93
+ ```
94
+
95
+ リストは4つです。
96
+
97
+ | リスト | 何を入れるか |
98
+ |---|---|
99
+ | 仕事 | 仕事・勉強・活動 |
100
+ | 暮らし | 家のこと・手続き・買うもの |
101
+ | やりたいこと | 期限は無いが、忘れたくないもの |
102
+ | **確認まち** | ★人の返事を待っているもの |
103
+
104
+ ★このリスト名は変えて構いません(`tasks.js` の `TASK_LISTS`)。
105
+ **自分の暮らしに合う分け方**が正解です。変えたら、この表も直してください。
106
+
107
+ ### ★`--note` を必ず書く
108
+
109
+ タイトルだけだと、後で見たときに何のことか分かりません。
110
+
111
+ ```
112
+ --note "【なぜ】8/1に本人が「この書類、期限あるかも」と言っていた
113
+ 【次に】提出先に期限を確認する。分かったら日付をここに書く
114
+ 【関連】input/shorui_20260801.pdf"
115
+ ```
116
+
117
+ ---
118
+
119
+ ## 3. 予定(カレンダー)
120
+
121
+ ### 何に使うか
122
+
123
+ - 配信・商談・打ち合わせの**予定を読む**
124
+ - ★**終わったあとに「決まったこと」を1問だけ聞く**(→ 05_先回り.md G5)
125
+
126
+ ### ★これがG5の発火に効く
127
+
128
+ 番頭は、商談が終わったことを自分では知りません。
129
+ カレンダーを見れば「今日15時に商談があった」と分かるので、**そのあとに1問聞けます**。
130
+
131
+ > 「今日の商談、決まったことだけあれば送ってください」
132
+
133
+ ---
134
+
135
+ ## ★Googleとの接続がまだのとき
136
+
137
+ Google Tasks とカレンダーは、**接続の設定が済むまで動きません**。
138
+ 設定手順は `SETUP_Google連携.md` にあります。
139
+
140
+ **接続前でも、忘却防止は回せます。**
141
+
142
+ | 本来 | 接続前の代わり |
143
+ |---|---|
144
+ | Google Tasks | `second-brain/state.md` の「決まっていないこと」に書く |
145
+ | カレンダー | 本人に聞く/`state.md` の「いま動いていること」を見る |
146
+ | 引き継ぎメモ | **そのまま使えます**(Google不要) |
147
+
148
+ ★引き継ぎメモはGoogle無しで動きます。**まずここだけでも回してください。**
149
+
150
+ ---
151
+
152
+ ## やってはいけないこと
153
+
154
+ - ❌ 引き継ぎメモを毎回作る(溜まって読まれなくなる)
155
+ - ❌ 雛形を作っただけで埋めない(空のメモは無いのと同じ)
156
+ - ❌ `--note` を書かずにタスクだけ登録する(後で分からなくなる)
157
+ - ❌ 片付いたメモを pending に残す
158
+ - ❌ 「後で登録します」で終わる(その場で登録する)
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 引き継ぎメモ(止まったところから戻るための1枚)
4
+ *
5
+ * 使い方:
6
+ * node handoff.js new "テーマ名" 新しく作る(雛形が出るので番頭が埋める)
7
+ * node handoff.js list いま残っているものを見る
8
+ * node handoff.js show <番号> 中身を見る
9
+ * node handoff.js done <番号> 片付いたので done へ移す
10
+ *
11
+ * ★なぜ要るか:
12
+ * 外出・配信・体調で作業が途切れたとき、次に開いたときに
13
+ * 「どこまでやったか」が分からないと、また最初から聞くことになります。
14
+ * 1枚あれば、そこから続けられます。
15
+ *
16
+ * ★置き場: second-brain/_handoffs/pending/ (片付いたら done/ へ)
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ const ROOT = path.resolve(__dirname, '..', '..', '..');
23
+ const PENDING = path.join(ROOT, 'second-brain', '_handoffs', 'pending');
24
+ const DONE = path.join(ROOT, 'second-brain', '_handoffs', 'done');
25
+
26
+ function ensure() { fs.mkdirSync(PENDING, { recursive: true }); fs.mkdirSync(DONE, { recursive: true }); }
27
+
28
+ function today() {
29
+ if (process.env.HANDOFF_DATE) return process.env.HANDOFF_DATE;
30
+ const d = new Date(), p = n => String(n).padStart(2, '0');
31
+ return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}`;
32
+ }
33
+
34
+ function slug(s) {
35
+ return String(s).replace(/[\\/:*?"<>|\s]/g, '_').slice(0, 30);
36
+ }
37
+
38
+ function listFiles() {
39
+ ensure();
40
+ return fs.readdirSync(PENDING).filter(f => f.endsWith('.md')).sort();
41
+ }
42
+
43
+ const TEMPLATE = (topic, date) => `---
44
+ topic: ${topic}
45
+ created: ${date}
46
+ status: pending
47
+ resume_phrases:
48
+ - "${topic}の続き"
49
+ ---
50
+
51
+ # ${topic}
52
+
53
+ ## いまどこまで
54
+
55
+ (★1行で。次に開いたときの入口になります)
56
+
57
+ ## 次にやること
58
+
59
+ 1.
60
+ 2.
61
+
62
+ ## 決まっていないこと
63
+
64
+ -
65
+
66
+ ## 触った場所(ファイル・画面)
67
+
68
+ -
69
+
70
+ ## 気をつけること
71
+
72
+ -
73
+ `;
74
+
75
+ function cmdNew(topic) {
76
+ if (!topic) { console.error('使い方: node handoff.js new "テーマ名"'); process.exit(1); }
77
+ ensure();
78
+ const name = `${today()}_${slug(topic)}.md`;
79
+ const p = path.join(PENDING, name);
80
+ if (fs.existsSync(p)) {
81
+ console.log(`すでにあります: ${name}`);
82
+ console.log('(同じテーマなら、上書きせずにそのファイルを直してください)');
83
+ return;
84
+ }
85
+ fs.writeFileSync(p, TEMPLATE(topic, today()), 'utf8');
86
+ console.log(`✅ 作りました: second-brain/_handoffs/pending/${name}`);
87
+ console.log('');
88
+ console.log('★このあと、中身を埋めてください。空のまま置くと役に立ちません。');
89
+ console.log(' 特に「いまどこまで」の1行が、次に開いたときの入口になります。');
90
+ }
91
+
92
+ function cmdList() {
93
+ const files = listFiles();
94
+ if (!files.length) { console.log('引き継ぎメモはありません。'); return; }
95
+ console.log(`引き継ぎメモ ${files.length}件`);
96
+ console.log('');
97
+ files.forEach((f, i) => {
98
+ const body = fs.readFileSync(path.join(PENDING, f), 'utf8');
99
+ const m = body.match(/## いまどこまで\s*\n+([^\n#(]*)/);
100
+ const line = m && m[1].trim() ? m[1].trim() : '(まだ書かれていません)';
101
+ console.log(` [${i + 1}] ${f}`);
102
+ console.log(` ${line}`);
103
+ });
104
+ console.log('');
105
+ if (files.length > 8) {
106
+ console.log('⚠️ 8件を超えています。片付いたものは done にしてください。');
107
+ console.log(' 溜まると、どれが生きているか分からなくなります。');
108
+ }
109
+ }
110
+
111
+ function pick(n) {
112
+ const files = listFiles();
113
+ const i = parseInt(n, 10) - 1;
114
+ if (isNaN(i) || i < 0 || i >= files.length) {
115
+ console.error(`番号が違います(1〜${files.length})`);
116
+ process.exit(1);
117
+ }
118
+ return files[i];
119
+ }
120
+
121
+ function cmdShow(n) {
122
+ const f = pick(n);
123
+ console.log(fs.readFileSync(path.join(PENDING, f), 'utf8'));
124
+ }
125
+
126
+ function cmdDone(n) {
127
+ const f = pick(n);
128
+ const src = path.join(PENDING, f);
129
+ let body = fs.readFileSync(src, 'utf8');
130
+ body = body.replace(/^status: pending$/m, `status: done\ndone_at: ${today()}`);
131
+ const dst = path.join(DONE, f);
132
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
133
+ fs.writeFileSync(dst, body, 'utf8');
134
+ fs.unlinkSync(src);
135
+ console.log(`✅ done へ移しました: ${f}`);
136
+ }
137
+
138
+ const cmd = process.argv[2];
139
+ if (cmd === 'new') cmdNew(process.argv[3]);
140
+ else if (cmd === 'list') cmdList();
141
+ else if (cmd === 'show') cmdShow(process.argv[3]);
142
+ else if (cmd === 'done') cmdDone(process.argv[3]);
143
+ else {
144
+ console.log('使い方:');
145
+ console.log(' node handoff.js new "テーマ名" 新しく作る');
146
+ console.log(' node handoff.js list 残っているものを見る');
147
+ console.log(' node handoff.js show <番号> 中身を見る');
148
+ console.log(' node handoff.js done <番号> 片付いたので done へ移す');
149
+ }