vss-cli 2025.8.0.dev1__tar.gz → 2025.9.0.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {vss_cli-2025.8.0.dev1/vss_cli.egg-info → vss_cli-2025.9.0.dev2}/PKG-INFO +1 -1
  2. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/config.py +205 -36
  3. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/const.py +2 -2
  4. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/clib.yaml +1 -1
  5. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/clone.yaml +1 -1
  6. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/shell.yaml +1 -1
  7. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/template.yaml +1 -1
  8. vss_cli-2025.9.0.dev2/vss_cli/plugins/assist.py +156 -0
  9. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2/vss_cli.egg-info}/PKG-INFO +1 -1
  10. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli.egg-info/top_level.txt +1 -0
  11. vss_cli-2025.8.0.dev1/vss_cli/plugins/assist.py +0 -76
  12. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/LICENSE +0 -0
  13. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/README.md +0 -0
  14. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/docs/conf.py +0 -0
  15. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/pyproject.toml +0 -0
  16. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/scripts/update_homebrew_formula.py +0 -0
  17. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/setup.cfg +0 -0
  18. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/tests/test_vss_cli.py +0 -0
  19. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/__init__.py +0 -0
  20. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/autocompletion.py +0 -0
  21. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/cli.py +0 -0
  22. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/config.yaml +0 -0
  23. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data/image.yaml +0 -0
  24. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/data_types.py +0 -0
  25. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/exceptions.py +0 -0
  26. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/hcio.py +0 -0
  27. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/helper.py +0 -0
  28. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/__init__.py +0 -0
  29. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/account.py +0 -0
  30. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/completion.py +0 -0
  31. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute.py +0 -0
  32. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/__init__.py +0 -0
  33. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/callbacks.py +0 -0
  34. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/contentlib.py +0 -0
  35. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/domain.py +0 -0
  36. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/floppy.py +0 -0
  37. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/folder.py +0 -0
  38. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/helper.py +0 -0
  39. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/image.py +0 -0
  40. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/inventory.py +0 -0
  41. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/iso.py +0 -0
  42. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/net.py +0 -0
  43. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/os.py +0 -0
  44. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/rel_args.py +0 -0
  45. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/rel_opts.py +0 -0
  46. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/template.py +0 -0
  47. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/vm.py +0 -0
  48. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/compute_plugins/vmdks.py +0 -0
  49. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/configure.py +0 -0
  50. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/key.py +0 -0
  51. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/mcp.py +0 -0
  52. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/message.py +0 -0
  53. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/misc.py +0 -0
  54. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/ovf.py +0 -0
  55. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/plugins.py +0 -0
  56. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/raw.py +0 -0
  57. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request.py +0 -0
  58. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/__init__.py +0 -0
  59. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/change.py +0 -0
  60. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/export.py +0 -0
  61. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/folder.py +0 -0
  62. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/image.py +0 -0
  63. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/inventory.py +0 -0
  64. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/new.py +0 -0
  65. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/restore.py +0 -0
  66. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/retirement.py +0 -0
  67. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/snapshot.py +0 -0
  68. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/request_plugins/vmdk.py +0 -0
  69. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/service.py +0 -0
  70. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/shell.py +0 -0
  71. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/status.py +0 -0
  72. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/stor.py +0 -0
  73. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/token.py +0 -0
  74. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/upgrade.py +0 -0
  75. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/plugins/vpn.py +0 -0
  76. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/rel_opts.py +0 -0
  77. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/sstatus.py +0 -0
  78. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/utils/__init__.py +0 -0
  79. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/utils/emoji.py +0 -0
  80. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/utils/mcp.py +0 -0
  81. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/utils/threading.py +0 -0
  82. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/validators.py +0 -0
  83. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/vss.py +0 -0
  84. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/vssconst.py +0 -0
  85. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli/yaml.py +0 -0
  86. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli.egg-info/SOURCES.txt +0 -0
  87. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli.egg-info/dependency_links.txt +0 -0
  88. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli.egg-info/entry_points.txt +0 -0
  89. {vss_cli-2025.8.0.dev1 → vss_cli-2025.9.0.dev2}/vss_cli.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vss-cli
3
- Version: 2025.8.0.dev1
3
+ Version: 2025.9.0.dev2
4
4
  Summary: ITS Private Cloud CLI
5
5
  Author-email: University of Toronto <vss-apps@eis.utoronto.ca>
6
6
  Maintainer-email: vss-py@eis.utoronto.ca
@@ -1736,8 +1736,42 @@ class Configuration(VssManager):
1736
1736
  else:
1737
1737
  os.system('clear')
1738
1738
 
1739
- @staticmethod
1739
+ def _get_client_ip(self) -> str:
1740
+ """Get the client's IP address."""
1741
+ import socket
1742
+
1743
+ try:
1744
+ # Try to get the IP by connecting to a public DNS server
1745
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1746
+ s.settimeout(1)
1747
+ # Connect to Google's public DNS
1748
+ s.connect(('8.8.8.8', 80))
1749
+ ip = s.getsockname()[0]
1750
+ s.close()
1751
+ return ip
1752
+ except Exception:
1753
+ # Fallback to localhost if unable to determine
1754
+ return '127.0.0.1'
1755
+
1756
+ def _generate_assistant_api_key(self) -> str:
1757
+ """Generate a new API key for the assistant."""
1758
+ ip_address = self._get_client_ip()
1759
+ client_name = f'{self.user_agent}/{ip_address}'
1760
+
1761
+ auth_endpoint = f'{self.gpt_server}/api/generate-key'
1762
+ payload = {'client_name': client_name}
1763
+
1764
+ with requests.post(auth_endpoint, json=payload) as response:
1765
+ if response.status_code not in [200, 201]:
1766
+ raise VssCliError(
1767
+ f'Failed to generate API key. '
1768
+ f'Status: {response.status_code}'
1769
+ )
1770
+ data = response.json()
1771
+ return data.get('api_key')
1772
+
1740
1773
  def get_new_chat_id(
1774
+ self,
1741
1775
  chat_endpoint: str,
1742
1776
  persona_id: int,
1743
1777
  description: str,
@@ -1751,7 +1785,7 @@ class Configuration(VssManager):
1751
1785
  if response.status_code in [401, 403, 500, 502, 503, 504]:
1752
1786
  raise VssCliError(
1753
1787
  'Invalid response from the API. '
1754
- 'Have you provided VSS_GPT_TOKEN?'
1788
+ 'Failed to create chat session.'
1755
1789
  )
1756
1790
  rv = response.json()
1757
1791
  chat_id = rv['chat_session_id']
@@ -1762,25 +1796,16 @@ class Configuration(VssManager):
1762
1796
  message: str,
1763
1797
  spinner_cls: Optional[Spinner] = None,
1764
1798
  final_message: str = None,
1765
- ):
1799
+ ) -> Tuple[str, str]:
1766
1800
  """Ask assistant."""
1801
+ # Generate a new API key for this session
1802
+ api_key = self._generate_assistant_api_key()
1803
+
1767
1804
  headers = {
1768
- 'Authorization': self.gpt_token,
1805
+ 'api-key': f'{api_key}',
1769
1806
  'Connection': 'keep-alive',
1770
1807
  'Content-Type': 'application/json',
1771
1808
  }
1772
- retrieval_options = {
1773
- "run_search": "auto",
1774
- "real_time": True,
1775
- "limit": 5,
1776
- "filters": {
1777
- "source_type": None,
1778
- "document_set": None,
1779
- "time_cutoff": None,
1780
- "tags": [],
1781
- "user_file_ids": None,
1782
- },
1783
- }
1784
1809
  top_documents = []
1785
1810
  chat_id = self.get_new_chat_id(
1786
1811
  chat_endpoint=f'{self.gpt_server}/api/chat/create-chat-session',
@@ -1790,17 +1815,9 @@ class Configuration(VssManager):
1790
1815
  )
1791
1816
  # chat payload
1792
1817
  payload = {
1793
- "alternate_assistant_id": 2,
1794
1818
  "chat_session_id": chat_id,
1795
- "prompt_id": self._gpt_persona,
1796
- "parent_message_id": None,
1797
- "regenerate": False,
1798
- "full_doc": False,
1799
- "search_doc_ids": [],
1800
1819
  "message": message,
1801
- "file_descriptors": [],
1802
- "retrieval_options": retrieval_options,
1803
- "use_agentic_search": False,
1820
+ "parent_message_id": None,
1804
1821
  }
1805
1822
  _LOGGING.debug(f'User data payload {payload}')
1806
1823
  answer_text = ''
@@ -1812,27 +1829,117 @@ class Configuration(VssManager):
1812
1829
  ) as response:
1813
1830
  if spinner_cls is not None:
1814
1831
  spinner_cls.stop()
1832
+ reasoning_text = ''
1833
+ message_content = ''
1834
+ internal_search_queries = []
1835
+ internal_search_docs = []
1836
+ final_documents = []
1837
+ citations = []
1838
+ assistant_message_id = None
1839
+
1815
1840
  for line in response.iter_lines():
1841
+ _LOGGING.debug(f"{line=}")
1816
1842
  if line:
1817
1843
  data = json.loads(line)
1818
- if 'top_documents' in data:
1844
+
1845
+ # Handle initial message IDs
1846
+ if (
1847
+ 'user_message_id' in data
1848
+ and 'reserved_assistant_message_id' in data
1849
+ ):
1850
+ assistant_message_id = data[
1851
+ 'reserved_assistant_message_id'
1852
+ ]
1853
+ _LOGGING.debug(
1854
+ f"Message IDs - User: {data['user_message_id']},"
1855
+ f" Assistant: {assistant_message_id}"
1856
+ )
1857
+ continue
1858
+
1859
+ # Handle indexed objects
1860
+ if 'ind' in data and 'obj' in data:
1861
+ obj = data['obj']
1862
+ obj_type = obj.get('type')
1863
+
1864
+ # Handle reasoning streaming
1865
+ if obj_type == 'reasoning_start':
1866
+ _LOGGING.debug("Reasoning started")
1867
+ elif obj_type == 'reasoning_delta':
1868
+ reasoning_chunk = obj.get('reasoning', '')
1869
+ reasoning_text += reasoning_chunk
1870
+ # Optionally display reasoning to user
1871
+ # (you might want to hide this)
1872
+ self.smooth_print(reasoning_chunk)
1873
+
1874
+ # Handle message content streaming
1875
+ elif obj_type == 'message_start':
1876
+ if 'final_documents' in obj:
1877
+ final_documents = obj['final_documents']
1878
+ _LOGGING.debug("Message started")
1879
+ elif obj_type == 'message_delta':
1880
+ content_chunk = obj.get('content', '')
1881
+ message_content += content_chunk
1882
+ answer_text += content_chunk
1883
+ self.smooth_print(content_chunk)
1884
+
1885
+ # Handle internal search tool
1886
+ elif obj_type == 'internal_search_tool_start':
1887
+ _LOGGING.debug(
1888
+ f"Internal search started - Internet: "
1889
+ f"{obj.get('is_internet_search', False)}"
1890
+ )
1891
+ elif obj_type == 'internal_search_tool_delta':
1892
+ if 'queries' in obj and obj['queries']:
1893
+ internal_search_queries.extend(obj['queries'])
1894
+ if 'documents' in obj and obj['documents']:
1895
+ internal_search_docs.extend(obj['documents'])
1896
+ # Update top_documents for backward
1897
+ # compatibility
1898
+ top_documents = obj['documents']
1899
+
1900
+ # Handle citations
1901
+ elif obj_type == 'citation_start':
1902
+ _LOGGING.debug("Citations started")
1903
+ elif obj_type == 'citation_delta':
1904
+ if 'citations' in obj:
1905
+ citations.extend(obj['citations'])
1906
+
1907
+ # Handle section end and stop
1908
+ elif obj_type == 'section_end':
1909
+ _LOGGING.debug(
1910
+ f"Section ended for index {data['ind']}"
1911
+ )
1912
+ elif obj_type == 'stop':
1913
+ _LOGGING.debug(
1914
+ f"Stream stopped for index {data['ind']}"
1915
+ )
1916
+
1917
+ # Handle legacy format (backward compatibility)
1918
+ elif 'top_documents' in data:
1819
1919
  top_documents = data['top_documents']
1820
- answer_piece = data.get('answer_piece')
1821
- if answer_piece is not None:
1822
- answer_text = answer_text + answer_piece
1823
- self.smooth_print(answer_piece)
1920
+ elif 'answer_piece' in data:
1921
+ answer_piece = data['answer_piece']
1922
+ if answer_piece is not None:
1923
+ answer_text = answer_text + answer_piece
1924
+ self.smooth_print(answer_piece)
1925
+ # Use final_documents if available, otherwise fall
1926
+ # back to top_documents
1927
+ docs_to_display = final_documents if final_documents else top_documents
1928
+
1824
1929
  docs = []
1825
1930
  n = 1
1826
- for doc in top_documents:
1827
- docs.append(
1828
- f'[{n}] [{doc["semantic_identifier"]}]({doc["document_id"]})'
1829
- )
1931
+ for doc in docs_to_display:
1932
+ # Handle both document_id and link fields
1933
+ doc_url = doc.get("link") or doc.get("document_id", "")
1934
+ doc_title = doc.get("semantic_identifier", "Unknown Document")
1935
+ docs.append(f'[{n}] [{doc_title}]({doc_url})')
1830
1936
  n += 1
1831
1937
  # make docs
1832
- docs_text = '\n'.join(docs)
1938
+ docs_text = '\n'.join(docs[:5])
1833
1939
  answer_text = answer_text + '\n\n' + docs_text
1834
1940
  # clear console for formatting
1835
- self.clear_console()
1941
+ if not self.debug:
1942
+ self.clear_console()
1836
1943
  from rich.console import Console
1837
1944
  from rich.markdown import Markdown
1838
1945
 
@@ -1841,3 +1948,65 @@ class Configuration(VssManager):
1841
1948
  console.print(markdown)
1842
1949
  console.print()
1843
1950
  console.print(Markdown(f'**Note: {final_message}**'))
1951
+
1952
+ # Return the assistant message ID and API key
1953
+ # for potential feedback
1954
+ return assistant_message_id, api_key
1955
+
1956
+ def provide_assistant_feedback(
1957
+ self,
1958
+ chat_message_id: str,
1959
+ api_key: str,
1960
+ is_positive: bool,
1961
+ feedback_text: Optional[str] = None,
1962
+ ) -> bool:
1963
+ """Provide feedback for an assistant response.
1964
+
1965
+ Args:
1966
+ chat_message_id: The ID of the assistant message
1967
+ to provide feedback for
1968
+ api_key: The API key generated for this session
1969
+ is_positive: True for thumbs up, False for thumbs down
1970
+ feedback_text: Optional feedback text (defaults to
1971
+ 'Helpful' or 'Not helpful')
1972
+
1973
+ Returns:
1974
+ bool: True if feedback was submitted successfully, False otherwise
1975
+ """
1976
+ if feedback_text is None:
1977
+ feedback_text = 'Helpful' if is_positive else 'Not helpful'
1978
+
1979
+ headers = {
1980
+ 'api-key': f'{api_key}',
1981
+ 'Content-Type': 'application/json',
1982
+ }
1983
+
1984
+ payload = {
1985
+ "chat_message_id": chat_message_id,
1986
+ "is_positive": is_positive,
1987
+ "feedback_text": feedback_text,
1988
+ "predefined_feedback": None,
1989
+ }
1990
+
1991
+ feedback_endpoint = f'{self.gpt_server}/api/chat/create-chat-feedback'
1992
+
1993
+ try:
1994
+ with requests.post(
1995
+ feedback_endpoint, headers=headers, json=payload
1996
+ ) as response:
1997
+ if response.status_code in [200, 201]:
1998
+ _LOGGING.debug(
1999
+ f'Feedback submitted successfully '
2000
+ f'for message {chat_message_id}'
2001
+ )
2002
+ return True
2003
+ else:
2004
+ _LOGGING.warning(
2005
+ f'Failed to submit feedback. '
2006
+ f'Status: {response.status_code}, '
2007
+ f'Response: {response.text}'
2008
+ )
2009
+ return False
2010
+ except Exception as e:
2011
+ _LOGGING.error(f'Error submitting feedback: {e}')
2012
+ return False
@@ -8,7 +8,7 @@ except ImportError:
8
8
 
9
9
  PACKAGE_NAME = "vss_cli"
10
10
 
11
- __version__ = "2025.8.0-dev1"
11
+ __version__ = "2025.9.0-dev2"
12
12
 
13
13
 
14
14
  DEFAULT_TIMEOUT = 30
@@ -16,7 +16,7 @@ DEFAULT_ENDPOINT = "https://cloud-api.eis.utoronto.ca"
16
16
  DEFAULT_ENDPOINT_NAME = "cloud-api"
17
17
  DEFAULT_S3_SERVER = "https://vskey-stor.eis.utoronto.ca"
18
18
  DEFAULT_VPN_SERVER = "https://vskey-vn.eis.utoronto.ca"
19
- DEFAULT_GPT_SERVER = "https://gpt.eis.utoronto.ca"
19
+ DEFAULT_GPT_SERVER = "https://gpt-wpy.eis.utoronto.ca"
20
20
  DEFAULT_GPT_PERSONA = 2
21
21
  DEFAULT_GPT_TOKEN = ''
22
22
  _LEGACY_CONFIG = ("~", ".vss-cli", "config.json")
@@ -1,5 +1,5 @@
1
1
  ####################################################
2
- # VSS-CLI Spec from CLib 2025.8.0-dev1 #
2
+ # VSS-CLI Spec from CLib 2025.9.0-dev2 #
3
3
  ####################################################
4
4
  built: clib # Required: Do not remove.
5
5
  machine:
@@ -1,5 +1,5 @@
1
1
  ####################################################
2
- # VSS-CLI Spec from Clone 2025.8.0-dev1 #
2
+ # VSS-CLI Spec from Clone 2025.9.0-dev2 #
3
3
  ####################################################
4
4
  built: clone # Required: Do not remove.
5
5
  machine:
@@ -1,5 +1,5 @@
1
1
  ####################################################
2
- # VSS-CLI Spec shell VM 2025.8.0-dev1 #
2
+ # VSS-CLI Spec shell VM 2025.9.0-dev2 #
3
3
  ####################################################
4
4
  built: os_install # Required: Do not remove.
5
5
  machine:
@@ -1,5 +1,5 @@
1
1
  ####################################################
2
- # VSS-CLI Spec from Template 2025.8.0-dev1 #
2
+ # VSS-CLI Spec from Template 2025.9.0-dev2 #
3
3
  ####################################################
4
4
  built: template # Required: Do not remove.
5
5
  machine:
@@ -0,0 +1,156 @@
1
+ """AI assistant."""
2
+ import logging
3
+ import random
4
+ import sys
5
+
6
+ import click
7
+
8
+ from vss_cli.cli import pass_context
9
+ from vss_cli.config import Configuration
10
+ from vss_cli.utils.emoji import EMOJI_UNICODE
11
+
12
+ _LOGGING = logging.getLogger(__name__)
13
+
14
+ ej_ai = EMOJI_UNICODE.get(':robot_face:')
15
+ ej_rk = EMOJI_UNICODE.get(':rocket:')
16
+ ej_thumbs_up = EMOJI_UNICODE.get(':thumbs_up:')
17
+ ej_thumbs_down = EMOJI_UNICODE.get(':thumbs_down:')
18
+ ej_checkmark = EMOJI_UNICODE.get(':white_heavy_check_mark:')
19
+ ej_star = EMOJI_UNICODE.get(':star:')
20
+
21
+ we_msg = f"""Hi, I’m UTORcloudy {ej_ai}, the ITS Private Cloud virtual agent.
22
+ I can help with account, virtual machine management, billing questions
23
+ and more. {ej_rk}
24
+ """
25
+
26
+ suggestions = [
27
+ "How to deploy an Ubuntu virtual machine?",
28
+ "How do I get started?",
29
+ "What are the VSS Guidelines?",
30
+ "How can I reset my account password?",
31
+ "How to enable Ubuntu Pro on your VM?",
32
+ "How to activate Windows Server in the ITS Private Cloud?",
33
+ "Do you provide public IP addresses?",
34
+ "How Can I change CPU, Memory,Network, HDD specs after a VM is created?",
35
+ "How to request an SSL certificate?",
36
+ "What additional services are included in my ITS Private Cloud bill?",
37
+ "What factors determine the cost of my ITS Private Cloud usage?",
38
+ 'How can I install sentinelOne on my VM?',
39
+ ]
40
+
41
+ final_msg = """
42
+ Responses are generated by artificial intelligence and may contain errors.
43
+ Always check sources and refer to actual policies for reliable information."""
44
+
45
+
46
+ @click.command('assist', short_help='VSS AI Assistant')
47
+ @click.option(
48
+ '--no-load', is_flag=True, default=False, help='do not load config'
49
+ )
50
+ @click.option(
51
+ '--no-feedback', is_flag=True, default=False, help='skip feedback prompt'
52
+ )
53
+ @click.argument(
54
+ "message",
55
+ required=False,
56
+ )
57
+ @pass_context
58
+ def cli(ctx: Configuration, no_load: bool, no_feedback: bool, message: str):
59
+ """Manage your VSS account."""
60
+ with ctx.spinner(disable=ctx.debug) as spinner_cls:
61
+ if no_load:
62
+ ctx.set_defaults()
63
+ else:
64
+ ctx.load_config(spinner_cls=spinner_cls)
65
+ _LOGGING.debug(
66
+ f'GPT settings: {ctx.gpt_persona=}, '
67
+ f'{ctx.gpt_token=}, {ctx.gpt_server}'
68
+ )
69
+ if not message:
70
+ spinner_cls.stop()
71
+ ctx.secho(we_msg)
72
+ default = random.choice(suggestions)
73
+ message = click.prompt(
74
+ "How may I assist you?",
75
+ type=str,
76
+ prompt_suffix=" ",
77
+ show_default=True,
78
+ default=f"{default}",
79
+ )
80
+ ctx.echo("")
81
+ spinner_cls.start()
82
+ # ask assistant
83
+ assistant_message_id, api_key = ctx.ask_assistant(
84
+ spinner_cls=spinner_cls, message=message, final_message=final_msg
85
+ )
86
+ # Only ask for feedback if enabled and we have a valid message ID
87
+ if not no_feedback and assistant_message_id and api_key:
88
+ # Compact feedback prompt
89
+ # Single line feedback options
90
+ ctx.echo("")
91
+ ctx.secho("Rate this response: ", fg='cyan', nl=False, bold=True)
92
+ ctx.secho(f"{ej_thumbs_up} Helpful (y) ", fg='green', nl=False)
93
+ ctx.secho(
94
+ f"{ej_thumbs_down} Not helpful (n) ", fg='yellow', nl=False
95
+ )
96
+ ctx.secho("Skip (s)", fg='bright_black')
97
+
98
+ # Get user input with compact prompt
99
+ feedback_choice = click.prompt(
100
+ "",
101
+ type=click.Choice(['y', 'n', 's'], case_sensitive=False),
102
+ default='s',
103
+ show_choices=False,
104
+ show_default=False,
105
+ prompt_suffix="Choice: ",
106
+ )
107
+
108
+ # Process feedback based on choice
109
+ if feedback_choice.lower() == 'y':
110
+ success = ctx.provide_assistant_feedback(
111
+ chat_message_id=assistant_message_id,
112
+ is_positive=True,
113
+ api_key=api_key,
114
+ )
115
+ if success:
116
+ ctx.secho(
117
+ f"{ej_checkmark} Thanks for your feedback!", fg='green'
118
+ )
119
+ else:
120
+ _LOGGING.debug("Failed to submit positive feedback")
121
+
122
+ elif feedback_choice.lower() == 'n':
123
+ # Compact negative feedback prompt
124
+ additional_feedback = click.prompt(
125
+ "Details (optional)",
126
+ default="",
127
+ show_default=False,
128
+ prompt_suffix=": ",
129
+ )
130
+
131
+ feedback_text = (
132
+ additional_feedback
133
+ if additional_feedback
134
+ else "Not helpful"
135
+ )
136
+ success = ctx.provide_assistant_feedback(
137
+ chat_message_id=assistant_message_id,
138
+ is_positive=False,
139
+ api_key=api_key,
140
+ feedback_text=feedback_text,
141
+ )
142
+ if success:
143
+ ctx.secho(
144
+ f"{ej_checkmark} Thanks! We'll improve.", fg='cyan'
145
+ )
146
+ else:
147
+ _LOGGING.debug("Failed to submit negative feedback")
148
+
149
+ else:
150
+ ctx.secho("Skipped.", fg='bright_black')
151
+ else:
152
+ _LOGGING.debug(
153
+ f"Cannot provide feedback: "
154
+ f"assistant_message_id={assistant_message_id}, "
155
+ f"api_key={'present' if api_key else 'missing'}"
156
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vss-cli
3
- Version: 2025.8.0.dev1
3
+ Version: 2025.9.0.dev2
4
4
  Summary: ITS Private Cloud CLI
5
5
  Author-email: University of Toronto <vss-apps@eis.utoronto.ca>
6
6
  Maintainer-email: vss-py@eis.utoronto.ca
@@ -1,4 +1,5 @@
1
1
  HomebrewFormula
2
+ _docs
2
3
  artwork
3
4
  dist
4
5
  docker
@@ -1,76 +0,0 @@
1
- """AI assistant."""
2
- import logging
3
- import random
4
-
5
- import click
6
-
7
- from vss_cli.cli import pass_context
8
- from vss_cli.config import Configuration
9
- from vss_cli.utils.emoji import EMOJI_UNICODE
10
-
11
- _LOGGING = logging.getLogger(__name__)
12
-
13
- ej_ai = EMOJI_UNICODE.get(':robot_face:')
14
- ej_rk = EMOJI_UNICODE.get(':rocket:')
15
-
16
- we_msg = f"""Hi, I’m UTORcloudy {ej_ai}, the ITS Private Cloud virtual agent.
17
- I can help with account, virtual machine management, billing questions
18
- and more. {ej_rk}
19
- """
20
-
21
- suggestions = [
22
- "How to deploy an Ubuntu virtual machine?",
23
- "How do I get started?",
24
- "What are the VSS Guidelines?",
25
- "How can I reset my account password?",
26
- "How to enable Ubuntu Pro on your VM?",
27
- "How to activate Windows Server in the ITS Private Cloud?",
28
- "Do you provide public IP addresses?",
29
- "How Can I change CPU, Memory,Network, HDD specs after a VM is created?",
30
- "How to request an SSL certificate?",
31
- "What additional services are included in my ITS Private Cloud bill?",
32
- "What factors determine the cost of my ITS Private Cloud usage?",
33
- 'How can I install sentinelOne on my VM?',
34
- ]
35
-
36
- final_msg = """
37
- Responses are generated by artificial intelligence and may contain errors.
38
- Always check sources and refer to actual policies for reliable information."""
39
-
40
-
41
- @click.command('assist', short_help='VSS AI Assistant')
42
- @click.option(
43
- '--no-load', is_flag=True, default=False, help='do not load config'
44
- )
45
- @click.argument(
46
- "message",
47
- required=False,
48
- )
49
- @pass_context
50
- def cli(ctx: Configuration, no_load: bool, message: str):
51
- """Manage your VSS account."""
52
- with ctx.spinner(disable=ctx.debug) as spinner_cls:
53
- if no_load:
54
- ctx.set_defaults()
55
- else:
56
- ctx.load_config(spinner_cls=spinner_cls)
57
- _LOGGING.debug(
58
- f'GPT settings: {ctx.gpt_persona=}, '
59
- f'{ctx.gpt_token=}, {ctx.gpt_server}'
60
- )
61
- if not message:
62
- spinner_cls.stop()
63
- ctx.secho(we_msg)
64
- default = random.choice(suggestions)
65
- message = click.prompt(
66
- "How may I assist you?",
67
- type=str,
68
- prompt_suffix=" ",
69
- show_default=True,
70
- default=f"{default}",
71
- )
72
- ctx.echo("")
73
- spinner_cls.start()
74
- ctx.ask_assistant(
75
- spinner_cls=spinner_cls, message=message, final_message=final_msg
76
- )
File without changes