@geoly-ai/social-hub-cli 0.0.11 → 0.0.13

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 (53) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/admin-index-gates.test.d.ts +2 -0
  3. package/dist/admin-index-gates.test.d.ts.map +1 -0
  4. package/dist/admin-index-gates.test.js +33 -0
  5. package/dist/admin-index-gates.test.js.map +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +723 -58
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.test.js +87 -5
  10. package/dist/index.test.js.map +1 -1
  11. package/dist/permission-runner.d.ts +11 -0
  12. package/dist/permission-runner.d.ts.map +1 -0
  13. package/dist/permission-runner.js +30 -0
  14. package/dist/permission-runner.js.map +1 -0
  15. package/dist/permission-runner.test.d.ts +2 -0
  16. package/dist/permission-runner.test.d.ts.map +1 -0
  17. package/dist/permission-runner.test.js +69 -0
  18. package/dist/permission-runner.test.js.map +1 -0
  19. package/dist/permissions-gates-admin.d.ts +4 -0
  20. package/dist/permissions-gates-admin.d.ts.map +1 -0
  21. package/dist/permissions-gates-admin.js +80 -0
  22. package/dist/permissions-gates-admin.js.map +1 -0
  23. package/dist/permissions-gates-admin.test.d.ts +2 -0
  24. package/dist/permissions-gates-admin.test.d.ts.map +1 -0
  25. package/dist/permissions-gates-admin.test.js +25 -0
  26. package/dist/permissions-gates-admin.test.js.map +1 -0
  27. package/dist/permissions.d.ts.map +1 -1
  28. package/dist/permissions.js +2 -3
  29. package/dist/permissions.js.map +1 -1
  30. package/dist/register-admin.d.ts.map +1 -1
  31. package/dist/register-admin.js +343 -29
  32. package/dist/register-admin.js.map +1 -1
  33. package/dist/register-extensions.d.ts.map +1 -1
  34. package/dist/register-extensions.js +16 -19
  35. package/dist/register-extensions.js.map +1 -1
  36. package/package.json +2 -2
  37. package/skills/README.md +7 -5
  38. package/skills/manifest.json +17 -7
  39. package/skills/social-hub-accounts/SKILL.md +46 -13
  40. package/skills/social-hub-admin/SKILL.md +76 -10
  41. package/skills/social-hub-calendar-jobs/SKILL.md +26 -10
  42. package/skills/social-hub-cli/SKILL.md +35 -191
  43. package/skills/social-hub-cli/evals/evals.json +4 -4
  44. package/skills/social-hub-events-observability/SKILL.md +49 -0
  45. package/skills/social-hub-graph-compliance/SKILL.md +60 -0
  46. package/skills/social-hub-intelligence/SKILL.md +72 -7
  47. package/skills/social-hub-migration/SKILL.md +18 -5
  48. package/skills/social-hub-openclaw-context/SKILL.md +21 -6
  49. package/skills/social-hub-ops-runtime/SKILL.md +10 -5
  50. package/skills/social-hub-posts/SKILL.md +54 -23
  51. package/skills/social-hub-posts/evals/evals.json +23 -0
  52. package/skills/social-hub-publishing/SKILL.md +75 -11
  53. package/skills/social-hub-shared/SKILL.md +8 -4
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ import { getSocialHubHealth } from "@geoly-ai/social-hub-sdk";
4
4
  import { getBaseUrl, requireClient } from "./client.js";
5
5
  import { registerAccountsExtensions, registerAuthAndConfig, registerPermissionsExplain, registerRedditExtensions, } from "./register-extensions.js";
6
6
  import { registerAdminCommands } from "./register-admin.js";
7
+ import { assertApplyOrDryRun } from "./dry-run.js";
8
+ import { withPermissionGate } from "./permission-runner.js";
7
9
  import { registerBatchAndExport } from "./register-batch.js";
8
10
  import { registerAgentContext, registerOpsRuntime, } from "./register-ops.js";
9
11
  import { registerDoctorAndVersion } from "./register-shared.js";
@@ -341,8 +343,35 @@ accounts
341
343
  });
342
344
  // --- brands & campaigns ---
343
345
  const brands = program.command("brands").description("品牌");
346
+ brands
347
+ .command("system-list")
348
+ .description("GET /system/brands (preferred)")
349
+ .action(async () => {
350
+ const res = await requireClient().listSystemBrands();
351
+ console.log(JSON.stringify(res, null, 2));
352
+ });
353
+ brands
354
+ .command("system-create")
355
+ .description("POST /system/brands (preferred)")
356
+ .requiredOption("-j, --json <json>", "{ name, slug? }")
357
+ .action(async (opts) => {
358
+ let body;
359
+ try {
360
+ body = JSON.parse(opts.json);
361
+ }
362
+ catch {
363
+ console.error("Invalid -j / --json");
364
+ process.exit(1);
365
+ }
366
+ const res = await requireClient().createSystemBrand({
367
+ name: String(body.name ?? ""),
368
+ slug: body.slug,
369
+ });
370
+ console.log(JSON.stringify(res, null, 2));
371
+ });
344
372
  brands
345
373
  .command("list")
374
+ .description("GET /teams/:teamId/brands (compat)")
346
375
  .requiredOption("-t, --team <teamId>", "Team UUID")
347
376
  .action(async (opts) => {
348
377
  const res = await requireClient().listBrands(opts.team);
@@ -577,6 +606,28 @@ compliance
577
606
  const res = await requireClient().upsertAccountRiskProfile(opts.team, opts.account, body);
578
607
  console.log(JSON.stringify(res, null, 2));
579
608
  });
609
+ compliance
610
+ .command("rule-caches-list")
611
+ .description("GET /subreddit-rule-caches")
612
+ .requiredOption("-t, --team <teamId>", "Team UUID")
613
+ .option("--include-expired", "include expired caches")
614
+ .action(async (opts) => {
615
+ const res = await requireClient().listSubredditRuleCaches(opts.team, {
616
+ includeExpired: Boolean(opts.includeExpired),
617
+ });
618
+ console.log(JSON.stringify(res, null, 2));
619
+ });
620
+ compliance
621
+ .command("rule-caches-upsert")
622
+ .description("PUT /subreddit-rule-caches/:subreddit")
623
+ .requiredOption("-t, --team <teamId>", "Team UUID")
624
+ .requiredOption("--sub <name>", "subreddit name")
625
+ .requiredOption("-j, --json <json>", "{ rulesMarkdown, sourceUrl?, expiresInDays? }")
626
+ .action(async (opts) => {
627
+ const body = JSON.parse(opts.json);
628
+ const res = await requireClient().upsertSubredditRuleCache(opts.team, opts.sub, body);
629
+ console.log(JSON.stringify(res, null, 2));
630
+ });
580
631
  // --- openclaw ---
581
632
  program
582
633
  .command("openclaw-ingest")
@@ -689,8 +740,14 @@ agentTeams
689
740
  .command("list")
690
741
  .description("GET /v1/agent-teams")
691
742
  .action(async () => {
692
- const res = await requireClient().listAgentTeams();
693
- console.log(JSON.stringify(res, null, 2));
743
+ await withPermissionGate({
744
+ command: "agent-teams list",
745
+ resource: "agentTeam",
746
+ action: "list",
747
+ }, async () => {
748
+ const res = await requireClient().listAgentTeams();
749
+ console.log(JSON.stringify(res, null, 2));
750
+ });
694
751
  });
695
752
  agentTeams
696
753
  .command("workspace-docs")
@@ -699,30 +756,71 @@ agentTeams
699
756
  .option("--kind <kind>", "file|daily_report|weekly_report|ops_record")
700
757
  .option("-n, --limit <n>", "limit", "50")
701
758
  .action(async (opts) => {
702
- const res = await requireClient().listWorkspaceDocuments(opts.team, {
703
- kind: opts.kind,
704
- limit: Number(opts.limit),
759
+ await withPermissionGate({
760
+ command: "agent-teams workspace-docs",
761
+ resource: "agentTeam",
762
+ action: "read",
763
+ teamId: opts.team,
764
+ }, async () => {
765
+ const res = await requireClient().listWorkspaceDocuments(opts.team, {
766
+ kind: opts.kind,
767
+ limit: Number(opts.limit),
768
+ });
769
+ console.log(JSON.stringify(res, null, 2));
705
770
  });
706
- console.log(JSON.stringify(res, null, 2));
707
771
  });
708
772
  agentTeams
709
773
  .command("create")
710
774
  .description("POST /v1/agent-teams — 新建团队(admin only)")
711
775
  .requiredOption("--slug <slug>", "团队 slug(小写字母、数字、横线)")
712
776
  .requiredOption("--name <name>", "团队名称")
713
- .action(async (opts) => {
714
- const res = await requireClient().createTeam({ slug: opts.slug, name: opts.name });
715
- console.log(JSON.stringify(res, null, 2));
777
+ .option("--dry-run", "预览", false)
778
+ .option("--apply", "执行写入", false)
779
+ .action(async (opts) => {
780
+ await withPermissionGate({
781
+ command: "agent-teams create",
782
+ resource: "agentTeam",
783
+ action: "create",
784
+ }, async () => {
785
+ if (!assertApplyOrDryRun(opts, {
786
+ command: "agent-teams create",
787
+ danger: "admin",
788
+ summary: { slug: opts.slug, name: opts.name },
789
+ })) {
790
+ return;
791
+ }
792
+ const res = await requireClient().createTeam({
793
+ slug: opts.slug,
794
+ name: opts.name,
795
+ });
796
+ console.log(JSON.stringify(res, null, 2));
797
+ });
716
798
  });
717
799
  agentTeams
718
800
  .command("update")
719
801
  .description("PATCH /v1/agent-teams/:teamId — 更新团队(admin only)")
720
802
  .requiredOption("-t, --team <teamId>", "Team UUID")
721
803
  .requiredOption("-j, --json <json>", "{ slug?, name? }")
722
- .action(async (opts) => {
723
- const body = JSON.parse(opts.json);
724
- const res = await requireClient().updateTeam(opts.team, body);
725
- console.log(JSON.stringify(res, null, 2));
804
+ .option("--dry-run", "预览", false)
805
+ .option("--apply", "执行写入", false)
806
+ .action(async (opts) => {
807
+ await withPermissionGate({
808
+ command: "agent-teams update",
809
+ resource: "agentTeam",
810
+ action: "update",
811
+ teamId: opts.team,
812
+ }, async () => {
813
+ const body = JSON.parse(opts.json);
814
+ if (!assertApplyOrDryRun(opts, {
815
+ command: "agent-teams update",
816
+ danger: "admin",
817
+ summary: { teamId: opts.team, ...body },
818
+ })) {
819
+ return;
820
+ }
821
+ const res = await requireClient().updateTeam(opts.team, body);
822
+ console.log(JSON.stringify(res, null, 2));
823
+ });
726
824
  });
727
825
  agentTeams
728
826
  .command("add-member")
@@ -730,12 +828,32 @@ agentTeams
730
828
  .requiredOption("-t, --team <teamId>", "Team UUID")
731
829
  .requiredOption("--user <userId>", "User UUID")
732
830
  .requiredOption("--role <role>", "admin|manager|supervisor|senior|internal|client")
733
- .action(async (opts) => {
734
- const res = await requireClient().addTeamMember(opts.team, {
735
- userId: opts.user,
736
- role: opts.role,
831
+ .option("--dry-run", "预览", false)
832
+ .option("--apply", "执行写入", false)
833
+ .action(async (opts) => {
834
+ await withPermissionGate({
835
+ command: "agent-teams add-member",
836
+ resource: "agentTeam",
837
+ action: "update",
838
+ teamId: opts.team,
839
+ }, async () => {
840
+ if (!assertApplyOrDryRun(opts, {
841
+ command: "agent-teams add-member",
842
+ danger: "admin",
843
+ summary: {
844
+ teamId: opts.team,
845
+ userId: opts.user,
846
+ role: opts.role,
847
+ },
848
+ })) {
849
+ return;
850
+ }
851
+ const res = await requireClient().addTeamMember(opts.team, {
852
+ userId: opts.user,
853
+ role: opts.role,
854
+ });
855
+ console.log(JSON.stringify(res, null, 2));
737
856
  });
738
- console.log(JSON.stringify(res, null, 2));
739
857
  });
740
858
  // --- intelligence (subreddit-intelligence) ---
741
859
  const intel = program.command("intelligence").description("板块情报与 KOL 挖掘");
@@ -876,6 +994,192 @@ intel
876
994
  const res = await requireClient().dispatchTaskFromHotPost(opts.team, body);
877
995
  console.log(JSON.stringify(res, null, 2));
878
996
  });
997
+ intel
998
+ .command("tier-rules-list")
999
+ .description("GET .../subreddit-intelligence/tier-rules")
1000
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1001
+ .option("--tier <tier>", "tier filter")
1002
+ .option("--enabled <bool>", "true|false")
1003
+ .option("-n, --limit <n>", "limit", "50")
1004
+ .option("--offset <n>", "offset", "0")
1005
+ .action(async (opts) => {
1006
+ const res = await requireClient().listTierRules(opts.team, {
1007
+ tier: opts.tier,
1008
+ enabled: opts.enabled === undefined
1009
+ ? undefined
1010
+ : opts.enabled === "true" || opts.enabled === "1",
1011
+ limit: Number(opts.limit),
1012
+ offset: Number(opts.offset),
1013
+ });
1014
+ console.log(JSON.stringify(res, null, 2));
1015
+ });
1016
+ intel
1017
+ .command("tier-rules-create")
1018
+ .description("POST .../subreddit-intelligence/tier-rules")
1019
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1020
+ .requiredOption("-j, --json <json>", "tier rule body")
1021
+ .action(async (opts) => {
1022
+ const body = JSON.parse(opts.json);
1023
+ const res = await requireClient().createTierRule(opts.team, body);
1024
+ console.log(JSON.stringify(res, null, 2));
1025
+ });
1026
+ intel
1027
+ .command("tier-rules-update")
1028
+ .description("PATCH .../subreddit-intelligence/tier-rules/:id")
1029
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1030
+ .requiredOption("--id <uuid>", "Tier rule UUID")
1031
+ .requiredOption("-j, --json <json>", "update body")
1032
+ .action(async (opts) => {
1033
+ const body = JSON.parse(opts.json);
1034
+ const res = await requireClient().updateTierRule(opts.team, opts.id, body);
1035
+ console.log(JSON.stringify(res, null, 2));
1036
+ });
1037
+ intel
1038
+ .command("tier-rules-delete")
1039
+ .description("DELETE .../subreddit-intelligence/tier-rules/:id")
1040
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1041
+ .requiredOption("--id <uuid>", "Tier rule UUID")
1042
+ .action(async (opts) => {
1043
+ const res = await requireClient().deleteTierRule(opts.team, opts.id);
1044
+ console.log(JSON.stringify(res, null, 2));
1045
+ });
1046
+ intel
1047
+ .command("runs-list")
1048
+ .description("GET .../subreddit-intelligence/runs")
1049
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1050
+ .option("--dimension <d>", "industry|keywords|tier")
1051
+ .option("--status <s>", "queued|running|succeeded|failed")
1052
+ .option("-n, --limit <n>", "limit", "50")
1053
+ .option("--offset <n>", "offset", "0")
1054
+ .action(async (opts) => {
1055
+ const res = await requireClient().listIntelRuns(opts.team, {
1056
+ dimension: opts.dimension,
1057
+ status: opts.status,
1058
+ limit: Number(opts.limit),
1059
+ offset: Number(opts.offset),
1060
+ });
1061
+ console.log(JSON.stringify(res, null, 2));
1062
+ });
1063
+ intel
1064
+ .command("kol-profiles-list")
1065
+ .description("GET .../subreddit-intelligence/kol-profiles")
1066
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1067
+ .option("--platform <p>", "reddit")
1068
+ .option("-n, --limit <n>", "limit", "50")
1069
+ .option("--offset <n>", "offset", "0")
1070
+ .action(async (opts) => {
1071
+ const res = await requireClient().listKolProfiles(opts.team, {
1072
+ platform: opts.platform,
1073
+ limit: Number(opts.limit),
1074
+ offset: Number(opts.offset),
1075
+ });
1076
+ console.log(JSON.stringify(res, null, 2));
1077
+ });
1078
+ intel
1079
+ .command("kol-profiles-get")
1080
+ .description("GET .../subreddit-intelligence/kol-profiles/:id")
1081
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1082
+ .requiredOption("--id <uuid>", "KOL profile UUID")
1083
+ .action(async (opts) => {
1084
+ const res = await requireClient().getKolProfile(opts.team, opts.id);
1085
+ console.log(JSON.stringify(res, null, 2));
1086
+ });
1087
+ intel
1088
+ .command("fetch-by-industry")
1089
+ .description("POST .../subreddit-intelligence/hot-posts/fetch-by-industry")
1090
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1091
+ .requiredOption("-j, --json <json>", "fetch body")
1092
+ .action(async (opts) => {
1093
+ const body = JSON.parse(opts.json);
1094
+ const res = await requireClient().fetchHotPostsByIndustry(opts.team, body);
1095
+ console.log(JSON.stringify(res, null, 2));
1096
+ });
1097
+ intel
1098
+ .command("fetch-by-tier")
1099
+ .description("POST .../subreddit-intelligence/hot-posts/fetch-by-tier")
1100
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1101
+ .requiredOption("-j, --json <json>", "fetch body")
1102
+ .action(async (opts) => {
1103
+ const body = JSON.parse(opts.json);
1104
+ const res = await requireClient().fetchHotPostsByTier(opts.team, body);
1105
+ console.log(JSON.stringify(res, null, 2));
1106
+ });
1107
+ intel
1108
+ .command("fetch-by-keywords")
1109
+ .description("POST .../subreddit-intelligence/insights/fetch-by-keywords")
1110
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1111
+ .requiredOption("-j, --json <json>", "fetch body")
1112
+ .action(async (opts) => {
1113
+ const body = JSON.parse(opts.json);
1114
+ const res = await requireClient().fetchInsightsByKeywords(opts.team, body);
1115
+ console.log(JSON.stringify(res, null, 2));
1116
+ });
1117
+ intel
1118
+ .command("brand-mention-radar")
1119
+ .description("POST .../subreddit-intelligence/insights/brand-mention-radar")
1120
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1121
+ .requiredOption("-j, --json <json>", "{ keywords, minScore?, minComments? }")
1122
+ .action(async (opts) => {
1123
+ const body = JSON.parse(opts.json);
1124
+ const res = await requireClient().getBrandMentionRadar(opts.team, body);
1125
+ console.log(JSON.stringify(res, null, 2));
1126
+ });
1127
+ intel
1128
+ .command("opportunity-map")
1129
+ .description("POST .../subreddit-intelligence/insights/opportunity-map")
1130
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1131
+ .requiredOption("-j, --json <json>", "{ keywords, minScore?, minComments? }")
1132
+ .action(async (opts) => {
1133
+ const body = JSON.parse(opts.json);
1134
+ const res = await requireClient().getOpportunityMap(opts.team, body);
1135
+ console.log(JSON.stringify(res, null, 2));
1136
+ });
1137
+ intel
1138
+ .command("sov")
1139
+ .description("POST .../subreddit-intelligence/insights/sov")
1140
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1141
+ .requiredOption("-j, --json <json>", "{ brandKeywords, competitorKeywords, minScore?, minComments? }")
1142
+ .action(async (opts) => {
1143
+ const body = JSON.parse(opts.json);
1144
+ const res = await requireClient().getInsightsSov(opts.team, body);
1145
+ console.log(JSON.stringify(res, null, 2));
1146
+ });
1147
+ intel
1148
+ .command("sentiment-intent")
1149
+ .description("GET .../subreddit-intelligence/insights/sentiment-intent")
1150
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1151
+ .action(async (opts) => {
1152
+ const res = await requireClient().getSentimentIntent(opts.team);
1153
+ console.log(JSON.stringify(res, null, 2));
1154
+ });
1155
+ intel
1156
+ .command("campaign-lift")
1157
+ .description("POST .../subreddit-intelligence/insights/campaign-lift")
1158
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1159
+ .requiredOption("-j, --json <json>", "{ keywords, campaignStartAt, campaignEndAt, windowDays?, minScore?, minComments? }")
1160
+ .action(async (opts) => {
1161
+ const body = JSON.parse(opts.json);
1162
+ const res = await requireClient().getCampaignLift(opts.team, body);
1163
+ console.log(JSON.stringify(res, null, 2));
1164
+ });
1165
+ intel
1166
+ .command("snapshot-latest")
1167
+ .description("GET .../subreddit-intelligence/insights/snapshot-latest")
1168
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1169
+ .action(async (opts) => {
1170
+ const res = await requireClient().getLatestInsightSnapshot(opts.team);
1171
+ console.log(JSON.stringify(res, null, 2));
1172
+ });
1173
+ intel
1174
+ .command("snapshot-save")
1175
+ .description("POST .../subreddit-intelligence/insights/snapshot")
1176
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1177
+ .requiredOption("-j, --json <json>", "{ payload: { ... } }")
1178
+ .action(async (opts) => {
1179
+ const body = JSON.parse(opts.json);
1180
+ const res = await requireClient().saveInsightSnapshot(opts.team, body);
1181
+ console.log(JSON.stringify(res, null, 2));
1182
+ });
879
1183
  // --- users ---
880
1184
  const usersCmd = program.command("users").description("团队成员管理");
881
1185
  usersCmd
@@ -883,8 +1187,28 @@ usersCmd
883
1187
  .description("GET /users")
884
1188
  .requiredOption("-t, --team <teamId>", "Team UUID")
885
1189
  .action(async (opts) => {
886
- const res = await requireClient().listUsers(opts.team);
887
- console.log(JSON.stringify(res, null, 2));
1190
+ await withPermissionGate({
1191
+ command: "users list",
1192
+ resource: "systemMember",
1193
+ action: "list",
1194
+ teamId: opts.team,
1195
+ }, async () => {
1196
+ const res = await requireClient().listUsers(opts.team);
1197
+ console.log(JSON.stringify(res, null, 2));
1198
+ });
1199
+ });
1200
+ usersCmd
1201
+ .command("system-list")
1202
+ .description("GET /v1/system/users")
1203
+ .action(async () => {
1204
+ await withPermissionGate({
1205
+ command: "users system-list",
1206
+ resource: "systemMember",
1207
+ action: "list",
1208
+ }, async () => {
1209
+ const res = await requireClient().listSystemUsers();
1210
+ console.log(JSON.stringify(res, null, 2));
1211
+ });
888
1212
  });
889
1213
  usersCmd
890
1214
  .command("create")
@@ -892,30 +1216,149 @@ usersCmd
892
1216
  .requiredOption("-t, --team <teamId>", "Team UUID")
893
1217
  .requiredOption("-j, --json <json>", "{ email, password, role? }")
894
1218
  .action(async (opts) => {
895
- const body = JSON.parse(opts.json);
896
- const res = await requireClient().createUser(opts.team, body);
897
- console.log(JSON.stringify(res, null, 2));
1219
+ await withPermissionGate({
1220
+ command: "users create",
1221
+ resource: "systemMember",
1222
+ action: "create",
1223
+ teamId: opts.team,
1224
+ }, async () => {
1225
+ const body = JSON.parse(opts.json);
1226
+ const res = await requireClient().createUser(opts.team, body);
1227
+ console.log(JSON.stringify(res, null, 2));
1228
+ });
1229
+ });
1230
+ usersCmd
1231
+ .command("system-create")
1232
+ .description("POST /v1/system/users")
1233
+ .requiredOption("-j, --json <json>", "{ email, name, password?, role? }")
1234
+ .option("--dry-run", "预览", false)
1235
+ .option("--apply", "执行写入", false)
1236
+ .action(async (opts) => {
1237
+ await withPermissionGate({
1238
+ command: "users system-create",
1239
+ resource: "systemMember",
1240
+ action: "create",
1241
+ }, async () => {
1242
+ const body = JSON.parse(opts.json);
1243
+ if (!assertApplyOrDryRun(opts, {
1244
+ command: "users system-create",
1245
+ danger: "write",
1246
+ summary: { email: body.email, name: body.name },
1247
+ })) {
1248
+ return;
1249
+ }
1250
+ const res = await requireClient().createSystemUser(body);
1251
+ console.log(JSON.stringify(res, null, 2));
1252
+ });
898
1253
  });
899
1254
  // --- brand-members ---
900
1255
  const brandMembersCmd = program.command("brand-members").description("品牌成员");
1256
+ brandMembersCmd
1257
+ .command("system-list")
1258
+ .description("GET /system/brand-members (preferred)")
1259
+ .option("--user <uuid>", "userId 过滤")
1260
+ .action(async (opts) => {
1261
+ await withPermissionGate({
1262
+ command: "brand-members system-list",
1263
+ resource: "systemBrand",
1264
+ action: "list",
1265
+ }, async () => {
1266
+ const res = await requireClient().listSystemBrandMembers({
1267
+ userId: opts.user,
1268
+ });
1269
+ console.log(JSON.stringify(res, null, 2));
1270
+ });
1271
+ });
1272
+ brandMembersCmd
1273
+ .command("system-create")
1274
+ .description("POST /system/brand-members (preferred)")
1275
+ .requiredOption("-j, --json <json>", "{ brandId, userId, role? }")
1276
+ .option("--dry-run", "预览", false)
1277
+ .option("--apply", "执行写入", false)
1278
+ .action(async (opts) => {
1279
+ await withPermissionGate({
1280
+ command: "brand-members system-create",
1281
+ resource: "brandMember",
1282
+ action: "create",
1283
+ }, async () => {
1284
+ const body = JSON.parse(opts.json);
1285
+ if (!assertApplyOrDryRun(opts, {
1286
+ command: "brand-members system-create",
1287
+ danger: "write",
1288
+ summary: body,
1289
+ })) {
1290
+ return;
1291
+ }
1292
+ const res = await requireClient().createSystemBrandMember(body);
1293
+ console.log(JSON.stringify(res, null, 2));
1294
+ });
1295
+ });
1296
+ brandMembersCmd
1297
+ .command("system-remove")
1298
+ .description("DELETE /system/brand-members/:memberId")
1299
+ .requiredOption("--id <memberId>", "BrandMember UUID")
1300
+ .option("--dry-run", "预览", false)
1301
+ .option("--apply", "执行写入", false)
1302
+ .action(async (opts) => {
1303
+ await withPermissionGate({
1304
+ command: "brand-members system-remove",
1305
+ resource: "brandMember",
1306
+ action: "delete",
1307
+ }, async () => {
1308
+ if (!assertApplyOrDryRun(opts, {
1309
+ command: "brand-members system-remove",
1310
+ danger: "delete",
1311
+ summary: { memberId: opts.id },
1312
+ })) {
1313
+ return;
1314
+ }
1315
+ await requireClient().deleteSystemBrandMember(opts.id);
1316
+ console.log("OK");
1317
+ });
1318
+ });
901
1319
  brandMembersCmd
902
1320
  .command("list")
903
- .description("GET /brand-members")
1321
+ .description("GET /teams/:teamId/brand-members (compat)")
904
1322
  .requiredOption("-t, --team <teamId>", "Team UUID")
905
1323
  .option("--user <uuid>", "userId 过滤")
906
1324
  .action(async (opts) => {
907
- const res = await requireClient().listBrandMembers(opts.team, { userId: opts.user });
908
- console.log(JSON.stringify(res, null, 2));
1325
+ await withPermissionGate({
1326
+ command: "brand-members list",
1327
+ resource: "brandMember",
1328
+ action: "list",
1329
+ teamId: opts.team,
1330
+ }, async () => {
1331
+ const res = await requireClient().listBrandMembers(opts.team, {
1332
+ userId: opts.user,
1333
+ });
1334
+ console.log(JSON.stringify(res, null, 2));
1335
+ });
909
1336
  });
910
1337
  brandMembersCmd
911
1338
  .command("create")
912
1339
  .description("POST /brand-members")
913
1340
  .requiredOption("-t, --team <teamId>", "Team UUID")
914
1341
  .requiredOption("-j, --json <json>", "{ brandId, userId, role? }")
915
- .action(async (opts) => {
916
- const body = JSON.parse(opts.json);
917
- const res = await requireClient().createBrandMember(opts.team, body);
918
- console.log(JSON.stringify(res, null, 2));
1342
+ .option("--dry-run", "预览", false)
1343
+ .option("--apply", "执行写入", false)
1344
+ .action(async (opts) => {
1345
+ await withPermissionGate({
1346
+ command: "brand-members create",
1347
+ resource: "brandMember",
1348
+ action: "create",
1349
+ teamId: opts.team,
1350
+ }, async () => {
1351
+ const body = JSON.parse(opts.json);
1352
+ if (!assertApplyOrDryRun(opts, {
1353
+ command: "brand-members create",
1354
+ danger: "write",
1355
+ summary: { teamId: opts.team, ...body },
1356
+ })) {
1357
+ return;
1358
+ }
1359
+ const res = await requireClient().createBrandMember(opts.team, body);
1360
+ console.log(JSON.stringify(res, null, 2));
1361
+ });
919
1362
  });
920
1363
  // --- notification-channels ---
921
1364
  const notifChannels = program
@@ -979,56 +1422,147 @@ apiKeys
979
1422
  .requiredOption("-t, --team <teamId>", "Team UUID")
980
1423
  .option("-n, --limit <n>", "limit", "50")
981
1424
  .action(async (opts) => {
982
- const res = await requireClient().listApiKeys(opts.team, { limit: Number(opts.limit) });
983
- console.log(JSON.stringify(res, null, 2));
1425
+ await withPermissionGate({
1426
+ command: "api-keys list",
1427
+ resource: "apiKey",
1428
+ action: "list",
1429
+ teamId: opts.team,
1430
+ }, async () => {
1431
+ const res = await requireClient().listApiKeys(opts.team, {
1432
+ limit: Number(opts.limit),
1433
+ });
1434
+ console.log(JSON.stringify(res, null, 2));
1435
+ });
984
1436
  });
985
1437
  apiKeys
986
1438
  .command("create")
987
1439
  .description("POST /api-keys")
988
1440
  .requiredOption("-t, --team <teamId>", "Team UUID")
989
1441
  .requiredOption("-j, --json <json>", "{ name, role, visibleCampaignIds? }")
990
- .action(async (opts) => {
991
- const body = JSON.parse(opts.json);
992
- const res = await requireClient().createApiKey(opts.team, body);
993
- console.log(JSON.stringify(res, null, 2));
1442
+ .option("--dry-run", "预览", false)
1443
+ .option("--apply", "执行写入", false)
1444
+ .action(async (opts) => {
1445
+ await withPermissionGate({
1446
+ command: "api-keys create",
1447
+ resource: "apiKey",
1448
+ action: "create",
1449
+ teamId: opts.team,
1450
+ }, async () => {
1451
+ const body = JSON.parse(opts.json);
1452
+ if (!assertApplyOrDryRun(opts, {
1453
+ command: "api-keys create",
1454
+ danger: "secret",
1455
+ summary: { teamId: opts.team, ...body },
1456
+ })) {
1457
+ return;
1458
+ }
1459
+ const res = await requireClient().createApiKey(opts.team, body);
1460
+ console.log(JSON.stringify(res, null, 2));
1461
+ });
994
1462
  });
995
1463
  apiKeys
996
1464
  .command("delete")
997
1465
  .description("DELETE /api-keys/:id")
998
1466
  .requiredOption("-t, --team <teamId>", "Team UUID")
999
1467
  .requiredOption("--key <uuid>", "API Key UUID")
1000
- .action(async (opts) => {
1001
- await requireClient().deleteApiKey(opts.team, opts.key);
1002
- console.log("OK");
1468
+ .option("--dry-run", "预览", false)
1469
+ .option("--apply", "执行写入", false)
1470
+ .action(async (opts) => {
1471
+ await withPermissionGate({
1472
+ command: "api-keys delete",
1473
+ resource: "apiKey",
1474
+ action: "delete",
1475
+ teamId: opts.team,
1476
+ }, async () => {
1477
+ if (!assertApplyOrDryRun(opts, {
1478
+ command: "api-keys delete",
1479
+ danger: "delete",
1480
+ summary: { teamId: opts.team, keyId: opts.key },
1481
+ })) {
1482
+ return;
1483
+ }
1484
+ await requireClient().deleteApiKey(opts.team, opts.key);
1485
+ console.log("OK");
1486
+ });
1003
1487
  });
1004
1488
  apiKeys
1005
1489
  .command("rotate")
1006
1490
  .description("POST /api-keys/:id/rotate")
1007
1491
  .requiredOption("-t, --team <teamId>", "Team UUID")
1008
1492
  .requiredOption("--key <uuid>", "API Key UUID")
1009
- .action(async (opts) => {
1010
- const res = await requireClient().rotateApiKey(opts.team, opts.key);
1011
- console.log(JSON.stringify(res, null, 2));
1493
+ .option("--dry-run", "预览", false)
1494
+ .option("--apply", "执行写入", false)
1495
+ .action(async (opts) => {
1496
+ await withPermissionGate({
1497
+ command: "api-keys rotate",
1498
+ resource: "apiKey",
1499
+ action: "update",
1500
+ teamId: opts.team,
1501
+ }, async () => {
1502
+ if (!assertApplyOrDryRun(opts, {
1503
+ command: "api-keys rotate",
1504
+ danger: "secret",
1505
+ summary: { teamId: opts.team, keyId: opts.key },
1506
+ })) {
1507
+ return;
1508
+ }
1509
+ const res = await requireClient().rotateApiKey(opts.team, opts.key);
1510
+ console.log(JSON.stringify(res, null, 2));
1511
+ });
1012
1512
  });
1013
1513
  apiKeys
1014
1514
  .command("batch-revoke")
1015
1515
  .description("POST /api-keys/batch/revoke")
1016
1516
  .requiredOption("-t, --team <teamId>", "Team UUID")
1017
- .requiredOption("-j, --json <json>", '{ "ids": ["uuid1","uuid2"] }')
1018
- .action(async (opts) => {
1019
- const body = JSON.parse(opts.json);
1020
- const res = await requireClient().batchRevokeApiKeys(opts.team, body.ids);
1021
- console.log(JSON.stringify(res, null, 2));
1517
+ .requiredOption("-j, --json <json>", '{ "apiKeyIds": ["uuid1","uuid2"] }')
1518
+ .option("--dry-run", "预览", false)
1519
+ .option("--apply", "执行写入", false)
1520
+ .action(async (opts) => {
1521
+ await withPermissionGate({
1522
+ command: "api-keys batch-revoke",
1523
+ resource: "apiKey",
1524
+ action: "delete",
1525
+ teamId: opts.team,
1526
+ }, async () => {
1527
+ const body = JSON.parse(opts.json);
1528
+ const apiKeyIds = body.apiKeyIds ?? body.ids ?? [];
1529
+ if (!assertApplyOrDryRun(opts, {
1530
+ command: "api-keys batch-revoke",
1531
+ danger: "delete",
1532
+ summary: { teamId: opts.team, apiKeyIds },
1533
+ })) {
1534
+ return;
1535
+ }
1536
+ const res = await requireClient().batchRevokeApiKeys(opts.team, apiKeyIds);
1537
+ console.log(JSON.stringify(res, null, 2));
1538
+ });
1022
1539
  });
1023
1540
  apiKeys
1024
1541
  .command("batch-rotate")
1025
1542
  .description("POST /api-keys/batch/rotate")
1026
1543
  .requiredOption("-t, --team <teamId>", "Team UUID")
1027
- .requiredOption("-j, --json <json>", '{ "ids": ["uuid1","uuid2"] }')
1028
- .action(async (opts) => {
1029
- const body = JSON.parse(opts.json);
1030
- const res = await requireClient().batchRotateApiKeys(opts.team, body.ids);
1031
- console.log(JSON.stringify(res, null, 2));
1544
+ .requiredOption("-j, --json <json>", '{ "apiKeyIds": ["uuid1","uuid2"] }')
1545
+ .option("--dry-run", "预览", false)
1546
+ .option("--apply", "执行写入", false)
1547
+ .action(async (opts) => {
1548
+ await withPermissionGate({
1549
+ command: "api-keys batch-rotate",
1550
+ resource: "apiKey",
1551
+ action: "update",
1552
+ teamId: opts.team,
1553
+ }, async () => {
1554
+ const body = JSON.parse(opts.json);
1555
+ const apiKeyIds = body.apiKeyIds ?? body.ids ?? [];
1556
+ if (!assertApplyOrDryRun(opts, {
1557
+ command: "api-keys batch-rotate",
1558
+ danger: "secret",
1559
+ summary: { teamId: opts.team, apiKeyIds },
1560
+ })) {
1561
+ return;
1562
+ }
1563
+ const res = await requireClient().batchRotateApiKeys(opts.team, apiKeyIds);
1564
+ console.log(JSON.stringify(res, null, 2));
1565
+ });
1032
1566
  });
1033
1567
  // ═══════════════════════════════════════════════════════════════════════════
1034
1568
  // Group C: scattered endpoint additions
@@ -1083,9 +1617,16 @@ program
1083
1617
  .requiredOption("-t, --team <teamId>", "Team UUID")
1084
1618
  .requiredOption("-j, --json <json>", "权限矩阵更新 body")
1085
1619
  .action(async (opts) => {
1086
- const body = JSON.parse(opts.json);
1087
- const res = await requireClient().updatePermissionsMatrix(opts.team, body);
1088
- console.log(JSON.stringify(res, null, 2));
1620
+ await withPermissionGate({
1621
+ command: "permissions-update",
1622
+ resource: "permissionMatrix",
1623
+ action: "update",
1624
+ teamId: opts.team,
1625
+ }, async () => {
1626
+ const body = JSON.parse(opts.json);
1627
+ const res = await requireClient().updatePermissionsMatrix(opts.team, body);
1628
+ console.log(JSON.stringify(res, null, 2));
1629
+ });
1089
1630
  });
1090
1631
  graph
1091
1632
  .command("drilldown")
@@ -1100,6 +1641,114 @@ graph
1100
1641
  });
1101
1642
  console.log(JSON.stringify(res, null, 2));
1102
1643
  });
1644
+ graph
1645
+ .command("v2-ego")
1646
+ .description("GET /graph/v2/ego")
1647
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1648
+ .option("--node-id <uuid>", "graph nodeId")
1649
+ .option("--node-ref <id>", "nodeRefId (e.g. account UUID)")
1650
+ .option("--node-type <type>", "account|team|campaign")
1651
+ .option("--relation-type <type>", "relationType filter")
1652
+ .option("--edge-kind <kind>", "edgeKind filter")
1653
+ .option("-n, --limit <n>", "limit", "100")
1654
+ .action(async (opts) => {
1655
+ const res = await requireClient().getGraphV2Ego(opts.team, {
1656
+ nodeId: opts.nodeId,
1657
+ nodeRefId: opts.nodeRef,
1658
+ nodeType: opts.nodeType,
1659
+ relationType: opts.relationType,
1660
+ edgeKind: opts.edgeKind,
1661
+ limit: Number(opts.limit),
1662
+ });
1663
+ console.log(JSON.stringify(res, null, 2));
1664
+ });
1665
+ graph
1666
+ .command("v2-timeline")
1667
+ .description("GET /graph/v2/timeline")
1668
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1669
+ .option("--node-id <uuid>", "graph nodeId")
1670
+ .option("--node-ref <id>", "nodeRefId")
1671
+ .option("--node-type <type>", "account|team|campaign")
1672
+ .option("--from <iso>", "from ISO time")
1673
+ .option("--to <iso>", "to ISO time")
1674
+ .option("-n, --limit <n>", "limit", "100")
1675
+ .action(async (opts) => {
1676
+ const res = await requireClient().getGraphV2Timeline(opts.team, {
1677
+ nodeId: opts.nodeId,
1678
+ nodeRefId: opts.nodeRef,
1679
+ nodeType: opts.nodeType,
1680
+ from: opts.from,
1681
+ to: opts.to,
1682
+ limit: Number(opts.limit),
1683
+ });
1684
+ console.log(JSON.stringify(res, null, 2));
1685
+ });
1686
+ graph
1687
+ .command("v2-explain")
1688
+ .description("GET /graph/v2/explain/:edgeId")
1689
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1690
+ .requiredOption("--edge-id <id>", "edge UUID")
1691
+ .action(async (opts) => {
1692
+ const res = await requireClient().getGraphV2Explain(opts.team, opts.edgeId);
1693
+ console.log(JSON.stringify(res, null, 2));
1694
+ });
1695
+ graph
1696
+ .command("v2-path")
1697
+ .description("GET /graph/v2/path")
1698
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1699
+ .option("--source-id <uuid>", "source nodeId")
1700
+ .option("--source-ref <id>", "source nodeRefId")
1701
+ .option("--source-type <type>", "source nodeType")
1702
+ .option("--target-id <uuid>", "target nodeId")
1703
+ .option("--target-ref <id>", "target nodeRefId")
1704
+ .option("--target-type <type>", "target nodeType")
1705
+ .option("--max-depth <n>", "maxDepth", "4")
1706
+ .option("-n, --limit <n>", "limit", "50")
1707
+ .action(async (opts) => {
1708
+ const res = await requireClient().getGraphV2Path(opts.team, {
1709
+ sourceNodeId: opts.sourceId,
1710
+ sourceNodeRefId: opts.sourceRef,
1711
+ sourceNodeType: opts.sourceType,
1712
+ targetNodeId: opts.targetId,
1713
+ targetNodeRefId: opts.targetRef,
1714
+ targetNodeType: opts.targetType,
1715
+ maxDepth: Number(opts.maxDepth),
1716
+ limit: Number(opts.limit),
1717
+ });
1718
+ console.log(JSON.stringify(res, null, 2));
1719
+ });
1720
+ graph
1721
+ .command("v2-clusters")
1722
+ .description("GET /graph/v2/clusters")
1723
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1724
+ .option("--scope-ref <id>", "scopeNodeRefId")
1725
+ .option("--scope-type <type>", "scopeNodeType", "account")
1726
+ .option("--min-risk <n>", "minRiskScore", "60")
1727
+ .option("-n, --limit <n>", "limit", "100")
1728
+ .action(async (opts) => {
1729
+ const res = await requireClient().getGraphV2Clusters(opts.team, {
1730
+ scopeNodeRefId: opts.scopeRef,
1731
+ scopeNodeType: opts.scopeType,
1732
+ minRiskScore: Number(opts.minRisk),
1733
+ limit: Number(opts.limit),
1734
+ });
1735
+ console.log(JSON.stringify(res, null, 2));
1736
+ });
1737
+ graph
1738
+ .command("v2-impact")
1739
+ .description("GET /graph/v2/impact")
1740
+ .requiredOption("-t, --team <teamId>", "Team UUID")
1741
+ .option("--campaign-ref <id>", "campaignNodeRefId")
1742
+ .option("--min-reuse <n>", "minReuseCount", "2")
1743
+ .option("-n, --limit <n>", "limit", "100")
1744
+ .action(async (opts) => {
1745
+ const res = await requireClient().getGraphV2Impact(opts.team, {
1746
+ campaignNodeRefId: opts.campaignRef,
1747
+ minReuseCount: Number(opts.minReuse),
1748
+ limit: Number(opts.limit),
1749
+ });
1750
+ console.log(JSON.stringify(res, null, 2));
1751
+ });
1103
1752
  // ═══════════════════════════════════════════════════════════════════════════
1104
1753
  // Group D: add filter params to existing list commands
1105
1754
  // ═══════════════════════════════════════════════════════════════════════════
@@ -1336,9 +1985,25 @@ brandMembersCmd
1336
1985
  .description("DELETE /brand-members/:memberId")
1337
1986
  .requiredOption("-t, --team <teamId>", "Team UUID")
1338
1987
  .requiredOption("-m, --member <memberId>", "BrandMember UUID")
1339
- .action(async (opts) => {
1340
- const res = await requireClient().deleteBrandMember(opts.team, opts.member);
1341
- console.log(JSON.stringify(res, null, 2));
1988
+ .option("--dry-run", "预览", false)
1989
+ .option("--apply", "执行写入", false)
1990
+ .action(async (opts) => {
1991
+ await withPermissionGate({
1992
+ command: "brand-members remove",
1993
+ resource: "brandMember",
1994
+ action: "delete",
1995
+ teamId: opts.team,
1996
+ }, async () => {
1997
+ if (!assertApplyOrDryRun(opts, {
1998
+ command: "brand-members remove",
1999
+ danger: "delete",
2000
+ summary: { teamId: opts.team, memberId: opts.member },
2001
+ })) {
2002
+ return;
2003
+ }
2004
+ const res = await requireClient().deleteBrandMember(opts.team, opts.member);
2005
+ console.log(JSON.stringify(res, null, 2));
2006
+ });
1342
2007
  });
1343
2008
  // Only parse when run directly (not when imported for testing)
1344
2009
  if (!process.env["VITEST"]) {