Compare commits
	
		
			4 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 976c4d0980 | |||
| eda652994d | |||
| 46f82d2b65 | |||
| 90c5f07be4 | 
							
								
								
									
										17
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								changelog.md
									
									
									
									
									
								
							@@ -1,5 +1,22 @@
 | 
				
			|||||||
# Changelog
 | 
					# Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-10-29 - 1.5.1 - fix(scriptindex)
 | 
				
			||||||
 | 
					Improve script search: use ObjectSorter with weighted results prioritizing slug and name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Replaced smartfuzzy.FuzzyMatcher with smartfuzzy.ObjectSorter for multi-field fuzzy searching
 | 
				
			||||||
 | 
					- Search now runs across slug, name, and description fields
 | 
				
			||||||
 | 
					- Added weighting so slug matches are prioritized, name matches receive a medium boost
 | 
				
			||||||
 | 
					- Search returns ordered script objects based on adjusted score
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-10-29 - 1.5.0 - feat(scripts)
 | 
				
			||||||
 | 
					Add fuzzy search and type filtering for community scripts; improve scripts CLI output and cache handling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Integrate @push.rocks/smartfuzzy and use FuzzyMatcher to provide ranked, fuzzy search results for scripts
 | 
				
			||||||
 | 
					- Add optional type filtering to scripts search (e.g. type:vm, type:ct, type:pve) and parse a --filter option in the CLI
 | 
				
			||||||
 | 
					- Improve scripts CLI output: colored type badges, truncated descriptions, clearer usage/help text and filtered result messaging
 | 
				
			||||||
 | 
					- Optimize ScriptIndex.loadCache to avoid reloading when cache is already present
 | 
				
			||||||
 | 
					- Update deno.json to include the smartfuzzy dependency and export/import smartfuzzy in plugins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-10-28 - 1.4.2 - fix(scriptindex)
 | 
					## 2025-10-28 - 1.4.2 - fix(scriptindex)
 | 
				
			||||||
Handle missing script metadata fields in ScriptIndex.search to prevent crashes
 | 
					Handle missing script metadata fields in ScriptIndex.search to prevent crashes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@serve.zone/moxytool",
 | 
					  "name": "@serve.zone/moxytool",
 | 
				
			||||||
  "version": "1.4.2",
 | 
					  "version": "1.5.1",
 | 
				
			||||||
  "exports": "./mod.ts",
 | 
					  "exports": "./mod.ts",
 | 
				
			||||||
  "nodeModulesDir": "auto",
 | 
					  "nodeModulesDir": "auto",
 | 
				
			||||||
  "tasks": {
 | 
					  "tasks": {
 | 
				
			||||||
@@ -49,6 +49,7 @@
 | 
				
			|||||||
    "@push.rocks/smartpath": "npm:@push.rocks/smartpath@^5.0.5",
 | 
					    "@push.rocks/smartpath": "npm:@push.rocks/smartpath@^5.0.5",
 | 
				
			||||||
    "@push.rocks/smartshell": "npm:@push.rocks/smartshell@^3.2.2",
 | 
					    "@push.rocks/smartshell": "npm:@push.rocks/smartshell@^3.2.2",
 | 
				
			||||||
    "@push.rocks/smartexpect": "npm:@push.rocks/smartexpect@^1.0.15",
 | 
					    "@push.rocks/smartexpect": "npm:@push.rocks/smartexpect@^1.0.15",
 | 
				
			||||||
 | 
					    "@push.rocks/smartfuzzy": "npm:@push.rocks/smartfuzzy@^2.0.0",
 | 
				
			||||||
    "@push.rocks/smartrx": "npm:@push.rocks/smartrx@^3.0.10",
 | 
					    "@push.rocks/smartrx": "npm:@push.rocks/smartrx@^3.0.10",
 | 
				
			||||||
    "@push.rocks/smartpromise": "npm:@push.rocks/smartpromise@^4.0.0",
 | 
					    "@push.rocks/smartpromise": "npm:@push.rocks/smartpromise@^4.0.0",
 | 
				
			||||||
    "@push.rocks/smartstring": "npm:@push.rocks/smartstring@^4.0.0",
 | 
					    "@push.rocks/smartstring": "npm:@push.rocks/smartstring@^4.0.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@serve.zone/moxytool",
 | 
					  "name": "@serve.zone/moxytool",
 | 
				
			||||||
  "version": "1.4.2",
 | 
					  "version": "1.5.1",
 | 
				
			||||||
  "description": "Proxmox administration tool for vGPU setup, VM management, and cluster configuration",
 | 
					  "description": "Proxmox administration tool for vGPU setup, VM management, and cluster configuration",
 | 
				
			||||||
  "keywords": [
 | 
					  "keywords": [
 | 
				
			||||||
    "proxmox",
 | 
					    "proxmox",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,6 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export const commitinfo = {
 | 
					export const commitinfo = {
 | 
				
			||||||
  name: '@serve.zone/moxytool',
 | 
					  name: '@serve.zone/moxytool',
 | 
				
			||||||
  version: '1.4.2',
 | 
					  version: '1.5.1',
 | 
				
			||||||
  description: 'Proxmox administration tool for vGPU setup, VM management, and cluster configuration'
 | 
					  description: 'Proxmox administration tool for vGPU setup, VM management, and cluster configuration'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -88,6 +88,11 @@ export class ScriptIndex {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  public async loadCache(): Promise<void> {
 | 
					  public async loadCache(): Promise<void> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      // Don't reload if already cached
 | 
				
			||||||
 | 
					      if (this.cache) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!Deno) {
 | 
					      if (!Deno) {
 | 
				
			||||||
        throw new Error('Deno runtime not available');
 | 
					        throw new Error('Deno runtime not available');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -251,23 +256,51 @@ export class ScriptIndex {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Search scripts by query string
 | 
					   * Search scripts by query string with optional type filter
 | 
				
			||||||
 | 
					   * @param query - Search query
 | 
				
			||||||
 | 
					   * @param typeFilter - Optional type filter (e.g., 'vm', 'ct', 'pve')
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public search(query: string): IScriptMetadata[] {
 | 
					  public search(query: string, typeFilter?: string): IScriptMetadata[] {
 | 
				
			||||||
    if (!this.cache) {
 | 
					    if (!this.cache) {
 | 
				
			||||||
      return [];
 | 
					      return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const lowerQuery = query.toLowerCase();
 | 
					    let scripts = this.cache.scripts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.cache.scripts.filter((script) => {
 | 
					    // Apply type filter if provided
 | 
				
			||||||
      // Search in name, description, and slug
 | 
					    if (typeFilter) {
 | 
				
			||||||
      return (
 | 
					      scripts = scripts.filter((script) => script.type === typeFilter);
 | 
				
			||||||
        (script.name && script.name.toLowerCase().includes(lowerQuery)) ||
 | 
					    }
 | 
				
			||||||
        (script.slug && script.slug.toLowerCase().includes(lowerQuery)) ||
 | 
					
 | 
				
			||||||
        (script.description && script.description.toLowerCase().includes(lowerQuery))
 | 
					    // Use ObjectSorter for fuzzy searching across multiple fields
 | 
				
			||||||
      );
 | 
					    const sorter = new plugins.smartfuzzy.ObjectSorter(scripts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Search across slug, name, and description
 | 
				
			||||||
 | 
					    const results = sorter.sort(query, ['slug', 'name', 'description']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Post-process to weight results by which field matched
 | 
				
			||||||
 | 
					    const weightedResults = results.map((result) => {
 | 
				
			||||||
 | 
					      let weight = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Boost score if match was in slug (highest priority)
 | 
				
			||||||
 | 
					      if (result.matches?.some((m) => m.key === 'slug')) {
 | 
				
			||||||
 | 
					        weight = 3;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Boost if match was in name (medium priority)
 | 
				
			||||||
 | 
					      else if (result.matches?.some((m) => m.key === 'name')) {
 | 
				
			||||||
 | 
					        weight = 2;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...result,
 | 
				
			||||||
 | 
					        adjustedScore: (result.score || 0) / weight,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Sort by adjusted score and return just the script objects
 | 
				
			||||||
 | 
					    return weightedResults
 | 
				
			||||||
 | 
					      .sort((a, b) => (a.adjustedScore || 0) - (b.adjustedScore || 0))
 | 
				
			||||||
 | 
					      .map((result) => result.item);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -140,17 +140,8 @@ export const runCli = async () => {
 | 
				
			|||||||
    logger.log('info', '');
 | 
					    logger.log('info', '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      // Get current version from deno.json
 | 
					      // Get current version from compile-time imported deno.json
 | 
				
			||||||
      const denoJsonPath = plugins.path.join(paths.packageDir, 'deno.json');
 | 
					      const currentVersion = denoConfig.version;
 | 
				
			||||||
      let currentVersion = '1.1.0'; // fallback
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        const denoJsonContent = await Deno.readTextFile(denoJsonPath);
 | 
					 | 
				
			||||||
        const denoJson = JSON.parse(denoJsonContent);
 | 
					 | 
				
			||||||
        currentVersion = denoJson.version || currentVersion;
 | 
					 | 
				
			||||||
      } catch {
 | 
					 | 
				
			||||||
        // Use fallback version
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Fetch latest version from Gitea API
 | 
					      // Fetch latest version from Gitea API
 | 
				
			||||||
      const apiUrl = 'https://code.foss.global/api/v1/repos/serve.zone/moxytool/releases/latest';
 | 
					      const apiUrl = 'https://code.foss.global/api/v1/repos/serve.zone/moxytool/releases/latest';
 | 
				
			||||||
@@ -289,28 +280,50 @@ export const runCli = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (!query) {
 | 
					        if (!query) {
 | 
				
			||||||
          logger.log('error', 'Please provide a search query');
 | 
					          logger.log('error', 'Please provide a search query');
 | 
				
			||||||
          logger.log('info', 'Usage: moxytool scripts search <query>');
 | 
					          logger.log('info', 'Usage: moxytool scripts search <query> [--filter type:vm]');
 | 
				
			||||||
 | 
					          logger.log('info', 'Filters: type:vm, type:ct, type:pve, type:addon');
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const results = scriptIndex.search(query as string);
 | 
					        // Parse filter option
 | 
				
			||||||
 | 
					        let typeFilter: string | undefined;
 | 
				
			||||||
 | 
					        if (argvArg.filter) {
 | 
				
			||||||
 | 
					          const filterString = argvArg.filter as string;
 | 
				
			||||||
 | 
					          if (filterString.startsWith('type:')) {
 | 
				
			||||||
 | 
					            typeFilter = filterString.substring(5);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const results = scriptIndex.search(query as string, typeFilter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (results.length === 0) {
 | 
					        if (results.length === 0) {
 | 
				
			||||||
          logger.log('warn', `No scripts found matching "${query}"`);
 | 
					          const filterMsg = typeFilter ? ` (filtered by type:${typeFilter})` : '';
 | 
				
			||||||
 | 
					          logger.log('warn', `No scripts found matching "${query}"${filterMsg}`);
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.log('info', `Found ${results.length} script(s) matching "${query}":`);
 | 
					        const filterMsg = typeFilter ? ` \x1b[2m(filtered by type:${typeFilter})\x1b[0m` : '';
 | 
				
			||||||
 | 
					        logger.log('info', `Found ${results.length} script(s) matching "\x1b[1m${query}\x1b[0m"${filterMsg}:`);
 | 
				
			||||||
        logger.log('info', '');
 | 
					        logger.log('info', '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        results.forEach(script => {
 | 
					        results.forEach(script => {
 | 
				
			||||||
          logger.log('info', `${script.slug} (${script.type})`);
 | 
					          const slug = script.slug || 'unknown';
 | 
				
			||||||
          logger.log('info', `  ${script.name}`);
 | 
					          const description = script.description ? script.description.substring(0, 100) : 'No description available';
 | 
				
			||||||
          logger.log('info', `  ${script.description.substring(0, 80)}...`);
 | 
					
 | 
				
			||||||
 | 
					          // Type badge with colors
 | 
				
			||||||
 | 
					          let typeBadge = '';
 | 
				
			||||||
 | 
					          if (script.type === 'ct') typeBadge = '\x1b[36m[LXC]\x1b[0m';
 | 
				
			||||||
 | 
					          else if (script.type === 'vm') typeBadge = '\x1b[35m[VM]\x1b[0m';
 | 
				
			||||||
 | 
					          else if (script.type === 'pve') typeBadge = '\x1b[33m[PVE]\x1b[0m';
 | 
				
			||||||
 | 
					          else typeBadge = `\x1b[2m[${script.type}]\x1b[0m`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          logger.log('info', `\x1b[1m\x1b[36m►\x1b[0m \x1b[1m${slug}\x1b[0m ${typeBadge}`);
 | 
				
			||||||
 | 
					          logger.log('info', `  \x1b[2m${script.name}\x1b[0m`);
 | 
				
			||||||
 | 
					          logger.log('info', `  ${description}${script.description && script.description.length > 100 ? '...' : ''}`);
 | 
				
			||||||
          logger.log('info', '');
 | 
					          logger.log('info', '');
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.log('info', 'Use "moxytool scripts info <slug>" for more details');
 | 
					        logger.log('info', '\x1b[2mUse "moxytool scripts info <slug>" for more details\x1b[0m');
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import * as projectinfo from '@push.rocks/projectinfo';
 | 
				
			|||||||
import * as smartcli from '@push.rocks/smartcli';
 | 
					import * as smartcli from '@push.rocks/smartcli';
 | 
				
			||||||
import * as smartdelay from '@push.rocks/smartdelay';
 | 
					import * as smartdelay from '@push.rocks/smartdelay';
 | 
				
			||||||
import * as smartfile from '@push.rocks/smartfile';
 | 
					import * as smartfile from '@push.rocks/smartfile';
 | 
				
			||||||
 | 
					import * as smartfuzzy from '@push.rocks/smartfuzzy';
 | 
				
			||||||
import * as smartjson from '@push.rocks/smartjson';
 | 
					import * as smartjson from '@push.rocks/smartjson';
 | 
				
			||||||
import * as smartlog from '@push.rocks/smartlog';
 | 
					import * as smartlog from '@push.rocks/smartlog';
 | 
				
			||||||
import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
 | 
					import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
 | 
				
			||||||
@@ -21,6 +22,7 @@ export {
 | 
				
			|||||||
  smartcli,
 | 
					  smartcli,
 | 
				
			||||||
  smartdelay,
 | 
					  smartdelay,
 | 
				
			||||||
  smartfile,
 | 
					  smartfile,
 | 
				
			||||||
 | 
					  smartfuzzy,
 | 
				
			||||||
  smartjson,
 | 
					  smartjson,
 | 
				
			||||||
  smartlog,
 | 
					  smartlog,
 | 
				
			||||||
  smartlogDestinationLocal,
 | 
					  smartlogDestinationLocal,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user