@unson/brainbase-mcp 0.1.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.
@@ -0,0 +1,97 @@
1
+ import { type SourceArea, type SourceDiagnosis } from './onboarding.js';
2
+ import { type OperationalizationPlan } from './operationalization.js';
3
+ import { type ProjectRegistrationInput } from './projects.js';
4
+ export type GuidedTarget = 'codex' | 'claude' | 'codecode';
5
+ export type GuidedFormat = 'markdown' | 'json';
6
+ export interface GuidedFirstRunInput {
7
+ dataDir: string;
8
+ target: GuidedTarget;
9
+ profile?: string;
10
+ host?: string;
11
+ name?: string;
12
+ value?: string[];
13
+ email?: string;
14
+ secondaryEmails?: string[];
15
+ calendar?: string;
16
+ drive?: string;
17
+ driveFolders?: string[];
18
+ localFolders?: string[];
19
+ tasks?: string;
20
+ inactiveTaskTools?: string[];
21
+ gogCommand?: string;
22
+ assumeGog?: boolean;
23
+ gogAvailable?: boolean;
24
+ project?: ProjectRegistrationInput;
25
+ missing?: string[];
26
+ connected?: boolean;
27
+ }
28
+ export interface GuidedInterviewSection {
29
+ id: string;
30
+ title: string;
31
+ questions: string[];
32
+ }
33
+ export interface GuidedSourceReadiness {
34
+ area: SourceArea;
35
+ title: string;
36
+ input: string;
37
+ status: SourceDiagnosis['status'];
38
+ statusLabel: string;
39
+ requiredUserInput: string[];
40
+ setupCommands: string[];
41
+ safetyNotes: string[];
42
+ }
43
+ export interface GuidedCommand {
44
+ id: string;
45
+ title: string;
46
+ when: string;
47
+ command: string;
48
+ }
49
+ export interface GuidedFirstRun {
50
+ goal: string;
51
+ language: 'ja';
52
+ profile: string;
53
+ target: GuidedTarget;
54
+ dataDir: string;
55
+ initialized: {
56
+ personalOs: true;
57
+ canonicalFactWrites: false;
58
+ note: string;
59
+ };
60
+ currentStatus: {
61
+ connected: boolean;
62
+ missing: string[];
63
+ };
64
+ firstValueExperience: {
65
+ title: string;
66
+ tryPrompt: string;
67
+ expectedValue: string;
68
+ sampleResult: string;
69
+ };
70
+ operationalization: OperationalizationPlan;
71
+ interview: GuidedInterviewSection[];
72
+ answers: {
73
+ selfName?: string;
74
+ values: string[];
75
+ projectName?: string;
76
+ email: string;
77
+ secondaryEmails: string[];
78
+ calendar: string;
79
+ drive: string;
80
+ driveFolders: string[];
81
+ localFolders: string[];
82
+ tasks: string;
83
+ inactiveTaskTools: string[];
84
+ };
85
+ sourceReadiness: GuidedSourceReadiness[];
86
+ projectRegistration: {
87
+ available: boolean;
88
+ dryRunCommand?: string;
89
+ writeCommand?: string;
90
+ approvalNote: string;
91
+ };
92
+ nextCommands: GuidedCommand[];
93
+ approvalGates: string[];
94
+ completionCheck: string[];
95
+ }
96
+ export declare function buildGuidedFirstRun(input: GuidedFirstRunInput): GuidedFirstRun;
97
+ export declare function renderGuidedFirstRun(input: GuidedFirstRunInput, format: GuidedFormat): string;
@@ -0,0 +1,490 @@
1
+ import { buildSourceDiagnosis } from './onboarding.js';
2
+ import { buildOperationalizationPlan } from './operationalization.js';
3
+ import { buildProjectRegistrationPlan } from './projects.js';
4
+ export function buildGuidedFirstRun(input) {
5
+ const profile = input.profile || 'google-workspace-local';
6
+ const email = normalize(input.email || 'google-workspace');
7
+ const calendar = normalize(input.calendar || 'google-calendar');
8
+ const drive = normalize(input.drive || 'google-drive');
9
+ const tasks = normalize(input.tasks || 'scattered-calendar-notes');
10
+ const secondaryEmails = input.secondaryEmails ?? [];
11
+ const driveFolders = input.driveFolders ?? [];
12
+ const localFolders = input.localFolders ?? [];
13
+ const values = input.value ?? [];
14
+ const inactiveTaskTools = input.inactiveTaskTools ?? [];
15
+ const sourceSet = buildSourceDiagnosis({
16
+ dataDir: input.dataDir,
17
+ email,
18
+ calendar,
19
+ drive,
20
+ tasks,
21
+ gogCommand: input.gogCommand,
22
+ gogAvailable: input.gogAvailable,
23
+ driveFolders
24
+ });
25
+ const project = input.project?.name
26
+ ? buildProjectRegistrationPlan({
27
+ ...input.project,
28
+ sources: normalizeProjectSources([
29
+ ...(input.project.sources ?? []),
30
+ ...deriveProjectSources({ email, calendar, drive, tasks, driveFolders, localFolders })
31
+ ])
32
+ })
33
+ : undefined;
34
+ const diagnoseCommand = command([
35
+ 'brainbase',
36
+ 'onboard:diagnose-sources',
37
+ '--dir', input.dataDir,
38
+ '--email', email,
39
+ '--calendar', calendar,
40
+ '--drive', drive,
41
+ ...driveFolders.flatMap((folder) => ['--drive-folder', folder]),
42
+ '--tasks', tasks,
43
+ ...(input.gogCommand ? ['--gog-command', input.gogCommand] : []),
44
+ ...(input.assumeGog ? ['--assume-gog'] : [])
45
+ ]);
46
+ const firstRelationship = firstProjectStakeholder(input.project);
47
+ const firstRelationshipArg = firstRelationship
48
+ ? `${firstRelationship.person}|${firstRelationship.role ?? ''}|${firstRelationship.context}`
49
+ : '<関係者>|<役割>|<AIに覚えてほしい文脈>';
50
+ const firstValueScenario = buildFirstValueScenario(input.project?.name, firstRelationship?.person);
51
+ const firstValueExperience = buildFirstValueExperience(input.project?.name, firstRelationship?.person);
52
+ const operationalization = buildOperationalizationPlan({
53
+ target: input.target,
54
+ dataDir: input.dataDir,
55
+ firstValueReady: false
56
+ });
57
+ const selfSeedCommand = command([
58
+ 'brainbase',
59
+ 'onboard:seed',
60
+ '--dir', input.dataDir,
61
+ '--name', input.name || '<あなたの名前>',
62
+ ...values.flatMap((value) => ['--value', value]),
63
+ '--project', input.project?.name || '<最初に登録するプロジェクト>',
64
+ '--relationship', firstRelationshipArg
65
+ ]);
66
+ const demoCommand = command([
67
+ 'brainbase',
68
+ 'onboard:demo',
69
+ '--dir', input.dataDir,
70
+ '--scenario', firstValueScenario
71
+ ]);
72
+ const projectDryRunCommand = project
73
+ ? projectCommand(input.dataDir, project.project.name, input.project, project.project.sources, false)
74
+ : undefined;
75
+ const projectWriteCommand = project
76
+ ? projectCommand(input.dataDir, project.project.name, input.project, project.project.sources, true)
77
+ : undefined;
78
+ return {
79
+ goal: 'Codex / Claude Code / CodeCode が質問しながら、ローカル Brainbase MCP の最初の価値体験まで日本語で到達できるようにする。',
80
+ language: 'ja',
81
+ profile,
82
+ target: input.target,
83
+ dataDir: input.dataDir,
84
+ initialized: {
85
+ personalOs: true,
86
+ canonicalFactWrites: false,
87
+ note: 'このコマンドはPersonal OSの最小ディレクトリだけを用意します。自己情報、プロジェクト、関係性、判断基準は承認後のコマンドで正本化します。'
88
+ },
89
+ currentStatus: {
90
+ connected: input.connected ?? false,
91
+ missing: input.missing ?? ['self', 'work', 'relationships']
92
+ },
93
+ firstValueExperience,
94
+ operationalization,
95
+ interview: buildJapaneseInterview(),
96
+ answers: {
97
+ selfName: input.name,
98
+ values,
99
+ projectName: input.project?.name,
100
+ email,
101
+ secondaryEmails,
102
+ calendar,
103
+ drive,
104
+ driveFolders,
105
+ localFolders,
106
+ tasks,
107
+ inactiveTaskTools
108
+ },
109
+ sourceReadiness: sourceSet.diagnostics.map(toGuidedSourceReadiness),
110
+ projectRegistration: {
111
+ available: Boolean(project),
112
+ dryRunCommand: projectDryRunCommand,
113
+ writeCommand: projectWriteCommand,
114
+ approvalNote: project
115
+ ? 'まずdry-runでプロジェクト名、目的、状態、自分の役割、関係者、許可ソースを確認し、承認後だけ --write で正本へ入れます。'
116
+ : 'プロジェクト名が未入力です。Codex / Claude Code は最初に「いまBrainbaseに登録したいプロジェクト」を確認してください。'
117
+ },
118
+ nextCommands: [
119
+ {
120
+ id: 'self-seed',
121
+ title: '最初のデモに必要な最小文脈を正本化する',
122
+ when: '名前、価値観、最初のプロジェクト、関係者文脈を本人が確認した後',
123
+ command: selfSeedCommand
124
+ },
125
+ {
126
+ id: 'first-value-demo',
127
+ title: '最初の価値体験を実行する',
128
+ when: '最小文脈のseed直後。接続診断より先に実行する',
129
+ command: demoCommand
130
+ },
131
+ {
132
+ id: 'skills',
133
+ title: '公開オンボーディングskillsを配置する',
134
+ when: 'first value demoを見せた後。agentが次回も同じ運用を辿れるようにする',
135
+ command: operationalization.pending.find((item) => item.id === 'public-skills')?.command ?? 'brainbase onboard:skills --target codex'
136
+ },
137
+ {
138
+ id: 'routines',
139
+ title: 'ohayo / oyasumi / retroを確認付きで登録する',
140
+ when: 'first value demoを見せた後。最初はPAUSEDまたは確認付きで登録する',
141
+ command: operationalization.pending.find((item) => item.id === 'routines')?.command ?? 'brainbase onboard:routines --target codex --cwd <brainbase-checkout>'
142
+ },
143
+ {
144
+ id: 'install',
145
+ title: `${targetLabel(input.target)} 用のMCP設定を実configへmergeする`,
146
+ when: 'first value demoとskills/routines確認後。dry-runをプレビューにして承認後に実configへ反映する',
147
+ command: operationalization.pending.find((item) => item.id === 'mcp-config')?.command ?? command(['brainbase', 'onboard:install', '--target', input.target, '--dir', input.dataDir, '--dry-run'])
148
+ },
149
+ {
150
+ id: 'doctor',
151
+ title: 'doctor と MCP get_context/search で確認する',
152
+ when: 'MCP設定を実configへmergeし、対象エージェントを再起動した後',
153
+ command: operationalization.pending.find((item) => item.id === 'verification')?.command ?? command(['brainbase', 'doctor', '--dir', input.dataDir])
154
+ },
155
+ ...(projectDryRunCommand ? [{
156
+ id: 'project-dry-run',
157
+ title: 'プロジェクト登録内容を確認する',
158
+ when: '最初のデモ後、プロジェクト詳細を追加したい時',
159
+ command: projectDryRunCommand
160
+ }] : []),
161
+ ...(projectWriteCommand ? [{
162
+ id: 'project-write',
163
+ title: '承認済みプロジェクトを正本化する',
164
+ when: 'dry-run結果を本人が承認した後',
165
+ command: projectWriteCommand
166
+ }] : []),
167
+ {
168
+ id: 'source-diagnosis',
169
+ title: 'メール・カレンダー・ドライブ・タスクの接続準備を診断する',
170
+ when: '最初の価値体験の後、追加文脈が必要だと分かった時',
171
+ command: diagnoseCommand
172
+ },
173
+ {
174
+ id: 'candidates',
175
+ title: '候補ファクトをレビュー用に作る',
176
+ when: '最初のデモ後、聞き取り内容をまだ正本化せずレビュー材料にしたい時',
177
+ command: command([
178
+ 'brainbase',
179
+ 'onboard:candidates',
180
+ '--dir', input.dataDir,
181
+ '--write',
182
+ '--name', input.name || '<あなたの名前>',
183
+ '--project', input.project?.name || '<現在のプロジェクト>'
184
+ ])
185
+ }
186
+ ],
187
+ approvalGates: [
188
+ 'OAuth token、password、API key、refresh tokenはチャットへ貼らない。',
189
+ '接続診断や候補JSONを初回オンボーディングの完了扱いにしない。最初の価値体験を先に見る。',
190
+ 'メール、カレンダー、ドライブ、タスクは最初はmetadata-firstで扱う。',
191
+ 'Google Driveとローカルファイルは明示されたfolder allowlistだけを見る。',
192
+ 'sources/ と candidates/ は二次材料であり、本人承認前にMCPの正本文脈へ入れない。',
193
+ 'プロジェクト、関係者、判断基準はdry-run確認後だけ --write または onboard:seed で正本化する。'
194
+ ],
195
+ completionCheck: [
196
+ 'brainbase onboard:demo が、保存済み文脈を使った自然なプロンプトとサンプル回答を返す。',
197
+ 'ready=true / first_value_demo_ready だけで完了扱いにせず、ユーザーが「説明し直さなくてよい」と分かる回答を見る。',
198
+ 'doctor の valueDemo.ready が true になる。',
199
+ '完了報告には、公開skills、ohayo/oyasumi/retro、MCP実config merge、source allowlist/import/candidate review、MCP get_context/search確認の未完了タスクを必ず出す。',
200
+ `${targetLabel(input.target)} のMCP設定に Brainbase が実登録されている。`,
201
+ '最初のプロジェクトが get_context/search で見える。',
202
+ '外部ソースは許可範囲、保存先、正本化前レビューの流れが説明できる。'
203
+ ]
204
+ };
205
+ }
206
+ export function renderGuidedFirstRun(input, format) {
207
+ const guide = buildGuidedFirstRun(input);
208
+ if (format === 'json') {
209
+ return `${JSON.stringify(guide, null, 2)}\n`;
210
+ }
211
+ const lines = [
212
+ '# Brainbase 初回オンボーディング開始',
213
+ '',
214
+ guide.goal,
215
+ '',
216
+ `- 対象エージェント: ${targetLabel(guide.target)}`,
217
+ `- データディレクトリ: \`${guide.dataDir}\``,
218
+ `- 正本ファクト書き込み: ${guide.initialized.canonicalFactWrites ? 'あり' : 'なし'}`,
219
+ `- 現在の不足: ${guide.currentStatus.missing.length > 0 ? guide.currentStatus.missing.join(', ') : 'なし'}`,
220
+ '',
221
+ '## まず試すこと',
222
+ guide.firstValueExperience.title,
223
+ '',
224
+ `- 試すプロンプト: 「${guide.firstValueExperience.tryPrompt}」`,
225
+ `- 期待する価値: ${guide.firstValueExperience.expectedValue}`,
226
+ '',
227
+ '### サンプル回答',
228
+ guide.firstValueExperience.sampleResult,
229
+ '',
230
+ '## まだ残っている運用化',
231
+ guide.operationalization.goal,
232
+ '',
233
+ '### 未完了の次アクション',
234
+ ...guide.operationalization.pending.map((item) => `- ${item.title}: \`${item.command}\` (${item.safety})`),
235
+ '',
236
+ '### 推奨順序',
237
+ ...guide.operationalization.recommendedOrder.map((item, index) => `${index + 1}. ${item}`),
238
+ '',
239
+ '## エージェントの聞き取り',
240
+ ...guide.interview.flatMap((section) => [
241
+ '',
242
+ `### ${section.title}`,
243
+ ...section.questions.map((question) => `- ${question}`)
244
+ ]),
245
+ '',
246
+ '## 接続準備(デモ後の任意ステップ)',
247
+ ...guide.sourceReadiness.flatMap((source) => [
248
+ '',
249
+ `### ${source.title}`,
250
+ `- 入力: ${source.input}`,
251
+ `- 状態: ${source.statusLabel}`,
252
+ '- 追加で必要な確認:',
253
+ ...(source.requiredUserInput.length === 0 ? [' - なし'] : source.requiredUserInput.map((item) => ` - ${item}`)),
254
+ '- 次に実行する候補:',
255
+ ...(source.setupCommands.length === 0 ? [' - なし'] : source.setupCommands.map((item) => ` - \`${item}\``))
256
+ ]),
257
+ '',
258
+ '## プロジェクト登録',
259
+ `- 状態: ${guide.projectRegistration.available ? '登録案あり' : 'プロジェクト名の聞き取り待ち'}`,
260
+ `- 方針: ${guide.projectRegistration.approvalNote}`,
261
+ ...(guide.projectRegistration.dryRunCommand ? [`- 確認コマンド: \`${guide.projectRegistration.dryRunCommand}\``] : []),
262
+ ...(guide.projectRegistration.writeCommand ? [`- 承認後コマンド: \`${guide.projectRegistration.writeCommand}\``] : []),
263
+ '',
264
+ '## 次コマンド',
265
+ ...guide.nextCommands.map((item) => `- ${item.title}: \`${item.command}\` (${item.when})`),
266
+ '',
267
+ '## 承認ルール',
268
+ ...guide.approvalGates.map((item) => `- ${item}`),
269
+ '',
270
+ '## 完了条件',
271
+ ...guide.completionCheck.map((item) => `- ${item}`),
272
+ ''
273
+ ];
274
+ return `${lines.join('\n')}\n`;
275
+ }
276
+ function buildJapaneseInterview() {
277
+ return [
278
+ {
279
+ id: 'value_target',
280
+ title: '最初の価値体験',
281
+ questions: [
282
+ 'Codex / Claude Code に毎回説明し直したくない仕事文脈は何ですか?',
283
+ '今日のオンボーディングで「これは便利」と判断できる実リクエストは何ですか?',
284
+ '最初のデモは仕事前提、関係者、判断基準、プロジェクトのどれを使えれば成功ですか?'
285
+ ]
286
+ },
287
+ {
288
+ id: 'self',
289
+ title: '最小の本人・仕事文脈',
290
+ questions: [
291
+ 'あなたの名前、呼ばれ方、Codex / Claude Code に覚えてほしい仕事上の前提は何ですか?',
292
+ '判断基準、重視する価値観、避けたい進め方は何ですか?',
293
+ '最初のデモに必要な関係者は誰で、どういう文脈を覚えておくべきですか?'
294
+ ]
295
+ },
296
+ {
297
+ id: 'project',
298
+ title: '最初のプロジェクト',
299
+ questions: [
300
+ 'まずBrainbaseへ登録したいプロジェクト名は何ですか?',
301
+ 'そのプロジェクトの目的、現在の状態、あなたの役割は何ですか?',
302
+ '関係者は誰で、それぞれ何をAIに覚えておいてほしいですか?'
303
+ ]
304
+ },
305
+ {
306
+ id: 'approval',
307
+ title: '正本化の承認',
308
+ questions: [
309
+ '最初のデモに必要な本人、仕事、関係性の事実をこのままローカル正本に入れてよいですか?',
310
+ '候補JSONではなく、会話上の要約として承認できますか?',
311
+ 'まだ正本化しない事実や除外したい文脈はありますか?'
312
+ ]
313
+ },
314
+ {
315
+ id: 'first_value_demo',
316
+ title: 'デモ確認',
317
+ questions: [
318
+ 'seed直後に brainbase onboard:demo を実行し、説明し直しが減ったか確認してください。',
319
+ 'デモが足りない場合、どの文脈が不足していましたか?'
320
+ ]
321
+ },
322
+ {
323
+ id: 'sources',
324
+ title: 'デモ後のメール・カレンダー・ドライブ・タスク',
325
+ questions: [
326
+ '最初のデモ後に追加文脈が必要なら、メールは Gmail / Google Workspace / Outlook / Apple Mail / その他のどれですか?',
327
+ 'カレンダーは Google Calendar / Outlook Calendar / Apple Calendar / その他のどれですか?',
328
+ 'ドキュメントは Google Drive / OneDrive / Dropbox / Notion / ローカルフォルダのどこにありますか?',
329
+ 'タスク管理は Notion / Todoist / Linear / GitHub Issues / NocoDB / CSV / カレンダーやメモ散在 / なし のどれですか?',
330
+ '読んでよいアカウント、カレンダー、Driveフォルダ、ローカルフォルダはどれですか?'
331
+ ]
332
+ }
333
+ ];
334
+ }
335
+ function firstProjectStakeholder(project) {
336
+ return project?.stakeholders?.find((stakeholder) => stakeholder.person && stakeholder.context);
337
+ }
338
+ function buildFirstValueScenario(projectName, person) {
339
+ const project = projectName || '<最初のプロジェクト>';
340
+ if (person) {
341
+ return `${person}さんに${project}の相談を投げるための論点メモを作って`;
342
+ }
343
+ return `${project}について、保存済み文脈を使った最初の作業メモを作って`;
344
+ }
345
+ function buildFirstValueExperience(projectName, person) {
346
+ const project = projectName || '<最初のプロジェクト>';
347
+ const tryPrompt = buildFirstValueScenario(projectName, person);
348
+ const expectedValue = person
349
+ ? `${project}と${person}さんの文脈を説明し直さなくても、相談論点が出ること。`
350
+ : `${project}の前提を説明し直さなくても、次に進める作業メモが出ること。`;
351
+ const sampleResult = person
352
+ ? [
353
+ `${person}さんに相談するなら、まず「何を判断したいか」と「どこで迷っているか」を分けます。`,
354
+ `${project}の前提説明から始めず、保存した仕事メモを使って次の確認事項を出します。`
355
+ ].join('\n')
356
+ : [
357
+ `${project}の次に進める作業を一つに絞ります。`,
358
+ '背景説明をもう一度求めず、保存した仕事メモを前提に最初の作業メモを出します。'
359
+ ].join('\n');
360
+ return {
361
+ title: '最小メモを保存したら、まずこの一問で価値を確認します。',
362
+ tryPrompt,
363
+ expectedValue,
364
+ sampleResult
365
+ };
366
+ }
367
+ function toGuidedSourceReadiness(diagnosis) {
368
+ return {
369
+ area: diagnosis.area,
370
+ title: sourceTitle(diagnosis.area),
371
+ input: diagnosis.input,
372
+ status: diagnosis.status,
373
+ statusLabel: statusLabel(diagnosis.status),
374
+ requiredUserInput: diagnosis.requiredUserInput.map(translateRequiredInput),
375
+ setupCommands: diagnosis.setupCommands,
376
+ safetyNotes: diagnosis.safetyNotes.map(translateSafetyNote)
377
+ };
378
+ }
379
+ function deriveProjectSources(input) {
380
+ const sources = [
381
+ { area: 'mail', label: input.email, ref: '<allowed-mail-account>' },
382
+ { area: 'calendar', label: input.calendar, ref: 'primary' },
383
+ { area: 'tasks', label: input.tasks, ref: input.tasks }
384
+ ];
385
+ for (const folder of input.driveFolders) {
386
+ sources.push({ area: 'drive', label: input.drive, ref: folder });
387
+ }
388
+ for (const folder of input.localFolders) {
389
+ sources.push({ area: 'local', label: 'local folder', ref: folder });
390
+ }
391
+ return sources.filter((source) => !isNone(source.label));
392
+ }
393
+ function normalizeProjectSources(sources) {
394
+ const seen = new Set();
395
+ const result = [];
396
+ for (const source of sources) {
397
+ const key = `${source.area}|${source.label}|${source.ref}`;
398
+ if (!seen.has(key)) {
399
+ seen.add(key);
400
+ result.push(source);
401
+ }
402
+ }
403
+ return result;
404
+ }
405
+ function projectCommand(dataDir, name, project, sources, write) {
406
+ const args = [
407
+ 'brainbase',
408
+ 'onboard:projects',
409
+ '--dir', dataDir,
410
+ '--name', name,
411
+ ...(project?.goal ? ['--goal', project.goal] : []),
412
+ ...(project?.status ? ['--status', project.status] : []),
413
+ ...(project?.role ? ['--role', project.role] : []),
414
+ ...(project?.stakeholders ?? []).flatMap((stakeholder) => [
415
+ '--stakeholder',
416
+ `${stakeholder.person}|${stakeholder.role ?? ''}|${stakeholder.context}`
417
+ ]),
418
+ ...sources.flatMap((source) => ['--source', `${source.area}|${source.label}|${source.ref}`]),
419
+ ...((project?.taskSources ?? [])).flatMap((taskSource) => ['--task-source', taskSource]),
420
+ ...((project?.decisionPrinciples ?? [])).flatMap((principle) => ['--decision-principle', principle]),
421
+ ...(write ? ['--write'] : [])
422
+ ];
423
+ return command(args);
424
+ }
425
+ function command(parts) {
426
+ return parts.map(quoteArg).join(' ');
427
+ }
428
+ function quoteArg(value) {
429
+ if (/^[A-Za-z0-9_./:=@+-]+$/u.test(value)) {
430
+ return value;
431
+ }
432
+ return JSON.stringify(value);
433
+ }
434
+ function sourceTitle(area) {
435
+ if (area === 'email')
436
+ return 'メール';
437
+ if (area === 'calendar')
438
+ return 'カレンダー';
439
+ if (area === 'drive')
440
+ return 'ドライブ';
441
+ return 'タスク';
442
+ }
443
+ function statusLabel(status) {
444
+ if (status === 'ready')
445
+ return '準備済み';
446
+ if (status === 'needs_setup')
447
+ return 'ローカル設定が必要';
448
+ if (status === 'needs_input')
449
+ return '追加の許可範囲が必要';
450
+ return '未設定';
451
+ }
452
+ function translateRequiredInput(value) {
453
+ if (value.includes('GoG'))
454
+ return value.replace('Install or configure local GoG command', 'ローカルGoGコマンドをインストールまたは設定する');
455
+ if (value.includes('Google Drive folder'))
456
+ return '読み取りを許可するGoogle DriveフォルダIDを少なくとも1つ指定する';
457
+ if (value.includes('Date range'))
458
+ return '取得する期間とprivate calendarを除外するかを決める';
459
+ if (value.includes('Mail account'))
460
+ return '読み取り対象メールアカウントと本文抜粋の可否を決める';
461
+ if (value.includes('Workspace/project/database'))
462
+ return '読み取り対象workspace/project/databaseのallowlistを決める';
463
+ if (value.includes('Read-only export'))
464
+ return 'read-only exportまたはconnectorの経路を決める';
465
+ if (value.includes('Explicit local'))
466
+ return '明示的に許可したローカルファイル、フォルダ、CSV、JSONL export pathを指定する';
467
+ return value;
468
+ }
469
+ function translateSafetyNote(value) {
470
+ if (value.includes('Do not paste secrets'))
471
+ return 'secretはチャットへ貼らない。';
472
+ if (value.includes('Do not scan the whole Drive'))
473
+ return 'Drive全体は走査せず、許可フォルダだけを見る。';
474
+ if (value.includes('metadata'))
475
+ return '最初はmetadata-firstで扱う。本文やファイル本文は明示承認後だけ扱う。';
476
+ return value;
477
+ }
478
+ function targetLabel(target) {
479
+ if (target === 'claude')
480
+ return 'Claude Code';
481
+ if (target === 'codecode')
482
+ return 'CodeCode';
483
+ return 'Codex';
484
+ }
485
+ function normalize(value) {
486
+ return value.trim().toLowerCase();
487
+ }
488
+ function isNone(value) {
489
+ return ['none', 'no', 'なし', ''].includes(normalize(value));
490
+ }