media:salamand user menu | explorer ext 1/2

This commit is contained in:
babayaga 2025-08-11 17:19:38 +02:00
parent 207cb53f6f
commit c7f92ece52
40 changed files with 6617 additions and 14 deletions

View File

@ -48,6 +48,10 @@ export const defaultOptions = (yargs) => {
describe: 'Preserve alpha channel from input image',
type: 'boolean',
default: true
}).option('jpg', {
describe: 'Convert PNG output to JPG format and delete PNG',
type: 'boolean',
default: false
});
};
export const command = 'background:remove:bria';
@ -69,6 +73,7 @@ export async function handler(argv) {
options.sync = argv.sync;
options.contentModeration = argv.contentModeration;
options.preserveAlpha = argv.preserveAlpha;
options.jpg = argv.jpg;
logger.info("Removing background with Bria AI options:", {
sync: options.sync,
contentModeration: options.contentModeration,
@ -78,4 +83,4 @@ export async function handler(argv) {
await briaBackgroundRemove(options);
}
cli.command(command, desc, builder, handler);
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFja2dyb3VuZC1yZW1vdmUtYnJpYS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb21tYW5kcy9iYWNrZ3JvdW5kLXJlbW92ZS1icmlhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNsRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFBO0FBQ3BDLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxXQUFXLENBQUE7QUFDL0IsT0FBTyxFQUNILFFBQVEsRUFDUixRQUFRLEVBQ1gsTUFBTSxZQUFZLENBQUE7QUFFbkIsT0FBTyxFQUNILG9CQUFvQixFQUV2QixNQUFNLCtDQUErQyxDQUFBO0FBRXRELE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxDQUFDLEtBQWUsRUFBRSxFQUFFO0lBQzlDLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDdkIsUUFBUSxFQUFFLGtCQUFrQjtRQUM1QixZQUFZLEVBQUUsSUFBSTtLQUNyQixDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRTtRQUNiLFFBQVEsRUFBRSxrQkFBa0I7S0FDL0IsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUU7UUFDZixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSxnQ0FBZ0M7UUFDMUMsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx5Q0FBeUM7UUFDbkQsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx3QkFBd0I7UUFDbEMsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDakIsT0FBTyxFQUFFLEtBQUs7UUFDZCxRQUFRLEVBQUUsd0JBQXdCO1FBQ2xDLElBQUksRUFBRSxTQUFTO0tBQ2xCLENBQUMsQ0FBQyxNQUFNLENBQUMsVUFBVSxFQUFFO1FBQ2xCLFFBQVEsRUFBRSxzQ0FBc0M7UUFDaEQsSUFBSSxFQUFFLFFBQVE7UUFDZCxPQUFPLEVBQUUsTUFBTTtLQUNsQixDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRTtRQUNmLE9BQU8sRUFBRSxJQUFJO1FBQ2IsUUFBUSxFQUFFLCtDQUErQztRQUN6RCxJQUFJLEVBQUUsU0FBUztLQUNsQixDQUFDLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRTtRQUNoQixRQUFRLEVBQUUsMENBQTBDO1FBQ3BELElBQUksRUFBRSxRQUFRO0tBQ2pCLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFO1FBQ2QsUUFBUSxFQUFFLDBDQUEwQztRQUNwRCxJQUFJLEVBQUUsU0FBUztRQUNmLE9BQU8sRUFBRSxJQUFJO0tBQ2hCLENBQUMsQ0FBQyxNQUFNLENBQUMsbUJBQW1CLEVBQUU7UUFDM0IsUUFBUSxFQUFFLDJCQUEyQjtRQUNyQyxJQUFJLEVBQUUsU0FBUztRQUNmLE9BQU8sRUFBRSxLQUFLO0tBQ2pCLENBQUMsQ0FBQyxNQUFNLENBQUMsZUFBZSxFQUFFO1FBQ3ZCLFFBQVEsRUFBRSx5Q0FBeUM7UUFDbkQsSUFBSSxFQUFFLFNBQVM7UUFDZixPQUFPLEVBQUUsSUFBSTtLQUNoQixDQUFDLENBQUE7QUFDTixDQUFDLENBQUE7QUFFRCxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsd0JBQXdCLENBQUM7QUFDaEQsTUFBTSxDQUFDLE1BQU0sSUFBSSxHQUFHLDZDQUE2QyxDQUFDO0FBQ2xFLE1BQU0sQ0FBQyxNQUFNLE9BQU8sR0FBRyxjQUFjLENBQUM7QUFFdEMsTUFBTSxDQUFDLEtBQUssVUFBVSxPQUFPLENBQUMsSUFBbUI7SUFDN0MsUUFBUSxFQUFFLENBQUE7SUFDVixNQUFNLE9BQU8sR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFnQyxDQUFBO0lBQzdELE1BQU0sQ0FBQyxRQUFRLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFlLENBQUE7SUFDbEQsTUFBTSxNQUFNLEdBQVEsY0FBYyxFQUFFLENBQUE7SUFFcEMsNkRBQTZEO0lBQzdELE9BQU8sQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLGNBQWMsSUFBSSxNQUFNLEVBQUUsSUFBSSxFQUFFLEdBQUcsQ0FBQztJQUVuRixJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ2xCLE1BQU0sQ0FBQyxLQUFLLENBQUMsc0lBQXNJLENBQUMsQ0FBQztRQUNySixNQUFNLENBQUMsSUFBSSxDQUFDLDRDQUE0QyxDQUFDLENBQUM7UUFDMUQsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNwQixDQUFDO0lBRUQsMERBQTBEO0lBQzFELE9BQU8sQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLElBQWUsQ0FBQztJQUNwQyxPQUFPLENBQUMsaUJBQWlCLEdBQUcsSUFBSSxDQUFDLGlCQUE0QixDQUFDO0lBQzlELE9BQU8sQ0FBQyxhQUFhLEdBQUcsSUFBSSxDQUFDLGFBQXdCLENBQUM7SUFFdEQsTUFBTSxDQUFDLElBQUksQ0FBQywyQ0FBMkMsRUFBRTtRQUNyRCxJQUFJLEVBQUUsT0FBTyxDQUFDLElBQUk7UUFDbEIsaUJBQWlCLEVBQUUsT0FBTyxDQUFDLGlCQUFpQjtRQUM1QyxhQUFhLEVBQUUsT0FBTyxDQUFDLGFBQWE7UUFDcEMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0tBQzdDLENBQUMsQ0FBQztJQUVILE1BQU0sb0JBQW9CLENBQUMsT0FBTyxDQUFDLENBQUM7QUFDeEMsQ0FBQztBQUVELEdBQUcsQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUEifQ==
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFja2dyb3VuZC1yZW1vdmUtYnJpYS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb21tYW5kcy9iYWNrZ3JvdW5kLXJlbW92ZS1icmlhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNsRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFBO0FBQ3BDLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxXQUFXLENBQUE7QUFDL0IsT0FBTyxFQUNILFFBQVEsRUFDUixRQUFRLEVBQ1gsTUFBTSxZQUFZLENBQUE7QUFFbkIsT0FBTyxFQUNILG9CQUFvQixFQUV2QixNQUFNLCtDQUErQyxDQUFBO0FBRXRELE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxDQUFDLEtBQWUsRUFBRSxFQUFFO0lBQzlDLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDdkIsUUFBUSxFQUFFLGtCQUFrQjtRQUM1QixZQUFZLEVBQUUsSUFBSTtLQUNyQixDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRTtRQUNiLFFBQVEsRUFBRSxrQkFBa0I7S0FDL0IsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUU7UUFDZixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSxnQ0FBZ0M7UUFDMUMsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx5Q0FBeUM7UUFDbkQsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx3QkFBd0I7UUFDbEMsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDakIs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

View File

@ -0,0 +1,6 @@
import * as CLI from 'yargs';
export declare const defaultOptions: (yargs: CLI.Argv) => any;
export declare const command = "register-commands";
export declare const desc = "Register all pm-media commands in Salamander menu";
export declare const builder: (yargs: CLI.Argv) => any;
export declare function handler(argv: CLI.Arguments): Promise<void>;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
import * as CLI from 'yargs';
export declare const defaultOptions: (yargs: CLI.Argv) => any;
export declare const command = "register-explorer";
export declare const desc = "Register pm-media commands in Windows Explorer context menu for image files";
export declare const builder: (yargs: CLI.Argv) => any;
export declare function handler(argv: CLI.Arguments): Promise<void>;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
import * as CLI from 'yargs';
export declare const defaultOptions: (yargs: CLI.Argv) => any;
export declare const command = "salamander";
export declare const desc = "Generate Salamander file manager menu entries from JSON configuration";
export declare const builder: (yargs: CLI.Argv) => any;
export declare function handler(argv: CLI.Arguments): Promise<void>;

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,7 @@ export interface BriaBackgroundRemoveOptions extends IOptions {
sync?: boolean;
contentModeration?: boolean;
preserveAlpha?: boolean;
jpg?: boolean;
}
export declare function removeBriaBackground(inputPath: string, outputPath: string, options: BriaBackgroundRemoveOptions): Promise<void>;
export declare const briaBackgroundRemove: (options: BriaBackgroundRemoveOptions) => Promise<{

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,130 @@
export interface SalamanderMenuItem {
name: string;
command?: string;
arguments?: string;
initialDirectory?: string;
executeThoughShell?: boolean;
closeShellWindow?: boolean;
openShellWindow?: boolean;
icon?: string;
showInToolbar?: boolean;
type?: 'command' | 'submenu' | 'submenu-end';
children?: SalamanderMenuItem[];
}
export interface SalamanderMenuConfig {
baseKey: string;
startIndex: number;
items: SalamanderMenuItem[];
}
export interface RegistryEntry {
key: string;
values: Record<string, string | number>;
}
export declare class SalamanderMenuGenerator {
private config;
private baseKey;
constructor(config: SalamanderMenuConfig);
/**
* Parse existing registry file to find the highest menu index
*/
static parseExistingRegistry(registryPath: string): number;
/**
* Generate registry entries from menu configuration
*/
generateRegistryEntries(): RegistryEntry[];
/**
* Convert menu item to registry values
*/
private itemToRegistryValues;
/**
* Escape arguments for registry format
*/
private escapeArguments;
/**
* Generate registry file content
*/
generateRegistryFile(): string;
/**
* Save registry file to disk
*/
saveRegistryFile(outputPath: string): void;
/**
* Load menu configuration from JSON file
*/
static loadFromJson(jsonPath: string): SalamanderMenuConfig;
/**
* Find insertion point in existing registry
*/
static findInsertionPoint(registryPath: string, groupName?: string): number;
}
/**
* Windows Registry helper for direct registry operations using regedit package
*/
export declare class WindowsRegistry {
private static isWindows;
private static ensureRegedit;
/**
* Read all User Menu entries from registry
*/
static readUserMenuEntries(baseKey?: string): Promise<Record<string, any>>;
/**
* Read specific menu entry by index
*/
static readMenuEntry(index: number, baseKey?: string): Promise<Record<string, any>>;
/**
* Get all existing menu indices
*/
static getExistingMenuIndices(baseKey?: string): Promise<number[]>;
/**
* Get next available menu index
*/
static getNextMenuIndex(baseKey?: string): Promise<number>;
/**
* Find insertion point for a specific group
*/
static findGroupInsertionPoint(groupName: string, baseKey?: string): Promise<number>;
/**
* Write registry values for a menu entry
*/
static writeMenuEntry(index: number, values: Record<string, any>, baseKey?: string): Promise<void>;
/**
* Delete a menu entry
*/
static deleteMenuEntry(index: number, baseKey?: string): Promise<void>;
/**
* List all menu entries with their details
*/
static listAllMenuEntries(baseKey?: string): Promise<Array<{
index: number;
name: string;
type: string;
command?: string;
}>>;
}
/**
* Extended Salamander Menu Generator with direct registry support
*/
export declare class SalamanderMenuGeneratorRegistry extends SalamanderMenuGenerator {
/**
* Apply menu configuration directly to Windows registry
*/
applyToRegistry(): Promise<void>;
/**
* Remove menu entries from Windows registry
*/
removeFromRegistry(): Promise<void>;
/**
* Auto-detect insertion point from Windows registry
*/
static autoDetectInsertionPoint(groupName?: string): Promise<number>;
/**
* List current menu entries from registry
*/
static listCurrentMenuEntries(): Promise<Array<{
index: number;
name: string;
type: string;
command?: string;
}>>;
}
export default SalamanderMenuGenerator;

File diff suppressed because one or more lines are too long

View File

@ -5,3 +5,6 @@ import './commands/watermark.js';
import './commands/background-remove.js';
import './commands/background-remove-bria.js';
import './commands/crop-foreground.js';
import './commands/salamander.js';
import './commands/register-commands.js';
import './commands/register-explorer.js';

View File

@ -8,6 +8,9 @@ import './commands/watermark.js';
import './commands/background-remove.js';
import './commands/background-remove-bria.js';
import './commands/crop-foreground.js';
import './commands/salamander.js';
import './commands/register-commands.js';
import './commands/register-explorer.js';
const argv = cli.argv;
if (argv.h || argv.help) {
cli.showHelp();
@ -16,4 +19,4 @@ if (argv.h || argv.help) {
else if (argv.v || argv.version) {
process.exit();
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9tYWluLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFDQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQUMsUUFBUSxFQUFFLENBQUE7QUFFaEQsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLFVBQVUsQ0FBQTtBQUU5QixPQUFPLHNCQUFzQixDQUFBO0FBQzdCLE9BQU8sdUJBQXVCLENBQUE7QUFDOUIsT0FBTyx5QkFBeUIsQ0FBQTtBQUNoQyxPQUFPLGlDQUFpQyxDQUFBO0FBQ3hDLE9BQU8sc0NBQXNDLENBQUE7QUFDN0MsT0FBTywrQkFBK0IsQ0FBQTtBQUV0QyxNQUFNLElBQUksR0FBUSxHQUFHLENBQUMsSUFBSSxDQUFDO0FBRTNCLElBQUksSUFBSSxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDdEIsR0FBRyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQ2YsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO0FBQ25CLENBQUM7S0FBTSxJQUFJLElBQUksQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2hDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUNuQixDQUFDIn0=
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9tYWluLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFDQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQUMsUUFBUSxFQUFFLENBQUE7QUFFaEQsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLFVBQVUsQ0FBQTtBQUU5QixPQUFPLHNCQUFzQixDQUFBO0FBQzdCLE9BQU8sdUJBQXVCLENBQUE7QUFDOUIsT0FBTyx5QkFBeUIsQ0FBQTtBQUNoQyxPQUFPLGlDQUFpQyxDQUFBO0FBQ3hDLE9BQU8sc0NBQXNDLENBQUE7QUFDN0MsT0FBTywrQkFBK0IsQ0FBQTtBQUN0QyxPQUFPLDBCQUEwQixDQUFBO0FBQ2pDLE9BQU8saUNBQWlDLENBQUE7QUFDeEMsT0FBTyxpQ0FBaUMsQ0FBQTtBQUV4QyxNQUFNLElBQUksR0FBUSxHQUFHLENBQUMsSUFBSSxDQUFDO0FBRTNCLElBQUksSUFBSSxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDdEIsR0FBRyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQ2YsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO0FBQ25CLENBQUM7S0FBTSxJQUFJLElBQUksQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2hDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUNuQixDQUFDIn0=

View File

@ -0,0 +1,52 @@
{
"baseKey": "HKEY_CURRENT_USER\\Software\\Altap\\Altap Salamander 4.0\\User Menu",
"startIndex": 5,
"items": [
{
"name": "Background Remove (Bria)",
"command": "pm-media",
"arguments": "background:remove:bria --alt=true --logLevel=info --src=\\\"$(FullName)/**/*.+(&{IMAGES})\\\" --dst=\\\"&{SRC_DIR}/&{SRC_NAME}_nobg.png\\\" --jpg",
"initialDirectory": "$(FullPath)",
"executeThoughShell": true,
"closeShellWindow": false,
"openShellWindow": true,
"icon": "",
"showInToolbar": true
},
{
"name": "Convert Tools",
"children": [
{
"name": "To WebP",
"command": "pm-media",
"arguments": "convert --alt=true --logLevel=info --src=\\\"$(FullName)/**/*.+(&{IMAGES})\\\" --dst=\\\"&{SRC_DIR}/&{SRC_NAME}.webp\\\"",
"initialDirectory": "$(FullPath)",
"executeThoughShell": true,
"closeShellWindow": true,
"openShellWindow": true,
"showInToolbar": true
},
{
"name": "To PNG",
"command": "pm-media",
"arguments": "convert --alt=true --logLevel=info --src=\\\"$(FullName)/**/*.+(&{IMAGES})\\\" --dst=\\\"&{SRC_DIR}/&{SRC_NAME}.png\\\"",
"initialDirectory": "$(FullPath)",
"executeThoughShell": true,
"closeShellWindow": true,
"openShellWindow": true,
"showInToolbar": true
}
]
},
{
"name": "PDF to Images",
"command": "pm-media",
"arguments": "pdf2jpg --alt=true --logLevel=info --src=\\\"$(FullName)\\\" --dst=\\\"&{SRC_DIR}/&{SRC_NAME}\\\"",
"initialDirectory": "$(FullPath)",
"executeThoughShell": true,
"closeShellWindow": false,
"openShellWindow": true,
"showInToolbar": true
}
]
}

View File

@ -25,6 +25,7 @@
"mupdf": "^1.3.3",
"novita-sdk": "^1.0.37",
"p-map": "^7.0.3",
"regedit": "^5.1.4",
"replicate": "^1.0.1",
"sharp": "^0.34.2",
"tslog": "^4.9.3",
@ -2207,7 +2208,6 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@ -3205,6 +3205,12 @@
],
"license": "BSD-3-Clause"
},
"node_modules/if-async": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/if-async/-/if-async-3.7.4.tgz",
"integrity": "sha512-BFEH2mZyeF6KZKaKLVPZ0wMjIiWOdjvZ7zbx8ENec0qfZhJwKFbX/4jKM5LTKyJEc/GOqUKiiJ2IFKT9yWrZqA==",
"license": "MIT"
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -3615,7 +3621,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/mupdf": {
@ -4011,6 +4016,18 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/regedit": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/regedit/-/regedit-5.1.4.tgz",
"integrity": "sha512-3VQ8BY2unUdl4nSx19QAn+pUlkqJhRbIsQc0zciWIVmELLXQLIHvdytUfcI56XYJZj3r9rkb7NTKxkQaXSYnow==",
"license": "MIT",
"dependencies": {
"debug": "^4.1.0",
"if-async": "^3.7.4",
"stream-slicer": "0.0.6",
"through2": "^0.6.3"
}
},
"node_modules/replicate": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/replicate/-/replicate-1.0.1.tgz",
@ -4333,6 +4350,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/stream-slicer": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/stream-slicer/-/stream-slicer-0.0.6.tgz",
"integrity": "sha512-QsY0LbweYE5L+e+iBQgtkM5WUIf7+kCMA/m2VULv8rEEDDnlDPsPvOHH4nli6uaZOKQEt64u65h0l/eeZo7lCw==",
"license": "MIT"
},
"node_modules/strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
@ -4542,6 +4565,40 @@
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"license": "MIT"
},
"node_modules/through2": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
"integrity": "sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==",
"license": "MIT",
"dependencies": {
"readable-stream": ">=1.0.33-1 <1.1.0-0",
"xtend": ">=4.0.0 <4.1.0-0"
}
},
"node_modules/through2/node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"license": "MIT"
},
"node_modules/through2/node_modules/readable-stream": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"node_modules/through2/node_modules/string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
"license": "MIT"
},
"node_modules/timed-out": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
@ -6298,7 +6355,6 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"requires": {
"ms": "^2.1.3"
}
@ -6969,6 +7025,11 @@
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"if-async": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/if-async/-/if-async-3.7.4.tgz",
"integrity": "sha512-BFEH2mZyeF6KZKaKLVPZ0wMjIiWOdjvZ7zbx8ENec0qfZhJwKFbX/4jKM5LTKyJEc/GOqUKiiJ2IFKT9yWrZqA=="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -7248,8 +7309,7 @@
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"mupdf": {
"version": "1.26.2",
@ -7494,6 +7554,17 @@
"string_decoder": "^1.3.0"
}
},
"regedit": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/regedit/-/regedit-5.1.4.tgz",
"integrity": "sha512-3VQ8BY2unUdl4nSx19QAn+pUlkqJhRbIsQc0zciWIVmELLXQLIHvdytUfcI56XYJZj3r9rkb7NTKxkQaXSYnow==",
"requires": {
"debug": "^4.1.0",
"if-async": "^3.7.4",
"stream-slicer": "0.0.6",
"through2": "^0.6.3"
}
},
"replicate": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/replicate/-/replicate-1.0.1.tgz",
@ -7703,6 +7774,11 @@
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
"dev": true
},
"stream-slicer": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/stream-slicer/-/stream-slicer-0.0.6.tgz",
"integrity": "sha512-QsY0LbweYE5L+e+iBQgtkM5WUIf7+kCMA/m2VULv8rEEDDnlDPsPvOHH4nli6uaZOKQEt64u65h0l/eeZo7lCw=="
},
"strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
@ -7858,6 +7934,38 @@
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
},
"through2": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
"integrity": "sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==",
"requires": {
"readable-stream": ">=1.0.33-1 <1.1.0-0",
"xtend": ">=4.0.0 <4.1.0-0"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
},
"readable-stream": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
}
}
},
"timed-out": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",

View File

@ -29,6 +29,7 @@
"mupdf": "^1.3.3",
"novita-sdk": "^1.0.37",
"p-map": "^7.0.3",
"regedit": "^5.1.4",
"replicate": "^1.0.1",
"sharp": "^0.34.2",
"tslog": "^4.9.3",

View File

@ -0,0 +1,14 @@
REGEDIT4
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\1]
"Item Name"="Watermark"
"Command"="pm-media"
"Arguments"="watermark --src=\\" \\ --registry --group Media"
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001

View File

@ -57,6 +57,10 @@ export const defaultOptions = (yargs: CLI.Argv) => {
describe: 'Preserve alpha channel from input image',
type: 'boolean',
default: true
}).option('jpg', {
describe: 'Convert PNG output to JPG format and delete PNG',
type: 'boolean',
default: false
})
}
@ -83,6 +87,7 @@ export async function handler(argv: CLI.Arguments) {
options.sync = argv.sync as boolean;
options.contentModeration = argv.contentModeration as boolean;
options.preserveAlpha = argv.preserveAlpha as boolean;
options.jpg = argv.jpg as boolean;
logger.info("Removing background with Bria AI options:", {
sync: options.sync,

View File

@ -0,0 +1,260 @@
import * as CLI from 'yargs'
import * as fs from 'fs'
import * as path from 'path'
import { logger } from '../index.js'
import { cli } from '../cli.js'
import { defaults } from '../_cli.js'
import { SalamanderMenuGeneratorRegistry, WindowsRegistry } from '../lib/salamander/index.js'
export const defaultOptions = (yargs: CLI.Argv) => {
return yargs.option('group', {
describe: 'Group name to register commands under',
type: 'string',
default: 'Media'
}).option('dry', {
default: false,
describe: 'Show what would be registered without actually registering',
type: 'boolean'
}).option('force', {
default: false,
describe: 'Force register even if command already exists',
type: 'boolean'
}).option('logLevel', {
describe: 'Log level : warn, info, debug, error',
type: 'string',
default: 'info'
})
}
export const command = 'register-commands'
export const desc = 'Register all pm-media commands in Salamander menu'
export const builder = defaultOptions
interface CommandInfo {
name: string
command: string
args: string
description: string
}
// Basic command mappings - users can extend these as needed
const COMMAND_MAPPINGS: Record<string, CommandInfo> = {
'resize': {
name: 'Resize Images',
command: 'pm-media',
args: 'resize --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_resized.&{SRC_EXT}\"',
description: 'Resize images'
},
'watermark': {
name: 'Add Watermark',
command: 'pm-media',
args: 'watermark --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --watermark=\"&{POLYMECH-ROOT}/nordin-ex/branding/polymech-saw-ex.svg\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_watermarked.&{SRC_EXT}\"',
description: 'Add watermark to images'
},
'background-remove': {
name: 'Remove Background',
command: 'pm-media',
args: 'background:remove --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_bg_removed.&{SRC_EXT}\"',
description: 'Remove background from images'
},
'background-remove-bria': {
name: 'Remove Background (Bria AI)',
command: 'pm-media',
args: 'background:remove:bria --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_bria_bg_removed.&{SRC_EXT}\"',
description: 'Remove background using Bria AI'
},
'convert': {
name: 'Convert Format',
command: 'pm-media',
args: 'convert --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_converted.jpg\"',
description: 'Convert image format'
},
'pdf2jpg': {
name: 'PDF to JPG',
command: 'pm-media',
args: 'pdf2jpg --alt=true --logLevel=info --src=\"$(FullName)/**/*.pdf\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_page.jpg\"',
description: 'Convert PDF pages to JPG images'
},
'svg2jpg': {
name: 'SVG to JPG',
command: 'pm-media',
args: 'svg2jpg --alt=true --logLevel=info --src=\"$(FullName)/**/*.svg\" --dst=\"&{SRC_DIR}/&{SRC_NAME}.jpg\"',
description: 'Convert SVG to JPG'
},
'crop-foreground': {
name: 'Crop Foreground',
command: 'pm-media',
args: 'crop-foreground --alt=true --logLevel=info --src=\"$(FullName)/**/*.+(&{IMAGES})\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_cropped.&{SRC_EXT}\"',
description: 'Crop to foreground content'
}
}
async function getAvailableCommands(): Promise<string[]> {
const commandsDir = path.join(process.cwd(), 'src', 'commands')
const files = fs.readdirSync(commandsDir)
return files
.filter(file => file.endsWith('.ts') &&
file !== 'salamander.ts' &&
file !== 'register-commands.ts')
.map(file => file.replace('.ts', ''))
.filter(cmd => COMMAND_MAPPINGS[cmd]) // Only include commands we have mappings for
}
export async function handler(argv: CLI.Arguments) {
defaults()
logger.settings.minLevel = argv.logLevel as any
const options = {
group: argv.group as string,
dry: argv.dry as boolean,
force: argv.force as boolean
}
try {
logger.info('Scanning available pm-media commands...')
const availableCommands = await getAvailableCommands()
logger.info(`Found ${availableCommands.length} commands: ${availableCommands.join(', ')}`)
if (options.dry) {
logger.info('\n=== DRY RUN - Commands that would be registered ===')
for (const cmdName of availableCommands) {
const cmdInfo = COMMAND_MAPPINGS[cmdName]
logger.info(`\nCommand: ${cmdName}`)
logger.info(` Name: ${cmdInfo.name}`)
logger.info(` Args: ${cmdInfo.args}`)
logger.info(` Group: ${options.group}`)
}
return
}
// Check which commands already exist
logger.info('Checking existing registry entries...')
const existingEntries = await WindowsRegistry.listAllMenuEntries()
const existingCommandNames = existingEntries
.filter(entry => entry.type === 'command') // Only commands, not submenus
.map(entry => entry.name)
logger.info(`Found ${existingCommandNames.length} existing menu entries`)
let registeredCount = 0
let skippedCount = 0
// Check if the target group exists, create it if it doesn't
const groupExists = existingEntries.some(entry =>
entry.type === 'submenu' && entry.name === options.group
)
let groupInsertionPoint: number
if (!groupExists) {
logger.info(`Creating group: ${options.group}`)
// Get the starting index for the new submenu
groupInsertionPoint = await WindowsRegistry.getNextMenuIndex()
// Create the group submenu start
const groupStartValues = {
'Item Name': options.group,
'Command': '',
'Arguments': '',
'Initial Directory': '',
'Execute Through Shell': 'dword:00000000',
'Close Shell Window': 'dword:00000000',
'Open Shell Window': 'dword:00000000',
'Icon': '',
'Type': 'dword:00000001',
'Show In Toolbar': 'dword:00000001'
}
await WindowsRegistry.writeMenuEntry(groupInsertionPoint, groupStartValues)
logger.info(`✓ Created group start: ${options.group} at index ${groupInsertionPoint}`)
// Increment for the first command slot
groupInsertionPoint++
} else {
logger.info(`Group '${options.group}' already exists`)
groupInsertionPoint = await WindowsRegistry.findGroupInsertionPoint(options.group)
}
for (const cmdName of availableCommands) {
const cmdInfo = COMMAND_MAPPINGS[cmdName]
// Check if command already exists
const exists = existingCommandNames.some(name =>
name.toLowerCase().includes(cmdInfo.name.toLowerCase()) ||
cmdInfo.name.toLowerCase().includes(name.toLowerCase())
)
if (exists && !options.force) {
logger.info(`Skipping '${cmdInfo.name}' - already exists (use --force to override)`)
skippedCount++
continue
}
logger.info(`Registering: ${cmdInfo.name}`)
try {
// Build registry values for the command
const values = {
'Item Name': cmdInfo.name,
'Command': cmdInfo.command,
'Arguments': cmdInfo.args,
'Initial Directory': '$(FullPath)',
'Execute Through Shell': 'dword:00000001',
'Close Shell Window': 'dword:00000001',
'Open Shell Window': 'dword:00000001',
'Icon': '',
'Type': 'dword:00000000',
'Show In Toolbar': 'dword:00000001'
}
await WindowsRegistry.writeMenuEntry(groupInsertionPoint, values)
registeredCount++
logger.info(`✓ Registered: ${cmdInfo.name} at index ${groupInsertionPoint}`)
// Increment insertion point for next command to insert them sequentially within the group
groupInsertionPoint++
} catch (error) {
logger.error(`Failed to register '${cmdInfo.name}':`, error)
}
}
// If we created a new group and registered commands, add the submenu end
if (!groupExists && registeredCount > 0) {
const groupEndValues = {
'Item Name': '(Submenu End)',
'Command': '',
'Arguments': '',
'Initial Directory': '',
'Execute Through Shell': 'dword:00000000',
'Close Shell Window': 'dword:00000000',
'Open Shell Window': 'dword:00000000',
'Icon': '',
'Type': 'dword:00000002',
'Show In Toolbar': 'dword:00000000'
}
await WindowsRegistry.writeMenuEntry(groupInsertionPoint, groupEndValues)
logger.info(`✓ Created group end at index ${groupInsertionPoint}`)
}
logger.info(`\n=== Registration Complete ===`)
logger.info(`Registered: ${registeredCount} commands`)
logger.info(`Skipped: ${skippedCount} commands`)
logger.info(`Total available: ${availableCommands.length} commands`)
if (registeredCount > 0) {
logger.info('\nCommands have been registered in Salamander\'s User Menu.')
logger.info('Restart Salamander to see the new menu entries.')
}
} catch (error) {
logger.error('Failed to register commands:', error)
process.exit(1)
}
}
cli.command(command, desc, builder, handler)

View File

@ -0,0 +1,240 @@
import * as CLI from 'yargs'
import { logger } from '../index.js'
import { cli } from '../cli.js'
import { defaults } from '../_cli.js'
import regedit, { RegistryItemPutCollection, REG_SZ_Value } from 'regedit'
export const defaultOptions = (yargs: CLI.Argv) => {
return yargs.option('group', {
describe: 'Group name for the context menu items',
type: 'string',
default: 'PM-Media'
}).option('unregister', {
default: false,
describe: 'Remove the shell extensions from Explorer',
type: 'boolean'
}).option('dry', {
default: false,
describe: 'Show what would be registered without actually doing it',
type: 'boolean'
}).option('logLevel', {
describe: 'Log level : warn, info, debug, error',
type: 'string',
default: 'info'
})
}
export const command = 'register-explorer'
export const desc = 'Register pm-media commands in Windows Explorer context menu for image files'
export const builder = defaultOptions
// Image file extensions to register with
const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.svg']
// Command mappings for Explorer context menu
const EXPLORER_COMMANDS = [
{
name: 'Convert to JPG',
command: 'pm-media',
args: 'convert --alt=true --logLevel=info --src="%1" --dst="%~dpn1_converted.jpg"'
},
{
name: 'Resize Image',
command: 'pm-media',
args: 'resize --alt=true --logLevel=info --src="%1" --dst="%~dpn1_resized%~x1"'
},
{
name: 'Add Watermark',
command: 'pm-media',
args: 'watermark --alt=true --logLevel=info --src="%1" --watermark="&{POLYMECH-ROOT}/nordin-ex/branding/polymech-saw-ex.svg" --dst="%~dpn1_watermarked%~x1"'
},
{
name: 'Remove Background',
command: 'pm-media',
args: 'background:remove --alt=true --logLevel=info --src="%1" --dst="%~dpn1_bg_removed%~x1"'
}
]
interface ExplorerRegistryEntry {
keyPath: string
valueName: string
value: string
type: 'REG_SZ' | 'REG_DWORD'
}
/**
* Generate registry entries for Windows Explorer shell extensions
*/
function generateExplorerRegistryEntries(groupName: string): ExplorerRegistryEntry[] {
const entries: ExplorerRegistryEntry[] = []
for (const ext of IMAGE_EXTENSIONS) {
const extKey = `HKCU\\Software\\Classes\\${ext}`
// For each command, create the shell extension entries
for (let i = 0; i < EXPLORER_COMMANDS.length; i++) {
const cmd = EXPLORER_COMMANDS[i]
const cmdKey = `${extKey}\\shell\\${groupName}\\shell\\${cmd.name.replace(/\s+/g, '')}`
// Command display name
entries.push({
keyPath: cmdKey,
valueName: '',
value: cmd.name,
type: 'REG_SZ'
})
// Command execution
entries.push({
keyPath: `${cmdKey}\\command`,
valueName: '',
value: `${cmd.command} ${cmd.args}`,
type: 'REG_SZ'
})
}
// Set the group submenu display name
entries.push({
keyPath: `${extKey}\\shell\\${groupName}`,
valueName: '',
value: groupName,
type: 'REG_SZ'
})
// Set submenu position (optional)
entries.push({
keyPath: `${extKey}\\shell\\${groupName}`,
valueName: 'Position',
value: 'Middle',
type: 'REG_SZ'
})
}
return entries
}
/**
* Write registry entries for Explorer shell extensions
*/
async function writeExplorerRegistryEntries(entries: ExplorerRegistryEntry[]): Promise<void> {
const promisified = regedit.promisified
for (const entry of entries) {
try {
// Create the key if it doesn't exist
await promisified.createKey([entry.keyPath])
// Write the value using correct regedit format
// For default values, use empty string as key name
const valueName = entry.valueName || ''
const valuesToWrite: RegistryItemPutCollection = {
[entry.keyPath]: {
[valueName]: {
value: entry.value,
type: entry.type as 'REG_SZ'
}
}
}
await promisified.putValue(valuesToWrite)
logger.debug(`✓ Created registry entry: ${entry.keyPath} = ${entry.value}`)
} catch (error) {
logger.error(`Failed to create registry entry ${entry.keyPath}:`, error)
logger.error(`Entry details:`, {
keyPath: entry.keyPath,
valueName: entry.valueName,
value: entry.value,
type: entry.type
})
}
}
}
/**
* Remove registry entries for Explorer shell extensions
*/
async function removeExplorerRegistryEntries(groupName: string): Promise<void> {
const promisified = regedit.promisified
for (const ext of IMAGE_EXTENSIONS) {
const extKey = `HKCU\\Software\\Classes\\${ext}`
const groupKey = `${extKey}\\shell\\${groupName}`
try {
// Check if the key exists
const result = await promisified.list([groupKey])
if (result[groupKey].exists) {
await promisified.deleteKey([groupKey])
logger.info(`✓ Removed shell extension for ${ext}`)
}
} catch (error) {
logger.debug(`Could not remove key ${groupKey}:`, error)
}
}
}
export async function handler(argv: CLI.Arguments) {
defaults()
logger.settings.minLevel = argv.logLevel as any
const options = {
group: argv.group as string,
unregister: argv.unregister as boolean,
dry: argv.dry as boolean
}
if (process.platform !== 'win32') {
logger.error('Windows Explorer shell extensions are only supported on Windows')
process.exit(1)
}
try {
if (options.unregister) {
logger.info(`Removing Explorer shell extensions for group: ${options.group}`)
if (options.dry) {
logger.info('\n=== DRY RUN - Extensions that would be removed ===')
for (const ext of IMAGE_EXTENSIONS) {
logger.info(` ${ext} files: Remove "${options.group}" context menu`)
}
return
}
await removeExplorerRegistryEntries(options.group)
logger.info(`✓ Successfully removed Explorer shell extensions for: ${options.group}`)
} else {
logger.info(`Registering Explorer shell extensions for group: ${options.group}`)
logger.info(`Target file types: ${IMAGE_EXTENSIONS.join(', ')}`)
const entries = generateExplorerRegistryEntries(options.group)
if (options.dry) {
logger.info('\n=== DRY RUN - Extensions that would be registered ===')
for (const ext of IMAGE_EXTENSIONS) {
logger.info(`\n${ext} files:`)
for (const cmd of EXPLORER_COMMANDS) {
logger.info(`${cmd.name}: ${cmd.command} ${cmd.args}`)
}
}
logger.info(`\nTotal registry entries: ${entries.length}`)
return
}
await writeExplorerRegistryEntries(entries)
logger.info(`\n=== Registration Complete ===`)
logger.info(`Registered ${EXPLORER_COMMANDS.length} commands for ${IMAGE_EXTENSIONS.length} file types`)
logger.info(`Total registry entries created: ${entries.length}`)
logger.info('\nExplorer context menu entries have been registered.')
logger.info('You may need to restart Explorer.exe to see the changes.')
}
} catch (error) {
logger.error('Failed to register/unregister Explorer shell extensions:', error)
process.exit(1)
}
}
cli.command(command, desc, builder, handler)

View File

@ -0,0 +1,270 @@
import * as CLI from 'yargs'
import * as path from 'path'
import { logger } from '../index.js'
import { cli } from '../cli.js'
import {
sanitize,
defaults
} from '../_cli.js'
import SalamanderMenuGenerator, { SalamanderMenuConfig, SalamanderMenuGeneratorRegistry, WindowsRegistry } from '../lib/salamander/index.js'
export const defaultOptions = (yargs: CLI.Argv) => {
return yargs.option('config', {
describe: 'JSON configuration file path',
type: 'string'
}).option('output', {
describe: 'Output registry file path',
type: 'string'
}).option('existing', {
describe: 'Path to existing registry file to parse for insertion point',
type: 'string'
}).option('group', {
describe: 'Group name to insert items into (e.g., "Media")',
type: 'string'
}).option('startIndex', {
describe: 'Starting menu index (overrides auto-detection)',
type: 'number'
}).option('debug', {
default: false,
describe: 'Enable internal debug messages',
type: 'boolean'
}).option('verbose', {
default: false,
describe: 'Show internal messages',
type: 'boolean'
}).option('logLevel', {
describe: 'Log level : warn, info, debug, error',
type: 'string',
default: 'info'
}).option('registry', {
describe: 'Apply directly to Windows registry (Windows only)',
type: 'boolean',
default: false
}).option('list', {
describe: 'List current menu entries from registry',
type: 'boolean',
default: false
}).option('remove', {
describe: 'Remove menu entries from registry',
type: 'boolean',
default: false
}).option('name', {
describe: 'Menu item name',
type: 'string'
}).option('command', {
describe: 'Command to execute',
type: 'string'
}).option('args', {
describe: 'Command arguments',
type: 'string'
}).option('workingDir', {
describe: 'Working directory for command execution',
type: 'string',
default: '$(FullPath)'
}).option('icon', {
describe: 'Icon path for menu item',
type: 'string',
default: ''
}).option('executeThoughShell', {
describe: 'Execute command through shell',
type: 'boolean',
default: true
}).option('closeShellWindow', {
describe: 'Close shell window after execution',
type: 'boolean',
default: false
}).option('openShellWindow', {
describe: 'Open shell window during execution',
type: 'boolean',
default: true
}).option('showInToolbar', {
describe: 'Show item in toolbar',
type: 'boolean',
default: true
})
}
export const command = 'salamander';
export const desc = 'Generate Salamander file manager menu entries from JSON configuration';
export const builder = defaultOptions;
export async function handler(argv: CLI.Arguments) {
defaults()
const options = sanitize(argv)
logger.settings.minLevel = options.logLevel as any
try {
// Handle listing current menu entries
if (argv.list) {
if (process.platform !== 'win32') {
logger.error('Registry listing is only supported on Windows');
process.exit(1);
}
logger.info('Current Salamander menu entries:');
const entries = await SalamanderMenuGeneratorRegistry.listCurrentMenuEntries();
for (const entry of entries) {
const typeStr = entry.type === 'submenu' ? '(submenu)' :
entry.type === 'submenu-end' ? '(submenu end)' : '(command)';
const cmdStr = entry.command ? `${entry.command}` : '';
logger.info(` [${entry.index}] ${entry.name} ${typeStr}${cmdStr}`);
}
return;
}
let config: SalamanderMenuConfig;
// Check if we're using individual command line options or JSON config
if (argv.name && argv.command) {
// Create config from command line options
logger.info('Creating menu entry from command line options');
config = {
baseKey: 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu',
startIndex: 1,
items: [{
name: argv.name as string,
command: argv.command as string,
arguments: argv.args as string || '',
initialDirectory: argv.workingDir as string,
executeThoughShell: argv.executeThoughShell as boolean,
closeShellWindow: argv.closeShellWindow as boolean,
openShellWindow: argv.openShellWindow as boolean,
icon: argv.icon as string,
showInToolbar: argv.showInToolbar as boolean
}]
};
} else if (argv.config) {
// Load configuration from JSON
const configPath = argv.config as string;
logger.info(`Loading configuration from: ${configPath}`);
config = SalamanderMenuGenerator.loadFromJson(configPath);
} else {
logger.error('Either --config file or --name and --command options are required');
process.exit(1);
}
// Determine starting index
let startIndex = argv.startIndex as number;
if (!startIndex) {
if (argv.registry && process.platform === 'win32') {
// Use registry to determine insertion point
if (argv.group) {
startIndex = await SalamanderMenuGeneratorRegistry.autoDetectInsertionPoint(argv.group as string);
logger.info(`Found insertion point for group "${argv.group}": index ${startIndex}`);
} else {
startIndex = await SalamanderMenuGeneratorRegistry.autoDetectInsertionPoint();
logger.info(`Detected next available index: ${startIndex}`);
}
} else if (argv.existing) {
const existingPath = argv.existing as string;
if (argv.group) {
startIndex = SalamanderMenuGenerator.findInsertionPoint(existingPath, argv.group as string);
logger.info(`Found insertion point for group "${argv.group}": index ${startIndex}`);
} else {
const maxIndex = SalamanderMenuGenerator.parseExistingRegistry(existingPath);
startIndex = maxIndex + 1;
logger.info(`Detected next available index: ${startIndex}`);
}
} else {
startIndex = config.startIndex || 1;
logger.info(`Using configured start index: ${startIndex}`);
}
}
// Update config with determined start index
config.startIndex = startIndex;
// Handle direct registry operations
if (argv.registry) {
if (process.platform !== 'win32') {
logger.error('Direct registry operations are only supported on Windows');
process.exit(1);
}
const generator = new SalamanderMenuGeneratorRegistry(config);
if (argv.remove) {
logger.info('Removing menu entries from registry...');
await generator.removeFromRegistry();
logger.info('Menu entries removed successfully');
} else {
logger.info('Applying menu entries to registry...');
await generator.applyToRegistry();
logger.info('Menu entries applied successfully');
logger.info('Restart Salamander to see the new menu items');
}
// Show summary
const entries = generator.generateRegistryEntries();
logger.info(`Processed ${entries.length} menu entries`);
if (options.verbose) {
logger.info('Processed entries:');
for (const entry of entries) {
const itemName = entry.values['"Item Name"'] as string;
const type = entry.values['"Type"'] as string;
const typeStr = type === 'dword:00000001' ? '(submenu)' :
type === 'dword:00000002' ? '(submenu end)' : '(command)';
logger.info(` ${itemName} ${typeStr}`);
}
}
return;
}
// Generate .reg file
const generator = new SalamanderMenuGenerator(config);
// Generate output path if not specified
let outputPath = argv.output as string;
if (!outputPath) {
if (argv.config) {
const configPath = argv.config as string;
const configDir = path.dirname(configPath);
const configName = path.basename(configPath, '.json');
outputPath = path.join(configDir, `${configName}-salamander-menu.reg`);
} else {
outputPath = `salamander-menu-${argv.name?.toString().toLowerCase().replace(/\s+/g, '-') || 'item'}.reg`;
}
}
logger.info(`Generating registry file: ${outputPath}`);
// Generate and save registry file
generator.saveRegistryFile(outputPath);
// Generate summary
const entries = generator.generateRegistryEntries();
logger.info(`Successfully generated ${entries.length} menu entries`);
if (options.verbose) {
logger.info('Generated entries:');
for (const entry of entries) {
const itemName = entry.values['"Item Name"'] as string;
const type = entry.values['"Type"'] as string;
const typeStr = type === 'dword:00000001' ? '(submenu)' :
type === 'dword:00000002' ? '(submenu end)' : '(command)';
logger.info(` ${itemName} ${typeStr}`);
}
}
logger.info(`\nTo apply the menu entries:`);
logger.info(`1. Double-click the generated .reg file: ${outputPath}`);
logger.info(`2. Confirm the registry import in Windows`);
logger.info(`3. Restart Salamander to see the new menu items`);
} catch (error) {
logger.error(`Failed to generate Salamander menu:`, error.message);
if (options.debug) {
logger.error(error.stack);
}
process.exit(1);
}
}
cli.command(command, desc, builder, handler)

View File

@ -1,5 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
import sharp from 'sharp';
import pMap from 'p-map';
import { logger } from '../../../index.js';
import { IOptions } from '../../../types.js';
@ -11,6 +12,7 @@ export interface BriaBackgroundRemoveOptions extends IOptions {
sync?: boolean;
contentModeration?: boolean;
preserveAlpha?: boolean;
jpg?: boolean;
}
// Read image file as buffer for Bria API
@ -34,6 +36,27 @@ async function downloadImageFromUrl(imageUrl: string, outputPath: string): Promi
fs.writeFileSync(outputPath, buffer);
}
// Convert PNG to JPG while preserving rotation and metadata
async function convertPngToJpg(pngPath: string, jpgPath: string): Promise<void> {
try {
await sharp(pngPath)
.jpeg({
quality: 95,
progressive: true
})
.withMetadata() // Preserve EXIF data including rotation
.toFile(jpgPath);
// Delete the temporary PNG file
fs.unlinkSync(pngPath);
logger.debug(`Converted PNG to JPG and cleaned up: ${pngPath}${jpgPath}`);
} catch (error) {
logger.error(`Failed to convert PNG to JPG: ${error.message}`);
throw error;
}
}
export async function removeBriaBackground(
inputPath: string,
outputPath: string,
@ -89,8 +112,18 @@ export async function removeBriaBackground(
if (result.result_url || result.image_res) {
// Download the processed image (Bria API uses result_url)
const imageUrl = result.result_url || result.image_res;
await downloadImageFromUrl(imageUrl, outputPath);
logger.info(`Background removed: ${inputPath}${outputPath}`);
if (options.jpg && path.extname(outputPath).toLowerCase() === '.jpg') {
// If JPG conversion is requested and output is JPG, download as PNG first then convert
const tempPngPath = outputPath.replace(/\.jpe?g$/i, '_temp.png');
await downloadImageFromUrl(imageUrl, tempPngPath);
await convertPngToJpg(tempPngPath, outputPath);
logger.info(`Background removed and converted to JPG: ${inputPath}${outputPath}`);
} else {
// Standard PNG output
await downloadImageFromUrl(imageUrl, outputPath);
logger.info(`Background removed: ${inputPath}${outputPath}`);
}
} else {
throw new Error('No image result returned from Bria API');
}

View File

@ -0,0 +1,496 @@
import * as fs from 'fs';
import * as path from 'path';
import regedit from 'regedit';
const promisified = regedit.promisified;
export interface SalamanderMenuItem {
name: string;
command?: string;
arguments?: string;
initialDirectory?: string;
executeThoughShell?: boolean;
closeShellWindow?: boolean;
openShellWindow?: boolean;
icon?: string;
showInToolbar?: boolean;
type?: 'command' | 'submenu' | 'submenu-end';
children?: SalamanderMenuItem[];
}
export interface SalamanderMenuConfig {
baseKey: string;
startIndex: number;
items: SalamanderMenuItem[];
}
export interface RegistryEntry {
key: string;
values: Record<string, string | number>;
}
export class SalamanderMenuGenerator {
private baseKey: string = 'HKEY_CURRENT_USER\\Software\\Altap\\Altap Salamander 4.0\\User Menu';
constructor(private config: SalamanderMenuConfig) {
if (config.baseKey) {
this.baseKey = config.baseKey;
}
}
/**
* Parse existing registry file to find the highest menu index
*/
static parseExistingRegistry(registryPath: string): number {
if (!fs.existsSync(registryPath)) {
return 0;
}
const content = fs.readFileSync(registryPath, 'utf8');
const lines = content.split('\n');
let maxIndex = 0;
for (const line of lines) {
const match = line.match(/\\User Menu\\(\d+)\]/);
if (match) {
const index = parseInt(match[1], 10);
if (index > maxIndex) {
maxIndex = index;
}
}
}
return maxIndex;
}
/**
* Generate registry entries from menu configuration
*/
generateRegistryEntries(): RegistryEntry[] {
const entries: RegistryEntry[] = [];
let currentIndex = this.config.startIndex;
const processItems = (items: SalamanderMenuItem[], isSubMenu = false) => {
for (const item of items) {
if (item.children && item.children.length > 0) {
// Submenu start
entries.push({
key: `[${this.baseKey}\\${currentIndex}]`,
values: this.itemToRegistryValues(item, 'submenu')
});
currentIndex++;
// Process children
processItems(item.children, true);
// Submenu end
entries.push({
key: `[${this.baseKey}\\${currentIndex}]`,
values: {
'"Item Name"': '"(Submenu End)"',
'"Command"': '""',
'"Arguments"': '""',
'"Initial Directory"': '""',
'"Execute Through Shell"': 'dword:00000000',
'"Close Shell Window"': 'dword:00000000',
'"Open Shell Window"': 'dword:00000000',
'"Icon"': '""',
'"Type"': 'dword:00000002',
'"Show In Toolbar"': 'dword:00000000'
}
});
currentIndex++;
} else {
// Regular command
entries.push({
key: `[${this.baseKey}\\${currentIndex}]`,
values: this.itemToRegistryValues(item, 'command')
});
currentIndex++;
}
}
};
processItems(this.config.items);
return entries;
}
/**
* Convert menu item to registry values
*/
private itemToRegistryValues(item: SalamanderMenuItem, type: 'command' | 'submenu'): Record<string, string | number> {
const values: Record<string, string | number> = {
'"Item Name"': `"${item.name}"`,
'"Command"': `"${item.command || ''}"`,
'"Arguments"': `"${this.escapeArguments(item.arguments || '')}"`,
'"Initial Directory"': `"${item.initialDirectory || ''}"`,
'"Execute Through Shell"': item.executeThoughShell !== false ? 'dword:00000001' : 'dword:00000000',
'"Close Shell Window"': item.closeShellWindow === true ? 'dword:00000001' : 'dword:00000000',
'"Open Shell Window"': item.openShellWindow !== false ? 'dword:00000001' : 'dword:00000000',
'"Icon"': `"${item.icon || ''}"`,
'"Type"': type === 'submenu' ? 'dword:00000001' : 'dword:00000000',
'"Show In Toolbar"': item.showInToolbar !== false ? 'dword:00000001' : 'dword:00000000'
};
return values;
}
/**
* Escape arguments for registry format
*/
private escapeArguments(args: string): string {
return args
.replace(/\\/g, '\\\\') // Escape backslashes
.replace(/"/g, '\\\\"'); // Escape quotes
}
/**
* Generate registry file content
*/
generateRegistryFile(): string {
const entries = this.generateRegistryEntries();
let content = 'REGEDIT4\n\n';
for (const entry of entries) {
content += `${entry.key}\n`;
for (const [key, value] of Object.entries(entry.values)) {
content += `${key}=${value}\n`;
}
content += '\n';
}
return content;
}
/**
* Save registry file to disk
*/
saveRegistryFile(outputPath: string): void {
const content = this.generateRegistryFile();
// Ensure output directory exists
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(outputPath, content, 'utf8');
}
/**
* Load menu configuration from JSON file
*/
static loadFromJson(jsonPath: string): SalamanderMenuConfig {
if (!fs.existsSync(jsonPath)) {
throw new Error(`JSON configuration file not found: ${jsonPath}`);
}
const content = fs.readFileSync(jsonPath, 'utf8');
return JSON.parse(content) as SalamanderMenuConfig;
}
/**
* Find insertion point in existing registry
*/
static findInsertionPoint(registryPath: string, groupName?: string): number {
if (!fs.existsSync(registryPath)) {
return 1; // Start at index 1 if no existing registry
}
const maxIndex = this.parseExistingRegistry(registryPath);
if (!groupName) {
return maxIndex + 1;
}
// Find group and insert before submenu end
const content = fs.readFileSync(registryPath, 'utf8');
const lines = content.split('\n');
let inTargetGroup = false;
let groupStartIndex = -1;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Check for menu entry
const menuMatch = line.match(/\\User Menu\\(\d+)\]/);
if (menuMatch) {
const index = parseInt(menuMatch[1], 10);
// Look for item name in next few lines
for (let j = i + 1; j < Math.min(i + 10, lines.length); j++) {
const nameLine = lines[j];
if (nameLine.includes('"Item Name"')) {
const nameMatch = nameLine.match(/"Item Name"="([^"]+)"/);
if (nameMatch) {
const itemName = nameMatch[1];
if (itemName === groupName) {
inTargetGroup = true;
groupStartIndex = index;
} else if (itemName === '(Submenu End)' && inTargetGroup) {
return index; // Insert before submenu end
}
}
break;
}
}
}
}
return maxIndex + 1; // Fallback to end
}
}
/**
* Windows Registry helper for direct registry operations using regedit package
*/
export class WindowsRegistry {
private static isWindows(): boolean {
return process.platform === 'win32' && promisified !== null;
}
private static ensureRegedit(): void {
if (!this.isWindows()) {
throw new Error('Registry operations are only supported on Windows with regedit package installed');
}
}
/**
* Read all User Menu entries from registry
*/
static async readUserMenuEntries(baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<Record<string, any>> {
this.ensureRegedit();
try {
const result = await promisified.list([baseKey]);
return result[baseKey] || { exists: false, keys: [], values: {} };
} catch (error) {
return { exists: false, keys: [], values: {} };
}
}
/**
* Read specific menu entry by index
*/
static async readMenuEntry(index: number, baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<Record<string, any>> {
this.ensureRegedit();
const keyPath = `${baseKey}\\${index}`;
try {
const result = await promisified.list([keyPath]);
return result[keyPath] || { exists: false, keys: [], values: {} };
} catch (error) {
return { exists: false, keys: [], values: {} };
}
}
/**
* Get all existing menu indices
*/
static async getExistingMenuIndices(baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<number[]> {
this.ensureRegedit();
const menuData = await this.readUserMenuEntries(baseKey);
if (!menuData.exists) {
return [];
}
return menuData.keys
.map((key: string) => parseInt(key, 10))
.filter((index: number) => !isNaN(index))
.sort((a: number, b: number) => a - b);
}
/**
* Get next available menu index
*/
static async getNextMenuIndex(baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<number> {
const indices = await this.getExistingMenuIndices(baseKey);
return indices.length > 0 ? Math.max(...indices) + 1 : 1;
}
/**
* Find insertion point for a specific group
*/
static async findGroupInsertionPoint(groupName: string, baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<number> {
this.ensureRegedit();
const indices = await this.getExistingMenuIndices(baseKey);
let inTargetGroup = false;
for (const index of indices) {
const entry = await this.readMenuEntry(index, baseKey);
if (entry.exists && entry.values['Item Name']) {
const itemName = entry.values['Item Name'].value;
if (itemName === groupName) {
inTargetGroup = true;
} else if (itemName === '(Submenu End)' && inTargetGroup) {
return index; // Insert before submenu end
}
}
}
return await this.getNextMenuIndex(baseKey); // Fallback to end
}
/**
* Write registry values for a menu entry
*/
static async writeMenuEntry(index: number, values: Record<string, any>, baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<void> {
this.ensureRegedit();
const keyPath = `${baseKey}\\${index}`;
// First create the key
await promisified.createKey([keyPath]);
// Then write the values
const valuesToWrite: Record<string, any> = {};
valuesToWrite[keyPath] = {};
for (const [valueName, valueData] of Object.entries(values)) {
const cleanValueName = valueName.replace(/"/g, '');
let cleanValueData = String(valueData);
let valueType = 'REG_SZ';
if (cleanValueData.startsWith('dword:')) {
valueType = 'REG_DWORD';
cleanValueData = parseInt(cleanValueData.replace('dword:', ''), 16).toString();
} else {
cleanValueData = cleanValueData.replace(/"/g, '');
}
valuesToWrite[keyPath][cleanValueName] = {
value: cleanValueData,
type: valueType
};
}
await promisified.putValue(valuesToWrite);
}
/**
* Delete a menu entry
*/
static async deleteMenuEntry(index: number, baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<void> {
this.ensureRegedit();
const keyPath = `${baseKey}\\${index}`;
try {
await promisified.deleteKey([keyPath]);
} catch (error) {
// Key might not exist, ignore error
}
}
/**
* List all menu entries with their details
*/
static async listAllMenuEntries(baseKey: string = 'HKCU\\Software\\Altap\\Altap Salamander 4.0\\User Menu'): Promise<Array<{index: number, name: string, type: string, command?: string}>> {
const indices = await this.getExistingMenuIndices(baseKey);
const entries = [];
for (const index of indices) {
const entry = await this.readMenuEntry(index, baseKey);
if (entry.exists && entry.values['Item Name']) {
const name = entry.values['Item Name'].value;
const type = entry.values['Type'] ? entry.values['Type'].value : 0;
const command = entry.values['Command'] ? entry.values['Command'].value : '';
let typeString = 'command';
if (type === 1 || type === '0x00000001') typeString = 'submenu';
if (type === 2 || type === '0x00000002') typeString = 'submenu-end';
entries.push({
index,
name,
type: typeString,
command: command || undefined
});
}
}
return entries;
}
}
/**
* Extended Salamander Menu Generator with direct registry support
*/
export class SalamanderMenuGeneratorRegistry extends SalamanderMenuGenerator {
/**
* Apply menu configuration directly to Windows registry
*/
async applyToRegistry(): Promise<void> {
if (process.platform !== 'win32') {
throw new Error('Direct registry operations are only supported on Windows');
}
const entries = this.generateRegistryEntries();
for (const entry of entries) {
// Extract menu index from key
const keyMatch = entry.key.match(/\\User Menu\\(\d+)\]/);
if (!keyMatch) continue;
const menuIndex = parseInt(keyMatch[1], 10);
await WindowsRegistry.writeMenuEntry(menuIndex, entry.values);
}
}
/**
* Remove menu entries from Windows registry
*/
async removeFromRegistry(): Promise<void> {
if (process.platform !== 'win32') {
throw new Error('Direct registry operations are only supported on Windows');
}
const entries = this.generateRegistryEntries();
// Remove in reverse order to avoid index issues
for (let i = entries.length - 1; i >= 0; i--) {
const entry = entries[i];
const keyMatch = entry.key.match(/\\User Menu\\(\d+)\]/);
if (keyMatch) {
const menuIndex = parseInt(keyMatch[1], 10);
await WindowsRegistry.deleteMenuEntry(menuIndex);
}
}
}
/**
* Auto-detect insertion point from Windows registry
*/
static async autoDetectInsertionPoint(groupName?: string): Promise<number> {
if (process.platform !== 'win32') {
throw new Error('Registry operations are only supported on Windows');
}
if (groupName) {
return await WindowsRegistry.findGroupInsertionPoint(groupName);
} else {
return await WindowsRegistry.getNextMenuIndex();
}
}
/**
* List current menu entries from registry
*/
static async listCurrentMenuEntries(): Promise<Array<{index: number, name: string, type: string, command?: string}>> {
return await WindowsRegistry.listAllMenuEntries();
}
}
export default SalamanderMenuGenerator;

View File

@ -9,6 +9,9 @@ import './commands/watermark.js'
import './commands/background-remove.js'
import './commands/background-remove-bria.js'
import './commands/crop-foreground.js'
import './commands/salamander.js'
import './commands/register-commands.js'
import './commands/register-explorer.js'
const argv: any = cli.argv;

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -52,16 +52,24 @@ echo -e "${GREEN}✓ Cache functionality test completed${NC}"
echo
echo -e "${BLUE}Test 5: Configuration options test${NC}"
pm-media background:remove:bria \
--src "tests/images/in/barrel.jpg" \
--src "tests/images/in/DSC01301.JPG" \
--dst "tests/background-remove-bria-test-output/config-test.png" \
--sync \
--contentModeration \
--preserveAlpha \
--dry \
--verbose
echo -e "${GREEN}✓ Configuration options test completed${NC}"
echo -e "${BLUE}Test 6: JPG conversion test (dry run)${NC}"
pm-media background:remove:bria \
--src "tests/images/in/DSC01177.JPG" \
--dst "tests/background-remove-bria-test-output/jpg-conversion-test.jpg" \
--jpg \
--verbose
echo -e "${GREEN}✓ JPG conversion test completed${NC}"
echo -e "${BLUE}Test 7: Cache test with real file${NC}"
# Try to process the same file again with cache enabled
pm-media background:remove:bria \
@ -104,9 +112,20 @@ for format in "${formats[@]}"; do
--src "tests/images/in/*.$format" \
--dst "tests/background-remove-bria-test-output/format-&{SRC_NAME}_nobg.png" \
--alt \
--dry \
--verbose | head -5
fi
done
echo -e "${GREEN}✓ Format tests completed${NC}"
echo -e "${BLUE}Test 11: Batch JPG conversion with alt tokenizer${NC}"
pm-media background:remove:bria \
--src "tests/images/in/sub/*.JPG" \
--dst "tests/background-remove-bria-test-output/batch-&{SRC_NAME}_nobg.jpg" \
--jpg \
--alt \
--verbose
echo -e "${GREEN}✓ Batch JPG conversion test completed${NC}"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 274 KiB

View File

@ -0,0 +1,74 @@
REGEDIT4
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\5]
"Item Name"="Background Remove (Bria)"
"Command"="pm-media"
"Arguments"="background:remove:bria --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}_nobg.png\\\\" --jpg"
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\6]
"Item Name"="Convert Tools"
"Command"=""
"Arguments"=""
"Initial Directory"=""
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000001
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\7]
"Item Name"="To WebP"
"Command"="pm-media"
"Arguments"="convert --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}.webp\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000001
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\8]
"Item Name"="To PNG"
"Command"="pm-media"
"Arguments"="convert --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}.png\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000001
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\9]
"Item Name"="(Submenu End)"
"Command"=""
"Arguments"=""
"Initial Directory"=""
"Execute Through Shell"=dword:00000000
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000000
"Icon"=""
"Type"=dword:00000002
"Show In Toolbar"=dword:00000000
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\10]
"Item Name"="PDF to Images"
"Command"="pm-media"
"Arguments"="pdf2jpg --alt=true --logLevel=info --src=\\\\"$(FullName)\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001

View File

@ -0,0 +1,74 @@
REGEDIT4
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\10]
"Item Name"="Background Remove (Bria)"
"Command"="pm-media"
"Arguments"="background:remove:bria --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}_nobg.png\\\\" --jpg"
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\11]
"Item Name"="Convert Tools"
"Command"=""
"Arguments"=""
"Initial Directory"=""
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000001
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\12]
"Item Name"="To WebP"
"Command"="pm-media"
"Arguments"="convert --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}.webp\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000001
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\13]
"Item Name"="To PNG"
"Command"="pm-media"
"Arguments"="convert --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}.png\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000001
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\14]
"Item Name"="(Submenu End)"
"Command"=""
"Arguments"=""
"Initial Directory"=""
"Execute Through Shell"=dword:00000000
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000000
"Icon"=""
"Type"=dword:00000002
"Show In Toolbar"=dword:00000000
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\15]
"Item Name"="PDF to Images"
"Command"="pm-media"
"Arguments"="pdf2jpg --alt=true --logLevel=info --src=\\\\"$(FullName)\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001

View File

@ -0,0 +1,74 @@
REGEDIT4
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\4]
"Item Name"="Background Remove (Bria)"
"Command"="pm-media"
"Arguments"="background:remove:bria --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}_nobg.png\\\\" --jpg"
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\5]
"Item Name"="Convert Tools"
"Command"=""
"Arguments"=""
"Initial Directory"=""
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000001
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\6]
"Item Name"="To WebP"
"Command"="pm-media"
"Arguments"="convert --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}.webp\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000001
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\7]
"Item Name"="To PNG"
"Command"="pm-media"
"Arguments"="convert --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}.png\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000001
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\8]
"Item Name"="(Submenu End)"
"Command"=""
"Arguments"=""
"Initial Directory"=""
"Execute Through Shell"=dword:00000000
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000000
"Icon"=""
"Type"=dword:00000002
"Show In Toolbar"=dword:00000000
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\9]
"Item Name"="PDF to Images"
"Command"="pm-media"
"Arguments"="pdf2jpg --alt=true --logLevel=info --src=\\\\"$(FullName)\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001

View File

@ -0,0 +1,74 @@
REGEDIT4
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\5]
"Item Name"="Background Remove (Bria)"
"Command"="pm-media"
"Arguments"="background:remove:bria --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}_nobg.png\\\\" --jpg"
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\6]
"Item Name"="Convert Tools"
"Command"=""
"Arguments"=""
"Initial Directory"=""
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000001
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\7]
"Item Name"="To WebP"
"Command"="pm-media"
"Arguments"="convert --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}.webp\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000001
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\8]
"Item Name"="To PNG"
"Command"="pm-media"
"Arguments"="convert --alt=true --logLevel=info --src=\\\\"$(FullName)/**/*.+(&{IMAGES})\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}.png\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000001
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\9]
"Item Name"="(Submenu End)"
"Command"=""
"Arguments"=""
"Initial Directory"=""
"Execute Through Shell"=dword:00000000
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000000
"Icon"=""
"Type"=dword:00000002
"Show In Toolbar"=dword:00000000
[HKEY_CURRENT_USER\Software\Altap\Altap Salamander 4.0\User Menu\10]
"Item Name"="PDF to Images"
"Command"="pm-media"
"Arguments"="pdf2jpg --alt=true --logLevel=info --src=\\\\"$(FullName)\\\\" --dst=\\\\"&{SRC_DIR}/&{SRC_NAME}\\\\""
"Initial Directory"="$(FullPath)"
"Execute Through Shell"=dword:00000001
"Close Shell Window"=dword:00000000
"Open Shell Window"=dword:00000001
"Icon"=""
"Type"=dword:00000000
"Show In Toolbar"=dword:00000001

View File

@ -0,0 +1,91 @@
echo "=== Salamander Menu Test Script ==="
echo "Testing Salamander menu generation functionality"
echo
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Create test output directory
mkdir -p tests/salamander-test-output
echo -e "${BLUE}Building project...${NC}"
npm run build
echo
echo -e "${BLUE}Test 1: Generate menu from sample JSON${NC}"
pm-media salamander \
--config "integration/salamander-menu-sample.json" \
--output "tests/salamander-test-output/sample-menu.reg" \
--verbose
echo -e "${GREEN}✓ Sample menu generation completed${NC}"
echo
echo -e "${BLUE}Test 2: Auto-detect insertion point from existing registry${NC}"
pm-media salamander \
--config "integration/salamander-menu-sample.json" \
--output "tests/salamander-test-output/auto-detect-menu.reg" \
--existing "src/ref/config_sal_min.reg" \
--verbose
echo -e "${GREEN}✓ Auto-detect insertion test completed${NC}"
echo
echo -e "${BLUE}Test 3: Insert into existing Media group${NC}"
pm-media salamander \
--config "integration/salamander-menu-sample.json" \
--output "tests/salamander-test-output/media-group-menu.reg" \
--existing "src/ref/config_sal_min.reg" \
--group "Media" \
--verbose
echo -e "${GREEN}✓ Media group insertion test completed${NC}"
echo
echo -e "${BLUE}Test 4: Custom start index${NC}"
pm-media salamander \
--config "integration/salamander-menu-sample.json" \
--output "tests/salamander-test-output/custom-index-menu.reg" \
--startIndex 10 \
--verbose
echo -e "${GREEN}✓ Custom start index test completed${NC}"
echo
echo -e "${BLUE}Test 5: Help and usage${NC}"
pm-media salamander --help | head -15
echo -e "${GREEN}✓ Help test completed${NC}"
echo
echo -e "${YELLOW}=== Test Summary ===${NC}"
echo "All tests completed! Check output files in: tests/salamander-test-output/"
echo
echo "Generated files:"
ls -la tests/salamander-test-output/
echo
echo -e "${GREEN}✓ All Salamander menu tests passed successfully!${NC}"
echo
echo -e "${BLUE}Generated menu structure preview:${NC}"
if [ -f "tests/salamander-test-output/sample-menu.reg" ]; then
echo "Sample menu entries:"
grep -E "\\[|Item Name" "tests/salamander-test-output/sample-menu.reg" | head -20
fi
echo
echo -e "${BLUE}Usage examples:${NC}"
echo "# Generate menu from JSON:"
echo "pm-media salamander --config menu.json --output menu.reg"
echo
echo "# Insert into existing Media group:"
echo "pm-media salamander --config menu.json --existing current.reg --group \"Media\""
echo
echo "# Auto-detect next available index:"
echo "pm-media salamander --config menu.json --existing current.reg"