···11+/**
22+ * Container Tab commands - creates and manages container tabs
33+ * Currently disabled, needs browser extension API
44+ */
55+66+export default {
77+ name: 'containertab-source',
88+ type: 'source',
99+1010+ /**
1111+ * Initializes and registers container tab commands
1212+ * @param {Function} addCommand - Function to register a command
1313+ */
1414+ initialize: async (addCommand) => {
1515+ if (typeof browser === 'undefined' || !browser.contextualIdentities || !browser.tabs) {
1616+ console.log('Container Tab source disabled: browser contextualIdentities API not available');
1717+ return;
1818+ }
1919+2020+ try {
2121+ // Initialize "New container tab" commands
2222+ const newCmdPrefix = 'New container tab: ';
2323+ const switchCmdPrefix = 'Switch container to: ';
2424+2525+ const identities = await browser.contextualIdentities.query({});
2626+2727+ if (!identities.length) {
2828+ console.log('No container identities found');
2929+ return;
3030+ }
3131+3232+ // Register "New container tab" commands
3333+ for (let identity of identities) {
3434+ // Command to create a new tab in a container
3535+ addCommand({
3636+ name: newCmdPrefix + identity.name,
3737+ async execute(msg) {
3838+ try {
3939+ await browser.tabs.create({
4040+ url: '',
4141+ cookieStoreId: identity.cookieStoreId
4242+ });
4343+ return {
4444+ success: true,
4545+ command: 'newcontainertab',
4646+ container: identity.name
4747+ };
4848+ } catch (error) {
4949+ console.error('Failed to create container tab:', error);
5050+ return { success: false, error: error.message };
5151+ }
5252+ }
5353+ });
5454+5555+ // Command to switch the current tab to a different container
5656+ addCommand({
5757+ name: switchCmdPrefix + identity.name,
5858+ async execute(msg) {
5959+ try {
6060+ const activeTabs = await browser.tabs.query({
6161+ currentWindow: true,
6262+ active: true
6363+ });
6464+ const tab = activeTabs[0];
6565+6666+ // Create a new tab in the target container with the same URL
6767+ await browser.tabs.create({
6868+ url: tab.url,
6969+ cookieStoreId: identity.cookieStoreId,
7070+ index: tab.index+1,
7171+ pinned: tab.pinned
7272+ });
7373+7474+ // Remove the original tab
7575+ browser.tabs.remove(tab.id);
7676+7777+ return {
7878+ success: true,
7979+ command: 'switchcontainer',
8080+ container: identity.name
8181+ };
8282+ } catch (error) {
8383+ console.error('Failed to switch container:', error);
8484+ return { success: false, error: error.message };
8585+ }
8686+ }
8787+ });
8888+ }
8989+9090+ console.log('Registered Container Tab commands:', identities.length * 2);
9191+ } catch (error) {
9292+ console.error('Failed to initialize Container Tab source:', error);
9393+ }
9494+ }
9595+};
+38
app/cmd/commands/debug.js
···11+/**
22+ * Debug command - opens a URL in a new window with DevTools enabled
33+ */
44+import windows from '../../windows.js';
55+66+export default {
77+ name: 'debug',
88+ execute: async (msg) => {
99+ console.log('debug command', msg);
1010+1111+ const parts = msg.typed.split(' ');
1212+ parts.shift();
1313+1414+ const address = parts.shift();
1515+1616+ if (!address) {
1717+ return;
1818+ }
1919+2020+ // Use the new windows API with DevTools enabled
2121+ try {
2222+ const windowController = await windows.createWindow(address, {
2323+ width: 900,
2424+ height: 700,
2525+ openDevTools: true,
2626+ detachedDevTools: true
2727+ });
2828+ console.log('Debug window opened with ID:', windowController.id);
2929+ } catch (error) {
3030+ console.error('Failed to open debug window:', error);
3131+ }
3232+3333+ return {
3434+ command: 'debug',
3535+ address
3636+ };
3737+ }
3838+};
+42
app/cmd/commands/email.js
···11+/**
22+ * Email command - emails the current page
33+ * Currently disabled, needs browser extension API
44+ */
55+66+export default {
77+ name: 'Email page to',
88+99+ /**
1010+ * Executes the email command
1111+ */
1212+ execute: async (msg) => {
1313+ if (typeof browser === 'undefined' || !browser.tabs) {
1414+ console.error('Email command disabled: browser API not available');
1515+ return { success: false, error: 'Browser API not available' };
1616+ }
1717+1818+ try {
1919+ let tabs = await browser.tabs.query({active:true});
2020+ let email = msg.typed.replace(msg.name, '').trim();
2121+ let url =
2222+ 'mailto:' + email +
2323+ '?subject=Web%20page!&body=' +
2424+ encodeURIComponent(tabs[0].title) +
2525+ '%0D%0A' +
2626+ encodeURIComponent(tabs[0].url);
2727+2828+ // Navigate the current tab to the mailto: URL
2929+ // Note: This approach might be replaced with a more modern API
3030+ tabs[0].url = url;
3131+3232+ return {
3333+ success: true,
3434+ command: 'email',
3535+ recipient: email
3636+ };
3737+ } catch (error) {
3838+ console.error('Failed to email page:', error);
3939+ return { success: false, error: error.message };
4040+ }
4141+ }
4242+};
+52
app/cmd/commands/googledocs.js
···11+/**
22+ * Google Docs commands - creates new Google Docs documents
33+ * Currently disabled, needs browser extension API
44+ */
55+66+export default {
77+ name: 'googledocs-source',
88+ type: 'source',
99+1010+ /**
1111+ * Initializes and registers Google Docs commands
1212+ * @param {Function} addCommand - Function to register a command
1313+ */
1414+ initialize: (addCommand) => {
1515+ if (typeof browser === 'undefined' || !browser.tabs) {
1616+ console.log('Google Docs source disabled: browser tabs API not available');
1717+ return;
1818+ }
1919+2020+ // Define the available document types
2121+ const documents = [
2222+ {
2323+ cmd: 'New Google doc',
2424+ url: 'http://docs.google.com/document/create?hl=en'
2525+ },
2626+ {
2727+ cmd: 'New Google sheet',
2828+ url: 'http://spreadsheets.google.com/ccc?new&hl=en'
2929+ }
3030+ ];
3131+3232+ // Register each document type as a command
3333+ documents.forEach(function(doc) {
3434+ addCommand({
3535+ name: doc.cmd,
3636+ async execute(msg) {
3737+ try {
3838+ await browser.tabs.create({
3939+ url: doc.url
4040+ });
4141+ return { success: true, command: doc.cmd };
4242+ } catch (error) {
4343+ console.error(`Failed to create ${doc.cmd}:`, error);
4444+ return { success: false, error: error.message };
4545+ }
4646+ }
4747+ });
4848+ });
4949+5050+ console.log('Registered Google Docs commands:', documents.length);
5151+ }
5252+};
+98
app/cmd/commands/index.js
···11+/**
22+ * Commands module - exports all available commands
33+ */
44+import openCommand from './open.js';
55+import debugCommand from './debug.js';
66+import modalCommand from './modal.js';
77+88+// Source commands (commented out as they need browser extension APIs)
99+// These modules contain command sources that dynamically generate commands
1010+import bookmarkletsSource from './bookmarklets.js';
1111+import googleDocsSource from './googledocs.js';
1212+import sendToWindowSource from './sendtowindow.js';
1313+import switchToWindowSource from './switchtowindow.js';
1414+import containerTabSource from './containertab.js';
1515+1616+// Individual commands (commented out as they need browser extension APIs)
1717+import bookmarkCommand from './bookmark.js';
1818+import emailCommand from './email.js';
1919+import noteCommand from './note.js';
2020+2121+// Active commands - only these will be loaded
2222+const activeCommands = [
2323+ openCommand,
2424+ debugCommand,
2525+ modalCommand
2626+];
2727+2828+// Inactive commands - these require browser extension APIs and are not loaded
2929+const inactiveCommands = [
3030+ // Individual commands
3131+ bookmarkCommand,
3232+ emailCommand,
3333+ noteCommand,
3434+3535+ // Source commands that dynamically generate commands
3636+ bookmarkletsSource,
3737+ googleDocsSource,
3838+ sendToWindowSource,
3939+ switchToWindowSource,
4040+ containerTabSource
4141+];
4242+4343+// Array of all available commands
4444+const commands = [...activeCommands];
4545+4646+// Source commands - these are modules that generate multiple commands
4747+const sources = [];
4848+4949+/**
5050+ * Initializes command sources that dynamically generate commands
5151+ * @param {Function} addCommand - Function to register a command
5252+ */
5353+export const initializeSources = (addCommand) => {
5454+ // Currently no active sources
5555+ sources.forEach(source => {
5656+ if (typeof source.initialize === 'function') {
5757+ source.initialize(addCommand);
5858+ }
5959+ });
6060+};
6161+6262+/**
6363+ * Gets a command by name
6464+ * @param {string} name - The command name to look for
6565+ * @returns {Object|null} The command object or null if not found
6666+ */
6767+export const getCommand = (name) => {
6868+ return commands.find(cmd => cmd.name === name) || null;
6969+};
7070+7171+/**
7272+ * Gets all active commands
7373+ * @returns {Array} Array of command objects
7474+ */
7575+export const getAllCommands = () => {
7676+ return commands;
7777+};
7878+7979+/**
8080+ * Converts commands array to object map for legacy compatibility
8181+ * @returns {Object} Map of command name to command object
8282+ */
8383+export const getCommandsMap = () => {
8484+ const commandMap = {};
8585+ commands.forEach(cmd => {
8686+ commandMap[cmd.name] = cmd;
8787+ });
8888+ return commandMap;
8989+};
9090+9191+export default {
9292+ commands,
9393+ sources,
9494+ getCommand,
9595+ getAllCommands,
9696+ getCommandsMap,
9797+ initializeSources
9898+};
+36
app/cmd/commands/modal.js
···11+/**
22+ * Modal command - opens a URL in a modal window that hides on blur or escape
33+ */
44+import windows from '../../windows.js';
55+66+export default {
77+ name: 'modal',
88+ execute: async (msg) => {
99+ console.log('modal command', msg);
1010+1111+ const parts = msg.typed.split(' ');
1212+ parts.shift();
1313+1414+ const address = parts.shift();
1515+1616+ if (!address) {
1717+ return;
1818+ }
1919+2020+ // Use the modal window API
2121+ try {
2222+ const result = await windows.openModalWindow(address, {
2323+ width: 700,
2424+ height: 500
2525+ });
2626+ console.log('Modal window opened:', result);
2727+ } catch (error) {
2828+ console.error('Failed to open modal window:', error);
2929+ }
3030+3131+ return {
3232+ command: 'modal',
3333+ address
3434+ };
3535+ }
3636+};
+86
app/cmd/commands/note.js
···11+/**
22+ * Note command - saves a note
33+ * Currently disabled, needs browser extension API
44+ */
55+66+export default {
77+ name: 'note',
88+99+ /**
1010+ * Executes the note command
1111+ */
1212+ execute: async (msg) => {
1313+ if (typeof browser === 'undefined' || !browser.storage) {
1414+ console.error('Note command disabled: browser storage API not available');
1515+ return { success: false, error: 'Browser API not available' };
1616+ }
1717+1818+ console.log('note executed', msg);
1919+2020+ try {
2121+ if (msg.typed.indexOf(' ') !== -1) {
2222+ const noteText = msg.typed.replace('note ', '');
2323+ await saveNewNote(noteText);
2424+2525+ // Notify user
2626+ if (typeof notify === 'function') {
2727+ notify('Note saved!', noteText);
2828+ } else {
2929+ console.log('Note saved:', noteText);
3030+ }
3131+3232+ return { success: true, command: 'note', text: noteText };
3333+ } else {
3434+ return { success: false, error: 'No note text provided' };
3535+ }
3636+ } catch (error) {
3737+ console.error('Failed to save note:', error);
3838+ return { success: false, error: error.message };
3939+ }
4040+ }
4141+};
4242+4343+// Storage constants
4444+const STG_KEY = 'cmd:notes';
4545+const STG_TYPE = 'local';
4646+4747+/**
4848+ * Saves a new note to browser storage
4949+ * @param {string} note - The note text to save
5050+ */
5151+async function saveNewNote(note) {
5252+ let store = await browser.storage[STG_TYPE].get(STG_KEY);
5353+ console.log('store', store);
5454+5555+ if (Object.keys(store).indexOf(STG_KEY) === -1) {
5656+ console.log('new store');
5757+ store = {
5858+ notes: []
5959+ };
6060+ } else {
6161+ store = store[STG_KEY];
6262+ }
6363+6464+ store.notes.push(note);
6565+6666+ await browser.storage[STG_TYPE].set({ [STG_KEY]: store });
6767+ console.log('saved store', store);
6868+}
6969+7070+/**
7171+ * Notifies the user
7272+ * @param {string} title - Notification title
7373+ * @param {string} content - Notification content
7474+ */
7575+function notify(title, content) {
7676+ if (browser && browser.notifications) {
7777+ browser.notifications.create({
7878+ "type": "basic",
7979+ "iconUrl": browser.extension.getURL("images/icon.png"),
8080+ "title": title,
8181+ "message": content
8282+ });
8383+ } else {
8484+ console.log('Notification:', title, content);
8585+ }
8686+}
+93
app/cmd/commands/open.js
···11+/**
22+ * Open command - opens a URL in a new window
33+ * Only opens window if input is a valid URL
44+ */
55+import windows from '../../windows.js';
66+77+export default {
88+ name: 'open',
99+ execute: async (msg) => {
1010+ console.log('open command', msg);
1111+1212+ const parts = msg.typed.split(' ');
1313+ parts.shift();
1414+1515+ const address = parts.shift();
1616+1717+ if (!address) {
1818+ console.log('No address provided');
1919+ return { error: 'No address provided' };
2020+ }
2121+2222+ // Check if the input is a valid URL and get the normalized version
2323+ const urlResult = getValidURL(address);
2424+ if (!urlResult.valid) {
2525+ console.log('Invalid URL:', address);
2626+ return { error: 'Invalid URL. Must be a valid URL starting with http://, https://, or other valid protocol.' };
2727+ }
2828+2929+ // Use the normalized URL (with protocol added if needed)
3030+ const normalizedAddress = urlResult.url;
3131+ console.log('Using normalized URL:', normalizedAddress);
3232+3333+ // Use the new windows API
3434+ try {
3535+ const windowController = await windows.createWindow(normalizedAddress, {
3636+ width: 800,
3737+ height: 600,
3838+ openDevTools: window.app.debug // Only open DevTools in debug mode
3939+ });
4040+ console.log('Window opened with ID:', windowController.id);
4141+4242+ return {
4343+ command: 'open',
4444+ address: normalizedAddress,
4545+ success: true
4646+ };
4747+ } catch (error) {
4848+ console.error('Failed to open window:', error);
4949+ return {
5050+ error: 'Failed to open window: ' + error.message,
5151+ address: normalizedAddress
5252+ };
5353+ }
5454+ }
5555+};
5656+5757+/**
5858+ * Validates and normalizes a URL string
5959+ * @param {string} str - The string to check
6060+ * @returns {Object} - Object with valid flag and normalized URL
6161+ */
6262+function getValidURL(str) {
6363+ // Quick check for empty string
6464+ if (!str) return { valid: false };
6565+6666+ // Check if it starts with a valid protocol
6767+ const hasValidProtocol = /^(https?|ftp|file|peek):\/\//.test(str);
6868+6969+ if (!hasValidProtocol) {
7070+ // If no protocol, check if it's a domain name pattern
7171+ const isDomainPattern = /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(str);
7272+ if (isDomainPattern) {
7373+ // It's a domain without protocol, add https://
7474+ const urlWithProtocol = 'https://' + str;
7575+ try {
7676+ // Validate the URL with added protocol
7777+ new URL(urlWithProtocol);
7878+ return { valid: true, url: urlWithProtocol };
7979+ } catch (e) {
8080+ return { valid: false };
8181+ }
8282+ }
8383+ return { valid: false };
8484+ }
8585+8686+ try {
8787+ // Already has protocol, just validate
8888+ new URL(str);
8989+ return { valid: true, url: str };
9090+ } catch (e) {
9191+ return { valid: false };
9292+ }
9393+}
+45
app/cmd/commands/sendtowindow.js
···11+/**
22+ * Send to Window command - moves the current tab to another window
33+ * Currently disabled, needs browser extension API
44+ */
55+66+export default {
77+ name: 'sendtowindow-source',
88+ type: 'source',
99+1010+ /**
1111+ * Initializes and registers Send to Window commands
1212+ * @param {Function} addCommand - Function to register a command
1313+ */
1414+ initialize: async (addCommand) => {
1515+ if (typeof browser === 'undefined' || !browser.windows || !browser.tabs) {
1616+ console.log('Send to Window source disabled: browser windows/tabs API not available');
1717+ return;
1818+ }
1919+2020+ try {
2121+ const cmdPrefix = 'Move to window: ';
2222+ const windows = await browser.windows.getAll({windowTypes: ['normal']});
2323+2424+ windows.forEach((w) => {
2525+ addCommand({
2626+ name: cmdPrefix + w.title,
2727+ async execute(msg) {
2828+ try {
2929+ const activeTabs = await browser.tabs.query({active: true});
3030+ await browser.tabs.move(activeTabs[0].id, {windowId: w.id, index: -1});
3131+ return { success: true, command: 'movetowindow', windowId: w.id };
3232+ } catch (error) {
3333+ console.error('Failed to move tab to window:', error);
3434+ return { success: false, error: error.message };
3535+ }
3636+ }
3737+ });
3838+ });
3939+4040+ console.log('Registered Send to Window commands:', windows.length);
4141+ } catch (error) {
4242+ console.error('Failed to initialize Send to Window source:', error);
4343+ }
4444+ }
4545+};
+44
app/cmd/commands/switchtowindow.js
···11+/**
22+ * Switch to Window command - focuses another window
33+ * Currently disabled, needs browser extension API
44+ */
55+66+export default {
77+ name: 'switchtowindow-source',
88+ type: 'source',
99+1010+ /**
1111+ * Initializes and registers Switch to Window commands
1212+ * @param {Function} addCommand - Function to register a command
1313+ */
1414+ initialize: async (addCommand) => {
1515+ if (typeof browser === 'undefined' || !browser.windows) {
1616+ console.log('Switch to Window source disabled: browser windows API not available');
1717+ return;
1818+ }
1919+2020+ try {
2121+ const cmdPrefix = 'Switch to window: ';
2222+ const windows = await browser.windows.getAll({});
2323+2424+ windows.forEach((w) => {
2525+ addCommand({
2626+ name: cmdPrefix + w.title,
2727+ async execute(msg) {
2828+ try {
2929+ await browser.windows.update(w.id, { focused: true });
3030+ return { success: true, command: 'switchtowindow', windowId: w.id };
3131+ } catch (error) {
3232+ console.error('Failed to switch to window:', error);
3333+ return { success: false, error: error.message };
3434+ }
3535+ }
3636+ });
3737+ });
3838+3939+ console.log('Registered Switch to Window commands:', windows.length);
4040+ } catch (error) {
4141+ console.error('Failed to initialize Switch to Window source:', error);
4242+ }
4343+ }
4444+};
+33
app/cmd/commands/template.js
···11+/**
22+ * Template command - use this as a starting point for new commands
33+ *
44+ * To create a new command:
55+ * 1. Copy this file to a new file named after your command (e.g., mycommand.js)
66+ * 2. Update the name and execute function
77+ * 3. Import the command in index.js and add it to the commands array
88+ */
99+1010+export default {
1111+ // Command name - what the user will type to execute this command
1212+ name: 'template',
1313+1414+ // Execute function - called when the command is selected
1515+ execute: async (msg) => {
1616+ console.log('template command executed', msg);
1717+1818+ // Parse any arguments from the command
1919+ const parts = msg.typed.split(' ');
2020+ parts.shift(); // Remove the command name
2121+2222+ const args = parts.join(' ');
2323+2424+ // Implement your command logic here
2525+2626+ // Return a result object
2727+ return {
2828+ command: 'template',
2929+ success: true,
3030+ // Add other properties as needed
3131+ };
3232+ }
3333+};