vigthoria-cli 1.1.0 → 1.4.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.
Files changed (49) hide show
  1. package/README.md +85 -2
  2. package/SECURITY_HARDENING.md +253 -0
  3. package/dist/commands/chat.d.ts +2 -2
  4. package/dist/commands/chat.js +4 -4
  5. package/dist/commands/chat.js.map +1 -1
  6. package/dist/commands/deploy.d.ts +80 -0
  7. package/dist/commands/deploy.d.ts.map +1 -0
  8. package/dist/commands/deploy.js +514 -0
  9. package/dist/commands/deploy.js.map +1 -0
  10. package/dist/commands/generate.d.ts +3 -0
  11. package/dist/commands/generate.d.ts.map +1 -1
  12. package/dist/commands/generate.js +45 -4
  13. package/dist/commands/generate.js.map +1 -1
  14. package/dist/commands/hub.d.ts +40 -0
  15. package/dist/commands/hub.d.ts.map +1 -0
  16. package/dist/commands/hub.js +289 -0
  17. package/dist/commands/hub.js.map +1 -0
  18. package/dist/commands/repo.d.ts +80 -0
  19. package/dist/commands/repo.d.ts.map +1 -0
  20. package/dist/commands/repo.js +607 -0
  21. package/dist/commands/repo.js.map +1 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +267 -10
  25. package/dist/index.js.map +1 -1
  26. package/dist/utils/api.d.ts +15 -0
  27. package/dist/utils/api.d.ts.map +1 -1
  28. package/dist/utils/api.js +62 -33
  29. package/dist/utils/api.js.map +1 -1
  30. package/dist/utils/config.js +1 -1
  31. package/dist/utils/config.js.map +1 -1
  32. package/dist/utils/session.d.ts +1 -1
  33. package/dist/utils/session.js +1 -1
  34. package/dist/utils/tools.d.ts +18 -3
  35. package/dist/utils/tools.d.ts.map +1 -1
  36. package/dist/utils/tools.js +326 -20
  37. package/dist/utils/tools.js.map +1 -1
  38. package/install.sh +1 -1
  39. package/package.json +6 -3
  40. package/src/commands/chat.ts +4 -4
  41. package/src/commands/deploy.ts +609 -0
  42. package/src/commands/generate.ts +49 -4
  43. package/src/commands/hub.ts +382 -0
  44. package/src/commands/repo.ts +729 -0
  45. package/src/index.ts +297 -10
  46. package/src/utils/api.ts +78 -34
  47. package/src/utils/config.ts +1 -1
  48. package/src/utils/session.ts +1 -1
  49. package/src/utils/tools.ts +348 -21
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Vigthoria CLI Tools - Agentic Tool System
3
3
  *
4
- * This module provides Claude Code-like autonomous tool execution.
4
+ * This module provides Vigthoria Autonomous autonomous tool execution.
5
5
  * Tools can be called by the AI to perform actions.
6
6
  *
7
7
  * Enhanced with:
@@ -159,7 +159,7 @@ export class AgenticTools {
159
159
  }
160
160
 
161
161
  /**
162
- * List of available tools - similar to Claude Code
162
+ * List of available tools - Vigthoria's advanced
163
163
  * Enhanced with risk levels and categories
164
164
  */
165
165
  static getToolDefinitions(): ToolDefinition[] {
@@ -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,
@@ -708,11 +726,40 @@ export class AgenticTools {
708
726
 
709
727
  /**
710
728
  * Execute bash command with enhanced error handling
729
+ * SECURITY: Commands are sandboxed to workspace directory
711
730
  */
712
731
  private bash(args: Record<string, string>): ToolResult {
713
732
  const cwd = args.cwd ? this.resolvePath(args.cwd) : this.cwd;
714
733
  const timeout = args.timeout ? parseInt(args.timeout) * 1000 : 30000;
715
734
 
735
+ // SECURITY: Block dangerous commands that could access outside workspace
736
+ const blockedPatterns = [
737
+ /\bcat\s+\/etc\//i, // Reading system files
738
+ /\bcat\s+\/var\/(?!log)/i, // Reading /var/ except logs
739
+ /\bcat\s+\/root\//i, // Reading root home
740
+ /\bcat\s+\/home\/[^\/]+\/\.[^\/]/i, // Reading hidden files in home dirs
741
+ /\bcat\s+~\/\./i, // Reading hidden files via ~
742
+ /\bcd\s+(\/etc|\/var\/www|\/root|\/home)/i, // CD to sensitive dirs
743
+ /\brm\s+-rf?\s+\//i, // Dangerous rm commands
744
+ /\b(curl|wget).*\|\s*(bash|sh)\b/i, // Downloading and executing scripts
745
+ /\bsudo\b/i, // Sudo commands
746
+ /\bsu\s+-/i, // Su to root
747
+ /\bchmod\s+[0-7]*777/i, // World-writable permissions
748
+ /\/(var\/www|opt)\/[a-z]+-?(database|models|coder|mcp|operator|voice|music)/i, // Vigthoria ecosystem
749
+ /vigthoria-(models|database|mcp|operator|voice|music)/i, // Vigthoria project names
750
+ ];
751
+
752
+ for (const pattern of blockedPatterns) {
753
+ if (pattern.test(args.command)) {
754
+ this.logger.warn(`Security: Blocked potentially dangerous command: ${args.command.substring(0, 50)}...`);
755
+ return this.createErrorResult(
756
+ ToolErrorType.PERMISSION_DENIED,
757
+ 'This command is blocked for security reasons',
758
+ 'Commands must operate within your current project workspace.'
759
+ );
760
+ }
761
+ }
762
+
716
763
  // Validate working directory
717
764
  if (!fs.existsSync(cwd)) {
718
765
  return this.createErrorResult(
@@ -850,27 +897,218 @@ export class AgenticTools {
850
897
  }
851
898
  }
852
899
 
900
+ /**
901
+ * Vigthoria Repository management tool
902
+ * Allows AI to push, pull, list, share, and manage projects in the Vigthoria Repository
903
+ */
904
+ private async repo(args: Record<string, string>): Promise<ToolResult> {
905
+ const action = args.action?.toLowerCase();
906
+ const project = args.project;
907
+ const visibility = args.visibility || 'private';
908
+ const targetPath = args.path || this.cwd;
909
+
910
+ try {
911
+ // Use vigthoria CLI for repo operations
912
+ let command: string;
913
+
914
+ switch (action) {
915
+ case 'push':
916
+ if (!project) {
917
+ return {
918
+ success: false,
919
+ error: 'Project name is required for push',
920
+ suggestion: 'Provide a project name, e.g., repo action=push project=my-project'
921
+ };
922
+ }
923
+ command = `vigthoria repo push "${project}" "${targetPath}" --visibility ${visibility}`;
924
+ break;
925
+
926
+ case 'pull':
927
+ if (!project) {
928
+ return {
929
+ success: false,
930
+ error: 'Project name is required for pull',
931
+ suggestion: 'Provide a project name, e.g., repo action=pull project=my-project'
932
+ };
933
+ }
934
+ command = `vigthoria repo pull "${project}"`;
935
+ break;
936
+
937
+ case 'list':
938
+ command = 'vigthoria repo list';
939
+ break;
940
+
941
+ case 'status':
942
+ if (!project) {
943
+ return {
944
+ success: false,
945
+ error: 'Project name is required for status',
946
+ suggestion: 'Provide a project name, e.g., repo action=status project=my-project'
947
+ };
948
+ }
949
+ command = `vigthoria repo status "${project}"`;
950
+ break;
951
+
952
+ case 'share':
953
+ if (!project || !args.username) {
954
+ return {
955
+ success: false,
956
+ error: 'Project name and username are required for share',
957
+ suggestion: 'Provide both, e.g., repo action=share project=my-project username=collaborator permission=read'
958
+ };
959
+ }
960
+ const permission = args.permission || 'read';
961
+ command = `vigthoria repo share "${project}" "${args.username}" --permission ${permission}`;
962
+ break;
963
+
964
+ case 'delete':
965
+ if (!project) {
966
+ return {
967
+ success: false,
968
+ error: 'Project name is required for delete',
969
+ suggestion: 'Provide a project name, e.g., repo action=delete project=my-project'
970
+ };
971
+ }
972
+ command = `vigthoria repo delete "${project}" --force`;
973
+ break;
974
+
975
+ case 'clone':
976
+ if (!project) {
977
+ return {
978
+ success: false,
979
+ error: 'Project name is required for clone',
980
+ suggestion: 'Provide a project name, e.g., repo action=clone project=my-project path=/path/to/target'
981
+ };
982
+ }
983
+ command = `vigthoria repo clone "${project}" "${targetPath}"`;
984
+ break;
985
+
986
+ default:
987
+ return {
988
+ success: false,
989
+ error: `Unknown repo action: ${action}`,
990
+ suggestion: 'Available actions: push, pull, list, status, share, delete, clone'
991
+ };
992
+ }
993
+
994
+ const output = execSync(command, {
995
+ cwd: this.cwd,
996
+ encoding: 'utf-8',
997
+ timeout: 120000, // 2 minute timeout for repo operations
998
+ env: { ...process.env, FORCE_COLOR: '0' } // Disable colors for clean output
999
+ });
1000
+
1001
+ return {
1002
+ success: true,
1003
+ output: output.trim(),
1004
+ metadata: { action, project }
1005
+ };
1006
+ } catch (error: any) {
1007
+ return {
1008
+ success: false,
1009
+ error: error.stderr || error.message,
1010
+ suggestion: 'Make sure you are logged in with vigthoria login and have the required permissions.'
1011
+ };
1012
+ }
1013
+ }
1014
+
1015
+ /**
1016
+ * Resolve and SANITIZE path - prevent path traversal outside workspace
1017
+ * SECURITY: All paths MUST stay within the workspace (cwd)
1018
+ */
853
1019
  private resolvePath(p: string): string {
1020
+ // Resolve the full path (handles both relative and absolute)
1021
+ let resolvedPath: string;
854
1022
  if (path.isAbsolute(p)) {
855
- return p;
1023
+ resolvedPath = path.normalize(p);
1024
+ } else {
1025
+ resolvedPath = path.normalize(path.join(this.cwd, p));
1026
+ }
1027
+
1028
+ // SECURITY CHECK: Ensure path is within workspace
1029
+ const workspaceRoot = path.normalize(this.cwd);
1030
+ if (!resolvedPath.startsWith(workspaceRoot + path.sep) && resolvedPath !== workspaceRoot) {
1031
+ // Path is outside workspace - force it back to workspace
1032
+ this.logger.warn(`Security: Blocked access to path outside workspace: ${p}`);
1033
+ // Return the sanitized relative path within workspace
1034
+ const basename = path.basename(p);
1035
+ return path.join(this.cwd, basename);
856
1036
  }
857
- return path.join(this.cwd, p);
1037
+
1038
+ return resolvedPath;
1039
+ }
1040
+
1041
+ /**
1042
+ * Check if a path is within the allowed workspace
1043
+ */
1044
+ private isPathWithinWorkspace(p: string): boolean {
1045
+ const resolvedPath = path.normalize(path.isAbsolute(p) ? p : path.join(this.cwd, p));
1046
+ const workspaceRoot = path.normalize(this.cwd);
1047
+ return resolvedPath.startsWith(workspaceRoot + path.sep) || resolvedPath === workspaceRoot;
858
1048
  }
859
1049
 
860
1050
  /**
861
- * Parse tool calls from AI response (Claude Code format)
1051
+ * Parse tool calls from AI response (Vigthoria Agent format)
1052
+ * Enhanced to handle various AI output formats including malformed JSON
862
1053
  */
863
1054
  static parseToolCalls(text: string): ToolCall[] {
864
1055
  const calls: ToolCall[] = [];
865
-
866
- // Match <tool_call>...</tool_call> blocks
867
- const toolCallRegex = /<tool_call>\s*(\{[\s\S]*?\})\s*<\/tool_call>/g;
868
1056
  let match;
869
1057
 
1058
+ // Helper to fix common JSON issues from AI outputs
1059
+ const fixJson = (jsonStr: string): string => {
1060
+ return jsonStr
1061
+ .replace(/'/g, '"') // Replace single quotes with double
1062
+ .replace(/([{,]\s*)(\w+):/g, '$1"$2":') // Quote unquoted keys
1063
+ .replace(/:\s*'([^']*)'\s*([,}])/g, ': "$1"$2') // Quote values with single quotes
1064
+ .replace(/\n/g, '\\n') // Escape newlines
1065
+ .replace(/\r/g, '') // Remove carriage returns
1066
+ .replace(/\t/g, '\\t') // Escape tabs
1067
+ .replace(/,\s*}/g, '}') // Remove trailing commas in objects
1068
+ .replace(/,\s*]/g, ']'); // Remove trailing commas in arrays
1069
+ };
1070
+
1071
+ // Normalize tool name from various formats
1072
+ const normalizeToolName = (name: string): string => {
1073
+ const normalized = name
1074
+ .replace(/^__/, '') // Remove leading underscores
1075
+ .replace(/__$/, '') // Remove trailing underscores
1076
+ .replace(/^execute_/i, '') // Remove execute_ prefix
1077
+ .replace(/_execute$/i, '') // Remove _execute suffix
1078
+ .toLowerCase();
1079
+
1080
+ // Map common variations
1081
+ const toolMap: Record<string, string> = {
1082
+ 'bash': 'bash',
1083
+ 'shell': 'bash',
1084
+ 'run': 'bash',
1085
+ 'command': 'bash',
1086
+ 'list_dir': 'list_dir',
1087
+ 'list_directory': 'list_dir',
1088
+ 'ls': 'list_dir',
1089
+ 'dir': 'list_dir',
1090
+ 'read_file': 'read_file',
1091
+ 'readfile': 'read_file',
1092
+ 'read': 'read_file',
1093
+ 'write_file': 'write_file',
1094
+ 'writefile': 'write_file',
1095
+ 'write': 'write_file',
1096
+ 'edit_file': 'edit_file',
1097
+ 'editfile': 'edit_file',
1098
+ 'edit': 'edit_file',
1099
+ };
1100
+
1101
+ return toolMap[normalized] || normalized;
1102
+ };
1103
+
1104
+ // Match <tool_call>...</tool_call> blocks
1105
+ const toolCallRegex = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/g;
870
1106
  while ((match = toolCallRegex.exec(text)) !== null) {
871
1107
  try {
872
- const parsed = JSON.parse(match[1]);
1108
+ const fixed = fixJson(match[1]);
1109
+ const parsed = JSON.parse(fixed);
873
1110
  if (parsed.tool && parsed.args) {
1111
+ parsed.tool = normalizeToolName(parsed.tool);
874
1112
  calls.push(parsed);
875
1113
  }
876
1114
  } catch (e) {
@@ -878,12 +1116,14 @@ export class AgenticTools {
878
1116
  }
879
1117
  }
880
1118
 
881
- // Also match ```tool format
882
- const codeBlockRegex = /```tool\s*\n(\{[\s\S]*?\})\n```/g;
1119
+ // Match ```tool format
1120
+ const codeBlockRegex = /```tool\s*\n([\s\S]*?)\n```/g;
883
1121
  while ((match = codeBlockRegex.exec(text)) !== null) {
884
1122
  try {
885
- const parsed = JSON.parse(match[1]);
1123
+ const fixed = fixJson(match[1]);
1124
+ const parsed = JSON.parse(fixed);
886
1125
  if (parsed.tool && parsed.args) {
1126
+ parsed.tool = normalizeToolName(parsed.tool);
887
1127
  calls.push(parsed);
888
1128
  }
889
1129
  } catch (e) {
@@ -891,6 +1131,67 @@ export class AgenticTools {
891
1131
  }
892
1132
  }
893
1133
 
1134
+ // Match ```json blocks with tool definitions
1135
+ const jsonBlockRegex = /```(?:json)?\s*\n?([\s\S]*?"tool"[\s\S]*?)\n?```/g;
1136
+ while ((match = jsonBlockRegex.exec(text)) !== null) {
1137
+ try {
1138
+ const fixed = fixJson(match[1]);
1139
+ const parsed = JSON.parse(fixed);
1140
+ if (parsed.tool && parsed.args) {
1141
+ parsed.tool = normalizeToolName(parsed.tool);
1142
+ // Prevent duplicates
1143
+ if (!calls.some(c => c.tool === parsed.tool && JSON.stringify(c.args) === JSON.stringify(parsed.args))) {
1144
+ calls.push(parsed);
1145
+ }
1146
+ }
1147
+ } catch (e) {
1148
+ // Invalid JSON, skip
1149
+ }
1150
+ }
1151
+
1152
+ // Match inline JSON with "tool" key (various formats)
1153
+ const inlineToolRegex = /\{[^{}]*"?tool"?\s*:\s*["']?([^"',}]+)["']?[^{}]*"?args"?\s*:\s*\{([^{}]*)\}[^{}]*\}/gi;
1154
+ while ((match = inlineToolRegex.exec(text)) !== null) {
1155
+ try {
1156
+ const fixed = fixJson(match[0]);
1157
+ const parsed = JSON.parse(fixed);
1158
+ if (parsed.tool && parsed.args) {
1159
+ parsed.tool = normalizeToolName(parsed.tool);
1160
+ if (!calls.some(c => c.tool === parsed.tool && JSON.stringify(c.args) === JSON.stringify(parsed.args))) {
1161
+ calls.push(parsed);
1162
+ }
1163
+ }
1164
+ } catch (e) {
1165
+ // Invalid JSON, skip
1166
+ }
1167
+ }
1168
+
1169
+ // Parse Vigthoria V2 format: {"tool": "__BASH__", ...}
1170
+ const vigV2Regex = /"?tool"?\s*:\s*["']__?([A-Za-z_]+)__?["']/gi;
1171
+ while ((match = vigV2Regex.exec(text)) !== null) {
1172
+ try {
1173
+ const toolName = normalizeToolName(match[1]);
1174
+ // Extract args from nearby context
1175
+ const pathMatch = text.match(/"?(?:arg_)?path"?\s*:\s*["']([^"']+)["']/i);
1176
+ const cmdMatch = text.match(/"?command"?\s*:\s*(?:["']([^"']+)["']|\[\s*["']([^"']+)["']\s*\])/i);
1177
+ const contentMatch = text.match(/"?content"?\s*:\s*["']([^"']+)["']/i);
1178
+
1179
+ const args: Record<string, string> = {};
1180
+ if (pathMatch) args.path = pathMatch[1];
1181
+ if (cmdMatch) args.command = cmdMatch[1] || cmdMatch[2];
1182
+ if (contentMatch) args.content = contentMatch[1];
1183
+
1184
+ if (Object.keys(args).length > 0) {
1185
+ // Prevent duplicates
1186
+ if (!calls.some(c => c.tool === toolName && JSON.stringify(c.args) === JSON.stringify(args))) {
1187
+ calls.push({ tool: toolName, args });
1188
+ }
1189
+ }
1190
+ } catch (e) {
1191
+ // Skip
1192
+ }
1193
+ }
1194
+
894
1195
  return calls;
895
1196
  }
896
1197
 
@@ -914,18 +1215,44 @@ ${tool.parameters.map(p => ` - ${p.name}${p.required ? ' (required)' : ''}: ${p
914
1215
  }
915
1216
 
916
1217
  prompt += `
917
- To use a tool, respond with a tool_call block:
1218
+ ## How to Use Tools
1219
+
1220
+ To use a tool, output a JSON block in a code fence with "tool" language:
1221
+
918
1222
  \`\`\`tool
919
- {
920
- "tool": "tool_name",
921
- "args": {
922
- "param1": "value1"
923
- }
924
- }
1223
+ {"tool": "tool_name", "args": {"param1": "value1"}}
1224
+ \`\`\`
1225
+
1226
+ ### Examples:
1227
+
1228
+ 1. List directory contents:
1229
+ \`\`\`tool
1230
+ {"tool": "list_dir", "args": {"path": "."}}
1231
+ \`\`\`
1232
+
1233
+ 2. Read a file:
1234
+ \`\`\`tool
1235
+ {"tool": "read_file", "args": {"path": "src/index.js"}}
1236
+ \`\`\`
1237
+
1238
+ 3. Run a shell command:
1239
+ \`\`\`tool
1240
+ {"tool": "bash", "args": {"command": "ls -la"}}
1241
+ \`\`\`
1242
+
1243
+ 4. Write a file:
1244
+ \`\`\`tool
1245
+ {"tool": "write_file", "args": {"path": "hello.py", "content": "print('Hello World')"}}
925
1246
  \`\`\`
926
1247
 
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.
1248
+ IMPORTANT:
1249
+ - You can ONLY access files within the current project workspace
1250
+ - Use relative paths (e.g., "src/file.js", "app.py", "./config.json")
1251
+ - Never try to access system files or directories outside the workspace
1252
+ - Use ONLY the exact tool names: list_dir, read_file, write_file, edit_file, bash, grep, glob, git
1253
+ - The JSON must be valid with double quotes for all keys and string values
1254
+ - After tool execution, you will receive results and can continue with the next step
1255
+ - Explain what you're doing before using tools
929
1256
  `;
930
1257
 
931
1258
  return prompt;