@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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/lib/cli.js +279 -18
  3. 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 apply --tool claude-code --scope project --project /path/to/project --profile full-enterprise
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 || []).filter((profile) => (profile.supported_tools || []).includes(tool));
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
- const assets = filterInstallableAssetsByTool(state, selectedTool);
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
- printSection("执行预览");
694
- printKeyValue("目标项目", projectRoot);
695
- printKeyValue("目标工具", selectedTool, "cyan");
696
- printKeyValue("安装作用域", scope === "project" ? "项目级" : "全局");
697
- printKeyValue("选择模式", selectionMode === "profile" ? `档位 ${profileId}` : "自定义资产");
698
- printBulletList("即将同步的资产", summarizeAssets(state, previewAssetIds));
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 runApply(options = {}) {
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 runApply(normalizedOptions);
1163
+ if (command === "apply" || command === "use") {
1164
+ await runUse(normalizedOptions);
904
1165
  return;
905
1166
  }
906
1167
  if (command === "update") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sswl/ai-manager",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "SSWL AI Manager CLI",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {