@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
package/gtasks.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* やること(Google Tasks)との連携
|
|
3
|
+
*
|
|
4
|
+
* ★2026-08-21 主
|
|
5
|
+
* 「googleAPIとの連携を最初の段階でスムーズに」
|
|
6
|
+
* 「tasks入ったら、AI用のタスクリストを作成。適宜AIがタスク登録と完了をやる」
|
|
7
|
+
* 「タスク連携後は、新規タブでタスクも見るようにする」
|
|
8
|
+
*
|
|
9
|
+
* ★設計
|
|
10
|
+
* ・**依存ゼロの原則を崩さない**。googleapis は「使う人だけ入れる」(optional)
|
|
11
|
+
* ・トークンは手元(BANTO_HOME)に置く。★配布物にも、うちのサーバーにも置かない
|
|
12
|
+
* ・AI用のリストを1本だけ作る(★主のふだんのリストを汚さない)
|
|
13
|
+
* ・★**起動のたびに件数を見て、instructions に載せる**=新しい窓でも必ず目に入る
|
|
14
|
+
*/
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const HOME = process.env.BANTO_HOME || path.join(require('os').homedir(), '.banto');
|
|
19
|
+
const TOKEN = path.join(HOME, 'google-token.json');
|
|
20
|
+
const LIST_NAME = process.env.BANTO_TASKLIST || '番頭のやること';
|
|
21
|
+
const SCOPES = ['https://www.googleapis.com/auth/tasks'];
|
|
22
|
+
|
|
23
|
+
// ★探す場所。利用者が置きやすい順
|
|
24
|
+
function credentialsPath() {
|
|
25
|
+
const cands = [
|
|
26
|
+
process.env.BANTO_GOOGLE_CREDENTIALS,
|
|
27
|
+
path.join(HOME, 'credentials.json'),
|
|
28
|
+
path.join(process.cwd(), 'credentials.json'),
|
|
29
|
+
path.join(process.cwd(), 'supplements', 'credentials.json'),
|
|
30
|
+
].filter(Boolean);
|
|
31
|
+
for (const p of cands) { try { if (fs.existsSync(p)) return p; } catch (_) {} }
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function haveLib() {
|
|
36
|
+
try { require.resolve('googleapis'); require.resolve('@google-cloud/local-auth'); return true; }
|
|
37
|
+
catch (_) { return false; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** いまどこまで進んでいるか(★毎回これを見てから案内を出す) */
|
|
41
|
+
function state() {
|
|
42
|
+
return {
|
|
43
|
+
部品: haveLib() ? 'あり' : 'まだ',
|
|
44
|
+
認証情報: credentialsPath() ? 'あり' : 'まだ',
|
|
45
|
+
つながっている: fs.existsSync(TOKEN),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* ★「最初の段階でスムーズに」の本体。
|
|
51
|
+
* いまどこで止まっているかを見て、**次の一手だけ**を返す。
|
|
52
|
+
* ★全部の手順を一度に渡さない(5つを超えると人はそこで止まる)。
|
|
53
|
+
*/
|
|
54
|
+
function setupGuide() {
|
|
55
|
+
const s = state();
|
|
56
|
+
if (s.つながっている) {
|
|
57
|
+
return {
|
|
58
|
+
いまの状態: '✅ つながっています',
|
|
59
|
+
リスト: LIST_NAME,
|
|
60
|
+
次にすること: 'ありません。「やること見せて」と言ってください。',
|
|
61
|
+
note: '★つなぎ直すときは、' + TOKEN + ' を消してから、もう一度お呼びください。',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (s.部品 === 'まだ') {
|
|
65
|
+
return {
|
|
66
|
+
いまの状態: '① 部品がまだ入っていません',
|
|
67
|
+
次にすること: '仕事のフォルダで、これを1回だけ実行してください',
|
|
68
|
+
実行するもの: 'npm i googleapis @google-cloud/local-auth',
|
|
69
|
+
note: '★これが終わったら、もう一度お呼びください。次の手順をご案内します。' +
|
|
70
|
+
'★Googleに繋がなくても、番頭の他の道具は全部使えます(あとでも構いません)。',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (s.認証情報 === 'まだ') {
|
|
74
|
+
return {
|
|
75
|
+
いまの状態: '② Googleの認証情報がまだです',
|
|
76
|
+
次にすること: [
|
|
77
|
+
'1. https://console.cloud.google.com/ でプロジェクトを作る(既にあればそれで可)',
|
|
78
|
+
'2. 「APIとサービス」→「ライブラリ」で **Google Tasks API** を有効にする',
|
|
79
|
+
'3. 「OAuth同意画面」→ 外部 →★**テストユーザーに、ご自身のGoogleアカウントを追加**',
|
|
80
|
+
'4. 「認証情報」→「OAuthクライアントID」→ 種類は **デスクトップアプリ**',
|
|
81
|
+
'5. JSONを落として、**credentials.json** という名前で次の場所へ置く',
|
|
82
|
+
],
|
|
83
|
+
置き場所: path.join(HOME, 'credentials.json'),
|
|
84
|
+
note: '★置いたら、もう一度お呼びください。ブラウザが開いて、最後の確認になります。' +
|
|
85
|
+
'★テストユーザーのままだと、**7日でつなぎ直し**が要ります(Googleの仕様です)。',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
いまの状態: '③ あと1歩です',
|
|
90
|
+
次にすること: '「Googleにつないで」と言ってください(connect_google)。ブラウザが開きます。',
|
|
91
|
+
note: '★開いた画面で、ご自身のGoogleアカウントを選んで許可してください。',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** ブラウザを開いて、つなぐ(★ここだけ人の操作が要る) */
|
|
96
|
+
async function connect() {
|
|
97
|
+
if (!haveLib()) throw new Error('部品がまだです。先に npm i googleapis @google-cloud/local-auth を実行してください');
|
|
98
|
+
const cp = credentialsPath();
|
|
99
|
+
if (!cp) throw new Error('credentials.json が見つかりません。「Googleの手順を教えて」と言ってください');
|
|
100
|
+
|
|
101
|
+
const { authenticate } = require('@google-cloud/local-auth');
|
|
102
|
+
const { google } = require('googleapis');
|
|
103
|
+
const client = await authenticate({ scopes: SCOPES, keyfilePath: cp });
|
|
104
|
+
if (!client || !client.credentials) throw new Error('つながりませんでした');
|
|
105
|
+
|
|
106
|
+
const keys = JSON.parse(fs.readFileSync(cp, 'utf8'));
|
|
107
|
+
const key = keys.installed || keys.web;
|
|
108
|
+
fs.mkdirSync(HOME, { recursive: true });
|
|
109
|
+
fs.writeFileSync(TOKEN, JSON.stringify({
|
|
110
|
+
type: 'authorized_user',
|
|
111
|
+
client_id: key.client_id,
|
|
112
|
+
client_secret: key.client_secret,
|
|
113
|
+
refresh_token: client.credentials.refresh_token,
|
|
114
|
+
}, null, 2), 'utf8');
|
|
115
|
+
|
|
116
|
+
const list = await ensureList();
|
|
117
|
+
return {
|
|
118
|
+
つながりました: true,
|
|
119
|
+
トークンの置き場: TOKEN + '(★お手元だけ。うちのサーバーには送っていません)',
|
|
120
|
+
やることリスト: list.title,
|
|
121
|
+
note: '★これで「やること登録して」「終わった」が使えます。' +
|
|
122
|
+
'★新しい窓を開くと、残っている件数が最初に出ます。',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function auth() {
|
|
127
|
+
if (!fs.existsSync(TOKEN)) throw new Error('まだGoogleにつながっていません。「Googleの手順を教えて」と言ってください');
|
|
128
|
+
const { google } = require('googleapis');
|
|
129
|
+
return google.auth.fromJSON(JSON.parse(fs.readFileSync(TOKEN, 'utf8')));
|
|
130
|
+
}
|
|
131
|
+
function svc() {
|
|
132
|
+
const { google } = require('googleapis');
|
|
133
|
+
return google.tasks({ version: 'v1', auth: auth() });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** ★AI用のリストを1本だけ作る(無ければ作る・あればそれを使う) */
|
|
137
|
+
async function ensureList() {
|
|
138
|
+
const s = svc();
|
|
139
|
+
const r = await s.tasklists.list({ maxResults: 100 });
|
|
140
|
+
const found = (r.data.items || []).find((x) => x.title === LIST_NAME);
|
|
141
|
+
if (found) return { id: found.id, title: found.title, 作った: false };
|
|
142
|
+
const c = await s.tasklists.insert({ requestBody: { title: LIST_NAME } });
|
|
143
|
+
return { id: c.data.id, title: c.data.title, 作った: true };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function listTasks(all) {
|
|
147
|
+
const s = svc();
|
|
148
|
+
const l = await ensureList();
|
|
149
|
+
const r = await s.tasks.list({
|
|
150
|
+
tasklist: l.id, maxResults: 100,
|
|
151
|
+
showCompleted: !!all, showHidden: !!all,
|
|
152
|
+
});
|
|
153
|
+
const items = (r.data.items || []).map((t) => ({
|
|
154
|
+
id: t.id, 題: t.title,
|
|
155
|
+
期限: t.due ? String(t.due).slice(0, 10) : null,
|
|
156
|
+
終わった: t.status === 'completed',
|
|
157
|
+
覚え書き: t.notes || null,
|
|
158
|
+
}));
|
|
159
|
+
const 残り = items.filter((t) => !t.終わった);
|
|
160
|
+
const 今日 = new Date().toISOString().slice(0, 10);
|
|
161
|
+
const 期限切れ = 残り.filter((t) => t.期限 && t.期限 < 今日);
|
|
162
|
+
return {
|
|
163
|
+
リスト: l.title,
|
|
164
|
+
残っているもの: 残り.length,
|
|
165
|
+
期限切れ: 期限切れ.length,
|
|
166
|
+
やること: (all ? items : 残り).sort((a, b) => (a.期限 || '9999') < (b.期限 || '9999') ? -1 : 1),
|
|
167
|
+
note: 期限切れ.length
|
|
168
|
+
? '★期限が過ぎているものがあります。**終わっているものは、終わったと言ってください**(残ったままだと、次も出ます)。'
|
|
169
|
+
: '',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function addTask(title, due, notes) {
|
|
174
|
+
const t = String(title || '').trim();
|
|
175
|
+
if (!t) throw new Error('やることの中身を教えてください');
|
|
176
|
+
const s = svc();
|
|
177
|
+
const l = await ensureList();
|
|
178
|
+
const body = { title: t };
|
|
179
|
+
if (due) body.due = new Date(String(due) + 'T00:00:00.000Z').toISOString();
|
|
180
|
+
if (notes) body.notes = String(notes);
|
|
181
|
+
const r = await s.tasks.insert({ tasklist: l.id, requestBody: body });
|
|
182
|
+
return {
|
|
183
|
+
登録しました: r.data.title,
|
|
184
|
+
期限: r.data.due ? String(r.data.due).slice(0, 10) : 'なし',
|
|
185
|
+
id: r.data.id,
|
|
186
|
+
リスト: l.title,
|
|
187
|
+
note: '★これは' + l.title + 'に入りました。ご自身のふだんのリストは触っていません。',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function completeTask(id, title) {
|
|
192
|
+
const s = svc();
|
|
193
|
+
const l = await ensureList();
|
|
194
|
+
let tid = id;
|
|
195
|
+
if (!tid) {
|
|
196
|
+
// ★題で探す(人は id を覚えていない)
|
|
197
|
+
const r = await s.tasks.list({ tasklist: l.id, maxResults: 100 });
|
|
198
|
+
const hit = (r.data.items || []).filter((x) => x.status !== 'completed' &&
|
|
199
|
+
String(x.title).indexOf(String(title || '')) !== -1);
|
|
200
|
+
if (!hit.length) throw new Error('見つかりません: ' + title);
|
|
201
|
+
if (hit.length > 1) {
|
|
202
|
+
return { 決められません: hit.map((x) => ({ id: x.id, 題: x.title })), note: '★どれか教えてください(同じ言葉が複数あります)' };
|
|
203
|
+
}
|
|
204
|
+
tid = hit[0].id;
|
|
205
|
+
}
|
|
206
|
+
const cur = await s.tasks.get({ tasklist: l.id, task: tid });
|
|
207
|
+
const r = await s.tasks.update({
|
|
208
|
+
tasklist: l.id, task: tid,
|
|
209
|
+
requestBody: Object.assign({}, cur.data, { status: 'completed' }),
|
|
210
|
+
});
|
|
211
|
+
return { 終わりにしました: r.data.title, note: '★取り消すときは、Google ToDo の画面で戻せます。' };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** ★起動時に見る用(軽い・失敗しても黙って null を返す) */
|
|
215
|
+
function quickCount() {
|
|
216
|
+
try {
|
|
217
|
+
if (!fs.existsSync(TOKEN) || !haveLib()) return null;
|
|
218
|
+
const cache = path.join(HOME, '.tasks-cache.json');
|
|
219
|
+
const c = JSON.parse(fs.readFileSync(cache, 'utf8'));
|
|
220
|
+
// ★古い数字を出さない(半日より古ければ黙る)
|
|
221
|
+
if (Date.now() - new Date(c.at).getTime() > 12 * 3600e3) return null;
|
|
222
|
+
return c;
|
|
223
|
+
} catch (_) { return null; }
|
|
224
|
+
}
|
|
225
|
+
function saveCount(残り, 期限切れ) {
|
|
226
|
+
try {
|
|
227
|
+
fs.mkdirSync(HOME, { recursive: true });
|
|
228
|
+
fs.writeFileSync(path.join(HOME, '.tasks-cache.json'),
|
|
229
|
+
JSON.stringify({ 残り, 期限切れ, at: new Date().toISOString() }, null, 2), 'utf8');
|
|
230
|
+
} catch (_) {}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = { state, setupGuide, connect, ensureList, listTasks, addTask, completeTask, quickCount, saveCount, LIST_NAME, TOKEN };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 鍵の確認(番頭ゲート用)
|
|
3
|
+
*
|
|
4
|
+
* ★設計(2026-08-20 決定)
|
|
5
|
+
* ・お試し30日。切れたら「使う機能は止まる/既にあるものは読める」
|
|
6
|
+
* ・送るのは **鍵のハッシュ・版番号** だけ。★検査した文章も顧客名も送らない
|
|
7
|
+
* ・1日1回しか問い合わせない(起動のたびに叩かない)
|
|
8
|
+
* ・★通信できないときは通す(猶予7日)。止めると仕事ができなくなるため
|
|
9
|
+
* ・★エンドポイント未設定なら、確認そのものをしない(=いまの動作のまま)
|
|
10
|
+
*
|
|
11
|
+
* 設定(どちらか)
|
|
12
|
+
* 環境変数 BANTOU_LICENSE_KEY / BANTOU_LICENSE_URL
|
|
13
|
+
* または license/config.json { "key": "...", "url": "..." }
|
|
14
|
+
*/
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const crypto = require('crypto');
|
|
18
|
+
const https = require('https');
|
|
19
|
+
|
|
20
|
+
const DIR = __dirname;
|
|
21
|
+
const CACHE = path.join(DIR, '.state.json');
|
|
22
|
+
const GRACE_DAYS = 7; // 通信できないときに通す日数
|
|
23
|
+
const CHECK_EVERY_H = 24; // 問い合わせの間隔
|
|
24
|
+
|
|
25
|
+
function config() {
|
|
26
|
+
const c = { key: process.env.BANTOU_LICENSE_KEY || '', url: process.env.BANTOU_LICENSE_URL || '',
|
|
27
|
+
anonKey: process.env.BANTOU_LICENSE_ANON || '' };
|
|
28
|
+
const f = path.join(DIR, 'config.json');
|
|
29
|
+
if (fs.existsSync(f)) {
|
|
30
|
+
try {
|
|
31
|
+
const j = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
32
|
+
c.key = c.key || j.key || '';
|
|
33
|
+
c.url = c.url || j.url || '';
|
|
34
|
+
c.anonKey = c.anonKey || j.anonKey || '';
|
|
35
|
+
} catch (_) {}
|
|
36
|
+
}
|
|
37
|
+
return c;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const readCache = () => {
|
|
41
|
+
try { return JSON.parse(fs.readFileSync(CACHE, 'utf8')); } catch (_) { return null; }
|
|
42
|
+
};
|
|
43
|
+
const writeCache = (o) => { try { fs.writeFileSync(CACHE, JSON.stringify(o, null, 2)); } catch (_) {} };
|
|
44
|
+
|
|
45
|
+
// ★Supabase の PostgREST は apikey ヘッダーが必須(記録あり/2026-08-20 にここで踏んだ)
|
|
46
|
+
function post(url, body, apiKey, timeoutMs = 4000) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const u = new URL(url);
|
|
49
|
+
const data = JSON.stringify(body);
|
|
50
|
+
const req = https.request(
|
|
51
|
+
{ hostname: u.hostname, path: u.pathname + u.search, method: 'POST',
|
|
52
|
+
headers: Object.assign(
|
|
53
|
+
{ 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) },
|
|
54
|
+
apiKey ? { apikey: apiKey, Authorization: 'Bearer ' + apiKey } : {}
|
|
55
|
+
) },
|
|
56
|
+
(res) => {
|
|
57
|
+
let b = '';
|
|
58
|
+
res.on('data', (d) => (b += d));
|
|
59
|
+
res.on('end', () => {
|
|
60
|
+
try { resolve(JSON.parse(b)); } catch (e) { reject(new Error('応答を読めません')); }
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
req.setTimeout(timeoutMs, () => { req.destroy(new Error('時間切れ')); });
|
|
65
|
+
req.on('error', reject);
|
|
66
|
+
req.write(data);
|
|
67
|
+
req.end();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @returns {Promise<{allowed:boolean, status:string, message:string, daysLeft:number|null}>}
|
|
73
|
+
* status: unconfigured / active / expiring / expired / revoked / unknown / offline
|
|
74
|
+
*/
|
|
75
|
+
async function check(version) {
|
|
76
|
+
const { key, url, anonKey } = config();
|
|
77
|
+
|
|
78
|
+
// ★未設定なら確認しない(=制限なし)。導入前・自社利用がこの状態
|
|
79
|
+
if (!key || !url || !anonKey) {
|
|
80
|
+
return { allowed: true, status: 'unconfigured', message: '', daysLeft: null };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const keyHash = crypto.createHash('sha256').update(key).digest('hex');
|
|
84
|
+
const cache = readCache();
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
|
|
87
|
+
// 1日以内に確認済みなら、その結果を使う
|
|
88
|
+
if (cache && cache.keyHash === keyHash && now - (cache.checkedAt || 0) < CHECK_EVERY_H * 3600e3) {
|
|
89
|
+
return decide(cache, now);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// ★送るのは鍵のハッシュと版だけ
|
|
94
|
+
const r0 = await post(url, { p_key_hash: keyHash, p_version: version || null }, config().anonKey);
|
|
95
|
+
const r = Array.isArray(r0) ? (r0[0] || {}) : r0;
|
|
96
|
+
const next = {
|
|
97
|
+
keyHash, checkedAt: now,
|
|
98
|
+
status: r.status || 'unknown',
|
|
99
|
+
daysLeft: typeof r.days_left === 'number' ? r.days_left : null,
|
|
100
|
+
plan: r.plan || null, expiresAt: r.expires_at || null,
|
|
101
|
+
lastOkAt: r.status === 'active' ? now : (cache && cache.lastOkAt) || null,
|
|
102
|
+
};
|
|
103
|
+
writeCache(next);
|
|
104
|
+
return decide(next, now);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// ★通信できない=止めない(猶予のあいだは通す)
|
|
107
|
+
const lastOk = (cache && cache.lastOkAt) || 0;
|
|
108
|
+
const withinGrace = lastOk && now - lastOk < GRACE_DAYS * 86400e3;
|
|
109
|
+
return {
|
|
110
|
+
allowed: true, status: 'offline', daysLeft: null,
|
|
111
|
+
message: withinGrace ? '' :
|
|
112
|
+
'※ライセンスの確認ができていません(通信不可)。しばらく続くようならご連絡ください',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function decide(c, now) {
|
|
118
|
+
const d = c.daysLeft;
|
|
119
|
+
if (c.status === 'active') {
|
|
120
|
+
return {
|
|
121
|
+
allowed: true, status: d !== null && d <= 3 ? 'expiring' : 'active', daysLeft: d,
|
|
122
|
+
message: d !== null && d <= 3
|
|
123
|
+
? `※お試し期間はあと ${d} 日です。続けてお使いになる場合はご連絡ください` : '',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (c.status === 'expired') {
|
|
127
|
+
return { allowed: false, status: 'expired', daysLeft: 0,
|
|
128
|
+
message: 'お試し期間が終了しました。★これまでの記録はお手元にそのまま残っています(テキストのまま読めます)。続けてお使いになる場合はご連絡ください' };
|
|
129
|
+
}
|
|
130
|
+
if (c.status === 'revoked') {
|
|
131
|
+
return { allowed: false, status: 'revoked', daysLeft: 0,
|
|
132
|
+
message: 'この鍵は停止されています。★記録はお手元に残っています。ご連絡ください' };
|
|
133
|
+
}
|
|
134
|
+
return { allowed: false, status: 'unknown', daysLeft: null,
|
|
135
|
+
message: 'この鍵が確認できませんでした。お手数ですがご連絡ください' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { check };
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@horizon_works/banto",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "AI番頭のOS。ルール・記憶・検査を1本のMCPで配ります。中身をクラウドへ渡さずに検査できます。",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"banto",
|
|
9
|
+
"bantou",
|
|
10
|
+
"ai-agent",
|
|
11
|
+
"privacy",
|
|
12
|
+
"japanese"
|
|
13
|
+
],
|
|
14
|
+
"license": "UNLICENSED",
|
|
15
|
+
"private": false,
|
|
16
|
+
"author": "株式会社ホライズンワークス",
|
|
17
|
+
"type": "commonjs",
|
|
18
|
+
"bin": {
|
|
19
|
+
"banto": "server.js"
|
|
20
|
+
},
|
|
21
|
+
"main": "server.js",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"server.js",
|
|
27
|
+
"gate_core.js",
|
|
28
|
+
"privacy.js",
|
|
29
|
+
"shelf.js",
|
|
30
|
+
"gtasks.js",
|
|
31
|
+
"env.js",
|
|
32
|
+
"subconscious.js",
|
|
33
|
+
"supplements.json",
|
|
34
|
+
"payload/",
|
|
35
|
+
"license/client.js",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"optionalDependencies": {
|
|
42
|
+
"googleapis": "^144.0.0",
|
|
43
|
+
"@google-cloud/local-auth": "^3.0.1"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compliance
|
|
3
|
+
description: 文章を出す前の関所。★このプレーン版では、辞書は空にしてあります。商売の文章(商品の紹介・広告・宣伝)を書くようになったときに、その業種の辞書を足して使います。個人の文章には使いません。
|
|
4
|
+
layer: L0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 文章を出す前の関所(compliance)
|
|
8
|
+
|
|
9
|
+
> ★**このプレーン版では、この関所はまだ仕事をしていません。**
|
|
10
|
+
> 辞書が空だからです。**それでいいです。**
|
|
11
|
+
>
|
|
12
|
+
> 個人で使うぶんには、止めなければいけない言葉はほとんどありません。
|
|
13
|
+
> ここは「**商売の文章を書くようになったときのための器**」として置いてあります。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## いまの状態
|
|
18
|
+
|
|
19
|
+
| 辞書 | 中身 |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `dict/00_動作確認.json` | 関所そのものが動くかを確かめる1件だけ。★消さないでください |
|
|
22
|
+
| `dict/06_house_rules.json` | **空**。自分ルールを足す場所 |
|
|
23
|
+
| `dict/_sample/` | 見本(美容・健康の商材向け)。★既定では**読み込まれません** |
|
|
24
|
+
|
|
25
|
+
`node kit/compliance/lint.js <ファイル>` は、いま走らせても
|
|
26
|
+
「危ない表現は見つかりませんでした」としか出ません。**それが正常です。**
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## いつ使い始めるか
|
|
31
|
+
|
|
32
|
+
次のことをするようになったら、この関所を起こします。
|
|
33
|
+
|
|
34
|
+
- 商品やサービスを**売る**文章を書く(紹介文・広告・SNSの宣伝・フリマの説明)
|
|
35
|
+
- 人から**お金をもらう**活動の告知を書く
|
|
36
|
+
- 健康・美容・食品・医療にかかわることを人に勧める
|
|
37
|
+
|
|
38
|
+
★これらは、**言い方ひとつで法律に触れます**。個人でも例外ではありません。
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 起こし方
|
|
43
|
+
|
|
44
|
+
### 1. 自分ルールから始める(★いちばん効く)
|
|
45
|
+
|
|
46
|
+
`dict/06_house_rules.json` の `entries` に足します。法律の話ではなく、
|
|
47
|
+
「**前にこれで嫌な思いをした**」を1件ずつ入れていくだけです。
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"ng": ["絶対に儲かる"],
|
|
52
|
+
"ok": "(数字と条件を書く。例:3ヶ月で◯件の実績があります)",
|
|
53
|
+
"why": "言い切ると、そうならなかったときに責任を問われる",
|
|
54
|
+
"flag": "auto"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
★**必ず `ok`(言い換え候補)を書いてください。** 「言えません」で止めると、人は回り道を探します。
|
|
59
|
+
|
|
60
|
+
### 2. 業種の辞書を足す
|
|
61
|
+
|
|
62
|
+
その業種で踏むと事故になる法律を、辞書にします。
|
|
63
|
+
作り方の見本が `dict/_sample/` にあります(美容・健康の商材向け)。
|
|
64
|
+
|
|
65
|
+
**そのまま使わないでください。** 業種が違えば、見るべき法律も違います。
|
|
66
|
+
見本は「どういう形で書くか」の参考にしてください。
|
|
67
|
+
|
|
68
|
+
★一次情報(行政・公式のページ)に当たってから書くこと。**記憶で条番号を書かない。**
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 通し方
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# ファイルを検査
|
|
76
|
+
node kit/compliance/lint.js 下書き.txt
|
|
77
|
+
|
|
78
|
+
# その場の文章を検査
|
|
79
|
+
echo "検査したい文章" | node kit/compliance/lint.js
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
終了コードの意味:
|
|
83
|
+
|
|
84
|
+
| コード | 意味 | どうする |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| 0 | 辞書に載っている言い方は無かった | ★**まだ出してよい、ではありません。** 下の4点を自分の目で見る |
|
|
87
|
+
| 1 | **要確認**あり | 言い換え候補を添えて、本人に判断を返す。勝手にOKにしない |
|
|
88
|
+
| 2 | **要修正**あり | 言い換えて、もう一度通す。通るまで出さない |
|
|
89
|
+
| 3 | **検査できなかった** | 辞書が空・壊れている・カテゴリ名が違う。★**0と混同しない** |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## ★辞書に無いものは検出できません
|
|
94
|
+
|
|
95
|
+
0件は「**辞書に載っている表現が無かった**」だけで、安全の証明ではありません。
|
|
96
|
+
0件のときも、次の4つは自分の目で見てください。
|
|
97
|
+
|
|
98
|
+
1. **病気・治療を思わせないか**(病名/治る・効く・改善する)
|
|
99
|
+
2. **体が変わると言い切っていないか**(消える・なくなる・落ちる・生える)
|
|
100
|
+
3. **数字や実績に根拠があるか**(母数・時期・対象・比べた相手を書けるか)
|
|
101
|
+
4. **他社・他人を下げていないか**
|
|
102
|
+
|
|
103
|
+
見つけたら、**辞書に足してください**。辞書は使いながら育てるものです。
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## やってはいけないこと
|
|
108
|
+
|
|
109
|
+
- ❌ **0件だったことを「安全の証明」として報告する**
|
|
110
|
+
- ❌ **[要確認]を自分の判断でOKにする**
|
|
111
|
+
- ❌ **「個人の感想です」を付けて危ない表現を通す**(注記は免罪符になりません)
|
|
112
|
+
- ❌ **数値・成分・実績を自分で作る**(分からない数字は `■要確認:○○`)
|
|
113
|
+
- ❌ 見本の辞書を、業種を確かめずにそのまま使う
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"category": "selftest",
|
|
3
|
+
"label": "動作確認(★消さないでください)",
|
|
4
|
+
"note": "この辞書は、関所そのものが動いているかを確かめるためだけに入っています。ふだんの文章には出てこない言葉なので、誤って引っかかることはありません。中身を空にすると点検が通らなくなります。",
|
|
5
|
+
"entries": [
|
|
6
|
+
{
|
|
7
|
+
"ng": ["ゲート動作確認テスト"],
|
|
8
|
+
"ok": "(これは動作確認用の言葉です。文章から消してください)",
|
|
9
|
+
"why": "関所が動いているかを確かめるための言葉です。ここが検出されれば、関所は生きています",
|
|
10
|
+
"law": "",
|
|
11
|
+
"flag": "auto"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"category": "house_rules",
|
|
3
|
+
"label": "自分ルール",
|
|
4
|
+
"note": "★ここは空の器です。使っていくうちに『これは書かないでほしい』が出てきたら、下の entries に足してください。法律でも規約でもない、その人の判断です。実際の現場では、この層がいちばん効きます。",
|
|
5
|
+
"_書き方の見本": {
|
|
6
|
+
"ng": ["止めたい言い方をここに書く", "言い換えも複数書ける"],
|
|
7
|
+
"regex": ["正規表現でしか拾えないものだけ。ふつうは要りません"],
|
|
8
|
+
"ok": "★代わりに使える言い方(必須。ここを空にしない)",
|
|
9
|
+
"why": "なぜ止めるのか(あとで自分が読んで分かるように)",
|
|
10
|
+
"law": "根拠があれば。無ければ空でよい",
|
|
11
|
+
"flag": "auto なら要修正 / human なら人が判断",
|
|
12
|
+
"near": ["この語が同じ行にあるときだけ検出する(誤検出よけ)"],
|
|
13
|
+
"except": ["この語が同じ行にあるときは検出しない(否定文よけ)"]
|
|
14
|
+
},
|
|
15
|
+
"entries": []
|
|
16
|
+
}
|