Add terminal code snippets feature #341

Merged
ZacharyZcR merged 4 commits from feature/terminal-snippets into dev-1.8.0 2025-10-04 01:35:22 +00:00
ZacharyZcR commented 2025-10-03 22:52:35 +00:00 (Migrated from github.com)

Summary

Add code snippets management for terminal sessions with sidebar UI and database persistence.

Changes

  • Backend: CRUD API endpoints for snippets (/snippets)
  • Database: New snippets table with user-scoped storage
  • Frontend: Collapsible sidebar with snippet list and execution
  • UI: Centered modal for create/edit (consistent with other components)
  • Layout: Absolutely positioned sidebar (zero terminal resize conflicts)

Features

  • Create/Read/Update/Delete snippets
  • Execute snippets with one click
  • Search and organize command snippets
  • Persistent storage with user isolation
  • i18n support (EN/CN)

Technical Details

  • Direct Drizzle ORM queries (no encryption layer for plain text commands)
  • Defensive array type checking in frontend
  • Toast-based delete confirmation
  • localStorage for sidebar state persistence
## Summary Add code snippets management for terminal sessions with sidebar UI and database persistence. ## Changes - **Backend**: CRUD API endpoints for snippets (`/snippets`) - **Database**: New `snippets` table with user-scoped storage - **Frontend**: Collapsible sidebar with snippet list and execution - **UI**: Centered modal for create/edit (consistent with other components) - **Layout**: Absolutely positioned sidebar (zero terminal resize conflicts) ## Features - Create/Read/Update/Delete snippets - Execute snippets with one click - Search and organize command snippets - Persistent storage with user isolation - i18n support (EN/CN) ## Technical Details - Direct Drizzle ORM queries (no encryption layer for plain text commands) - Defensive array type checking in frontend - Toast-based delete confirmation - localStorage for sidebar state persistence
gemini-code-assist[bot] commented 2025-10-03 22:52:57 +00:00 (Migrated from github.com)

Summary of Changes

Hello @ZacharyZcR, I'm Gemini Code Assist1 ! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a significant new feature: terminal code snippets. Users can now save, organize, and quickly execute frequently used commands directly from a dedicated sidebar within their terminal sessions. This enhancement is backed by a new database table and a robust API for managing snippets. Furthermore, the PR improves SSH host configuration by introducing a 'none' authentication option and adding support for Two-Factor Authentication (TOTP/2FA) during connection, making the terminal more versatile and secure.

Highlights

  • New Feature: Terminal Code Snippets: Introduced a comprehensive feature for managing and executing terminal code snippets directly within sessions, complete with a dedicated sidebar UI and database persistence.
  • Backend API for Snippets: Implemented full CRUD (Create, Read, Update, Delete) API endpoints for code snippets, ensuring user-scoped storage and secure access.
  • Database Schema Update: Added a new snippets table to the database schema, designed to store user-specific code snippets with fields for name, content, description, and timestamps.
  • Frontend UI for Snippets: Developed a collapsible sidebar component for the terminal interface, allowing users to view, create, edit, delete, and execute their saved code snippets. A centered modal handles snippet creation and editing.
  • Enhanced SSH Authentication: Added a 'none' authentication type for SSH hosts, allowing connections without explicit credentials. Also, implemented support for Two-Factor Authentication (TOTP/2FA) via keyboard-interactive prompts during SSH connection.
  • Internationalization (i18n) Support: Extended i18n support to cover all new snippet-related texts and the new 'none' authentication type, available in both English and Chinese.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩︎

## Summary of Changes Hello @ZacharyZcR, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request delivers a significant new feature: terminal code snippets. Users can now save, organize, and quickly execute frequently used commands directly from a dedicated sidebar within their terminal sessions. This enhancement is backed by a new database table and a robust API for managing snippets. Furthermore, the PR improves SSH host configuration by introducing a 'none' authentication option and adding support for Two-Factor Authentication (TOTP/2FA) during connection, making the terminal more versatile and secure. ### Highlights * **New Feature: Terminal Code Snippets**: Introduced a comprehensive feature for managing and executing terminal code snippets directly within sessions, complete with a dedicated sidebar UI and database persistence. * **Backend API for Snippets**: Implemented full CRUD (Create, Read, Update, Delete) API endpoints for code snippets, ensuring user-scoped storage and secure access. * **Database Schema Update**: Added a new `snippets` table to the database schema, designed to store user-specific code snippets with fields for name, content, description, and timestamps. * **Frontend UI for Snippets**: Developed a collapsible sidebar component for the terminal interface, allowing users to view, create, edit, delete, and execute their saved code snippets. A centered modal handles snippet creation and editing. * **Enhanced SSH Authentication**: Added a 'none' authentication type for SSH hosts, allowing connections without explicit credentials. Also, implemented support for Two-Factor Authentication (TOTP/2FA) via keyboard-interactive prompts during SSH connection. * **Internationalization (i18n) Support**: Extended i18n support to cover all new snippet-related texts and the new 'none' authentication type, available in both English and Chinese. <details> <summary><b>Using Gemini Code Assist</b></summary> <br> The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips. <b>Invoking Gemini</b> You can request assistance from Gemini at any point by creating a comment using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands on the current page. Feature | Command | Description --- | --- | --- Code Review | `/gemini review` | Performs a code review for the current pull request in its current state. Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state. Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in pull request comments and review comments. Help | `/gemini help` | Displays a list of available commands. <b>Customization</b> To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github). <b>Limitations & Feedback</b> Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up [here](https://google.qualtrics.com/jfe/form/SV_2cyuGuTWsEw84yG). <b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b> </details> [^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487).
gemini-code-assist[bot] (Migrated from github.com) reviewed 2025-10-03 22:54:14 +00:00
gemini-code-assist[bot] (Migrated from github.com) left a comment

Code Review

This pull request introduces a new feature for managing and executing terminal code snippets, complete with a backend API, database persistence, and a sidebar UI in the terminal. It also adds support for TOTP-based two-factor authentication for SSH connections and allows for SSH connections without credentials. The changes are well-structured, touching the backend, database, frontend UI, and type definitions. My review focuses on improving the security and efficiency of the new snippets API, enhancing type safety, and aligning the new UI code with React best practices.

## Code Review This pull request introduces a new feature for managing and executing terminal code snippets, complete with a backend API, database persistence, and a sidebar UI in the terminal. It also adds support for TOTP-based two-factor authentication for SSH connections and allows for SSH connections without credentials. The changes are well-structured, touching the backend, database, frontend UI, and type definitions. My review focuses on improving the security and efficiency of the new snippets API, enhancing type safety, and aligning the new UI code with React best practices.
@@ -0,0 +23,4 @@
authenticateJWT,
requireDataAccess,
async (req: Request, res: Response) => {
const userId = (req as any).userId;
gemini-code-assist[bot] (Migrated from github.com) commented 2025-10-03 22:54:14 +00:00

medium

The userId is accessed via (req as any).userId, which bypasses TypeScript's type checking. To improve type safety and maintainability across the application, consider extending Express's Request interface to include the userId property. This is typically done in a type definition file (e.g., src/types/express.d.ts).

Example:

// src/types/express.d.ts
declare namespace Express {
  export interface Request {
    userId?: string;
  }
}

With this in place, you can safely access req.userId without type casting to any.

![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) The `userId` is accessed via `(req as any).userId`, which bypasses TypeScript's type checking. To improve type safety and maintainability across the application, consider extending Express's `Request` interface to include the `userId` property. This is typically done in a type definition file (e.g., `src/types/express.d.ts`). Example: ```typescript // src/types/express.d.ts declare namespace Express { export interface Request { userId?: string; } } ``` With this in place, you can safely access `req.userId` without type casting to `any`.
@@ -0,0 +77,4 @@
authLogger.error("Failed to fetch snippet", err);
res.status(500).json({
error: err instanceof Error ? err.message : "Failed to fetch snippet",
});
gemini-code-assist[bot] (Migrated from github.com) commented 2025-10-03 22:54:14 +00:00

medium

The error handling in the catch block exposes internal error details (err.message) to the client. This can be a security risk, as it might leak information about the database or application structure. It's a best practice to return a generic error message for 500-level errors. This applies to the other handlers in this file as well.

      authLogger.error("Failed to fetch snippet", err);
      res.status(500).json({ error: "Failed to fetch snippet" });
![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) The error handling in the `catch` block exposes internal error details (`err.message`) to the client. This can be a security risk, as it might leak information about the database or application structure. It's a best practice to return a generic error message for 500-level errors. This applies to the other handlers in this file as well. ```typescript authLogger.error("Failed to fetch snippet", err); res.status(500).json({ error: "Failed to fetch snippet" }); ```
@@ -0,0 +195,4 @@
);
res.json(updated[0]);
} catch (err) {
gemini-code-assist[bot] (Migrated from github.com) commented 2025-10-03 22:54:14 +00:00

high

The current implementation for updating a snippet is inefficient and has a potential security issue. It involves three separate database queries: one to check existence, one to update, and a final one to fetch the updated record. The final select query on line 182 lacks a userId filter, which could potentially leak data under specific race conditions. Additionally, updateFields is typed as any, reducing type safety.

This can be simplified, secured, and made more type-safe by using Drizzle's returning() method on the update query. This performs the update and returns the modified record in a single, atomic operation.

    try {
      const updateFields: Partial<typeof snippets.$inferInsert> = {
        updatedAt: sql`CURRENT_TIMESTAMP`,
      };

      if (updateData.name !== undefined) {
        updateFields.name = updateData.name.trim();
      }
      if (updateData.content !== undefined) {
        updateFields.content = updateData.content.trim();
      }
      if (updateData.description !== undefined) {
        updateFields.description = updateData.description?.trim() || null;
      }

      const updatedResult = await db
        .update(snippets)
        .set(updateFields)
        .where(
          and(eq(snippets.id, parseInt(id)), eq(snippets.userId, userId)),
        )
        .returning();

      if (updatedResult.length === 0) {
        return res.status(404).json({ error: "Snippet not found" });
      }

      const updatedSnippet = updatedResult[0];

      authLogger.success(
        `Snippet updated: ${updatedSnippet.name} by user ${userId}`,
        {
          operation: "snippet_update_success",
          userId,
          snippetId: parseInt(id),
          name: updatedSnippet.name,
        },
      );

      res.json(updatedSnippet);
    } catch (err) {
![high](https://www.gstatic.com/codereviewagent/high-priority.svg) The current implementation for updating a snippet is inefficient and has a potential security issue. It involves three separate database queries: one to check existence, one to update, and a final one to fetch the updated record. The final `select` query on line 182 lacks a `userId` filter, which could potentially leak data under specific race conditions. Additionally, `updateFields` is typed as `any`, reducing type safety. This can be simplified, secured, and made more type-safe by using Drizzle's `returning()` method on the update query. This performs the update and returns the modified record in a single, atomic operation. ```typescript try { const updateFields: Partial<typeof snippets.$inferInsert> = { updatedAt: sql`CURRENT_TIMESTAMP`, }; if (updateData.name !== undefined) { updateFields.name = updateData.name.trim(); } if (updateData.content !== undefined) { updateFields.content = updateData.content.trim(); } if (updateData.description !== undefined) { updateFields.description = updateData.description?.trim() || null; } const updatedResult = await db .update(snippets) .set(updateFields) .where( and(eq(snippets.id, parseInt(id)), eq(snippets.userId, userId)), ) .returning(); if (updatedResult.length === 0) { return res.status(404).json({ error: "Snippet not found" }); } const updatedSnippet = updatedResult[0]; authLogger.success( `Snippet updated: ${updatedSnippet.name} by user ${userId}`, { operation: "snippet_update_success", userId, snippetId: parseInt(id), name: updatedSnippet.name, }, ); res.json(updatedSnippet); } catch (err) { ```
gemini-code-assist[bot] (Migrated from github.com) commented 2025-10-03 22:54:14 +00:00

medium

To align with React best practices, this form should be converted to use a controlled component. This improves state management and predictability.

You'll need to add a new state variable at the top of your component:
const [totpCode, setTotpCode] = useState("");

Then, you can update the form as suggested. This change also ensures the input field is cleared upon submission or cancellation.

            <form
              onSubmit={(e) => {
                e.preventDefault();
                if (totpCode.trim()) {
                  handleTotpSubmit(totpCode);
                }
              }}
            >
              <input
                type="text"
                name="totpCode"
                autoFocus
                maxLength={6}
                pattern="[0-9]*"
                inputMode="numeric"
                placeholder={t("terminal.totpPlaceholder")}
                className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-md text-white text-center text-lg tracking-widest focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4"
                value={totpCode}
                onChange={(e) => setTotpCode(e.target.value)}
              />
              <div className="flex gap-3">
                <button
                  type="submit"
                  className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md font-medium transition-colors"
                >
                  {t("terminal.submit")}
                </button>
                <button
                  type="button"
                  onClick={() => {
                    setTotpRequired(false);
                    setTotpPrompt("");
                    setTotpCode("");
                    if (webSocketRef.current) {
                      webSocketRef.current.close();
                    }
                    if (onClose) {
                      onClose();
                    }
                  }}
                  className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-md font-medium transition-colors"
                >
                  {t("terminal.cancel")}
                </button>
              </div>
            </form>
![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) To align with React best practices, this form should be converted to use a controlled component. This improves state management and predictability. You'll need to add a new state variable at the top of your component: `const [totpCode, setTotpCode] = useState("");` Then, you can update the form as suggested. This change also ensures the input field is cleared upon submission or cancellation. ``` <form onSubmit={(e) => { e.preventDefault(); if (totpCode.trim()) { handleTotpSubmit(totpCode); } }} > <input type="text" name="totpCode" autoFocus maxLength={6} pattern="[0-9]*" inputMode="numeric" placeholder={t("terminal.totpPlaceholder")} className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-md text-white text-center text-lg tracking-widest focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4" value={totpCode} onChange={(e) => setTotpCode(e.target.value)} /> <div className="flex gap-3"> <button type="submit" className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md font-medium transition-colors" > {t("terminal.submit")} </button> <button type="button" onClick={() => { setTotpRequired(false); setTotpPrompt(""); setTotpCode(""); if (webSocketRef.current) { webSocketRef.current.close(); } if (onClose) { onClose(); } }} className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-md font-medium transition-colors" > {t("terminal.cancel")} </button> </div> </form> ```
Sign in to join this conversation.