@sswl/ai-manager 0.1.0 → 0.1.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/README.md +3 -1
- package/lib/cli.js +279 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,9 @@ SSWL AI Manager CLI。
|
|
|
16
16
|
```bash
|
|
17
17
|
npx @sswl/ai-manager
|
|
18
18
|
npx @sswl/ai-manager update
|
|
19
|
-
npx @sswl/ai-manager
|
|
19
|
+
npx @sswl/ai-manager use --tool claude-code --scope project --project /path/to/project --profile full-enterprise
|
|
20
|
+
npx @sswl/ai-manager profiles --tool claude-code
|
|
21
|
+
npx @sswl/ai-manager assets --tool codex
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
## Publish
|
package/lib/cli.js
CHANGED
|
@@ -36,6 +36,14 @@ function truncateText(text, width) {
|
|
|
36
36
|
return `${raw.slice(0, Math.max(0, width - 1))}…`;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function padPlainText(text, width) {
|
|
40
|
+
const raw = stripAnsi(text);
|
|
41
|
+
if (raw.length >= width) {
|
|
42
|
+
return truncateText(text, width);
|
|
43
|
+
}
|
|
44
|
+
return `${text}${" ".repeat(width - raw.length)}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
function usage(commandName = "ai-manager") {
|
|
40
48
|
console.log(`${color("SSWL AI Manager CLI", "bold")}
|
|
41
49
|
|
|
@@ -43,11 +51,17 @@ Usage:
|
|
|
43
51
|
${commandName}
|
|
44
52
|
${commandName} init [--repo-url URL] [--repo-dir PATH]
|
|
45
53
|
${commandName} sync [--repo-url URL] [--repo-dir PATH]
|
|
54
|
+
${commandName} use --tool TOOL [--scope project|global] [--project PATH] [--profile ID | --asset ID ...]
|
|
55
|
+
${commandName} update [--project PATH] [--tool TOOL]
|
|
56
|
+
${commandName} remove --tool TOOL [--scope project|global] [--project PATH]
|
|
57
|
+
${commandName} doctor [--project PATH]
|
|
58
|
+
${commandName} profiles [--tool TOOL] [--project PATH]
|
|
59
|
+
${commandName} assets [--tool TOOL] [--project PATH]
|
|
60
|
+
|
|
61
|
+
兼容命令:
|
|
46
62
|
${commandName} list [--project PATH]
|
|
47
63
|
${commandName} doctor [--project PATH]
|
|
48
64
|
${commandName} apply --tool TOOL [--scope project|global] [--project PATH] [--profile ID | --asset ID ...]
|
|
49
|
-
${commandName} update [--project PATH] [--tool TOOL]
|
|
50
|
-
${commandName} remove --tool TOOL [--scope project|global] [--project PATH]
|
|
51
65
|
`);
|
|
52
66
|
}
|
|
53
67
|
|
|
@@ -220,6 +234,18 @@ function createPrompt() {
|
|
|
220
234
|
}
|
|
221
235
|
|
|
222
236
|
return {
|
|
237
|
+
renderFrame,
|
|
238
|
+
async pause(label = "继续", subtitle = "按回车继续") {
|
|
239
|
+
renderFrame({
|
|
240
|
+
eyebrow: "流程暂停",
|
|
241
|
+
title: label,
|
|
242
|
+
subtitle,
|
|
243
|
+
bodyLines: [],
|
|
244
|
+
footerLines: ["回车继续"]
|
|
245
|
+
});
|
|
246
|
+
setRawMode(false);
|
|
247
|
+
await ask(`${color(">", "green")} ${label},直接回车继续: `);
|
|
248
|
+
},
|
|
223
249
|
async input(label, defaultValue = "", subtitle = "") {
|
|
224
250
|
renderFrame({
|
|
225
251
|
eyebrow: "文本输入",
|
|
@@ -618,8 +644,87 @@ function summarizeAssets(state, assetIds) {
|
|
|
618
644
|
});
|
|
619
645
|
}
|
|
620
646
|
|
|
647
|
+
function summarizeProjectState(state, projectRoot, currentTool = "") {
|
|
648
|
+
const projectTools = state.project?.tools || [];
|
|
649
|
+
const toolState = currentTool
|
|
650
|
+
? projectTools.find((tool) => tool.tool === currentTool) || state.tools.find((tool) => tool.tool === currentTool)
|
|
651
|
+
: null;
|
|
652
|
+
const summary = [
|
|
653
|
+
`项目: ${projectRoot}`,
|
|
654
|
+
`仓库: ${state.settings.repo_dir}`,
|
|
655
|
+
`模式: ${state.settings.asset_source_mode}`
|
|
656
|
+
];
|
|
657
|
+
if (toolState) {
|
|
658
|
+
summary.push(`工具: ${toolState.tool} / ${toolState.enabled ? `已装 ${toolState.installed_asset_ids.length} 项` : "未安装"}`);
|
|
659
|
+
}
|
|
660
|
+
return summary;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function buildWorkspaceBoardLines({
|
|
664
|
+
state,
|
|
665
|
+
projectRoot,
|
|
666
|
+
selectedTool = "",
|
|
667
|
+
scope = "",
|
|
668
|
+
profileId = "",
|
|
669
|
+
selectionMode = "",
|
|
670
|
+
inheritGlobal = false,
|
|
671
|
+
selectedAssetIds = [],
|
|
672
|
+
previewAssetIds = []
|
|
673
|
+
}) {
|
|
674
|
+
const lines = [];
|
|
675
|
+
const currentToolState = selectedTool
|
|
676
|
+
? (state.project?.tools || []).find((item) => item.tool === selectedTool) || state.tools.find((item) => item.tool === selectedTool)
|
|
677
|
+
: null;
|
|
678
|
+
lines.push(color("上下文", "dim"));
|
|
679
|
+
lines.push(` 项目 ${projectRoot || "-"}`);
|
|
680
|
+
lines.push(` 仓库 ${state.settings.repo_dir || "-"}`);
|
|
681
|
+
lines.push(` 模式 ${state.settings.asset_source_mode || "-"}`);
|
|
682
|
+
lines.push("");
|
|
683
|
+
lines.push(color("当前选择", "dim"));
|
|
684
|
+
lines.push(` 工具 ${selectedTool || "-"}`);
|
|
685
|
+
lines.push(` 作用域 ${scope ? (scope === "project" ? "项目级" : "全局") : "-"}`);
|
|
686
|
+
lines.push(` 档位/模式 ${profileId || (selectionMode === "custom" ? "自定义资产" : "-")}`);
|
|
687
|
+
lines.push(` 继承全局 ${scope === "project" ? (inheritGlobal ? "是" : "否") : "-"}`);
|
|
688
|
+
lines.push("");
|
|
689
|
+
lines.push(color("状态摘要", "dim"));
|
|
690
|
+
lines.push(` 已选资产 ${selectedAssetIds.length} 项`);
|
|
691
|
+
lines.push(` 实际安装 ${previewAssetIds.length} 项`);
|
|
692
|
+
if (currentToolState) {
|
|
693
|
+
lines.push(` 当前已装 ${(currentToolState.installed_asset_ids || []).length} 项`);
|
|
694
|
+
lines.push(` 检测结果 ${currentToolState.detected_installed ? "已检测到本机安装" : "未检测到本机安装"}`);
|
|
695
|
+
}
|
|
696
|
+
return lines;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function buildAssetsPreviewLines(state, assetIds, limit = 10) {
|
|
700
|
+
const items = summarizeAssets(state, assetIds);
|
|
701
|
+
if (items.length <= limit) {
|
|
702
|
+
return items.map((item) => ` • ${item}`);
|
|
703
|
+
}
|
|
704
|
+
return [
|
|
705
|
+
...items.slice(0, limit).map((item) => ` • ${item}`),
|
|
706
|
+
` … 共 ${items.length} 项`
|
|
707
|
+
];
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function buildTwoColumnLines(leftTitle, leftLines, rightTitle, rightLines) {
|
|
711
|
+
const leftBody = [color(leftTitle, "dim"), ...(leftLines.length ? leftLines : [" -"])];
|
|
712
|
+
const rightBody = [color(rightTitle, "dim"), ...(rightLines.length ? rightLines : [" -"])];
|
|
713
|
+
const totalRows = Math.max(leftBody.length, rightBody.length);
|
|
714
|
+
const lines = [];
|
|
715
|
+
const leftWidth = 45;
|
|
716
|
+
for (let index = 0; index < totalRows; index += 1) {
|
|
717
|
+
const left = padPlainText(leftBody[index] || "", leftWidth);
|
|
718
|
+
const right = rightBody[index] || "";
|
|
719
|
+
lines.push(`${left} ${color("│", "dim")} ${right}`);
|
|
720
|
+
}
|
|
721
|
+
return lines;
|
|
722
|
+
}
|
|
723
|
+
|
|
621
724
|
function filterProfilesByTool(state, tool) {
|
|
622
|
-
return (state.profiles || [])
|
|
725
|
+
return (state.profiles || [])
|
|
726
|
+
.filter((profile) => (profile.supported_tools || []).includes(tool))
|
|
727
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
623
728
|
}
|
|
624
729
|
|
|
625
730
|
function filterInstallableAssetsByTool(state, tool) {
|
|
@@ -628,6 +733,39 @@ function filterInstallableAssetsByTool(state, tool) {
|
|
|
628
733
|
.filter((asset) => asset.installable !== false);
|
|
629
734
|
}
|
|
630
735
|
|
|
736
|
+
function buildDiffSummary(baseAssetIds, finalAssetIds, selectedAssetIds) {
|
|
737
|
+
const base = new Set(baseAssetIds || []);
|
|
738
|
+
const finalSet = new Set(finalAssetIds || []);
|
|
739
|
+
const selected = new Set(selectedAssetIds || []);
|
|
740
|
+
return {
|
|
741
|
+
added: [...finalSet].filter((id) => !base.has(id)).sort(),
|
|
742
|
+
removed: [...base].filter((id) => !finalSet.has(id)).sort(),
|
|
743
|
+
kept: [...finalSet].filter((id) => base.has(id)).sort(),
|
|
744
|
+
autoDependencies: [...finalSet].filter((id) => !selected.has(id)).sort()
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async function selectAssetsForCustomMode(prompt, state, tool) {
|
|
749
|
+
const allAssets = filterInstallableAssetsByTool(state, tool);
|
|
750
|
+
const keyword = await prompt.input(
|
|
751
|
+
"资产筛选关键字",
|
|
752
|
+
"",
|
|
753
|
+
"可选。支持按资产 ID、类型、Domain、状态筛选;留空则展示全部可安装资产"
|
|
754
|
+
);
|
|
755
|
+
const normalizedKeyword = String(keyword || "").trim().toLowerCase();
|
|
756
|
+
const filteredAssets = normalizedKeyword
|
|
757
|
+
? allAssets.filter((asset) => [asset.id, asset.type, asset.domain, asset.status, asset.description].join(" ").toLowerCase().includes(normalizedKeyword))
|
|
758
|
+
: allAssets;
|
|
759
|
+
if (!filteredAssets.length) {
|
|
760
|
+
throw new Error(`没有命中关键字 "${normalizedKeyword}" 的可安装资产`);
|
|
761
|
+
}
|
|
762
|
+
return prompt.multiSelect("选择需要同步到项目的资产", filteredAssets.map((asset) => ({
|
|
763
|
+
label: `${asset.id}`,
|
|
764
|
+
hint: `${asset.type} / ${asset.domain} / ${asset.status}`,
|
|
765
|
+
value: asset.id
|
|
766
|
+
})));
|
|
767
|
+
}
|
|
768
|
+
|
|
631
769
|
async function interactiveInstall(options = {}) {
|
|
632
770
|
const prompt = createPrompt();
|
|
633
771
|
try {
|
|
@@ -640,6 +778,25 @@ async function interactiveInstall(options = {}) {
|
|
|
640
778
|
repoDir: syncedState.settings.repo_dir,
|
|
641
779
|
projectRoot
|
|
642
780
|
});
|
|
781
|
+
prompt.renderFrame({
|
|
782
|
+
eyebrow: "AI Manager Workspace",
|
|
783
|
+
title: "项目工作台",
|
|
784
|
+
subtitle: "这是当前项目的安装上下文。接下来会依次完成工具、作用域、档位和资产选择。",
|
|
785
|
+
bodyLines: buildTwoColumnLines(
|
|
786
|
+
"当前上下文",
|
|
787
|
+
buildWorkspaceBoardLines({ state, projectRoot }),
|
|
788
|
+
"下一步",
|
|
789
|
+
[
|
|
790
|
+
" 1. 选择目标工具",
|
|
791
|
+
" 2. 选择项目级或全局",
|
|
792
|
+
" 3. 选择档位或自定义资产",
|
|
793
|
+
" 4. 预览新增、依赖和移除项",
|
|
794
|
+
" 5. 确认后执行安装"
|
|
795
|
+
]
|
|
796
|
+
),
|
|
797
|
+
footerLines: ["Enter 继续"]
|
|
798
|
+
});
|
|
799
|
+
await prompt.pause("进入选择流程", "当前上下文已确认,按回车继续进入工具与作用域选择");
|
|
643
800
|
const projectTools = state.project?.tools || [];
|
|
644
801
|
const availableTools = projectTools.filter((toolState) => toolState.manageable && SUPPORTED_TOOLS.includes(toolState.tool));
|
|
645
802
|
printSection("工具选择");
|
|
@@ -657,6 +814,30 @@ async function interactiveInstall(options = {}) {
|
|
|
657
814
|
if (scope === "project" && (state.tools.find((item) => item.tool === selectedTool)?.installed_asset_ids || []).length) {
|
|
658
815
|
inheritGlobal = await prompt.confirm("项目级安装是否继承当前全局已安装资产", true);
|
|
659
816
|
}
|
|
817
|
+
prompt.renderFrame({
|
|
818
|
+
eyebrow: "Workspace Context",
|
|
819
|
+
title: "已确认安装上下文",
|
|
820
|
+
subtitle: "下面开始选择档位或自定义资产。",
|
|
821
|
+
bodyLines: buildTwoColumnLines(
|
|
822
|
+
"当前配置",
|
|
823
|
+
buildWorkspaceBoardLines({
|
|
824
|
+
state,
|
|
825
|
+
projectRoot,
|
|
826
|
+
selectedTool,
|
|
827
|
+
scope,
|
|
828
|
+
inheritGlobal
|
|
829
|
+
}),
|
|
830
|
+
"提示",
|
|
831
|
+
[
|
|
832
|
+
" • 选 profile 时会直接带出资产组合",
|
|
833
|
+
" • 自定义模式支持关键字筛选",
|
|
834
|
+
" • 依赖会在执行前自动补齐",
|
|
835
|
+
" • 项目级可按需继承全局安装"
|
|
836
|
+
]
|
|
837
|
+
),
|
|
838
|
+
footerLines: ["Enter 继续"]
|
|
839
|
+
});
|
|
840
|
+
await prompt.pause("继续选择资产", "当前工具与作用域已确认,按回车继续进入档位或资产选择");
|
|
660
841
|
const profiles = filterProfilesByTool(state, selectedTool);
|
|
661
842
|
printSection("资产组合");
|
|
662
843
|
const profileChoices = profiles.map((profile) => ({
|
|
@@ -674,12 +855,7 @@ async function interactiveInstall(options = {}) {
|
|
|
674
855
|
let selectionMode = "profile";
|
|
675
856
|
let planName = profileId;
|
|
676
857
|
if (profileId === "__custom__") {
|
|
677
|
-
|
|
678
|
-
selectedAssetIds = await prompt.multiSelect("选择需要同步到项目的资产", assets.map((asset) => ({
|
|
679
|
-
label: `${asset.id}`,
|
|
680
|
-
hint: `${asset.type} / ${asset.domain} / ${asset.status}`,
|
|
681
|
-
value: asset.id
|
|
682
|
-
})));
|
|
858
|
+
selectedAssetIds = await selectAssetsForCustomMode(prompt, state, selectedTool);
|
|
683
859
|
selectionMode = "custom";
|
|
684
860
|
planName = "custom-selection";
|
|
685
861
|
} else {
|
|
@@ -690,12 +866,46 @@ async function interactiveInstall(options = {}) {
|
|
|
690
866
|
? (state.tools.find((item) => item.tool === selectedTool)?.installed_asset_ids || [])
|
|
691
867
|
: [];
|
|
692
868
|
const previewAssetIds = resolveAssetDependencies(state.assets || [], normalizeAssetSet([...globalInstalled, ...selectedAssetIds]));
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
869
|
+
const diffSummary = buildDiffSummary(toolState.installed_asset_ids || [], previewAssetIds, selectedAssetIds);
|
|
870
|
+
prompt.renderFrame({
|
|
871
|
+
eyebrow: "Execution Preview",
|
|
872
|
+
title: "应用预览",
|
|
873
|
+
subtitle: "确认后会开始同步仓库、构建组合包并写入目标项目或全局目录。",
|
|
874
|
+
bodyLines: [
|
|
875
|
+
...buildTwoColumnLines(
|
|
876
|
+
"安装上下文",
|
|
877
|
+
buildWorkspaceBoardLines({
|
|
878
|
+
state,
|
|
879
|
+
projectRoot,
|
|
880
|
+
selectedTool,
|
|
881
|
+
scope,
|
|
882
|
+
profileId: selectionMode === "profile" ? profileId : "",
|
|
883
|
+
selectionMode,
|
|
884
|
+
inheritGlobal,
|
|
885
|
+
selectedAssetIds,
|
|
886
|
+
previewAssetIds
|
|
887
|
+
}),
|
|
888
|
+
"差异摘要",
|
|
889
|
+
[
|
|
890
|
+
` 新增 ${diffSummary.added.length} 项`,
|
|
891
|
+
` 自动依赖 ${diffSummary.autoDependencies.length} 项`,
|
|
892
|
+
` 移除 ${diffSummary.removed.length} 项`,
|
|
893
|
+
` 最终安装 ${previewAssetIds.length} 项`
|
|
894
|
+
]
|
|
895
|
+
),
|
|
896
|
+
"",
|
|
897
|
+
...buildTwoColumnLines(
|
|
898
|
+
"最终安装资产",
|
|
899
|
+
buildAssetsPreviewLines(state, previewAssetIds, 8),
|
|
900
|
+
"新增与移除",
|
|
901
|
+
[
|
|
902
|
+
...buildAssetsPreviewLines(state, diffSummary.added, 4),
|
|
903
|
+
...buildAssetsPreviewLines(state, diffSummary.removed, 4)
|
|
904
|
+
]
|
|
905
|
+
)
|
|
906
|
+
],
|
|
907
|
+
footerLines: ["Enter 确认执行 Ctrl+C 退出"]
|
|
908
|
+
});
|
|
699
909
|
const confirmed = options.yes || await prompt.confirm("确认继续执行安装", true);
|
|
700
910
|
if (!confirmed) {
|
|
701
911
|
printWarning("已取消。");
|
|
@@ -768,6 +978,49 @@ async function runList(options = {}) {
|
|
|
768
978
|
});
|
|
769
979
|
}
|
|
770
980
|
|
|
981
|
+
async function runProfiles(options = {}) {
|
|
982
|
+
printBanner();
|
|
983
|
+
ensureGitMode(options);
|
|
984
|
+
const projectRoot = options.project ? path.resolve(options.project) : "";
|
|
985
|
+
const state = core.getAppState({ projectRoot });
|
|
986
|
+
const targetTools = options.tool ? [options.tool] : SUPPORTED_TOOLS;
|
|
987
|
+
for (const tool of targetTools) {
|
|
988
|
+
if (!SUPPORTED_TOOLS.includes(tool)) {
|
|
989
|
+
throw new Error(`--tool 必须是 ${SUPPORTED_TOOLS.join(" / ")}`);
|
|
990
|
+
}
|
|
991
|
+
printSection(`${tool} 可用档位`);
|
|
992
|
+
const profiles = filterProfilesByTool(state, tool);
|
|
993
|
+
if (!profiles.length) {
|
|
994
|
+
printWarning("当前没有可用档位");
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
profiles.forEach((profile) => {
|
|
998
|
+
console.log(` ${color("•", "green")} ${profile.id} ${color(`${profile.name || profile.id} / ${profile.asset_ids.length} 项`, "dim")}`);
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
async function runAssets(options = {}) {
|
|
1004
|
+
printBanner();
|
|
1005
|
+
ensureGitMode(options);
|
|
1006
|
+
const projectRoot = options.project ? path.resolve(options.project) : "";
|
|
1007
|
+
const state = core.getAppState({ projectRoot });
|
|
1008
|
+
const tool = options.tool || SUPPORTED_TOOLS[0];
|
|
1009
|
+
if (!SUPPORTED_TOOLS.includes(tool)) {
|
|
1010
|
+
throw new Error(`--tool 必须是 ${SUPPORTED_TOOLS.join(" / ")}`);
|
|
1011
|
+
}
|
|
1012
|
+
const assets = filterInstallableAssetsByTool(state, tool);
|
|
1013
|
+
printSection(`${tool} 可安装资产`);
|
|
1014
|
+
if (!assets.length) {
|
|
1015
|
+
printWarning("当前没有可安装资产");
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
assets.forEach((asset) => {
|
|
1019
|
+
console.log(` ${color("•", "green")} ${asset.id}`);
|
|
1020
|
+
console.log(` ${color(`${asset.type} / ${asset.domain} / ${asset.status}`, "dim")}`);
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
|
|
771
1024
|
async function runDoctor(options = {}) {
|
|
772
1025
|
printBanner();
|
|
773
1026
|
ensureGitMode(options);
|
|
@@ -790,7 +1043,7 @@ async function runDoctor(options = {}) {
|
|
|
790
1043
|
});
|
|
791
1044
|
}
|
|
792
1045
|
|
|
793
|
-
async function
|
|
1046
|
+
async function runUse(options = {}) {
|
|
794
1047
|
printBanner();
|
|
795
1048
|
if (!options.tool || !SUPPORTED_TOOLS.includes(options.tool)) {
|
|
796
1049
|
throw new Error(`--tool 必须是 ${SUPPORTED_TOOLS.join(" / ")}`);
|
|
@@ -895,12 +1148,20 @@ async function runCli(argv = process.argv, options = {}) {
|
|
|
895
1148
|
await runList(normalizedOptions);
|
|
896
1149
|
return;
|
|
897
1150
|
}
|
|
1151
|
+
if (command === "profiles") {
|
|
1152
|
+
await runProfiles(normalizedOptions);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
if (command === "assets") {
|
|
1156
|
+
await runAssets(normalizedOptions);
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
898
1159
|
if (command === "doctor") {
|
|
899
1160
|
await runDoctor(normalizedOptions);
|
|
900
1161
|
return;
|
|
901
1162
|
}
|
|
902
|
-
if (command === "apply") {
|
|
903
|
-
await
|
|
1163
|
+
if (command === "apply" || command === "use") {
|
|
1164
|
+
await runUse(normalizedOptions);
|
|
904
1165
|
return;
|
|
905
1166
|
}
|
|
906
1167
|
if (command === "update") {
|