@nahisaho/satori 0.28.0 → 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.
- package/bin/satori.js +314 -13
- 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 });
|
|
@@ -77,6 +79,7 @@ Usage:
|
|
|
77
79
|
satori pipeline suggest Interactive pipeline recommendation
|
|
78
80
|
satori pipeline list List all available pipelines
|
|
79
81
|
satori pipeline custom <action> Manage custom pipelines
|
|
82
|
+
satori docs generate [--preview] Generate docs/ and docs/qiita/ files
|
|
80
83
|
satori validate [--verbose] Validate all SKILL.md files
|
|
81
84
|
satori stats Show skill/TU coverage statistics
|
|
82
85
|
satori help Show this help message
|
|
@@ -86,6 +89,7 @@ Options:
|
|
|
86
89
|
--force Overwrite existing .github/ directory
|
|
87
90
|
--dry-run Preview what would be installed without making changes
|
|
88
91
|
--verbose Show detailed validation output
|
|
92
|
+
--preview Show docs generation summary without writing files
|
|
89
93
|
|
|
90
94
|
Custom Pipelines:
|
|
91
95
|
satori pipeline custom list List custom pipelines
|
|
@@ -480,6 +484,54 @@ const PIPELINES = [
|
|
|
480
484
|
},
|
|
481
485
|
];
|
|
482
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
|
+
|
|
483
535
|
function pipelineSuggest() {
|
|
484
536
|
const readline = require('node:readline');
|
|
485
537
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -493,15 +545,20 @@ function pipelineSuggest() {
|
|
|
493
545
|
|
|
494
546
|
const input = await ask('何を解析しますか? キーワードや研究テーマを入力してください:\n> ');
|
|
495
547
|
const query = input.toLowerCase();
|
|
548
|
+
const normalizedQuery = normalizeKeyword(query);
|
|
496
549
|
|
|
497
|
-
// Score each pipeline by keyword match
|
|
550
|
+
// Score each pipeline by keyword match (with synonym support)
|
|
498
551
|
const scored = PIPELINES.map((p) => {
|
|
499
552
|
let score = 0;
|
|
500
553
|
for (const kw of p.keywords) {
|
|
554
|
+
const normalizedKw = normalizeKeyword(kw);
|
|
555
|
+
// 完全一致:2点、同義語マッチ:1.5点、部分一致:1点
|
|
501
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;
|
|
502
559
|
}
|
|
503
560
|
// Partial match on name
|
|
504
|
-
if (query.includes(p.name.toLowerCase()) || p.name.toLowerCase().includes(query)) score +=
|
|
561
|
+
if (query.includes(p.name.toLowerCase()) || p.name.toLowerCase().includes(query)) score += 0.5;
|
|
505
562
|
return { ...p, score };
|
|
506
563
|
})
|
|
507
564
|
.filter((p) => p.score > 0)
|
|
@@ -663,7 +720,7 @@ function validate() {
|
|
|
663
720
|
|
|
664
721
|
// ── Stats ──
|
|
665
722
|
|
|
666
|
-
function
|
|
723
|
+
function collectStats() {
|
|
667
724
|
const skillsDir = path.join(SOURCE_DIR, 'skills');
|
|
668
725
|
|
|
669
726
|
if (!fs.existsSync(skillsDir)) {
|
|
@@ -697,21 +754,142 @@ function stats() {
|
|
|
697
754
|
}
|
|
698
755
|
}
|
|
699
756
|
|
|
700
|
-
const
|
|
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
|
+
|
|
701
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();
|
|
702
783
|
|
|
703
784
|
console.log(`
|
|
704
|
-
📊 SATORI v${
|
|
705
|
-
|
|
706
|
-
スキル総数: ${totalSkills}
|
|
707
|
-
パイプライン数: ${
|
|
708
|
-
TU 連携スキル: ${tuLinked} (${coverage}%)
|
|
709
|
-
TU 未連携: ${totalSkills - tuLinked}
|
|
710
|
-
ユニーク TU キー: ${
|
|
711
|
-
コードブロック総数: ${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}
|
|
712
793
|
`);
|
|
713
794
|
}
|
|
714
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
|
+
|
|
715
893
|
// ── Skill Search / Info ──
|
|
716
894
|
|
|
717
895
|
function loadAllSkills() {
|
|
@@ -962,6 +1140,118 @@ function skillRecommend() {
|
|
|
962
1140
|
}
|
|
963
1141
|
}
|
|
964
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
|
+
|
|
965
1255
|
switch (COMMAND) {
|
|
966
1256
|
case 'init':
|
|
967
1257
|
init();
|
|
@@ -984,9 +1274,20 @@ switch (COMMAND) {
|
|
|
984
1274
|
pipelineSuggest();
|
|
985
1275
|
} else if (SUBCOMMAND === 'list') {
|
|
986
1276
|
pipelineList();
|
|
1277
|
+
} else if (SUBCOMMAND === 'custom') {
|
|
1278
|
+
pipelineCustom();
|
|
987
1279
|
} else {
|
|
988
1280
|
console.error(`Unknown pipeline subcommand: ${SUBCOMMAND || '(none)'}`);
|
|
989
|
-
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]');
|
|
990
1291
|
process.exit(1);
|
|
991
1292
|
}
|
|
992
1293
|
break;
|