vigthoria-cli 1.0.2 → 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.
@@ -262,6 +262,22 @@ export class AgenticTools {
262
262
  riskLevel: 'medium',
263
263
  category: 'execute',
264
264
  },
265
+ {
266
+ name: 'repo',
267
+ description: 'Manage projects in Vigthoria Repository - push, pull, list, share, delete, or clone projects',
268
+ parameters: [
269
+ { name: 'action', description: 'Action: push, pull, list, status, share, delete, clone', required: true },
270
+ { name: 'project', description: 'Project name (for push/pull/status/share/delete)', required: false },
271
+ { name: 'visibility', description: 'Visibility: public or private (for push)', required: false },
272
+ { name: 'path', description: 'Directory path (for push) or target path (for clone)', required: false },
273
+ { name: 'username', description: 'Username to share with (for share action)', required: false },
274
+ { name: 'permission', description: 'Permission level: read, write, admin (for share action)', required: false },
275
+ ],
276
+ requiresPermission: true,
277
+ dangerous: false,
278
+ riskLevel: 'medium',
279
+ category: 'execute',
280
+ },
265
281
  ];
266
282
  }
267
283
 
@@ -376,6 +392,8 @@ export class AgenticTools {
376
392
  return this.glob(call.args);
377
393
  case 'git':
378
394
  return this.git(call.args);
395
+ case 'repo':
396
+ return this.repo(call.args);
379
397
  default:
380
398
  return this.createErrorResult(
381
399
  ToolErrorType.INVALID_ARGS,
@@ -850,6 +868,121 @@ export class AgenticTools {
850
868
  }
851
869
  }
852
870
 
871
+ /**
872
+ * Vigthoria Repository management tool
873
+ * Allows AI to push, pull, list, share, and manage projects in the Vigthoria Repository
874
+ */
875
+ private async repo(args: Record<string, string>): Promise<ToolResult> {
876
+ const action = args.action?.toLowerCase();
877
+ const project = args.project;
878
+ const visibility = args.visibility || 'private';
879
+ const targetPath = args.path || this.cwd;
880
+
881
+ try {
882
+ // Use vigthoria CLI for repo operations
883
+ let command: string;
884
+
885
+ switch (action) {
886
+ case 'push':
887
+ if (!project) {
888
+ return {
889
+ success: false,
890
+ error: 'Project name is required for push',
891
+ suggestion: 'Provide a project name, e.g., repo action=push project=my-project'
892
+ };
893
+ }
894
+ command = `vigthoria repo push "${project}" "${targetPath}" --visibility ${visibility}`;
895
+ break;
896
+
897
+ case 'pull':
898
+ if (!project) {
899
+ return {
900
+ success: false,
901
+ error: 'Project name is required for pull',
902
+ suggestion: 'Provide a project name, e.g., repo action=pull project=my-project'
903
+ };
904
+ }
905
+ command = `vigthoria repo pull "${project}"`;
906
+ break;
907
+
908
+ case 'list':
909
+ command = 'vigthoria repo list';
910
+ break;
911
+
912
+ case 'status':
913
+ if (!project) {
914
+ return {
915
+ success: false,
916
+ error: 'Project name is required for status',
917
+ suggestion: 'Provide a project name, e.g., repo action=status project=my-project'
918
+ };
919
+ }
920
+ command = `vigthoria repo status "${project}"`;
921
+ break;
922
+
923
+ case 'share':
924
+ if (!project || !args.username) {
925
+ return {
926
+ success: false,
927
+ error: 'Project name and username are required for share',
928
+ suggestion: 'Provide both, e.g., repo action=share project=my-project username=collaborator permission=read'
929
+ };
930
+ }
931
+ const permission = args.permission || 'read';
932
+ command = `vigthoria repo share "${project}" "${args.username}" --permission ${permission}`;
933
+ break;
934
+
935
+ case 'delete':
936
+ if (!project) {
937
+ return {
938
+ success: false,
939
+ error: 'Project name is required for delete',
940
+ suggestion: 'Provide a project name, e.g., repo action=delete project=my-project'
941
+ };
942
+ }
943
+ command = `vigthoria repo delete "${project}" --force`;
944
+ break;
945
+
946
+ case 'clone':
947
+ if (!project) {
948
+ return {
949
+ success: false,
950
+ error: 'Project name is required for clone',
951
+ suggestion: 'Provide a project name, e.g., repo action=clone project=my-project path=/path/to/target'
952
+ };
953
+ }
954
+ command = `vigthoria repo clone "${project}" "${targetPath}"`;
955
+ break;
956
+
957
+ default:
958
+ return {
959
+ success: false,
960
+ error: `Unknown repo action: ${action}`,
961
+ suggestion: 'Available actions: push, pull, list, status, share, delete, clone'
962
+ };
963
+ }
964
+
965
+ const output = execSync(command, {
966
+ cwd: this.cwd,
967
+ encoding: 'utf-8',
968
+ timeout: 120000, // 2 minute timeout for repo operations
969
+ env: { ...process.env, FORCE_COLOR: '0' } // Disable colors for clean output
970
+ });
971
+
972
+ return {
973
+ success: true,
974
+ output: output.trim(),
975
+ metadata: { action, project }
976
+ };
977
+ } catch (error: any) {
978
+ return {
979
+ success: false,
980
+ error: error.stderr || error.message,
981
+ suggestion: 'Make sure you are logged in with vigthoria login and have the required permissions.'
982
+ };
983
+ }
984
+ }
985
+
853
986
  private resolvePath(p: string): string {
854
987
  if (path.isAbsolute(p)) {
855
988
  return p;
@@ -859,18 +992,66 @@ export class AgenticTools {
859
992
 
860
993
  /**
861
994
  * Parse tool calls from AI response (Claude Code format)
995
+ * Enhanced to handle various AI output formats including malformed JSON
862
996
  */
863
997
  static parseToolCalls(text: string): ToolCall[] {
864
998
  const calls: ToolCall[] = [];
865
-
866
- // Match <tool_call>...</tool_call> blocks
867
- const toolCallRegex = /<tool_call>\s*(\{[\s\S]*?\})\s*<\/tool_call>/g;
868
999
  let match;
869
1000
 
1001
+ // Helper to fix common JSON issues from AI outputs
1002
+ const fixJson = (jsonStr: string): string => {
1003
+ return jsonStr
1004
+ .replace(/'/g, '"') // Replace single quotes with double
1005
+ .replace(/([{,]\s*)(\w+):/g, '$1"$2":') // Quote unquoted keys
1006
+ .replace(/:\s*'([^']*)'\s*([,}])/g, ': "$1"$2') // Quote values with single quotes
1007
+ .replace(/\n/g, '\\n') // Escape newlines
1008
+ .replace(/\r/g, '') // Remove carriage returns
1009
+ .replace(/\t/g, '\\t') // Escape tabs
1010
+ .replace(/,\s*}/g, '}') // Remove trailing commas in objects
1011
+ .replace(/,\s*]/g, ']'); // Remove trailing commas in arrays
1012
+ };
1013
+
1014
+ // Normalize tool name from various formats
1015
+ const normalizeToolName = (name: string): string => {
1016
+ const normalized = name
1017
+ .replace(/^__/, '') // Remove leading underscores
1018
+ .replace(/__$/, '') // Remove trailing underscores
1019
+ .replace(/^execute_/i, '') // Remove execute_ prefix
1020
+ .replace(/_execute$/i, '') // Remove _execute suffix
1021
+ .toLowerCase();
1022
+
1023
+ // Map common variations
1024
+ const toolMap: Record<string, string> = {
1025
+ 'bash': 'bash',
1026
+ 'shell': 'bash',
1027
+ 'run': 'bash',
1028
+ 'command': 'bash',
1029
+ 'list_dir': 'list_dir',
1030
+ 'list_directory': 'list_dir',
1031
+ 'ls': 'list_dir',
1032
+ 'dir': 'list_dir',
1033
+ 'read_file': 'read_file',
1034
+ 'readfile': 'read_file',
1035
+ 'read': 'read_file',
1036
+ 'write_file': 'write_file',
1037
+ 'writefile': 'write_file',
1038
+ 'write': 'write_file',
1039
+ 'edit_file': 'edit_file',
1040
+ 'editfile': 'edit_file',
1041
+ 'edit': 'edit_file',
1042
+ };
1043
+
1044
+ return toolMap[normalized] || normalized;
1045
+ };
1046
+
1047
+ // Match <tool_call>...</tool_call> blocks
1048
+ const toolCallRegex = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/g;
870
1049
  while ((match = toolCallRegex.exec(text)) !== null) {
871
1050
  try {
872
- const parsed = JSON.parse(match[1]);
1051
+ const fixed = fixJson(match[1]);
1052
+ const parsed = JSON.parse(fixed);
873
1053
  if (parsed.tool && parsed.args) {
1054
+ parsed.tool = normalizeToolName(parsed.tool);
874
1055
  calls.push(parsed);
875
1056
  }
876
1057
  } catch (e) {
@@ -878,12 +1059,14 @@ export class AgenticTools {
878
1059
  }
879
1060
  }
880
1061
 
881
- // Also match ```tool format
882
- const codeBlockRegex = /```tool\s*\n(\{[\s\S]*?\})\n```/g;
1062
+ // Match ```tool format
1063
+ const codeBlockRegex = /```tool\s*\n([\s\S]*?)\n```/g;
883
1064
  while ((match = codeBlockRegex.exec(text)) !== null) {
884
1065
  try {
885
- const parsed = JSON.parse(match[1]);
1066
+ const fixed = fixJson(match[1]);
1067
+ const parsed = JSON.parse(fixed);
886
1068
  if (parsed.tool && parsed.args) {
1069
+ parsed.tool = normalizeToolName(parsed.tool);
887
1070
  calls.push(parsed);
888
1071
  }
889
1072
  } catch (e) {
@@ -891,6 +1074,67 @@ export class AgenticTools {
891
1074
  }
892
1075
  }
893
1076
 
1077
+ // Match ```json blocks with tool definitions
1078
+ const jsonBlockRegex = /```(?:json)?\s*\n?([\s\S]*?"tool"[\s\S]*?)\n?```/g;
1079
+ while ((match = jsonBlockRegex.exec(text)) !== null) {
1080
+ try {
1081
+ const fixed = fixJson(match[1]);
1082
+ const parsed = JSON.parse(fixed);
1083
+ if (parsed.tool && parsed.args) {
1084
+ parsed.tool = normalizeToolName(parsed.tool);
1085
+ // Prevent duplicates
1086
+ if (!calls.some(c => c.tool === parsed.tool && JSON.stringify(c.args) === JSON.stringify(parsed.args))) {
1087
+ calls.push(parsed);
1088
+ }
1089
+ }
1090
+ } catch (e) {
1091
+ // Invalid JSON, skip
1092
+ }
1093
+ }
1094
+
1095
+ // Match inline JSON with "tool" key (various formats)
1096
+ const inlineToolRegex = /\{[^{}]*"?tool"?\s*:\s*["']?([^"',}]+)["']?[^{}]*"?args"?\s*:\s*\{([^{}]*)\}[^{}]*\}/gi;
1097
+ while ((match = inlineToolRegex.exec(text)) !== null) {
1098
+ try {
1099
+ const fixed = fixJson(match[0]);
1100
+ const parsed = JSON.parse(fixed);
1101
+ if (parsed.tool && parsed.args) {
1102
+ parsed.tool = normalizeToolName(parsed.tool);
1103
+ if (!calls.some(c => c.tool === parsed.tool && JSON.stringify(c.args) === JSON.stringify(parsed.args))) {
1104
+ calls.push(parsed);
1105
+ }
1106
+ }
1107
+ } catch (e) {
1108
+ // Invalid JSON, skip
1109
+ }
1110
+ }
1111
+
1112
+ // Parse Vigthoria V2 format: {"tool": "__BASH__", ...}
1113
+ const vigV2Regex = /"?tool"?\s*:\s*["']__?([A-Za-z_]+)__?["']/gi;
1114
+ while ((match = vigV2Regex.exec(text)) !== null) {
1115
+ try {
1116
+ const toolName = normalizeToolName(match[1]);
1117
+ // Extract args from nearby context
1118
+ const pathMatch = text.match(/"?(?:arg_)?path"?\s*:\s*["']([^"']+)["']/i);
1119
+ const cmdMatch = text.match(/"?command"?\s*:\s*(?:["']([^"']+)["']|\[\s*["']([^"']+)["']\s*\])/i);
1120
+ const contentMatch = text.match(/"?content"?\s*:\s*["']([^"']+)["']/i);
1121
+
1122
+ const args: Record<string, string> = {};
1123
+ if (pathMatch) args.path = pathMatch[1];
1124
+ if (cmdMatch) args.command = cmdMatch[1] || cmdMatch[2];
1125
+ if (contentMatch) args.content = contentMatch[1];
1126
+
1127
+ if (Object.keys(args).length > 0) {
1128
+ // Prevent duplicates
1129
+ if (!calls.some(c => c.tool === toolName && JSON.stringify(c.args) === JSON.stringify(args))) {
1130
+ calls.push({ tool: toolName, args });
1131
+ }
1132
+ }
1133
+ } catch (e) {
1134
+ // Skip
1135
+ }
1136
+ }
1137
+
894
1138
  return calls;
895
1139
  }
896
1140
 
@@ -914,18 +1158,41 @@ ${tool.parameters.map(p => ` - ${p.name}${p.required ? ' (required)' : ''}: ${p
914
1158
  }
915
1159
 
916
1160
  prompt += `
917
- To use a tool, respond with a tool_call block:
1161
+ ## How to Use Tools
1162
+
1163
+ To use a tool, output a JSON block in a code fence with "tool" language:
1164
+
918
1165
  \`\`\`tool
919
- {
920
- "tool": "tool_name",
921
- "args": {
922
- "param1": "value1"
923
- }
924
- }
1166
+ {"tool": "tool_name", "args": {"param1": "value1"}}
1167
+ \`\`\`
1168
+
1169
+ ### Examples:
1170
+
1171
+ 1. List directory contents:
1172
+ \`\`\`tool
1173
+ {"tool": "list_dir", "args": {"path": "/var/www/project"}}
1174
+ \`\`\`
1175
+
1176
+ 2. Read a file:
1177
+ \`\`\`tool
1178
+ {"tool": "read_file", "args": {"path": "/var/www/project/index.html"}}
1179
+ \`\`\`
1180
+
1181
+ 3. Run a shell command:
1182
+ \`\`\`tool
1183
+ {"tool": "bash", "args": {"command": "ls -la /var/www"}}
1184
+ \`\`\`
1185
+
1186
+ 4. Write a file:
1187
+ \`\`\`tool
1188
+ {"tool": "write_file", "args": {"path": "/var/www/project/new.txt", "content": "Hello World"}}
925
1189
  \`\`\`
926
1190
 
927
- You can use multiple tool calls in one response. After tool execution, you'll receive the results and can continue.
928
- Always explain what you're doing before using tools.
1191
+ IMPORTANT:
1192
+ - Use ONLY the exact tool names: list_dir, read_file, write_file, edit_file, bash, grep, glob, git
1193
+ - The JSON must be valid with double quotes for all keys and string values
1194
+ - After tool execution, you will receive results and can continue with the next step
1195
+ - Explain what you're doing before using tools
929
1196
  `;
930
1197
 
931
1198
  return prompt;