gfclaw 2.0.0 → 2.1.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/bin/cli.js +427 -2
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -651,8 +651,18 @@ async function createNewAgent(rl, config, agentName) {
|
|
|
651
651
|
botToken: botToken,
|
|
652
652
|
groupPolicy: "allowlist",
|
|
653
653
|
streaming: "off",
|
|
654
|
+
commands: {
|
|
655
|
+
native: false,
|
|
656
|
+
nativeSkills: false,
|
|
657
|
+
},
|
|
658
|
+
customCommands: [
|
|
659
|
+
{ command: "newphoto", description: "Change my reference photo" },
|
|
660
|
+
{ command: "personality", description: "Change my personality" },
|
|
661
|
+
{ command: "status", description: "Show current setup" },
|
|
662
|
+
],
|
|
654
663
|
};
|
|
655
664
|
logSuccess(`Added Telegram account: ${accountName}`);
|
|
665
|
+
logSuccess("Configured Telegram command menu (gfclaw commands only)");
|
|
656
666
|
|
|
657
667
|
// --- Allow user ---
|
|
658
668
|
if (allowedUser) {
|
|
@@ -750,6 +760,408 @@ async function handleReinstall(rl) {
|
|
|
750
760
|
return true;
|
|
751
761
|
}
|
|
752
762
|
|
|
763
|
+
// ─────────────────────────────────────────────
|
|
764
|
+
// CLI Management Commands
|
|
765
|
+
// ─────────────────────────────────────────────
|
|
766
|
+
|
|
767
|
+
// Find the gfclaw agent entry in config
|
|
768
|
+
function findGfclawAgent(config) {
|
|
769
|
+
const agents = (config.agents && config.agents.list) || [];
|
|
770
|
+
// Try exact match first
|
|
771
|
+
let agent = agents.find((a) => a.id === "gfclaw");
|
|
772
|
+
if (agent) return agent;
|
|
773
|
+
// Try workspace path containing 'gfclaw'
|
|
774
|
+
agent = agents.find((a) => a.workspace && a.workspace.includes("gfclaw"));
|
|
775
|
+
if (agent) return agent;
|
|
776
|
+
// Fallback: first non-main agent
|
|
777
|
+
agent = agents.find((a) => a.id !== "main");
|
|
778
|
+
return agent || null;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Find the gfclaw telegram account name from bindings
|
|
782
|
+
function findGfclawAccountName(config, agentId) {
|
|
783
|
+
const bindings = config.bindings || [];
|
|
784
|
+
const binding = bindings.find(
|
|
785
|
+
(b) => b.agentId === agentId && b.match && b.match.channel === "telegram"
|
|
786
|
+
);
|
|
787
|
+
return binding ? binding.match.accountId : null;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// --help
|
|
791
|
+
function showHelp() {
|
|
792
|
+
console.log(`
|
|
793
|
+
${c("magenta", "GFClaw")} - Selfie Skill for OpenClaw
|
|
794
|
+
|
|
795
|
+
${c("bright", "Usage:")}
|
|
796
|
+
npx gfclaw Install GFClaw (first-time setup)
|
|
797
|
+
npx gfclaw --help Show this help message
|
|
798
|
+
npx gfclaw --status Show current GFClaw setup
|
|
799
|
+
npx gfclaw --reconfigure Change photo, personality, or API key
|
|
800
|
+
npx gfclaw --repair Change allowed Telegram user
|
|
801
|
+
|
|
802
|
+
${c("bright", "Aliases:")}
|
|
803
|
+
--help, -h
|
|
804
|
+
--status, -s
|
|
805
|
+
--reconfigure, -c
|
|
806
|
+
--repair, -r
|
|
807
|
+
`);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// --status
|
|
811
|
+
async function showStatus() {
|
|
812
|
+
if (!fs.existsSync(OPENCLAW_CONFIG)) {
|
|
813
|
+
logError("GFClaw is not installed (no OpenClaw config found)");
|
|
814
|
+
logInfo(`Expected: ${OPENCLAW_CONFIG}`);
|
|
815
|
+
process.exit(1);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const config = readJsonFile(OPENCLAW_CONFIG);
|
|
819
|
+
if (!config) {
|
|
820
|
+
logError("Failed to parse OpenClaw config");
|
|
821
|
+
process.exit(1);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const agent = findGfclawAgent(config);
|
|
825
|
+
if (!agent) {
|
|
826
|
+
logError("No GFClaw agent found in config");
|
|
827
|
+
logInfo("Run 'npx gfclaw' to install.");
|
|
828
|
+
process.exit(1);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const workspace = agent.workspace || path.join(OPENCLAW_DIR, `workspace-${agent.id}`);
|
|
832
|
+
const accountName = findGfclawAccountName(config, agent.id);
|
|
833
|
+
|
|
834
|
+
// Read workspace files
|
|
835
|
+
let onboarding = null;
|
|
836
|
+
const onboardingPath = path.join(workspace, "onboarding.json");
|
|
837
|
+
if (fs.existsSync(onboardingPath)) {
|
|
838
|
+
try { onboarding = JSON.parse(fs.readFileSync(onboardingPath, "utf8")); } catch {}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
let personality = null;
|
|
842
|
+
const personalityPath = path.join(workspace, "personality.txt");
|
|
843
|
+
if (fs.existsSync(personalityPath)) {
|
|
844
|
+
personality = fs.readFileSync(personalityPath, "utf8").trim();
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const hasCustomPhoto = fs.existsSync(path.join(workspace, "my-reference.png"));
|
|
848
|
+
|
|
849
|
+
// Get bot info
|
|
850
|
+
let botToken = null;
|
|
851
|
+
if (accountName && config.channels && config.channels.telegram && config.channels.telegram.accounts) {
|
|
852
|
+
const acct = config.channels.telegram.accounts[accountName];
|
|
853
|
+
if (acct) botToken = acct.botToken;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Get allowed users
|
|
857
|
+
const allowFrom = (config.channels && config.channels.telegram && config.channels.telegram.allowFrom) || [];
|
|
858
|
+
|
|
859
|
+
// Check API key
|
|
860
|
+
let hasApiKey = false;
|
|
861
|
+
if (fs.existsSync(OPENCLAW_ENV)) {
|
|
862
|
+
const envContent = fs.readFileSync(OPENCLAW_ENV, "utf8");
|
|
863
|
+
hasApiKey = /^GEMINI_API_KEY=.+$/m.test(envContent);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Print status
|
|
867
|
+
console.log(`
|
|
868
|
+
${c("bright", "GFClaw Status")}
|
|
869
|
+
${c("cyan", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
|
|
870
|
+
|
|
871
|
+
${c("cyan", "Agent:")} ${c("bright", agent.id)}
|
|
872
|
+
${c("cyan", "Workspace:")} ${workspace}
|
|
873
|
+
`);
|
|
874
|
+
|
|
875
|
+
// Onboarding
|
|
876
|
+
if (onboarding && onboarding.completed) {
|
|
877
|
+
const date = onboarding.completedAt ? ` (${onboarding.completedAt.split("T")[0]})` : "";
|
|
878
|
+
logSuccess(`Onboarding: Completed${date}`);
|
|
879
|
+
} else {
|
|
880
|
+
logError("Onboarding: Not completed");
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Photo
|
|
884
|
+
if (hasCustomPhoto) {
|
|
885
|
+
logSuccess("Custom Photo: Set");
|
|
886
|
+
} else {
|
|
887
|
+
logWarn("Custom Photo: Using default");
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Personality
|
|
891
|
+
if (personality) {
|
|
892
|
+
logSuccess(`Personality: \"${personality}\"`);
|
|
893
|
+
} else {
|
|
894
|
+
logWarn("Personality: Not set (using default)");
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
log("");
|
|
898
|
+
|
|
899
|
+
// Telegram
|
|
900
|
+
if (botToken) {
|
|
901
|
+
const botId = botToken.split(":")[0];
|
|
902
|
+
logInfo(`Telegram Bot: token ${botId}:***`);
|
|
903
|
+
}
|
|
904
|
+
if (allowFrom.length > 0) {
|
|
905
|
+
logInfo(`Allowed Users: ${allowFrom.join(", ")}`);
|
|
906
|
+
} else {
|
|
907
|
+
logWarn("Allowed Users: None configured");
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// API Key
|
|
911
|
+
if (hasApiKey) {
|
|
912
|
+
logSuccess("Gemini API Key: Configured");
|
|
913
|
+
} else {
|
|
914
|
+
logError("Gemini API Key: Not found");
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
log("");
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// --reconfigure
|
|
921
|
+
async function runReconfigure() {
|
|
922
|
+
if (!fs.existsSync(OPENCLAW_CONFIG)) {
|
|
923
|
+
logError("GFClaw is not installed (no OpenClaw config found)");
|
|
924
|
+
process.exit(1);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const config = readJsonFile(OPENCLAW_CONFIG);
|
|
928
|
+
if (!config) {
|
|
929
|
+
logError("Failed to parse OpenClaw config");
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const agent = findGfclawAgent(config);
|
|
934
|
+
if (!agent) {
|
|
935
|
+
logError("No GFClaw agent found in config");
|
|
936
|
+
logInfo("Run 'npx gfclaw' to install.");
|
|
937
|
+
process.exit(1);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const workspace = agent.workspace || path.join(OPENCLAW_DIR, `workspace-${agent.id}`);
|
|
941
|
+
|
|
942
|
+
console.log(`
|
|
943
|
+
${c("bright", "GFClaw Reconfigure")}
|
|
944
|
+
${c("cyan", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
|
|
945
|
+
|
|
946
|
+
What would you like to change?
|
|
947
|
+
${c("bright", "1.")} Reference photo
|
|
948
|
+
${c("bright", "2.")} Personality
|
|
949
|
+
${c("bright", "3.")} Gemini API key
|
|
950
|
+
${c("bright", "4.")} All of the above
|
|
951
|
+
`);
|
|
952
|
+
|
|
953
|
+
const rl = createPrompt();
|
|
954
|
+
|
|
955
|
+
try {
|
|
956
|
+
const choice = await ask(rl, "Choose (1-4): ");
|
|
957
|
+
log("");
|
|
958
|
+
|
|
959
|
+
if (choice === "1" || choice === "4") {
|
|
960
|
+
await reconfigurePhoto(rl, workspace);
|
|
961
|
+
}
|
|
962
|
+
if (choice === "2" || choice === "4") {
|
|
963
|
+
await reconfigurePersonality(rl, workspace);
|
|
964
|
+
}
|
|
965
|
+
if (choice === "3" || choice === "4") {
|
|
966
|
+
await reconfigureApiKey(rl);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (!["1", "2", "3", "4"].includes(choice)) {
|
|
970
|
+
logError("Invalid choice. Please enter 1, 2, 3, or 4.");
|
|
971
|
+
rl.close();
|
|
972
|
+
process.exit(1);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
log("");
|
|
976
|
+
logSuccess("Changes saved!");
|
|
977
|
+
logInfo("Restart OpenClaw for changes to take effect:");
|
|
978
|
+
log(` ${c("bright", "systemctl --user restart openclaw-gateway.service")}`);
|
|
979
|
+
log("");
|
|
980
|
+
|
|
981
|
+
rl.close();
|
|
982
|
+
} catch (error) {
|
|
983
|
+
logError(`Reconfigure failed: ${error.message}`);
|
|
984
|
+
rl.close();
|
|
985
|
+
process.exit(1);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
async function reconfigurePhoto(rl, workspace) {
|
|
990
|
+
logStep("Photo", "Changing reference photo...");
|
|
991
|
+
const photoPath = await ask(rl, "Enter path to new reference photo: ");
|
|
992
|
+
|
|
993
|
+
if (!photoPath) {
|
|
994
|
+
logWarn("Skipped (no path entered)");
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Expand ~ to home dir
|
|
999
|
+
const resolvedPath = photoPath.replace(/^~/, HOME);
|
|
1000
|
+
|
|
1001
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
1002
|
+
logError(`File not found: ${resolvedPath}`);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const ext = path.extname(resolvedPath).toLowerCase();
|
|
1007
|
+
if (![".png", ".jpg", ".jpeg", ".webp"].includes(ext)) {
|
|
1008
|
+
logWarn(`Unexpected file type: ${ext}. Expected .png, .jpg, .jpeg, or .webp`);
|
|
1009
|
+
const proceed = await ask(rl, "Continue anyway? (y/N): ");
|
|
1010
|
+
if (proceed.toLowerCase() !== "y") return;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const destPath = path.join(workspace, "my-reference.png");
|
|
1014
|
+
fs.copyFileSync(resolvedPath, destPath);
|
|
1015
|
+
logSuccess(`Reference photo updated: ${destPath}`);
|
|
1016
|
+
|
|
1017
|
+
// Update onboarding.json
|
|
1018
|
+
const onboardingPath = path.join(workspace, "onboarding.json");
|
|
1019
|
+
let onboarding = {};
|
|
1020
|
+
if (fs.existsSync(onboardingPath)) {
|
|
1021
|
+
try { onboarding = JSON.parse(fs.readFileSync(onboardingPath, "utf8")); } catch {}
|
|
1022
|
+
}
|
|
1023
|
+
onboarding.hasCustomImage = true;
|
|
1024
|
+
fs.writeFileSync(onboardingPath, JSON.stringify(onboarding, null, 2) + "\n");
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
async function reconfigurePersonality(rl, workspace) {
|
|
1028
|
+
logStep("Personality", "Changing personality...");
|
|
1029
|
+
|
|
1030
|
+
// Show current personality if exists
|
|
1031
|
+
const personalityPath = path.join(workspace, "personality.txt");
|
|
1032
|
+
if (fs.existsSync(personalityPath)) {
|
|
1033
|
+
const current = fs.readFileSync(personalityPath, "utf8").trim();
|
|
1034
|
+
logInfo(`Current: \"${current}\"`);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const personality = await ask(rl, "Enter new personality description: ");
|
|
1038
|
+
|
|
1039
|
+
if (!personality) {
|
|
1040
|
+
logWarn("Skipped (no input)");
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
fs.writeFileSync(personalityPath, personality + "\n");
|
|
1045
|
+
logSuccess(`Personality updated: ${personalityPath}`);
|
|
1046
|
+
|
|
1047
|
+
// Update onboarding.json
|
|
1048
|
+
const onboardingPath = path.join(workspace, "onboarding.json");
|
|
1049
|
+
let onboarding = {};
|
|
1050
|
+
if (fs.existsSync(onboardingPath)) {
|
|
1051
|
+
try { onboarding = JSON.parse(fs.readFileSync(onboardingPath, "utf8")); } catch {}
|
|
1052
|
+
}
|
|
1053
|
+
onboarding.personality = personality;
|
|
1054
|
+
fs.writeFileSync(onboardingPath, JSON.stringify(onboarding, null, 2) + "\n");
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
async function reconfigureApiKey(rl) {
|
|
1058
|
+
logStep("API Key", "Changing Gemini API key...");
|
|
1059
|
+
|
|
1060
|
+
const geminiKey = await getGeminiApiKey(rl);
|
|
1061
|
+
if (!geminiKey) {
|
|
1062
|
+
logWarn("Skipped (no key entered)");
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Update .env file
|
|
1067
|
+
let envContent = "";
|
|
1068
|
+
if (fs.existsSync(OPENCLAW_ENV)) {
|
|
1069
|
+
envContent = fs.readFileSync(OPENCLAW_ENV, "utf8");
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (envContent.includes("GEMINI_API_KEY=")) {
|
|
1073
|
+
envContent = envContent.replace(/^GEMINI_API_KEY=.*$/m, `GEMINI_API_KEY=${geminiKey}`);
|
|
1074
|
+
} else {
|
|
1075
|
+
if (envContent && !envContent.endsWith("\n")) envContent += "\n";
|
|
1076
|
+
envContent += `GEMINI_API_KEY=${geminiKey}\n`;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
fs.writeFileSync(OPENCLAW_ENV, envContent);
|
|
1080
|
+
logSuccess(`API key saved to: ${OPENCLAW_ENV}`);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// --repair
|
|
1084
|
+
async function runRepair() {
|
|
1085
|
+
if (!fs.existsSync(OPENCLAW_CONFIG)) {
|
|
1086
|
+
logError("GFClaw is not installed (no OpenClaw config found)");
|
|
1087
|
+
process.exit(1);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const config = readJsonFile(OPENCLAW_CONFIG);
|
|
1091
|
+
if (!config) {
|
|
1092
|
+
logError("Failed to parse OpenClaw config");
|
|
1093
|
+
process.exit(1);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
console.log(`
|
|
1097
|
+
${c("bright", "GFClaw Repair")} — Change Allowed Telegram User
|
|
1098
|
+
${c("cyan", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
|
|
1099
|
+
`);
|
|
1100
|
+
|
|
1101
|
+
// Show current allowed users
|
|
1102
|
+
const allowFrom = (config.channels && config.channels.telegram && config.channels.telegram.allowFrom) || [];
|
|
1103
|
+
if (allowFrom.length > 0) {
|
|
1104
|
+
logInfo(`Current allowed users: ${allowFrom.join(", ")}`);
|
|
1105
|
+
} else {
|
|
1106
|
+
logWarn("No allowed users currently configured");
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
const rl = createPrompt();
|
|
1110
|
+
|
|
1111
|
+
try {
|
|
1112
|
+
log("");
|
|
1113
|
+
const newUser = await ask(rl, "Enter new Telegram chat ID (e.g. tg:123456789): ");
|
|
1114
|
+
|
|
1115
|
+
if (!newUser) {
|
|
1116
|
+
logWarn("No changes made.");
|
|
1117
|
+
rl.close();
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (!newUser.startsWith("tg:")) {
|
|
1122
|
+
logWarn(`Expected format \"tg:<chat_id>\". Got: \"${newUser}\"`);
|
|
1123
|
+
const proceed = await ask(rl, "Continue anyway? (y/N): ");
|
|
1124
|
+
if (proceed.toLowerCase() !== "y") {
|
|
1125
|
+
rl.close();
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
log("");
|
|
1131
|
+
const mode = await ask(rl, "Replace all existing users, or add this one? (replace/add): ");
|
|
1132
|
+
|
|
1133
|
+
if (!config.channels) config.channels = {};
|
|
1134
|
+
if (!config.channels.telegram) config.channels.telegram = {};
|
|
1135
|
+
|
|
1136
|
+
if (mode.toLowerCase() === "replace" || mode.toLowerCase() === "r") {
|
|
1137
|
+
config.channels.telegram.allowFrom = [newUser];
|
|
1138
|
+
logSuccess(`Replaced allowed users with: ${newUser}`);
|
|
1139
|
+
} else {
|
|
1140
|
+
if (!config.channels.telegram.allowFrom) config.channels.telegram.allowFrom = [];
|
|
1141
|
+
if (!config.channels.telegram.allowFrom.includes(newUser)) {
|
|
1142
|
+
config.channels.telegram.allowFrom.push(newUser);
|
|
1143
|
+
logSuccess(`Added allowed user: ${newUser}`);
|
|
1144
|
+
} else {
|
|
1145
|
+
logInfo(`User ${newUser} is already in the list`);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
writeJsonFile(OPENCLAW_CONFIG, config);
|
|
1150
|
+
logSuccess(`Config saved: ${OPENCLAW_CONFIG}`);
|
|
1151
|
+
|
|
1152
|
+
log("");
|
|
1153
|
+
logInfo("Restart OpenClaw for changes to take effect:");
|
|
1154
|
+
log(` ${c("bright", "systemctl --user restart openclaw-gateway.service")}`);
|
|
1155
|
+
log("");
|
|
1156
|
+
|
|
1157
|
+
rl.close();
|
|
1158
|
+
} catch (error) {
|
|
1159
|
+
logError(`Repair failed: ${error.message}`);
|
|
1160
|
+
rl.close();
|
|
1161
|
+
process.exit(1);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
753
1165
|
// Main function
|
|
754
1166
|
async function main() {
|
|
755
1167
|
const rl = createPrompt();
|
|
@@ -807,5 +1219,18 @@ async function main() {
|
|
|
807
1219
|
}
|
|
808
1220
|
}
|
|
809
1221
|
|
|
810
|
-
//
|
|
811
|
-
|
|
1222
|
+
// Parse CLI arguments and dispatch
|
|
1223
|
+
const args = process.argv.slice(2);
|
|
1224
|
+
const flag = args[0];
|
|
1225
|
+
|
|
1226
|
+
if (flag === "--help" || flag === "-h") {
|
|
1227
|
+
showHelp();
|
|
1228
|
+
} else if (flag === "--status" || flag === "-s") {
|
|
1229
|
+
showStatus();
|
|
1230
|
+
} else if (flag === "--reconfigure" || flag === "-c") {
|
|
1231
|
+
runReconfigure();
|
|
1232
|
+
} else if (flag === "--repair" || flag === "-r") {
|
|
1233
|
+
runRepair();
|
|
1234
|
+
} else {
|
|
1235
|
+
main();
|
|
1236
|
+
}
|