@nahisaho/shikigami 2.0.5 → 2.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.
package/CHANGELOG.md CHANGED
@@ -5,23 +5,6 @@ All notable changes to SHIKIGAMI will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [2.0.5] - 2026-05-18
9
-
10
- ### Changed
11
-
12
- - **Copilot Cowork連携を公式プラグイン方式に変更**
13
- - 旧: サードパーティMCPサーバー(msartem/copilot_cowork_mcp)のセットアップ
14
- - 新: M365 Unified App Manifest準拠のプラグインパッケージ(.zip)を生成
15
- - `npx shikigami cowork` でCoworkプラグインをビルド
16
- - manifest.json自動生成、アイコン生成、スキル検証機能
17
- - Companionファイル20制限への自動統合(63ファイル→14ファイル等)
18
- - 参照: https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development
19
-
20
- ### Removed
21
-
22
- - `setup-cowork.js` — サードパーティMCPサーバーセットアップスクリプト
23
- - `init.js` の旧Cowork MCP状態チェック
24
-
25
8
  ## [2.0.4] - 2026-05-18
26
9
 
27
10
  ### Fixed
package/README.md CHANGED
@@ -207,25 +207,6 @@ SHIKIGAMI:
207
207
  4. [Writing] 優先順位付きの削減提言レポートを生成
208
208
  ```
209
209
 
210
- ## 🤝 Microsoft 365 Copilot Cowork 連携
211
-
212
- SHIKIGAMIのAgent SkillsをCopilot Coworkプラグインとしてパッケージ化できます。
213
-
214
- ```bash
215
- # Coworkプラグインパッケージをビルド
216
- npx shikigami cowork
217
-
218
- # 検証のみ(ドライラン)
219
- npx shikigami cowork --check
220
-
221
- # 出力先を指定
222
- npx shikigami cowork --out ./dist
223
- ```
224
-
225
- 生成された `.zip` ファイルをM365 Admin Center > Manage Apps > Upload Custom Appからアップロードすると、Coworkで SHIKIGAMIのスキルが利用可能になります。
226
-
227
- **参照**: [Cowork Plugin Development](https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development)
228
-
229
210
  ## 📄 ライセンス
230
211
 
231
212
  MIT License
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nahisaho/shikigami-pwc-mcp-server",
3
- "version": "2.0.5",
3
+ "version": "2.1.0",
4
4
  "description": "SHIKIGAMI-PwC MCP Server - Deep Research tools with PwC Consulting Frameworks",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nahisaho/shikigami",
3
- "version": "2.0.5",
3
+ "version": "2.1.0",
4
4
  "description": "GitHub Copilot Agent Skills for Deep Research & Consulting - AI-Powered Research Assistant with 50+ Consulting Frameworks",
5
5
  "keywords": [
6
6
  "github-copilot",
package/scripts/cli.js CHANGED
@@ -32,7 +32,7 @@ Usage: npx shikigami [command] [options]
32
32
  Commands:
33
33
  init Initialize SHIKIGAMI files in current directory
34
34
  upgrade, update Upgrade SHIKIGAMI files (same as init --force) (v1.23.0)
35
- cowork [options] Build Copilot Cowork plugin package (v2.0.4)
35
+ cowork [options] Set up Microsoft Copilot Cowork MCP (v2.0.0)
36
36
  new [name] Create a new research project
37
37
  find-related [keyword] Find related projects by keywords (v1.7.0)
38
38
  inherit [options] Prepare knowledge inheritance from past reports (v1.8.0)
@@ -42,9 +42,8 @@ Commands:
42
42
  Examples:
43
43
  npx shikigami init # Initialize SHIKIGAMI
44
44
  npx shikigami upgrade # Upgrade to latest version
45
- npx shikigami cowork # Build Cowork plugin .zip
46
- npx shikigami cowork --check # Validate skills (dry-run)
47
- npx shikigami cowork --out ./dist # Specify output directory
45
+ npx shikigami cowork # Set up Copilot Cowork MCP (v2.0.0)
46
+ npx shikigami cowork --status # Check Cowork installation status
48
47
  npx shikigami new my-research # Create new project "my-research"
49
48
  npx shikigami new # Create new project with auto-numbered name
50
49
  npx shikigami find-related レアアース # Find projects related to "レアアース"
@@ -72,9 +71,10 @@ if (command === 'upgrade' || command === 'update') {
72
71
  process.exit(0);
73
72
  }
74
73
 
75
- // Cowork command (v2.0.4)
74
+ // Cowork command (v2.0.0)
76
75
  if (command === 'cowork') {
77
- require('./build-cowork-plugin.js');
76
+ const { setup } = require('./setup-cowork.js');
77
+ setup();
78
78
  process.exit(0);
79
79
  }
80
80
 
package/scripts/init.js CHANGED
@@ -167,10 +167,21 @@ function init() {
167
167
  console.log(`📊 Summary: ${copiedCount} copied, ${overwrittenCount} overwritten, ${skippedCount} skipped`);
168
168
  console.log('');
169
169
 
170
- // Cowork plugin hint
171
- console.log('🤝 Copilot Cowork:');
172
- console.log(' npx shikigami cowork でCoworkプラグインパッケージを生成');
173
- console.log('');
170
+ // Check Cowork MCP status
171
+ try {
172
+ const { isInstalled, isMcpConfigured } = require('./setup-cowork.js');
173
+ if (!isInstalled() || !isMcpConfigured()) {
174
+ console.log('🤝 Copilot Cowork MCP (M365連携):');
175
+ console.log(' メール・カレンダー・Teams・OneDrive にCopilot CLIからアクセス可能に');
176
+ console.log(' セットアップ: npx shikigami cowork');
177
+ console.log('');
178
+ } else {
179
+ console.log('🤝 Copilot Cowork MCP: ✅ Configured');
180
+ console.log('');
181
+ }
182
+ } catch {
183
+ // Ignore errors from cowork check
184
+ }
174
185
 
175
186
  console.log('📝 Next steps:');
176
187
  console.log(' 1. cd mcp-server && npm install && npm run build');
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SHIKIGAMI - Copilot Cowork MCP Setup
4
+ * Usage: npx shikigami cowork [options]
5
+ *
6
+ * Sets up Microsoft Copilot Cowork as an MCP server for GitHub Copilot CLI.
7
+ * Enables access to M365 data (Mail, Calendar, Teams, Files) from Copilot CLI.
8
+ *
9
+ * v2.0.0: Initial release
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { execSync } = require('child_process');
15
+
16
+ const COWORK_REPO = 'https://github.com/msartem/copilot_cowork_mcp.git';
17
+ const COPILOT_DIR = path.join(require('os').homedir(), '.copilot');
18
+ const PLUGINS_DIR = path.join(COPILOT_DIR, 'installed-plugins');
19
+ const COWORK_DIR = path.join(PLUGINS_DIR, 'copilot_cowork_mcp');
20
+ const MCP_CONFIG = path.join(COPILOT_DIR, '.mcp.json');
21
+
22
+ function parseArgs() {
23
+ const args = process.argv.slice(2).filter(a => a !== 'cowork');
24
+ return {
25
+ uninstall: args.includes('--uninstall'),
26
+ status: args.includes('--status'),
27
+ help: args.includes('--help') || args.includes('-h'),
28
+ };
29
+ }
30
+
31
+ function showHelp() {
32
+ console.log(`
33
+ 🤝 SHIKIGAMI - Copilot Cowork Setup
34
+
35
+ Usage: npx shikigami cowork [options]
36
+
37
+ Options:
38
+ --status Check installation status
39
+ --uninstall Remove Cowork MCP configuration
40
+ -h, --help Show this help message
41
+
42
+ What is Copilot Cowork?
43
+ Microsoft Copilot Cowork MCP enables GitHub Copilot CLI to access
44
+ your M365 data: Mail, Calendar, Teams messages, and OneDrive files.
45
+
46
+ Requirements:
47
+ - Python 3.10+
48
+ - Microsoft 365 account
49
+ - First run requires browser sign-in (Edge/Chromium)
50
+
51
+ After setup:
52
+ 1. Restart Copilot CLI (or start a new session)
53
+ 2. First use will open a browser for M365 authentication
54
+ 3. After sign-in, tokens are cached for ~90 days
55
+ `);
56
+ }
57
+
58
+ function checkPython() {
59
+ try {
60
+ const version = execSync('python3 --version 2>&1', { encoding: 'utf-8' }).trim();
61
+ const match = version.match(/Python (\d+)\.(\d+)/);
62
+ if (match && (parseInt(match[1]) > 3 || (parseInt(match[1]) === 3 && parseInt(match[2]) >= 10))) {
63
+ return { ok: true, version };
64
+ }
65
+ return { ok: false, version, error: 'Python 3.10+ required' };
66
+ } catch {
67
+ return { ok: false, version: null, error: 'Python 3 not found' };
68
+ }
69
+ }
70
+
71
+ function checkGit() {
72
+ try {
73
+ execSync('git --version', { encoding: 'utf-8' });
74
+ return true;
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ function isInstalled() {
81
+ return fs.existsSync(path.join(COWORK_DIR, 'server.py'));
82
+ }
83
+
84
+ function isMcpConfigured() {
85
+ if (!fs.existsSync(MCP_CONFIG)) return false;
86
+ try {
87
+ const config = JSON.parse(fs.readFileSync(MCP_CONFIG, 'utf-8'));
88
+ return !!(config.mcpServers && config.mcpServers.cowork);
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ function checkDependencies() {
95
+ const deps = ['fastmcp', 'requests', 'playwright'];
96
+ const results = {};
97
+ for (const dep of deps) {
98
+ try {
99
+ execSync(`python3 -c "import ${dep}" 2>&1`, { encoding: 'utf-8' });
100
+ results[dep] = true;
101
+ } catch {
102
+ results[dep] = false;
103
+ }
104
+ }
105
+ return results;
106
+ }
107
+
108
+ function showStatus() {
109
+ console.log('');
110
+ console.log('🤝 Copilot Cowork MCP Status');
111
+ console.log('════════════════════════════');
112
+ console.log('');
113
+
114
+ const python = checkPython();
115
+ console.log(` Python: ${python.ok ? `✅ ${python.version}` : `❌ ${python.error}`}`);
116
+
117
+ const installed = isInstalled();
118
+ console.log(` Plugin: ${installed ? '✅ Installed' : '❌ Not installed'}`);
119
+
120
+ if (installed) {
121
+ const deps = checkDependencies();
122
+ for (const [dep, ok] of Object.entries(deps)) {
123
+ console.log(` ${dep.padEnd(14)} ${ok ? '✅' : '❌ Missing'}`);
124
+ }
125
+ }
126
+
127
+ const configured = isMcpConfigured();
128
+ console.log(` MCP Config: ${configured ? '✅ Configured' : '❌ Not configured'}`);
129
+ console.log(` Config Path: ${MCP_CONFIG}`);
130
+ console.log('');
131
+
132
+ if (installed && configured) {
133
+ console.log(' ✨ Ready to use! Restart Copilot CLI to activate.');
134
+ } else {
135
+ console.log(' Run "npx shikigami cowork" to set up.');
136
+ }
137
+ console.log('');
138
+ }
139
+
140
+ function installDependencies() {
141
+ const deps = checkDependencies();
142
+ const missing = Object.entries(deps).filter(([, ok]) => !ok).map(([dep]) => dep);
143
+
144
+ if (missing.length === 0) {
145
+ console.log(' ✅ All Python dependencies already installed');
146
+ return true;
147
+ }
148
+
149
+ console.log(` 📦 Installing: ${missing.join(', ')}...`);
150
+ try {
151
+ // Try with --break-system-packages first (Ubuntu/Debian), fallback to without
152
+ try {
153
+ execSync(`pip install --break-system-packages ${missing.join(' ')} 2>&1`, {
154
+ encoding: 'utf-8',
155
+ stdio: 'pipe',
156
+ });
157
+ } catch {
158
+ execSync(`pip install ${missing.join(' ')} 2>&1`, {
159
+ encoding: 'utf-8',
160
+ stdio: 'pipe',
161
+ });
162
+ }
163
+
164
+ // Install Playwright browser if playwright was just installed
165
+ if (missing.includes('playwright')) {
166
+ console.log(' 🌐 Installing Playwright browser (chromium)...');
167
+ execSync('python3 -m playwright install chromium 2>&1', {
168
+ encoding: 'utf-8',
169
+ stdio: 'pipe',
170
+ });
171
+ }
172
+
173
+ console.log(' ✅ Dependencies installed');
174
+ return true;
175
+ } catch (err) {
176
+ console.error(` ❌ Failed to install dependencies: ${err.message}`);
177
+ console.log(' 💡 Try manually: pip install fastmcp requests playwright');
178
+ return false;
179
+ }
180
+ }
181
+
182
+ function clonePlugin() {
183
+ if (isInstalled()) {
184
+ console.log(' ✅ Plugin already installed');
185
+ // Pull latest
186
+ try {
187
+ execSync(`git -C "${COWORK_DIR}" pull --quiet 2>&1`, {
188
+ encoding: 'utf-8',
189
+ stdio: 'pipe',
190
+ });
191
+ console.log(' 🔄 Updated to latest version');
192
+ } catch {
193
+ // Ignore pull errors
194
+ }
195
+ return true;
196
+ }
197
+
198
+ console.log(' 📥 Cloning Copilot Cowork MCP...');
199
+ try {
200
+ fs.mkdirSync(PLUGINS_DIR, { recursive: true });
201
+ execSync(`git clone "${COWORK_REPO}" "${COWORK_DIR}" 2>&1`, {
202
+ encoding: 'utf-8',
203
+ stdio: 'pipe',
204
+ });
205
+ console.log(' ✅ Plugin cloned');
206
+ return true;
207
+ } catch (err) {
208
+ console.error(` ❌ Failed to clone: ${err.message}`);
209
+ return false;
210
+ }
211
+ }
212
+
213
+ function configureMcp() {
214
+ if (isMcpConfigured()) {
215
+ console.log(' ✅ MCP already configured');
216
+ return true;
217
+ }
218
+
219
+ console.log(' ⚙️ Configuring MCP server...');
220
+ try {
221
+ fs.mkdirSync(COPILOT_DIR, { recursive: true });
222
+
223
+ let config = { mcpServers: {} };
224
+ if (fs.existsSync(MCP_CONFIG)) {
225
+ try {
226
+ config = JSON.parse(fs.readFileSync(MCP_CONFIG, 'utf-8'));
227
+ if (!config.mcpServers) config.mcpServers = {};
228
+ } catch {
229
+ // Corrupted config, start fresh but keep backup
230
+ const backup = `${MCP_CONFIG}.bak`;
231
+ fs.copyFileSync(MCP_CONFIG, backup);
232
+ console.log(` ⚠️ Backed up corrupted config to ${backup}`);
233
+ }
234
+ }
235
+
236
+ config.mcpServers.cowork = {
237
+ type: 'stdio',
238
+ command: 'python3',
239
+ args: [path.join(COWORK_DIR, 'server.py')],
240
+ env: {
241
+ COWORK_SHOW_THINKING: 'false',
242
+ },
243
+ };
244
+
245
+ fs.writeFileSync(MCP_CONFIG, JSON.stringify(config, null, 2) + '\n');
246
+ console.log(' ✅ MCP configured');
247
+ return true;
248
+ } catch (err) {
249
+ console.error(` ❌ Failed to configure MCP: ${err.message}`);
250
+ return false;
251
+ }
252
+ }
253
+
254
+ function uninstall() {
255
+ console.log('');
256
+ console.log('🤝 Uninstalling Copilot Cowork MCP...');
257
+ console.log('');
258
+
259
+ // Remove from MCP config
260
+ if (fs.existsSync(MCP_CONFIG)) {
261
+ try {
262
+ const config = JSON.parse(fs.readFileSync(MCP_CONFIG, 'utf-8'));
263
+ if (config.mcpServers && config.mcpServers.cowork) {
264
+ delete config.mcpServers.cowork;
265
+ fs.writeFileSync(MCP_CONFIG, JSON.stringify(config, null, 2) + '\n');
266
+ console.log(' ✅ Removed from MCP config');
267
+ } else {
268
+ console.log(' ⏭️ Not in MCP config');
269
+ }
270
+ } catch {
271
+ console.log(' ⚠️ Could not parse MCP config');
272
+ }
273
+ }
274
+
275
+ // Remove plugin directory
276
+ if (fs.existsSync(COWORK_DIR)) {
277
+ fs.rmSync(COWORK_DIR, { recursive: true, force: true });
278
+ console.log(' ✅ Removed plugin files');
279
+ } else {
280
+ console.log(' ⏭️ Plugin not installed');
281
+ }
282
+
283
+ console.log('');
284
+ console.log(' ✨ Uninstall complete. Restart Copilot CLI to apply.');
285
+ console.log('');
286
+ }
287
+
288
+ function setup() {
289
+ const { uninstall: doUninstall, status, help } = parseArgs();
290
+
291
+ if (help) {
292
+ showHelp();
293
+ return;
294
+ }
295
+
296
+ if (status) {
297
+ showStatus();
298
+ return;
299
+ }
300
+
301
+ if (doUninstall) {
302
+ uninstall();
303
+ return;
304
+ }
305
+
306
+ console.log('');
307
+ console.log('🤝 Setting up Copilot Cowork MCP');
308
+ console.log('═════════════════════════════════');
309
+ console.log('');
310
+
311
+ // Step 1: Check prerequisites
312
+ console.log('Step 1/4: Checking prerequisites...');
313
+ const python = checkPython();
314
+ if (!python.ok) {
315
+ console.error(` ❌ ${python.error}`);
316
+ console.log(' 💡 Install Python 3.10+: https://www.python.org/downloads/');
317
+ process.exit(1);
318
+ }
319
+ console.log(` ✅ ${python.version}`);
320
+
321
+ if (!checkGit()) {
322
+ console.error(' ❌ Git not found');
323
+ process.exit(1);
324
+ }
325
+ console.log(' ✅ Git available');
326
+ console.log('');
327
+
328
+ // Step 2: Clone plugin
329
+ console.log('Step 2/4: Installing plugin...');
330
+ if (!clonePlugin()) process.exit(1);
331
+ console.log('');
332
+
333
+ // Step 3: Install dependencies
334
+ console.log('Step 3/4: Installing dependencies...');
335
+ if (!installDependencies()) process.exit(1);
336
+ console.log('');
337
+
338
+ // Step 4: Configure MCP
339
+ console.log('Step 4/4: Configuring MCP...');
340
+ if (!configureMcp()) process.exit(1);
341
+ console.log('');
342
+
343
+ // Done
344
+ console.log('═════════════════════════════════');
345
+ console.log('✨ Copilot Cowork MCP setup complete!');
346
+ console.log('');
347
+ console.log('📝 Next steps:');
348
+ console.log(' 1. Restart Copilot CLI (or start a new session)');
349
+ console.log(' 2. First use will open a browser for M365 sign-in');
350
+ console.log(' 3. After sign-in, tokens are cached for ~90 days');
351
+ console.log('');
352
+ console.log('💡 Usage examples:');
353
+ console.log(' "メールの未読を要約して"');
354
+ console.log(' "明日の会議は?"');
355
+ console.log(' "Teamsで自分にhelloとメッセージ送って"');
356
+ console.log('');
357
+ console.log('🔧 Commands:');
358
+ console.log(' npx shikigami cowork --status # Check status');
359
+ console.log(' npx shikigami cowork --uninstall # Remove');
360
+ console.log('');
361
+ }
362
+
363
+ module.exports = { setup, showStatus, isInstalled, isMcpConfigured };
364
+
365
+ if (require.main === module) {
366
+ setup();
367
+ }
@@ -1,587 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * SHIKIGAMI Cowork Plugin Builder
4
- *
5
- * Builds a Microsoft 365 Copilot Cowork plugin package (.zip)
6
- * from SHIKIGAMI's Agent Skills.
7
- *
8
- * Usage: npx shikigami cowork [options]
9
- * --out <dir> Output directory (default: ./dist)
10
- * --check Dry-run validation only
11
- * --help Show help
12
- *
13
- * Reference: https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development
14
- */
15
-
16
- const fs = require('fs');
17
- const path = require('path');
18
- const { execSync } = require('child_process');
19
-
20
- // ── Constants ──────────────────────────────────────────────────────────────
21
-
22
- const MAX_COMPANIONS_PER_SKILL = 20;
23
- const MAX_COMPANION_SIZE = 5 * 1024 * 1024; // 5 MB
24
- const MAX_COMPANION_TOTAL = 10 * 1024 * 1024; // 10 MB per skill
25
- const MAX_SKILLS = 20;
26
-
27
- const PACKAGE_NAME = 'com.nahisaho.shikigami';
28
- const APP_ID = 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d'; // Stable GUID
29
- const ACCENT_COLOR = '#6B46C1';
30
-
31
- // ── Minimal PNG generators (no dependencies) ──────────────────────────────
32
-
33
- function createMinimalPNG(width, height, r, g, b) {
34
- // Creates a minimal valid PNG with a solid color
35
- // PNG signature
36
- const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
37
-
38
- function crc32(buf) {
39
- let crc = 0xFFFFFFFF;
40
- const table = new Int32Array(256);
41
- for (let i = 0; i < 256; i++) {
42
- let c = i;
43
- for (let j = 0; j < 8; j++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
44
- table[i] = c;
45
- }
46
- for (let i = 0; i < buf.length; i++) {
47
- crc = table[(crc ^ buf[i]) & 0xFF] ^ (crc >>> 8);
48
- }
49
- return (crc ^ 0xFFFFFFFF) >>> 0;
50
- }
51
-
52
- function makeChunk(type, data) {
53
- const len = Buffer.alloc(4);
54
- len.writeUInt32BE(data.length);
55
- const typeAndData = Buffer.concat([Buffer.from(type), data]);
56
- const crc = Buffer.alloc(4);
57
- crc.writeUInt32BE(crc32(typeAndData));
58
- return Buffer.concat([len, typeAndData, crc]);
59
- }
60
-
61
- // IHDR
62
- const ihdr = Buffer.alloc(13);
63
- ihdr.writeUInt32BE(width, 0);
64
- ihdr.writeUInt32BE(height, 4);
65
- ihdr[8] = 8; // bit depth
66
- ihdr[9] = 2; // color type RGB
67
- ihdr[10] = 0; // compression
68
- ihdr[11] = 0; // filter
69
- ihdr[12] = 0; // interlace
70
-
71
- // IDAT - raw image data
72
- const rowSize = 1 + width * 3; // filter byte + RGB per pixel
73
- const rawData = Buffer.alloc(rowSize * height);
74
- for (let y = 0; y < height; y++) {
75
- rawData[y * rowSize] = 0; // no filter
76
- for (let x = 0; x < width; x++) {
77
- const offset = y * rowSize + 1 + x * 3;
78
- rawData[offset] = r;
79
- rawData[offset + 1] = g;
80
- rawData[offset + 2] = b;
81
- }
82
- }
83
-
84
- const zlib = require('zlib');
85
- const compressed = zlib.deflateSync(rawData);
86
-
87
- // IEND
88
- const iend = Buffer.alloc(0);
89
-
90
- return Buffer.concat([
91
- signature,
92
- makeChunk('IHDR', ihdr),
93
- makeChunk('IDAT', compressed),
94
- makeChunk('IEND', iend),
95
- ]);
96
- }
97
-
98
- // ── SKILL.md Parser ────────────────────────────────────────────────────────
99
-
100
- function parseFrontmatter(content) {
101
- const match = content.match(/^---\n([\s\S]*?)\n---/);
102
- if (!match) return null;
103
-
104
- const yaml = match[1];
105
- const result = {};
106
-
107
- // Simple YAML parser for frontmatter fields
108
- let currentKey = null;
109
- let blockValue = [];
110
- let inBlock = false;
111
-
112
- for (const line of yaml.split('\n')) {
113
- if (inBlock) {
114
- if (line.match(/^\s{2,}/) || line.trim() === '') {
115
- blockValue.push(line.replace(/^\s{2}/, ''));
116
- continue;
117
- } else {
118
- result[currentKey] = blockValue.join('\n').trim();
119
- inBlock = false;
120
- }
121
- }
122
-
123
- const kvMatch = line.match(/^(\w[\w.-]*)\s*:\s*(.*)/);
124
- if (kvMatch) {
125
- currentKey = kvMatch[1];
126
- const value = kvMatch[2].trim();
127
- if (value === '|' || value === '>') {
128
- inBlock = true;
129
- blockValue = [];
130
- } else {
131
- result[currentKey] = value.replace(/^["']|["']$/g, '');
132
- }
133
- }
134
- }
135
-
136
- if (inBlock && currentKey) {
137
- result[currentKey] = blockValue.join('\n').trim();
138
- }
139
-
140
- return result;
141
- }
142
-
143
- // ── Companion File Consolidation ───────────────────────────────────────────
144
-
145
- function consolidateCompanions(skillDir, skillName) {
146
- /**
147
- * If a skill has more than MAX_COMPANIONS_PER_SKILL companion files,
148
- * consolidate them into category-level reference files.
149
- * Returns an array of { relativePath, content } for the consolidated files.
150
- */
151
- const files = [];
152
- const allFiles = [];
153
-
154
- function walkDir(dir, prefix) {
155
- if (!fs.existsSync(dir)) return;
156
- const entries = fs.readdirSync(dir, { withFileTypes: true });
157
- for (const entry of entries) {
158
- const fullPath = path.join(dir, entry.name);
159
- const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
160
- if (entry.isDirectory()) {
161
- walkDir(fullPath, relPath);
162
- } else if (entry.name !== 'SKILL.md') {
163
- allFiles.push({ fullPath, relPath, name: entry.name });
164
- }
165
- }
166
- }
167
-
168
- walkDir(skillDir, '');
169
-
170
- if (allFiles.length <= MAX_COMPANIONS_PER_SKILL) {
171
- // No consolidation needed - return original files
172
- return allFiles.map(f => ({
173
- relativePath: f.relPath,
174
- content: fs.readFileSync(f.fullPath),
175
- }));
176
- }
177
-
178
- // Group by top-level directory
179
- const groups = {};
180
- for (const f of allFiles) {
181
- const parts = f.relPath.split('/');
182
- const topDir = parts.length > 1 ? parts[0] : '_root';
183
- const subDir = parts.length > 2 ? parts.slice(0, 2).join('/') : topDir;
184
- if (!groups[subDir]) groups[subDir] = [];
185
- groups[subDir].push(f);
186
- }
187
-
188
- // If groups are still too many, merge into top-level categories
189
- if (Object.keys(groups).length > MAX_COMPANIONS_PER_SKILL) {
190
- const topGroups = {};
191
- for (const f of allFiles) {
192
- const topDir = f.relPath.split('/')[0] || '_root';
193
- if (!topGroups[topDir]) topGroups[topDir] = [];
194
- topGroups[topDir].push(f);
195
- }
196
-
197
- for (const [dir, dirFiles] of Object.entries(topGroups)) {
198
- if (dir === '_root') {
199
- for (const f of dirFiles) {
200
- files.push({
201
- relativePath: f.relPath,
202
- content: fs.readFileSync(f.fullPath),
203
- });
204
- }
205
- continue;
206
- }
207
-
208
- // Consolidate all files under this dir into one reference file
209
- let consolidated = `# ${dir}\n\nConsolidated reference for ${skillName}.\n\n`;
210
- for (const f of dirFiles) {
211
- const content = fs.readFileSync(f.fullPath, 'utf-8');
212
- const basename = path.basename(f.name, path.extname(f.name));
213
- consolidated += `---\n\n## ${basename}\n\n${content}\n\n`;
214
- }
215
-
216
- files.push({
217
- relativePath: `references/${dir}.md`,
218
- content: Buffer.from(consolidated, 'utf-8'),
219
- });
220
- }
221
- } else {
222
- // Consolidate sub-directories into category files
223
- for (const [subDir, subFiles] of Object.entries(groups)) {
224
- if (subDir === '_root' || subFiles.length <= 1) {
225
- for (const f of subFiles) {
226
- files.push({
227
- relativePath: f.relPath,
228
- content: fs.readFileSync(f.fullPath),
229
- });
230
- }
231
- continue;
232
- }
233
-
234
- let consolidated = `# ${subDir}\n\n`;
235
- for (const f of subFiles) {
236
- const content = fs.readFileSync(f.fullPath, 'utf-8');
237
- const basename = path.basename(f.name, path.extname(f.name));
238
- consolidated += `---\n\n## ${basename}\n\n${content}\n\n`;
239
- }
240
-
241
- files.push({
242
- relativePath: `references/${subDir.replace(/\//g, '-')}.md`,
243
- content: Buffer.from(consolidated, 'utf-8'),
244
- });
245
- }
246
- }
247
-
248
- return files;
249
- }
250
-
251
- // ── ZIP Builder (uses system zip or tar) ───────────────────────────────────
252
-
253
- function createZip(outputPath, files) {
254
- /**
255
- * Creates a zip file from an array of { path, content } entries.
256
- * Uses a temp directory and system zip command.
257
- */
258
- const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'shikigami-cowork-'));
259
-
260
- try {
261
- // Write all files to temp directory
262
- for (const file of files) {
263
- const filePath = path.join(tmpDir, file.path);
264
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
265
- if (Buffer.isBuffer(file.content)) {
266
- fs.writeFileSync(filePath, file.content);
267
- } else {
268
- fs.writeFileSync(filePath, file.content, 'utf-8');
269
- }
270
- }
271
-
272
- // Create zip
273
- const absOutput = path.resolve(outputPath);
274
- try {
275
- execSync(`cd "${tmpDir}" && zip -r "${absOutput}" .`, { stdio: 'pipe' });
276
- } catch {
277
- // Fallback: try PowerShell (Windows)
278
- try {
279
- execSync(
280
- `powershell -Command "Compress-Archive -Path '${tmpDir}${path.sep}*' -DestinationPath '${absOutput}' -Force"`,
281
- { stdio: 'pipe' }
282
- );
283
- } catch {
284
- console.error('❌ zip command not found. Install zip or use PowerShell.');
285
- process.exit(1);
286
- }
287
- }
288
- } finally {
289
- fs.rmSync(tmpDir, { recursive: true, force: true });
290
- }
291
- }
292
-
293
- // ── Manifest Generator ─────────────────────────────────────────────────────
294
-
295
- function generateManifest(skills, version) {
296
- return {
297
- '$schema': 'https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.schema.json',
298
- manifestVersion: 'devPreview',
299
- version: version,
300
- id: APP_ID,
301
- packageName: PACKAGE_NAME,
302
- developer: {
303
- name: 'nahisaho',
304
- websiteUrl: 'https://github.com/nahisaho/SHIKIGAMI',
305
- privacyUrl: 'https://github.com/nahisaho/SHIKIGAMI/blob/main/LICENSE',
306
- termsOfUseUrl: 'https://github.com/nahisaho/SHIKIGAMI/blob/main/LICENSE',
307
- },
308
- name: {
309
- short: 'SHIKIGAMI',
310
- full: 'SHIKIGAMI - Deep Research & Consulting Agent Skills for Copilot Cowork',
311
- },
312
- description: {
313
- short: 'AI-powered deep research and consulting framework analysis',
314
- full: 'SHIKIGAMI provides 4 specialized Agent Skills for Copilot Cowork: '
315
- + 'deep research with hallucination prevention, 50+ consulting frameworks '
316
- + '(SWOT, PEST, 5Forces, BCG Matrix, etc.), structured report writing, '
317
- + 'and project planning with 5 Whys/JTBD purpose discovery.',
318
- },
319
- icons: {
320
- color: 'color.png',
321
- outline: 'outline.png',
322
- },
323
- accentColor: ACCENT_COLOR,
324
- agentSkills: skills.map(s => ({ folder: `./skills/${s.name}` })),
325
- };
326
- }
327
-
328
- // ── Validation ─────────────────────────────────────────────────────────────
329
-
330
- function validateSkill(skillDir, skillName) {
331
- const errors = [];
332
- const warnings = [];
333
-
334
- const skillMdPath = path.join(skillDir, 'SKILL.md');
335
- if (!fs.existsSync(skillMdPath)) {
336
- errors.push(`SKILL.md not found in ${skillName}`);
337
- return { errors, warnings };
338
- }
339
-
340
- const content = fs.readFileSync(skillMdPath, 'utf-8');
341
- const frontmatter = parseFrontmatter(content);
342
-
343
- if (!frontmatter) {
344
- errors.push(`${skillName}: No YAML frontmatter found`);
345
- return { errors, warnings };
346
- }
347
-
348
- // Validate name
349
- if (!frontmatter.name) {
350
- errors.push(`${skillName}: Missing 'name' field in frontmatter`);
351
- } else {
352
- if (frontmatter.name !== skillName) {
353
- errors.push(`${skillName}: Frontmatter name '${frontmatter.name}' doesn't match folder '${skillName}'`);
354
- }
355
- if (frontmatter.name.length > 64) {
356
- errors.push(`${skillName}: Name exceeds 64 characters`);
357
- }
358
- if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(frontmatter.name)) {
359
- errors.push(`${skillName}: Name must be kebab-case`);
360
- }
361
- }
362
-
363
- // Validate description
364
- if (!frontmatter.description) {
365
- errors.push(`${skillName}: Missing 'description' field in frontmatter`);
366
- } else if (frontmatter.description.length > 1024) {
367
- warnings.push(`${skillName}: Description exceeds 1024 characters (${frontmatter.description.length})`);
368
- }
369
-
370
- // Count companion files
371
- const companions = consolidateCompanions(skillDir, skillName);
372
- if (companions.length > MAX_COMPANIONS_PER_SKILL) {
373
- errors.push(`${skillName}: ${companions.length} companion files after consolidation (max ${MAX_COMPANIONS_PER_SKILL})`);
374
- }
375
-
376
- const totalSize = companions.reduce((sum, f) => sum + f.content.length, 0);
377
- if (totalSize > MAX_COMPANION_TOTAL) {
378
- errors.push(`${skillName}: Total companion size ${(totalSize / 1024 / 1024).toFixed(1)}MB exceeds 10MB limit`);
379
- }
380
-
381
- const oversized = companions.filter(f => f.content.length > MAX_COMPANION_SIZE);
382
- for (const f of oversized) {
383
- errors.push(`${skillName}: ${f.relativePath} exceeds 5MB limit`);
384
- }
385
-
386
- return { errors, warnings, companions, frontmatter };
387
- }
388
-
389
- // ── Main ───────────────────────────────────────────────────────────────────
390
-
391
- function parseArgs() {
392
- const args = process.argv.slice(2);
393
- return {
394
- out: args.includes('--out') ? args[args.indexOf('--out') + 1] : './dist',
395
- check: args.includes('--check') || args.includes('--dry-run'),
396
- help: args.includes('--help') || args.includes('-h'),
397
- };
398
- }
399
-
400
- function showHelp() {
401
- console.log(`
402
- 🎭 SHIKIGAMI Cowork Plugin Builder
403
-
404
- Builds a Microsoft 365 Copilot Cowork plugin package (.zip)
405
-
406
- Usage: npx shikigami cowork [options]
407
-
408
- Options:
409
- --out <dir> Output directory (default: ./dist)
410
- --check Validation only (dry-run)
411
- -h, --help Show this help
412
-
413
- Output:
414
- Creates shikigami-cowork-plugin.zip ready for M365 Admin Center upload.
415
-
416
- Reference:
417
- https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development
418
- `);
419
- }
420
-
421
- function build() {
422
- const { out, check, help } = parseArgs();
423
-
424
- if (help) {
425
- showHelp();
426
- process.exit(0);
427
- }
428
-
429
- console.log('');
430
- console.log('🎭 SHIKIGAMI Cowork Plugin Builder');
431
- console.log('====================================');
432
- console.log('');
433
-
434
- // Locate skills directory
435
- const packageDir = path.resolve(__dirname, '..');
436
- let skillsRoot = path.join(packageDir, 'github-assets', 'skills');
437
-
438
- // If running from installed package, check node_modules path
439
- if (!fs.existsSync(skillsRoot)) {
440
- skillsRoot = path.join(packageDir, '.github', 'skills');
441
- }
442
- // Also check project's .github/skills (after init)
443
- if (!fs.existsSync(skillsRoot)) {
444
- skillsRoot = path.join(process.cwd(), '.github', 'skills');
445
- }
446
-
447
- if (!fs.existsSync(skillsRoot)) {
448
- console.error('❌ Skills directory not found.');
449
- console.error(' Run `npx shikigami init` first to copy skills to your project.');
450
- process.exit(1);
451
- }
452
-
453
- // Read package version
454
- let version = '1.0.0';
455
- try {
456
- const pkg = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf-8'));
457
- version = pkg.version || version;
458
- } catch { /* ignore */ }
459
-
460
- // Discover skills
461
- const skillDirs = fs.readdirSync(skillsRoot, { withFileTypes: true })
462
- .filter(d => d.isDirectory() && d.name.startsWith('shikigami-'))
463
- .map(d => d.name);
464
-
465
- if (skillDirs.length === 0) {
466
- console.error('❌ No skills found (expected shikigami-* directories)');
467
- process.exit(1);
468
- }
469
-
470
- console.log(`📂 Skills root: ${skillsRoot}`);
471
- console.log(`📦 Found ${skillDirs.length} skills`);
472
- console.log('');
473
-
474
- // Validate all skills
475
- let hasErrors = false;
476
- const validatedSkills = [];
477
-
478
- for (const skillName of skillDirs) {
479
- const skillDir = path.join(skillsRoot, skillName);
480
- const result = validateSkill(skillDir, skillName);
481
-
482
- if (result.errors.length > 0) {
483
- hasErrors = true;
484
- for (const err of result.errors) console.log(` ❌ ${err}`);
485
- }
486
- for (const warn of result.warnings) console.log(` ⚠️ ${warn}`);
487
-
488
- if (result.errors.length === 0) {
489
- const companionCount = result.companions ? result.companions.length : 0;
490
- console.log(` ✅ ${skillName} (${companionCount} companion files)`);
491
- validatedSkills.push({
492
- name: skillName,
493
- dir: skillDir,
494
- companions: result.companions,
495
- frontmatter: result.frontmatter,
496
- });
497
- }
498
- }
499
-
500
- console.log('');
501
-
502
- if (hasErrors) {
503
- console.error('❌ Validation failed. Fix errors above before building.');
504
- process.exit(1);
505
- }
506
-
507
- if (validatedSkills.length > MAX_SKILLS) {
508
- console.error(`❌ Too many skills: ${validatedSkills.length} (max ${MAX_SKILLS})`);
509
- process.exit(1);
510
- }
511
-
512
- if (check) {
513
- console.log('✅ Validation passed (dry-run mode)');
514
- console.log(` ${validatedSkills.length} skills ready for packaging`);
515
- process.exit(0);
516
- }
517
-
518
- // Build package
519
- console.log('📦 Building Cowork plugin package...');
520
-
521
- const zipFiles = [];
522
-
523
- // 1. Generate manifest.json
524
- const manifest = generateManifest(validatedSkills, version);
525
- zipFiles.push({
526
- path: 'manifest.json',
527
- content: JSON.stringify(manifest, null, 2),
528
- });
529
- console.log(' ✅ manifest.json');
530
-
531
- // 2. Generate icons
532
- const colorPng = createMinimalPNG(192, 192, 107, 70, 193); // Purple (#6B46C1)
533
- const outlinePng = createMinimalPNG(32, 32, 107, 70, 193);
534
- zipFiles.push({ path: 'color.png', content: colorPng });
535
- zipFiles.push({ path: 'outline.png', content: outlinePng });
536
- console.log(' ✅ color.png (192×192)');
537
- console.log(' ✅ outline.png (32×32)');
538
-
539
- // 3. Copy skills with consolidation
540
- for (const skill of validatedSkills) {
541
- // Copy SKILL.md
542
- const skillMd = fs.readFileSync(path.join(skill.dir, 'SKILL.md'));
543
- zipFiles.push({
544
- path: `skills/${skill.name}/SKILL.md`,
545
- content: skillMd,
546
- });
547
-
548
- // Copy companion files (already consolidated)
549
- for (const companion of skill.companions) {
550
- zipFiles.push({
551
- path: `skills/${skill.name}/${companion.relativePath}`,
552
- content: companion.content,
553
- });
554
- }
555
-
556
- console.log(` ✅ skills/${skill.name}/ (${skill.companions.length + 1} files)`);
557
- }
558
-
559
- // 4. Create ZIP
560
- const outDir = path.resolve(out);
561
- fs.mkdirSync(outDir, { recursive: true });
562
- const zipPath = path.join(outDir, 'shikigami-cowork-plugin.zip');
563
-
564
- // Remove old zip if exists
565
- if (fs.existsSync(zipPath)) {
566
- fs.unlinkSync(zipPath);
567
- }
568
-
569
- createZip(zipPath, zipFiles);
570
-
571
- const zipSize = fs.statSync(zipPath).size;
572
- console.log('');
573
- console.log(`✅ Plugin package created: ${zipPath}`);
574
- console.log(` Size: ${(zipSize / 1024).toFixed(1)} KB`);
575
- console.log(` Skills: ${validatedSkills.length}`);
576
- console.log(` Version: ${version}`);
577
- console.log('');
578
- console.log('📝 Next steps:');
579
- console.log(' 1. Open M365 Admin Center > Manage Apps > Upload Custom App');
580
- console.log(' 2. Upload shikigami-cowork-plugin.zip');
581
- console.log(' 3. Open Cowork > Sources & Skills to verify');
582
- console.log('');
583
- console.log('📖 https://learn.microsoft.com/microsoft-365/copilot/cowork/cowork-plugin-development');
584
- console.log('');
585
- }
586
-
587
- build();