diff --git a/bespin/argparser.py b/bespin/argparser.py
index 5fc8fa3..f97d735 100644
--- a/bespin/argparser.py
+++ b/bespin/argparser.py
@@ -40,6 +40,8 @@ class ArgParser(object):
         jobs_subparser = jobs_parser.add_subparsers()
 
         list_jobs_parser = jobs_subparser.add_parser('list', description='list workflows')
+        list_jobs_parser.add_argument('-a', '--all', action='store_true',
+                                      help='show all workflow versions instead of just the most recent.')
         list_jobs_parser.set_defaults(func=self._run_list_workflows)
 
     def _create_job_parser(self, subparsers):
@@ -79,8 +81,8 @@ class ArgParser(object):
     def _run_list_jobs(self, _):
         self.target_object.jobs_list()
 
-    def _run_list_workflows(self, _):
-        self.target_object.workflows_list()
+    def _run_list_workflows(self, args):
+        self.target_object.workflows_list(all_versions=args.all)
 
     def _run_init_job(self, args):
         self.target_object.init_job(args.tag, args.outfile)
diff --git a/bespin/commands.py b/bespin/commands.py
index 8f7271d..c8c1fb6 100644
--- a/bespin/commands.py
+++ b/bespin/commands.py
@@ -44,6 +44,7 @@ USER_PLACEHOLDER_DICT = {
     }
 }
 
+
 class Commands(object):
     """
     Commands run based on command line input.
@@ -61,12 +62,13 @@ class Commands(object):
         config = ConfigFile().read_or_create_config()
         return BespinApi(config, user_agent_str=self.user_agent_str)
 
-    def workflows_list(self):
+    def workflows_list(self, all_versions):
         """
         Print out a table of workflows/questionnaires
+        :param all_versions: bool: when true show all versions otherwise show most recent
         """
         api = self._create_api()
-        workflow_data = WorkflowDetails(api)
+        workflow_data = WorkflowDetails(api, all_versions)
         print(Table(workflow_data.column_names, workflow_data.get_column_data()))
 
     def jobs_list(self):
@@ -167,10 +169,11 @@ class WorkflowDetails(object):
     """
     Creates column data based on workflows/questionnaires
     """
-    TAG_COLUMN_NAME = "latest version tag"
+    TAG_COLUMN_NAME = "version tag"
 
-    def __init__(self, api):
+    def __init__(self, api, all_versions):
         self.api = api
+        self.all_versions = all_versions
         self.column_names = ["id", "name", self.TAG_COLUMN_NAME]
 
     def get_column_data(self):
@@ -181,10 +184,13 @@ class WorkflowDetails(object):
         data = []
         for workflow in self.api.workflows_list():
             if len(workflow['versions']):
-                latest_version = workflow['versions'][-1]
-                for questionnaire in self.api.questionnaires_list(workflow_version=latest_version):
-                    workflow[self.TAG_COLUMN_NAME] = questionnaire['tag']
-                    data.append(workflow)
+                versions = workflow['versions']
+                if not self.all_versions:
+                    versions = versions[-1:]
+                for version in versions:
+                    for questionnaire in self.api.questionnaires_list(workflow_version=version):
+                        workflow[self.TAG_COLUMN_NAME] = questionnaire['tag']
+                        data.append(dict(workflow))
         return data
 
 

Allow listing all workflow versions
Allow users to see all version of workflows so the can pick older tags.
Initially we thought users could just change the `v#` portion of the tags, but due to changes in the suffix portion of tags this is not possible.
diff --git a/bespin/test_argparse.py b/bespin/test_argparse.py
index 1f1cd6e..1af15be 100644
--- a/bespin/test_argparse.py
+++ b/bespin/test_argparse.py
@@ -10,9 +10,17 @@ class ArgParserTestCase(TestCase):
         self.target_object = Mock()
         self.arg_parser = ArgParser(version_str='1.0', target_object=self.target_object)
 
-    def test_workflows_list(self):
+    def test_workflows_list_current_versions(self):
         self.arg_parser.parse_and_run_commands(["workflows", "list"])
-        self.target_object.workflows_list.assert_called()
+        self.target_object.workflows_list.assert_called_with(all_versions=False)
+
+    def test_workflows_list_all_versions(self):
+        self.arg_parser.parse_and_run_commands(["workflows", "list", "--all"])
+        self.target_object.workflows_list.assert_called_with(all_versions=True)
+
+    def test_workflows_list_all_versions_short_flag(self):
+        self.arg_parser.parse_and_run_commands(["workflows", "list", "-a"])
+        self.target_object.workflows_list.assert_called_with(all_versions=True)
 
     def test_init_job(self):
         self.arg_parser.parse_and_run_commands(["jobs", "init", "--tag", "exome/v1/human"])
diff --git a/bespin/test_commands.py b/bespin/test_commands.py
index e3756c8..5bd78a8 100644
--- a/bespin/test_commands.py
+++ b/bespin/test_commands.py
@@ -18,13 +18,30 @@ class CommandsTestCase(TestCase):
     @patch('bespin.commands.WorkflowDetails')
     @patch('bespin.commands.Table')
     @patch('bespin.commands.print')
-    def test_workflows_list(self, mock_print, mock_table, mock_workflow_details, mock_bespin_api, mock_config_file):
+    def test_workflows_list_latest_versions(self, mock_print, mock_table, mock_workflow_details, mock_bespin_api,
+                                            mock_config_file):
         commands = Commands(self.version_str, self.user_agent_str)
-        commands.workflows_list()
+        commands.workflows_list(all_versions=False)
         workflow_details = mock_workflow_details.return_value
         mock_table.assert_called_with(workflow_details.column_names,
                                       workflow_details.get_column_data.return_value)
         mock_print.assert_called_with(mock_table.return_value)
+        mock_workflow_details.assert_called_with(mock_bespin_api.return_value, False)
+
+    @patch('bespin.commands.ConfigFile')
+    @patch('bespin.commands.BespinApi')
+    @patch('bespin.commands.WorkflowDetails')
+    @patch('bespin.commands.Table')
+    @patch('bespin.commands.print')
+    def test_workflows_list_all_versions(self, mock_print, mock_table, mock_workflow_details, mock_bespin_api,
+                                         mock_config_file):
+        commands = Commands(self.version_str, self.user_agent_str)
+        commands.workflows_list(all_versions=True)
+        workflow_details = mock_workflow_details.return_value
+        mock_table.assert_called_with(workflow_details.column_names,
+                                      workflow_details.get_column_data.return_value)
+        mock_print.assert_called_with(mock_table.return_value)
+        mock_workflow_details.assert_called_with(mock_bespin_api.return_value, True)
 
     @patch('bespin.commands.ConfigFile')
     @patch('bespin.commands.BespinApi')
@@ -156,9 +173,9 @@ class WorkflowDetailsTestCase(TestCase):
         mock_api.questionnaires_list.return_value = [
             {'tag': 'exome/v2/human'}
         ]
-        details = WorkflowDetails(mock_api)
+        details = WorkflowDetails(mock_api, all_versions=False)
         expected_data = [{'id': 1,
-                          'latest version tag': 'exome/v2/human',
+                          'version tag': 'exome/v2/human',
                           'name': 'exome',
                           'versions': [1, 2]}]
         column_data = details.get_column_data()
@@ -166,12 +183,50 @@ class WorkflowDetailsTestCase(TestCase):
         self.assertEqual(column_data, expected_data)
         mock_api.questionnaires_list.assert_called_with(workflow_version=2)
 
-    def test_ignores_workflows_without_versions(self):
+        mock_api.questionnaires_list.reset_mock()
+        mock_api.questionnaires_list.side_effect = [
+            [{'tag': 'exome/v1/human'}],
+            [{'tag': 'exome/v2/human'}]
+        ]
+        details = WorkflowDetails(mock_api, all_versions=True)
+        expected_data = [
+            {
+                'id': 1,
+                'version tag': 'exome/v1/human',
+                'name': 'exome',
+                'versions': [1, 2]
+            },
+            {
+                'id': 1,
+                'version tag': 'exome/v2/human',
+                'name': 'exome',
+                'versions': [1, 2]
+            },
+        ]
+        column_data = details.get_column_data()
+        self.assertEqual(len(column_data), 2)
+        self.assertEqual(column_data, expected_data)
+        mock_api.questionnaires_list.assert_has_calls([
+            call(workflow_version=1),
+            call(workflow_version=2),
+        ])
+
+    def test_ignores_workflows_without_versions_when_latest(self):
+        mock_api = Mock()
+        mock_api.workflows_list.return_value = [
+            {'id': 1, 'name': 'no-versions', 'versions': []},
+        ]
+        details = WorkflowDetails(mock_api, all_versions=False)
+        column_data = details.get_column_data()
+        self.assertEqual(len(column_data), 0)
+        mock_api.questionnaires_list.assert_not_called()
+
+    def test_ignores_workflows_without_versions_when_all(self):
         mock_api = Mock()
         mock_api.workflows_list.return_value = [
             {'id': 1, 'name': 'no-versions', 'versions': []},
         ]
-        details = WorkflowDetails(mock_api)
+        details = WorkflowDetails(mock_api, all_versions=True)
         column_data = details.get_column_data()
         self.assertEqual(len(column_data), 0)
         mock_api.questionnaires_list.assert_not_called()

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
devRequirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
azure-common==1.1.28
azure-core==1.32.0
azure-identity==1.21.0
azure-mgmt-core==1.5.0
azure-mgmt-storage==22.1.1
azure-storage-blob==12.25.1
azure-storage-file-datalake==12.20.0
-e git+https://github.com/Duke-GCB/bespin-cli.git@70450d7b00c477580d6851b8aaa65433a54cf415#egg=bespin_cli
certifi==2025.1.31
cffi==1.17.1
charset-normalizer==3.4.1
cryptography==44.0.2
DukeDSClient==4.0.0
exceptiongroup==1.2.2
future==1.0.0
idna==3.10
iniconfig==2.1.0
isodate==0.7.2
mock==2.0.0
msal==1.32.0
msal-extensions==1.3.1
msgraph-core==0.2.2
packaging==24.2
pbr==6.1.1
pluggy==1.5.0
pycparser==2.22
PyJWT==2.10.1
pytest==8.3.5
pytz==2025.2
PyYAML==6.0.2
requests==2.32.3
six==1.17.0
tabulate==0.9.0
tenacity==6.2.0
tomli==2.2.1
typing_extensions==4.13.0
urllib3==2.3.0

name: bespin-cli
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - azure-common==1.1.28
      - azure-core==1.32.0
      - azure-identity==1.21.0
      - azure-mgmt-core==1.5.0
      - azure-mgmt-storage==22.1.1
      - azure-storage-blob==12.25.1
      - azure-storage-file-datalake==12.20.0
      - certifi==2025.1.31
      - cffi==1.17.1
      - charset-normalizer==3.4.1
      - cryptography==44.0.2
      - dukedsclient==4.0.0
      - exceptiongroup==1.2.2
      - future==1.0.0
      - idna==3.10
      - iniconfig==2.1.0
      - isodate==0.7.2
      - mock==2.0.0
      - msal==1.32.0
      - msal-extensions==1.3.1
      - msgraph-core==0.2.2
      - packaging==24.2
      - pbr==6.1.1
      - pluggy==1.5.0
      - pycparser==2.22
      - pyjwt==2.10.1
      - pytest==8.3.5
      - pytz==2025.2
      - pyyaml==6.0.2
      - requests==2.32.3
      - six==1.17.0
      - tabulate==0.9.0
      - tenacity==6.2.0
      - tomli==2.2.1
      - typing-extensions==4.13.0
      - urllib3==2.3.0
prefix: /opt/conda/envs/bespin-cli

bespin/test_argparse.py::ArgParserTestCase::test_workflows_list_all_versions bespin/test_argparse.py::ArgParserTestCase::test_workflows_list_all_versions_short_flag bespin/test_argparse.py::ArgParserTestCase::test_workflows_list_current_versions bespin/test_commands.py::CommandsTestCase::test_workflows_list_all_versions bespin/test_commands.py::CommandsTestCase::test_workflows_list_latest_versions bespin/test_commands.py::WorkflowDetailsTestCase::test_get_column_data bespin/test_commands.py::WorkflowDetailsTestCase::test_ignores_workflows_without_versions_when_all bespin/test_commands.py::WorkflowDetailsTestCase::test_ignores_workflows_without_versions_when_latest
bespin/test_commands.py::JobFileTestCase::test_yaml_str
bespin/test_argparse.py::ArgParserTestCase::test_cancel_job bespin/test_argparse.py::ArgParserTestCase::test_create_job bespin/test_argparse.py::ArgParserTestCase::test_delete_job bespin/test_argparse.py::ArgParserTestCase::test_init_job bespin/test_argparse.py::ArgParserTestCase::test_restart_job bespin/test_argparse.py::ArgParserTestCase::test_start_job bespin/test_argparse.py::ArgParserTestCase::test_start_job_with_optional_token bespin/test_commands.py::CommandsTestCase::test_cancel_job bespin/test_commands.py::CommandsTestCase::test_create_job bespin/test_commands.py::CommandsTestCase::test_delete_job bespin/test_commands.py::CommandsTestCase::test_init_job bespin/test_commands.py::CommandsTestCase::test_jobs_list bespin/test_commands.py::CommandsTestCase::test_restart_job bespin/test_commands.py::CommandsTestCase::test_start_job_no_token bespin/test_commands.py::CommandsTestCase::test_start_job_with_token bespin/test_commands.py::TableTestCase::test_str bespin/test_commands.py::JobFileTestCase::test_create_job bespin/test_commands.py::JobFileTestCase::test_create_user_job_order_json bespin/test_commands.py::JobFileTestCase::test_get_dds_files_details bespin/test_commands.py::JobFileLoaderTestCase::test_create_job_file bespin/test_commands.py::JobFileLoaderTestCase::test_validate_job_file_data_invalid_job_order_params bespin/test_commands.py::JobFileLoaderTestCase::test_validate_job_file_data_invalid_name_and_fund_code bespin/test_commands.py::JobFileLoaderTestCase::test_validate_job_file_data_ok bespin/test_commands.py::JobQuestionnaireTestCase::test_create_job_file_with_placeholders bespin/test_commands.py::JobQuestionnaireTestCase::test_create_placeholder_value bespin/test_commands.py::JobQuestionnaireTestCase::test_format_user_fields bespin/test_commands.py::JobOrderWalkerTestCase::test_format_file_path bespin/test_commands.py::JobOrderWalkerTestCase::test_walk bespin/test_commands.py::JobOrderPlaceholderCheckTestCase::test_walk bespin/test_commands.py::JobOrderFormatFilesTestCase::test_walk bespin/test_commands.py::JobOrderFileDetailsTestCase::test_walk bespin/test_commands.py::JobsListTestCase::test_column_names bespin/test_commands.py::JobsListTestCase::test_get_column_data bespin/test_commands.py::JobsListTestCase::test_get_elapsed_hours bespin/test_commands.py::JobsListTestCase::test_get_workflow_tag
bespin/argparser.py bespin/commands.py
bespin/argparser.py bespin/commands.py