mdjournal 1.0.14 → 1.0.16
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 +287 -164
- package/dist/client/assets/index-DlkkIyB6.js +402 -0
- package/dist/client/dist/assets/index-DlkkIyB6.js +402 -0
- package/dist/client/dist/index.html +1 -1
- package/dist/client/index.html +1 -1
- package/docs/api-reference.html +2295 -0
- package/docs/config-spec.md +322 -0
- package/docs/extension-spec.md +676 -0
- package/docs/index.md +261 -0
- package/docs/integration-spec.md +518 -0
- package/docs/markdown-format-spec.md +779 -0
- package/docs/openapi.yaml +1009 -0
- package/docs/requirements.md +837 -0
- package/docs/screenshot.png +0 -0
- package/package.json +5 -3
- package/dist/client/assets/index-BrIwqS4G.js +0 -402
- package/dist/client/dist/assets/index-BrIwqS4G.js +0 -402
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
# mdJournal 拡張機能仕様書
|
|
2
|
+
|
|
3
|
+
## 1. 概要
|
|
4
|
+
|
|
5
|
+
本ドキュメントは、mdJournalのユーザー拡張機能(Extensions)の仕様を定義する。
|
|
6
|
+
|
|
7
|
+
ユーザーは独自のTypeScriptコードを作成し、以下の機能を拡張できる:
|
|
8
|
+
- 外部システムとの連携(勤怠システム、独自API等)
|
|
9
|
+
- データ変換・加工処理
|
|
10
|
+
- カスタムアクション(ボタン・メニュー)
|
|
11
|
+
- 通知・Webhook
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2. 拡張機能の種類
|
|
16
|
+
|
|
17
|
+
| タイプ | 説明 | 用途例 |
|
|
18
|
+
|-------|------|--------|
|
|
19
|
+
| `integration` | 外部システム連携 | 勤怠API、独自Slack設定、カスタムWebhook |
|
|
20
|
+
| `transformer` | データ変換 | 日報フォーマット変換、エクスポート処理 |
|
|
21
|
+
| `action` | カスタムアクション | ボタン追加、メニュー拡張 |
|
|
22
|
+
| `hook` | ライフサイクルフック | 保存前/後処理、起動時処理 |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 3. ディレクトリ構成
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
my-journals/
|
|
30
|
+
└── extensions/
|
|
31
|
+
├── tsconfig.json # TypeScript設定
|
|
32
|
+
├── package.json # 依存関係
|
|
33
|
+
├── index.ts # エントリーポイント(オプション)
|
|
34
|
+
│
|
|
35
|
+
├── integrations/ # 連携拡張
|
|
36
|
+
│ ├── my-attendance.ts # 勤怠システム連携
|
|
37
|
+
│ └── my-slack.ts # Slack拡張設定
|
|
38
|
+
│
|
|
39
|
+
├── transformers/ # データ変換
|
|
40
|
+
│ └── export-csv.ts # CSV出力
|
|
41
|
+
│
|
|
42
|
+
├── actions/ # カスタムアクション
|
|
43
|
+
│ └── quick-report.ts # クイック日報作成
|
|
44
|
+
│
|
|
45
|
+
└── hooks/ # ライフサイクルフック
|
|
46
|
+
└── on-save.ts # 保存時処理
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 4. 基本インターフェース
|
|
52
|
+
|
|
53
|
+
### 4.1 拡張機能の基底インターフェース
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// mdjournal/types からインポート可能
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 拡張機能のメタデータ
|
|
60
|
+
*/
|
|
61
|
+
interface ExtensionMeta {
|
|
62
|
+
/** 拡張機能ID(一意) */
|
|
63
|
+
id: string;
|
|
64
|
+
/** 表示名 */
|
|
65
|
+
name: string;
|
|
66
|
+
/** 説明 */
|
|
67
|
+
description?: string;
|
|
68
|
+
/** バージョン */
|
|
69
|
+
version?: string;
|
|
70
|
+
/** 拡張タイプ */
|
|
71
|
+
type: 'integration' | 'transformer' | 'action' | 'hook';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 拡張機能の基底インターフェース
|
|
76
|
+
*/
|
|
77
|
+
interface Extension {
|
|
78
|
+
meta: ExtensionMeta;
|
|
79
|
+
|
|
80
|
+
/** 初期化処理(起動時に呼ばれる) */
|
|
81
|
+
initialize?(context: ExtensionContext): Promise<void>;
|
|
82
|
+
|
|
83
|
+
/** 終了処理(シャットダウン時に呼ばれる) */
|
|
84
|
+
dispose?(): Promise<void>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 拡張機能に渡されるコンテキスト
|
|
89
|
+
*/
|
|
90
|
+
interface ExtensionContext {
|
|
91
|
+
/** 設定ファイルへのアクセス */
|
|
92
|
+
config: ConfigService;
|
|
93
|
+
/** ログ出力 */
|
|
94
|
+
logger: Logger;
|
|
95
|
+
/** 環境変数 */
|
|
96
|
+
env: Record<string, string | undefined>;
|
|
97
|
+
/** データディレクトリパス */
|
|
98
|
+
dataDir: string;
|
|
99
|
+
/** HTTPクライアント(axios互換) */
|
|
100
|
+
http: HttpClient;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 4.2 Integration(外部連携)拡張
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
/**
|
|
108
|
+
* 外部システム連携の拡張
|
|
109
|
+
*/
|
|
110
|
+
interface IntegrationExtension extends Extension {
|
|
111
|
+
meta: ExtensionMeta & { type: 'integration' };
|
|
112
|
+
|
|
113
|
+
/** 連携の有効/無効を返す */
|
|
114
|
+
isEnabled(): boolean;
|
|
115
|
+
|
|
116
|
+
/** 連携アクションを定義 */
|
|
117
|
+
actions: IntegrationAction[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface IntegrationAction {
|
|
121
|
+
/** アクションID */
|
|
122
|
+
id: string;
|
|
123
|
+
/** 表示名 */
|
|
124
|
+
name: string;
|
|
125
|
+
/** アイコン(Ant Design アイコン名) */
|
|
126
|
+
icon?: string;
|
|
127
|
+
/** 実行関数 */
|
|
128
|
+
execute(params: ActionParams): Promise<ActionResult>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface ActionParams {
|
|
132
|
+
/** 対象日付 */
|
|
133
|
+
date?: string;
|
|
134
|
+
/** 日報データ */
|
|
135
|
+
report?: DailyReport;
|
|
136
|
+
/** その他パラメータ */
|
|
137
|
+
[key: string]: unknown;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface ActionResult {
|
|
141
|
+
success: boolean;
|
|
142
|
+
message?: string;
|
|
143
|
+
data?: unknown;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 4.3 Hook(ライフサイクルフック)拡張
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
/**
|
|
151
|
+
* ライフサイクルフックの拡張
|
|
152
|
+
*/
|
|
153
|
+
interface HookExtension extends Extension {
|
|
154
|
+
meta: ExtensionMeta & { type: 'hook' };
|
|
155
|
+
|
|
156
|
+
/** 日報保存前 */
|
|
157
|
+
onBeforeSave?(report: DailyReport): Promise<DailyReport | void>;
|
|
158
|
+
|
|
159
|
+
/** 日報保存後 */
|
|
160
|
+
onAfterSave?(report: DailyReport): Promise<void>;
|
|
161
|
+
|
|
162
|
+
/** 日報読み込み後 */
|
|
163
|
+
onAfterLoad?(report: DailyReport): Promise<DailyReport | void>;
|
|
164
|
+
|
|
165
|
+
/** TODO状態変更時 */
|
|
166
|
+
onTodoStatusChange?(todo: TodoItem, oldStatus: string, newStatus: string): Promise<void>;
|
|
167
|
+
|
|
168
|
+
/** 日付変更時 */
|
|
169
|
+
onDateChange?(newDate: string, oldDate: string): Promise<void>;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 4.4 Transformer(データ変換)拡張
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
/**
|
|
177
|
+
* データ変換の拡張
|
|
178
|
+
*/
|
|
179
|
+
interface TransformerExtension extends Extension {
|
|
180
|
+
meta: ExtensionMeta & { type: 'transformer' };
|
|
181
|
+
|
|
182
|
+
/** 対応する出力形式 */
|
|
183
|
+
outputFormats: TransformFormat[];
|
|
184
|
+
|
|
185
|
+
/** 変換実行 */
|
|
186
|
+
transform(report: DailyReport, format: string): Promise<TransformResult>;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface TransformFormat {
|
|
190
|
+
id: string;
|
|
191
|
+
name: string;
|
|
192
|
+
extension: string; // ファイル拡張子
|
|
193
|
+
mimeType: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
interface TransformResult {
|
|
197
|
+
content: string | Buffer;
|
|
198
|
+
filename: string;
|
|
199
|
+
mimeType: string;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 4.5 Action(カスタムアクション)拡張
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
/**
|
|
207
|
+
* カスタムアクションの拡張
|
|
208
|
+
*/
|
|
209
|
+
interface ActionExtension extends Extension {
|
|
210
|
+
meta: ExtensionMeta & { type: 'action' };
|
|
211
|
+
|
|
212
|
+
/** UIに追加するアクション */
|
|
213
|
+
actions: CustomAction[];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
interface CustomAction {
|
|
217
|
+
id: string;
|
|
218
|
+
name: string;
|
|
219
|
+
icon?: string;
|
|
220
|
+
/** 表示位置 */
|
|
221
|
+
placement: 'toolbar' | 'menu' | 'context-menu';
|
|
222
|
+
/** キーボードショートカット */
|
|
223
|
+
shortcut?: string;
|
|
224
|
+
/** 実行関数 */
|
|
225
|
+
execute(context: ActionContext): Promise<void>;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface ActionContext {
|
|
229
|
+
/** 現在の日付 */
|
|
230
|
+
currentDate: string;
|
|
231
|
+
/** 現在の日報 */
|
|
232
|
+
currentReport?: DailyReport;
|
|
233
|
+
/** 選択中のTODO */
|
|
234
|
+
selectedTodo?: TodoItem;
|
|
235
|
+
/** UIサービス(トースト表示等) */
|
|
236
|
+
ui: UIService;
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 5. 拡張機能の実装例
|
|
243
|
+
|
|
244
|
+
### 5.1 勤怠システム連携
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// extensions/integrations/my-attendance.ts
|
|
248
|
+
|
|
249
|
+
import {
|
|
250
|
+
IntegrationExtension,
|
|
251
|
+
ExtensionContext,
|
|
252
|
+
ActionParams,
|
|
253
|
+
ActionResult
|
|
254
|
+
} from 'mdjournal/types';
|
|
255
|
+
|
|
256
|
+
const myAttendanceExtension: IntegrationExtension = {
|
|
257
|
+
meta: {
|
|
258
|
+
id: 'my-attendance',
|
|
259
|
+
name: '勤怠システム連携',
|
|
260
|
+
description: '社内勤怠システムへの出退勤データ送信',
|
|
261
|
+
type: 'integration',
|
|
262
|
+
version: '1.0.0',
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
isEnabled() {
|
|
266
|
+
return !!process.env.ATTENDANCE_API_KEY;
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
actions: [
|
|
270
|
+
{
|
|
271
|
+
id: 'submit-attendance',
|
|
272
|
+
name: '勤怠データ送信',
|
|
273
|
+
icon: 'ClockCircleOutlined',
|
|
274
|
+
|
|
275
|
+
async execute(params: ActionParams): Promise<ActionResult> {
|
|
276
|
+
const { report, date } = params;
|
|
277
|
+
|
|
278
|
+
if (!report) {
|
|
279
|
+
return { success: false, message: '日報データがありません' };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// PLANから出勤時刻を取得
|
|
283
|
+
const workStart = report.plan[0]?.time;
|
|
284
|
+
// RESULTから退勤時刻を取得
|
|
285
|
+
const workEnd = report.result[report.result.length - 1]?.time;
|
|
286
|
+
|
|
287
|
+
if (!workStart || !workEnd) {
|
|
288
|
+
return { success: false, message: '出退勤時刻が特定できません' };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const response = await fetch(process.env.ATTENDANCE_API_URL!, {
|
|
293
|
+
method: 'POST',
|
|
294
|
+
headers: {
|
|
295
|
+
'Authorization': `Bearer ${process.env.ATTENDANCE_API_KEY}`,
|
|
296
|
+
'Content-Type': 'application/json',
|
|
297
|
+
},
|
|
298
|
+
body: JSON.stringify({
|
|
299
|
+
date,
|
|
300
|
+
start_time: workStart,
|
|
301
|
+
end_time: workEnd,
|
|
302
|
+
break_minutes: 60,
|
|
303
|
+
}),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (!response.ok) {
|
|
307
|
+
throw new Error(`API error: ${response.status}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
success: true,
|
|
312
|
+
message: `勤怠データを送信しました (${workStart} - ${workEnd})`
|
|
313
|
+
};
|
|
314
|
+
} catch (error) {
|
|
315
|
+
return {
|
|
316
|
+
success: false,
|
|
317
|
+
message: `送信エラー: ${error}`
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export default myAttendanceExtension;
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### 5.2 Slack拡張(独自チャンネル設定)
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// extensions/integrations/my-slack.ts
|
|
332
|
+
|
|
333
|
+
import {
|
|
334
|
+
IntegrationExtension,
|
|
335
|
+
ActionParams,
|
|
336
|
+
ActionResult
|
|
337
|
+
} from 'mdjournal/types';
|
|
338
|
+
|
|
339
|
+
const mySlackExtension: IntegrationExtension = {
|
|
340
|
+
meta: {
|
|
341
|
+
id: 'my-slack',
|
|
342
|
+
name: 'Slack拡張',
|
|
343
|
+
description: 'プロジェクト別チャンネルへの投稿',
|
|
344
|
+
type: 'integration',
|
|
345
|
+
version: '1.0.0',
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
isEnabled() {
|
|
349
|
+
return !!process.env.SLACK_BOT_TOKEN;
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
actions: [
|
|
353
|
+
{
|
|
354
|
+
id: 'post-to-project-channel',
|
|
355
|
+
name: 'プロジェクトチャンネルに投稿',
|
|
356
|
+
icon: 'SlackOutlined',
|
|
357
|
+
|
|
358
|
+
async execute(params: ActionParams): Promise<ActionResult> {
|
|
359
|
+
const { report, date } = params;
|
|
360
|
+
|
|
361
|
+
if (!report) {
|
|
362
|
+
return { success: false, message: '日報データがありません' };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// プロジェクト別にチャンネルを分けて投稿
|
|
366
|
+
const projectChannels: Record<string, string> = {
|
|
367
|
+
'P34': 'C0123456789', // #clientA-daily
|
|
368
|
+
'P14': 'C0234567890', // #systemB-daily
|
|
369
|
+
'P37': 'C0345678901', // #clientD-daily
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const results: string[] = [];
|
|
373
|
+
|
|
374
|
+
for (const [projectCode, channelId] of Object.entries(projectChannels)) {
|
|
375
|
+
const projectTasks = report.plan.filter(p => p.projectCode === projectCode);
|
|
376
|
+
|
|
377
|
+
if (projectTasks.length === 0) continue;
|
|
378
|
+
|
|
379
|
+
const message = formatProjectMessage(date!, projectCode, projectTasks);
|
|
380
|
+
|
|
381
|
+
// Slack API呼び出し
|
|
382
|
+
const response = await fetch('https://slack.com/api/chat.postMessage', {
|
|
383
|
+
method: 'POST',
|
|
384
|
+
headers: {
|
|
385
|
+
'Authorization': `Bearer ${process.env.SLACK_BOT_TOKEN}`,
|
|
386
|
+
'Content-Type': 'application/json',
|
|
387
|
+
},
|
|
388
|
+
body: JSON.stringify({
|
|
389
|
+
channel: channelId,
|
|
390
|
+
text: message,
|
|
391
|
+
}),
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
if (response.ok) {
|
|
395
|
+
results.push(projectCode);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
success: true,
|
|
401
|
+
message: `投稿完了: ${results.join(', ')}`,
|
|
402
|
+
};
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
function formatProjectMessage(date: string, projectCode: string, tasks: any[]): string {
|
|
409
|
+
const taskList = tasks.map(t => `• ${t.time} ${t.task}`).join('\n');
|
|
410
|
+
return `📋 *${date} - ${projectCode}*\n${taskList}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export default mySlackExtension;
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### 5.3 保存時フック
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
// extensions/hooks/on-save.ts
|
|
420
|
+
|
|
421
|
+
import { HookExtension, DailyReport } from 'mdjournal/types';
|
|
422
|
+
|
|
423
|
+
const onSaveHook: HookExtension = {
|
|
424
|
+
meta: {
|
|
425
|
+
id: 'on-save-hook',
|
|
426
|
+
name: '保存時処理',
|
|
427
|
+
type: 'hook',
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
async onBeforeSave(report: DailyReport): Promise<DailyReport> {
|
|
431
|
+
// 保存前にTODOを自動ソート(未完了を上に)
|
|
432
|
+
const sortedTodos = [...report.todos].sort((a, b) => {
|
|
433
|
+
const statusOrder = { pending: 0, in_progress: 1, hold: 2, completed: 3 };
|
|
434
|
+
return (statusOrder[a.status] || 99) - (statusOrder[b.status] || 99);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
...report,
|
|
439
|
+
todos: sortedTodos,
|
|
440
|
+
};
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
async onAfterSave(report: DailyReport): Promise<void> {
|
|
444
|
+
// 保存後にログ出力
|
|
445
|
+
console.log(`[Hook] Report saved: ${report.date}`);
|
|
446
|
+
|
|
447
|
+
// 完了TODOがあればSlackに通知(例)
|
|
448
|
+
const completedToday = report.todos.filter(t => t.status === 'completed');
|
|
449
|
+
if (completedToday.length > 0) {
|
|
450
|
+
console.log(`[Hook] ${completedToday.length} TODOs completed today!`);
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
export default onSaveHook;
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### 5.4 CSVエクスポート
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
// extensions/transformers/export-csv.ts
|
|
462
|
+
|
|
463
|
+
import { TransformerExtension, DailyReport, TransformResult } from 'mdjournal/types';
|
|
464
|
+
|
|
465
|
+
const csvExporter: TransformerExtension = {
|
|
466
|
+
meta: {
|
|
467
|
+
id: 'csv-exporter',
|
|
468
|
+
name: 'CSVエクスポート',
|
|
469
|
+
type: 'transformer',
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
outputFormats: [
|
|
473
|
+
{ id: 'plan-csv', name: '計画CSV', extension: 'csv', mimeType: 'text/csv' },
|
|
474
|
+
{ id: 'todo-csv', name: 'TODO CSV', extension: 'csv', mimeType: 'text/csv' },
|
|
475
|
+
],
|
|
476
|
+
|
|
477
|
+
async transform(report: DailyReport, format: string): Promise<TransformResult> {
|
|
478
|
+
if (format === 'plan-csv') {
|
|
479
|
+
const header = '日付,時刻,プロジェクト,タスク\n';
|
|
480
|
+
const rows = report.plan.map(p =>
|
|
481
|
+
`${report.date},${p.time},${p.projectCode},${p.task}`
|
|
482
|
+
).join('\n');
|
|
483
|
+
|
|
484
|
+
return {
|
|
485
|
+
content: header + rows,
|
|
486
|
+
filename: `plan_${report.date}.csv`,
|
|
487
|
+
mimeType: 'text/csv',
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (format === 'todo-csv') {
|
|
492
|
+
const header = '日付,ステータス,プロジェクト,タスク,期限\n';
|
|
493
|
+
const rows = report.todos.map(t =>
|
|
494
|
+
`${report.date},${t.status},${t.projectCode || ''},${t.task},${t.dueDate || ''}`
|
|
495
|
+
).join('\n');
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
content: header + rows,
|
|
499
|
+
filename: `todos_${report.date}.csv`,
|
|
500
|
+
mimeType: 'text/csv',
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
throw new Error(`Unknown format: ${format}`);
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
export default csvExporter;
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## 6. 設定ファイル
|
|
514
|
+
|
|
515
|
+
### 6.1 extensions/tsconfig.json
|
|
516
|
+
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"compilerOptions": {
|
|
520
|
+
"target": "ES2020",
|
|
521
|
+
"module": "ESNext",
|
|
522
|
+
"moduleResolution": "node",
|
|
523
|
+
"esModuleInterop": true,
|
|
524
|
+
"strict": true,
|
|
525
|
+
"skipLibCheck": true,
|
|
526
|
+
"declaration": true,
|
|
527
|
+
"outDir": "./dist",
|
|
528
|
+
"rootDir": "./",
|
|
529
|
+
"baseUrl": ".",
|
|
530
|
+
"paths": {
|
|
531
|
+
"mdjournal/types": ["node_modules/mdjournal/types"]
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
"include": ["./**/*.ts"],
|
|
535
|
+
"exclude": ["node_modules", "dist"]
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### 6.2 extensions/package.json
|
|
540
|
+
|
|
541
|
+
```json
|
|
542
|
+
{
|
|
543
|
+
"name": "my-journal-extensions",
|
|
544
|
+
"version": "1.0.0",
|
|
545
|
+
"type": "module",
|
|
546
|
+
"scripts": {
|
|
547
|
+
"build": "tsc",
|
|
548
|
+
"watch": "tsc --watch"
|
|
549
|
+
},
|
|
550
|
+
"dependencies": {
|
|
551
|
+
"mdjournal": "latest"
|
|
552
|
+
},
|
|
553
|
+
"devDependencies": {
|
|
554
|
+
"typescript": "^5.0.0"
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### 6.3 integrations.yamlでの拡張有効化
|
|
560
|
+
|
|
561
|
+
```yaml
|
|
562
|
+
# config/integrations.yaml
|
|
563
|
+
|
|
564
|
+
extensions:
|
|
565
|
+
enabled: true
|
|
566
|
+
dir: "./extensions"
|
|
567
|
+
|
|
568
|
+
# 有効にする拡張機能
|
|
569
|
+
active:
|
|
570
|
+
- my-attendance
|
|
571
|
+
- my-slack
|
|
572
|
+
- on-save-hook
|
|
573
|
+
- csv-exporter
|
|
574
|
+
|
|
575
|
+
# 拡張機能ごとの設定
|
|
576
|
+
config:
|
|
577
|
+
my-attendance:
|
|
578
|
+
auto_submit: false
|
|
579
|
+
submit_time: "18:00"
|
|
580
|
+
|
|
581
|
+
my-slack:
|
|
582
|
+
default_channel: "#daily-report"
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## 7. CLI コマンド
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
# 拡張機能ディレクトリを初期化
|
|
591
|
+
npx mdjournal init-extension
|
|
592
|
+
|
|
593
|
+
# 拡張機能のテンプレートを生成
|
|
594
|
+
npx mdjournal create-extension --type integration --name my-api
|
|
595
|
+
|
|
596
|
+
# 拡張機能をビルド
|
|
597
|
+
npx mdjournal build-extension
|
|
598
|
+
|
|
599
|
+
# 拡張機能を有効にして起動
|
|
600
|
+
npx mdjournal serve --extensions ./extensions
|
|
601
|
+
|
|
602
|
+
# 拡張機能の一覧を表示
|
|
603
|
+
npx mdjournal list-extensions
|
|
604
|
+
|
|
605
|
+
# 特定の拡張機能のアクションを実行
|
|
606
|
+
npx mdjournal run-action my-attendance:submit-attendance --date 2025-12-18
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## 8. セキュリティ考慮事項
|
|
612
|
+
|
|
613
|
+
### 8.1 拡張機能の実行環境
|
|
614
|
+
|
|
615
|
+
- 拡張機能はサーバーサイド(Node.js)で実行される
|
|
616
|
+
- ファイルシステムアクセスは `dataDir` 配下に制限(オプション)
|
|
617
|
+
- 環境変数は `process.env` 経由でアクセス可能
|
|
618
|
+
|
|
619
|
+
### 8.2 APIキーの管理
|
|
620
|
+
|
|
621
|
+
- APIキー等の機密情報は `.env` ファイルで管理
|
|
622
|
+
- `.env` は `.gitignore` に追加してGit管理から除外
|
|
623
|
+
- 拡張機能内でハードコードしない
|
|
624
|
+
|
|
625
|
+
### 8.3 外部通信
|
|
626
|
+
|
|
627
|
+
- HTTP(S)通信はログに記録(オプション)
|
|
628
|
+
- 許可されたドメインのみに制限可能(設定ファイル)
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
## 9. API リファレンス
|
|
633
|
+
|
|
634
|
+
### GET `/api/extensions`
|
|
635
|
+
|
|
636
|
+
有効な拡張機能一覧を取得
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
interface ExtensionListResponse {
|
|
640
|
+
extensions: {
|
|
641
|
+
id: string;
|
|
642
|
+
name: string;
|
|
643
|
+
type: string;
|
|
644
|
+
enabled: boolean;
|
|
645
|
+
actions?: { id: string; name: string }[];
|
|
646
|
+
}[];
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### POST `/api/extensions/:extensionId/actions/:actionId`
|
|
651
|
+
|
|
652
|
+
拡張機能のアクションを実行
|
|
653
|
+
|
|
654
|
+
```typescript
|
|
655
|
+
// Request
|
|
656
|
+
interface ExecuteActionRequest {
|
|
657
|
+
date?: string;
|
|
658
|
+
params?: Record<string, unknown>;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Response
|
|
662
|
+
interface ExecuteActionResponse {
|
|
663
|
+
success: boolean;
|
|
664
|
+
message?: string;
|
|
665
|
+
data?: unknown;
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
## 更新履歴
|
|
672
|
+
|
|
673
|
+
| バージョン | 日付 | 更新内容 |
|
|
674
|
+
|-----------|------|---------|
|
|
675
|
+
| 1.0 | 2025-12-20 | mdJournalとして公開準備 |
|
|
676
|
+
| 0.1 | 2025-12-18 | 初版作成 |
|