@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 +0 -17
- package/README.md +0 -19
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/scripts/cli.js +6 -6
- package/scripts/init.js +15 -4
- package/scripts/setup-cowork.js +367 -0
- package/scripts/build-cowork-plugin.js +0 -587
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
|
package/mcp-server/package.json
CHANGED
package/package.json
CHANGED
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]
|
|
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 #
|
|
46
|
-
npx shikigami cowork --
|
|
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.
|
|
74
|
+
// Cowork command (v2.0.0)
|
|
76
75
|
if (command === 'cowork') {
|
|
77
|
-
require('./
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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();
|