@socialneuron/mcp-server 1.2.1 → 1.3.0
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/CHANGELOG.md +26 -0
- package/README.md +19 -7
- package/dist/http.js +411 -7
- package/dist/index.js +2298 -1076
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,6 +9,15 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/lib/version.ts
|
|
13
|
+
var MCP_VERSION;
|
|
14
|
+
var init_version = __esm({
|
|
15
|
+
"src/lib/version.ts"() {
|
|
16
|
+
"use strict";
|
|
17
|
+
MCP_VERSION = "1.3.0";
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
12
21
|
// src/lib/posthog.ts
|
|
13
22
|
import { createHash } from "node:crypto";
|
|
14
23
|
import { PostHog } from "posthog-node";
|
|
@@ -802,12 +811,364 @@ var init_quality = __esm({
|
|
|
802
811
|
}
|
|
803
812
|
});
|
|
804
813
|
|
|
805
|
-
// src/
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
814
|
+
// src/lib/tool-catalog.ts
|
|
815
|
+
function getToolsByModule(module) {
|
|
816
|
+
return TOOL_CATALOG.filter((t) => t.module === module);
|
|
817
|
+
}
|
|
818
|
+
function getToolsByScope(scope) {
|
|
819
|
+
return TOOL_CATALOG.filter((t) => t.scope === scope);
|
|
820
|
+
}
|
|
821
|
+
function searchTools(query) {
|
|
822
|
+
const q = query.toLowerCase();
|
|
823
|
+
return TOOL_CATALOG.filter(
|
|
824
|
+
(t) => t.name.toLowerCase().includes(q) || t.description.toLowerCase().includes(q)
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
function getModules() {
|
|
828
|
+
return [...new Set(TOOL_CATALOG.map((t) => t.module))];
|
|
829
|
+
}
|
|
830
|
+
var TOOL_CATALOG;
|
|
831
|
+
var init_tool_catalog = __esm({
|
|
832
|
+
"src/lib/tool-catalog.ts"() {
|
|
833
|
+
"use strict";
|
|
834
|
+
TOOL_CATALOG = [
|
|
835
|
+
// ideation
|
|
836
|
+
{
|
|
837
|
+
name: "generate_content",
|
|
838
|
+
description: "Generate social media content ideas based on brand profile and trends",
|
|
839
|
+
module: "ideation",
|
|
840
|
+
scope: "mcp:write"
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: "fetch_trends",
|
|
844
|
+
description: "Fetch current trending topics for content ideation",
|
|
845
|
+
module: "ideation",
|
|
846
|
+
scope: "mcp:read"
|
|
847
|
+
},
|
|
848
|
+
// ideation-context
|
|
849
|
+
{
|
|
850
|
+
name: "get_ideation_context",
|
|
851
|
+
description: "Get full ideation context including brand, analytics, and trends",
|
|
852
|
+
module: "ideation-context",
|
|
853
|
+
scope: "mcp:read"
|
|
854
|
+
},
|
|
855
|
+
// content
|
|
856
|
+
{
|
|
857
|
+
name: "adapt_content",
|
|
858
|
+
description: "Adapt existing content for different platforms",
|
|
859
|
+
module: "content",
|
|
860
|
+
scope: "mcp:write"
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
name: "generate_video",
|
|
864
|
+
description: "Generate video content using AI",
|
|
865
|
+
module: "content",
|
|
866
|
+
scope: "mcp:write"
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
name: "generate_image",
|
|
870
|
+
description: "Generate images using AI",
|
|
871
|
+
module: "content",
|
|
872
|
+
scope: "mcp:write"
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
name: "check_status",
|
|
876
|
+
description: "Check status of async content generation job",
|
|
877
|
+
module: "content",
|
|
878
|
+
scope: "mcp:write"
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
name: "create_storyboard",
|
|
882
|
+
description: "Create a video storyboard with scenes and shots",
|
|
883
|
+
module: "content",
|
|
884
|
+
scope: "mcp:write"
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
name: "generate_voiceover",
|
|
888
|
+
description: "Generate AI voiceover audio",
|
|
889
|
+
module: "content",
|
|
890
|
+
scope: "mcp:write"
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
name: "generate_carousel",
|
|
894
|
+
description: "Generate carousel/slide content",
|
|
895
|
+
module: "content",
|
|
896
|
+
scope: "mcp:write"
|
|
897
|
+
},
|
|
898
|
+
// distribution
|
|
899
|
+
{
|
|
900
|
+
name: "schedule_post",
|
|
901
|
+
description: "Schedule content for publishing to social platforms",
|
|
902
|
+
module: "distribution",
|
|
903
|
+
scope: "mcp:distribute"
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
name: "list_recent_posts",
|
|
907
|
+
description: "List recently published or scheduled posts",
|
|
908
|
+
module: "distribution",
|
|
909
|
+
scope: "mcp:read"
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
name: "list_connected_accounts",
|
|
913
|
+
description: "List connected social media accounts",
|
|
914
|
+
module: "distribution",
|
|
915
|
+
scope: "mcp:read"
|
|
916
|
+
},
|
|
917
|
+
// analytics
|
|
918
|
+
{
|
|
919
|
+
name: "fetch_analytics",
|
|
920
|
+
description: "Fetch post performance analytics",
|
|
921
|
+
module: "analytics",
|
|
922
|
+
scope: "mcp:read"
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
name: "refresh_platform_analytics",
|
|
926
|
+
description: "Refresh analytics data from connected platforms",
|
|
927
|
+
module: "analytics",
|
|
928
|
+
scope: "mcp:analytics"
|
|
929
|
+
},
|
|
930
|
+
// insights
|
|
931
|
+
{
|
|
932
|
+
name: "get_performance_insights",
|
|
933
|
+
description: "Get AI-generated performance insights",
|
|
934
|
+
module: "insights",
|
|
935
|
+
scope: "mcp:read"
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
name: "get_best_posting_times",
|
|
939
|
+
description: "Get recommended posting times based on audience data",
|
|
940
|
+
module: "insights",
|
|
941
|
+
scope: "mcp:read"
|
|
942
|
+
},
|
|
943
|
+
// brand
|
|
944
|
+
{
|
|
945
|
+
name: "extract_brand",
|
|
946
|
+
description: "Extract brand identity from URL or text",
|
|
947
|
+
module: "brand",
|
|
948
|
+
scope: "mcp:read"
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
name: "get_brand_profile",
|
|
952
|
+
description: "Get the current brand profile",
|
|
953
|
+
module: "brand",
|
|
954
|
+
scope: "mcp:read"
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
name: "save_brand_profile",
|
|
958
|
+
description: "Save or update brand profile",
|
|
959
|
+
module: "brand",
|
|
960
|
+
scope: "mcp:write"
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
name: "update_platform_voice",
|
|
964
|
+
description: "Update platform-specific brand voice settings",
|
|
965
|
+
module: "brand",
|
|
966
|
+
scope: "mcp:write"
|
|
967
|
+
},
|
|
968
|
+
// screenshot
|
|
969
|
+
{
|
|
970
|
+
name: "capture_screenshot",
|
|
971
|
+
description: "Capture a screenshot of a URL",
|
|
972
|
+
module: "screenshot",
|
|
973
|
+
scope: "mcp:read"
|
|
974
|
+
},
|
|
975
|
+
{
|
|
976
|
+
name: "capture_app_page",
|
|
977
|
+
description: "Capture a screenshot of an app page",
|
|
978
|
+
module: "screenshot",
|
|
979
|
+
scope: "mcp:read"
|
|
980
|
+
},
|
|
981
|
+
// remotion
|
|
982
|
+
{
|
|
983
|
+
name: "render_demo_video",
|
|
984
|
+
description: "Render a demo video using Remotion",
|
|
985
|
+
module: "remotion",
|
|
986
|
+
scope: "mcp:write"
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
name: "list_compositions",
|
|
990
|
+
description: "List available Remotion video compositions",
|
|
991
|
+
module: "remotion",
|
|
992
|
+
scope: "mcp:read"
|
|
993
|
+
},
|
|
994
|
+
// youtube-analytics
|
|
995
|
+
{
|
|
996
|
+
name: "fetch_youtube_analytics",
|
|
997
|
+
description: "Fetch YouTube channel analytics data",
|
|
998
|
+
module: "youtube-analytics",
|
|
999
|
+
scope: "mcp:analytics"
|
|
1000
|
+
},
|
|
1001
|
+
// comments
|
|
1002
|
+
{
|
|
1003
|
+
name: "list_comments",
|
|
1004
|
+
description: "List comments on published posts",
|
|
1005
|
+
module: "comments",
|
|
1006
|
+
scope: "mcp:comments"
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
name: "reply_to_comment",
|
|
1010
|
+
description: "Reply to a comment on a post",
|
|
1011
|
+
module: "comments",
|
|
1012
|
+
scope: "mcp:comments"
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
name: "post_comment",
|
|
1016
|
+
description: "Post a new comment",
|
|
1017
|
+
module: "comments",
|
|
1018
|
+
scope: "mcp:comments"
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
name: "moderate_comment",
|
|
1022
|
+
description: "Moderate a comment (approve/hide/flag)",
|
|
1023
|
+
module: "comments",
|
|
1024
|
+
scope: "mcp:comments"
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
name: "delete_comment",
|
|
1028
|
+
description: "Delete a comment",
|
|
1029
|
+
module: "comments",
|
|
1030
|
+
scope: "mcp:comments"
|
|
1031
|
+
},
|
|
1032
|
+
// planning
|
|
1033
|
+
{
|
|
1034
|
+
name: "plan_content_week",
|
|
1035
|
+
description: "Generate a weekly content plan",
|
|
1036
|
+
module: "planning",
|
|
1037
|
+
scope: "mcp:write"
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
name: "save_content_plan",
|
|
1041
|
+
description: "Save a content plan",
|
|
1042
|
+
module: "planning",
|
|
1043
|
+
scope: "mcp:write"
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
name: "get_content_plan",
|
|
1047
|
+
description: "Get a specific content plan by ID",
|
|
1048
|
+
module: "planning",
|
|
1049
|
+
scope: "mcp:read"
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
name: "update_content_plan",
|
|
1053
|
+
description: "Update an existing content plan",
|
|
1054
|
+
module: "planning",
|
|
1055
|
+
scope: "mcp:write"
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
name: "submit_content_plan_for_approval",
|
|
1059
|
+
description: "Submit a content plan for team approval",
|
|
1060
|
+
module: "planning",
|
|
1061
|
+
scope: "mcp:write"
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
name: "schedule_content_plan",
|
|
1065
|
+
description: "Schedule all posts in an approved content plan",
|
|
1066
|
+
module: "planning",
|
|
1067
|
+
scope: "mcp:distribute"
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
name: "find_next_slots",
|
|
1071
|
+
description: "Find next available scheduling slots",
|
|
1072
|
+
module: "planning",
|
|
1073
|
+
scope: "mcp:read"
|
|
1074
|
+
},
|
|
1075
|
+
// plan-approvals
|
|
1076
|
+
{
|
|
1077
|
+
name: "create_plan_approvals",
|
|
1078
|
+
description: "Create approval requests for a content plan",
|
|
1079
|
+
module: "plan-approvals",
|
|
1080
|
+
scope: "mcp:write"
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
name: "respond_plan_approval",
|
|
1084
|
+
description: "Respond to a plan approval request",
|
|
1085
|
+
module: "plan-approvals",
|
|
1086
|
+
scope: "mcp:write"
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
name: "list_plan_approvals",
|
|
1090
|
+
description: "List pending plan approval requests",
|
|
1091
|
+
module: "plan-approvals",
|
|
1092
|
+
scope: "mcp:read"
|
|
1093
|
+
},
|
|
1094
|
+
// quality
|
|
1095
|
+
{
|
|
1096
|
+
name: "quality_check",
|
|
1097
|
+
description: "Run quality checks on content before publishing",
|
|
1098
|
+
module: "quality",
|
|
1099
|
+
scope: "mcp:read"
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
name: "quality_check_plan",
|
|
1103
|
+
description: "Run quality checks on an entire content plan",
|
|
1104
|
+
module: "quality",
|
|
1105
|
+
scope: "mcp:read"
|
|
1106
|
+
},
|
|
1107
|
+
// credits
|
|
1108
|
+
{
|
|
1109
|
+
name: "get_credit_balance",
|
|
1110
|
+
description: "Get current credit balance",
|
|
1111
|
+
module: "credits",
|
|
1112
|
+
scope: "mcp:read"
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
name: "get_budget_status",
|
|
1116
|
+
description: "Get budget and spending status",
|
|
1117
|
+
module: "credits",
|
|
1118
|
+
scope: "mcp:read"
|
|
1119
|
+
},
|
|
1120
|
+
// autopilot
|
|
1121
|
+
{
|
|
1122
|
+
name: "list_autopilot_configs",
|
|
1123
|
+
description: "List autopilot configurations",
|
|
1124
|
+
module: "autopilot",
|
|
1125
|
+
scope: "mcp:autopilot"
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
name: "update_autopilot_config",
|
|
1129
|
+
description: "Update autopilot configuration",
|
|
1130
|
+
module: "autopilot",
|
|
1131
|
+
scope: "mcp:autopilot"
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
name: "get_autopilot_status",
|
|
1135
|
+
description: "Get current autopilot status",
|
|
1136
|
+
module: "autopilot",
|
|
1137
|
+
scope: "mcp:autopilot"
|
|
1138
|
+
},
|
|
1139
|
+
// extraction
|
|
1140
|
+
{
|
|
1141
|
+
name: "extract_url_content",
|
|
1142
|
+
description: "Extract content from a URL for repurposing",
|
|
1143
|
+
module: "extraction",
|
|
1144
|
+
scope: "mcp:read"
|
|
1145
|
+
},
|
|
1146
|
+
// loop-summary
|
|
1147
|
+
{
|
|
1148
|
+
name: "get_loop_summary",
|
|
1149
|
+
description: "Get growth loop summary and recommendations",
|
|
1150
|
+
module: "loop-summary",
|
|
1151
|
+
scope: "mcp:read"
|
|
1152
|
+
},
|
|
1153
|
+
// usage
|
|
1154
|
+
{
|
|
1155
|
+
name: "get_mcp_usage",
|
|
1156
|
+
description: "Get MCP usage statistics for the current billing period",
|
|
1157
|
+
module: "usage",
|
|
1158
|
+
scope: "mcp:read"
|
|
1159
|
+
},
|
|
1160
|
+
// discovery
|
|
1161
|
+
{
|
|
1162
|
+
name: "search_tools",
|
|
1163
|
+
description: "Search and discover available MCP tools",
|
|
1164
|
+
module: "discovery",
|
|
1165
|
+
scope: "mcp:read"
|
|
1166
|
+
}
|
|
1167
|
+
];
|
|
1168
|
+
}
|
|
810
1169
|
});
|
|
1170
|
+
|
|
1171
|
+
// src/cli/sn/parse.ts
|
|
811
1172
|
import { createHash as createHash3 } from "node:crypto";
|
|
812
1173
|
function parseSnArgs(argv) {
|
|
813
1174
|
const parsed = { _: [] };
|
|
@@ -828,34 +1189,6 @@ function parseSnArgs(argv) {
|
|
|
828
1189
|
}
|
|
829
1190
|
return parsed;
|
|
830
1191
|
}
|
|
831
|
-
function printSnUsage() {
|
|
832
|
-
console.error("");
|
|
833
|
-
console.error("Usage: socialneuron-mcp sn <command> [flags]");
|
|
834
|
-
console.error("");
|
|
835
|
-
console.error("Commands:");
|
|
836
|
-
console.error(
|
|
837
|
-
" publish --media-url <url> --caption <text> --platforms <comma-list> --confirm [--title <text>] [--schedule-at <iso8601>] [--idempotency-key <key>] [--json]"
|
|
838
|
-
);
|
|
839
|
-
console.error(" preflight [--privacy-url <url>] [--terms-url <url>] [--check-urls] [--json]");
|
|
840
|
-
console.error(" oauth-health [--warn-days <1-90>] [--platforms <comma-list>] [--all] [--json]");
|
|
841
|
-
console.error(" oauth-refresh (--platforms <comma-list> | --all) [--json]");
|
|
842
|
-
console.error(
|
|
843
|
-
" quality-check --caption <text> [--title <text>] [--platforms <comma-list>] [--threshold <0-35>] [--json]"
|
|
844
|
-
);
|
|
845
|
-
console.error(
|
|
846
|
-
" e2e --media-url <url> --caption <text> --platforms <comma-list> --confirm [--title <text>] [--schedule-at <iso8601>] [--check-urls] [--threshold <0-35>] [--dry-run] [--force] [--json]"
|
|
847
|
-
);
|
|
848
|
-
console.error(" status --job-id <id> [--json]");
|
|
849
|
-
console.error(
|
|
850
|
-
" posts [--days <1-90>] [--platform <name>] [--status <draft|scheduled|published|failed>] [--json]"
|
|
851
|
-
);
|
|
852
|
-
console.error(" refresh-analytics [--json]");
|
|
853
|
-
console.error(" autopilot [--json]");
|
|
854
|
-
console.error(" usage [--json]");
|
|
855
|
-
console.error(" loop [--json]");
|
|
856
|
-
console.error(" credits [--json]");
|
|
857
|
-
console.error("");
|
|
858
|
-
}
|
|
859
1192
|
function isEnabledFlag(value) {
|
|
860
1193
|
if (value === true) return true;
|
|
861
1194
|
if (typeof value === "string") {
|
|
@@ -866,7 +1199,8 @@ function isEnabledFlag(value) {
|
|
|
866
1199
|
}
|
|
867
1200
|
function emitSnResult(payload, asJson) {
|
|
868
1201
|
if (asJson) {
|
|
869
|
-
|
|
1202
|
+
const envelope = { schema_version: "1", ...payload };
|
|
1203
|
+
process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
|
|
870
1204
|
}
|
|
871
1205
|
}
|
|
872
1206
|
function isValidHttpsUrl(value) {
|
|
@@ -953,1057 +1287,1646 @@ function tryGetSupabaseClient() {
|
|
|
953
1287
|
return null;
|
|
954
1288
|
}
|
|
955
1289
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
process.exit(1);
|
|
1290
|
+
var init_parse = __esm({
|
|
1291
|
+
"src/cli/sn/parse.ts"() {
|
|
1292
|
+
"use strict";
|
|
1293
|
+
init_supabase();
|
|
961
1294
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const confirmed = isEnabledFlag(args.confirm);
|
|
975
|
-
if (!confirmed) {
|
|
976
|
-
const message = "Publish confirmation required. Re-run with --confirm to execute schedule-post.";
|
|
977
|
-
if (asJson) {
|
|
978
|
-
emitSnResult(
|
|
979
|
-
{
|
|
980
|
-
ok: false,
|
|
981
|
-
command: "publish",
|
|
982
|
-
error: message,
|
|
983
|
-
hint: "Use --confirm (or --confirm true)."
|
|
984
|
-
},
|
|
985
|
-
true
|
|
986
|
-
);
|
|
987
|
-
} else {
|
|
988
|
-
console.error(message);
|
|
989
|
-
}
|
|
990
|
-
process.exit(1);
|
|
991
|
-
}
|
|
992
|
-
const caseMap = {
|
|
993
|
-
youtube: "YouTube",
|
|
994
|
-
tiktok: "TikTok",
|
|
995
|
-
instagram: "Instagram",
|
|
996
|
-
twitter: "Twitter",
|
|
997
|
-
linkedin: "LinkedIn",
|
|
998
|
-
facebook: "Facebook",
|
|
999
|
-
threads: "Threads",
|
|
1000
|
-
bluesky: "Bluesky"
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
// src/cli/error-handling.ts
|
|
1298
|
+
function classifyError(err) {
|
|
1299
|
+
const message = err instanceof Error ? err.message : typeof err === "string" ? err : String(err ?? "Unknown error");
|
|
1300
|
+
const lower = message.toLowerCase();
|
|
1301
|
+
if (lower.includes("no authentication") || lower.includes("unauthorized") || lower.includes("api key invalid") || lower.includes("expired") || lower.includes("not logged in") || lower.includes("invalid api key") || lower.includes("invalid signature")) {
|
|
1302
|
+
return {
|
|
1303
|
+
message,
|
|
1304
|
+
errorType: "AUTH",
|
|
1305
|
+
retryable: false,
|
|
1306
|
+
hint: "Run: socialneuron-mcp login"
|
|
1001
1307
|
};
|
|
1002
|
-
const platforms = platformsRaw.split(",").map((p) => p.trim().toLowerCase()).filter(Boolean).map((p) => caseMap[p] ?? p);
|
|
1003
|
-
const title = typeof args.title === "string" ? args.title : void 0;
|
|
1004
|
-
const scheduledAt = typeof args["schedule-at"] === "string" ? args["schedule-at"] : void 0;
|
|
1005
|
-
const idempotencyKey = typeof args["idempotency-key"] === "string" ? args["idempotency-key"] : buildPublishIdempotencyKey({
|
|
1006
|
-
mediaUrl,
|
|
1007
|
-
caption,
|
|
1008
|
-
platforms,
|
|
1009
|
-
title,
|
|
1010
|
-
scheduledAt
|
|
1011
|
-
});
|
|
1012
|
-
const { data, error } = await callEdgeFunction("schedule-post", {
|
|
1013
|
-
mediaUrl,
|
|
1014
|
-
caption,
|
|
1015
|
-
platforms,
|
|
1016
|
-
title,
|
|
1017
|
-
scheduledAt,
|
|
1018
|
-
idempotencyKey,
|
|
1019
|
-
userId
|
|
1020
|
-
});
|
|
1021
|
-
if (error || !data) {
|
|
1022
|
-
const message = `Publish failed: ${error ?? "Unknown error"}`;
|
|
1023
|
-
if (asJson) {
|
|
1024
|
-
emitSnResult({ ok: false, command: "publish", error: message }, true);
|
|
1025
|
-
} else {
|
|
1026
|
-
console.error(message);
|
|
1027
|
-
}
|
|
1028
|
-
process.exit(1);
|
|
1029
|
-
}
|
|
1030
|
-
if (asJson) {
|
|
1031
|
-
emitSnResult(
|
|
1032
|
-
{
|
|
1033
|
-
ok: data.success,
|
|
1034
|
-
command: "publish",
|
|
1035
|
-
idempotencyKey,
|
|
1036
|
-
scheduledAt: data.scheduledAt,
|
|
1037
|
-
results: data.results
|
|
1038
|
-
},
|
|
1039
|
-
true
|
|
1040
|
-
);
|
|
1041
|
-
} else {
|
|
1042
|
-
console.error(`Idempotency key: ${idempotencyKey}`);
|
|
1043
|
-
console.error(`Scheduled for: ${data.scheduledAt}`);
|
|
1044
|
-
for (const [platform3, result] of Object.entries(
|
|
1045
|
-
data.results
|
|
1046
|
-
)) {
|
|
1047
|
-
if (result.success) {
|
|
1048
|
-
console.error(`${platform3}: OK (jobId=${result.jobId}, postId=${result.postId})`);
|
|
1049
|
-
} else {
|
|
1050
|
-
console.error(`${platform3}: FAILED (${result.error})`);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
process.exit(data.success ? 0 : 1);
|
|
1055
1308
|
}
|
|
1056
|
-
if (
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
const platformsNormalized = normalizePlatforms(platformsRaw);
|
|
1071
|
-
const thresholdRaw = typeof args.threshold === "string" ? Number(args.threshold) : void 0;
|
|
1072
|
-
const quality = evaluateQuality({
|
|
1073
|
-
caption,
|
|
1074
|
-
title,
|
|
1075
|
-
platforms: platformsNormalized,
|
|
1076
|
-
threshold: Number.isFinite(thresholdRaw) ? thresholdRaw : void 0
|
|
1077
|
-
});
|
|
1078
|
-
const payload = {
|
|
1079
|
-
ok: quality.passed,
|
|
1080
|
-
command: "quality-check",
|
|
1081
|
-
platforms: platformsNormalized,
|
|
1082
|
-
threshold: quality.threshold,
|
|
1083
|
-
score: quality.total,
|
|
1084
|
-
maxScore: quality.maxTotal,
|
|
1085
|
-
blockers: quality.blockers,
|
|
1086
|
-
categories: quality.categories
|
|
1309
|
+
if (lower.includes("econnrefused") || lower.includes("etimedout") || lower.includes("fetch failed") || lower.includes("aborterror") || lower.includes("network") || lower.includes("dns")) {
|
|
1310
|
+
return {
|
|
1311
|
+
message,
|
|
1312
|
+
errorType: "NETWORK",
|
|
1313
|
+
retryable: true,
|
|
1314
|
+
hint: "Check your network connection and try again."
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
if (lower.includes("rate limit") || lower.includes("429") || lower.includes("too many requests")) {
|
|
1318
|
+
return {
|
|
1319
|
+
message,
|
|
1320
|
+
errorType: "RATE_LIMIT",
|
|
1321
|
+
retryable: true,
|
|
1322
|
+
hint: "Wait a moment and retry."
|
|
1087
1323
|
};
|
|
1324
|
+
}
|
|
1325
|
+
if (lower.includes("not found") || lower.includes("404") || lower.includes("no job found")) {
|
|
1326
|
+
return { message, errorType: "NOT_FOUND", retryable: false };
|
|
1327
|
+
}
|
|
1328
|
+
if (lower.includes("missing required") || lower.includes("invalid")) {
|
|
1329
|
+
return { message, errorType: "VALIDATION", retryable: false };
|
|
1330
|
+
}
|
|
1331
|
+
return { message, errorType: "INTERNAL", retryable: true };
|
|
1332
|
+
}
|
|
1333
|
+
async function withSnErrorHandling(command2, asJson, fn, replMode = false) {
|
|
1334
|
+
try {
|
|
1335
|
+
await fn();
|
|
1336
|
+
} catch (err) {
|
|
1337
|
+
const classified = classifyError(err);
|
|
1088
1338
|
if (asJson) {
|
|
1089
|
-
|
|
1339
|
+
const response = {
|
|
1340
|
+
ok: false,
|
|
1341
|
+
command: command2,
|
|
1342
|
+
error: classified.message,
|
|
1343
|
+
errorType: classified.errorType,
|
|
1344
|
+
retryable: classified.retryable,
|
|
1345
|
+
...classified.hint ? { hint: classified.hint } : {},
|
|
1346
|
+
schema_version: "1"
|
|
1347
|
+
};
|
|
1348
|
+
process.stdout.write(JSON.stringify(response, null, 2) + "\n");
|
|
1090
1349
|
} else {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
)
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
console.error("Blockers: " + quality.blockers.join("; "));
|
|
1099
|
-
}
|
|
1100
|
-
console.error("Decision: " + (quality.passed ? "Publish-ready" : "Needs revision"));
|
|
1350
|
+
process.stderr.write(`Error [${command2}]: ${classified.message}
|
|
1351
|
+
`);
|
|
1352
|
+
if (classified.hint) process.stderr.write(`Hint: ${classified.hint}
|
|
1353
|
+
`);
|
|
1354
|
+
}
|
|
1355
|
+
if (!replMode) {
|
|
1356
|
+
process.exit(1);
|
|
1101
1357
|
}
|
|
1102
|
-
process.exit(quality.passed ? 0 : 1);
|
|
1103
1358
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1359
|
+
}
|
|
1360
|
+
var init_error_handling = __esm({
|
|
1361
|
+
"src/cli/error-handling.ts"() {
|
|
1362
|
+
"use strict";
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
// src/cli/sn/content.ts
|
|
1367
|
+
var content_exports = {};
|
|
1368
|
+
__export(content_exports, {
|
|
1369
|
+
handleE2e: () => handleE2e,
|
|
1370
|
+
handlePublish: () => handlePublish,
|
|
1371
|
+
handleQualityCheck: () => handleQualityCheck
|
|
1372
|
+
});
|
|
1373
|
+
async function ensureAuth() {
|
|
1374
|
+
await initializeAuth();
|
|
1375
|
+
return getDefaultUserId();
|
|
1376
|
+
}
|
|
1377
|
+
async function handlePublish(args, asJson) {
|
|
1378
|
+
const mediaUrl = args["media-url"];
|
|
1379
|
+
const caption = args.caption;
|
|
1380
|
+
const platformsRaw = args.platforms;
|
|
1381
|
+
if (typeof mediaUrl !== "string" || typeof caption !== "string" || typeof platformsRaw !== "string") {
|
|
1382
|
+
throw new Error("Missing required flags for publish: --media-url, --caption, --platforms");
|
|
1383
|
+
}
|
|
1384
|
+
const confirmed = isEnabledFlag(args.confirm);
|
|
1385
|
+
if (!confirmed) {
|
|
1386
|
+
throw new Error(
|
|
1387
|
+
"Missing required flag: --confirm. Re-run with --confirm to execute schedule-post."
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
const platforms = normalizePlatforms(platformsRaw);
|
|
1391
|
+
const title = typeof args.title === "string" ? args.title : void 0;
|
|
1392
|
+
const scheduledAt = typeof args["schedule-at"] === "string" ? args["schedule-at"] : void 0;
|
|
1393
|
+
const idempotencyKey = typeof args["idempotency-key"] === "string" ? args["idempotency-key"] : buildPublishIdempotencyKey({
|
|
1394
|
+
mediaUrl,
|
|
1395
|
+
caption,
|
|
1396
|
+
platforms,
|
|
1397
|
+
title,
|
|
1398
|
+
scheduledAt
|
|
1399
|
+
});
|
|
1400
|
+
const userId = await ensureAuth();
|
|
1401
|
+
const { data, error } = await callEdgeFunction("schedule-post", {
|
|
1402
|
+
mediaUrl,
|
|
1403
|
+
caption,
|
|
1404
|
+
platforms,
|
|
1405
|
+
title,
|
|
1406
|
+
scheduledAt,
|
|
1407
|
+
idempotencyKey,
|
|
1408
|
+
userId
|
|
1409
|
+
});
|
|
1410
|
+
if (error || !data) {
|
|
1411
|
+
throw new Error(`Publish failed: ${error ?? "Unknown error"}`);
|
|
1412
|
+
}
|
|
1413
|
+
if (asJson) {
|
|
1414
|
+
emitSnResult(
|
|
1415
|
+
{
|
|
1416
|
+
ok: data.success,
|
|
1417
|
+
command: "publish",
|
|
1418
|
+
idempotencyKey,
|
|
1419
|
+
scheduledAt: data.scheduledAt,
|
|
1420
|
+
results: data.results
|
|
1421
|
+
},
|
|
1422
|
+
true
|
|
1423
|
+
);
|
|
1424
|
+
} else {
|
|
1425
|
+
console.error(`Idempotency key: ${idempotencyKey}`);
|
|
1426
|
+
console.error(`Scheduled for: ${data.scheduledAt}`);
|
|
1427
|
+
for (const [platform3, result] of Object.entries(
|
|
1428
|
+
data.results
|
|
1429
|
+
)) {
|
|
1430
|
+
if (result.success) {
|
|
1431
|
+
console.error(`${platform3}: OK (jobId=${result.jobId}, postId=${result.postId})`);
|
|
1112
1432
|
} else {
|
|
1113
|
-
console.error(
|
|
1114
|
-
printSnUsage();
|
|
1433
|
+
console.error(`${platform3}: FAILED (${result.error})`);
|
|
1115
1434
|
}
|
|
1116
|
-
process.exit(1);
|
|
1117
1435
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
ok: r.ok,
|
|
1152
|
-
detail: r.ok ? "Reachable (HTTP " + (r.status ?? 200) + ")" : "Unreachable (" + (r.error ?? "HTTP " + (r.status ?? "unknown")) + ")"
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
|
-
let activeAccounts = [];
|
|
1156
|
-
if (supabase) {
|
|
1157
|
-
const { data: accounts, error: accountsError } = await supabase.from("connected_accounts").select("platform, status, username, expires_at").eq("user_id", userId).eq("status", "active");
|
|
1158
|
-
if (accountsError) {
|
|
1159
|
-
const formatted = classifySupabaseCliError("load connected accounts", accountsError);
|
|
1160
|
-
if (asJson) {
|
|
1161
|
-
emitSnResult(
|
|
1162
|
-
{ ok: false, command: "e2e", error: formatted.message, hint: formatted.hint },
|
|
1163
|
-
true
|
|
1164
|
-
);
|
|
1165
|
-
} else {
|
|
1166
|
-
console.error(formatted.message);
|
|
1167
|
-
if (formatted.hint) console.error("Hint: " + formatted.hint);
|
|
1168
|
-
}
|
|
1169
|
-
process.exit(1);
|
|
1170
|
-
}
|
|
1171
|
-
activeAccounts = accounts ?? [];
|
|
1172
|
-
} else {
|
|
1173
|
-
const { data: data2, error: error2 } = await callEdgeFunction(
|
|
1174
|
-
"mcp-data",
|
|
1175
|
-
{ action: "connected-accounts", userId }
|
|
1176
|
-
);
|
|
1177
|
-
if (error2 || !data2?.success) {
|
|
1178
|
-
const message = "Failed to load connected accounts: " + (error2 ?? "Unknown error");
|
|
1179
|
-
if (asJson) emitSnResult({ ok: false, command: "e2e", error: message }, true);
|
|
1180
|
-
else console.error(message);
|
|
1181
|
-
process.exit(1);
|
|
1182
|
-
}
|
|
1183
|
-
activeAccounts = data2.accounts ?? [];
|
|
1184
|
-
}
|
|
1185
|
-
const expired = activeAccounts.filter(
|
|
1186
|
-
(a) => a.expires_at && new Date(a.expires_at).getTime() <= Date.now()
|
|
1436
|
+
}
|
|
1437
|
+
process.exit(data.success ? 0 : 1);
|
|
1438
|
+
}
|
|
1439
|
+
async function handleQualityCheck(args, asJson) {
|
|
1440
|
+
const caption = args.caption;
|
|
1441
|
+
if (typeof caption !== "string") {
|
|
1442
|
+
throw new Error("Missing required flag: --caption");
|
|
1443
|
+
}
|
|
1444
|
+
const title = typeof args.title === "string" ? args.title : void 0;
|
|
1445
|
+
const platformsRaw = typeof args.platforms === "string" ? args.platforms : "youtube";
|
|
1446
|
+
const platformsNormalized = normalizePlatforms(platformsRaw);
|
|
1447
|
+
const thresholdRaw = typeof args.threshold === "string" ? Number(args.threshold) : void 0;
|
|
1448
|
+
const quality = evaluateQuality({
|
|
1449
|
+
caption,
|
|
1450
|
+
title,
|
|
1451
|
+
platforms: platformsNormalized,
|
|
1452
|
+
threshold: Number.isFinite(thresholdRaw) ? thresholdRaw : void 0
|
|
1453
|
+
});
|
|
1454
|
+
const payload = {
|
|
1455
|
+
ok: quality.passed,
|
|
1456
|
+
command: "quality-check",
|
|
1457
|
+
platforms: platformsNormalized,
|
|
1458
|
+
threshold: quality.threshold,
|
|
1459
|
+
score: quality.total,
|
|
1460
|
+
maxScore: quality.maxTotal,
|
|
1461
|
+
blockers: quality.blockers,
|
|
1462
|
+
categories: quality.categories
|
|
1463
|
+
};
|
|
1464
|
+
if (asJson) {
|
|
1465
|
+
emitSnResult(payload, true);
|
|
1466
|
+
} else {
|
|
1467
|
+
console.error(
|
|
1468
|
+
"QUALITY SCORE: " + quality.total + "/" + quality.maxTotal + " (threshold " + quality.threshold + ")"
|
|
1187
1469
|
);
|
|
1470
|
+
for (const c of quality.categories) {
|
|
1471
|
+
console.error("- " + c.name + ": " + c.score + "/" + c.maxScore);
|
|
1472
|
+
}
|
|
1473
|
+
if (quality.blockers.length) {
|
|
1474
|
+
console.error("Blockers: " + quality.blockers.join("; "));
|
|
1475
|
+
}
|
|
1476
|
+
console.error("Decision: " + (quality.passed ? "Publish-ready" : "Needs revision"));
|
|
1477
|
+
}
|
|
1478
|
+
process.exit(quality.passed ? 0 : 1);
|
|
1479
|
+
}
|
|
1480
|
+
async function handleE2e(args, asJson) {
|
|
1481
|
+
const mediaUrl = args["media-url"];
|
|
1482
|
+
const caption = args.caption;
|
|
1483
|
+
const platformsRaw = args.platforms;
|
|
1484
|
+
if (typeof mediaUrl !== "string" || typeof caption !== "string" || typeof platformsRaw !== "string") {
|
|
1485
|
+
throw new Error("Missing required flags for e2e: --media-url, --caption, --platforms");
|
|
1486
|
+
}
|
|
1487
|
+
const title = typeof args.title === "string" ? args.title : void 0;
|
|
1488
|
+
const scheduledAt = typeof args["schedule-at"] === "string" ? args["schedule-at"] : void 0;
|
|
1489
|
+
const checkUrls = isEnabledFlag(args["check-urls"]);
|
|
1490
|
+
const dryRun = isEnabledFlag(args["dry-run"]);
|
|
1491
|
+
const force = isEnabledFlag(args.force);
|
|
1492
|
+
const platformsNormalized = normalizePlatforms(platformsRaw);
|
|
1493
|
+
const thresholdRaw = typeof args.threshold === "string" ? Number(args.threshold) : void 0;
|
|
1494
|
+
const userId = await ensureAuth();
|
|
1495
|
+
const supabase = tryGetSupabaseClient();
|
|
1496
|
+
const privacyUrl = process.env.SOCIALNEURON_PRIVACY_POLICY_URL ?? null;
|
|
1497
|
+
const termsUrl = process.env.SOCIALNEURON_TERMS_URL ?? null;
|
|
1498
|
+
const preflightChecks = [];
|
|
1499
|
+
preflightChecks.push({
|
|
1500
|
+
name: "privacy_policy_url_present",
|
|
1501
|
+
ok: Boolean(privacyUrl),
|
|
1502
|
+
detail: privacyUrl ? privacyUrl : "Missing SOCIALNEURON_PRIVACY_POLICY_URL"
|
|
1503
|
+
});
|
|
1504
|
+
preflightChecks.push({
|
|
1505
|
+
name: "terms_url_present",
|
|
1506
|
+
ok: Boolean(termsUrl),
|
|
1507
|
+
detail: termsUrl ? termsUrl : "Missing SOCIALNEURON_TERMS_URL"
|
|
1508
|
+
});
|
|
1509
|
+
if (checkUrls && privacyUrl && isValidHttpsUrl(privacyUrl)) {
|
|
1510
|
+
const r = await checkUrlReachability(privacyUrl);
|
|
1188
1511
|
preflightChecks.push({
|
|
1189
|
-
name: "
|
|
1190
|
-
ok:
|
|
1191
|
-
detail:
|
|
1512
|
+
name: "privacy_policy_url_reachable",
|
|
1513
|
+
ok: r.ok,
|
|
1514
|
+
detail: r.ok ? "Reachable (HTTP " + (r.status ?? 200) + ")" : "Unreachable (" + (r.error ?? "HTTP " + (r.status ?? "unknown")) + ")"
|
|
1192
1515
|
});
|
|
1516
|
+
}
|
|
1517
|
+
if (checkUrls && termsUrl && isValidHttpsUrl(termsUrl)) {
|
|
1518
|
+
const r = await checkUrlReachability(termsUrl);
|
|
1193
1519
|
preflightChecks.push({
|
|
1194
|
-
name: "
|
|
1195
|
-
ok:
|
|
1196
|
-
detail:
|
|
1197
|
-
});
|
|
1198
|
-
const preflightOk = preflightChecks.every((c) => c.ok);
|
|
1199
|
-
const quality = evaluateQuality({
|
|
1200
|
-
caption,
|
|
1201
|
-
title,
|
|
1202
|
-
platforms: platformsNormalized,
|
|
1203
|
-
threshold: Number.isFinite(thresholdRaw) ? thresholdRaw : void 0
|
|
1520
|
+
name: "terms_url_reachable",
|
|
1521
|
+
ok: r.ok,
|
|
1522
|
+
detail: r.ok ? "Reachable (HTTP " + (r.status ?? 200) + ")" : "Unreachable (" + (r.error ?? "HTTP " + (r.status ?? "unknown")) + ")"
|
|
1204
1523
|
});
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
if (
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
ok: canPublish,
|
|
1213
|
-
command: "e2e",
|
|
1214
|
-
dryRun,
|
|
1215
|
-
mediaUrl,
|
|
1216
|
-
platforms: platformsNormalized,
|
|
1217
|
-
scheduledAt: scheduledAt ?? null,
|
|
1218
|
-
preflight: {
|
|
1219
|
-
ok: preflightOk,
|
|
1220
|
-
checks: preflightChecks,
|
|
1221
|
-
connectedPlatforms: activeAccounts.map((a) => ({
|
|
1222
|
-
platform: a.platform,
|
|
1223
|
-
username: a.username,
|
|
1224
|
-
expiresAt: a.expires_at
|
|
1225
|
-
}))
|
|
1226
|
-
},
|
|
1227
|
-
quality: {
|
|
1228
|
-
passed: quality.passed,
|
|
1229
|
-
threshold: quality.threshold,
|
|
1230
|
-
score: quality.total,
|
|
1231
|
-
maxScore: quality.maxTotal,
|
|
1232
|
-
blockers: quality.blockers,
|
|
1233
|
-
categories: quality.categories
|
|
1234
|
-
},
|
|
1235
|
-
blockedReasons
|
|
1236
|
-
};
|
|
1237
|
-
if (dryRun || !canPublish && !force) {
|
|
1238
|
-
if (asJson) {
|
|
1239
|
-
emitSnResult(report, true);
|
|
1240
|
-
} else {
|
|
1241
|
-
console.error("E2E: " + (canPublish ? "READY" : "BLOCKED"));
|
|
1242
|
-
console.error("Preflight: " + (preflightOk ? "PASS" : "FAIL"));
|
|
1243
|
-
console.error(
|
|
1244
|
-
"Quality: " + (quality.passed ? "PASS" : "FAIL") + " (" + quality.total + "/" + quality.maxTotal + ", threshold " + quality.threshold + ")"
|
|
1245
|
-
);
|
|
1246
|
-
if (blockedReasons.length) console.error("Blocked: " + blockedReasons.join(", "));
|
|
1247
|
-
console.error(
|
|
1248
|
-
"Use --dry-run for JSON output; use --force to publish despite blockers (not recommended)."
|
|
1249
|
-
);
|
|
1250
|
-
}
|
|
1251
|
-
process.exit(canPublish ? 0 : 1);
|
|
1524
|
+
}
|
|
1525
|
+
let activeAccounts = [];
|
|
1526
|
+
if (supabase) {
|
|
1527
|
+
const { data: accounts, error: accountsError } = await supabase.from("connected_accounts").select("platform, status, username, expires_at").eq("user_id", userId).eq("status", "active");
|
|
1528
|
+
if (accountsError) {
|
|
1529
|
+
const formatted = classifySupabaseCliError("load connected accounts", accountsError);
|
|
1530
|
+
throw new Error(formatted.message);
|
|
1252
1531
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
mediaUrl,
|
|
1262
|
-
caption,
|
|
1263
|
-
platforms: platformsNormalized,
|
|
1264
|
-
title,
|
|
1265
|
-
scheduledAt,
|
|
1266
|
-
idempotencyKey,
|
|
1267
|
-
userId
|
|
1268
|
-
});
|
|
1269
|
-
if (error || !data) {
|
|
1270
|
-
const message = "Publish failed: " + (error ?? "Unknown error");
|
|
1271
|
-
if (asJson)
|
|
1272
|
-
emitSnResult({ ok: false, command: "e2e", error: message, idempotencyKey, report }, true);
|
|
1273
|
-
else console.error(message);
|
|
1274
|
-
process.exit(1);
|
|
1532
|
+
activeAccounts = accounts ?? [];
|
|
1533
|
+
} else {
|
|
1534
|
+
const { data: data2, error: error2 } = await callEdgeFunction(
|
|
1535
|
+
"mcp-data",
|
|
1536
|
+
{ action: "connected-accounts", userId }
|
|
1537
|
+
);
|
|
1538
|
+
if (error2 || !data2?.success) {
|
|
1539
|
+
throw new Error("Failed to load connected accounts: " + (error2 ?? "Unknown error"));
|
|
1275
1540
|
}
|
|
1541
|
+
activeAccounts = data2.accounts ?? [];
|
|
1542
|
+
}
|
|
1543
|
+
const expired = activeAccounts.filter(
|
|
1544
|
+
(a) => a.expires_at && new Date(a.expires_at).getTime() <= Date.now()
|
|
1545
|
+
);
|
|
1546
|
+
preflightChecks.push({
|
|
1547
|
+
name: "oauth_connections_present",
|
|
1548
|
+
ok: activeAccounts.length > 0,
|
|
1549
|
+
detail: activeAccounts.length ? activeAccounts.length + " active account(s)" : "No active connected_accounts found"
|
|
1550
|
+
});
|
|
1551
|
+
preflightChecks.push({
|
|
1552
|
+
name: "oauth_tokens_not_expired",
|
|
1553
|
+
ok: expired.length === 0,
|
|
1554
|
+
detail: expired.length ? "Expired: " + expired.map((a) => a.platform).join(", ") : "No expired tokens detected"
|
|
1555
|
+
});
|
|
1556
|
+
const preflightOk = preflightChecks.every((c) => c.ok);
|
|
1557
|
+
const quality = evaluateQuality({
|
|
1558
|
+
caption,
|
|
1559
|
+
title,
|
|
1560
|
+
platforms: platformsNormalized,
|
|
1561
|
+
threshold: Number.isFinite(thresholdRaw) ? thresholdRaw : void 0
|
|
1562
|
+
});
|
|
1563
|
+
const confirmed = isEnabledFlag(args.confirm);
|
|
1564
|
+
const canPublish = confirmed && preflightOk && quality.passed;
|
|
1565
|
+
const blockedReasons = [];
|
|
1566
|
+
if (!preflightOk) blockedReasons.push("preflight_failed");
|
|
1567
|
+
if (!quality.passed) blockedReasons.push("quality_failed");
|
|
1568
|
+
if (!confirmed) blockedReasons.push("missing_confirm");
|
|
1569
|
+
const report = {
|
|
1570
|
+
ok: canPublish,
|
|
1571
|
+
command: "e2e",
|
|
1572
|
+
dryRun,
|
|
1573
|
+
mediaUrl,
|
|
1574
|
+
platforms: platformsNormalized,
|
|
1575
|
+
scheduledAt: scheduledAt ?? null,
|
|
1576
|
+
preflight: {
|
|
1577
|
+
ok: preflightOk,
|
|
1578
|
+
checks: preflightChecks,
|
|
1579
|
+
connectedPlatforms: activeAccounts.map((a) => ({
|
|
1580
|
+
platform: a.platform,
|
|
1581
|
+
username: a.username,
|
|
1582
|
+
expiresAt: a.expires_at
|
|
1583
|
+
}))
|
|
1584
|
+
},
|
|
1585
|
+
quality: {
|
|
1586
|
+
passed: quality.passed,
|
|
1587
|
+
threshold: quality.threshold,
|
|
1588
|
+
score: quality.total,
|
|
1589
|
+
maxScore: quality.maxTotal,
|
|
1590
|
+
blockers: quality.blockers,
|
|
1591
|
+
categories: quality.categories
|
|
1592
|
+
},
|
|
1593
|
+
blockedReasons
|
|
1594
|
+
};
|
|
1595
|
+
if (dryRun || !canPublish && !force) {
|
|
1276
1596
|
if (asJson) {
|
|
1277
|
-
emitSnResult(
|
|
1278
|
-
{
|
|
1279
|
-
ok: data.success,
|
|
1280
|
-
command: "e2e",
|
|
1281
|
-
idempotencyKey,
|
|
1282
|
-
scheduledAt: data.scheduledAt,
|
|
1283
|
-
results: data.results,
|
|
1284
|
-
report
|
|
1285
|
-
},
|
|
1286
|
-
true
|
|
1287
|
-
);
|
|
1597
|
+
emitSnResult(report, true);
|
|
1288
1598
|
} else {
|
|
1289
|
-
console.error("E2E
|
|
1290
|
-
console.error("
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1599
|
+
console.error("E2E: " + (canPublish ? "READY" : "BLOCKED"));
|
|
1600
|
+
console.error("Preflight: " + (preflightOk ? "PASS" : "FAIL"));
|
|
1601
|
+
console.error(
|
|
1602
|
+
"Quality: " + (quality.passed ? "PASS" : "FAIL") + " (" + quality.total + "/" + quality.maxTotal + ", threshold " + quality.threshold + ")"
|
|
1603
|
+
);
|
|
1604
|
+
if (blockedReasons.length) console.error("Blocked: " + blockedReasons.join(", "));
|
|
1605
|
+
console.error(
|
|
1606
|
+
"Use --dry-run for JSON output; use --force to publish despite blockers (not recommended)."
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
process.exit(canPublish ? 0 : 1);
|
|
1610
|
+
}
|
|
1611
|
+
const idempotencyKey = buildPublishIdempotencyKey({
|
|
1612
|
+
mediaUrl,
|
|
1613
|
+
caption,
|
|
1614
|
+
platforms: platformsNormalized,
|
|
1615
|
+
title,
|
|
1616
|
+
scheduledAt
|
|
1617
|
+
});
|
|
1618
|
+
const { data, error } = await callEdgeFunction("schedule-post", {
|
|
1619
|
+
mediaUrl,
|
|
1620
|
+
caption,
|
|
1621
|
+
platforms: platformsNormalized,
|
|
1622
|
+
title,
|
|
1623
|
+
scheduledAt,
|
|
1624
|
+
idempotencyKey,
|
|
1625
|
+
userId
|
|
1626
|
+
});
|
|
1627
|
+
if (error || !data) {
|
|
1628
|
+
throw new Error("Publish failed: " + (error ?? "Unknown error"));
|
|
1629
|
+
}
|
|
1630
|
+
if (asJson) {
|
|
1631
|
+
emitSnResult(
|
|
1632
|
+
{
|
|
1633
|
+
ok: data.success,
|
|
1634
|
+
command: "e2e",
|
|
1635
|
+
idempotencyKey,
|
|
1636
|
+
scheduledAt: data.scheduledAt,
|
|
1637
|
+
results: data.results,
|
|
1638
|
+
report
|
|
1639
|
+
},
|
|
1640
|
+
true
|
|
1641
|
+
);
|
|
1642
|
+
} else {
|
|
1643
|
+
console.error("E2E publish executed. Idempotency key: " + idempotencyKey);
|
|
1644
|
+
console.error("Scheduled for: " + data.scheduledAt);
|
|
1645
|
+
for (const [platform3, result] of Object.entries(data.results)) {
|
|
1646
|
+
if (result.success)
|
|
1647
|
+
console.error(platform3 + ": OK (jobId=" + result.jobId + ", postId=" + result.postId + ")");
|
|
1648
|
+
else console.error(platform3 + ": FAILED (" + result.error + ")");
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
process.exit(data.success ? 0 : 1);
|
|
1652
|
+
}
|
|
1653
|
+
var init_content = __esm({
|
|
1654
|
+
"src/cli/sn/content.ts"() {
|
|
1655
|
+
"use strict";
|
|
1656
|
+
init_edge_function();
|
|
1657
|
+
init_quality();
|
|
1658
|
+
init_supabase();
|
|
1659
|
+
init_parse();
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
// src/cli/sn/account.ts
|
|
1664
|
+
var account_exports = {};
|
|
1665
|
+
__export(account_exports, {
|
|
1666
|
+
handleOauthHealth: () => handleOauthHealth,
|
|
1667
|
+
handleOauthRefresh: () => handleOauthRefresh,
|
|
1668
|
+
handlePreflight: () => handlePreflight
|
|
1669
|
+
});
|
|
1670
|
+
async function ensureAuth2() {
|
|
1671
|
+
await initializeAuth();
|
|
1672
|
+
return getDefaultUserId();
|
|
1673
|
+
}
|
|
1674
|
+
async function handleOauthHealth(args, asJson) {
|
|
1675
|
+
const userId = await ensureAuth2();
|
|
1676
|
+
const supabase = tryGetSupabaseClient();
|
|
1677
|
+
const warnDaysRaw = typeof args["warn-days"] === "string" ? Number(args["warn-days"]) : 7;
|
|
1678
|
+
const warnDays = Number.isFinite(warnDaysRaw) && warnDaysRaw > 0 ? Math.min(warnDaysRaw, 90) : 7;
|
|
1679
|
+
const includeAll = isEnabledFlag(args.all);
|
|
1680
|
+
const platformsFilter = typeof args.platforms === "string" ? normalizePlatforms(args.platforms) : null;
|
|
1681
|
+
let accounts = [];
|
|
1682
|
+
if (supabase) {
|
|
1683
|
+
let query = supabase.from("connected_accounts").select("platform, status, username, expires_at, refresh_token, updated_at, created_at").eq("user_id", userId).order("platform");
|
|
1684
|
+
if (!includeAll) {
|
|
1685
|
+
query = query.eq("status", "active");
|
|
1686
|
+
}
|
|
1687
|
+
const { data, error } = await query;
|
|
1688
|
+
if (error) {
|
|
1689
|
+
const formatted = classifySupabaseCliError("load oauth health", error);
|
|
1690
|
+
throw new Error(formatted.message);
|
|
1691
|
+
}
|
|
1692
|
+
accounts = data ?? [];
|
|
1693
|
+
} else {
|
|
1694
|
+
const { data, error } = await callEdgeFunction(
|
|
1695
|
+
"mcp-data",
|
|
1696
|
+
{
|
|
1697
|
+
action: "connected-accounts",
|
|
1698
|
+
userId,
|
|
1699
|
+
includeAll
|
|
1297
1700
|
}
|
|
1701
|
+
);
|
|
1702
|
+
if (error || !data?.success) {
|
|
1703
|
+
throw new Error("Failed to load oauth health: " + (error ?? "Unknown error"));
|
|
1704
|
+
}
|
|
1705
|
+
accounts = data.accounts ?? [];
|
|
1706
|
+
}
|
|
1707
|
+
const now = Date.now();
|
|
1708
|
+
const rows = (accounts ?? []).map((a) => {
|
|
1709
|
+
const expiresAtMs = a.expires_at ? new Date(a.expires_at).getTime() : null;
|
|
1710
|
+
const daysLeft = expiresAtMs ? Math.ceil((expiresAtMs - now) / (1e3 * 60 * 60 * 24)) : null;
|
|
1711
|
+
const refreshTokenPresent = Boolean(a.refresh_token ?? a.has_refresh_token);
|
|
1712
|
+
let state = "ok";
|
|
1713
|
+
if (a.status && String(a.status).toLowerCase() !== "active") state = "inactive";
|
|
1714
|
+
if (expiresAtMs && expiresAtMs <= now) state = "expired";
|
|
1715
|
+
else if (daysLeft !== null && daysLeft <= warnDays) state = "expiring_soon";
|
|
1716
|
+
if (!refreshTokenPresent && state === "ok") state = "missing_refresh_token";
|
|
1717
|
+
return {
|
|
1718
|
+
platform: a.platform,
|
|
1719
|
+
username: a.username ?? null,
|
|
1720
|
+
status: a.status,
|
|
1721
|
+
expiresAt: a.expires_at ?? null,
|
|
1722
|
+
daysLeft,
|
|
1723
|
+
refreshTokenPresent,
|
|
1724
|
+
state
|
|
1725
|
+
};
|
|
1726
|
+
});
|
|
1727
|
+
const filtered = platformsFilter ? rows.filter(
|
|
1728
|
+
(r) => platformsFilter.some(
|
|
1729
|
+
(p) => p.toLowerCase() === String(r.platform ?? "").toLowerCase()
|
|
1730
|
+
)
|
|
1731
|
+
) : rows;
|
|
1732
|
+
const ok = filtered.every((r) => {
|
|
1733
|
+
if (String(r.status).toLowerCase() !== "active") return false;
|
|
1734
|
+
if (r.state === "expired") return false;
|
|
1735
|
+
if (!r.refreshTokenPresent) return false;
|
|
1736
|
+
return true;
|
|
1737
|
+
});
|
|
1738
|
+
const payload = {
|
|
1739
|
+
ok,
|
|
1740
|
+
command: "oauth-health",
|
|
1741
|
+
warnDays,
|
|
1742
|
+
accountCount: filtered.length,
|
|
1743
|
+
accounts: filtered
|
|
1744
|
+
};
|
|
1745
|
+
if (asJson) {
|
|
1746
|
+
emitSnResult(payload, true);
|
|
1747
|
+
} else {
|
|
1748
|
+
console.error("OAuth Health: " + (ok ? "PASS" : "WARN/FAIL"));
|
|
1749
|
+
for (const row of filtered) {
|
|
1750
|
+
const exp = row.daysLeft === null ? "n/a" : row.daysLeft + "d";
|
|
1751
|
+
console.error(
|
|
1752
|
+
String(row.platform).toLowerCase() + " | " + (row.username ?? "(unnamed)") + " | status=" + row.status + " | expires=" + exp + " | refresh=" + (row.refreshTokenPresent ? "yes" : "no") + " | state=" + row.state
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
if (!ok) {
|
|
1756
|
+
console.error("");
|
|
1757
|
+
console.error(
|
|
1758
|
+
"Recommended: run oauth-refresh for expiring accounts, or reconnect expired/missing-refresh accounts."
|
|
1759
|
+
);
|
|
1298
1760
|
}
|
|
1299
|
-
process.exit(data.success ? 0 : 1);
|
|
1300
1761
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1762
|
+
process.exit(ok ? 0 : 1);
|
|
1763
|
+
}
|
|
1764
|
+
async function handleOauthRefresh(args, asJson) {
|
|
1765
|
+
const userId = await ensureAuth2();
|
|
1766
|
+
const supabase = tryGetSupabaseClient();
|
|
1767
|
+
const includeAll = isEnabledFlag(args.all);
|
|
1768
|
+
let platforms = [];
|
|
1769
|
+
if (typeof args.platforms === "string") {
|
|
1770
|
+
platforms = normalizePlatforms(args.platforms);
|
|
1771
|
+
} else if (includeAll) {
|
|
1308
1772
|
if (supabase) {
|
|
1309
|
-
|
|
1310
|
-
if (!includeAll) {
|
|
1311
|
-
query = query.eq("status", "active");
|
|
1312
|
-
}
|
|
1313
|
-
const { data, error } = await query;
|
|
1773
|
+
const { data: accounts, error } = await supabase.from("connected_accounts").select("platform").eq("user_id", userId).eq("status", "active");
|
|
1314
1774
|
if (error) {
|
|
1315
|
-
const formatted = classifySupabaseCliError("load
|
|
1316
|
-
|
|
1317
|
-
emitSnResult(
|
|
1318
|
-
{ ok: false, command: "oauth-health", error: formatted.message, hint: formatted.hint },
|
|
1319
|
-
true
|
|
1320
|
-
);
|
|
1321
|
-
} else {
|
|
1322
|
-
console.error(formatted.message);
|
|
1323
|
-
if (formatted.hint) console.error("Hint: " + formatted.hint);
|
|
1324
|
-
}
|
|
1325
|
-
process.exit(1);
|
|
1775
|
+
const formatted = classifySupabaseCliError("load connected accounts", error);
|
|
1776
|
+
throw new Error(formatted.message);
|
|
1326
1777
|
}
|
|
1327
|
-
|
|
1778
|
+
platforms = (accounts ?? []).map((a) => String(a.platform));
|
|
1328
1779
|
} else {
|
|
1329
1780
|
const { data, error } = await callEdgeFunction(
|
|
1330
1781
|
"mcp-data",
|
|
1331
1782
|
{
|
|
1332
1783
|
action: "connected-accounts",
|
|
1333
|
-
userId
|
|
1334
|
-
includeAll
|
|
1784
|
+
userId
|
|
1335
1785
|
}
|
|
1336
1786
|
);
|
|
1337
1787
|
if (error || !data?.success) {
|
|
1338
|
-
|
|
1339
|
-
if (asJson) emitSnResult({ ok: false, command: "oauth-health", error: message }, true);
|
|
1340
|
-
else console.error(message);
|
|
1341
|
-
process.exit(1);
|
|
1788
|
+
throw new Error("Failed to load connected accounts: " + (error ?? "Unknown error"));
|
|
1342
1789
|
}
|
|
1343
|
-
|
|
1790
|
+
platforms = (data.accounts ?? []).map((a) => String(a.platform));
|
|
1344
1791
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
status: a.status,
|
|
1359
|
-
expiresAt: a.expires_at ?? null,
|
|
1360
|
-
daysLeft,
|
|
1361
|
-
refreshTokenPresent,
|
|
1362
|
-
state
|
|
1363
|
-
};
|
|
1364
|
-
});
|
|
1365
|
-
const filtered = platformsFilter ? rows.filter(
|
|
1366
|
-
(r) => platformsFilter.some((p) => p.toLowerCase() === String(r.platform ?? "").toLowerCase())
|
|
1367
|
-
) : rows;
|
|
1368
|
-
const ok = filtered.every((r) => {
|
|
1369
|
-
if (String(r.status).toLowerCase() !== "active") return false;
|
|
1370
|
-
if (r.state === "expired") return false;
|
|
1371
|
-
if (!r.refreshTokenPresent) return false;
|
|
1372
|
-
return true;
|
|
1373
|
-
});
|
|
1374
|
-
const payload = {
|
|
1375
|
-
ok,
|
|
1376
|
-
command: "oauth-health",
|
|
1377
|
-
warnDays,
|
|
1378
|
-
accountCount: filtered.length,
|
|
1379
|
-
accounts: filtered
|
|
1380
|
-
};
|
|
1381
|
-
if (asJson) {
|
|
1382
|
-
emitSnResult(payload, true);
|
|
1792
|
+
}
|
|
1793
|
+
if (!platforms.length) {
|
|
1794
|
+
throw new Error('Missing required flags: pass --platforms "youtube,tiktok" or --all');
|
|
1795
|
+
}
|
|
1796
|
+
const results = {};
|
|
1797
|
+
for (const platform3 of platforms) {
|
|
1798
|
+
const { data, error } = await callEdgeFunction(
|
|
1799
|
+
"social-auth",
|
|
1800
|
+
{},
|
|
1801
|
+
{ query: { action: "refresh", platform: platform3 }, timeoutMs: 3e4 }
|
|
1802
|
+
);
|
|
1803
|
+
if (error || !data?.success) {
|
|
1804
|
+
results[platform3] = { ok: false, expiresAt: null, error: error ?? "Refresh failed" };
|
|
1383
1805
|
} else {
|
|
1384
|
-
|
|
1385
|
-
for (const row of filtered) {
|
|
1386
|
-
const exp = row.daysLeft === null ? "n/a" : row.daysLeft + "d";
|
|
1387
|
-
console.error(
|
|
1388
|
-
String(row.platform).toLowerCase() + " | " + (row.username ?? "(unnamed)") + " | status=" + row.status + " | expires=" + exp + " | refresh=" + (row.refreshTokenPresent ? "yes" : "no") + " | state=" + row.state
|
|
1389
|
-
);
|
|
1390
|
-
}
|
|
1391
|
-
if (!ok) {
|
|
1392
|
-
console.error("");
|
|
1393
|
-
console.error(
|
|
1394
|
-
"Recommended: run oauth-refresh for expiring accounts, or reconnect expired/missing-refresh accounts."
|
|
1395
|
-
);
|
|
1396
|
-
}
|
|
1806
|
+
results[platform3] = { ok: true, expiresAt: data.expires_at ?? null };
|
|
1397
1807
|
}
|
|
1398
|
-
process.exit(ok ? 0 : 1);
|
|
1399
1808
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1409
|
-
if (error) {
|
|
1410
|
-
const formatted = classifySupabaseCliError("load connected accounts", error);
|
|
1411
|
-
if (asJson) {
|
|
1412
|
-
emitSnResult(
|
|
1413
|
-
{
|
|
1414
|
-
ok: false,
|
|
1415
|
-
command: "oauth-refresh",
|
|
1416
|
-
error: formatted.message,
|
|
1417
|
-
hint: formatted.hint
|
|
1418
|
-
},
|
|
1419
|
-
true
|
|
1420
|
-
);
|
|
1421
|
-
} else {
|
|
1422
|
-
console.error(formatted.message);
|
|
1423
|
-
if (formatted.hint) console.error("Hint: " + formatted.hint);
|
|
1424
|
-
}
|
|
1425
|
-
process.exit(1);
|
|
1426
|
-
}
|
|
1427
|
-
platforms = (accounts ?? []).map((a) => String(a.platform));
|
|
1428
|
-
} else {
|
|
1429
|
-
const { data, error } = await callEdgeFunction(
|
|
1430
|
-
"mcp-data",
|
|
1431
|
-
{
|
|
1432
|
-
action: "connected-accounts",
|
|
1433
|
-
userId
|
|
1434
|
-
}
|
|
1435
|
-
);
|
|
1436
|
-
if (error || !data?.success) {
|
|
1437
|
-
const message = "Failed to load connected accounts: " + (error ?? "Unknown error");
|
|
1438
|
-
if (asJson) emitSnResult({ ok: false, command: "oauth-refresh", error: message }, true);
|
|
1439
|
-
else console.error(message);
|
|
1440
|
-
process.exit(1);
|
|
1441
|
-
}
|
|
1442
|
-
platforms = (data.accounts ?? []).map((a) => String(a.platform));
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
if (!platforms.length) {
|
|
1446
|
-
const message = 'Missing required flags: pass --platforms "youtube,tiktok" or --all';
|
|
1447
|
-
if (asJson) {
|
|
1448
|
-
emitSnResult({ ok: false, command: "oauth-refresh", error: message }, true);
|
|
1449
|
-
} else {
|
|
1450
|
-
console.error(message);
|
|
1451
|
-
printSnUsage();
|
|
1452
|
-
}
|
|
1453
|
-
process.exit(1);
|
|
1454
|
-
}
|
|
1455
|
-
const results = {};
|
|
1456
|
-
for (const platform3 of platforms) {
|
|
1457
|
-
const { data, error } = await callEdgeFunction(
|
|
1458
|
-
"social-auth",
|
|
1459
|
-
{},
|
|
1460
|
-
{ query: { action: "refresh", platform: platform3 }, timeoutMs: 3e4 }
|
|
1461
|
-
);
|
|
1462
|
-
if (error || !data?.success) {
|
|
1463
|
-
results[platform3] = { ok: false, expiresAt: null, error: error ?? "Refresh failed" };
|
|
1809
|
+
const ok = Object.values(results).every((r) => r.ok);
|
|
1810
|
+
if (asJson) {
|
|
1811
|
+
emitSnResult({ ok, command: "oauth-refresh", results }, true);
|
|
1812
|
+
} else {
|
|
1813
|
+
console.error("OAuth refresh: " + (ok ? "OK" : "ERRORS"));
|
|
1814
|
+
for (const platform3 of Object.keys(results)) {
|
|
1815
|
+
const result = results[platform3];
|
|
1816
|
+
if (result.ok) {
|
|
1817
|
+
console.error(platform3 + " refreshed (expires_at=" + (result.expiresAt ?? "n/a") + ")");
|
|
1464
1818
|
} else {
|
|
1465
|
-
|
|
1819
|
+
console.error(platform3 + " FAILED (" + result.error + ")");
|
|
1466
1820
|
}
|
|
1467
1821
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
const checks = [];
|
|
1822
|
+
}
|
|
1823
|
+
process.exit(ok ? 0 : 1);
|
|
1824
|
+
}
|
|
1825
|
+
async function handlePreflight(args, asJson) {
|
|
1826
|
+
const userId = await ensureAuth2();
|
|
1827
|
+
const supabase = tryGetSupabaseClient();
|
|
1828
|
+
const privacyFromArg = typeof args["privacy-url"] === "string" ? args["privacy-url"] : null;
|
|
1829
|
+
const termsFromArg = typeof args["terms-url"] === "string" ? args["terms-url"] : null;
|
|
1830
|
+
const privacyUrl = privacyFromArg ?? process.env.SOCIALNEURON_PRIVACY_POLICY_URL ?? null;
|
|
1831
|
+
const termsUrl = termsFromArg ?? process.env.SOCIALNEURON_TERMS_URL ?? null;
|
|
1832
|
+
const checkUrls = isEnabledFlag(args["check-urls"]);
|
|
1833
|
+
const creditCapRaw = process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN ?? "";
|
|
1834
|
+
const assetCapRaw = process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN ?? "";
|
|
1835
|
+
const creditCap = Number(creditCapRaw);
|
|
1836
|
+
const assetCap = Number(assetCapRaw);
|
|
1837
|
+
const checks = [];
|
|
1838
|
+
checks.push({
|
|
1839
|
+
name: "privacy_policy_url_present",
|
|
1840
|
+
ok: Boolean(privacyUrl),
|
|
1841
|
+
detail: privacyUrl ? privacyUrl : "Missing SOCIALNEURON_PRIVACY_POLICY_URL"
|
|
1842
|
+
});
|
|
1843
|
+
checks.push({
|
|
1844
|
+
name: "terms_url_present",
|
|
1845
|
+
ok: Boolean(termsUrl),
|
|
1846
|
+
detail: termsUrl ? termsUrl : "Missing SOCIALNEURON_TERMS_URL"
|
|
1847
|
+
});
|
|
1848
|
+
if (privacyUrl) {
|
|
1496
1849
|
checks.push({
|
|
1497
|
-
name: "
|
|
1498
|
-
ok:
|
|
1499
|
-
detail: privacyUrl ?
|
|
1850
|
+
name: "privacy_policy_url_https",
|
|
1851
|
+
ok: isValidHttpsUrl(privacyUrl),
|
|
1852
|
+
detail: isValidHttpsUrl(privacyUrl) ? "Uses HTTPS" : "Privacy Policy URL must be a valid https:// URL"
|
|
1500
1853
|
});
|
|
1854
|
+
}
|
|
1855
|
+
if (termsUrl) {
|
|
1501
1856
|
checks.push({
|
|
1502
|
-
name: "
|
|
1503
|
-
ok:
|
|
1504
|
-
detail: termsUrl ?
|
|
1857
|
+
name: "terms_url_https",
|
|
1858
|
+
ok: isValidHttpsUrl(termsUrl),
|
|
1859
|
+
detail: isValidHttpsUrl(termsUrl) ? "Uses HTTPS" : "Terms URL must be a valid https:// URL"
|
|
1505
1860
|
});
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
ok: isValidHttpsUrl(privacyUrl),
|
|
1510
|
-
detail: isValidHttpsUrl(privacyUrl) ? "Uses HTTPS" : "Privacy Policy URL must be a valid https:// URL"
|
|
1511
|
-
});
|
|
1512
|
-
}
|
|
1513
|
-
if (termsUrl) {
|
|
1514
|
-
checks.push({
|
|
1515
|
-
name: "terms_url_https",
|
|
1516
|
-
ok: isValidHttpsUrl(termsUrl),
|
|
1517
|
-
detail: isValidHttpsUrl(termsUrl) ? "Uses HTTPS" : "Terms URL must be a valid https:// URL"
|
|
1518
|
-
});
|
|
1519
|
-
}
|
|
1520
|
-
if (checkUrls && privacyUrl && isValidHttpsUrl(privacyUrl)) {
|
|
1521
|
-
const result = await checkUrlReachability(privacyUrl);
|
|
1522
|
-
checks.push({
|
|
1523
|
-
name: "privacy_policy_url_reachable",
|
|
1524
|
-
ok: result.ok,
|
|
1525
|
-
detail: result.ok ? `Reachable (HTTP ${result.status ?? 200})` : `Unreachable (${result.error ?? `HTTP ${result.status ?? "unknown"}`})`
|
|
1526
|
-
});
|
|
1527
|
-
}
|
|
1528
|
-
if (checkUrls && termsUrl && isValidHttpsUrl(termsUrl)) {
|
|
1529
|
-
const result = await checkUrlReachability(termsUrl);
|
|
1530
|
-
checks.push({
|
|
1531
|
-
name: "terms_url_reachable",
|
|
1532
|
-
ok: result.ok,
|
|
1533
|
-
detail: result.ok ? `Reachable (HTTP ${result.status ?? 200})` : `Unreachable (${result.error ?? `HTTP ${result.status ?? "unknown"}`})`
|
|
1534
|
-
});
|
|
1535
|
-
}
|
|
1861
|
+
}
|
|
1862
|
+
if (checkUrls && privacyUrl && isValidHttpsUrl(privacyUrl)) {
|
|
1863
|
+
const result = await checkUrlReachability(privacyUrl);
|
|
1536
1864
|
checks.push({
|
|
1537
|
-
name: "
|
|
1538
|
-
ok:
|
|
1539
|
-
detail:
|
|
1865
|
+
name: "privacy_policy_url_reachable",
|
|
1866
|
+
ok: result.ok,
|
|
1867
|
+
detail: result.ok ? `Reachable (HTTP ${result.status ?? 200})` : `Unreachable (${result.error ?? `HTTP ${result.status ?? "unknown"}`})`
|
|
1540
1868
|
});
|
|
1869
|
+
}
|
|
1870
|
+
if (checkUrls && termsUrl && isValidHttpsUrl(termsUrl)) {
|
|
1871
|
+
const result = await checkUrlReachability(termsUrl);
|
|
1541
1872
|
checks.push({
|
|
1542
|
-
name: "
|
|
1543
|
-
ok:
|
|
1544
|
-
detail:
|
|
1873
|
+
name: "terms_url_reachable",
|
|
1874
|
+
ok: result.ok,
|
|
1875
|
+
detail: result.ok ? `Reachable (HTTP ${result.status ?? 200})` : `Unreachable (${result.error ?? `HTTP ${result.status ?? "unknown"}`})`
|
|
1545
1876
|
});
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
);
|
|
1573
|
-
if (error || !data?.success) {
|
|
1574
|
-
const message = "Failed to load connected accounts: " + (error ?? "Unknown error");
|
|
1575
|
-
if (asJson) {
|
|
1576
|
-
emitSnResult({ ok: false, command: "preflight", error: message }, true);
|
|
1577
|
-
} else {
|
|
1578
|
-
console.error(message);
|
|
1579
|
-
}
|
|
1580
|
-
process.exit(1);
|
|
1581
|
-
}
|
|
1582
|
-
activeAccounts = data.accounts ?? [];
|
|
1877
|
+
}
|
|
1878
|
+
checks.push({
|
|
1879
|
+
name: "max_credits_per_run_configured",
|
|
1880
|
+
ok: Number.isFinite(creditCap) && creditCap > 0,
|
|
1881
|
+
detail: Number.isFinite(creditCap) && creditCap > 0 ? `${creditCap} credits cap` : "Set SOCIALNEURON_MAX_CREDITS_PER_RUN to a positive number"
|
|
1882
|
+
});
|
|
1883
|
+
checks.push({
|
|
1884
|
+
name: "max_assets_per_run_configured",
|
|
1885
|
+
ok: Number.isFinite(assetCap) && assetCap > 0,
|
|
1886
|
+
detail: Number.isFinite(assetCap) && assetCap > 0 ? `${assetCap} assets cap` : "Set SOCIALNEURON_MAX_ASSETS_PER_RUN to a positive number"
|
|
1887
|
+
});
|
|
1888
|
+
let activeAccounts = [];
|
|
1889
|
+
if (supabase) {
|
|
1890
|
+
const { data: accounts, error: accountsError } = await supabase.from("connected_accounts").select("platform, status, username, expires_at").eq("user_id", userId).eq("status", "active").order("platform");
|
|
1891
|
+
if (accountsError) {
|
|
1892
|
+
const formatted = classifySupabaseCliError("load connected accounts", accountsError);
|
|
1893
|
+
throw new Error(formatted.message);
|
|
1894
|
+
}
|
|
1895
|
+
activeAccounts = accounts ?? [];
|
|
1896
|
+
} else {
|
|
1897
|
+
const { data, error } = await callEdgeFunction(
|
|
1898
|
+
"mcp-data",
|
|
1899
|
+
{ action: "connected-accounts", userId }
|
|
1900
|
+
);
|
|
1901
|
+
if (error || !data?.success) {
|
|
1902
|
+
throw new Error("Failed to load connected accounts: " + (error ?? "Unknown error"));
|
|
1583
1903
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1904
|
+
activeAccounts = data.accounts ?? [];
|
|
1905
|
+
}
|
|
1906
|
+
const expiredAccounts = activeAccounts.filter((account) => {
|
|
1907
|
+
if (!account.expires_at) return false;
|
|
1908
|
+
const expiresAt = new Date(account.expires_at);
|
|
1909
|
+
return Number.isFinite(expiresAt.getTime()) && expiresAt.getTime() <= Date.now();
|
|
1910
|
+
});
|
|
1911
|
+
checks.push({
|
|
1912
|
+
name: "oauth_connections_present",
|
|
1913
|
+
ok: activeAccounts.length > 0,
|
|
1914
|
+
detail: activeAccounts.length > 0 ? `${activeAccounts.length} active account(s)` : "No active connected_accounts found"
|
|
1915
|
+
});
|
|
1916
|
+
checks.push({
|
|
1917
|
+
name: "oauth_tokens_not_expired",
|
|
1918
|
+
ok: expiredAccounts.length === 0,
|
|
1919
|
+
detail: expiredAccounts.length === 0 ? "No expired tokens detected" : `Expired: ${expiredAccounts.map((a) => a.platform).join(", ")}`
|
|
1920
|
+
});
|
|
1921
|
+
const ok = checks.every((check) => check.ok);
|
|
1922
|
+
const summary = {
|
|
1923
|
+
ok,
|
|
1924
|
+
command: "preflight",
|
|
1925
|
+
checkCount: checks.length,
|
|
1926
|
+
passed: checks.filter((check) => check.ok).length,
|
|
1927
|
+
failed: checks.filter((check) => !check.ok).length,
|
|
1928
|
+
connectedPlatforms: activeAccounts.map((a) => ({
|
|
1929
|
+
platform: a.platform,
|
|
1930
|
+
username: a.username,
|
|
1931
|
+
expiresAt: a.expires_at
|
|
1932
|
+
})),
|
|
1933
|
+
checks
|
|
1934
|
+
};
|
|
1935
|
+
if (asJson) {
|
|
1936
|
+
emitSnResult(summary, true);
|
|
1937
|
+
} else {
|
|
1938
|
+
console.error(`Preflight: ${ok ? "PASS" : "FAIL"}`);
|
|
1939
|
+
console.error(`Checks: ${summary.passed}/${summary.checkCount} passed`);
|
|
1940
|
+
for (const check of checks) {
|
|
1941
|
+
console.error(`${check.ok ? "[ok]" : "[x]"} ${check.name}: ${check.detail}`);
|
|
1942
|
+
}
|
|
1943
|
+
if (!ok) {
|
|
1944
|
+
console.error("");
|
|
1945
|
+
console.error("Blocking checks failed. Recommended: run in Draft-only mode until fixed.");
|
|
1625
1946
|
}
|
|
1626
|
-
process.exit(ok ? 0 : 1);
|
|
1627
1947
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1948
|
+
process.exit(ok ? 0 : 1);
|
|
1949
|
+
}
|
|
1950
|
+
var init_account = __esm({
|
|
1951
|
+
"src/cli/sn/account.ts"() {
|
|
1952
|
+
"use strict";
|
|
1953
|
+
init_edge_function();
|
|
1954
|
+
init_supabase();
|
|
1955
|
+
init_parse();
|
|
1956
|
+
}
|
|
1957
|
+
});
|
|
1958
|
+
|
|
1959
|
+
// src/cli/sn/analytics.ts
|
|
1960
|
+
var analytics_exports = {};
|
|
1961
|
+
__export(analytics_exports, {
|
|
1962
|
+
handleLoop: () => handleLoop,
|
|
1963
|
+
handlePosts: () => handlePosts,
|
|
1964
|
+
handleRefreshAnalytics: () => handleRefreshAnalytics
|
|
1965
|
+
});
|
|
1966
|
+
async function ensureAuth3() {
|
|
1967
|
+
await initializeAuth();
|
|
1968
|
+
return getDefaultUserId();
|
|
1969
|
+
}
|
|
1970
|
+
async function handlePosts(args, asJson) {
|
|
1971
|
+
const userId = await ensureAuth3();
|
|
1972
|
+
const supabase = tryGetSupabaseClient();
|
|
1973
|
+
const daysRaw = args.days;
|
|
1974
|
+
const days = typeof daysRaw === "string" ? Number(daysRaw) : 7;
|
|
1975
|
+
const lookbackDays = Number.isFinite(days) && days > 0 ? Math.min(days, 90) : 7;
|
|
1976
|
+
const since = /* @__PURE__ */ new Date();
|
|
1977
|
+
since.setDate(since.getDate() - lookbackDays);
|
|
1978
|
+
let posts = [];
|
|
1979
|
+
if (supabase) {
|
|
1980
|
+
let query = supabase.from("posts").select(
|
|
1981
|
+
"id, platform, status, title, external_post_id, scheduled_at, published_at, created_at"
|
|
1982
|
+
).eq("user_id", userId).gte("created_at", since.toISOString()).order("created_at", { ascending: false }).limit(50);
|
|
1983
|
+
if (typeof args.platform === "string") {
|
|
1984
|
+
query = query.eq("platform", args.platform);
|
|
1985
|
+
}
|
|
1986
|
+
if (typeof args.status === "string") {
|
|
1987
|
+
query = query.eq("status", args.status);
|
|
1988
|
+
}
|
|
1989
|
+
const { data, error } = await query;
|
|
1990
|
+
if (error) {
|
|
1991
|
+
const formatted = classifySupabaseCliError("fetch posts", error);
|
|
1992
|
+
throw new Error(formatted.message);
|
|
1993
|
+
}
|
|
1994
|
+
posts = data ?? [];
|
|
1995
|
+
} else {
|
|
1996
|
+
const { data, error } = await callEdgeFunction("mcp-data", {
|
|
1997
|
+
action: "recent-posts",
|
|
1998
|
+
userId,
|
|
1999
|
+
days: lookbackDays,
|
|
2000
|
+
limit: 50,
|
|
2001
|
+
platform: typeof args.platform === "string" ? args.platform : void 0,
|
|
2002
|
+
status: typeof args.status === "string" ? args.status : void 0
|
|
2003
|
+
});
|
|
2004
|
+
if (error || !data?.success) {
|
|
2005
|
+
throw new Error("Failed to fetch posts: " + (error ?? "Unknown error"));
|
|
1638
2006
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
).eq("user_id", userId).eq("id", jobId).maybeSingle();
|
|
1645
|
-
if (byIdError) {
|
|
1646
|
-
const formatted = classifySupabaseCliError("fetch job status", byIdError);
|
|
1647
|
-
if (asJson) {
|
|
1648
|
-
emitSnResult(
|
|
1649
|
-
{ ok: false, command: "status", error: formatted.message, hint: formatted.hint, jobId },
|
|
1650
|
-
true
|
|
1651
|
-
);
|
|
1652
|
-
} else {
|
|
1653
|
-
console.error(formatted.message);
|
|
1654
|
-
if (formatted.hint) console.error(`Hint: ${formatted.hint}`);
|
|
1655
|
-
}
|
|
1656
|
-
process.exit(1);
|
|
1657
|
-
}
|
|
1658
|
-
if (byId) {
|
|
1659
|
-
job = byId;
|
|
1660
|
-
} else {
|
|
1661
|
-
const { data: byExternal, error: byExternalError } = await supabase.from("async_jobs").select(
|
|
1662
|
-
"id, external_id, status, job_type, model, result_url, error_message, created_at, completed_at"
|
|
1663
|
-
).eq("user_id", userId).eq("external_id", jobId).maybeSingle();
|
|
1664
|
-
if (byExternalError) {
|
|
1665
|
-
const formatted = classifySupabaseCliError("fetch job status", byExternalError);
|
|
1666
|
-
if (asJson) {
|
|
1667
|
-
emitSnResult(
|
|
1668
|
-
{
|
|
1669
|
-
ok: false,
|
|
1670
|
-
command: "status",
|
|
1671
|
-
error: formatted.message,
|
|
1672
|
-
hint: formatted.hint,
|
|
1673
|
-
jobId
|
|
1674
|
-
},
|
|
1675
|
-
true
|
|
1676
|
-
);
|
|
1677
|
-
} else {
|
|
1678
|
-
console.error(formatted.message);
|
|
1679
|
-
if (formatted.hint) console.error(`Hint: ${formatted.hint}`);
|
|
1680
|
-
}
|
|
1681
|
-
process.exit(1);
|
|
1682
|
-
}
|
|
1683
|
-
job = byExternal;
|
|
1684
|
-
}
|
|
2007
|
+
posts = data.posts ?? [];
|
|
2008
|
+
}
|
|
2009
|
+
if (!posts || posts.length === 0) {
|
|
2010
|
+
if (asJson) {
|
|
2011
|
+
emitSnResult({ ok: true, command: "posts", posts: [] }, true);
|
|
1685
2012
|
} else {
|
|
1686
|
-
|
|
1687
|
-
action: "job-status",
|
|
1688
|
-
userId,
|
|
1689
|
-
jobId
|
|
1690
|
-
});
|
|
1691
|
-
if (error || !data?.success) {
|
|
1692
|
-
const message = `Failed to fetch job status: ${error ?? data?.error ?? "Unknown error"}`;
|
|
1693
|
-
if (asJson) {
|
|
1694
|
-
emitSnResult({ ok: false, command: "status", error: message, jobId }, true);
|
|
1695
|
-
} else {
|
|
1696
|
-
console.error(message);
|
|
1697
|
-
}
|
|
1698
|
-
process.exit(1);
|
|
1699
|
-
}
|
|
1700
|
-
job = data.job ?? null;
|
|
2013
|
+
console.error("No posts found.");
|
|
1701
2014
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
2015
|
+
process.exit(0);
|
|
2016
|
+
}
|
|
2017
|
+
if (asJson) {
|
|
2018
|
+
emitSnResult({ ok: true, command: "posts", posts }, true);
|
|
2019
|
+
} else {
|
|
2020
|
+
for (const post of posts) {
|
|
2021
|
+
console.error(
|
|
2022
|
+
`${post.created_at} | ${post.platform} | ${post.status} | ${post.title ?? "(untitled)"} | ${post.id}`
|
|
2023
|
+
);
|
|
1710
2024
|
}
|
|
2025
|
+
}
|
|
2026
|
+
process.exit(0);
|
|
2027
|
+
}
|
|
2028
|
+
async function handleRefreshAnalytics(args, asJson) {
|
|
2029
|
+
await ensureAuth3();
|
|
2030
|
+
const { data, error } = await callEdgeFunction(
|
|
2031
|
+
"fetch-analytics",
|
|
2032
|
+
{}
|
|
2033
|
+
);
|
|
2034
|
+
if (error || !data?.success) {
|
|
2035
|
+
throw new Error(`Analytics refresh failed: ${error ?? "Unknown error"}`);
|
|
2036
|
+
}
|
|
2037
|
+
if (asJson) {
|
|
2038
|
+
emitSnResult(
|
|
2039
|
+
{ ok: true, command: "refresh-analytics", postsProcessed: data.postsProcessed },
|
|
2040
|
+
true
|
|
2041
|
+
);
|
|
2042
|
+
} else {
|
|
2043
|
+
console.error(`Analytics refresh queued for ${data.postsProcessed} post(s).`);
|
|
2044
|
+
}
|
|
2045
|
+
process.exit(0);
|
|
2046
|
+
}
|
|
2047
|
+
async function handleLoop(args, asJson) {
|
|
2048
|
+
const userId = await ensureAuth3();
|
|
2049
|
+
try {
|
|
2050
|
+
const supabase = getSupabaseClient();
|
|
2051
|
+
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
2052
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
2053
|
+
const [brandResult, contentResult, insightsResult] = await Promise.all([
|
|
2054
|
+
supabase.from("brand_profiles").select("id").eq("user_id", userId).eq("is_active", true).limit(1).maybeSingle(),
|
|
2055
|
+
supabase.from("content_history").select("id, content_type, created_at").eq("user_id", userId).gte("created_at", thirtyDaysAgo.toISOString()).order("created_at", { ascending: false }).limit(10),
|
|
2056
|
+
supabase.from("performance_insights").select("id, insight_type, generated_at").gte("generated_at", thirtyDaysAgo.toISOString()).gt("expires_at", (/* @__PURE__ */ new Date()).toISOString()).limit(20)
|
|
2057
|
+
]);
|
|
2058
|
+
const hasProfile = !!brandResult.data;
|
|
2059
|
+
const recentCount = contentResult.data?.length ?? 0;
|
|
2060
|
+
const insightsCount = insightsResult.data?.length ?? 0;
|
|
2061
|
+
let nextAction = "Generate content to start building your feedback loop";
|
|
2062
|
+
if (!hasProfile) nextAction = "Set up your brand profile first";
|
|
2063
|
+
else if (recentCount === 0)
|
|
2064
|
+
nextAction = "Generate and publish content to collect performance data";
|
|
2065
|
+
else if (insightsCount === 0)
|
|
2066
|
+
nextAction = "Publish more content \u2014 insights need 5+ data points";
|
|
2067
|
+
else nextAction = "Loop is active \u2014 use insights to improve next content batch";
|
|
1711
2068
|
if (asJson) {
|
|
1712
|
-
emitSnResult(
|
|
2069
|
+
emitSnResult(
|
|
2070
|
+
{
|
|
2071
|
+
ok: true,
|
|
2072
|
+
command: "loop",
|
|
2073
|
+
brandStatus: { hasProfile },
|
|
2074
|
+
recentContent: contentResult.data ?? [],
|
|
2075
|
+
currentInsights: insightsResult.data ?? [],
|
|
2076
|
+
recommendedNextAction: nextAction
|
|
2077
|
+
},
|
|
2078
|
+
true
|
|
2079
|
+
);
|
|
1713
2080
|
} else {
|
|
1714
|
-
console.error(
|
|
1715
|
-
console.error(
|
|
1716
|
-
console.error(`
|
|
1717
|
-
console.error(`
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
if (job.completed_at) console.error(`Completed: ${job.completed_at}`);
|
|
2081
|
+
console.error("Feedback Loop Summary");
|
|
2082
|
+
console.error("=====================");
|
|
2083
|
+
console.error(`Brand Profile: ${hasProfile ? "Ready" : "Missing"}`);
|
|
2084
|
+
console.error(`Recent Content: ${recentCount} items (last 30 days)`);
|
|
2085
|
+
console.error(`Current Insights: ${insightsCount} active`);
|
|
2086
|
+
console.error(`
|
|
2087
|
+
Next Action: ${nextAction}`);
|
|
1722
2088
|
}
|
|
1723
|
-
|
|
2089
|
+
} catch (err) {
|
|
2090
|
+
throw new Error(`Loop summary failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1724
2091
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
2092
|
+
process.exit(0);
|
|
2093
|
+
}
|
|
2094
|
+
var init_analytics = __esm({
|
|
2095
|
+
"src/cli/sn/analytics.ts"() {
|
|
2096
|
+
"use strict";
|
|
2097
|
+
init_edge_function();
|
|
2098
|
+
init_supabase();
|
|
2099
|
+
init_parse();
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
2102
|
+
|
|
2103
|
+
// src/cli/sn/system.ts
|
|
2104
|
+
var system_exports = {};
|
|
2105
|
+
__export(system_exports, {
|
|
2106
|
+
handleAutopilot: () => handleAutopilot,
|
|
2107
|
+
handleCredits: () => handleCredits,
|
|
2108
|
+
handleStatus: () => handleStatus,
|
|
2109
|
+
handleUsage: () => handleUsage
|
|
2110
|
+
});
|
|
2111
|
+
async function ensureAuth4() {
|
|
2112
|
+
await initializeAuth();
|
|
2113
|
+
return getDefaultUserId();
|
|
2114
|
+
}
|
|
2115
|
+
async function handleStatus(args, asJson) {
|
|
2116
|
+
const jobId = args["job-id"];
|
|
2117
|
+
if (typeof jobId !== "string") {
|
|
2118
|
+
throw new Error("Missing required flag: --job-id");
|
|
2119
|
+
}
|
|
2120
|
+
const userId = await ensureAuth4();
|
|
2121
|
+
const supabase = tryGetSupabaseClient();
|
|
2122
|
+
let job = null;
|
|
2123
|
+
if (supabase) {
|
|
2124
|
+
const { data: byId, error: byIdError } = await supabase.from("async_jobs").select(
|
|
2125
|
+
"id, external_id, status, job_type, model, result_url, error_message, created_at, completed_at"
|
|
2126
|
+
).eq("user_id", userId).eq("id", jobId).maybeSingle();
|
|
2127
|
+
if (byIdError) {
|
|
2128
|
+
const formatted = classifySupabaseCliError("fetch job status", byIdError);
|
|
2129
|
+
throw new Error(formatted.message);
|
|
2130
|
+
}
|
|
2131
|
+
if (byId) {
|
|
2132
|
+
job = byId;
|
|
1758
2133
|
} else {
|
|
1759
|
-
const { data, error } = await
|
|
1760
|
-
"
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
limit: 50,
|
|
1766
|
-
platform: typeof args.platform === "string" ? args.platform : void 0,
|
|
1767
|
-
status: typeof args.status === "string" ? args.status : void 0
|
|
1768
|
-
}
|
|
1769
|
-
);
|
|
1770
|
-
if (error || !data?.success) {
|
|
1771
|
-
const message = "Failed to fetch posts: " + (error ?? "Unknown error");
|
|
1772
|
-
if (asJson) emitSnResult({ ok: false, command: "posts", error: message }, true);
|
|
1773
|
-
else console.error(message);
|
|
1774
|
-
process.exit(1);
|
|
2134
|
+
const { data: byExternal, error: byExternalError } = await supabase.from("async_jobs").select(
|
|
2135
|
+
"id, external_id, status, job_type, model, result_url, error_message, created_at, completed_at"
|
|
2136
|
+
).eq("user_id", userId).eq("external_id", jobId).maybeSingle();
|
|
2137
|
+
if (byExternalError) {
|
|
2138
|
+
const formatted = classifySupabaseCliError("fetch job status", byExternalError);
|
|
2139
|
+
throw new Error(formatted.message);
|
|
1775
2140
|
}
|
|
1776
|
-
|
|
2141
|
+
job = byExternal;
|
|
1777
2142
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
2143
|
+
} else {
|
|
2144
|
+
const { data, error } = await callEdgeFunction("mcp-data", {
|
|
2145
|
+
action: "job-status",
|
|
2146
|
+
userId,
|
|
2147
|
+
jobId
|
|
2148
|
+
});
|
|
2149
|
+
if (error || !data?.success) {
|
|
2150
|
+
throw new Error(`Failed to fetch job status: ${error ?? data?.error ?? "Unknown error"}`);
|
|
1785
2151
|
}
|
|
2152
|
+
job = data.job ?? null;
|
|
2153
|
+
}
|
|
2154
|
+
if (!job) {
|
|
2155
|
+
throw new Error(`No job found with ID "${jobId}".`);
|
|
2156
|
+
}
|
|
2157
|
+
if (asJson) {
|
|
2158
|
+
emitSnResult({ ok: true, command: "status", job }, true);
|
|
2159
|
+
} else {
|
|
2160
|
+
console.error(`Job: ${job.id}`);
|
|
2161
|
+
console.error(`Status: ${job.status}`);
|
|
2162
|
+
console.error(`Type: ${job.job_type}`);
|
|
2163
|
+
console.error(`Model: ${job.model}`);
|
|
2164
|
+
if (job.result_url) console.error(`Result URL: ${job.result_url}`);
|
|
2165
|
+
if (job.error_message) console.error(`Error: ${job.error_message}`);
|
|
2166
|
+
console.error(`Created: ${job.created_at}`);
|
|
2167
|
+
if (job.completed_at) console.error(`Completed: ${job.completed_at}`);
|
|
2168
|
+
}
|
|
2169
|
+
process.exit(0);
|
|
2170
|
+
}
|
|
2171
|
+
async function handleAutopilot(args, asJson) {
|
|
2172
|
+
const userId = await ensureAuth4();
|
|
2173
|
+
try {
|
|
2174
|
+
const supabase = getSupabaseClient();
|
|
2175
|
+
const [configsResult, approvalsResult] = await Promise.all([
|
|
2176
|
+
supabase.from("autopilot_configs").select("id, platform, is_enabled, schedule_config, updated_at").eq("user_id", userId).eq("is_enabled", true),
|
|
2177
|
+
supabase.from("approval_queue").select("id").eq("user_id", userId).eq("status", "pending")
|
|
2178
|
+
]);
|
|
2179
|
+
const activeConfigs = configsResult.data?.length ?? 0;
|
|
2180
|
+
const pendingApprovals = approvalsResult.data?.length ?? 0;
|
|
1786
2181
|
if (asJson) {
|
|
1787
|
-
emitSnResult(
|
|
2182
|
+
emitSnResult(
|
|
2183
|
+
{
|
|
2184
|
+
ok: true,
|
|
2185
|
+
command: "autopilot",
|
|
2186
|
+
activeConfigs,
|
|
2187
|
+
pendingApprovals,
|
|
2188
|
+
configs: configsResult.data ?? []
|
|
2189
|
+
},
|
|
2190
|
+
true
|
|
2191
|
+
);
|
|
1788
2192
|
} else {
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2193
|
+
console.error("Autopilot Status");
|
|
2194
|
+
console.error("================");
|
|
2195
|
+
console.error(`Active Configs: ${activeConfigs}`);
|
|
2196
|
+
console.error(`Pending Approvals: ${pendingApprovals}`);
|
|
2197
|
+
if (configsResult.data?.length) {
|
|
2198
|
+
console.error("\nConfigs:");
|
|
2199
|
+
for (const cfg of configsResult.data) {
|
|
2200
|
+
console.error(`- ${cfg.platform}: enabled (updated ${cfg.updated_at})`);
|
|
2201
|
+
}
|
|
1793
2202
|
}
|
|
1794
2203
|
}
|
|
1795
|
-
|
|
2204
|
+
} catch (err) {
|
|
2205
|
+
throw new Error(`Autopilot status failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1796
2206
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
2207
|
+
process.exit(0);
|
|
2208
|
+
}
|
|
2209
|
+
async function handleUsage(args, asJson) {
|
|
2210
|
+
const userId = await ensureAuth4();
|
|
2211
|
+
try {
|
|
2212
|
+
const supabase = getSupabaseClient();
|
|
2213
|
+
const startOfMonth = /* @__PURE__ */ new Date();
|
|
2214
|
+
startOfMonth.setDate(1);
|
|
2215
|
+
startOfMonth.setHours(0, 0, 0, 0);
|
|
2216
|
+
const { data: rows, error: rpcError } = await supabase.rpc("get_mcp_monthly_usage", {
|
|
2217
|
+
p_user_id: userId,
|
|
2218
|
+
p_since: startOfMonth.toISOString()
|
|
2219
|
+
});
|
|
2220
|
+
if (rpcError) {
|
|
2221
|
+
const { data: logs } = await supabase.from("activity_logs").select("action, metadata").eq("user_id", userId).gte("created_at", startOfMonth.toISOString()).like("action", "mcp:%");
|
|
2222
|
+
const totalCalls = logs?.length ?? 0;
|
|
1804
2223
|
if (asJson) {
|
|
1805
|
-
emitSnResult({ ok:
|
|
2224
|
+
emitSnResult({ ok: true, command: "usage", totalCalls, totalCredits: 0, tools: [] }, true);
|
|
1806
2225
|
} else {
|
|
1807
|
-
console.error(
|
|
2226
|
+
console.error("MCP Usage This Month");
|
|
2227
|
+
console.error("====================");
|
|
2228
|
+
console.error(`Total Calls: ${totalCalls}`);
|
|
2229
|
+
console.error("(Detailed breakdown requires get_mcp_monthly_usage RPC function)");
|
|
2230
|
+
}
|
|
2231
|
+
} else {
|
|
2232
|
+
const tools = rows ?? [];
|
|
2233
|
+
const totalCalls = tools.reduce((sum, t) => sum + (t.call_count ?? 0), 0);
|
|
2234
|
+
const totalCredits = tools.reduce((sum, t) => sum + (t.credits_total ?? 0), 0);
|
|
2235
|
+
if (asJson) {
|
|
2236
|
+
emitSnResult({ ok: true, command: "usage", totalCalls, totalCredits, tools }, true);
|
|
2237
|
+
} else {
|
|
2238
|
+
console.error("MCP Usage This Month");
|
|
2239
|
+
console.error("====================");
|
|
2240
|
+
console.error(`Total Calls: ${totalCalls}`);
|
|
2241
|
+
console.error(`Total Credits: ${totalCredits}`);
|
|
2242
|
+
if (tools.length) {
|
|
2243
|
+
console.error("\nPer-Tool Breakdown:");
|
|
2244
|
+
for (const tool of tools) {
|
|
2245
|
+
console.error(
|
|
2246
|
+
`- ${tool.tool_name}: ${tool.call_count} calls, ${tool.credits_total} credits`
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
1808
2250
|
}
|
|
1809
|
-
process.exit(1);
|
|
1810
2251
|
}
|
|
2252
|
+
} catch (err) {
|
|
2253
|
+
throw new Error(`Usage fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2254
|
+
}
|
|
2255
|
+
process.exit(0);
|
|
2256
|
+
}
|
|
2257
|
+
async function handleCredits(args, asJson) {
|
|
2258
|
+
const userId = await ensureAuth4();
|
|
2259
|
+
try {
|
|
2260
|
+
const supabase = getSupabaseClient();
|
|
2261
|
+
const [profileResult, subResult] = await Promise.all([
|
|
2262
|
+
supabase.from("user_profiles").select("credits, monthly_credits_used").eq("id", userId).maybeSingle(),
|
|
2263
|
+
supabase.from("subscriptions").select("tier, status, monthly_credits").eq("user_id", userId).eq("status", "active").order("created_at", { ascending: false }).limit(1).maybeSingle()
|
|
2264
|
+
]);
|
|
2265
|
+
if (profileResult.error) throw profileResult.error;
|
|
2266
|
+
const balance = Number(profileResult.data?.credits || 0);
|
|
2267
|
+
const monthlyUsed = Number(profileResult.data?.monthly_credits_used || 0);
|
|
2268
|
+
const monthlyLimit = Number(subResult.data?.monthly_credits || 0);
|
|
2269
|
+
const plan = subResult.data?.tier || "free";
|
|
1811
2270
|
if (asJson) {
|
|
1812
2271
|
emitSnResult(
|
|
1813
|
-
{ ok: true, command: "
|
|
2272
|
+
{ ok: true, command: "credits", balance, monthlyUsed, monthlyLimit, plan },
|
|
1814
2273
|
true
|
|
1815
2274
|
);
|
|
1816
2275
|
} else {
|
|
1817
|
-
console.error(
|
|
2276
|
+
console.error("Credit Balance");
|
|
2277
|
+
console.error("==============");
|
|
2278
|
+
console.error(`Plan: ${plan.toUpperCase()}`);
|
|
2279
|
+
console.error(`Balance: ${balance} credits`);
|
|
2280
|
+
if (monthlyLimit) {
|
|
2281
|
+
console.error(`Monthly Usage: ${monthlyUsed} / ${monthlyLimit}`);
|
|
2282
|
+
}
|
|
1818
2283
|
}
|
|
2284
|
+
} catch (err) {
|
|
2285
|
+
throw new Error(`Credit balance failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2286
|
+
}
|
|
2287
|
+
process.exit(0);
|
|
2288
|
+
}
|
|
2289
|
+
var init_system = __esm({
|
|
2290
|
+
"src/cli/sn/system.ts"() {
|
|
2291
|
+
"use strict";
|
|
2292
|
+
init_edge_function();
|
|
2293
|
+
init_supabase();
|
|
2294
|
+
init_parse();
|
|
2295
|
+
}
|
|
2296
|
+
});
|
|
2297
|
+
|
|
2298
|
+
// src/cli/sn/discovery.ts
|
|
2299
|
+
var discovery_exports = {};
|
|
2300
|
+
__export(discovery_exports, {
|
|
2301
|
+
handleInfo: () => handleInfo,
|
|
2302
|
+
handleTools: () => handleTools
|
|
2303
|
+
});
|
|
2304
|
+
async function handleTools(args, asJson) {
|
|
2305
|
+
let tools = TOOL_CATALOG;
|
|
2306
|
+
const scope = args.scope;
|
|
2307
|
+
if (typeof scope === "string") {
|
|
2308
|
+
tools = getToolsByScope(scope);
|
|
2309
|
+
}
|
|
2310
|
+
const module = args.module;
|
|
2311
|
+
if (typeof module === "string") {
|
|
2312
|
+
tools = getToolsByModule(module);
|
|
2313
|
+
}
|
|
2314
|
+
if (asJson) {
|
|
2315
|
+
emitSnResult({ ok: true, command: "tools", toolCount: tools.length, tools }, true);
|
|
1819
2316
|
process.exit(0);
|
|
2317
|
+
return;
|
|
1820
2318
|
}
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
2319
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2320
|
+
for (const tool of tools) {
|
|
2321
|
+
const group = grouped.get(tool.module) ?? [];
|
|
2322
|
+
group.push(tool);
|
|
2323
|
+
grouped.set(tool.module, group);
|
|
2324
|
+
}
|
|
2325
|
+
if (grouped.size === 0) {
|
|
2326
|
+
console.error("No tools found matching the given filters.");
|
|
2327
|
+
process.exit(0);
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
for (const [moduleName, moduleTools] of grouped) {
|
|
2331
|
+
console.error(`
|
|
2332
|
+
Module: ${moduleName} (${moduleTools.length} tools)`);
|
|
2333
|
+
const maxNameLen = Math.max(...moduleTools.map((t) => t.name.length));
|
|
2334
|
+
for (const tool of moduleTools) {
|
|
2335
|
+
const padded = tool.name.padEnd(maxNameLen + 2);
|
|
2336
|
+
console.error(` ${padded}${tool.description}`);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
console.error("");
|
|
2340
|
+
process.exit(0);
|
|
2341
|
+
}
|
|
2342
|
+
async function handleInfo(args, asJson) {
|
|
2343
|
+
const info = {
|
|
2344
|
+
version: MCP_VERSION,
|
|
2345
|
+
toolCount: TOOL_CATALOG.length,
|
|
2346
|
+
modules: getModules()
|
|
2347
|
+
};
|
|
2348
|
+
try {
|
|
2349
|
+
const { loadApiKey: loadApiKey2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
|
|
2350
|
+
const { validateApiKey: validateApiKey2 } = await Promise.resolve().then(() => (init_api_keys(), api_keys_exports));
|
|
2351
|
+
const apiKey = await loadApiKey2();
|
|
2352
|
+
if (apiKey) {
|
|
2353
|
+
const result = await validateApiKey2(apiKey);
|
|
2354
|
+
if (result.valid) {
|
|
2355
|
+
info.auth = {
|
|
2356
|
+
email: result.email || null,
|
|
2357
|
+
scopes: result.scopes || [],
|
|
2358
|
+
expiresAt: result.expiresAt || null
|
|
2359
|
+
};
|
|
1852
2360
|
}
|
|
1853
|
-
} catch (err) {
|
|
1854
|
-
const message = `Autopilot status failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
1855
|
-
if (asJson) emitSnResult({ ok: false, command: "autopilot", error: message }, true);
|
|
1856
|
-
else console.error(message);
|
|
1857
|
-
process.exit(1);
|
|
1858
2361
|
}
|
|
2362
|
+
} catch {
|
|
2363
|
+
info.auth = null;
|
|
2364
|
+
}
|
|
2365
|
+
if (info.auth) {
|
|
2366
|
+
try {
|
|
2367
|
+
const { callEdgeFunction: callEdgeFunction2 } = await Promise.resolve().then(() => (init_edge_function(), edge_function_exports));
|
|
2368
|
+
const { data } = await callEdgeFunction2("mcp-data", {
|
|
2369
|
+
action: "credit-balance"
|
|
2370
|
+
});
|
|
2371
|
+
info.creditBalance = data?.balance ?? null;
|
|
2372
|
+
} catch {
|
|
2373
|
+
info.creditBalance = null;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
if (asJson) {
|
|
2377
|
+
emitSnResult({ ok: true, command: "info", data: info }, true);
|
|
1859
2378
|
process.exit(0);
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
console.error(`Version: ${info.version}`);
|
|
2382
|
+
console.error(`Tools: ${info.toolCount}`);
|
|
2383
|
+
console.error(`Modules: ${info.modules.join(", ")}`);
|
|
2384
|
+
if (info.auth === null) {
|
|
2385
|
+
console.error("Auth: not configured");
|
|
2386
|
+
} else if (info.auth) {
|
|
2387
|
+
const auth = info.auth;
|
|
2388
|
+
console.error(`Auth: ${auth.email ?? "authenticated"}`);
|
|
2389
|
+
console.error(`Scopes: ${auth.scopes.length > 0 ? auth.scopes.join(", ") : "none"}`);
|
|
2390
|
+
if (auth.expiresAt) {
|
|
2391
|
+
console.error(`Expires: ${auth.expiresAt}`);
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
if (info.creditBalance !== void 0) {
|
|
2395
|
+
console.error(`Credits: ${info.creditBalance !== null ? info.creditBalance : "unavailable"}`);
|
|
2396
|
+
}
|
|
2397
|
+
console.error("");
|
|
2398
|
+
process.exit(0);
|
|
2399
|
+
}
|
|
2400
|
+
var init_discovery = __esm({
|
|
2401
|
+
"src/cli/sn/discovery.ts"() {
|
|
2402
|
+
"use strict";
|
|
2403
|
+
init_tool_catalog();
|
|
2404
|
+
init_parse();
|
|
2405
|
+
init_version();
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
|
|
2409
|
+
// src/cli/sn/planning.ts
|
|
2410
|
+
var planning_exports = {};
|
|
2411
|
+
__export(planning_exports, {
|
|
2412
|
+
handlePlan: () => handlePlan
|
|
2413
|
+
});
|
|
2414
|
+
async function ensureAuth5() {
|
|
2415
|
+
await initializeAuth();
|
|
2416
|
+
return getDefaultUserId();
|
|
2417
|
+
}
|
|
2418
|
+
async function handlePlanList(args, asJson) {
|
|
2419
|
+
const status = typeof args.status === "string" ? args.status : void 0;
|
|
2420
|
+
const userId = await ensureAuth5();
|
|
2421
|
+
const body = { action: "list-content-plans", userId };
|
|
2422
|
+
if (status) {
|
|
2423
|
+
body.status = status;
|
|
2424
|
+
}
|
|
2425
|
+
const { data, error } = await callEdgeFunction("mcp-data", body);
|
|
2426
|
+
if (error || !data) {
|
|
2427
|
+
throw new Error(`Failed to list plans: ${error ?? "Unknown error"}`);
|
|
2428
|
+
}
|
|
2429
|
+
const plans = data.plans ?? [];
|
|
2430
|
+
if (asJson) {
|
|
2431
|
+
emitSnResult({ ok: true, command: "plan list", plans }, true);
|
|
2432
|
+
} else {
|
|
2433
|
+
if (plans.length === 0) {
|
|
2434
|
+
console.error("No content plans found.");
|
|
2435
|
+
} else {
|
|
2436
|
+
console.error("plan-id | status | created_at | title");
|
|
2437
|
+
console.error("--------|--------|------------|------");
|
|
2438
|
+
for (const p of plans) {
|
|
2439
|
+
const title = p.title ?? p.summary ?? "(untitled)";
|
|
2440
|
+
console.error(`${p.id} | ${p.status} | ${p.created_at} | ${title}`);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
async function handlePlanView(args, asJson) {
|
|
2446
|
+
const planId = args["plan-id"];
|
|
2447
|
+
if (typeof planId !== "string") {
|
|
2448
|
+
throw new Error("Missing required flag: --plan-id");
|
|
2449
|
+
}
|
|
2450
|
+
const userId = await ensureAuth5();
|
|
2451
|
+
const { data, error } = await callEdgeFunction("mcp-data", {
|
|
2452
|
+
action: "get-content-plan",
|
|
2453
|
+
userId,
|
|
2454
|
+
planId
|
|
2455
|
+
});
|
|
2456
|
+
if (error || !data) {
|
|
2457
|
+
throw new Error(`Failed to view plan: ${error ?? "Unknown error"}`);
|
|
2458
|
+
}
|
|
2459
|
+
const plan = data.plan;
|
|
2460
|
+
if (asJson) {
|
|
2461
|
+
emitSnResult({ ok: true, command: "plan view", plan }, true);
|
|
2462
|
+
} else {
|
|
2463
|
+
const title = plan.title ?? plan.summary ?? "(untitled)";
|
|
2464
|
+
console.error(`Plan: ${plan.id}`);
|
|
2465
|
+
console.error(`Title: ${title}`);
|
|
2466
|
+
console.error(`Status: ${plan.status}`);
|
|
2467
|
+
console.error(`Created: ${plan.created_at}`);
|
|
2468
|
+
if (plan.plan_payload) {
|
|
2469
|
+
console.error(`Payload: ${JSON.stringify(plan.plan_payload, null, 2)}`);
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
async function handlePlanApprove(args, asJson) {
|
|
2474
|
+
const planId = args["plan-id"];
|
|
2475
|
+
if (typeof planId !== "string") {
|
|
2476
|
+
throw new Error("Missing required flag: --plan-id");
|
|
2477
|
+
}
|
|
2478
|
+
const userId = await ensureAuth5();
|
|
2479
|
+
const { data, error } = await callEdgeFunction("mcp-data", {
|
|
2480
|
+
action: "respond-plan-approval",
|
|
2481
|
+
userId,
|
|
2482
|
+
planId,
|
|
2483
|
+
response: "approved"
|
|
2484
|
+
});
|
|
2485
|
+
if (error || !data) {
|
|
2486
|
+
throw new Error(`Failed to approve plan: ${error ?? "Unknown error"}`);
|
|
2487
|
+
}
|
|
2488
|
+
if (asJson) {
|
|
2489
|
+
emitSnResult(
|
|
2490
|
+
{
|
|
2491
|
+
ok: data.success,
|
|
2492
|
+
command: "plan approve",
|
|
2493
|
+
planId,
|
|
2494
|
+
message: data.message ?? "Plan approved"
|
|
2495
|
+
},
|
|
2496
|
+
true
|
|
2497
|
+
);
|
|
2498
|
+
} else {
|
|
2499
|
+
console.error(`Plan ${planId}: ${data.success ? "Approved" : "Failed"}`);
|
|
2500
|
+
if (data.message) {
|
|
2501
|
+
console.error(data.message);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
async function handlePlan(args, asJson) {
|
|
2506
|
+
const subcommand = args._[0];
|
|
2507
|
+
if (!subcommand || args.help === true) {
|
|
2508
|
+
console.error(PLAN_USAGE);
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
switch (subcommand) {
|
|
2512
|
+
case "list":
|
|
2513
|
+
return handlePlanList({ ...args, _: args._.slice(1) }, asJson);
|
|
2514
|
+
case "view":
|
|
2515
|
+
return handlePlanView({ ...args, _: args._.slice(1) }, asJson);
|
|
2516
|
+
case "approve":
|
|
2517
|
+
return handlePlanApprove({ ...args, _: args._.slice(1) }, asJson);
|
|
2518
|
+
default:
|
|
2519
|
+
throw new Error(`Unknown plan subcommand: '${subcommand}'. Run 'sn plan --help' for usage.`);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
var PLAN_USAGE;
|
|
2523
|
+
var init_planning = __esm({
|
|
2524
|
+
"src/cli/sn/planning.ts"() {
|
|
2525
|
+
"use strict";
|
|
2526
|
+
init_edge_function();
|
|
2527
|
+
init_supabase();
|
|
2528
|
+
init_parse();
|
|
2529
|
+
PLAN_USAGE = `Usage: sn plan <subcommand> [flags]
|
|
2530
|
+
|
|
2531
|
+
Subcommands:
|
|
2532
|
+
list List content plans
|
|
2533
|
+
view View a single content plan
|
|
2534
|
+
approve Approve a content plan
|
|
2535
|
+
|
|
2536
|
+
Flags:
|
|
2537
|
+
list:
|
|
2538
|
+
--status <draft|submitted|approved> Filter by status (optional)
|
|
2539
|
+
|
|
2540
|
+
view:
|
|
2541
|
+
--plan-id <id> Plan ID (required)
|
|
2542
|
+
|
|
2543
|
+
approve:
|
|
2544
|
+
--plan-id <id> Plan ID (required)
|
|
2545
|
+
`;
|
|
2546
|
+
}
|
|
2547
|
+
});
|
|
2548
|
+
|
|
2549
|
+
// src/cli/sn/presets.ts
|
|
2550
|
+
var presets_exports = {};
|
|
2551
|
+
__export(presets_exports, {
|
|
2552
|
+
BUILTIN_PRESETS: () => BUILTIN_PRESETS,
|
|
2553
|
+
handlePreset: () => handlePreset,
|
|
2554
|
+
resolvePreset: () => resolvePreset
|
|
2555
|
+
});
|
|
2556
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "node:fs";
|
|
2557
|
+
import { join as join2 } from "node:path";
|
|
2558
|
+
import { homedir as homedir2 } from "node:os";
|
|
2559
|
+
function loadUserPresets() {
|
|
2560
|
+
if (!existsSync2(PRESETS_FILE)) return [];
|
|
2561
|
+
try {
|
|
2562
|
+
const raw = readFileSync2(PRESETS_FILE, "utf-8");
|
|
2563
|
+
const parsed = JSON.parse(raw);
|
|
2564
|
+
if (!Array.isArray(parsed)) return [];
|
|
2565
|
+
return parsed;
|
|
2566
|
+
} catch {
|
|
2567
|
+
return [];
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
function saveUserPresets(presets) {
|
|
2571
|
+
if (!existsSync2(PRESETS_DIR)) {
|
|
2572
|
+
mkdirSync2(PRESETS_DIR, { recursive: true });
|
|
2573
|
+
}
|
|
2574
|
+
writeFileSync2(PRESETS_FILE, JSON.stringify(presets, null, 2) + "\n", "utf-8");
|
|
2575
|
+
}
|
|
2576
|
+
function resolvePreset(name) {
|
|
2577
|
+
const lower = name.toLowerCase();
|
|
2578
|
+
const builtin = BUILTIN_PRESETS.find((p) => p.name === lower);
|
|
2579
|
+
if (builtin) return builtin;
|
|
2580
|
+
const userPresets = loadUserPresets();
|
|
2581
|
+
return userPresets.find((p) => p.name === lower) ?? null;
|
|
2582
|
+
}
|
|
2583
|
+
function handlePresetList(asJson) {
|
|
2584
|
+
const all = [...BUILTIN_PRESETS, ...loadUserPresets()];
|
|
2585
|
+
if (asJson) {
|
|
2586
|
+
emitSnResult({ ok: true, command: "preset", presets: all, count: all.length }, asJson);
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
const header = "NAME PLATFORM MAX-LEN RATIO SOURCE";
|
|
2590
|
+
const lines = all.map((p) => {
|
|
2591
|
+
const name = p.name.padEnd(20);
|
|
2592
|
+
const plat = p.platform.padEnd(12);
|
|
2593
|
+
const len = String(p.maxLength).padEnd(8);
|
|
2594
|
+
const ratio = (p.aspectRatio ?? "-").padEnd(8);
|
|
2595
|
+
const src = p.builtin ? "builtin" : "user";
|
|
2596
|
+
return `${name} ${plat} ${len} ${ratio} ${src}`;
|
|
2597
|
+
});
|
|
2598
|
+
process.stdout.write(header + "\n" + lines.join("\n") + "\n");
|
|
2599
|
+
}
|
|
2600
|
+
function handlePresetShow(args, asJson) {
|
|
2601
|
+
const name = args["name"];
|
|
2602
|
+
if (!name || typeof name !== "string") {
|
|
2603
|
+
throw new Error('--name is required for "preset show"');
|
|
2604
|
+
}
|
|
2605
|
+
const preset = resolvePreset(name);
|
|
2606
|
+
if (!preset) {
|
|
2607
|
+
throw new Error(`Preset "${name}" not found`);
|
|
2608
|
+
}
|
|
2609
|
+
if (asJson) {
|
|
2610
|
+
emitSnResult({ ok: true, command: "preset", preset }, asJson);
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
process.stdout.write(
|
|
2614
|
+
`name: ${preset.name}
|
|
2615
|
+
platform: ${preset.platform}
|
|
2616
|
+
maxLength: ${preset.maxLength}
|
|
2617
|
+
aspectRatio: ${preset.aspectRatio ?? "-"}
|
|
2618
|
+
source: ${preset.builtin ? "builtin" : "user"}
|
|
2619
|
+
`
|
|
2620
|
+
);
|
|
2621
|
+
}
|
|
2622
|
+
function handlePresetSave(args, asJson) {
|
|
2623
|
+
const name = args["name"];
|
|
2624
|
+
if (!name || typeof name !== "string") {
|
|
2625
|
+
throw new Error('--name is required for "preset save"');
|
|
2626
|
+
}
|
|
2627
|
+
const platform3 = args["platform"];
|
|
2628
|
+
if (!platform3 || typeof platform3 !== "string") {
|
|
2629
|
+
throw new Error('--platform is required for "preset save"');
|
|
2630
|
+
}
|
|
2631
|
+
const maxLengthRaw = args["max-length"];
|
|
2632
|
+
if (!maxLengthRaw) {
|
|
2633
|
+
throw new Error('--max-length is required for "preset save"');
|
|
2634
|
+
}
|
|
2635
|
+
const maxLength = Number(maxLengthRaw);
|
|
2636
|
+
if (!Number.isFinite(maxLength) || maxLength <= 0) {
|
|
2637
|
+
throw new Error("--max-length must be a positive number");
|
|
2638
|
+
}
|
|
2639
|
+
const lowerName = name.toLowerCase();
|
|
2640
|
+
if (BUILTIN_PRESETS.some((p) => p.name === lowerName)) {
|
|
2641
|
+
throw new Error(`Cannot overwrite builtin preset "${lowerName}"`);
|
|
2642
|
+
}
|
|
2643
|
+
const aspectRatio = typeof args["aspect-ratio"] === "string" ? args["aspect-ratio"] : void 0;
|
|
2644
|
+
const preset = {
|
|
2645
|
+
name: lowerName,
|
|
2646
|
+
platform: platform3,
|
|
2647
|
+
maxLength,
|
|
2648
|
+
aspectRatio,
|
|
2649
|
+
builtin: false
|
|
2650
|
+
};
|
|
2651
|
+
const userPresets = loadUserPresets();
|
|
2652
|
+
const idx = userPresets.findIndex((p) => p.name === lowerName);
|
|
2653
|
+
if (idx >= 0) {
|
|
2654
|
+
userPresets[idx] = preset;
|
|
2655
|
+
} else {
|
|
2656
|
+
userPresets.push(preset);
|
|
2657
|
+
}
|
|
2658
|
+
saveUserPresets(userPresets);
|
|
2659
|
+
if (asJson) {
|
|
2660
|
+
emitSnResult({ ok: true, command: "preset", saved: preset }, asJson);
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
process.stdout.write(`Preset "${lowerName}" saved.
|
|
2664
|
+
`);
|
|
2665
|
+
}
|
|
2666
|
+
function handlePresetDelete(args, asJson) {
|
|
2667
|
+
const name = args["name"];
|
|
2668
|
+
if (!name || typeof name !== "string") {
|
|
2669
|
+
throw new Error('--name is required for "preset delete"');
|
|
2670
|
+
}
|
|
2671
|
+
const lowerName = name.toLowerCase();
|
|
2672
|
+
if (BUILTIN_PRESETS.some((p) => p.name === lowerName)) {
|
|
2673
|
+
throw new Error(`Cannot delete builtin preset "${lowerName}"`);
|
|
2674
|
+
}
|
|
2675
|
+
const userPresets = loadUserPresets();
|
|
2676
|
+
const idx = userPresets.findIndex((p) => p.name === lowerName);
|
|
2677
|
+
if (idx < 0) {
|
|
2678
|
+
throw new Error(`User preset "${lowerName}" not found`);
|
|
2679
|
+
}
|
|
2680
|
+
userPresets.splice(idx, 1);
|
|
2681
|
+
saveUserPresets(userPresets);
|
|
2682
|
+
if (asJson) {
|
|
2683
|
+
emitSnResult({ ok: true, command: "preset", deleted: lowerName }, asJson);
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
process.stdout.write(`Preset "${lowerName}" deleted.
|
|
2687
|
+
`);
|
|
2688
|
+
}
|
|
2689
|
+
async function handlePreset(args, asJson) {
|
|
2690
|
+
const sub = args._[0];
|
|
2691
|
+
if (!sub || sub === "--help" || args["help"] === true) {
|
|
2692
|
+
process.stdout.write(USAGE);
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
switch (sub) {
|
|
2696
|
+
case "list":
|
|
2697
|
+
return handlePresetList(asJson);
|
|
2698
|
+
case "show":
|
|
2699
|
+
return handlePresetShow(args, asJson);
|
|
2700
|
+
case "save":
|
|
2701
|
+
return handlePresetSave(args, asJson);
|
|
2702
|
+
case "delete":
|
|
2703
|
+
return handlePresetDelete(args, asJson);
|
|
2704
|
+
default:
|
|
2705
|
+
throw new Error(`Unknown preset sub-command: "${sub}". Run "sn preset --help" for usage.`);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
var BUILTIN_PRESETS, PRESETS_DIR, PRESETS_FILE, USAGE;
|
|
2709
|
+
var init_presets = __esm({
|
|
2710
|
+
"src/cli/sn/presets.ts"() {
|
|
2711
|
+
"use strict";
|
|
2712
|
+
init_parse();
|
|
2713
|
+
BUILTIN_PRESETS = [
|
|
2714
|
+
{
|
|
2715
|
+
name: "instagram-reel",
|
|
2716
|
+
platform: "Instagram",
|
|
2717
|
+
maxLength: 2200,
|
|
2718
|
+
aspectRatio: "9:16",
|
|
2719
|
+
builtin: true
|
|
2720
|
+
},
|
|
2721
|
+
{
|
|
2722
|
+
name: "instagram-post",
|
|
2723
|
+
platform: "Instagram",
|
|
2724
|
+
maxLength: 2200,
|
|
2725
|
+
aspectRatio: "1:1",
|
|
2726
|
+
builtin: true
|
|
2727
|
+
},
|
|
2728
|
+
{ name: "tiktok", platform: "TikTok", maxLength: 4e3, aspectRatio: "9:16", builtin: true },
|
|
2729
|
+
{
|
|
2730
|
+
name: "youtube-short",
|
|
2731
|
+
platform: "YouTube",
|
|
2732
|
+
maxLength: 5e3,
|
|
2733
|
+
aspectRatio: "9:16",
|
|
2734
|
+
builtin: true
|
|
2735
|
+
},
|
|
2736
|
+
{ name: "linkedin-post", platform: "LinkedIn", maxLength: 3e3, builtin: true },
|
|
2737
|
+
{ name: "twitter-post", platform: "Twitter", maxLength: 280, builtin: true }
|
|
2738
|
+
];
|
|
2739
|
+
PRESETS_DIR = join2(homedir2(), ".config", "socialneuron");
|
|
2740
|
+
PRESETS_FILE = join2(PRESETS_DIR, "presets.json");
|
|
2741
|
+
USAGE = `Usage: sn preset <sub-command> [options]
|
|
2742
|
+
|
|
2743
|
+
Sub-commands:
|
|
2744
|
+
list List all presets (builtin + user)
|
|
2745
|
+
show --name <preset> Show a single preset
|
|
2746
|
+
save --name <n> --platform <p> --max-length <len> [--aspect-ratio <r>]
|
|
2747
|
+
delete --name <preset> Delete a user preset
|
|
2748
|
+
`;
|
|
2749
|
+
}
|
|
2750
|
+
});
|
|
2751
|
+
|
|
2752
|
+
// src/cli/sn.ts
|
|
2753
|
+
var sn_exports = {};
|
|
2754
|
+
__export(sn_exports, {
|
|
2755
|
+
printSnUsage: () => printSnUsage,
|
|
2756
|
+
runSnCli: () => runSnCli
|
|
2757
|
+
});
|
|
2758
|
+
function lazyContent(name) {
|
|
2759
|
+
return async (args, asJson) => {
|
|
2760
|
+
const mod = await Promise.resolve().then(() => (init_content(), content_exports));
|
|
2761
|
+
return mod[name](args, asJson);
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
function lazyAccount(name) {
|
|
2765
|
+
return async (args, asJson) => {
|
|
2766
|
+
const mod = await Promise.resolve().then(() => (init_account(), account_exports));
|
|
2767
|
+
return mod[name](args, asJson);
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
function lazyAnalytics(name) {
|
|
2771
|
+
return async (args, asJson) => {
|
|
2772
|
+
const mod = await Promise.resolve().then(() => (init_analytics(), analytics_exports));
|
|
2773
|
+
return mod[name](args, asJson);
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
function lazySystem(name) {
|
|
2777
|
+
return async (args, asJson) => {
|
|
2778
|
+
const mod = await Promise.resolve().then(() => (init_system(), system_exports));
|
|
2779
|
+
return mod[name](args, asJson);
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
function lazyDiscovery(name) {
|
|
2783
|
+
return async (args, asJson) => {
|
|
2784
|
+
const mod = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
|
|
2785
|
+
return mod[name](args, asJson);
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
function lazyPlanning(name) {
|
|
2789
|
+
return async (args, asJson) => {
|
|
2790
|
+
const mod = await Promise.resolve().then(() => (init_planning(), planning_exports));
|
|
2791
|
+
return mod[name](args, asJson);
|
|
2792
|
+
};
|
|
2793
|
+
}
|
|
2794
|
+
function lazyPresets(name) {
|
|
2795
|
+
return async (args, asJson) => {
|
|
2796
|
+
const mod = await Promise.resolve().then(() => (init_presets(), presets_exports));
|
|
2797
|
+
return mod[name](args, asJson);
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
function printSnUsage() {
|
|
2801
|
+
console.error("");
|
|
2802
|
+
console.error("Usage: socialneuron-mcp sn <command> [flags]");
|
|
2803
|
+
console.error("");
|
|
2804
|
+
console.error("Discovery:");
|
|
2805
|
+
console.error(" tools [--scope <scope>] [--module <module>] [--json]");
|
|
2806
|
+
console.error(" info [--json]");
|
|
2807
|
+
console.error("");
|
|
2808
|
+
console.error("Content:");
|
|
2809
|
+
console.error(
|
|
2810
|
+
" publish --media-url <url> --caption <text> --platforms <comma-list> --confirm [--title <text>] [--schedule-at <iso8601>] [--idempotency-key <key>] [--json]"
|
|
2811
|
+
);
|
|
2812
|
+
console.error(
|
|
2813
|
+
" quality-check --caption <text> [--title <text>] [--platforms <comma-list>] [--threshold <0-35>] [--json]"
|
|
2814
|
+
);
|
|
2815
|
+
console.error(
|
|
2816
|
+
" e2e --media-url <url> --caption <text> --platforms <comma-list> --confirm [--title <text>] [--schedule-at <iso8601>] [--check-urls] [--threshold <0-35>] [--dry-run] [--force] [--json]"
|
|
2817
|
+
);
|
|
2818
|
+
console.error(
|
|
2819
|
+
" plan (list|view|approve) [--plan-id <id>] [--status <draft|submitted|approved>] [--json]"
|
|
2820
|
+
);
|
|
2821
|
+
console.error(
|
|
2822
|
+
" preset (list|show|save|delete) [--name <name>] [--platform <name>] [--max-length <n>] [--aspect-ratio <ratio>] [--json]"
|
|
2823
|
+
);
|
|
2824
|
+
console.error("");
|
|
2825
|
+
console.error("Account:");
|
|
2826
|
+
console.error(" preflight [--privacy-url <url>] [--terms-url <url>] [--check-urls] [--json]");
|
|
2827
|
+
console.error(" oauth-health [--warn-days <1-90>] [--platforms <comma-list>] [--all] [--json]");
|
|
2828
|
+
console.error(" oauth-refresh (--platforms <comma-list> | --all) [--json]");
|
|
2829
|
+
console.error("");
|
|
2830
|
+
console.error("Analytics:");
|
|
2831
|
+
console.error(
|
|
2832
|
+
" posts [--days <1-90>] [--platform <name>] [--status <draft|scheduled|published|failed>] [--json]"
|
|
2833
|
+
);
|
|
2834
|
+
console.error(" refresh-analytics [--json]");
|
|
2835
|
+
console.error(" loop [--json]");
|
|
2836
|
+
console.error("");
|
|
2837
|
+
console.error("System:");
|
|
2838
|
+
console.error(" status --job-id <id> [--json]");
|
|
2839
|
+
console.error(" autopilot [--json]");
|
|
2840
|
+
console.error(" usage [--json]");
|
|
2841
|
+
console.error(" credits [--json]");
|
|
2842
|
+
console.error("");
|
|
2843
|
+
}
|
|
2844
|
+
async function runSnCli(argv) {
|
|
2845
|
+
const [first, ...rest] = argv;
|
|
2846
|
+
if (!first) {
|
|
2847
|
+
printSnUsage();
|
|
2848
|
+
process.exit(1);
|
|
1860
2849
|
}
|
|
1861
|
-
if (
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
p_user_id: userId,
|
|
1869
|
-
p_since: startOfMonth.toISOString()
|
|
1870
|
-
});
|
|
1871
|
-
if (rpcError) {
|
|
1872
|
-
const { data: logs } = await supabase.from("activity_logs").select("action, metadata").eq("user_id", userId).gte("created_at", startOfMonth.toISOString()).like("action", "mcp:%");
|
|
1873
|
-
const totalCalls = logs?.length ?? 0;
|
|
1874
|
-
if (asJson) {
|
|
1875
|
-
emitSnResult(
|
|
1876
|
-
{ ok: true, command: "usage", totalCalls, totalCredits: 0, tools: [] },
|
|
1877
|
-
true
|
|
1878
|
-
);
|
|
1879
|
-
} else {
|
|
1880
|
-
console.error("MCP Usage This Month");
|
|
1881
|
-
console.error("====================");
|
|
1882
|
-
console.error(`Total Calls: ${totalCalls}`);
|
|
1883
|
-
console.error("(Detailed breakdown requires get_mcp_monthly_usage RPC function)");
|
|
1884
|
-
}
|
|
1885
|
-
} else {
|
|
1886
|
-
const tools = rows ?? [];
|
|
1887
|
-
const totalCalls = tools.reduce((sum, t) => sum + (t.call_count ?? 0), 0);
|
|
1888
|
-
const totalCredits = tools.reduce((sum, t) => sum + (t.credits_total ?? 0), 0);
|
|
1889
|
-
if (asJson) {
|
|
1890
|
-
emitSnResult({ ok: true, command: "usage", totalCalls, totalCredits, tools }, true);
|
|
1891
|
-
} else {
|
|
1892
|
-
console.error("MCP Usage This Month");
|
|
1893
|
-
console.error("====================");
|
|
1894
|
-
console.error(`Total Calls: ${totalCalls}`);
|
|
1895
|
-
console.error(`Total Credits: ${totalCredits}`);
|
|
1896
|
-
if (tools.length) {
|
|
1897
|
-
console.error("\nPer-Tool Breakdown:");
|
|
1898
|
-
for (const tool of tools) {
|
|
1899
|
-
console.error(
|
|
1900
|
-
`- ${tool.tool_name}: ${tool.call_count} calls, ${tool.credits_total} credits`
|
|
1901
|
-
);
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
2850
|
+
if (GROUP_COMMANDS[first]) {
|
|
2851
|
+
const [subcommand, ...groupRest] = rest;
|
|
2852
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
2853
|
+
console.error(`
|
|
2854
|
+
Commands in "${first}" group:`);
|
|
2855
|
+
for (const cmd of GROUP_COMMANDS[first]) {
|
|
2856
|
+
console.error(` ${cmd}`);
|
|
1905
2857
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
if (asJson) emitSnResult({ ok: false, command: "usage", error: message }, true);
|
|
1909
|
-
else console.error(message);
|
|
1910
|
-
process.exit(1);
|
|
2858
|
+
console.error("");
|
|
2859
|
+
process.exit(subcommand ? 0 : 1);
|
|
1911
2860
|
}
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
const supabase = getSupabaseClient();
|
|
1917
|
-
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
1918
|
-
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
1919
|
-
const [brandResult, contentResult, insightsResult] = await Promise.all([
|
|
1920
|
-
supabase.from("brand_profiles").select("id").eq("user_id", userId).eq("is_active", true).limit(1).maybeSingle(),
|
|
1921
|
-
supabase.from("content_history").select("id, content_type, created_at").eq("user_id", userId).gte("created_at", thirtyDaysAgo.toISOString()).order("created_at", { ascending: false }).limit(10),
|
|
1922
|
-
supabase.from("performance_insights").select("id, insight_type, generated_at").gte("generated_at", thirtyDaysAgo.toISOString()).gt("expires_at", (/* @__PURE__ */ new Date()).toISOString()).limit(20)
|
|
1923
|
-
]);
|
|
1924
|
-
const hasProfile = !!brandResult.data;
|
|
1925
|
-
const recentCount = contentResult.data?.length ?? 0;
|
|
1926
|
-
const insightsCount = insightsResult.data?.length ?? 0;
|
|
1927
|
-
let nextAction = "Generate content to start building your feedback loop";
|
|
1928
|
-
if (!hasProfile) nextAction = "Set up your brand profile first";
|
|
1929
|
-
else if (recentCount === 0)
|
|
1930
|
-
nextAction = "Generate and publish content to collect performance data";
|
|
1931
|
-
else if (insightsCount === 0)
|
|
1932
|
-
nextAction = "Publish more content \u2014 insights need 5+ data points";
|
|
1933
|
-
else nextAction = "Loop is active \u2014 use insights to improve next content batch";
|
|
1934
|
-
if (asJson) {
|
|
1935
|
-
emitSnResult(
|
|
1936
|
-
{
|
|
1937
|
-
ok: true,
|
|
1938
|
-
command: "loop",
|
|
1939
|
-
brandStatus: { hasProfile },
|
|
1940
|
-
recentContent: contentResult.data ?? [],
|
|
1941
|
-
currentInsights: insightsResult.data ?? [],
|
|
1942
|
-
recommendedNextAction: nextAction
|
|
1943
|
-
},
|
|
1944
|
-
true
|
|
1945
|
-
);
|
|
1946
|
-
} else {
|
|
1947
|
-
console.error("Feedback Loop Summary");
|
|
1948
|
-
console.error("=====================");
|
|
1949
|
-
console.error(`Brand Profile: ${hasProfile ? "Ready" : "Missing"}`);
|
|
1950
|
-
console.error(`Recent Content: ${recentCount} items (last 30 days)`);
|
|
1951
|
-
console.error(`Current Insights: ${insightsCount} active`);
|
|
1952
|
-
console.error(`
|
|
1953
|
-
Next Action: ${nextAction}`);
|
|
1954
|
-
}
|
|
1955
|
-
} catch (err) {
|
|
1956
|
-
const message = `Loop summary failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
1957
|
-
if (asJson) emitSnResult({ ok: false, command: "loop", error: message }, true);
|
|
1958
|
-
else console.error(message);
|
|
2861
|
+
const entry2 = COMMAND_REGISTRY[subcommand];
|
|
2862
|
+
if (!entry2 || entry2.group !== first) {
|
|
2863
|
+
console.error(`Unknown ${first} subcommand: ${subcommand}`);
|
|
2864
|
+
console.error(`Available: ${GROUP_COMMANDS[first].join(", ")}`);
|
|
1959
2865
|
process.exit(1);
|
|
1960
2866
|
}
|
|
1961
|
-
|
|
2867
|
+
const args2 = parseSnArgs(groupRest);
|
|
2868
|
+
const asJson2 = isEnabledFlag(args2.json);
|
|
2869
|
+
await withSnErrorHandling(subcommand, asJson2, () => entry2.handler(args2, asJson2));
|
|
2870
|
+
return;
|
|
1962
2871
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
supabase.from("subscriptions").select("tier, status, monthly_credits").eq("user_id", userId).eq("status", "active").order("created_at", { ascending: false }).limit(1).maybeSingle()
|
|
1969
|
-
]);
|
|
1970
|
-
if (profileResult.error) throw profileResult.error;
|
|
1971
|
-
const balance = Number(profileResult.data?.credits || 0);
|
|
1972
|
-
const monthlyUsed = Number(profileResult.data?.monthly_credits_used || 0);
|
|
1973
|
-
const monthlyLimit = Number(subResult.data?.monthly_credits || 0);
|
|
1974
|
-
const plan = subResult.data?.tier || "free";
|
|
1975
|
-
if (asJson) {
|
|
1976
|
-
emitSnResult(
|
|
1977
|
-
{ ok: true, command: "credits", balance, monthlyUsed, monthlyLimit, plan },
|
|
1978
|
-
true
|
|
1979
|
-
);
|
|
1980
|
-
} else {
|
|
1981
|
-
console.error("Credit Balance");
|
|
1982
|
-
console.error("==============");
|
|
1983
|
-
console.error(`Plan: ${plan.toUpperCase()}`);
|
|
1984
|
-
console.error(`Balance: ${balance} credits`);
|
|
1985
|
-
if (monthlyLimit) {
|
|
1986
|
-
console.error(`Monthly Usage: ${monthlyUsed} / ${monthlyLimit}`);
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
} catch (err) {
|
|
1990
|
-
const message = `Credit balance failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
1991
|
-
if (asJson) emitSnResult({ ok: false, command: "credits", error: message }, true);
|
|
1992
|
-
else console.error(message);
|
|
1993
|
-
process.exit(1);
|
|
1994
|
-
}
|
|
1995
|
-
process.exit(0);
|
|
2872
|
+
const entry = COMMAND_REGISTRY[first];
|
|
2873
|
+
if (!entry) {
|
|
2874
|
+
console.error(`Unknown subcommand: ${first}`);
|
|
2875
|
+
printSnUsage();
|
|
2876
|
+
process.exit(1);
|
|
1996
2877
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2878
|
+
const args = parseSnArgs(rest);
|
|
2879
|
+
const asJson = isEnabledFlag(args.json);
|
|
2880
|
+
await withSnErrorHandling(first, asJson, () => entry.handler(args, asJson));
|
|
2000
2881
|
}
|
|
2882
|
+
var COMMAND_REGISTRY, GROUP_COMMANDS;
|
|
2001
2883
|
var init_sn = __esm({
|
|
2002
2884
|
"src/cli/sn.ts"() {
|
|
2003
2885
|
"use strict";
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2886
|
+
init_parse();
|
|
2887
|
+
init_error_handling();
|
|
2888
|
+
COMMAND_REGISTRY = {
|
|
2889
|
+
publish: { handler: lazyContent("handlePublish"), needsAuth: true, group: "content" },
|
|
2890
|
+
"quality-check": {
|
|
2891
|
+
handler: lazyContent("handleQualityCheck"),
|
|
2892
|
+
needsAuth: false,
|
|
2893
|
+
group: "content"
|
|
2894
|
+
},
|
|
2895
|
+
e2e: { handler: lazyContent("handleE2e"), needsAuth: true, group: "content" },
|
|
2896
|
+
"oauth-health": {
|
|
2897
|
+
handler: lazyAccount("handleOauthHealth"),
|
|
2898
|
+
needsAuth: true,
|
|
2899
|
+
group: "account"
|
|
2900
|
+
},
|
|
2901
|
+
"oauth-refresh": {
|
|
2902
|
+
handler: lazyAccount("handleOauthRefresh"),
|
|
2903
|
+
needsAuth: true,
|
|
2904
|
+
group: "account"
|
|
2905
|
+
},
|
|
2906
|
+
preflight: { handler: lazyAccount("handlePreflight"), needsAuth: true, group: "account" },
|
|
2907
|
+
status: { handler: lazySystem("handleStatus"), needsAuth: true, group: "system" },
|
|
2908
|
+
autopilot: { handler: lazySystem("handleAutopilot"), needsAuth: true, group: "system" },
|
|
2909
|
+
usage: { handler: lazySystem("handleUsage"), needsAuth: true, group: "system" },
|
|
2910
|
+
credits: { handler: lazySystem("handleCredits"), needsAuth: true, group: "system" },
|
|
2911
|
+
posts: { handler: lazyAnalytics("handlePosts"), needsAuth: true, group: "analytics" },
|
|
2912
|
+
"refresh-analytics": {
|
|
2913
|
+
handler: lazyAnalytics("handleRefreshAnalytics"),
|
|
2914
|
+
needsAuth: true,
|
|
2915
|
+
group: "analytics"
|
|
2916
|
+
},
|
|
2917
|
+
loop: { handler: lazyAnalytics("handleLoop"), needsAuth: true, group: "analytics" },
|
|
2918
|
+
tools: { handler: lazyDiscovery("handleTools"), needsAuth: false, group: "discovery" },
|
|
2919
|
+
info: { handler: lazyDiscovery("handleInfo"), needsAuth: false, group: "discovery" },
|
|
2920
|
+
plan: { handler: lazyPlanning("handlePlan"), needsAuth: true, group: "content" },
|
|
2921
|
+
preset: { handler: lazyPresets("handlePreset"), needsAuth: false, group: "content" }
|
|
2922
|
+
};
|
|
2923
|
+
GROUP_COMMANDS = {
|
|
2924
|
+
content: ["publish", "quality-check", "e2e", "plan", "preset"],
|
|
2925
|
+
account: ["oauth-health", "oauth-refresh", "preflight"],
|
|
2926
|
+
analytics: ["posts", "refresh-analytics", "loop"],
|
|
2927
|
+
system: ["status", "autopilot", "usage", "credits"],
|
|
2928
|
+
discovery: ["tools", "info"]
|
|
2929
|
+
};
|
|
2007
2930
|
}
|
|
2008
2931
|
});
|
|
2009
2932
|
|
|
@@ -2017,9 +2940,9 @@ __export(setup_exports, {
|
|
|
2017
2940
|
});
|
|
2018
2941
|
import { createHash as createHash4, randomBytes, randomUUID as randomUUID3 } from "node:crypto";
|
|
2019
2942
|
import { createServer } from "node:http";
|
|
2020
|
-
import { existsSync as
|
|
2021
|
-
import { homedir as
|
|
2022
|
-
import { join as
|
|
2943
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2944
|
+
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
2945
|
+
import { join as join3 } from "node:path";
|
|
2023
2946
|
function base64url(buffer) {
|
|
2024
2947
|
return buffer.toString("base64url");
|
|
2025
2948
|
}
|
|
@@ -2041,8 +2964,8 @@ function getConfigPaths() {
|
|
|
2041
2964
|
const os = platform2();
|
|
2042
2965
|
if (os === "darwin") {
|
|
2043
2966
|
paths.push({
|
|
2044
|
-
path:
|
|
2045
|
-
|
|
2967
|
+
path: join3(
|
|
2968
|
+
homedir3(),
|
|
2046
2969
|
"Library",
|
|
2047
2970
|
"Application Support",
|
|
2048
2971
|
"Claude",
|
|
@@ -2052,16 +2975,16 @@ function getConfigPaths() {
|
|
|
2052
2975
|
});
|
|
2053
2976
|
} else if (os === "linux") {
|
|
2054
2977
|
paths.push({
|
|
2055
|
-
path:
|
|
2978
|
+
path: join3(homedir3(), ".config", "claude", "claude_desktop_config.json"),
|
|
2056
2979
|
name: "Claude Desktop"
|
|
2057
2980
|
});
|
|
2058
2981
|
}
|
|
2059
|
-
const claudeCodeGlobal =
|
|
2060
|
-
if (
|
|
2982
|
+
const claudeCodeGlobal = join3(homedir3(), ".claude", ".mcp.json");
|
|
2983
|
+
if (existsSync3(claudeCodeGlobal)) {
|
|
2061
2984
|
paths.push({ path: claudeCodeGlobal, name: "Claude Code (global)" });
|
|
2062
2985
|
}
|
|
2063
|
-
const projectConfig =
|
|
2064
|
-
if (
|
|
2986
|
+
const projectConfig = join3(process.cwd(), ".mcp.json");
|
|
2987
|
+
if (existsSync3(projectConfig)) {
|
|
2065
2988
|
paths.push({ path: projectConfig, name: "Claude Code (project)" });
|
|
2066
2989
|
}
|
|
2067
2990
|
return paths;
|
|
@@ -2069,13 +2992,13 @@ function getConfigPaths() {
|
|
|
2069
2992
|
function configureMcpClient(configPath) {
|
|
2070
2993
|
try {
|
|
2071
2994
|
let config = {};
|
|
2072
|
-
if (
|
|
2073
|
-
const raw =
|
|
2995
|
+
if (existsSync3(configPath)) {
|
|
2996
|
+
const raw = readFileSync3(configPath, "utf-8");
|
|
2074
2997
|
config = JSON.parse(raw);
|
|
2075
2998
|
} else {
|
|
2076
|
-
const dir =
|
|
2077
|
-
if (!
|
|
2078
|
-
|
|
2999
|
+
const dir = join3(configPath, "..");
|
|
3000
|
+
if (!existsSync3(dir)) {
|
|
3001
|
+
mkdirSync3(dir, { recursive: true });
|
|
2079
3002
|
}
|
|
2080
3003
|
}
|
|
2081
3004
|
if (!config.mcpServers) {
|
|
@@ -2085,7 +3008,7 @@ function configureMcpClient(configPath) {
|
|
|
2085
3008
|
command: "npx",
|
|
2086
3009
|
args: ["-y", "@socialneuron/mcp-server"]
|
|
2087
3010
|
};
|
|
2088
|
-
|
|
3011
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
2089
3012
|
return true;
|
|
2090
3013
|
} catch {
|
|
2091
3014
|
return false;
|
|
@@ -2399,18 +3322,19 @@ async function runLoginDevice() {
|
|
|
2399
3322
|
console.error(" Error: Authorization timed out. Please try again.");
|
|
2400
3323
|
process.exit(1);
|
|
2401
3324
|
}
|
|
2402
|
-
async function runLogoutCommand() {
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
3325
|
+
async function runLogoutCommand(options) {
|
|
3326
|
+
const asJson = options?.json ?? false;
|
|
3327
|
+
if (!asJson) {
|
|
3328
|
+
console.error("");
|
|
3329
|
+
console.error(" Social Neuron - Logout");
|
|
3330
|
+
console.error(" ======================");
|
|
3331
|
+
console.error("");
|
|
3332
|
+
}
|
|
2407
3333
|
const apiKey = await loadApiKey();
|
|
2408
3334
|
if (apiKey) {
|
|
2409
3335
|
try {
|
|
2410
|
-
const supabaseUrl = getDefaultSupabaseUrl2();
|
|
2411
|
-
const serviceKey = process.env.SOCIALNEURON_SERVICE_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY || "";
|
|
2412
3336
|
const validation = await validateApiKey(apiKey);
|
|
2413
|
-
if (validation.valid) {
|
|
3337
|
+
if (validation.valid && !asJson) {
|
|
2414
3338
|
console.error(" Key removed from this device.");
|
|
2415
3339
|
console.error(
|
|
2416
3340
|
" Note: To revoke the key server-side, visit socialneuron.com/settings/developer"
|
|
@@ -2420,54 +3344,96 @@ async function runLogoutCommand() {
|
|
|
2420
3344
|
}
|
|
2421
3345
|
}
|
|
2422
3346
|
await deleteApiKey();
|
|
2423
|
-
|
|
2424
|
-
|
|
3347
|
+
if (asJson) {
|
|
3348
|
+
process.stdout.write(
|
|
3349
|
+
JSON.stringify({ ok: true, message: "Credentials removed", schema_version: "1" }, null, 2) + "\n"
|
|
3350
|
+
);
|
|
3351
|
+
} else {
|
|
3352
|
+
console.error(" Credentials removed from keychain.");
|
|
3353
|
+
console.error("");
|
|
3354
|
+
}
|
|
2425
3355
|
}
|
|
2426
|
-
async function runWhoami() {
|
|
2427
|
-
|
|
2428
|
-
console.error(" Social Neuron - Current Identity");
|
|
2429
|
-
console.error(" ================================");
|
|
2430
|
-
console.error("");
|
|
3356
|
+
async function runWhoami(options) {
|
|
3357
|
+
const asJson = options?.json ?? false;
|
|
2431
3358
|
const apiKey = await loadApiKey();
|
|
2432
3359
|
if (!apiKey) {
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
3360
|
+
if (asJson) {
|
|
3361
|
+
process.stdout.write(
|
|
3362
|
+
JSON.stringify({ ok: false, error: "Not logged in", schema_version: "1" }, null, 2) + "\n"
|
|
3363
|
+
);
|
|
3364
|
+
} else {
|
|
3365
|
+
console.error("");
|
|
3366
|
+
console.error(" Not logged in.");
|
|
3367
|
+
console.error(" Run: npx @socialneuron/mcp-server login");
|
|
3368
|
+
console.error("");
|
|
3369
|
+
}
|
|
2436
3370
|
process.exit(1);
|
|
2437
3371
|
}
|
|
2438
|
-
|
|
2439
|
-
const result = await validateApiKey(apiKey);
|
|
2440
|
-
if (!result.valid) {
|
|
2441
|
-
console.error(" Key is invalid or expired.");
|
|
2442
|
-
console.error(` Error: ${result.error || "Unknown"}`);
|
|
2443
|
-
console.error(" Run: npx @socialneuron/mcp-server login");
|
|
3372
|
+
if (!asJson) {
|
|
2444
3373
|
console.error("");
|
|
2445
|
-
|
|
3374
|
+
console.error(" Social Neuron - Current Identity");
|
|
3375
|
+
console.error(" ================================");
|
|
3376
|
+
console.error("");
|
|
3377
|
+
console.error(" Validating key...");
|
|
2446
3378
|
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
console.error(
|
|
3379
|
+
const result = await validateApiKey(apiKey);
|
|
3380
|
+
if (!result.valid) {
|
|
3381
|
+
if (asJson) {
|
|
3382
|
+
process.stdout.write(
|
|
3383
|
+
JSON.stringify(
|
|
3384
|
+
{ ok: false, error: result.error || "Key invalid or expired", schema_version: "1" },
|
|
3385
|
+
null,
|
|
3386
|
+
2
|
|
3387
|
+
) + "\n"
|
|
3388
|
+
);
|
|
3389
|
+
} else {
|
|
3390
|
+
console.error(" Key is invalid or expired.");
|
|
3391
|
+
console.error(` Error: ${result.error || "Unknown"}`);
|
|
2459
3392
|
console.error(" Run: npx @socialneuron/mcp-server login");
|
|
3393
|
+
console.error("");
|
|
2460
3394
|
}
|
|
3395
|
+
process.exit(1);
|
|
3396
|
+
}
|
|
3397
|
+
if (asJson) {
|
|
3398
|
+
const payload = {
|
|
3399
|
+
ok: true,
|
|
3400
|
+
email: result.email || null,
|
|
3401
|
+
userId: result.userId,
|
|
3402
|
+
keyPrefix: apiKey.substring(0, 12) + "...",
|
|
3403
|
+
scopes: result.scopes || ["mcp:full"],
|
|
3404
|
+
schema_version: "1"
|
|
3405
|
+
};
|
|
3406
|
+
if (result.expiresAt) payload.expiresAt = result.expiresAt;
|
|
3407
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
2461
3408
|
} else {
|
|
2462
|
-
console.error("
|
|
3409
|
+
console.error("");
|
|
3410
|
+
console.error(` Email: ${result.email || "(not available)"}`);
|
|
3411
|
+
console.error(` User ID: ${result.userId}`);
|
|
3412
|
+
console.error(` Key: ${apiKey.substring(0, 12)}...`);
|
|
3413
|
+
console.error(` Scopes: ${result.scopes?.join(", ") || "mcp:full"}`);
|
|
3414
|
+
if (result.expiresAt) {
|
|
3415
|
+
const expiresMs = new Date(result.expiresAt).getTime();
|
|
3416
|
+
const daysLeft = Math.ceil((expiresMs - Date.now()) / (1e3 * 60 * 60 * 24));
|
|
3417
|
+
console.error(` Expires: ${result.expiresAt} (${daysLeft} days)`);
|
|
3418
|
+
if (daysLeft <= 7) {
|
|
3419
|
+
console.error("");
|
|
3420
|
+
console.error(` Warning: Key expires in ${daysLeft} day(s).`);
|
|
3421
|
+
console.error(" Run: npx @socialneuron/mcp-server login");
|
|
3422
|
+
}
|
|
3423
|
+
} else {
|
|
3424
|
+
console.error(" Expires: never");
|
|
3425
|
+
}
|
|
3426
|
+
console.error("");
|
|
2463
3427
|
}
|
|
2464
|
-
console.error("");
|
|
2465
3428
|
}
|
|
2466
|
-
async function runHealthCheck() {
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
3429
|
+
async function runHealthCheck(options) {
|
|
3430
|
+
const asJson = options?.json ?? false;
|
|
3431
|
+
if (!asJson) {
|
|
3432
|
+
console.error("");
|
|
3433
|
+
console.error(" Social Neuron \u2014 Health Check");
|
|
3434
|
+
console.error(" ============================");
|
|
3435
|
+
console.error("");
|
|
3436
|
+
}
|
|
2471
3437
|
const checks = [];
|
|
2472
3438
|
const apiKey = await loadApiKey();
|
|
2473
3439
|
if (!apiKey) {
|
|
@@ -2552,13 +3518,26 @@ async function runHealthCheck() {
|
|
|
2552
3518
|
}
|
|
2553
3519
|
}
|
|
2554
3520
|
const allOk = checks.every((c) => c.ok);
|
|
2555
|
-
|
|
2556
|
-
const
|
|
2557
|
-
|
|
3521
|
+
if (asJson) {
|
|
3522
|
+
const checksObj = {};
|
|
3523
|
+
for (const check of checks) {
|
|
3524
|
+
checksObj[check.name.toLowerCase().replace(/\s+/g, "_")] = {
|
|
3525
|
+
status: check.ok ? "pass" : "fail",
|
|
3526
|
+
detail: check.detail
|
|
3527
|
+
};
|
|
3528
|
+
}
|
|
3529
|
+
process.stdout.write(
|
|
3530
|
+
JSON.stringify({ ok: allOk, checks: checksObj, schema_version: "1" }, null, 2) + "\n"
|
|
3531
|
+
);
|
|
3532
|
+
} else {
|
|
3533
|
+
for (const check of checks) {
|
|
3534
|
+
const icon = check.ok ? "\u2713" : "\u2717";
|
|
3535
|
+
console.error(` ${icon} ${check.name}: ${check.detail}`);
|
|
3536
|
+
}
|
|
3537
|
+
console.error("");
|
|
3538
|
+
console.error(` Overall: ${allOk ? "All checks passed" : "Some checks failed"}`);
|
|
3539
|
+
console.error("");
|
|
2558
3540
|
}
|
|
2559
|
-
console.error("");
|
|
2560
|
-
console.error(` Overall: ${allOk ? "All checks passed" : "Some checks failed"}`);
|
|
2561
|
-
console.error("");
|
|
2562
3541
|
if (!allOk) {
|
|
2563
3542
|
process.exit(1);
|
|
2564
3543
|
}
|
|
@@ -2573,13 +3552,153 @@ var init_commands = __esm({
|
|
|
2573
3552
|
}
|
|
2574
3553
|
});
|
|
2575
3554
|
|
|
3555
|
+
// src/cli/repl.ts
|
|
3556
|
+
var repl_exports = {};
|
|
3557
|
+
__export(repl_exports, {
|
|
3558
|
+
runRepl: () => runRepl
|
|
3559
|
+
});
|
|
3560
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
3561
|
+
async function runRepl() {
|
|
3562
|
+
process.stderr.write(`
|
|
3563
|
+
Social Neuron CLI v${MCP_VERSION} \u2014 Interactive Mode
|
|
3564
|
+
`);
|
|
3565
|
+
process.stderr.write("Type a command, .help for help, or .exit to quit.\n\n");
|
|
3566
|
+
let authEmail = null;
|
|
3567
|
+
try {
|
|
3568
|
+
const { loadApiKey: loadApiKey2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
|
|
3569
|
+
const { validateApiKey: validateApiKey2 } = await Promise.resolve().then(() => (init_api_keys(), api_keys_exports));
|
|
3570
|
+
const key = await loadApiKey2();
|
|
3571
|
+
if (key) {
|
|
3572
|
+
const result = await validateApiKey2(key);
|
|
3573
|
+
if (result.valid) {
|
|
3574
|
+
authEmail = result.email || null;
|
|
3575
|
+
process.stderr.write(` Authenticated as: ${authEmail || "unknown"}
|
|
3576
|
+
|
|
3577
|
+
`);
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
} catch {
|
|
3581
|
+
process.stderr.write(" Not authenticated (some commands will require login)\n\n");
|
|
3582
|
+
}
|
|
3583
|
+
const COMPLETIONS = [
|
|
3584
|
+
"publish",
|
|
3585
|
+
"quality-check",
|
|
3586
|
+
"e2e",
|
|
3587
|
+
"oauth-health",
|
|
3588
|
+
"oauth-refresh",
|
|
3589
|
+
"preflight",
|
|
3590
|
+
"posts",
|
|
3591
|
+
"refresh-analytics",
|
|
3592
|
+
"loop",
|
|
3593
|
+
"status",
|
|
3594
|
+
"autopilot",
|
|
3595
|
+
"usage",
|
|
3596
|
+
"credits",
|
|
3597
|
+
"tools",
|
|
3598
|
+
"info",
|
|
3599
|
+
"plan",
|
|
3600
|
+
"preset",
|
|
3601
|
+
"content",
|
|
3602
|
+
"account",
|
|
3603
|
+
"analytics",
|
|
3604
|
+
"system",
|
|
3605
|
+
".help",
|
|
3606
|
+
".exit",
|
|
3607
|
+
".clear"
|
|
3608
|
+
];
|
|
3609
|
+
const promptStr = authEmail ? `sn[${authEmail.split("@")[0]}]> ` : "sn> ";
|
|
3610
|
+
const rl = createInterface2({
|
|
3611
|
+
input: process.stdin,
|
|
3612
|
+
output: process.stderr,
|
|
3613
|
+
prompt: promptStr,
|
|
3614
|
+
completer: (line) => {
|
|
3615
|
+
const hits = COMPLETIONS.filter((c) => c.startsWith(line.trim()));
|
|
3616
|
+
return [hits.length ? hits : COMPLETIONS, line];
|
|
3617
|
+
}
|
|
3618
|
+
});
|
|
3619
|
+
rl.prompt();
|
|
3620
|
+
rl.on("line", async (line) => {
|
|
3621
|
+
const trimmed = line.trim();
|
|
3622
|
+
if (!trimmed) {
|
|
3623
|
+
rl.prompt();
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
if (trimmed === ".exit" || trimmed === "exit" || trimmed === "quit") {
|
|
3627
|
+
process.stderr.write("Goodbye.\n");
|
|
3628
|
+
rl.close();
|
|
3629
|
+
process.exit(0);
|
|
3630
|
+
}
|
|
3631
|
+
if (trimmed === ".help") {
|
|
3632
|
+
process.stderr.write("\nREPL Commands:\n");
|
|
3633
|
+
process.stderr.write(" .help Show this help\n");
|
|
3634
|
+
process.stderr.write(" .clear Clear the screen\n");
|
|
3635
|
+
process.stderr.write(" .exit Exit the REPL\n");
|
|
3636
|
+
process.stderr.write("\nCLI Commands:\n");
|
|
3637
|
+
process.stderr.write(" publish, quality-check, e2e, posts, credits, etc.\n");
|
|
3638
|
+
process.stderr.write(' Type any sn subcommand directly (no "sn" prefix needed)\n\n');
|
|
3639
|
+
rl.prompt();
|
|
3640
|
+
return;
|
|
3641
|
+
}
|
|
3642
|
+
if (trimmed === ".clear") {
|
|
3643
|
+
process.stderr.write("\x1B[2J\x1B[H");
|
|
3644
|
+
rl.prompt();
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3647
|
+
const originalExit = process.exit;
|
|
3648
|
+
process.exit = ((_code) => {
|
|
3649
|
+
});
|
|
3650
|
+
try {
|
|
3651
|
+
const { runSnCli: runSnCli2 } = await Promise.resolve().then(() => (init_sn(), sn_exports));
|
|
3652
|
+
const argv = splitArgs(trimmed);
|
|
3653
|
+
await runSnCli2(argv);
|
|
3654
|
+
} catch (err) {
|
|
3655
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3656
|
+
process.stderr.write(`Error: ${message}
|
|
3657
|
+
`);
|
|
3658
|
+
} finally {
|
|
3659
|
+
process.exit = originalExit;
|
|
3660
|
+
}
|
|
3661
|
+
rl.prompt();
|
|
3662
|
+
});
|
|
3663
|
+
rl.on("close", () => {
|
|
3664
|
+
process.exit(0);
|
|
3665
|
+
});
|
|
3666
|
+
await new Promise(() => {
|
|
3667
|
+
});
|
|
3668
|
+
}
|
|
3669
|
+
function splitArgs(line) {
|
|
3670
|
+
const args = [];
|
|
3671
|
+
let current = "";
|
|
3672
|
+
let inQuotes = false;
|
|
3673
|
+
for (const char of line) {
|
|
3674
|
+
if (char === '"') {
|
|
3675
|
+
inQuotes = !inQuotes;
|
|
3676
|
+
continue;
|
|
3677
|
+
}
|
|
3678
|
+
if (char === " " && !inQuotes) {
|
|
3679
|
+
if (current) {
|
|
3680
|
+
args.push(current);
|
|
3681
|
+
current = "";
|
|
3682
|
+
}
|
|
3683
|
+
continue;
|
|
3684
|
+
}
|
|
3685
|
+
current += char;
|
|
3686
|
+
}
|
|
3687
|
+
if (current) args.push(current);
|
|
3688
|
+
return args;
|
|
3689
|
+
}
|
|
3690
|
+
var init_repl = __esm({
|
|
3691
|
+
"src/cli/repl.ts"() {
|
|
3692
|
+
"use strict";
|
|
3693
|
+
init_version();
|
|
3694
|
+
}
|
|
3695
|
+
});
|
|
3696
|
+
|
|
2576
3697
|
// src/index.ts
|
|
3698
|
+
init_version();
|
|
2577
3699
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2578
3700
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2579
3701
|
|
|
2580
|
-
// src/lib/version.ts
|
|
2581
|
-
var MCP_VERSION = "1.2.1";
|
|
2582
|
-
|
|
2583
3702
|
// src/auth/scopes.ts
|
|
2584
3703
|
var SCOPE_HIERARCHY = {
|
|
2585
3704
|
"mcp:full": [
|
|
@@ -2658,7 +3777,8 @@ var TOOL_SCOPES = {
|
|
|
2658
3777
|
schedule_content_plan: "mcp:distribute",
|
|
2659
3778
|
// mcp:read (usage is read-only)
|
|
2660
3779
|
get_mcp_usage: "mcp:read",
|
|
2661
|
-
list_plan_approvals: "mcp:read"
|
|
3780
|
+
list_plan_approvals: "mcp:read",
|
|
3781
|
+
search_tools: "mcp:read"
|
|
2662
3782
|
};
|
|
2663
3783
|
function hasScope(userScopes, required) {
|
|
2664
3784
|
if (userScopes.includes(required)) return true;
|
|
@@ -3193,11 +4313,11 @@ function asEnvelope(data) {
|
|
|
3193
4313
|
};
|
|
3194
4314
|
}
|
|
3195
4315
|
var VIDEO_CREDIT_ESTIMATES = {
|
|
3196
|
-
"veo3-fast":
|
|
3197
|
-
"veo3-quality":
|
|
3198
|
-
"runway-aleph":
|
|
3199
|
-
sora2:
|
|
3200
|
-
"sora2-pro":
|
|
4316
|
+
"veo3-fast": 200,
|
|
4317
|
+
"veo3-quality": 1e3,
|
|
4318
|
+
"runway-aleph": 340,
|
|
4319
|
+
sora2: 500,
|
|
4320
|
+
"sora2-pro": 1500,
|
|
3201
4321
|
kling: 170,
|
|
3202
4322
|
"kling-3": 100,
|
|
3203
4323
|
"kling-3-pro": 135
|
|
@@ -8000,6 +9120,7 @@ Next Action: ${payload.recommendedNextAction}`
|
|
|
8000
9120
|
// src/tools/usage.ts
|
|
8001
9121
|
init_supabase();
|
|
8002
9122
|
import { z as z14 } from "zod";
|
|
9123
|
+
init_version();
|
|
8003
9124
|
function asEnvelope10(data) {
|
|
8004
9125
|
return {
|
|
8005
9126
|
_meta: {
|
|
@@ -8088,6 +9209,7 @@ ${"=".repeat(40)}
|
|
|
8088
9209
|
// src/tools/autopilot.ts
|
|
8089
9210
|
init_supabase();
|
|
8090
9211
|
import { z as z15 } from "zod";
|
|
9212
|
+
init_version();
|
|
8091
9213
|
function asEnvelope11(data) {
|
|
8092
9214
|
return {
|
|
8093
9215
|
_meta: {
|
|
@@ -9669,6 +10791,59 @@ function registerPlanApprovalTools(server2) {
|
|
|
9669
10791
|
);
|
|
9670
10792
|
}
|
|
9671
10793
|
|
|
10794
|
+
// src/tools/discovery.ts
|
|
10795
|
+
init_tool_catalog();
|
|
10796
|
+
import { z as z20 } from "zod";
|
|
10797
|
+
function registerDiscoveryTools(server2) {
|
|
10798
|
+
server2.tool(
|
|
10799
|
+
"search_tools",
|
|
10800
|
+
'Search and discover available MCP tools. Use detail level to control token usage: "name" (~50 tokens), "summary" (~500 tokens), "full" (complete schemas).',
|
|
10801
|
+
{
|
|
10802
|
+
query: z20.string().optional().describe("Search query to filter tools by name or description"),
|
|
10803
|
+
module: z20.string().optional().describe('Filter by module name (e.g. "planning", "content", "analytics")'),
|
|
10804
|
+
scope: z20.string().optional().describe('Filter by required scope (e.g. "mcp:read", "mcp:write")'),
|
|
10805
|
+
detail: z20.enum(["name", "summary", "full"]).default("summary").describe(
|
|
10806
|
+
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
10807
|
+
)
|
|
10808
|
+
},
|
|
10809
|
+
async ({ query, module, scope, detail }) => {
|
|
10810
|
+
let results = [...TOOL_CATALOG];
|
|
10811
|
+
if (query) {
|
|
10812
|
+
results = searchTools(query);
|
|
10813
|
+
}
|
|
10814
|
+
if (module) {
|
|
10815
|
+
const moduleTools = getToolsByModule(module);
|
|
10816
|
+
results = results.filter((t) => moduleTools.some((mt) => mt.name === t.name));
|
|
10817
|
+
}
|
|
10818
|
+
if (scope) {
|
|
10819
|
+
const scopeTools = getToolsByScope(scope);
|
|
10820
|
+
results = results.filter((t) => scopeTools.some((st) => st.name === t.name));
|
|
10821
|
+
}
|
|
10822
|
+
let output;
|
|
10823
|
+
switch (detail) {
|
|
10824
|
+
case "name":
|
|
10825
|
+
output = results.map((t) => t.name);
|
|
10826
|
+
break;
|
|
10827
|
+
case "summary":
|
|
10828
|
+
output = results.map((t) => ({ name: t.name, description: t.description }));
|
|
10829
|
+
break;
|
|
10830
|
+
case "full":
|
|
10831
|
+
default:
|
|
10832
|
+
output = results;
|
|
10833
|
+
break;
|
|
10834
|
+
}
|
|
10835
|
+
return {
|
|
10836
|
+
content: [
|
|
10837
|
+
{
|
|
10838
|
+
type: "text",
|
|
10839
|
+
text: JSON.stringify({ toolCount: results.length, tools: output }, null, 2)
|
|
10840
|
+
}
|
|
10841
|
+
]
|
|
10842
|
+
};
|
|
10843
|
+
}
|
|
10844
|
+
);
|
|
10845
|
+
}
|
|
10846
|
+
|
|
9672
10847
|
// src/lib/register-tools.ts
|
|
9673
10848
|
function applyScopeEnforcement(server2, scopeResolver) {
|
|
9674
10849
|
const originalTool = server2.tool.bind(server2);
|
|
@@ -9732,6 +10907,7 @@ function registerAllTools(server2, options) {
|
|
|
9732
10907
|
registerQualityTools(server2);
|
|
9733
10908
|
registerPlanningTools(server2);
|
|
9734
10909
|
registerPlanApprovalTools(server2);
|
|
10910
|
+
registerDiscoveryTools(server2);
|
|
9735
10911
|
}
|
|
9736
10912
|
|
|
9737
10913
|
// src/index.ts
|
|
@@ -9751,21 +10927,59 @@ process.on("unhandledRejection", (reason) => {
|
|
|
9751
10927
|
});
|
|
9752
10928
|
var command = process.argv[2];
|
|
9753
10929
|
if (command === "--version" || command === "-v") {
|
|
9754
|
-
const { readFileSync:
|
|
10930
|
+
const { readFileSync: readFileSync4 } = await import("node:fs");
|
|
9755
10931
|
const { resolve: resolve3, dirname } = await import("node:path");
|
|
9756
10932
|
const { fileURLToPath } = await import("node:url");
|
|
10933
|
+
let version = MCP_VERSION;
|
|
9757
10934
|
try {
|
|
9758
10935
|
const pkgPath = resolve3(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
9759
|
-
const pkg = JSON.parse(
|
|
9760
|
-
|
|
10936
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
10937
|
+
version = pkg.version;
|
|
9761
10938
|
} catch {
|
|
9762
|
-
|
|
10939
|
+
}
|
|
10940
|
+
if (process.argv.includes("--json")) {
|
|
10941
|
+
process.stdout.write(
|
|
10942
|
+
JSON.stringify(
|
|
10943
|
+
{
|
|
10944
|
+
ok: true,
|
|
10945
|
+
command: "version",
|
|
10946
|
+
version,
|
|
10947
|
+
name: "@socialneuron/mcp-server",
|
|
10948
|
+
schema_version: "1"
|
|
10949
|
+
},
|
|
10950
|
+
null,
|
|
10951
|
+
2
|
|
10952
|
+
) + "\n"
|
|
10953
|
+
);
|
|
10954
|
+
} else {
|
|
10955
|
+
console.log(`@socialneuron/mcp-server v${version}`);
|
|
9763
10956
|
}
|
|
9764
10957
|
process.exit(0);
|
|
9765
10958
|
}
|
|
9766
10959
|
if (command === "--help" || command === "-h") {
|
|
9767
|
-
|
|
9768
|
-
|
|
10960
|
+
if (process.argv.includes("--json")) {
|
|
10961
|
+
process.stdout.write(
|
|
10962
|
+
JSON.stringify(
|
|
10963
|
+
{
|
|
10964
|
+
ok: true,
|
|
10965
|
+
command: "help",
|
|
10966
|
+
commands: [
|
|
10967
|
+
{ name: "setup", aliases: ["login"], description: "Interactive OAuth setup" },
|
|
10968
|
+
{ name: "logout", description: "Remove credentials" },
|
|
10969
|
+
{ name: "whoami", description: "Show auth info" },
|
|
10970
|
+
{ name: "health", description: "Check connectivity" },
|
|
10971
|
+
{ name: "sn", description: "CLI tools (publish, preflight, etc.)" },
|
|
10972
|
+
{ name: "repl", description: "Interactive REPL mode" }
|
|
10973
|
+
],
|
|
10974
|
+
schema_version: "1"
|
|
10975
|
+
},
|
|
10976
|
+
null,
|
|
10977
|
+
2
|
|
10978
|
+
) + "\n"
|
|
10979
|
+
);
|
|
10980
|
+
} else {
|
|
10981
|
+
console.log(
|
|
10982
|
+
`
|
|
9769
10983
|
@socialneuron/mcp-server \u2014 AI content creation tools for Claude
|
|
9770
10984
|
|
|
9771
10985
|
Usage:
|
|
@@ -9778,6 +10992,7 @@ Usage:
|
|
|
9778
10992
|
socialneuron-mcp whoami Show authenticated user info
|
|
9779
10993
|
socialneuron-mcp health Check connectivity, key validity, credits
|
|
9780
10994
|
socialneuron-mcp sn <command> CLI tools (publish, preflight, e2e, etc.)
|
|
10995
|
+
socialneuron-mcp repl Interactive REPL mode
|
|
9781
10996
|
socialneuron-mcp --version Show version
|
|
9782
10997
|
socialneuron-mcp --help Show this help
|
|
9783
10998
|
|
|
@@ -9788,7 +11003,8 @@ Environment:
|
|
|
9788
11003
|
|
|
9789
11004
|
Docs: https://github.com/socialneuron/mcp-server#readme
|
|
9790
11005
|
`.trim()
|
|
9791
|
-
|
|
11006
|
+
);
|
|
11007
|
+
}
|
|
9792
11008
|
process.exit(0);
|
|
9793
11009
|
}
|
|
9794
11010
|
if (command === "setup" || command === "login") {
|
|
@@ -9807,19 +11023,26 @@ if (command === "setup" || command === "login") {
|
|
|
9807
11023
|
}
|
|
9808
11024
|
if (command === "logout") {
|
|
9809
11025
|
const { runLogoutCommand: runLogoutCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
|
|
9810
|
-
|
|
11026
|
+
const jsonFlag = process.argv.slice(3).includes("--json");
|
|
11027
|
+
await runLogoutCommand2({ json: jsonFlag });
|
|
9811
11028
|
process.exit(0);
|
|
9812
11029
|
}
|
|
9813
11030
|
if (command === "whoami") {
|
|
9814
11031
|
const { runWhoami: runWhoami2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
|
|
9815
|
-
|
|
11032
|
+
const jsonFlag = process.argv.slice(3).includes("--json");
|
|
11033
|
+
await runWhoami2({ json: jsonFlag });
|
|
9816
11034
|
process.exit(0);
|
|
9817
11035
|
}
|
|
9818
11036
|
if (command === "health") {
|
|
9819
11037
|
const { runHealthCheck: runHealthCheck2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
|
|
9820
|
-
|
|
11038
|
+
const jsonFlag = process.argv.slice(3).includes("--json");
|
|
11039
|
+
await runHealthCheck2({ json: jsonFlag });
|
|
9821
11040
|
process.exit(0);
|
|
9822
11041
|
}
|
|
11042
|
+
if (command === "repl") {
|
|
11043
|
+
const { runRepl: runRepl2 } = await Promise.resolve().then(() => (init_repl(), repl_exports));
|
|
11044
|
+
await runRepl2();
|
|
11045
|
+
}
|
|
9823
11046
|
if (command === "sn") {
|
|
9824
11047
|
const snSubcommand = process.argv[3];
|
|
9825
11048
|
if (!snSubcommand || snSubcommand === "--help" || snSubcommand === "-h") {
|
|
@@ -9827,11 +11050,10 @@ if (command === "sn") {
|
|
|
9827
11050
|
printSnUsage2();
|
|
9828
11051
|
process.exit(snSubcommand ? 0 : 1);
|
|
9829
11052
|
}
|
|
9830
|
-
await initializeAuth();
|
|
9831
11053
|
await runSnCli(process.argv.slice(3));
|
|
9832
11054
|
process.exit(0);
|
|
9833
11055
|
}
|
|
9834
|
-
if (command && !["setup", "login", "logout", "whoami", "health", "sn"].includes(command)) {
|
|
11056
|
+
if (command && !["setup", "login", "logout", "whoami", "health", "sn", "repl"].includes(command)) {
|
|
9835
11057
|
process.stderr.write(`Unknown command: ${command}
|
|
9836
11058
|
Run socialneuron-mcp --help for usage.
|
|
9837
11059
|
`);
|