@nahisaho/satori 0.27.1 → 0.28.1

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.
Files changed (2) hide show
  1. package/bin/satori.js +384 -14
  2. package/package.json +1 -1
package/bin/satori.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require('node:fs');
4
+ const os = require('node:os');
4
5
  const path = require('node:path');
5
6
 
6
7
  const COMMAND = process.argv[2];
@@ -9,6 +10,7 @@ const FLAGS = process.argv.slice(3);
9
10
 
10
11
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
11
12
  const SOURCE_DIR = path.join(PACKAGE_ROOT, 'src', '.github');
13
+ const CUSTOM_PIPELINES_PATH = path.join(process.env.HOME || os.homedir(), '.satori', 'custom-pipelines.json');
12
14
 
13
15
  function copyDirSync(src, dest) {
14
16
  fs.mkdirSync(dest, { recursive: true });
@@ -73,8 +75,11 @@ Usage:
73
75
  satori init [--force] [--dry-run] Install .github/ skills into current directory
74
76
  satori skill search <query> Search skills by keyword
75
77
  satori skill info <name> Show detailed skill information
78
+ satori skill recommend <name> Get similar/related skills
76
79
  satori pipeline suggest Interactive pipeline recommendation
77
80
  satori pipeline list List all available pipelines
81
+ satori pipeline custom <action> Manage custom pipelines
82
+ satori docs generate [--preview] Generate docs/ and docs/qiita/ files
78
83
  satori validate [--verbose] Validate all SKILL.md files
79
84
  satori stats Show skill/TU coverage statistics
80
85
  satori help Show this help message
@@ -84,6 +89,12 @@ Options:
84
89
  --force Overwrite existing .github/ directory
85
90
  --dry-run Preview what would be installed without making changes
86
91
  --verbose Show detailed validation output
92
+ --preview Show docs generation summary without writing files
93
+
94
+ Custom Pipelines:
95
+ satori pipeline custom list List custom pipelines
96
+ satori pipeline custom add <path> Add custom pipeline from file
97
+ satori pipeline custom remove <id> Remove custom pipeline
87
98
  `);
88
99
  }
89
100
 
@@ -473,6 +484,54 @@ const PIPELINES = [
473
484
  },
474
485
  ];
475
486
 
487
+ // ── Synonym Dictionary ──
488
+ const SYNONYM_DICT = {
489
+ // 機械学習・AI
490
+ ml: ['machine learning', '機械学習', 'ML'],
491
+ ai: ['artificial intelligence', '人工知能', 'AI', 'AI'],
492
+ dl: ['deep learning', '深層学習', 'DL'],
493
+ 'neural network': ['NN', 'ニューラルネットワーク'],
494
+
495
+ // バイオインフォマティクス
496
+ bioinfo: ['バイオインフォマティクス', 'bioinformatics'],
497
+ genomics: ['ゲノミクス', 'ゲノム', 'genomics'],
498
+ seq: ['シーケンシング', 'sequencing'],
499
+ rna: ['RNA', 'RNA-seq', 'トランスクリプトーム'],
500
+ protein: ['プロテイン', 'タンパク質'],
501
+
502
+ // 創薬・化学
503
+ 'drug discovery': ['創薬', '創薬', 'drug-discovery'],
504
+ admet: ['ADMET', '薬物動態'],
505
+ docking: ['ドッキング', 'molecular docking'],
506
+ cheminformatics: ['ケモインフォマティクス'],
507
+
508
+ // データ分析
509
+ 'data analysis': ['データ解析', 'data analysis'],
510
+ statistics: ['統計', '統計学'],
511
+ visualization: ['可視化', 'ビジュアル'],
512
+ pipeline: ['パイプライン'],
513
+
514
+ // 医療・臨床
515
+ clinical: ['臨床', 'クリニカル'],
516
+ precision: ['精密医療', '精密'],
517
+ oncology: ['腫瘍学', 'がん'],
518
+ };
519
+
520
+ function normalizeKeyword(keyword) {
521
+ const lower = keyword.toLowerCase().trim();
522
+
523
+ // 同義語チェック
524
+ for (const [key, synonyms] of Object.entries(SYNONYM_DICT)) {
525
+ for (const syn of synonyms) {
526
+ if (lower.includes(syn.toLowerCase()) || syn.toLowerCase().includes(lower)) {
527
+ return key;
528
+ }
529
+ }
530
+ }
531
+
532
+ return lower;
533
+ }
534
+
476
535
  function pipelineSuggest() {
477
536
  const readline = require('node:readline');
478
537
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -486,15 +545,20 @@ function pipelineSuggest() {
486
545
 
487
546
  const input = await ask('何を解析しますか? キーワードや研究テーマを入力してください:\n> ');
488
547
  const query = input.toLowerCase();
548
+ const normalizedQuery = normalizeKeyword(query);
489
549
 
490
- // Score each pipeline by keyword match
550
+ // Score each pipeline by keyword match (with synonym support)
491
551
  const scored = PIPELINES.map((p) => {
492
552
  let score = 0;
493
553
  for (const kw of p.keywords) {
554
+ const normalizedKw = normalizeKeyword(kw);
555
+ // 完全一致:2点、同義語マッチ:1.5点、部分一致:1点
494
556
  if (query.includes(kw.toLowerCase())) score += 2;
557
+ else if (normalizedQuery === normalizedKw) score += 1.5;
558
+ else if (normalizedKw.includes(normalizedQuery) || query.includes(normalizedKw)) score += 1;
495
559
  }
496
560
  // Partial match on name
497
- if (query.includes(p.name.toLowerCase()) || p.name.toLowerCase().includes(query)) score += 1;
561
+ if (query.includes(p.name.toLowerCase()) || p.name.toLowerCase().includes(query)) score += 0.5;
498
562
  return { ...p, score };
499
563
  })
500
564
  .filter((p) => p.score > 0)
@@ -656,7 +720,7 @@ function validate() {
656
720
 
657
721
  // ── Stats ──
658
722
 
659
- function stats() {
723
+ function collectStats() {
660
724
  const skillsDir = path.join(SOURCE_DIR, 'skills');
661
725
 
662
726
  if (!fs.existsSync(skillsDir)) {
@@ -690,21 +754,142 @@ function stats() {
690
754
  }
691
755
  }
692
756
 
693
- const coverage = ((tuLinked / totalSkills) * 100).toFixed(1);
757
+ const pipelineBreakdown = {
758
+ domain: PIPELINES.filter((p) => typeof p.id === 'number').length,
759
+ cross: PIPELINES.filter((p) => p.domain === 'cross-domain').length,
760
+ industry: PIPELINES.filter((p) => p.domain === 'industry').length,
761
+ methodology: PIPELINES.filter((p) => p.domain === 'methodology').length,
762
+ };
763
+
694
764
  const pkg = require(path.join(PACKAGE_ROOT, 'package.json'));
765
+ const date = new Date().toISOString().slice(0, 10);
766
+ const coverage = ((tuLinked / totalSkills) * 100).toFixed(1);
767
+
768
+ return {
769
+ version: pkg.version,
770
+ date,
771
+ totalSkills,
772
+ pipelinesCount: PIPELINES.length,
773
+ pipelineBreakdown,
774
+ tuLinked,
775
+ tuKeysCount: allTuKeys.size,
776
+ totalCodeBlocks,
777
+ coverage,
778
+ };
779
+ }
780
+
781
+ function stats() {
782
+ const summary = collectStats();
695
783
 
696
784
  console.log(`
697
- 📊 SATORI v${pkg.version} — 統計
698
-
699
- スキル総数: ${totalSkills}
700
- パイプライン数: ${PIPELINES.length}
701
- TU 連携スキル: ${tuLinked} (${coverage}%)
702
- TU 未連携: ${totalSkills - tuLinked}
703
- ユニーク TU キー: ${allTuKeys.size}
704
- コードブロック総数: ${totalCodeBlocks}
785
+ 📊 SATORI v${summary.version} — 統計
786
+
787
+ スキル総数: ${summary.totalSkills}
788
+ パイプライン数: ${summary.pipelinesCount}
789
+ TU 連携スキル: ${summary.tuLinked} (${summary.coverage}%)
790
+ TU 未連携: ${summary.totalSkills - summary.tuLinked}
791
+ ユニーク TU キー: ${summary.tuKeysCount}
792
+ コードブロック総数: ${summary.totalCodeBlocks}
705
793
  `);
706
794
  }
707
795
 
796
+ // ── Docs Generate ──
797
+
798
+ function updateReverseIndexDoc(content, summary) {
799
+ const pipelineLine = `| パイプライン数 | **${summary.pipelinesCount}** (ドメイン ${summary.pipelineBreakdown.domain} + クロスドメイン ${summary.pipelineBreakdown.cross} + インダストリー ${summary.pipelineBreakdown.industry} + メソドロジー ${summary.pipelineBreakdown.methodology}) |`;
800
+
801
+ return content
802
+ .replace(/\n\| SATORI バージョン \| \*\*v[^*]+\*\* \|/g, `\n| SATORI バージョン | **v${summary.version}** |`)
803
+ .replace(/\n\| 生成日 \| .* \|/g, `\n| 生成日 | ${summary.date} |`)
804
+ .replace(/\n\| スキル数 \| \*\*\d+\*\* \|/g, `\n| スキル数 | **${summary.totalSkills}** |`)
805
+ .replace(/\n\| パイプライン数 \| .* \|/g, `\n${pipelineLine}`)
806
+ .replace(
807
+ /\n\| ToolUniverse 連携スキル数 \| \*\*\d+\*\* \|/g,
808
+ `\n| ToolUniverse 連携スキル数 | **${summary.tuLinked}** |`,
809
+ )
810
+ .replace(
811
+ /\n\| ToolUniverse キー数(ユニーク) \| \*\*\d+\*\* \|/g,
812
+ `\n| ToolUniverse キー数(ユニーク) | **${summary.tuKeysCount}** |`,
813
+ );
814
+ }
815
+
816
+ function updatePipelineExamplesDoc(content, summary) {
817
+ const pipelineLine = `| 掲載パイプライン数 | ${summary.pipelineBreakdown.domain} ドメイン + ${summary.pipelineBreakdown.cross} クロスドメイン + ${summary.pipelineBreakdown.industry} インダストリー + ${summary.pipelineBreakdown.methodology} メソドロジー = **${summary.pipelinesCount}** |`;
818
+
819
+ return content
820
+ .replace(
821
+ /^> \*\*SATORI v[^*]+\*\* — .*$/m,
822
+ `> **SATORI v${summary.version}** — ${summary.totalSkills} スキル + ${summary.pipelinesCount} パイプラインの連携レシピ集`,
823
+ )
824
+ .replace(/\n\| 生成日 \| .* \|/g, `\n| 生成日 | ${summary.date} |`)
825
+ .replace(/\n\| 対象バージョン \| .* \|/g, `\n| 対象バージョン | v${summary.version} |`)
826
+ .replace(/\n\| 掲載パイプライン数 \| .* \|/g, `\n${pipelineLine}`)
827
+ .replace(
828
+ /\n\| スキル総数 \| \d+ \(.*\) \|/g,
829
+ `\n| スキル総数 | ${summary.totalSkills} (\`src/.github/skills/scientific-*/SKILL.md\`) |`,
830
+ )
831
+ .replace(/\n\| ToolUniverse キー数 \| .* \|/g, `\n| ToolUniverse キー数 | ${summary.tuKeysCount} (ユニーク) |`);
832
+ }
833
+
834
+ function updateQiitaReverseIndexDoc(content, summary) {
835
+ const title = `title: 【SATORI v${summary.version}】${summary.totalSkills}スキル×${summary.pipelinesCount}パイプライン逆引き辞書 完全索引`;
836
+ const intro = `**[SATORI](https://github.com/nahisaho/satori)** は **GitHub Copilot** 上で動作する、${summary.totalSkills} の専門スキルと ${summary.pipelinesCount} の統合パイプライン(${summary.pipelineBreakdown.domain} ドメイン + ${summary.pipelineBreakdown.cross} クロスドメイン + ${summary.pipelineBreakdown.industry} インダストリー + ${summary.pipelineBreakdown.methodology} メソドロジー)により、仮説構築から論文出版まで、あらゆる科学研究ワークフローを自動化するフレームワークです。`;
837
+
838
+ return content.replace(/^title: .+$/m, title).replace(/^\*\*\[SATORI\][\s\S]*?フレームワークです。$/m, intro);
839
+ }
840
+
841
+ function updateQiitaPipelineExamplesDoc(content, summary) {
842
+ const title = `title: 【SATORI v${summary.version}】${summary.totalSkills}スキル×${summary.pipelinesCount}パイプラインで実現する科学研究自動化 完全ガイド`;
843
+ const intro = `**[SATORI](https://github.com/nahisaho/satori)** は **GitHub Copilot** 上で動作する、${summary.totalSkills} の専門スキルを組み合わせて構築した ${summary.pipelinesCount} 個のパイプライン(${summary.pipelineBreakdown.domain} ドメイン + ${summary.pipelineBreakdown.cross} クロスドメイン + ${summary.pipelineBreakdown.industry} インダストリー + ${summary.pipelineBreakdown.methodology} メソドロジー)により、仮説構築から論文出版まで、あらゆる科学研究ワークフローを自動化するフレームワークです。`;
844
+
845
+ return content.replace(/^title: .+$/m, title).replace(/^\*\*\[SATORI\][\s\S]*?フレームワークです。$/m, intro);
846
+ }
847
+
848
+ function applyDocUpdate(filePath, updater, summary, preview) {
849
+ if (!fs.existsSync(filePath)) {
850
+ console.error(`Error: ドキュメントが見つかりません: ${filePath}`);
851
+ return false;
852
+ }
853
+ const content = fs.readFileSync(filePath, 'utf-8');
854
+ const updated = updater(content, summary);
855
+ if (updated !== content && !preview) {
856
+ fs.writeFileSync(filePath, updated);
857
+ }
858
+ return updated !== content;
859
+ }
860
+
861
+ function docsGenerate() {
862
+ const summary = collectStats();
863
+ const docsFlags = process.argv.slice(4);
864
+ const preview = docsFlags.includes('--preview');
865
+
866
+ const targets = [
867
+ { path: path.join(PACKAGE_ROOT, 'docs', 'SATORI_REVERSE_INDEX.md'), update: updateReverseIndexDoc },
868
+ { path: path.join(PACKAGE_ROOT, 'docs', 'SATORI_PIPELINE_EXAMPLES.md'), update: updatePipelineExamplesDoc },
869
+ {
870
+ path: path.join(PACKAGE_ROOT, 'docs', 'qiita', 'SATORI_REVERSE_INDEX_QIITA.md'),
871
+ update: updateQiitaReverseIndexDoc,
872
+ },
873
+ {
874
+ path: path.join(PACKAGE_ROOT, 'docs', 'qiita', 'SATORI_PIPELINE_EXAMPLES_QIITA.md'),
875
+ update: updateQiitaPipelineExamplesDoc,
876
+ },
877
+ ];
878
+
879
+ let updatedCount = 0;
880
+ for (const target of targets) {
881
+ if (applyDocUpdate(target.path, target.update, summary, preview)) {
882
+ updatedCount++;
883
+ }
884
+ }
885
+
886
+ if (preview) {
887
+ console.log(`✔ docs generate (preview) 完了: ${updatedCount} 件更新予定`);
888
+ } else {
889
+ console.log(`✔ docs generate 完了: ${updatedCount} 件更新`);
890
+ }
891
+ }
892
+
708
893
  // ── Skill Search / Info ──
709
894
 
710
895
  function loadAllSkills() {
@@ -895,6 +1080,178 @@ function skillInfo() {
895
1080
  console.log(`ファイル: src/.github/skills/${dirName}/SKILL.md`);
896
1081
  }
897
1082
 
1083
+ function skillRecommend() {
1084
+ const name = process.argv[4];
1085
+ if (!name) {
1086
+ console.error('Error: スキル名を指定してください。');
1087
+ console.log('Usage: satori skill recommend <name>');
1088
+ process.exit(1);
1089
+ }
1090
+
1091
+ const skillsDir = path.join(SOURCE_DIR, 'skills');
1092
+ const dirName = name.startsWith('scientific-') ? name : `scientific-${name}`;
1093
+ const filePath = path.join(skillsDir, dirName, 'SKILL.md');
1094
+
1095
+ if (!fs.existsSync(filePath)) {
1096
+ console.error(`Error: スキル "${name}" が見つかりません。`);
1097
+ process.exit(1);
1098
+ }
1099
+
1100
+ // スキル名から短縮形を取得
1101
+ const shortName = dirName.replace('scientific-', '');
1102
+
1103
+ // 全パイプラインから、このスキルを使用するパイプラインを検出
1104
+ const usedInPipelines = PIPELINES.filter((p) => p.skills.includes(shortName));
1105
+
1106
+ // これらのパイプラインで使用される他のスキルをカウント
1107
+ const skillCooccurrence = {};
1108
+ for (const p of usedInPipelines) {
1109
+ const skillNames = p.skills.split(' → ').map((s) => s.trim());
1110
+ for (const sk of skillNames) {
1111
+ if (sk !== shortName && !skillCooccurrence[sk]) {
1112
+ skillCooccurrence[sk] = 0;
1113
+ }
1114
+ if (sk !== shortName) {
1115
+ skillCooccurrence[sk]++;
1116
+ }
1117
+ }
1118
+ }
1119
+
1120
+ // スコア降順にソート
1121
+ const recommended = Object.entries(skillCooccurrence)
1122
+ .map(([skill, count]) => ({ skill, count }))
1123
+ .sort((a, b) => b.count - a.count)
1124
+ .slice(0, 5);
1125
+
1126
+ console.log(`\n🎯 "${name}" に関連するスキル\n`);
1127
+
1128
+ if (usedInPipelines.length > 0) {
1129
+ console.log(`このスキルが使用されるパイプライン: ${usedInPipelines.length} 件\n`);
1130
+ console.log('関連スキル:');
1131
+ for (let i = 0; i < recommended.length; i++) {
1132
+ const { skill, count } = recommended[i];
1133
+ console.log(` ${i + 1}. ${skill} (${count} パイプラインで併用)`);
1134
+ }
1135
+ console.log('');
1136
+ console.log('詳細は `satori skill info <related-skill>` で確認できます。');
1137
+ } else {
1138
+ console.log('❌ このスキルが使用されるパイプラインが見つかりませんでした。');
1139
+ console.log('すべてのパイプラインは `satori pipeline list` で確認できます。');
1140
+ }
1141
+ }
1142
+
1143
+ // ── Custom Pipeline Management ──
1144
+
1145
+ function loadCustomPipelines() {
1146
+ if (!fs.existsSync(CUSTOM_PIPELINES_PATH)) {
1147
+ return [];
1148
+ }
1149
+ try {
1150
+ const content = fs.readFileSync(CUSTOM_PIPELINES_PATH, 'utf-8');
1151
+ const data = JSON.parse(content);
1152
+ return data.customPipelines || [];
1153
+ } catch (err) {
1154
+ console.error('Warning: Failed to load custom pipelines:', err.message);
1155
+ return [];
1156
+ }
1157
+ }
1158
+
1159
+ function saveCustomPipelines(pipelines) {
1160
+ const dir = path.dirname(CUSTOM_PIPELINES_PATH);
1161
+ fs.mkdirSync(dir, { recursive: true });
1162
+ fs.writeFileSync(CUSTOM_PIPELINES_PATH, JSON.stringify({ customPipelines: pipelines }, null, 2));
1163
+ }
1164
+
1165
+ function pipelineCustom() {
1166
+ const action = process.argv[4];
1167
+ const customPipelines = loadCustomPipelines();
1168
+
1169
+ if (action === 'list') {
1170
+ listCustomPipelines(customPipelines);
1171
+ } else if (action === 'add') {
1172
+ addCustomPipeline(customPipelines);
1173
+ } else if (action === 'remove') {
1174
+ removeCustomPipeline(customPipelines);
1175
+ } else {
1176
+ console.error(`Unknown custom pipeline action: ${action || '(none)'}`);
1177
+ console.log('Usage: satori pipeline custom list | add <file> | remove <id>');
1178
+ process.exit(1);
1179
+ }
1180
+ }
1181
+
1182
+ function listCustomPipelines(pipelines) {
1183
+ if (pipelines.length === 0) {
1184
+ console.log('\n📋 カスタムパイプライン: 0 件\n');
1185
+ console.log('新しいカスタムパイプラインを追加するには:');
1186
+ console.log(' satori pipeline custom add <file>');
1187
+ return;
1188
+ }
1189
+
1190
+ console.log(`\n📋 カスタムパイプライン一覧 (${pipelines.length} 件)\n`);
1191
+ for (const p of pipelines) {
1192
+ console.log(` 🔧 [${p.id}] ${p.name}`);
1193
+ console.log(` スキル連鎖: ${p.skills}`);
1194
+ console.log('');
1195
+ }
1196
+ }
1197
+
1198
+ function addCustomPipeline(pipelines) {
1199
+ const filePath = process.argv[5];
1200
+ if (!filePath) {
1201
+ console.error('Error: ファイルパスを指定してください。');
1202
+ console.log('Usage: satori pipeline custom add <file>');
1203
+ process.exit(1);
1204
+ }
1205
+
1206
+ if (!fs.existsSync(filePath)) {
1207
+ console.error(`Error: ファイルが見つかりません: ${filePath}`);
1208
+ process.exit(1);
1209
+ }
1210
+
1211
+ try {
1212
+ const content = fs.readFileSync(filePath, 'utf-8');
1213
+ const pipelineData = JSON.parse(content);
1214
+
1215
+ // バリデーション
1216
+ if (!pipelineData.id || !pipelineData.name || !pipelineData.skills) {
1217
+ console.error('Error: パイプラインは id, name, skills を含む必要があります。');
1218
+ process.exit(1);
1219
+ }
1220
+
1221
+ // 重複チェック
1222
+ if (pipelines.some((p) => p.id === pipelineData.id)) {
1223
+ console.error(`Error: ID "${pipelineData.id}" は既に存在します。`);
1224
+ process.exit(1);
1225
+ }
1226
+
1227
+ pipelines.push(pipelineData);
1228
+ saveCustomPipelines(pipelines);
1229
+ console.log(`✔ カスタムパイプライン "${pipelineData.name}" を追加しました。`);
1230
+ } catch (err) {
1231
+ console.error('Error: パイプラインファイルの解析に失敗しました:', err.message);
1232
+ process.exit(1);
1233
+ }
1234
+ }
1235
+
1236
+ function removeCustomPipeline(pipelines) {
1237
+ const id = process.argv[5];
1238
+ if (!id) {
1239
+ console.error('Error: パイプライン ID を指定してください。');
1240
+ console.log('Usage: satori pipeline custom remove <id>');
1241
+ process.exit(1);
1242
+ }
1243
+
1244
+ const index = pipelines.findIndex((p) => p.id === id);
1245
+ if (index === -1) {
1246
+ console.error(`Error: パイプライン "${id}" が見つかりません。`);
1247
+ process.exit(1);
1248
+ }
1249
+
1250
+ const removed = pipelines.splice(index, 1)[0];
1251
+ saveCustomPipelines(pipelines);
1252
+ console.log(`✔ カスタムパイプライン "${removed.name}" を削除しました。`);
1253
+ }
1254
+
898
1255
  switch (COMMAND) {
899
1256
  case 'init':
900
1257
  init();
@@ -904,9 +1261,11 @@ switch (COMMAND) {
904
1261
  skillSearch();
905
1262
  } else if (SUBCOMMAND === 'info') {
906
1263
  skillInfo();
1264
+ } else if (SUBCOMMAND === 'recommend') {
1265
+ skillRecommend();
907
1266
  } else {
908
1267
  console.error(`Unknown skill subcommand: ${SUBCOMMAND || '(none)'}`);
909
- console.log('Usage: satori skill search <query> | satori skill info <name>');
1268
+ console.log('Usage: satori skill search <query> | satori skill info <name> | satori skill recommend <name>');
910
1269
  process.exit(1);
911
1270
  }
912
1271
  break;
@@ -915,9 +1274,20 @@ switch (COMMAND) {
915
1274
  pipelineSuggest();
916
1275
  } else if (SUBCOMMAND === 'list') {
917
1276
  pipelineList();
1277
+ } else if (SUBCOMMAND === 'custom') {
1278
+ pipelineCustom();
918
1279
  } else {
919
1280
  console.error(`Unknown pipeline subcommand: ${SUBCOMMAND || '(none)'}`);
920
- console.log('Usage: satori pipeline suggest | satori pipeline list');
1281
+ console.log('Usage: satori pipeline suggest | satori pipeline list | satori pipeline custom list|add|remove');
1282
+ process.exit(1);
1283
+ }
1284
+ break;
1285
+ case 'docs':
1286
+ if (SUBCOMMAND === 'generate') {
1287
+ docsGenerate();
1288
+ } else {
1289
+ console.error(`Unknown docs subcommand: ${SUBCOMMAND || '(none)'}`);
1290
+ console.log('Usage: satori docs generate [--preview]');
921
1291
  process.exit(1);
922
1292
  }
923
1293
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nahisaho/satori",
3
- "version": "0.27.1",
3
+ "version": "0.28.1",
4
4
  "description": "SATORI — Agent Skills for Science. GitHub Copilot Agent Skills collection for scientific data analysis.",
5
5
  "main": "index.js",
6
6
  "bin": {