@tyhld/conductor 0.9.0 → 0.10.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/dist/cli.js +8 -1
- package/dist/proto-profile.js +336 -0
- package/package.json +1 -1
- package/sales-template/README.md +30 -1
- package/sales-template/install.sh +16 -0
- package/sales-template/setup.sh +100 -2
package/dist/cli.js
CHANGED
|
@@ -301,6 +301,8 @@ function printVersion() {
|
|
|
301
301
|
* ★このコマンドがあれば「conductor setup <現場名>」の1行で済む。
|
|
302
302
|
* 【やること】自分(dist/cli.js)から見た相対位置のインストーラを、引数をそのまま渡して呼ぶ。
|
|
303
303
|
* ★中身の判断はすべて setup.sh 側。ここは場所を知っているだけの入口。
|
|
304
|
+
* ★`--profile proto`(試作の型)もそのまま素通しする。どの型で何を置くかの判断は
|
|
305
|
+
* setup.sh と、締め出す物の正本 src/proto-profile.ts が持つ(ここには一覧を書かない)。
|
|
304
306
|
*/
|
|
305
307
|
function runSetupCommand(argv) {
|
|
306
308
|
const setup = fileURLToPath(new URL('../sales-template/setup.sh', import.meta.url));
|
|
@@ -319,7 +321,12 @@ function printUsage() {
|
|
|
319
321
|
'使い方:',
|
|
320
322
|
' conductor start [-s <site>] [-t <idle|working>] [-a <lastAction>] 現場ごとの常駐(heartbeat+relay)',
|
|
321
323
|
' conductor ear この機に1本の「耳」(合図の受け取り役)',
|
|
322
|
-
' conductor setup <現場名> [フォルダ]
|
|
324
|
+
' conductor setup <現場名> [フォルダ] [--profile <default|proto>] 采配くんをこのPCに設置する(初回の1回だけ)',
|
|
325
|
+
'',
|
|
326
|
+
'設置の型(--profile。既定は default=今までどおり):',
|
|
327
|
+
' default ふつうの現場。従来と同じ設定を置きます。',
|
|
328
|
+
' proto ★試作の現場(例 ai-kana-proto)。本番の記録(DB)・鍵・送信・課金・本番の入口を',
|
|
329
|
+
' はじめから締め出した設定と、.env の見本(試作の口の2項目だけ)を置きます。',
|
|
323
330
|
'',
|
|
324
331
|
'必須の環境変数:',
|
|
325
332
|
' CONDUCTOR_URL 中央(devlog-tracker)のベースURL',
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 試作(proto)の型 — ★「試作環境から締め出す物」の正本はこの1ファイルだけ。
|
|
3
|
+
*
|
|
4
|
+
* 【この型は何のためにあるか】
|
|
5
|
+
* 試作の現場(例: ai-kana-proto)は「作って見せる」ための場所で、本番の記録(DB)も
|
|
6
|
+
* 本番の鍵も持たない。持たないのだから、そこへ触る操作は【はじめから出来なくしておく】。
|
|
7
|
+
* ★ねらいは「確認が要る操作を試作環境から排除する」こと。人に聞かないと進めない操作が
|
|
8
|
+
* 残っていると、端末に人が居ない時間に職人がそこで止まってしまう(ADR-016 と同じ型)。
|
|
9
|
+
*
|
|
10
|
+
* 【なぜ正本を1ファイルにするか】
|
|
11
|
+
* 締め出す物は2か所に効かせる必要がある。
|
|
12
|
+
* ・permissions.deny … 職人が打つコマンド(psql / supabase db push / gh secret set …)
|
|
13
|
+
* ・sandbox.network.deniedDomains … 職人が出て行く接続先(本番DB・送信・課金 …)
|
|
14
|
+
* この2つを人が手で二重管理すると、片方に足してもう片方を忘れる事故が必ず起きる
|
|
15
|
+
* (この製品で ADR-011/013 と繰り返し起きた型)。だから★下の PROTO_FORBIDDEN だけを正とし、
|
|
16
|
+
* deny も deniedDomains も【そこから機械的に生成する】。写しを2か所に持たない。
|
|
17
|
+
*
|
|
18
|
+
* 【★番人は増やさない】
|
|
19
|
+
* ここは既存の仕組み(Claude Code の deny と sandbox)に載せるだけで、新しい番人
|
|
20
|
+
* (cc2_guard.py などのフック)は1本も足さない。既存の三重保護はそのまま効く。
|
|
21
|
+
*
|
|
22
|
+
* 【★deniedDomains と allowedDomains の関係(実測・ADR-004 / settings.sample.json)】
|
|
23
|
+
* 本体の判定順は Denied → Allowed → 一致なし。つまり deniedDomains が【必ず先に】効く。
|
|
24
|
+
* だから
|
|
25
|
+
* ・許可の一覧(allowedDomains)は「通したい物」を書くだけの側で、後から配り役
|
|
26
|
+
* (scripts/update-guard.sh ③)に "*" を足されて広がることがある。
|
|
27
|
+
* ・★止める側の効力は deniedDomains が持つ。ここが本当の栓になる。
|
|
28
|
+
* =「通す物」より「止める物」を正確に書くこと。
|
|
29
|
+
*/
|
|
30
|
+
import { readFileSync } from 'node:fs';
|
|
31
|
+
import { fileURLToPath } from 'node:url';
|
|
32
|
+
/** 試作の口(ai-kana の試作用API)。★ここだけは通す。 */
|
|
33
|
+
export const PROTO_ENDPOINT_HOST = 'ai-kana.ty-hld.com';
|
|
34
|
+
/** 試作の口のパス。接続先の判定はホスト単位なので、パスは deny 側(コマンド)で守る。 */
|
|
35
|
+
export const PROTO_ENDPOINT_PATH = '/api/proto/';
|
|
36
|
+
export const PROTO_ENDPOINT = `https://${PROTO_ENDPOINT_HOST}${PROTO_ENDPOINT_PATH}`;
|
|
37
|
+
/**
|
|
38
|
+
* ★試作の現場から締め出す物の正本(これがこのファイルの本体)。
|
|
39
|
+
* 足すときはここに1行足すだけでよい。deny も deniedDomains も自動で付いてくる。
|
|
40
|
+
*/
|
|
41
|
+
export const PROTO_FORBIDDEN = [
|
|
42
|
+
{
|
|
43
|
+
key: 'db',
|
|
44
|
+
reason: '本番の記録(データベース)に直接つなぐ・作り替える(試作リポは鍵もDDLも持たない)',
|
|
45
|
+
commands: [
|
|
46
|
+
'psql',
|
|
47
|
+
'pg_dump',
|
|
48
|
+
'pg_restore',
|
|
49
|
+
'mysql',
|
|
50
|
+
'mysqldump',
|
|
51
|
+
'supabase db push',
|
|
52
|
+
'supabase db reset',
|
|
53
|
+
'supabase db dump',
|
|
54
|
+
'supabase migration up',
|
|
55
|
+
'supabase link',
|
|
56
|
+
'npx supabase db push',
|
|
57
|
+
'npx supabase db reset',
|
|
58
|
+
'npx supabase db dump',
|
|
59
|
+
'npx supabase migration up',
|
|
60
|
+
'npx supabase link',
|
|
61
|
+
'prisma migrate deploy',
|
|
62
|
+
'prisma migrate reset',
|
|
63
|
+
'prisma db push',
|
|
64
|
+
'prisma db execute',
|
|
65
|
+
'npx prisma migrate deploy',
|
|
66
|
+
'npx prisma migrate reset',
|
|
67
|
+
'npx prisma db push',
|
|
68
|
+
'npx prisma db execute',
|
|
69
|
+
'drizzle-kit push',
|
|
70
|
+
'npx drizzle-kit push',
|
|
71
|
+
],
|
|
72
|
+
domains: [
|
|
73
|
+
// ★ワイルドカードと実ホストの両方を書く。照合の書き方に依らず止めるため
|
|
74
|
+
// (販売用テンプレートも pooler の実ホストを名指ししている)。
|
|
75
|
+
'*.supabase.co',
|
|
76
|
+
'aws-0-ap-northeast-1.pooler.supabase.com',
|
|
77
|
+
'aws-1-ap-northeast-1.pooler.supabase.com',
|
|
78
|
+
],
|
|
79
|
+
urls: [],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
key: 'secret',
|
|
83
|
+
reason: '本番の鍵(secret)を出し入れする・本番へ配る',
|
|
84
|
+
commands: [
|
|
85
|
+
'wrangler secret put',
|
|
86
|
+
'wrangler secret delete',
|
|
87
|
+
'wrangler deploy',
|
|
88
|
+
'npx wrangler secret put',
|
|
89
|
+
'npx wrangler secret delete',
|
|
90
|
+
'npx wrangler deploy',
|
|
91
|
+
'vercel env add',
|
|
92
|
+
'vercel env rm',
|
|
93
|
+
'vercel env pull',
|
|
94
|
+
'npx vercel env add',
|
|
95
|
+
'npx vercel env rm',
|
|
96
|
+
'npx vercel env pull',
|
|
97
|
+
'gh secret set',
|
|
98
|
+
'gh secret delete',
|
|
99
|
+
'gh variable set',
|
|
100
|
+
'supabase secrets set',
|
|
101
|
+
'npx supabase secrets set',
|
|
102
|
+
'fly secrets set',
|
|
103
|
+
'heroku config:set',
|
|
104
|
+
],
|
|
105
|
+
domains: [],
|
|
106
|
+
urls: [],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: 'send',
|
|
110
|
+
reason: 'お客さまへ本当に届いてしまう送信(LINE・メール・SMS)',
|
|
111
|
+
commands: [],
|
|
112
|
+
domains: [
|
|
113
|
+
'api.line.me',
|
|
114
|
+
'api.sendgrid.com',
|
|
115
|
+
'api.resend.com',
|
|
116
|
+
'api.twilio.com',
|
|
117
|
+
'api.postmarkapp.com',
|
|
118
|
+
],
|
|
119
|
+
urls: [],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
key: 'billing',
|
|
123
|
+
reason: '実際にお金が動く課金(本番の Stripe)',
|
|
124
|
+
commands: [
|
|
125
|
+
'stripe trigger',
|
|
126
|
+
'stripe payment_intents create',
|
|
127
|
+
'stripe charges create',
|
|
128
|
+
'stripe subscriptions create',
|
|
129
|
+
],
|
|
130
|
+
domains: ['api.stripe.com'],
|
|
131
|
+
// 本番鍵(sk_live_ / pk_live_)を使う呼び出しは、どの道具から出ても止める。
|
|
132
|
+
urls: ['sk_live_', 'pk_live_', 'rk_live_'],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
key: 'production',
|
|
136
|
+
reason: '本番の入口(ty-hld.com)— ★試作の口(/api/proto/*)以外は塞ぐ',
|
|
137
|
+
commands: [],
|
|
138
|
+
// ★ここに "*.ty-hld.com" と書いてはいけない。deniedDomains は allowedDomains より
|
|
139
|
+
// 先に効くので、試作の口(ai-kana.ty-hld.com)まで一緒に塞がってしまう。
|
|
140
|
+
// だから「試作の口以外の本番ホスト」を名指しで書く(下の点検関数が取り違えを止める)。
|
|
141
|
+
domains: ['ty-hld.com', 'www.ty-hld.com'],
|
|
142
|
+
// 試作の口と同じホストにある【本番の口】。接続先の判定はホスト単位なので、
|
|
143
|
+
// ここだけはコマンド側(curl/wget の deny)でパスごと塞ぐ。
|
|
144
|
+
urls: [
|
|
145
|
+
`${PROTO_ENDPOINT_HOST}/api/conductor`,
|
|
146
|
+
`${PROTO_ENDPOINT_HOST}/api/admin`,
|
|
147
|
+
`${PROTO_ENDPOINT_HOST}/api/auth`,
|
|
148
|
+
`${PROTO_ENDPOINT_HOST}/api/devlog`,
|
|
149
|
+
`${PROTO_ENDPOINT_HOST}/api/stripe`,
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
/**
|
|
154
|
+
* ★試作の現場で通す物の正本。
|
|
155
|
+
* (allowedDomains は「先に効く deniedDomains」の後ろで判定される=ここを広げても
|
|
156
|
+
* 上の禁止は破れない。逆にここが狭すぎると職人が黙って 403 で止まるので、
|
|
157
|
+
* 試作に本当に要る4つだけを書く。)
|
|
158
|
+
*/
|
|
159
|
+
export const PROTO_ALLOWED = [
|
|
160
|
+
{
|
|
161
|
+
key: 'proto-endpoint',
|
|
162
|
+
reason: `試作の口(${PROTO_ENDPOINT})`,
|
|
163
|
+
domains: [PROTO_ENDPOINT_HOST],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
key: 'github',
|
|
167
|
+
reason: 'ソースの置き場(GitHub)',
|
|
168
|
+
domains: ['github.com', 'api.github.com', 'codeload.github.com', 'objects.githubusercontent.com'],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
key: 'npm',
|
|
172
|
+
reason: '部品の取り寄せ先(npm)',
|
|
173
|
+
domains: ['registry.npmjs.org'],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
key: 'vercel',
|
|
177
|
+
reason: '試作の配備先(Vercel・テスト鍵のみ)',
|
|
178
|
+
domains: ['api.vercel.com', 'vercel.com'],
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
/** 試作の現場に置く .env の見本。★本番の鍵の名前を1つも書かない。 */
|
|
182
|
+
export const PROTO_ENV_EXAMPLE = [
|
|
183
|
+
'# 試作(proto)の現場の環境変数の見本です。',
|
|
184
|
+
'#',
|
|
185
|
+
'# ★ここに書いてよいのは、試作の口につなぐための2つだけです。',
|
|
186
|
+
'# 本番のデータベース・送信・課金の鍵は、試作の現場には置きません(置かないから漏れません)。',
|
|
187
|
+
'#',
|
|
188
|
+
'# 使い方: このファイルを .env という名前でコピーし、PROTO_APP_KEY に合言葉を入れてください。',
|
|
189
|
+
'# 合言葉は人が入れます(AI は .env を書けません)。',
|
|
190
|
+
'',
|
|
191
|
+
`PROTO_API_BASE=${PROTO_ENDPOINT.replace(/\/$/, '')}`,
|
|
192
|
+
'PROTO_APP_KEY=',
|
|
193
|
+
'',
|
|
194
|
+
].join('\n');
|
|
195
|
+
/** 重複を作らず、書いた順を保つ。 */
|
|
196
|
+
function dedupe(items) {
|
|
197
|
+
const seen = new Set();
|
|
198
|
+
const out = [];
|
|
199
|
+
for (const x of items) {
|
|
200
|
+
if (seen.has(x))
|
|
201
|
+
continue;
|
|
202
|
+
seen.add(x);
|
|
203
|
+
out.push(x);
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
/** 接続先の型 `pattern` がホスト `host` に当たるか("*" と "*.example.com" に対応)。 */
|
|
208
|
+
export function domainMatches(pattern, host) {
|
|
209
|
+
if (pattern === '*')
|
|
210
|
+
return true;
|
|
211
|
+
if (pattern === host)
|
|
212
|
+
return true;
|
|
213
|
+
if (pattern.startsWith('*.'))
|
|
214
|
+
return host.endsWith(pattern.slice(1));
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
/** 止める接続先(sandbox.network.deniedDomains)。正本から生成する。 */
|
|
218
|
+
export function buildProtoDeniedDomains() {
|
|
219
|
+
const domains = dedupe(PROTO_FORBIDDEN.flatMap((r) => [...r.domains]));
|
|
220
|
+
// ★自己点検: 試作の口を自分で塞いでいないか。deniedDomains は allowedDomains より
|
|
221
|
+
// 先に効くので、ここに当たる型を1つでも書くと試作の現場が黙って止まる。
|
|
222
|
+
for (const d of domains) {
|
|
223
|
+
if (domainMatches(d, PROTO_ENDPOINT_HOST)) {
|
|
224
|
+
throw new Error(`試作の口 ${PROTO_ENDPOINT_HOST} を塞ぐ指定が禁止リストに入っています: "${d}"。` +
|
|
225
|
+
'(deniedDomains は allowedDomains より先に効くため、試作の現場が動かなくなります)');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return domains;
|
|
229
|
+
}
|
|
230
|
+
/** 通す接続先(sandbox.network.allowedDomains)。正本から生成する。 */
|
|
231
|
+
export function buildProtoAllowedDomains() {
|
|
232
|
+
const allowed = dedupe(PROTO_ALLOWED.flatMap((g) => [...g.domains]));
|
|
233
|
+
const denied = buildProtoDeniedDomains();
|
|
234
|
+
for (const a of allowed) {
|
|
235
|
+
const hit = denied.find((d) => domainMatches(d, a));
|
|
236
|
+
if (hit) {
|
|
237
|
+
throw new Error(`通す接続先 "${a}" を禁止リスト "${hit}" が先に塞いでいます(矛盾)。`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return allowed;
|
|
241
|
+
}
|
|
242
|
+
/** 止めるコマンド(permissions.deny のうち試作の型で足す分)。正本から生成する。 */
|
|
243
|
+
export function buildProtoDenyRules() {
|
|
244
|
+
const out = [];
|
|
245
|
+
for (const rule of PROTO_FORBIDDEN) {
|
|
246
|
+
for (const cmd of rule.commands)
|
|
247
|
+
out.push(`Bash(${cmd}:*)`);
|
|
248
|
+
for (const d of rule.domains) {
|
|
249
|
+
out.push(`Bash(curl *${d}*)`);
|
|
250
|
+
out.push(`Bash(wget *${d}*)`);
|
|
251
|
+
// WebFetch の deny はホスト名そのものを取る(型(*.)は取らないので出さない)。
|
|
252
|
+
if (!d.startsWith('*.'))
|
|
253
|
+
out.push(`WebFetch(domain:${d})`);
|
|
254
|
+
}
|
|
255
|
+
for (const u of rule.urls) {
|
|
256
|
+
out.push(`Bash(curl *${u}*)`);
|
|
257
|
+
out.push(`Bash(wget *${u}*)`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return dedupe(out);
|
|
261
|
+
}
|
|
262
|
+
/** 販売用テンプレート(既定の型)の場所。★読むだけ・1文字も書き換えない。 */
|
|
263
|
+
function baseSettingsPath() {
|
|
264
|
+
return fileURLToPath(new URL('../sales-template/settings.json', import.meta.url));
|
|
265
|
+
}
|
|
266
|
+
/** 既定の型の settings.json を読む(deny の「床」をそのまま引き継ぐため)。 */
|
|
267
|
+
export function readBaseSettings() {
|
|
268
|
+
return JSON.parse(readFileSync(baseSettingsPath(), 'utf8'));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* 試作の型の現場設定(.claude/settings.json)を組み立てる。
|
|
272
|
+
* ★既定の型の deny(.env/.ssh/鍵/本番DB書込などの「床」)はそのまま引き継ぎ、その上に足す。
|
|
273
|
+
* =試作の型は既定より必ず厳しい。緩む方向には決してならない。
|
|
274
|
+
*/
|
|
275
|
+
export function buildProtoSettings(base = readBaseSettings()) {
|
|
276
|
+
const baseDeny = Array.isArray(base?.permissions?.deny) ? base.permissions.deny : [];
|
|
277
|
+
const deny = dedupe([...baseDeny, ...buildProtoDenyRules()]);
|
|
278
|
+
return {
|
|
279
|
+
_note: '★試作(proto)の型 — 采配くんが生成した現場設定です。手で書き換えないでください。' +
|
|
280
|
+
'正本は @tyhld/conductor の src/proto-profile.ts で、この deny と sandbox.network は' +
|
|
281
|
+
'そこから機械的に作られます(写しを2か所に持たない)。作り直しは ' +
|
|
282
|
+
'conductor setup <現場名> --profile proto です。',
|
|
283
|
+
_note_why: '★試作の現場は「作って見せる」ための場所で、本番の記録(DB)も本番の鍵も持ちません。' +
|
|
284
|
+
'持たない物に触る操作は、はじめから出来なくしてあります(=人に確認を求める操作が' +
|
|
285
|
+
'試作環境に残らない=端末に人が居なくても職人が止まりません)。',
|
|
286
|
+
_note_guard: '★番人(~/.tyhld/hooks の cc2_guard.py ほか)は既定の型と同じものが1台に1本だけ動きます。' +
|
|
287
|
+
'試作の型のために番人を増やしてはいません。ここにあるのは権限ルール(deny)と接続先だけです。',
|
|
288
|
+
_note_order: '★判定の順: deniedDomains → allowedDomains。止める側が必ず先に効くので、あとから' +
|
|
289
|
+
'許可の一覧に "*" が足されても、下の禁止(本番DB・送信・本番課金・本番の入口)は効いたままです。',
|
|
290
|
+
_note_blocked: PROTO_FORBIDDEN.map((r) => `${r.key}: ${r.reason}` +
|
|
291
|
+
`(コマンド ${r.commands.length}件 / 接続先 ${r.domains.length}件 / URL ${r.urls.length}件)`),
|
|
292
|
+
_note_allowed: PROTO_ALLOWED.map((g) => `${g.key}: ${g.reason}(${g.domains.join(' , ')})`),
|
|
293
|
+
permissions: {
|
|
294
|
+
defaultMode: 'default',
|
|
295
|
+
disableBypassPermissionsMode: 'disable',
|
|
296
|
+
allow: [],
|
|
297
|
+
ask: [],
|
|
298
|
+
deny,
|
|
299
|
+
},
|
|
300
|
+
sandbox: {
|
|
301
|
+
enabled: true,
|
|
302
|
+
failIfUnavailable: true,
|
|
303
|
+
autoAllowBashIfSandboxed: true,
|
|
304
|
+
allowUnsandboxedCommands: false,
|
|
305
|
+
network: {
|
|
306
|
+
allowedDomains: buildProtoAllowedDomains(),
|
|
307
|
+
deniedDomains: buildProtoDeniedDomains(),
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/** 現場に置くファイルの中身(文字列)を返す。 */
|
|
313
|
+
export function renderProtoSettings() {
|
|
314
|
+
return `${JSON.stringify(buildProtoSettings(), null, 2)}\n`;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* ★このファイルを直接実行したときだけ、生成物を標準出力へ出す(setup.sh から使う)。
|
|
318
|
+
* 使い方: node dist/proto-profile.js settings | env
|
|
319
|
+
* ★setup.sh に一覧を直書きさせないための入口。判断はすべてこのファイル(正本)が持つ。
|
|
320
|
+
*/
|
|
321
|
+
function runAsScript() {
|
|
322
|
+
const what = process.argv[2] ?? '';
|
|
323
|
+
if (what === 'settings') {
|
|
324
|
+
process.stdout.write(renderProtoSettings());
|
|
325
|
+
}
|
|
326
|
+
else if (what === 'env') {
|
|
327
|
+
process.stdout.write(PROTO_ENV_EXAMPLE);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
console.error('使い方: node proto-profile.js <settings|env>');
|
|
331
|
+
process.exit(2);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
335
|
+
runAsScript();
|
|
336
|
+
}
|
package/package.json
CHANGED
package/sales-template/README.md
CHANGED
|
@@ -16,6 +16,35 @@ conductor setup my-app # 2) 設置する(現場名を指定)
|
|
|
16
16
|
- 設置のあとに聞かれるのは **管制のアドレス**と**合言葉**の2つだけ(どちらも弊社からお渡しします)。
|
|
17
17
|
★合言葉は入力しても画面には表示されません。
|
|
18
18
|
|
|
19
|
+
### 設置の型(`--profile`)
|
|
20
|
+
|
|
21
|
+
| 型 | 打ち方 | どんな現場向けか |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `default`(既定) | `conductor setup my-app` | ふつうの現場。従来と同じ設定を置きます。 |
|
|
24
|
+
| `proto`(試作) | `conductor setup my-proto --profile proto` | **試作の現場**。作って見せるための場所。 |
|
|
25
|
+
|
|
26
|
+
**試作(proto)の型で置かれるもの**
|
|
27
|
+
|
|
28
|
+
- **止める物**(本番へ手が届かないようにする)
|
|
29
|
+
- 本番の記録(データベース)につなぐ・作り替える(`psql` / `supabase db push` / `prisma migrate` ほか)
|
|
30
|
+
- 本番の鍵の出し入れ(`gh secret set` / `vercel env` / `wrangler secret` ほか)
|
|
31
|
+
- お客さまへ本当に届いてしまう送信(LINE・メール・SMS)
|
|
32
|
+
- 実際にお金が動く課金(本番の Stripe)
|
|
33
|
+
- 本番の入口(`ty-hld.com`)— ★試作の口だけは通します
|
|
34
|
+
- **通す物**:試作の口(`ai-kana.ty-hld.com` の `/api/proto/*`)・GitHub・npm・Vercel(テスト鍵)
|
|
35
|
+
- **`.env` の見本**:試作の口につなぐ2項目だけ。本番の鍵の名前は1つも書かれていません
|
|
36
|
+
(合言葉は空のまま置かれるので、人が入れてください)
|
|
37
|
+
|
|
38
|
+
> ★**止める物・通す物の正本は1ファイル**(`src/proto-profile.ts`)です。`.claude/settings.json` の
|
|
39
|
+
> 禁止(`deny`)も接続先(`deniedDomains`)も、そこから機械的に作られます。同じ一覧を2か所で
|
|
40
|
+
> 持たないので、「片方に足してもう片方を忘れる」という穴が構造的に起きません。
|
|
41
|
+
>
|
|
42
|
+
> ★**番人は増えません**。この型が使うのは既存の仕組み(権限ルールとサンドボックス)だけで、
|
|
43
|
+
> 安全装置(`cc2_guard.py` ほか)は1台に1本のまま・中身も変わりません。
|
|
44
|
+
>
|
|
45
|
+
> ★`curl … | bash -s <現場名>` の入口では `--profile` は指定できません(黙って無視せず、
|
|
46
|
+
> 正しい打ち方を出して止まります)。試作の現場は上の2行で作ってください。
|
|
47
|
+
|
|
19
48
|
<details><summary>ファイルを直接指定して実行する場合(上級者向け)</summary>
|
|
20
49
|
|
|
21
50
|
```bash
|
|
@@ -47,7 +76,7 @@ bash "$(npm root -g)/@tyhld/conductor/sales-template/setup.sh" my-app /path/to/m
|
|
|
47
76
|
### setup.sh がやること
|
|
48
77
|
|
|
49
78
|
```
|
|
50
|
-
bash setup.sh <プロジェクト名> [プロジェクトディレクトリ]
|
|
79
|
+
bash setup.sh <プロジェクト名> [プロジェクトディレクトリ] [--profile <default|proto>]
|
|
51
80
|
|
|
52
81
|
① 前提チェック (node/tmux/git/python3/systemd)
|
|
53
82
|
② conductor 本体インストール (npm i -g @tyhld/conductor)
|
|
@@ -330,6 +330,22 @@ USAGE
|
|
|
330
330
|
exit 1
|
|
331
331
|
fi
|
|
332
332
|
|
|
333
|
+
# ★設置の型(--profile)はこの入口では受け取らない。黙って無視すると「指定したつもりで
|
|
334
|
+
# ふつうの現場が出来ている」という静かな間違いになるので、その場で止めて正しい打ち方を出す。
|
|
335
|
+
if [[ "${2:-}" == --profile* ]]; then
|
|
336
|
+
cat >&2 <<USAGE
|
|
337
|
+
|
|
338
|
+
この入口(curl … | bash -s ${SITE})では、設置の型(--profile)は指定できません。
|
|
339
|
+
|
|
340
|
+
試作の現場を作るときは、先に采配くんを入れてから次のように打ってください:
|
|
341
|
+
|
|
342
|
+
npm i -g @tyhld/conductor
|
|
343
|
+
conductor setup ${SITE} --profile proto
|
|
344
|
+
|
|
345
|
+
USAGE
|
|
346
|
+
exit 1
|
|
347
|
+
fi
|
|
348
|
+
|
|
333
349
|
say " お仕事の名前: ${SITE}"
|
|
334
350
|
PROJECT_DIR="$HOME/projects/$SITE"
|
|
335
351
|
say " 作業フォルダ: ${PROJECT_DIR}"
|
package/sales-template/setup.sh
CHANGED
|
@@ -3,11 +3,19 @@
|
|
|
3
3
|
# setup.sh — 采配くん一式を顧客PCに1コマンドでインストールする統合インストーラ。
|
|
4
4
|
#
|
|
5
5
|
# 使い方:
|
|
6
|
-
# bash setup.sh <プロジェクト名> [プロジェクトディレクトリ]
|
|
6
|
+
# bash setup.sh <プロジェクト名> [プロジェクトディレクトリ] [--profile <default|proto>]
|
|
7
7
|
#
|
|
8
8
|
# 例:
|
|
9
9
|
# bash setup.sh my-app # ~/projects/my-app を想定
|
|
10
10
|
# bash setup.sh my-app /home/user/work/my-app # ディレクトリを明示指定
|
|
11
|
+
# bash setup.sh my-proto --profile proto # ★試作の現場(下の「設置の型」を参照)
|
|
12
|
+
#
|
|
13
|
+
# 設置の型(--profile・既定は default=従来と1文字も変わらない):
|
|
14
|
+
# default … ふつうの現場。同梱の settings.json をそのまま置く。
|
|
15
|
+
# proto … ★試作の現場。本番の記録(DB)・鍵・送信・課金・本番の入口を締め出した設定を
|
|
16
|
+
# その場で作って置き、.env の見本(試作の口の2項目だけ)も置く。
|
|
17
|
+
# ★締め出す物の正本は1ファイル(src/proto-profile.ts=dist/proto-profile.js)で、
|
|
18
|
+
# deny も deniedDomains もそこから生成する(写しを2か所に持たない)。
|
|
11
19
|
#
|
|
12
20
|
# やること(順番に・冪等・失敗時は止めて報告):
|
|
13
21
|
# ① 前提チェック(node>=18 / tmux / git / python3>=3.8 / systemd / sudo / ★Claude Code)
|
|
@@ -30,20 +38,52 @@ set -euo pipefail
|
|
|
30
38
|
|
|
31
39
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
40
|
|
|
41
|
+
# ─── 設置の型(プロファイル)を先に取り分ける ────────────────────────────────
|
|
42
|
+
# 【なぜ要るか】試作の現場(例 ai-kana-proto)は本番の記録(DB)も本番の鍵も持たない。
|
|
43
|
+
# 持たない物に触る操作は、はじめから出来なくしておく=「確認が要る操作を試作環境から排除する」。
|
|
44
|
+
# ★既定は default。--profile を書かないときの動きは【1文字も変わらない】。
|
|
45
|
+
# ★何を締め出すかの一覧はここには書かない。正本は @tyhld/conductor の src/proto-profile.ts。
|
|
46
|
+
PROFILE="default"
|
|
47
|
+
_ARGS=()
|
|
48
|
+
while [[ $# -gt 0 ]]; do
|
|
49
|
+
case "$1" in
|
|
50
|
+
--profile)
|
|
51
|
+
PROFILE="${2:-}"
|
|
52
|
+
shift 2 || shift
|
|
53
|
+
;;
|
|
54
|
+
--profile=*)
|
|
55
|
+
PROFILE="${1#*=}"
|
|
56
|
+
shift
|
|
57
|
+
;;
|
|
58
|
+
*)
|
|
59
|
+
_ARGS+=("$1")
|
|
60
|
+
shift
|
|
61
|
+
;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
# ★bash 3.2(Mac 標準)+ set -u では空配列の展開が落ちるので +演算子で守る。
|
|
65
|
+
set -- ${_ARGS[@]+"${_ARGS[@]}"}
|
|
66
|
+
|
|
33
67
|
# ─── 引数チェック ─────────────────────────────────────────────────────────────
|
|
34
68
|
if [[ $# -lt 1 || -z "${1:-}" ]]; then
|
|
35
69
|
cat >&2 <<'USAGE'
|
|
36
70
|
采配くんセットアップ
|
|
37
71
|
|
|
38
|
-
使い方: bash setup.sh <プロジェクト名> [プロジェクトディレクトリ]
|
|
72
|
+
使い方: bash setup.sh <プロジェクト名> [プロジェクトディレクトリ] [--profile <default|proto>]
|
|
39
73
|
|
|
40
74
|
例:
|
|
41
75
|
bash setup.sh my-app # ~/projects/my-app を想定
|
|
42
76
|
bash setup.sh my-app /home/user/work/my-app # ディレクトリを明示指定
|
|
77
|
+
bash setup.sh my-proto --profile proto # ★試作の現場(本番DB・鍵・送信・課金を締め出す)
|
|
43
78
|
USAGE
|
|
44
79
|
exit 1
|
|
45
80
|
fi
|
|
46
81
|
|
|
82
|
+
if [[ "$PROFILE" != "default" && "$PROFILE" != "proto" ]]; then
|
|
83
|
+
echo "[setup] エラー: --profile は default か proto を指定してください(指定値: \"$PROFILE\")" >&2
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
|
|
47
87
|
SITE="$1"
|
|
48
88
|
PROJECT_DIR="${2:-$HOME/projects/$SITE}"
|
|
49
89
|
|
|
@@ -651,8 +691,26 @@ fi
|
|
|
651
691
|
# --- 現場の権限ルール settings.json(番人フックは書かない) ---
|
|
652
692
|
# ★ここに置くのは「その現場固有の権限ルール(deny・Supabase 等の sandbox)」だけ。
|
|
653
693
|
# 番人フックは上でホームに1本化済み。現場側には hooks を書かない(二重番人を作らない)。
|
|
694
|
+
#
|
|
695
|
+
# ★試作(proto)の型のときは、置く中身を【その場で作る】。
|
|
696
|
+
# 締め出す物の正本は @tyhld/conductor の src/proto-profile.ts(=dist/proto-profile.js)1本で、
|
|
697
|
+
# deny も deniedDomains もそこから生成される。ここに一覧を写さない(写しを2か所に持たない=
|
|
698
|
+
# 片方に足してもう片方を忘れる事故を構造的に起こせなくする)。
|
|
654
699
|
SETTINGS_SRC="$SCRIPT_DIR/settings.json"
|
|
655
700
|
SETTINGS_DST="$CLAUDE_DIR/settings.json"
|
|
701
|
+
PROTO_GEN="$SCRIPT_DIR/../dist/proto-profile.js"
|
|
702
|
+
if [[ "$PROFILE" == "proto" ]]; then
|
|
703
|
+
[[ -f "$PROTO_GEN" ]] || fail "試作の型の正本が見つかりません: $PROTO_GEN
|
|
704
|
+
配布物が壊れている可能性があります。次の1行で入れ直してください:
|
|
705
|
+
npm i -g @tyhld/conductor"
|
|
706
|
+
PROTO_SETTINGS="$CLAUDE_DIR/.settings.proto.$$"
|
|
707
|
+
node "$PROTO_GEN" settings > "$PROTO_SETTINGS" \
|
|
708
|
+
|| fail "試作の型の設定を作れませんでした(node \"$PROTO_GEN\" settings)"
|
|
709
|
+
[[ -s "$PROTO_SETTINGS" ]] || fail "試作の型の設定が空です(node \"$PROTO_GEN\" settings)"
|
|
710
|
+
trap 'rm -f "$PROTO_SETTINGS"' EXIT
|
|
711
|
+
SETTINGS_SRC="$PROTO_SETTINGS"
|
|
712
|
+
echo "[setup] 型: proto(試作)— 本番の記録(DB)・鍵・送信・課金・本番の入口を締め出した設定を作りました。"
|
|
713
|
+
fi
|
|
656
714
|
if [[ -f "$SETTINGS_SRC" ]]; then
|
|
657
715
|
if [[ -f "$SETTINGS_DST" ]]; then
|
|
658
716
|
echo "[setup] $SETTINGS_DST は既に存在します。上書きしますか?"
|
|
@@ -710,6 +768,30 @@ else
|
|
|
710
768
|
warn "settings.json が見つかりません: $SETTINGS_SRC(スキップ)"
|
|
711
769
|
fi
|
|
712
770
|
|
|
771
|
+
# --- 試作(proto)の型のときだけ: .env の見本を置く --------------------------
|
|
772
|
+
# ★試作の現場に置くのは「試作の口につなぐ2項目」だけ。本番の鍵の名前は1つも書かない
|
|
773
|
+
# =書く場所が無いから、間違って本番の鍵を置けない。
|
|
774
|
+
# ★合言葉は空のまま置き、人が入れる(AI は .env を書けない=deny)。
|
|
775
|
+
# ★中身(鍵の名前を含む)も正本(proto-profile.js)が持つ。ここには1行も書かない。
|
|
776
|
+
if [[ "$PROFILE" == "proto" ]]; then
|
|
777
|
+
ENV_EXAMPLE_DST="$PROJECT_DIR/.env.example"
|
|
778
|
+
write_env_example=1
|
|
779
|
+
if [[ -f "$ENV_EXAMPLE_DST" ]]; then
|
|
780
|
+
echo "[setup] $ENV_EXAMPLE_DST は既に存在します。上書きしますか?"
|
|
781
|
+
read -r -p " 上書き (y/N): " overwrite_env
|
|
782
|
+
[[ "$(lower "$overwrite_env")" == "y" ]] || write_env_example=0
|
|
783
|
+
fi
|
|
784
|
+
if [[ "$write_env_example" == "1" ]]; then
|
|
785
|
+
if node "$PROTO_GEN" env > "$ENV_EXAMPLE_DST"; then
|
|
786
|
+
echo "[setup] 配置: $ENV_EXAMPLE_DST(試作の口の2項目だけ)"
|
|
787
|
+
else
|
|
788
|
+
warn ".env の見本を作れませんでした(node \"$PROTO_GEN\" env)"
|
|
789
|
+
fi
|
|
790
|
+
else
|
|
791
|
+
echo "[setup] .env の見本はそのままにしました。"
|
|
792
|
+
fi
|
|
793
|
+
fi
|
|
794
|
+
|
|
713
795
|
# --- 職人の鉄則スキル conductor-craftsman を配置 ------------------------------
|
|
714
796
|
# ★番人(安全装置)が「やらせない」側なら、こちらは職人自身が「そう動く」側。
|
|
715
797
|
# 便(CONDUCTOR_JOB 印の付いた作業指示)を受けた職人が最初に読む作業規律を、
|
|
@@ -933,6 +1015,22 @@ if command -v claude >/dev/null 2>&1; then
|
|
|
933
1015
|
else
|
|
934
1016
|
mark ng "職人(Claude Code)が使える" "npm i -g @anthropic-ai/claude-code を実行してください"
|
|
935
1017
|
fi
|
|
1018
|
+
# ★試作の型のときだけ増える点検。「締め出しが本当に置かれたか」を字で確かめる。
|
|
1019
|
+
if [[ "$PROFILE" == "proto" ]]; then
|
|
1020
|
+
PROTO_RETRY="conductor setup ${SITE} --profile proto をもう一度実行してください"
|
|
1021
|
+
# ★何が締め出されているかの一覧はここで持たない(正本は1ファイル)。
|
|
1022
|
+
# 「正本から作った物」と「実際に置いた物」が同じかどうかだけを見る。
|
|
1023
|
+
if [[ -f "$SETTINGS_DST" ]] && cmp -s "$PROTO_SETTINGS" "$SETTINGS_DST"; then
|
|
1024
|
+
mark ok "試作の型: 本番の記録(DB)・鍵・送信・課金・本番の入口を締め出してある" ""
|
|
1025
|
+
else
|
|
1026
|
+
mark ng "試作の型: 本番の記録(DB)・鍵・送信・課金・本番の入口を締め出してある" "$PROTO_RETRY"
|
|
1027
|
+
fi
|
|
1028
|
+
if [[ -f "$PROJECT_DIR/.env.example" ]]; then
|
|
1029
|
+
mark ok "試作の型: .env の見本(試作の口の2項目だけ)がある" ""
|
|
1030
|
+
else
|
|
1031
|
+
mark ng "試作の型: .env の見本(試作の口の2項目だけ)がある" "$PROTO_RETRY"
|
|
1032
|
+
fi
|
|
1033
|
+
fi
|
|
936
1034
|
|
|
937
1035
|
echo
|
|
938
1036
|
if [[ ${#ng_lines[@]} -eq 0 ]]; then
|