diff --git a/A5/dsh_cli.c b/A5/dsh_cli.c
new file mode 100644
index 0000000000000000000000000000000000000000..6af7b5b6f9f502d4f4f8e1ff4b4a84afbc2f9e0d
--- /dev/null
+++ b/A5/dsh_cli.c
@@ -0,0 +1,15 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "dshlib.h"
+
+/*
+ * DO NOT EDIT
+ * main() simply calls exec_local_cmd_loop() from dshlib.c
+ */
+int main()
+{
+    int rc = exec_local_cmd_loop();
+    printf("cmd loop returned %d\n", rc);
+    return 0;
+}
diff --git a/A5/dshlib.c b/A5/dshlib.c
new file mode 100644
index 0000000000000000000000000000000000000000..79bdc509978f84f7046ca51ffe04b1530a4ad272
--- /dev/null
+++ b/A5/dshlib.c
@@ -0,0 +1,379 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include "dshlib.h"
+
+/* -------------------------
+parse_line (from Part 2)
+Handles quotes within a single command
+------------------------- */
+static int parse_line(char *line, char *argv[])
+{
+    int argc = 0;
+    char *p = line;
+
+    // Skip leading spaces
+    while (*p && isspace((unsigned char)*p))
+    {
+        p++;
+    }
+
+    while (*p != '\0')
+    {
+        if (argc >= CMD_ARGV_MAX - 1)
+        {
+            break;
+        }
+        if (*p == '"')
+        {
+            // Quoted argument
+            p++;
+            argv[argc++] = p;
+            while (*p && *p != '"')
+            {
+                p++;
+            }
+            if (*p == '"')
+            {
+                *p = '\0';
+                p++;
+            }
+        }
+        else
+        {
+            // Unquoted argument
+            argv[argc++] = p;
+            while (*p && !isspace((unsigned char)*p))
+            {
+                p++;
+            }
+            if (*p != '\0')
+            {
+                *p = '\0';
+                p++;
+            }
+        }
+        // Skip trailing spaces
+        while (*p && isspace((unsigned char)*p))
+        {
+            p++;
+        }
+    }
+    argv[argc] = NULL;
+    return argc;
+}
+
+/* -------------------------
+build_cmd_list:
+    Splits the user input by '|' into multiple commands.
+    Then calls parse_line() for each subcommand to fill out
+    cmd_buff_t structures in command_list_t.
+------------------------- */
+int build_cmd_list(char *line, command_list_t *clist)
+{
+    clist->num = 0;
+
+    // Trim leading/trailing spaces from the entire line
+    // (optional but recommended)
+    // ...
+    // If line is empty, return WARN_NO_CMDS
+    if (strlen(line) == 0)
+    {
+        return WARN_NO_CMDS;
+    }
+
+    char *saveptr;
+    char *subcmd = strtok_r(line, "|", &saveptr);
+    while (subcmd)
+    {
+        if (clist->num >= CMD_MAX)
+        {
+            return ERR_TOO_MANY_COMMANDS;
+        }
+
+        // Trim leading/trailing spaces from subcmd if you want
+        while (*subcmd && isspace((unsigned char)*subcmd))
+            subcmd++;
+        char *end = subcmd + strlen(subcmd) - 1;
+        while (end > subcmd && isspace((unsigned char)*end))
+        {
+            *end = '\0';
+            end--;
+        }
+
+        cmd_buff_t *cmd_b = &clist->commands[clist->num];
+        cmd_b->argc = 0;
+        memset(cmd_b->argv, 0, sizeof(cmd_b->argv));
+        cmd_b->_cmd_buffer = NULL; // Not strictly needed if you parse in place
+
+        // parse_line to fill argv
+        cmd_b->argc = parse_line(subcmd, cmd_b->argv);
+
+        clist->num++;
+        subcmd = strtok_r(NULL, "|", &saveptr);
+    }
+
+    if (clist->num == 0)
+    {
+        return WARN_NO_CMDS;
+    }
+    return OK;
+}
+
+/* -------------------------
+run_single_command (from Part 2):
+------------------------- */
+static bool run_single_command(cmd_buff_t *cmd_b)
+{
+    int argc = cmd_b->argc;
+    char **argv = cmd_b->argv;
+    if (argc == 0)
+    {
+        printf("%s", CMD_WARN_NO_CMD);
+        return false;
+    }
+
+    // Built-in commands
+    if (strcmp(argv[0], EXIT_CMD) == 0)
+    {
+        // "exit"
+        return true; // signal to stop shell
+    }
+    else if (strcmp(argv[0], "cd") == 0)
+    {
+        // "cd"
+        if (argc > 1)
+        {
+            if (chdir(argv[1]) != 0)
+            {
+                perror("cd");
+            }
+        }
+        return false;
+    }
+    else if (strcmp(argv[0], "dragon") == 0)
+    {
+        // Extra credit
+        print_dragon();
+        return false;
+    }
+    else
+    {
+        // External command
+        pid_t pid = fork();
+        if (pid == 0)
+        {
+            // Child
+            execvp(argv[0], argv);
+            perror("execvp");
+            printf(CMD_ERR_EXECUTE);
+            _exit(1);
+        }
+        else if (pid < 0)
+        {
+            // Fork failed
+            perror("fork");
+        }
+        else
+        {
+            // Parent waits
+            int status;
+            waitpid(pid, &status, 0);
+        }
+    }
+    return false;
+}
+
+/* -------------------------
+execute_pipeline:
+------------------------- */
+int execute_pipeline(command_list_t *clist)
+{
+    int n = clist->num;
+    if (n == 0)
+    {
+        return WARN_NO_CMDS;
+    }
+    if (n == 1)
+    {
+        // Just run single command logic
+        run_single_command(&clist->commands[0]);
+        return OK;
+    }
+
+    // More than 1 command -> pipeline
+    int pipe_count = n - 1;
+    int fds[2 * pipe_count];
+
+    // Create pipes
+    for (int i = 0; i < pipe_count; i++)
+    {
+        if (pipe(fds + i * 2) < 0)
+        {
+            perror("pipe");
+            return -1;
+        }
+    }
+
+    // Fork each command
+    pid_t pids[CMD_MAX];
+    for (int i = 0; i < n; i++)
+    {
+        pid_t pid = fork();
+        if (pid == 0)
+        {
+            // Child i
+            // If not first command, read from previous pipe
+            if (i > 0)
+            {
+                dup2(fds[(i - 1) * 2], STDIN_FILENO);
+            }
+            // If not last command, write to current pipe
+            if (i < n - 1)
+            {
+                dup2(fds[i * 2 + 1], STDOUT_FILENO);
+            }
+            // Close all pipe fds
+            for (int j = 0; j < 2 * pipe_count; j++)
+            {
+                close(fds[j]);
+            }
+            // Now exec
+            char **argv = clist->commands[i].argv;
+            if (argv[0] != NULL)
+            {
+                // Check if built-in is typed in a pipeline
+                // Usually, built-ins in the middle of a pipeline is tricky,
+                // but let's keep it simple and just do external
+                execvp(argv[0], argv);
+                perror("execvp");
+                _exit(1);
+            }
+            _exit(0);
+        }
+        else if (pid < 0)
+        {
+            perror("fork");
+        }
+        else
+        {
+            pids[i] = pid;
+        }
+    }
+
+    // Parent closes all pipes
+    for (int j = 0; j < 2 * pipe_count; j++)
+    {
+        close(fds[j]);
+    }
+
+    // Wait for each child
+    for (int i = 0; i < n; i++)
+    {
+        waitpid(pids[i], NULL, 0);
+    }
+
+    return OK;
+}
+
+/*
+ * Implement your exec_local_cmd_loop function by building a loop that prompts the
+ * user for input.  Use the SH_PROMPT constant from dshlib.h and then
+ * use fgets to accept user input.
+ *
+ *      while(1){
+ *        printf("%s", SH_PROMPT);
+ *        if (fgets(cmd_buff, ARG_MAX, stdin) == NULL){
+ *           printf("\n");
+ *           break;
+ *        }
+ *        //remove the trailing \n from cmd_buff
+ *        cmd_buff[strcspn(cmd_buff,"\n")] = '\0';
+ *
+ *        //IMPLEMENT THE REST OF THE REQUIREMENTS
+ *      }
+ *
+ *   Also, use the constants in the dshlib.h in this code.
+ *      SH_CMD_MAX              maximum buffer size for user input
+ *      EXIT_CMD                constant that terminates the dsh program
+ *      SH_PROMPT               the shell prompt
+ *      OK                      the command was parsed properly
+ *      WARN_NO_CMDS            the user command was empty
+ *      ERR_TOO_MANY_COMMANDS   too many pipes used
+ *      ERR_MEMORY              dynamic memory management failure
+ *
+ *   errors returned
+ *      OK                     No error
+ *      ERR_MEMORY             Dynamic memory management failure
+ *      WARN_NO_CMDS           No commands parsed
+ *      ERR_TOO_MANY_COMMANDS  too many pipes used
+ *
+ *   console messages
+ *      CMD_WARN_NO_CMD        print on WARN_NO_CMDS
+ *      CMD_ERR_PIPE_LIMIT     print on ERR_TOO_MANY_COMMANDS
+ *      CMD_ERR_EXECUTE        print on execution failure of external command
+ *
+ *  Standard Library Functions You Might Want To Consider Using (assignment 1+)
+ *      malloc(), free(), strlen(), fgets(), strcspn(), printf()
+ *
+ *  Standard Library Functions You Might Want To Consider Using (assignment 2+)
+ *      fork(), execvp(), exit(), chdir()
+ */
+int exec_local_cmd_loop(void)
+{
+    char line[SH_CMD_MAX];
+
+    while (true)
+    {
+        // Print prompt
+        printf("%s", SH_PROMPT);
+        fflush(stdout);
+
+        // Read line
+        if (!fgets(line, sizeof(line), stdin))
+        {
+            // EOF
+            printf("\n");
+            break;
+        }
+        // Remove newline
+        line[strcspn(line, "\n")] = '\0';
+
+        command_list_t clist;
+        memset(&clist, 0, sizeof(clist));
+
+        // Build the list of commands (split on '|')
+        int rc = build_cmd_list(line, &clist);
+        if (rc == WARN_NO_CMDS)
+        {
+            printf("%s", CMD_WARN_NO_CMD);
+            continue;
+        }
+        else if (rc == ERR_TOO_MANY_COMMANDS)
+        {
+            printf(CMD_ERR_PIPE_LIMIT, CMD_MAX);
+            continue;
+        }
+
+        // If there's exactly 1 command and it's "exit", break
+        if (clist.num == 1)
+        {
+            cmd_buff_t *cmd_b = &clist.commands[0];
+            if (cmd_b->argc > 0 && strcmp(cmd_b->argv[0], EXIT_CMD) == 0)
+            {
+                printf("exiting...\n");
+                break;
+            }
+        }
+
+        // Otherwise, run pipeline
+        execute_pipeline(&clist);
+    }
+
+    return OK;
+}
diff --git a/A5/dshlib.h b/A5/dshlib.h
new file mode 100644
index 0000000000000000000000000000000000000000..7334bda3e3da9c6feeec61e7cc2f8b8c12a03c1a
--- /dev/null
+++ b/A5/dshlib.h
@@ -0,0 +1,92 @@
+
+#ifndef __DSHLIB_H__
+#define __DSHLIB_H__
+
+// Constants for command structure sizes
+#define EXE_MAX 64
+#define ARG_MAX 256
+#define CMD_MAX 8
+#define CMD_ARGV_MAX (CMD_MAX + 1)
+// Longest command that can be read from the shell
+#define SH_CMD_MAX EXE_MAX + ARG_MAX
+
+typedef struct command
+{
+    char exe[EXE_MAX];
+    char args[ARG_MAX];
+} command_t;
+
+typedef struct cmd_buff
+{
+    int argc;
+    char *argv[CMD_ARGV_MAX];
+    char *_cmd_buffer;
+} cmd_buff_t;
+
+/* WIP - Move to next assignment
+#define N_ARG_MAX    15     //MAX number of args for a command
+typedef struct command{
+    char exe [EXE_MAX];
+    char args[ARG_MAX];
+    int  argc;
+    char *argv[N_ARG_MAX + 1];  //last argv[LAST] must be \0
+}command_t;
+*/
+
+typedef struct command_list
+{
+    int num;
+    cmd_buff_t commands[CMD_MAX];
+} command_list_t;
+
+// Special character #defines
+#define SPACE_CHAR ' '
+#define PIPE_CHAR '|'
+#define PIPE_STRING "|"
+
+#define SH_PROMPT "dsh3> "
+#define EXIT_CMD "exit"
+#define EXIT_SC 99
+
+// Standard Return Codes
+#define OK 0
+#define WARN_NO_CMDS -1
+#define ERR_TOO_MANY_COMMANDS -2
+#define ERR_CMD_OR_ARGS_TOO_BIG -3
+#define ERR_CMD_ARGS_BAD -4 // for extra credit
+#define ERR_MEMORY -5
+#define ERR_EXEC_CMD -6
+#define OK_EXIT -7
+
+// prototypes
+int alloc_cmd_buff(cmd_buff_t *cmd_buff);
+int free_cmd_buff(cmd_buff_t *cmd_buff);
+int clear_cmd_buff(cmd_buff_t *cmd_buff);
+int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff);
+int close_cmd_buff(cmd_buff_t *cmd_buff);
+int build_cmd_list(char *cmd_line, command_list_t *clist);
+int free_cmd_list(command_list_t *cmd_lst);
+
+// built in command stuff
+typedef enum
+{
+    BI_CMD_EXIT,
+    BI_CMD_DRAGON,
+    BI_CMD_CD,
+    BI_NOT_BI,
+    BI_EXECUTED,
+} Built_In_Cmds;
+Built_In_Cmds match_command(const char *input);
+Built_In_Cmds exec_built_in_cmd(cmd_buff_t *cmd);
+
+// main execution context
+int exec_local_cmd_loop();
+int exec_cmd(cmd_buff_t *cmd);
+int execute_pipeline(command_list_t *clist);
+
+// output constants
+#define CMD_OK_HEADER "PARSED COMMAND LINE - TOTAL COMMANDS %d\n"
+#define CMD_WARN_NO_CMD "warning: no commands provided\n"
+#define CMD_ERR_PIPE_LIMIT "error: piping limited to %d commands\n"
+
+#endif
diff --git a/A5/makefile b/A5/makefile
new file mode 100644
index 0000000000000000000000000000000000000000..48bf9f0d4cc1ac347ea386d359a65da12cf19832
--- /dev/null
+++ b/A5/makefile
@@ -0,0 +1,32 @@
+
+# Compiler settings
+CC = gcc
+CFLAGS = -Wall -Wextra -g
+
+# Target executable name
+TARGET = dsh
+
+# Find all source and header files
+SRCS = $(wildcard *.c)
+HDRS = $(wildcard *.h)
+
+# Default target
+all: $(TARGET)
+
+# Compile source to executable
+$(TARGET): $(SRCS) $(HDRS)
+	$(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
+
+# Clean up build files
+clean:
+	rm -f $(TARGET)
+
+test:
+	bats $(wildcard ./bats/*.sh)
+
+valgrind:
+	echo "pwd\nexit" | valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./$(TARGET) 
+	echo "pwd\nexit" | valgrind --tool=helgrind --error-exitcode=1 ./$(TARGET) 
+
+# Phony targets
+.PHONY: all clean test
diff --git a/A5/questions.md b/A5/questions.md
new file mode 100644
index 0000000000000000000000000000000000000000..5755e20f95c4c57455032e3006f70e09ec11816a
--- /dev/null
+++ b/A5/questions.md
@@ -0,0 +1,15 @@
+1. Your shell forks multiple child processes when executing piped commands. How does your implementation ensure that all child processes complete before the shell continues accepting user input? What would happen if you forgot to call waitpid() on all child processes?
+
+_answer here_ : I think my shell calls waitpid() for each child process in the pipeline. This ensures the shell doesn’t move on to the next user command until all children have finished. Without calling waitpid(), the child processes would become “zombies,” lingering in the process table and potentially causing resource leaks.
+
+2. The dup2() function is used to redirect input and output file descriptors. Explain why it is necessary to close unused pipe ends after calling dup2(). What could go wrong if you leave pipes open?
+
+_answer here_ : After using dup2() to redirect a child’s STDIN/STDOUT, any pipe file descriptors not needed by that child should be closed. If you leave these pipe ends open, the kernel won’t detect an end-of-stream (EOF) correctly, and you risk running out of file descriptors. It can also cause hangs if the reading process waits forever for a pipe to close.
+
+3. Your shell recognizes built-in commands (cd, exit, dragon). Unlike external commands, built-in commands do not require execvp(). Why is cd implemented as a built-in rather than an external command? What challenges would arise if cd were implemented as an external process?
+
+_answer here_ : cd changes the current working directory of the shell process itself. If it were an external command, it would run in a child process, change that process’s directory, and then exit—leaving the shell’s directory unchanged. Implementing cd as a built-in avoids that problem by calling chdir() in the shell’s own process space.
+
+4. Currently, your shell supports a fixed number of piped commands (CMD_MAX). How would you modify your implementation to allow an arbitrary number of piped commands while still handling memory allocation efficiently? What trade-offs would you need to consider?
+
+_answer here_ : For now, there’s a fixed limit (CMD_MAX). To support an arbitrary number of commands, I would dynamically allocate the array (e.g., using realloc as I parse more commands). The main trade-offs are complexity (managing memory carefully) and performance (frequent resizing vs. a static array). But it lets the shell handle pipelines of any length without a hard-coded limit.
\ No newline at end of file