ping-mcp-server 0.1.9 → 0.1.11
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/dist/index.js +108 -79
- package/package.json +1 -1
- package/src/index.ts +156 -86
package/dist/index.js
CHANGED
|
@@ -515,7 +515,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
515
515
|
},
|
|
516
516
|
{
|
|
517
517
|
name: "ping_submit_answer",
|
|
518
|
-
description: "Submit
|
|
518
|
+
description: "Submit a SINGLE answer. DEPRECATED: Use ping_submit_batch instead for better UX. Only use this if you need to submit exactly one answer.",
|
|
519
519
|
inputSchema: {
|
|
520
520
|
type: "object",
|
|
521
521
|
properties: {
|
|
@@ -535,9 +535,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
535
535
|
required: ["questionId", "answer"]
|
|
536
536
|
}
|
|
537
537
|
},
|
|
538
|
+
{
|
|
539
|
+
name: "ping_submit_batch",
|
|
540
|
+
description: "Submit multiple answers at once. ALWAYS use this instead of ping_submit_answer. Returns a fun receipt-style summary. Much cleaner UX than individual submissions.",
|
|
541
|
+
inputSchema: {
|
|
542
|
+
type: "object",
|
|
543
|
+
properties: {
|
|
544
|
+
answers: {
|
|
545
|
+
type: "array",
|
|
546
|
+
items: {
|
|
547
|
+
type: "object",
|
|
548
|
+
properties: {
|
|
549
|
+
questionId: { type: "string" },
|
|
550
|
+
answer: { type: "string" },
|
|
551
|
+
preApproved: { type: "boolean" }
|
|
552
|
+
},
|
|
553
|
+
required: ["questionId", "answer"]
|
|
554
|
+
},
|
|
555
|
+
description: "Array of answers to submit"
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
required: ["answers"]
|
|
559
|
+
}
|
|
560
|
+
},
|
|
538
561
|
{
|
|
539
562
|
name: "ping_answer_flow",
|
|
540
|
-
description: 'Start an interactive Q&A session to earn money answering Ping questions. TRIGGERS: "ping", "/ping", "answer questions", "earn money", "make money with ping", "start earning". Returns questions with suggested answers. CRITICAL:
|
|
563
|
+
description: 'Start an interactive Q&A session to earn money answering Ping questions. TRIGGERS: "ping", "/ping", "answer questions", "earn money", "make money with ping", "start earning". Returns questions with suggested answers. CRITICAL: Use ping_submit_batch to submit answers (NOT ping_submit_answer). Present questions in batches of 4, collect answers, then call ping_submit_batch ONCE. AUTO-CLAIM: Call ping_claim_reward AFTER all batches submitted. Requires GitHub login first (ping_login).',
|
|
541
564
|
inputSchema: {
|
|
542
565
|
type: "object",
|
|
543
566
|
properties: {
|
|
@@ -775,11 +798,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
775
798
|
return {
|
|
776
799
|
content: [{
|
|
777
800
|
type: "text",
|
|
778
|
-
text:
|
|
779
|
-
loggedIn: false,
|
|
780
|
-
message: "You're not logged in to Ping.",
|
|
781
|
-
hint: "Use ping_login to connect with GitHub."
|
|
782
|
-
}, null, 2)
|
|
801
|
+
text: "\u274C Not logged in. Use ping_login to connect with GitHub."
|
|
783
802
|
}]
|
|
784
803
|
};
|
|
785
804
|
}
|
|
@@ -789,31 +808,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
789
808
|
return {
|
|
790
809
|
content: [{
|
|
791
810
|
type: "text",
|
|
792
|
-
text:
|
|
793
|
-
loggedIn: true,
|
|
794
|
-
handle: userData.handle,
|
|
795
|
-
authProvider: userData.authProvider,
|
|
796
|
-
walletAddress: userData.walletAddress || "Not set yet",
|
|
797
|
-
balance: {
|
|
798
|
-
available: userData.availableBalance,
|
|
799
|
-
pending: userData.pendingBalance,
|
|
800
|
-
claimed: userData.claimedBalance
|
|
801
|
-
},
|
|
802
|
-
message: `Logged in as ${userData.handle}`
|
|
803
|
-
}, null, 2)
|
|
811
|
+
text: `\u2705 Logged in as @${userData.handle}`
|
|
804
812
|
}]
|
|
805
813
|
};
|
|
806
814
|
} catch (err) {
|
|
807
815
|
return {
|
|
808
816
|
content: [{
|
|
809
817
|
type: "text",
|
|
810
|
-
text:
|
|
811
|
-
loggedIn: true,
|
|
812
|
-
handle: `@${auth.handle}`,
|
|
813
|
-
authProvider: "github",
|
|
814
|
-
message: `Logged in as @${auth.handle}`,
|
|
815
|
-
note: "Could not fetch balance (API may be unavailable)"
|
|
816
|
-
}, null, 2)
|
|
818
|
+
text: `\u2705 Logged in as @${auth.handle} (offline mode)`
|
|
817
819
|
}]
|
|
818
820
|
};
|
|
819
821
|
}
|
|
@@ -821,13 +823,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
821
823
|
return {
|
|
822
824
|
content: [{
|
|
823
825
|
type: "text",
|
|
824
|
-
text:
|
|
825
|
-
loggedIn: false,
|
|
826
|
-
legacyMode: true,
|
|
827
|
-
walletAddress: wallet,
|
|
828
|
-
message: "Using legacy wallet-only mode (not logged in with GitHub).",
|
|
829
|
-
hint: "Use ping_login to connect with GitHub for the full experience."
|
|
830
|
-
}, null, 2)
|
|
826
|
+
text: `\u26A0\uFE0F Legacy mode: wallet ${wallet?.slice(0, 10)}... (Use ping_login for full features)`
|
|
831
827
|
}]
|
|
832
828
|
};
|
|
833
829
|
}
|
|
@@ -1021,6 +1017,58 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1021
1017
|
};
|
|
1022
1018
|
}
|
|
1023
1019
|
// ────────────────────────────────────────────────────────
|
|
1020
|
+
// BATCH SUBMIT (Submit multiple answers at once)
|
|
1021
|
+
// ────────────────────────────────────────────────────────
|
|
1022
|
+
case "ping_submit_batch": {
|
|
1023
|
+
const auth = getAuth();
|
|
1024
|
+
if (!auth) {
|
|
1025
|
+
return {
|
|
1026
|
+
content: [{
|
|
1027
|
+
type: "text",
|
|
1028
|
+
text: JSON.stringify({
|
|
1029
|
+
success: false,
|
|
1030
|
+
error: "Not logged in.",
|
|
1031
|
+
hint: "Use ping_login to connect your GitHub account first."
|
|
1032
|
+
}, null, 2)
|
|
1033
|
+
}]
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
const { answers } = args || {};
|
|
1037
|
+
if (!answers || answers.length === 0) {
|
|
1038
|
+
return {
|
|
1039
|
+
content: [{
|
|
1040
|
+
type: "text",
|
|
1041
|
+
text: JSON.stringify({
|
|
1042
|
+
success: false,
|
|
1043
|
+
error: "No answers provided.",
|
|
1044
|
+
hint: "Provide an array of answers with questionId and answer fields."
|
|
1045
|
+
}, null, 2)
|
|
1046
|
+
}]
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
const data = await apiRequest("/answers/batch", {
|
|
1050
|
+
method: "POST",
|
|
1051
|
+
body: JSON.stringify({ answers })
|
|
1052
|
+
});
|
|
1053
|
+
const { summary } = data;
|
|
1054
|
+
const checkmark = summary.approved > 0 ? "\u2705" : "\u2B1C";
|
|
1055
|
+
const statusLine = summary.errors > 0 ? `${summary.approved}/${summary.submitted} approved, ${summary.errors} error(s)` : `${summary.approved}/${summary.submitted} approved`;
|
|
1056
|
+
const receipt = `
|
|
1057
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
1058
|
+
\u2502 \u{1F9FE} PING RECEIPT \u2502
|
|
1059
|
+
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
1060
|
+
\u2502 ${checkmark} ${statusLine.padEnd(24)}\u2502
|
|
1061
|
+
\u2502 \u{1F4B0} ${summary.totalEarned.padEnd(24)}\u2502
|
|
1062
|
+
\u2502 \u{1F4CA} Balance: ${summary.pendingBalance.padEnd(14)}\u2502
|
|
1063
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518`;
|
|
1064
|
+
return {
|
|
1065
|
+
content: [{
|
|
1066
|
+
type: "text",
|
|
1067
|
+
text: receipt.trim() + "\n\n" + (summary.approved > 0 ? "\u{1F389} Nice work! Your earnings are ready to claim." : "Keep trying - quality answers get approved!")
|
|
1068
|
+
}]
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
// ────────────────────────────────────────────────────────
|
|
1024
1072
|
// ANSWER FLOW (Interactive Q&A Session)
|
|
1025
1073
|
// ────────────────────────────────────────────────────────
|
|
1026
1074
|
case "ping_answer_flow": {
|
|
@@ -1040,15 +1088,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1040
1088
|
const { limit = 50 } = args || {};
|
|
1041
1089
|
const data = await apiRequest(`/questions?limit=${limit}`);
|
|
1042
1090
|
if (data.questions.length === 0) {
|
|
1091
|
+
const noQuestionsMessage = `
|
|
1092
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
1093
|
+
\u2502 \u{1F4ED} No questions available \u2502
|
|
1094
|
+
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
1095
|
+
\u2502 All caught up! Check back soon \u2502
|
|
1096
|
+
\u2502 for new earning opportunities. \u2502
|
|
1097
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
1098
|
+
|
|
1099
|
+
\u{1F4A1} Tip: Questions are added throughout the day. Try again later!`;
|
|
1043
1100
|
return {
|
|
1044
1101
|
content: [{
|
|
1045
1102
|
type: "text",
|
|
1046
|
-
text:
|
|
1047
|
-
success: true,
|
|
1048
|
-
questions: [],
|
|
1049
|
-
message: "No questions available right now.",
|
|
1050
|
-
hint: "New questions are added regularly. Check back soon to start earning!"
|
|
1051
|
-
}, null, 2)
|
|
1103
|
+
text: noQuestionsMessage.trim()
|
|
1052
1104
|
}]
|
|
1053
1105
|
};
|
|
1054
1106
|
}
|
|
@@ -1062,41 +1114,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1062
1114
|
suggestedAnswers: suggestions
|
|
1063
1115
|
};
|
|
1064
1116
|
});
|
|
1117
|
+
const summaryCard = `
|
|
1118
|
+
\u{1F4EC} ${questionsWithSuggestions.length} questions available \xB7 Earn up to ${data.totalPotentialEarnings}
|
|
1119
|
+
|
|
1120
|
+
Ready to start answering!`;
|
|
1121
|
+
const flowData = {
|
|
1122
|
+
_meta: "INTERNAL: Do not display this JSON to user. Use AskUserQuestion to present questions.",
|
|
1123
|
+
questions: questionsWithSuggestions,
|
|
1124
|
+
instructions: "Present questions in batches of 4 using AskUserQuestion. Use ping_validate_batch first, then ping_submit_batch after user answers. Auto-claim at end."
|
|
1125
|
+
};
|
|
1065
1126
|
return {
|
|
1066
1127
|
content: [{
|
|
1067
1128
|
type: "text",
|
|
1068
|
-
text: JSON.stringify(
|
|
1069
|
-
success: true,
|
|
1070
|
-
flow: "answer_session",
|
|
1071
|
-
handle: `@${auth.handle}`,
|
|
1072
|
-
// Summary stats for initial presentation
|
|
1073
|
-
summary: {
|
|
1074
|
-
questionCount: questionsWithSuggestions.length,
|
|
1075
|
-
totalPotentialEarnings: data.totalPotentialEarnings,
|
|
1076
|
-
totalAnswersToday: data.totalAnswersToday,
|
|
1077
|
-
oneLiner: `\u{1F4EC} ${questionsWithSuggestions.length} questions available \xB7 Earn up to ${data.totalPotentialEarnings} \xB7 ${data.totalAnswersToday} answers submitted today`
|
|
1078
|
-
},
|
|
1079
|
-
// Full question data for immediate presentation
|
|
1080
|
-
questions: questionsWithSuggestions,
|
|
1081
|
-
instructions: {
|
|
1082
|
-
overview: "This is a continuous batch validation workflow. Fetch 50 questions upfront, then validate+present in batches of 4 until all questions are exhausted.",
|
|
1083
|
-
step1_startImmediately: "Show the oneLiner summary briefly, then IMMEDIATELY start the batch loop. Do NOT ask for confirmation - user already chose to answer questions.",
|
|
1084
|
-
batchLoop: {
|
|
1085
|
-
step1_selectNext: "Take the next 4 question IDs from the questions array (questions you haven't shown yet).",
|
|
1086
|
-
step2_validate: "Call ping_validate_batch with those 4 question IDs to check availability.",
|
|
1087
|
-
step3_handleExhausted: "If some questions are exhausted, use only the available ones returned (might be 0-4 questions).",
|
|
1088
|
-
step4_skip: "If 0 questions available in this batch, skip to next 4 question IDs and validate again.",
|
|
1089
|
-
step5_present: "Present available questions using AskUserQuestion (1-4 questions). Format: question text with reward in header, options are ONLY suggested answers.",
|
|
1090
|
-
step6_collect: "User answers the questions.",
|
|
1091
|
-
step7_submit: "Submit answers IN PARALLEL using multiple ping_submit_answer calls. preApproved=true for suggested answers, preApproved=false for custom.",
|
|
1092
|
-
step8_handleErrors: 'If some submissions fail with "question_exhausted" error, log them but continue.',
|
|
1093
|
-
step9_repeat: "If more questions remain in the original questions array, repeat from step1_selectNext. Otherwise, proceed to finalClaim."
|
|
1094
|
-
},
|
|
1095
|
-
finalClaim: "AFTER ALL batches complete (no more questions left to validate), call ping_claim_reward ONCE automatically.",
|
|
1096
|
-
claimRetry: "If ping_claim_reward fails, wait 2 seconds and retry ONCE. If it fails again, tell user earnings are saved.",
|
|
1097
|
-
completionMessage: "Show: total questions answered, total earned, any exhausted questions encountered, transaction hash, and basescan link."
|
|
1098
|
-
}
|
|
1099
|
-
}, null, 2)
|
|
1129
|
+
text: summaryCard.trim() + "\n\n<!-- FLOW_DATA:" + JSON.stringify(flowData) + " -->"
|
|
1100
1130
|
}]
|
|
1101
1131
|
};
|
|
1102
1132
|
}
|
|
@@ -1226,17 +1256,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1226
1256
|
// ────────────────────────────────────────────────────────
|
|
1227
1257
|
case "ping_stats": {
|
|
1228
1258
|
const data = await apiRequest("/stats");
|
|
1259
|
+
const statsLine = `\u{1F4CA} ${data.questions.active} questions \xB7 ${data.responses.today} answers today \xB7 ${data.rewards.claimedToday} claimed`;
|
|
1229
1260
|
return {
|
|
1230
1261
|
content: [{
|
|
1231
1262
|
type: "text",
|
|
1232
|
-
text:
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
}
|
|
1239
|
-
}, null, 2)
|
|
1263
|
+
text: statsLine + `
|
|
1264
|
+
<!-- STATS:${JSON.stringify({
|
|
1265
|
+
questionsAvailable: data.questions.active,
|
|
1266
|
+
answersToday: data.responses.today,
|
|
1267
|
+
rewardsClaimedToday: data.rewards.claimedToday
|
|
1268
|
+
})} -->`
|
|
1240
1269
|
}]
|
|
1241
1270
|
};
|
|
1242
1271
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -813,10 +813,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
813
813
|
{
|
|
814
814
|
name: 'ping_submit_answer',
|
|
815
815
|
description:
|
|
816
|
-
'Submit
|
|
817
|
-
'
|
|
818
|
-
'Set preApproved=true when the user selects a suggested answer (skips AI review for instant approval). ' +
|
|
819
|
-
'Requires GitHub login (ping_login) or wallet address.',
|
|
816
|
+
'Submit a SINGLE answer. DEPRECATED: Use ping_submit_batch instead for better UX. ' +
|
|
817
|
+
'Only use this if you need to submit exactly one answer.',
|
|
820
818
|
inputSchema: {
|
|
821
819
|
type: 'object',
|
|
822
820
|
properties: {
|
|
@@ -836,17 +834,40 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
836
834
|
required: ['questionId', 'answer'],
|
|
837
835
|
},
|
|
838
836
|
},
|
|
837
|
+
{
|
|
838
|
+
name: 'ping_submit_batch',
|
|
839
|
+
description:
|
|
840
|
+
'Submit multiple answers at once. ALWAYS use this instead of ping_submit_answer. ' +
|
|
841
|
+
'Returns a fun receipt-style summary. Much cleaner UX than individual submissions.',
|
|
842
|
+
inputSchema: {
|
|
843
|
+
type: 'object',
|
|
844
|
+
properties: {
|
|
845
|
+
answers: {
|
|
846
|
+
type: 'array',
|
|
847
|
+
items: {
|
|
848
|
+
type: 'object',
|
|
849
|
+
properties: {
|
|
850
|
+
questionId: { type: 'string' },
|
|
851
|
+
answer: { type: 'string' },
|
|
852
|
+
preApproved: { type: 'boolean' },
|
|
853
|
+
},
|
|
854
|
+
required: ['questionId', 'answer'],
|
|
855
|
+
},
|
|
856
|
+
description: 'Array of answers to submit',
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
required: ['answers'],
|
|
860
|
+
},
|
|
861
|
+
},
|
|
839
862
|
{
|
|
840
863
|
name: 'ping_answer_flow',
|
|
841
864
|
description:
|
|
842
865
|
'Start an interactive Q&A session to earn money answering Ping questions. ' +
|
|
843
866
|
'TRIGGERS: "ping", "/ping", "answer questions", "earn money", "make money with ping", "start earning". ' +
|
|
844
867
|
'Returns questions with suggested answers. ' +
|
|
845
|
-
'CRITICAL:
|
|
846
|
-
'
|
|
847
|
-
'
|
|
848
|
-
'Submit answers IN PARALLEL for each batch. ' +
|
|
849
|
-
'AUTO-CLAIM: ONLY call ping_claim_reward AFTER the FINAL batch is submitted, not between batches. ' +
|
|
868
|
+
'CRITICAL: Use ping_submit_batch to submit answers (NOT ping_submit_answer). ' +
|
|
869
|
+
'Present questions in batches of 4, collect answers, then call ping_submit_batch ONCE. ' +
|
|
870
|
+
'AUTO-CLAIM: Call ping_claim_reward AFTER all batches submitted. ' +
|
|
850
871
|
'Requires GitHub login first (ping_login).',
|
|
851
872
|
inputSchema: {
|
|
852
873
|
type: 'object',
|
|
@@ -1135,11 +1156,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1135
1156
|
return {
|
|
1136
1157
|
content: [{
|
|
1137
1158
|
type: 'text',
|
|
1138
|
-
text:
|
|
1139
|
-
loggedIn: false,
|
|
1140
|
-
message: "You're not logged in to Ping.",
|
|
1141
|
-
hint: 'Use ping_login to connect with GitHub.',
|
|
1142
|
-
}, null, 2),
|
|
1159
|
+
text: '❌ Not logged in. Use ping_login to connect with GitHub.',
|
|
1143
1160
|
}],
|
|
1144
1161
|
};
|
|
1145
1162
|
}
|
|
@@ -1164,18 +1181,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1164
1181
|
return {
|
|
1165
1182
|
content: [{
|
|
1166
1183
|
type: 'text',
|
|
1167
|
-
text:
|
|
1168
|
-
loggedIn: true,
|
|
1169
|
-
handle: userData.handle,
|
|
1170
|
-
authProvider: userData.authProvider,
|
|
1171
|
-
walletAddress: userData.walletAddress || 'Not set yet',
|
|
1172
|
-
balance: {
|
|
1173
|
-
available: userData.availableBalance,
|
|
1174
|
-
pending: userData.pendingBalance,
|
|
1175
|
-
claimed: userData.claimedBalance,
|
|
1176
|
-
},
|
|
1177
|
-
message: `Logged in as ${userData.handle}`,
|
|
1178
|
-
}, null, 2),
|
|
1184
|
+
text: `✅ Logged in as @${userData.handle}`,
|
|
1179
1185
|
}],
|
|
1180
1186
|
};
|
|
1181
1187
|
} catch (err) {
|
|
@@ -1183,13 +1189,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1183
1189
|
return {
|
|
1184
1190
|
content: [{
|
|
1185
1191
|
type: 'text',
|
|
1186
|
-
text:
|
|
1187
|
-
loggedIn: true,
|
|
1188
|
-
handle: `@${auth.handle}`,
|
|
1189
|
-
authProvider: 'github',
|
|
1190
|
-
message: `Logged in as @${auth.handle}`,
|
|
1191
|
-
note: 'Could not fetch balance (API may be unavailable)',
|
|
1192
|
-
}, null, 2),
|
|
1192
|
+
text: `✅ Logged in as @${auth.handle} (offline mode)`,
|
|
1193
1193
|
}],
|
|
1194
1194
|
};
|
|
1195
1195
|
}
|
|
@@ -1199,13 +1199,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1199
1199
|
return {
|
|
1200
1200
|
content: [{
|
|
1201
1201
|
type: 'text',
|
|
1202
|
-
text:
|
|
1203
|
-
loggedIn: false,
|
|
1204
|
-
legacyMode: true,
|
|
1205
|
-
walletAddress: wallet,
|
|
1206
|
-
message: 'Using legacy wallet-only mode (not logged in with GitHub).',
|
|
1207
|
-
hint: 'Use ping_login to connect with GitHub for the full experience.',
|
|
1208
|
-
}, null, 2),
|
|
1202
|
+
text: `⚠️ Legacy mode: wallet ${wallet?.slice(0, 10)}... (Use ping_login for full features)`,
|
|
1209
1203
|
}],
|
|
1210
1204
|
};
|
|
1211
1205
|
}
|
|
@@ -1456,6 +1450,95 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1456
1450
|
};
|
|
1457
1451
|
}
|
|
1458
1452
|
|
|
1453
|
+
// ────────────────────────────────────────────────────────
|
|
1454
|
+
// BATCH SUBMIT (Submit multiple answers at once)
|
|
1455
|
+
// ────────────────────────────────────────────────────────
|
|
1456
|
+
case 'ping_submit_batch': {
|
|
1457
|
+
const auth = getAuth();
|
|
1458
|
+
|
|
1459
|
+
if (!auth) {
|
|
1460
|
+
return {
|
|
1461
|
+
content: [{
|
|
1462
|
+
type: 'text',
|
|
1463
|
+
text: JSON.stringify({
|
|
1464
|
+
success: false,
|
|
1465
|
+
error: 'Not logged in.',
|
|
1466
|
+
hint: 'Use ping_login to connect your GitHub account first.',
|
|
1467
|
+
}, null, 2),
|
|
1468
|
+
}],
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
const { answers } = (args || {}) as {
|
|
1473
|
+
answers: Array<{
|
|
1474
|
+
questionId: string;
|
|
1475
|
+
answer: string;
|
|
1476
|
+
preApproved?: boolean;
|
|
1477
|
+
}>;
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
if (!answers || answers.length === 0) {
|
|
1481
|
+
return {
|
|
1482
|
+
content: [{
|
|
1483
|
+
type: 'text',
|
|
1484
|
+
text: JSON.stringify({
|
|
1485
|
+
success: false,
|
|
1486
|
+
error: 'No answers provided.',
|
|
1487
|
+
hint: 'Provide an array of answers with questionId and answer fields.',
|
|
1488
|
+
}, null, 2),
|
|
1489
|
+
}],
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// Call the batch endpoint
|
|
1494
|
+
const data = await apiRequest<{
|
|
1495
|
+
success: boolean;
|
|
1496
|
+
summary: {
|
|
1497
|
+
submitted: number;
|
|
1498
|
+
approved: number;
|
|
1499
|
+
rejected: number;
|
|
1500
|
+
errors: number;
|
|
1501
|
+
totalEarned: string;
|
|
1502
|
+
totalEarnedCents: number;
|
|
1503
|
+
pendingBalance: string;
|
|
1504
|
+
};
|
|
1505
|
+
results: Array<{
|
|
1506
|
+
questionId: string;
|
|
1507
|
+
status: string;
|
|
1508
|
+
earned?: string;
|
|
1509
|
+
error?: string;
|
|
1510
|
+
}>;
|
|
1511
|
+
}>('/answers/batch', {
|
|
1512
|
+
method: 'POST',
|
|
1513
|
+
body: JSON.stringify({ answers }),
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
// Build a fun receipt-style ASCII art response
|
|
1517
|
+
const { summary } = data;
|
|
1518
|
+
const checkmark = summary.approved > 0 ? '✅' : '⬜';
|
|
1519
|
+
const statusLine = summary.errors > 0
|
|
1520
|
+
? `${summary.approved}/${summary.submitted} approved, ${summary.errors} error(s)`
|
|
1521
|
+
: `${summary.approved}/${summary.submitted} approved`;
|
|
1522
|
+
|
|
1523
|
+
const receipt = `
|
|
1524
|
+
┌─────────────────────────────┐
|
|
1525
|
+
│ 🧾 PING RECEIPT │
|
|
1526
|
+
├─────────────────────────────┤
|
|
1527
|
+
│ ${checkmark} ${statusLine.padEnd(24)}│
|
|
1528
|
+
│ 💰 ${summary.totalEarned.padEnd(24)}│
|
|
1529
|
+
│ 📊 Balance: ${summary.pendingBalance.padEnd(14)}│
|
|
1530
|
+
└─────────────────────────────┘`;
|
|
1531
|
+
|
|
1532
|
+
return {
|
|
1533
|
+
content: [{
|
|
1534
|
+
type: 'text',
|
|
1535
|
+
text: receipt.trim() + '\n\n' + (summary.approved > 0
|
|
1536
|
+
? '🎉 Nice work! Your earnings are ready to claim.'
|
|
1537
|
+
: 'Keep trying - quality answers get approved!'),
|
|
1538
|
+
}],
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1459
1542
|
// ────────────────────────────────────────────────────────
|
|
1460
1543
|
// ANSWER FLOW (Interactive Q&A Session)
|
|
1461
1544
|
// ────────────────────────────────────────────────────────
|
|
@@ -1494,15 +1577,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1494
1577
|
}>(`/questions?limit=${limit}`);
|
|
1495
1578
|
|
|
1496
1579
|
if (data.questions.length === 0) {
|
|
1580
|
+
const noQuestionsMessage = `
|
|
1581
|
+
┌─────────────────────────────────────┐
|
|
1582
|
+
│ 📭 No questions available │
|
|
1583
|
+
├─────────────────────────────────────┤
|
|
1584
|
+
│ All caught up! Check back soon │
|
|
1585
|
+
│ for new earning opportunities. │
|
|
1586
|
+
└─────────────────────────────────────┘
|
|
1587
|
+
|
|
1588
|
+
💡 Tip: Questions are added throughout the day. Try again later!`;
|
|
1589
|
+
|
|
1497
1590
|
return {
|
|
1498
1591
|
content: [{
|
|
1499
1592
|
type: 'text',
|
|
1500
|
-
text:
|
|
1501
|
-
success: true,
|
|
1502
|
-
questions: [],
|
|
1503
|
-
message: 'No questions available right now.',
|
|
1504
|
-
hint: 'New questions are added regularly. Check back soon to start earning!',
|
|
1505
|
-
}, null, 2),
|
|
1593
|
+
text: noQuestionsMessage.trim(),
|
|
1506
1594
|
}],
|
|
1507
1595
|
};
|
|
1508
1596
|
}
|
|
@@ -1520,41 +1608,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1520
1608
|
};
|
|
1521
1609
|
});
|
|
1522
1610
|
|
|
1611
|
+
// Build a nice human-readable summary
|
|
1612
|
+
const summaryCard = `
|
|
1613
|
+
📬 ${questionsWithSuggestions.length} questions available · Earn up to ${data.totalPotentialEarnings}
|
|
1614
|
+
|
|
1615
|
+
Ready to start answering!`;
|
|
1616
|
+
|
|
1617
|
+
// Compact data for Claude to process (not for user display)
|
|
1618
|
+
const flowData = {
|
|
1619
|
+
_meta: 'INTERNAL: Do not display this JSON to user. Use AskUserQuestion to present questions.',
|
|
1620
|
+
questions: questionsWithSuggestions,
|
|
1621
|
+
instructions: 'Present questions in batches of 4 using AskUserQuestion. Use ping_validate_batch first, then ping_submit_batch after user answers. Auto-claim at end.',
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1523
1624
|
return {
|
|
1524
1625
|
content: [{
|
|
1525
1626
|
type: 'text',
|
|
1526
|
-
text: JSON.stringify(
|
|
1527
|
-
success: true,
|
|
1528
|
-
flow: 'answer_session',
|
|
1529
|
-
handle: `@${auth.handle}`,
|
|
1530
|
-
// Summary stats for initial presentation
|
|
1531
|
-
summary: {
|
|
1532
|
-
questionCount: questionsWithSuggestions.length,
|
|
1533
|
-
totalPotentialEarnings: data.totalPotentialEarnings,
|
|
1534
|
-
totalAnswersToday: data.totalAnswersToday,
|
|
1535
|
-
oneLiner: `📬 ${questionsWithSuggestions.length} questions available · Earn up to ${data.totalPotentialEarnings} · ${data.totalAnswersToday} answers submitted today`,
|
|
1536
|
-
},
|
|
1537
|
-
// Full question data for immediate presentation
|
|
1538
|
-
questions: questionsWithSuggestions,
|
|
1539
|
-
instructions: {
|
|
1540
|
-
overview: 'This is a continuous batch validation workflow. Fetch 50 questions upfront, then validate+present in batches of 4 until all questions are exhausted.',
|
|
1541
|
-
step1_startImmediately: 'Show the oneLiner summary briefly, then IMMEDIATELY start the batch loop. Do NOT ask for confirmation - user already chose to answer questions.',
|
|
1542
|
-
batchLoop: {
|
|
1543
|
-
step1_selectNext: 'Take the next 4 question IDs from the questions array (questions you haven\'t shown yet).',
|
|
1544
|
-
step2_validate: 'Call ping_validate_batch with those 4 question IDs to check availability.',
|
|
1545
|
-
step3_handleExhausted: 'If some questions are exhausted, use only the available ones returned (might be 0-4 questions).',
|
|
1546
|
-
step4_skip: 'If 0 questions available in this batch, skip to next 4 question IDs and validate again.',
|
|
1547
|
-
step5_present: 'Present available questions using AskUserQuestion (1-4 questions). Format: question text with reward in header, options are ONLY suggested answers.',
|
|
1548
|
-
step6_collect: 'User answers the questions.',
|
|
1549
|
-
step7_submit: 'Submit answers IN PARALLEL using multiple ping_submit_answer calls. preApproved=true for suggested answers, preApproved=false for custom.',
|
|
1550
|
-
step8_handleErrors: 'If some submissions fail with "question_exhausted" error, log them but continue.',
|
|
1551
|
-
step9_repeat: 'If more questions remain in the original questions array, repeat from step1_selectNext. Otherwise, proceed to finalClaim.',
|
|
1552
|
-
},
|
|
1553
|
-
finalClaim: 'AFTER ALL batches complete (no more questions left to validate), call ping_claim_reward ONCE automatically.',
|
|
1554
|
-
claimRetry: 'If ping_claim_reward fails, wait 2 seconds and retry ONCE. If it fails again, tell user earnings are saved.',
|
|
1555
|
-
completionMessage: 'Show: total questions answered, total earned, any exhausted questions encountered, transaction hash, and basescan link.',
|
|
1556
|
-
},
|
|
1557
|
-
}, null, 2),
|
|
1627
|
+
text: summaryCard.trim() + '\n\n<!-- FLOW_DATA:' + JSON.stringify(flowData) + ' -->',
|
|
1558
1628
|
}],
|
|
1559
1629
|
};
|
|
1560
1630
|
}
|
|
@@ -1730,17 +1800,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1730
1800
|
rewards: { claimedToday: string; claimedTodayCents: number };
|
|
1731
1801
|
}>('/stats');
|
|
1732
1802
|
|
|
1803
|
+
// Human-readable summary with parseable data for banner
|
|
1804
|
+
const statsLine = `📊 ${data.questions.active} questions · ${data.responses.today} answers today · ${data.rewards.claimedToday} claimed`;
|
|
1805
|
+
|
|
1733
1806
|
return {
|
|
1734
1807
|
content: [{
|
|
1735
1808
|
type: 'text',
|
|
1736
|
-
text: JSON.stringify({
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
rewardsClaimedToday: data.rewards.claimedToday,
|
|
1742
|
-
},
|
|
1743
|
-
}, null, 2),
|
|
1809
|
+
text: statsLine + `\n<!-- STATS:${JSON.stringify({
|
|
1810
|
+
questionsAvailable: data.questions.active,
|
|
1811
|
+
answersToday: data.responses.today,
|
|
1812
|
+
rewardsClaimedToday: data.rewards.claimedToday,
|
|
1813
|
+
})} -->`,
|
|
1744
1814
|
}],
|
|
1745
1815
|
};
|
|
1746
1816
|
}
|