diff --git a/.aitests/config b/.aitests/config
new file mode 100644
index 00000000..eb742a25
--- /dev/null
+++ b/.aitests/config
@@ -0,0 +1 @@
+testDirectory: ../tests
diff --git a/.azure/pipelines/docker_proxy_setup.yaml b/.azure/pipelines/docker_proxy_setup.yaml
index 28656817..13a7df80 100644
--- a/.azure/pipelines/docker_proxy_setup.yaml
+++ b/.azure/pipelines/docker_proxy_setup.yaml
@@ -9,7 +9,7 @@ steps:
inputs:
command: build
repository: nginx-recording-test-proxy-dev
- dockerfile: 'tests/recordproxy/Dockerfile'
+ dockerfile: 'src/testing/adapters/recordproxy/Dockerfile'
containerRegistry: 'acrbn-acr'
- bash: |
docker images
diff --git a/.azure/pipelines/nginx-docker-build-and-push.yaml b/.azure/pipelines/nginx-docker-build-and-push.yaml
index 264269ca..7c1a0750 100644
--- a/.azure/pipelines/nginx-docker-build-and-push.yaml
+++ b/.azure/pipelines/nginx-docker-build-and-push.yaml
@@ -10,7 +10,7 @@ variables:
baseVer: '20.04'
# The path of the image (should normally not change)
repositorydocker: nginx-recording-test-proxy
- dockerfile: 'tests/recordproxy/Dockerfile.base'
+ dockerfile: 'src/testing/adapters/recordproxy/Dockerfile.base'
version: $(Build.BuildId)
trigger:
@@ -47,14 +47,13 @@ stages:
inputs:
containerRegistry: 'acrbn-acr'
command: 'login'
- - task: Docker@2
- displayName: Build ($(dockerfile) - $(version))
- inputs:
- command: 'build'
- containerRegistry: 'acrbn-acr'
- repository: $[variables.repositorydocker]
- Dockerfile: $(dockerfile)
- buildContext: '.'
+ - task: Docker@2
+ displayName: Build ($(dockerfile) - $(version))
+ inputs:
+ command: 'build'
+ containerRegistry: 'acrbn-acr'
+ repository: $(repositorydocker)
+ Dockerfile: $(dockerfile)
################################################################################
## SBOM
@@ -110,6 +109,4 @@ stages:
command: 'push'
containerRegistry: 'acrbn-acr'
repository: '$(repositorydocker)'
- tags: |
- ${{ variables.repositorydocker }}
- ${{ variables.repositorydocker }}-$(Build.BuildId)
+ tags: $(Build.BuildId)
diff --git a/.azure/pipelines/test_execute_and_report.yaml b/.azure/pipelines/test_execute_and_report.yaml
index 2247e136..d8d2f1c0 100644
--- a/.azure/pipelines/test_execute_and_report.yaml
+++ b/.azure/pipelines/test_execute_and_report.yaml
@@ -3,6 +3,31 @@ parameters:
TestFilter: 'TestCategory!=SkipOnVSTS'
envToSet: ''
steps:
+
+# -----------------------------------------------------------------------------
+# Ensure we can use the appropriate Java, Go, and NodeJs tools
+# -----------------------------------------------------------------------------
+- bash: |
+ sudo apt update
+ sudo apt install -y msopenjdk-21
+ displayName: 'Install msopenjdk-21'
+ continueOnError: true
+# Equivalent to: sudo apt-get install -y golang-go
+- task: GoTool@0
+ displayName: 'Use Go 1.18'
+ inputs:
+ version: '1.18'
+ continueOnError: true
+
+# Equivalent to:
+# curl -fsSL https://deb.nodesource.com/setup_21.x | sudo -E bash - &&\
+# sudo apt-get install -y nodejs
+- task: NodeTool@0
+ displayName: 'Use Node.js 21.x'
+ inputs:
+ versionSpec: '21.x'
+ continueOnError: true
+
# -----------------------------------------------------------------------------
# Run the tests
# -----------------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index e6151fc2..acbdac27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,12 @@
**/.ai/data/*
+**/.pyenv/**
**/.vs/**
**/.vscode/**
**/bin/*/net6.0/*
**/bin/*/net7.0/*
**/bin/*/net8.0/*
**/obj/*
-**/Properties/launchSettings.json
ideas/website/node_modules/**
testresults/**
-*.user
+**/launchSettings.json
+*.user
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..1229b674
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,14 @@
+# Contributing
+
+This project welcomes contributions and suggestions. Most contributions require you to
+agree to a Contributor License Agreement (CLA) declaring that you have the right to,
+and actually do, grant us the rights to use your contribution. For details, visit
+https://cla.microsoft.com.
+
+When you submit a pull request, a CLA-bot will automatically determine whether you need
+to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the
+instructions provided by the bot. You will only need to do this once across all repositories using our CLA.
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
+or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
diff --git a/ai-cli.sln b/ai-cli.sln
index d1468dad..18f485d3 100644
--- a/ai-cli.sln
+++ b/ai-cli.sln
@@ -11,19 +11,21 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "helper_functions_extension"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{644B75F1-C768-4DB3-BAF2-C69A1F36DD28}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test_helper_functions_extension", "src\extensions\test_helper_functions_extension\test_helper_functions_extension.csproj", "{7BD6EF67-BA75-478D-9721-C1B2AB6DE3FF}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test_helper_functions_extension", "src\testing\samples\helper_functions_sample_project\test_helper_functions_extension.csproj", "{7BD6EF67-BA75-478D-9721-C1B2AB6DE3FF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "template_extension", "src\extensions\template_extension\template_extension.csproj", "{023B4F9C-E2B3-4CCD-A993-87E337C16EDE}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YamlTestAdapter", "tests\testadapter\YamlTestAdapter.csproj", "{7C3F1355-B679-487D-904D-7E5FEBA9E75C}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YamlTestAdapter", "src\testing\adapters\testadapter\YamlTestAdapter.csproj", "{7C3F1355-B679-487D-904D-7E5FEBA9E75C}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YamlTestFramework", "tests\testframework\YamlTestFramework.csproj", "{B0B3437F-1828-4A13-866F-1CF7C924015E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YamlTestFramework", "src\extensions\testframework\YamlTestFramework.csproj", "{B0B3437F-1828-4A13-866F-1CF7C924015E}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YamlTestRunner", "tests\testrunner\YamlTestRunner.csproj", "{39876475-2D98-40CF-8B08-CD423A5EB4E8}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YamlTestRunner", "src\testing\runner\YamlTestRunner.csproj", "{39876475-2D98-40CF-8B08-CD423A5EB4E8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C8AFF891-D6AA-4B8F-BC21-10404DF4B355}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RecordedYamlTestAdapter", "tests\recordingadapter\RecordedYamlTestAdapter.csproj", "{9499C018-FA08-4133-93B3-FC0F3863A6CC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RecordedYamlTestAdapter", "src\testing\adapters\recordingadapter\RecordedYamlTestAdapter.csproj", "{9499C018-FA08-4133-93B3-FC0F3863A6CC}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "speech-extension", "src\extensions\speech_extension\speech-extension.csproj", "{CED7C805-0435-4BF7-A42F-9F3BBF14A18F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "telemetry.aria", "src\telemetry\telemetry.aria.csproj", "{306A3CD6-91C2-450B-9995-79701CE63FE2}"
EndProject
@@ -78,6 +80,10 @@ Global
{306A3CD6-91C2-450B-9995-79701CE63FE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{306A3CD6-91C2-450B-9995-79701CE63FE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{306A3CD6-91C2-450B-9995-79701CE63FE2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CED7C805-0435-4BF7-A42F-9F3BBF14A18F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CED7C805-0435-4BF7-A42F-9F3BBF14A18F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CED7C805-0435-4BF7-A42F-9F3BBF14A18F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CED7C805-0435-4BF7-A42F-9F3BBF14A18F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -90,6 +96,7 @@ Global
{B0B3437F-1828-4A13-866F-1CF7C924015E} = {C8AFF891-D6AA-4B8F-BC21-10404DF4B355}
{39876475-2D98-40CF-8B08-CD423A5EB4E8} = {C8AFF891-D6AA-4B8F-BC21-10404DF4B355}
{9499C018-FA08-4133-93B3-FC0F3863A6CC} = {C8AFF891-D6AA-4B8F-BC21-10404DF4B355}
+ {CED7C805-0435-4BF7-A42F-9F3BBF14A18F} = {644B75F1-C768-4DB3-BAF2-C69A1F36DD28}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {002655B1-E1E1-4F2A-8D53-C9CD55136AE2}
diff --git a/ideas/cool-helper-function-scenarios/HelperFunctionClass.cs b/ideas/cool-helper-function-scenarios/HelperFunctionClass.cs
index 6213129f..440a0b49 100644
--- a/ideas/cool-helper-function-scenarios/HelperFunctionClass.cs
+++ b/ideas/cool-helper-function-scenarios/HelperFunctionClass.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using Azure.AI.Details.Common.CLI.Extensions.HelperFunctions;
public static class HelperFunctionClass
diff --git a/ideas/cool-helper-function-scenarios/HelperFunctionsProject.csproj b/ideas/cool-helper-function-scenarios/HelperFunctionsProject.csproj
index faf7eac5..6bdd27a7 100644
--- a/ideas/cool-helper-function-scenarios/HelperFunctionsProject.csproj
+++ b/ideas/cool-helper-function-scenarios/HelperFunctionsProject.csproj
@@ -9,10 +9,9 @@
-
-
+
diff --git a/ideas/cool-helper-function-scenarios/NutritionHelperFunctions.cs b/ideas/cool-helper-function-scenarios/NutritionHelperFunctions.cs
index 3529b43e..791b2e0f 100644
--- a/ideas/cool-helper-function-scenarios/NutritionHelperFunctions.cs
+++ b/ideas/cool-helper-function-scenarios/NutritionHelperFunctions.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
using System.Net.Http;
using System.Threading.Tasks;
diff --git a/ideas/cool-helper-function-scenarios/PokemonHelperFunctions.cs b/ideas/cool-helper-function-scenarios/PokemonHelperFunctions.cs
index 5d0c80a6..0811f57b 100644
--- a/ideas/cool-helper-function-scenarios/PokemonHelperFunctions.cs
+++ b/ideas/cool-helper-function-scenarios/PokemonHelperFunctions.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
using System.Collections.Generic;
using System.Net.Http;
diff --git a/ideas/cool-helper-function-scenarios/QRCodeHelper.cs b/ideas/cool-helper-function-scenarios/QRCodeHelper.cs
index fe9b3a9c..76cec6ed 100644
--- a/ideas/cool-helper-function-scenarios/QRCodeHelper.cs
+++ b/ideas/cool-helper-function-scenarios/QRCodeHelper.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using Azure.AI.Details.Common.CLI.Extensions.HelperFunctions;
public static class QRCodeHelper
diff --git a/ideas/cool-helper-function-scenarios/WolframAlphaHelper.cs b/ideas/cool-helper-function-scenarios/WolframAlphaHelper.cs
index 06359376..4ba0276f 100644
--- a/ideas/cool-helper-function-scenarios/WolframAlphaHelper.cs
+++ b/ideas/cool-helper-function-scenarios/WolframAlphaHelper.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using Azure.AI.Details.Common.CLI.Extensions.HelperFunctions;
using System;
using System.Net.Http;
diff --git a/ideas/junit-conversation.md b/ideas/junit-conversation.md
new file mode 100644
index 00000000..109f5af4
--- /dev/null
+++ b/ideas/junit-conversation.md
@@ -0,0 +1,174 @@
+You
+show me a sample pytest reporting xml file output
+
+Assistant
+Sure, here's a simplified example of a potential XML file output from Pytest using JUnit-style reporting:
+
+
+
+
+
+ Traceback (most recent call last): Test `test_fail` failed with AssertionError
+
+
+ Traceback (most recent call last): Test `test_error` error out with TypeError
+
+
+ Traceback (most recent call last): Test `test_skip` skipped with unittest.SkipTest
+
+
+
+
+
+This file provides a summary of the test results, including the number of tests run, number of errors, failures, and skipped tests, as well as detailed information about each individual test.
+Please note that the actual contents can greatly vary depending on the complexity of your tests and configuration of your Pytest.
+
+You
+is testsuite always the root?
+
+Assistant
+Yes, in JUnit-style XML reports, the element is generally the root. It represents a series of test cases (represented by elements) that together form a test suite.
+However, if you generate the report with pytest's --junit-prefix option or you're running the test within a suite of test suites, the report might include a wrapping element:
+
+
+
+
+
+
+
+
+In this case is the root node, containing one or more elements.
+
+You
+ok. under testsuite, is testcase the only valid child?
+
+Assistant
+Primarily, yes, the tag in a JUnit XML report contains a series of elements. Each element represents one test.
+However, can also have a few other types of children in addition to . These additional child elements provide metadata about the suite as a whole or report on issues that affected the suite rather than individual tests.
+These additional tags include:
+: A collection of properties, or environment data, which applies to the entire test suite. This is usually used to store information relevant to the environment where the tests were run.
+: Indicates an error that prevented the test suite from running to completion. This tag is not associated with an individual test case.
+ and : These capture text that was sent to standard output or standard error during test execution. This can include logs or debugging information.
+Here's a simplified example:
+
+
+
+
+
+
+
+
+
+ System output text (e.g., logs) goes here...
+
+
+
+ System error text (e.g., exception messages) goes here...
+
+
+
+Remember that the actual content and structure can vary based on your test configuration and the specifics of your test environment.
+
+You
+what are all the valid children of testcase?
+
+Assistant
+In a JUnit-style XML report, the element can have several types of child elements. Each one provides additional information about the result or execution of the specific test case.
+Here are the key types:
+: This indicates an error that occurred while the test was running. This is typically used for unexpected problems, such as uncaught exceptions. The message attribute provides a short description of the error.
+: This indicates that the test case failed, i.e., a test assertion was not met. The message attribute provides a short description of the failure.
+: This indicates that the test case was skipped and not run for some reason. This could be because the test was manually disabled, or a setup method failed. This element usually has no content.
+ and : These capture text that was sent to standard output or standard error during the execution of this specific test case. This could be logs or debugging information.
+Here's an example of a testcase element with failure and system-out children:
+
+ Traceback (most recent call last):...
+ Some standard output text here...
+
+
+Remember that the actual content and structure of these elements can vary based on the specifics of your test configuration and environment.
+
+You
+tell me all the xml elements, valid parents, valid children, and what are required and optional attributes.
+
+Assistant
+JUnit XML is a standard to report test results that can be processed by tools that understand the format. Below, you'll find a summary of all XML elements, their valid parents and children, and required and optional attributes.
+
+: The root element for an XML document representing collection of test suites.
+Parent: None
+Children:
+Attributes: None
+
+: Represents a test suite which is a collection of test cases.
+Parent: (optional),
+Children: , , , , and
+Attributes: name (required), tests (required), failures, errors, skipped, time,timestamp, hostname, id, package
+
+: Represents a set of properties relevant to the entire suite.
+Parent:
+Children:
+Attributes: None
+
+: Represents a single property in the set.
+Parent:
+Children: None
+Attributes: name (required), value (required)
+
+: Represents a single test case.
+Parent:
+Children: , , , ,
+Attributes: name (required), classname (required), time
+
+: Represents an error that occurred while the test was running.
+Parent: ,
+Children: None
+Attributes: message, type
+
+: Represents a failure of a test case.
+Parent:
+Children: None
+Attributes: message, type
+
+: Represents a test case that was skipped.
+Parent:
+Children: None
+Attributes: None
+
+: Captures text that was sent to standard output during the execution.
+Parent: ,
+Children: None
+Attributes: None
+
+: Captures text that was sent to standard error during the execution.
+Parent: ,
+Children: None
+Attributes: None
+
+You
+is that everything?
+
+Assistant
+Yes, that covers the standard JUnit XML schema in terms of elements, parents, children, and attributes. Please note that since it's a standard, it's generally followed, but some testing frameworks may have slight variations.
+Standard Elements include:
+
+
+
+
+
+
+
+
+
+
+With their accompanying attributes and nested relationships.
+However, it's a standard and its tooling dependent, some tools may add additional elements or attributes to provide more information about the tests. It's always good to check with documentation of the specific tool you are using.
+
+You
+in java, how do namespaces show up here, as part of which attribute?
+
+Assistant
+In a JUnit XML report, the Java package name is typically included in the classname attribute of the element. The classname usually contains the fully qualified name of the class, which includes the package name.
+Here's an example:
+
+
+In this case, com.example.myapp is the package name, and MyTestClass is the name of the Java class that contains the test method. This naming convention allows each test case to be uniquely identified, even if the test method names are the same in different test classes.
+Please note that the way the package or namespace information is included can depend on the testing tool and configuration being used. This is typical behavior but may vary.
\ No newline at end of file
diff --git a/ideas/porter/1-instructions.md b/ideas/porter/1-instructions.md
index bcfa020c..47d89e31 100644
--- a/ideas/porter/1-instructions.md
+++ b/ideas/porter/1-instructions.md
@@ -20,30 +20,33 @@ You are an AI assistant that ports code from C# to Python.
## Scenario description
-I have a C# project in this directory (*.cs, and *.csproj) that I want to make an exact copy of in Python (*.py, requirements.txt).
-
-Your job is to help me, by doing the following:
-1. You **must** read all the C# files in the current directory
-2. You **must** create a corresponding python module for each corresponding C# file
-3. Each module you create **must** have the same functionality as the C# file it was created from
-4. Each module you create **must** be named similarly, but using Python naming conventions
-5. Each module you create **must** have the same class names and method names, but using Python naming conventions
-6. Each function you create **must** have the same functionality as the C# function it was created from
+I have a sample implemented in both C# and Python that show the basic set of capabilities of our SDK in both languages.
+* Base C# example: In the `{sample1}-cs` directory, you'll find C# source code and project files that demonstrate the basic set of capabilities of our C# SDK.
+* Base Python example: In the `{sample1}-py` directory, you'll find Python source code and project files that demonstrate the exact same basic set of capabilities of our corresponding Python SDK.
+
+I also have a new sample that shows a new set of features we have created that sample to show.
+* New C# example: In the `{sample2}-cs` directory, you'll find C# source code and project files that demonstrate a NEW set of features we have created that sample to show.
+
+I need you to port the new C# example to Python, following these detailed instructions:
+1. First, you **must** read all the C# and Python files in the `{sample1}-cs` and `{sample1}-py` directory to understand the basic capabilities, and how we have demonstrated them in the C# and Python SDK samples.
+2. Next, you **must** read all the C# files in the `{sample2}-cs` to understand how we have demonstrated the new set of features in the C# SDK sample.
+3. Then, you **must** create a new directory for the new Python source code files following the naming convention: `{sample2}-py`
+4. Next, you **must** create a corresponding python module for each corresponding C# file
+5. Each module you create **must** have the same functionality as the C# file it was created from
+6. Each module you create **must** be named similarly, but using Python naming conventions
+7. Each module you create **must** have the same class names and method names, but using Python naming conventions
+8. Each function you create **must** have the same functionality as the C# function it was created from
## Do it now
Task you must perform:
-1. Please begin, by reading each C# source file.
-2. Please create a corresponding python module for each C# source file.
-3. Some of the modules have already been ported; you only hve to do these
- * HelperFunctionDescriptionAttribute.cs
- * HelperFunctionFactory.cs
- * HelperFunctionFactoryExtensions.cs
- * HelperFunctionParameterDescriptionAttribute.cs
+1. If I forgot to tell you the directory names (would have curly brackets around them if I didn't give them to you), you **must** ask me for them.
+2. Please begin, by reading each C# and python source code files from Sample 1, 2, and 3.
+3. Analyze the source code and understand the functionality of each file.
+4. Create a new directory for the new Python source code files.
+5. Create a corresponding python module for each C# source file.
## Bonus
If you do it perfectly, I'll give you a $100 tip.
## Time to begin
Don't show me the code, just create the files. Do it now.
-
-
diff --git a/ideas/speech-templates-todo.md b/ideas/speech-templates-todo.md
new file mode 100644
index 00000000..12a745a8
--- /dev/null
+++ b/ideas/speech-templates-todo.md
@@ -0,0 +1,11 @@
+| name | short | languages
+| ---- | ----- | ---------
+| Speech-to-text | speech-to-text | C++, __C#__, Go, Java, JavaScript, __Python__
+| Speech-to-text (w/ Continuous recognition) | speech-to-text-continuous | C++, __C#__, Go, Java, JavaScript, __Python__
+| Speech-to-text (w/ File input) | speech-to-text-with-file | C++, __C#__, Go, Java, JavaScript, Python
+| Speech-to-text (w/ Keyword detection) | speech-to-text-with-keyword | C++, __C#__, Go, Java, JavaScript, Python
+| Speech-to-text (w/ Language identification) | speech-to-text-with-lid | C++, C#, Go, Java, JavaScript, Python
+| Speech-to-text (w/ Translation) | speech-to-text-with-translation | C++, __C#__, Go, Java, JavaScript, Python
+| Text-to-speech | text-to-speech | C++, __C#__, Go, Java, JavaScript, Python
+| Text-to-speech (w/ File output) | text-to-speech-with-file | C++, __C#__, Go, Java, JavaScript, Python
+| Text-to-speech (w/ SSML input) | text-to-speech-with-ssml | C++, C#, Go, Java, JavaScript, Python
diff --git a/scripts/Azure-AI-CLI-Setup.wxl b/scripts/Azure-AI-CLI-Setup.wxl
new file mode 100644
index 00000000..ce41631c
--- /dev/null
+++ b/scripts/Azure-AI-CLI-Setup.wxl
@@ -0,0 +1,66 @@
+
+
+ [WixBundleName] Setup
+ [WixBundleName]
+ Version [WixBundleVersion]
+ Are you sure you want to cancel?
+ Previous version
+
+ /install | /repair | /uninstall | /layout [directory] - installs, repairs, uninstalls or
+ creates a complete local copy of the bundle in directory. Install is the default.
+
+/passive | /quiet - displays minimal UI with no prompts or displays no UI and
+ no prompts. By default UI and all prompts are displayed.
+
+/norestart - suppress any attempts to restart. By default UI will prompt before restart.
+/log log.txt - logs to a specific file. By default a log file is created in %TEMP%.
+ &Close
+ I &agree to the license terms and conditions
+ &Options
+ &Install
+ &Close
+
+ Install location:
+ &Browse
+ &OK
+ &Cancel
+
+ Processing:
+ Initializing...
+ &Cancel
+
+ &Repair
+ &Uninstall
+ &Close
+
+
+
+
+ &Launch
+ You must restart your computer before you can use the software.
+ &Restart
+ &Close
+
+
+
+
+ One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the <a href="#">log file</a>.
+ You must restart your computer to complete the rollback of the software.
+ &Restart
+ &Close
+
+ The following applications are using files that need to be updated:
+ Close the &applications and attempt to restart them.
+ &Do not close applications. A reboot will be required.
+ &OK
+ &Cancel
+ No action was taken as a system reboot is required.
+
diff --git a/scripts/Azure-AI-CLI-Bundle.wxs b/scripts/Azure-AI-CLI-Setup.wxs
similarity index 52%
rename from scripts/Azure-AI-CLI-Bundle.wxs
rename to scripts/Azure-AI-CLI-Setup.wxs
index 13ed5ffb..cbc2cead 100644
--- a/scripts/Azure-AI-CLI-Bundle.wxs
+++ b/scripts/Azure-AI-CLI-Setup.wxs
@@ -6,33 +6,33 @@
+ UpgradeCode="BF9A65EF-7D5A-4DC4-90E4-CDC44F042E96">
+ ThemeFile="Azure-AI-CLI-Setup.xml"/>
-
-
+
+
NOT WixBundleAction = 5 OR NOT AlreadyInstalledVersion >= v$(var.productVersion)
+
@@ -42,14 +42,22 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scripts/Azure-AI-CLI-Setup.xml b/scripts/Azure-AI-CLI-Setup.xml
new file mode 100644
index 00000000..33c36c1c
--- /dev/null
+++ b/scripts/Azure-AI-CLI-Setup.xml
@@ -0,0 +1,79 @@
+
+
+ #(loc.Caption)
+ Segoe UI
+ Segoe UI
+ Segoe UI
+ Segoe UI
+ Segoe UI
+ Segoe UI
+
+
+ #(loc.Title)
+
+
+ #(loc.HelpHeader)
+ #(loc.HelpText)
+
+
+
+
+ #(loc.InstallAcceptCheckbox)
+
+
+
+
+
+ #(loc.OptionsHeader)
+ #(loc.OptionsLocationLabel)
+
+
+
+
+
+
+ #(loc.FilesInUseHeader)
+ #(loc.FilesInUseLabel)
+
+
+
+
+
+
+
+
+
+ #(loc.ProgressHeader)
+ #(loc.ProgressLabel)
+ #(loc.OverallProgressPackageText)
+
+
+
+
+ #(loc.ModifyHeader)
+
+
+
+
+
+ #(loc.SuccessHeader)
+ #(loc.SuccessInstallHeader)
+ #(loc.SuccessRepairHeader)
+ #(loc.SuccessUninstallHeader)
+
+ #(loc.SuccessRestartText)
+
+
+
+
+ #(loc.FailureHeader)
+ #(loc.FailureInstallHeader)
+ #(loc.FailureUninstallHeader)
+ #(loc.FailureRepairHeader)
+ #(loc.FailureHyperlinkLogText)
+
+ #(loc.FailureRestartText)
+
+
+
+
diff --git a/scripts/Azure-AI-CLI.wxs b/scripts/Azure-AI-CLI.wxs
index 31fada63..2fe1edad 100644
--- a/scripts/Azure-AI-CLI.wxs
+++ b/scripts/Azure-AI-CLI.wxs
@@ -4,7 +4,8 @@
diff --git a/scripts/CLI_License.rtf b/scripts/CLI_License.rtf
index a462e2ce..92b02809 100644
--- a/scripts/CLI_License.rtf
+++ b/scripts/CLI_License.rtf
@@ -47,7 +47,7 @@ Microsoft Azure AI CLI
\pard\tx450\pardeftab720\li450\fi-357\ri0\sb120\sa120\partightenfactor0
\ls4\ilvl0
\f0\b \cf0 3. DATA.
-\b0 The software may collect information about you and your use of the software, and send that to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may opt-out of many of these scenarios, but not all, as described in the product documentation. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications and you should provide a copy of Microsoft\'92s privacy statement to your users. The Microsoft privacy statement is located here {\field{\*\fldinst{HYPERLINK "https://go.microsoft.com/fwlink/?LinkID=824704"}}{\fldrslt \cf2 \ul \ulc2 https://go.microsoft.com/fwlink/?LinkID=824704}}. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.\
+\b0 The software may collect information about you and your use of the software, and send that to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may opt-out of many of these scenarios, but not all, as described in the product documentation. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications and you should provide a copy of Microsoft\'92s privacy statement to your users. The Microsoft privacy statement is located here {\field{\*\fldinst{HYPERLINK "https://privacy.microsoft.com/privacystatement"}}{\fldrslt \cf2 \ul \ulc2 https://privacy.microsoft.com/privacystatement}}. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.\
\pard\tx360\pardeftab720\li357\fi-357\ri0\sb120\sa120\partightenfactor0
\ls4\ilvl0
\b \cf0 4. SCOPE OF LICENSE.
diff --git a/scripts/InstallAzureAICLIDeb.sh b/scripts/InstallAzureAICLIDeb.sh
index f948c04e..566de709 100644
--- a/scripts/InstallAzureAICLIDeb.sh
+++ b/scripts/InstallAzureAICLIDeb.sh
@@ -60,7 +60,7 @@ else
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
elif [[ "$CHECK_VERSION" == "22.04" ]]; then
- # Install the Microsoft package signing key for Ubuntu 20.04
+ # Install the Microsoft package signing key for Ubuntu 22.04
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
diff --git a/scripts/WixBuildInstaller.cmd b/scripts/WixBuildInstaller.cmd
index d39a2469..2118823b 100644
--- a/scripts/WixBuildInstaller.cmd
+++ b/scripts/WixBuildInstaller.cmd
@@ -4,7 +4,7 @@ setlocal
REM Azure AI CLI package version (e.g. 1.0.0 or 1.0.0-preview-20231214.1)
if "%~1"=="" (
echo Error: Azure AI CLI package version is not set. 1>&2
- exit /b 1
+ exit /b 11
)
REM Azure AI CLI product version x.y.z (e.g. 1.0.0), ref. https://learn.microsoft.com/windows/win32/msi/productversion
@@ -13,7 +13,7 @@ REM If the package version changes but product version remains the same,
REM an existing installation cannot be upgraded without uninstalling it first!
if "%~2"=="" (
echo Error: Azure AI CLI product version is not set. 1>&2
- exit /b 2
+ exit /b 12
)
set PACKAGE_VERSION=%~1
@@ -22,13 +22,17 @@ set TARGET_PLATFORM=x64
set INSTALLER_FILE=Setup-%TARGET_PLATFORM%.exe
set PACKAGE_URL=https://csspeechstorage.blob.core.windows.net/drop/private/ai/Azure.AI.CLI.%PACKAGE_VERSION%.nupkg
-REM Dependencies
+REM Dependencies (note: do NOT use redirecting URLs like aka.ms)
set AZURE_CLI_VERSION=2.57.0
set AZURE_CLI_INSTALLER=azure-cli-%AZURE_CLI_VERSION%-%TARGET_PLATFORM%.msi
set AZURE_CLI_URL=https://azcliprod.blob.core.windows.net/msi/%AZURE_CLI_INSTALLER%
-set DOTNET_VERSION=8.0.101
+set DOTNET_VERSION=8.0.201
set DOTNET_INSTALLER=dotnet-sdk-%DOTNET_VERSION%-win-%TARGET_PLATFORM%.exe
set DOTNET_URL=https://dotnetcli.azureedge.net/dotnet/Sdk/%DOTNET_VERSION%/%DOTNET_INSTALLER%
+set VCRT_VERSION=14.38.33135
+set VCRT_INSTALLER=VC_redist.%TARGET_PLATFORM%.exe
+REM Below URL as redirected from https://aka.ms/vs/17/release/vc_redist.x64.exe while 14.38.33135 is the latest version.
+set VCRT_URL=https://download.visualstudio.microsoft.com/download/pr/c7707d68-d6ce-4479-973e-e2a3dc4341fe/1AD7988C17663CC742B01BEF1A6DF2ED1741173009579AD50A94434E54F56073/%VCRT_INSTALLER%
REM Check for WiX toolset
where candle.exe >nul 2>&1
@@ -36,57 +40,65 @@ if %ERRORLEVEL% neq 0 set PATH=%PATH%;C:\Program Files (x86)\WiX Toolset v3.11\b
where candle.exe >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo Error: Install WiX v3.14 Toolset from https://wixtoolset.org/docs/v3/releases/v3-14-0-6526/ 1>&2
- exit /b 3
+ exit /b 21
)
REM Check for curl.exe (https://techcommunity.microsoft.com/t5/containers/tar-and-curl-come-to-windows/ba-p/382409)
where curl.exe >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo Error: curl.exe not found 1>&2
- exit /b 4
+ exit /b 22
)
REM Download Azure CLI installer
-curl.exe --output %AZURE_CLI_INSTALLER% --silent --url %AZURE_CLI_URL%
+curl.exe --output %AZURE_CLI_INSTALLER% --silent --location --url %AZURE_CLI_URL%
if %ERRORLEVEL% neq 0 (
echo Error while downloading Azure CLI installer 1>&2
- exit /b 5
+ exit /b 31
)
REM Download .NET SDK installer
-curl.exe --output %DOTNET_INSTALLER% --silent --url %DOTNET_URL%
+curl.exe --output %DOTNET_INSTALLER% --silent --location --url %DOTNET_URL%
if %ERRORLEVEL% neq 0 (
echo Error while downloading .NET SDK installer 1>&2
- exit /b 6
+ exit /b 32
+)
+
+REM Download Visual C++ runtime installer
+curl.exe --output %VCRT_INSTALLER% --silent --location --url %VCRT_URL%
+if %ERRORLEVEL% neq 0 (
+ echo Error while downloading Visual C++ runtime installer 1>&2
+ exit /b 33
)
REM Build AI CLI installer .msi
candle.exe Azure-AI-CLI.wxs -dproductVersion=%PRODUCT_VERSION% -dpackageVersion=%PACKAGE_VERSION% -dpackageUrl=%PACKAGE_URL% -dtargetPlatform=%TARGET_PLATFORM%
if %ERRORLEVEL% neq 0 (
echo Error from candle.exe 1>&2
- exit /b 7
+ exit /b 41
)
light.exe Azure-AI-CLI.wixobj -ext WixUIExtension -ext WixUtilExtension
if %ERRORLEVEL% neq 0 (
echo Error from light.exe 1>&2
- exit /b 8
+ exit /b 42
)
REM Build installation bundle .exe
-candle.exe Azure-AI-CLI-Bundle.wxs -ext WixBalExtension -ext WixUtilExtension ^
+candle.exe Azure-AI-CLI-Setup.wxs -ext WixBalExtension -ext WixUtilExtension ^
-dproductVersion=%PRODUCT_VERSION% -dtargetPlatform=%TARGET_PLATFORM% ^
-dazureCliVersion=%AZURE_CLI_VERSION% -dazureCliUrl=%AZURE_CLI_URL% ^
- -ddotNetVersion=%DOTNET_VERSION% -ddotNetUrl=%DOTNET_URL%
+ -ddotNetVersion=%DOTNET_VERSION% -ddotNetUrl=%DOTNET_URL% ^
+ -dvcrtVersion=%VCRT_VERSION% -dvcrtUrl=%VCRT_URL%
if %ERRORLEVEL% neq 0 (
echo Error from candle.exe 1>&2
- exit /b 9
+ exit /b 51
)
-light.exe Azure-AI-CLI-Bundle.wixobj -ext WixBalExtension -ext WixUtilExtension -out %INSTALLER_FILE%
+light.exe Azure-AI-CLI-Setup.wixobj -ext WixBalExtension -ext WixUtilExtension -out %INSTALLER_FILE%
if %ERRORLEVEL% neq 0 (
echo Error from light.exe 1>&2
- exit /b 10
+ exit /b 52
)
:end
diff --git a/src/ai/.x/config/test.default.config b/src/ai/.x/config/test.default.config
new file mode 100644
index 00000000..6671d381
--- /dev/null
+++ b/src/ai/.x/config/test.default.config
@@ -0,0 +1,3 @@
+@default.log
+@default.path
+@my.defaults
\ No newline at end of file
diff --git a/src/ai/.x/config/vision.default.connection b/src/ai/.x/config/vision.default.connection
index 27b451bd..d77ccbc2 100644
--- a/src/ai/.x/config/vision.default.connection
+++ b/src/ai/.x/config/vision.default.connection
@@ -1 +1 @@
-@connection.from.region
\ No newline at end of file
+@connection.from.endpoint
\ No newline at end of file
diff --git a/src/ai/.x/help/help b/src/ai/.x/help/help
index 2f6a1daa..6fdb79d8 100644
--- a/src/ai/.x/help/help
+++ b/src/ai/.x/help/help
@@ -30,6 +30,7 @@ USAGE: ai [...]
ai config [...] (see: ai help config)
ai dev [...] (see: ai help dev)
+ ai test [...] (see: ai help test)
ai chat [...] (see: ai help chat)
ai flow [...] (see: ai help flow)
diff --git a/src/ai/.x/help/include.python.script.function_call_evaluate.py b/src/ai/.x/help/include.python.script.function_call_evaluate.py
index fe6b1174..c42af85e 100755
--- a/src/ai/.x/help/include.python.script.function_call_evaluate.py
+++ b/src/ai/.x/help/include.python.script.function_call_evaluate.py
@@ -224,10 +224,10 @@ def run_evaluate_part(subscription_id, resource_group_name, project_name, run_re
prediction_data="answer",
task_type="qa",
data_mapping={
- "questions": "question",
- "contexts": "context",
- "y_pred": "answer",
- "y_test": "truth"
+ "question": "question",
+ "context": "context",
+ "answer": "answer",
+ "ground_truth": "truth"
},
model_config={
"api_version": os.getenv("OPENAI_API_VERSION"),
diff --git a/src/ai/.x/help/speech/_ b/src/ai/.x/help/speech/_
index eb640e92..bf188704 100644
--- a/src/ai/.x/help/speech/_
+++ b/src/ai/.x/help/speech/_
@@ -7,19 +7,17 @@ USAGE: ai speech [...]
COMMANDS
-`#e_;*` ai speech recognize [...] (see: ai help speech recognize)
-`#e_;*` ai speech synthesize [...] (see: ai help speech synthesize)
+ ai speech recognize [...] (see: ai help speech recognize)
+ ai speech synthesize [...] (see: ai help speech synthesize)
-`#e_;*` ai speech intent [...] (see: ai help speech intent)
-`#e_;*` ai speech translate [...] (see: ai help speech translate)
+ ai speech intent [...] (see: ai help speech intent)
+ ai speech translate [...] (see: ai help speech translate)
-`#e_;*` ai speech batch [...] (see: ai help speech batch)
-`#e_;*` ai speech csr [...] (see: ai help speech csr)
+ ai speech batch [...] (see: ai help speech batch)
+ ai speech csr [...] (see: ai help speech csr)
-`#e_;*` ai speech profile [...] (see: ai help speech profile)
-`#e_;*` ai speech speaker [...] (see: ai help speech speaker)
-
-`#e_;* - COMING SOON`
+ ai speech profile [...] (see: ai help speech profile)
+ ai speech speaker [...] (see: ai help speech speaker)
ADDITIONAL TOPICS
diff --git a/src/ai/.x/help/speech/csr.examples b/src/ai/.x/help/speech/csr.examples
index 2615cc60..3e7b51ef 100644
--- a/src/ai/.x/help/speech/csr.examples
+++ b/src/ai/.x/help/speech/csr.examples
@@ -13,7 +13,7 @@ CUSTOM SPEECH RECOGNITION EXAMPLES
ai speech csr endpoint create --name "LM example" --text @sentences.txt --output id @my.cid.txt
ai speech csr endpoint status --endpoint @my.cid.txt --wait
- ai speech recognize --files test.wav --endpoint id @my.cid
+ ai speech recognize --files test.wav --endpoint id @my.cid.txt
`EXAMPLE 2`: List custom speech recognition projects or endpoints
diff --git a/src/ai/.x/help/test b/src/ai/.x/help/test
new file mode 100644
index 00000000..fb6738a6
--- /dev/null
+++ b/src/ai/.x/help/test
@@ -0,0 +1,18 @@
+ ___ ____ ___ _____ ____________________
+ / _ /_ / / _ |/_ _/ /_ __/ __/ __/_ __/
+ / __ |/ /_/ __ |_/ /_ / / / _/_\ \ / /
+/_/ |_/___/_/ |_/____/ /_/ /___/___/ /_/
+
+USAGE: ai test [...]
+
+ The `ai test` command manages CLI YAML tests.
+
+COMMANDS
+
+ ai test list [...] (see: ai help test list)
+ ai test run [...] (see: ai help test run)
+
+ADDITIONAL TOPICS
+
+ ai help test examples
+ ai help find topics test
diff --git a/src/ai/.x/help/test.list b/src/ai/.x/help/test.list
new file mode 100644
index 00000000..274fa49b
--- /dev/null
+++ b/src/ai/.x/help/test.list
@@ -0,0 +1,37 @@
+TEST LIST
+
+ The `ai test list` command lists CLI YAML tests.
+
+USAGE: ai test list [...]
+
+ FILES
+ --file FILE
+ --files FILE1 [FILE2 [...]]
+ --files PATTERN1 [PATTERN2 [...]]
+
+ TESTS
+ --test TEXT
+ --tests TEXT1 [TEXT2 [...]]
+
+ FILTERING
+ --contains TEXT1 [TEXT2 [...]]
+ --remove TEXT1 [TEXT2 [...]]
+
+EXAMPLES
+
+ EXAMPLE 1: Lists all available tests
+
+ ai test list
+
+ EXAMPLE 2: Lists tests from files under current directory, that contain 'setup' or 'nightly', and 'java', but not 'skip'
+
+ ai test list --tests setup nightly --contains java --remove skip
+
+ EXAMPLE 3: Lists tests from files under 'tests' directory, that contain 'test3', but not 'skip'
+
+ ai test list --files ../tests/**/*.yaml --contains test3 --remove skip
+
+SEE ALSO
+
+ ai help test examples
+ ai help find topics test
\ No newline at end of file
diff --git a/src/ai/.x/help/test.run b/src/ai/.x/help/test.run
new file mode 100644
index 00000000..d1b76c7d
--- /dev/null
+++ b/src/ai/.x/help/test.run
@@ -0,0 +1,41 @@
+TEST RUN
+
+ The `ai test list` command runs CLI YAML tests.
+
+USAGE: ai test run [...]
+
+ FILES
+ --file FILE
+ --files FILE1 [FILE2 [...]]
+ --files PATTERN1 [PATTERN2 [...]]
+
+ TESTS
+ --test TEXT
+ --tests TEXT1 [TEXT2 [...]]
+
+ FILTERING
+ --contains TEXT1 [TEXT2 [...]]
+ --remove TEXT1 [TEXT2 [...]]
+
+ OUTPUT
+ --output-file FILE
+ --output-format trx|junit
+
+EXAMPLES
+
+ EXAMPLE 1: Runs all available tests
+
+ ai test run
+
+ EXAMPLE 2: Run tests from files under current directory, that contain 'setup' or 'nightly', and 'java', but not 'skip'
+
+ ai test run --tests setup nightly --contains java --remove skip
+
+ EXAMPLE 3: Run tests from files under 'tests' directory, that contain 'test3', but not 'skip'
+
+ ai test run --files ../tests/**/*.yaml --contains test3 --remove skip
+
+SEE ALSO
+
+ ai help test examples
+ ai help find topics test
\ No newline at end of file
diff --git a/src/ai/.x/help/usage b/src/ai/.x/help/usage
index 34c06fdb..41f21cef 100644
--- a/src/ai/.x/help/usage
+++ b/src/ai/.x/help/usage
@@ -16,6 +16,7 @@ USAGE: ai [...]
ai config [...] (see: ai help config)
ai dev [...] (see: ai help dev)
+ ai test [...] (see: ai help test)
ai chat [...] (see: ai help chat)
ai flow [...] (see: ai help flow)
diff --git a/src/ai/.x/help/vision b/src/ai/.x/help/vision
index f58397dc..fc030c04 100644
--- a/src/ai/.x/help/vision
+++ b/src/ai/.x/help/vision
@@ -10,7 +10,7 @@ COMMANDS
`#e_;*` ai vision form [...] (see: ai help vision form)
`#e_;*` ai vision document [...] (see: ai help vision document)
-`#e_;*` ai vision image [...] (see: ai help vision image)
+` ai vision image [...] (see: ai help vision image)
`#e_;*` ai vision person [...] (see: ai help vision person)
`#e_;*` ai vision face [...] (see: ai help vision face)
diff --git a/src/ai/.x/help/vision.image.analyze b/src/ai/.x/help/vision.image.analyze
new file mode 100644
index 00000000..f6764a42
--- /dev/null
+++ b/src/ai/.x/help/vision.image.analyze
@@ -0,0 +1,32 @@
+VISION IMAGE ANALYZE
+
+@include.the.azure.ai.service
+
+USAGE: ai vision image analyze [...]
+
+ INPUT
+ --file FILEPATH (see: ai help vision image analyze file)
+ --url IMAGEURL (see: ai help vision image analyze url)
+
+ FEATURES
+ --visual-features FEATURES (see: ai help vision image analyze visualfeatures)
+
+ LANGUAGE
+ --language LANGUAGE (see: ai help vision image analyze language)
+
+ CAPTIONS
+ --gender-neutral-captions TRUE|FALSE (see: ai help vision image analyze genderneutralcaptions)
+
+ ASPECT RATIOS
+ --smart-aspect-ratios RATIOS (see: ai help vision image analyze smartaspectratios)
+
+ MODEL
+ --model-version VERSION (see: ai help vision image analyze modelversion)
+
+EXAMPLES
+
+ ai vision image analyze --file image.jpg --visual-features Caption
+
+SEE ALSO
+
+ ai help vision image analyze examples
\ No newline at end of file
diff --git a/src/ai/.x/help/vision.image.analyze.examples b/src/ai/.x/help/vision.image.analyze.examples
new file mode 100644
index 00000000..dea38214
--- /dev/null
+++ b/src/ai/.x/help/vision.image.analyze.examples
@@ -0,0 +1,25 @@
+VISION IMAGE ANALYZE EXAMPLES
+
+ `EXAMPLE 1`: Analyze an image file using the Caption feature
+
+ ai vision image analyze --file image.jpg --visual-features Caption
+
+ `EXAMPLE 2`: Analyze an image file using the Read feature for OCR
+
+ ai vision image analyze --file text_image.jpg --visual-features Read
+
+ `EXAMPLE 3`: Analyze an image from a URL using the Caption feature
+
+ ai vision image analyze --url https://example.com/image.jpg --visual-features Caption
+
+ `EXAMPLE 4`: Analyze an image file using multiple features
+
+ ai vision image analyze --file image.jpg --visual-features Caption,Objects
+
+ `EXAMPLE 5`: Analyze an image file using the Caption feature with gender-neutral terms
+
+ ai vision image analyze --file image.jpg --visual-features Caption --gender-neutral-captions true
+
+SEE ALSO
+
+ ai help vision image analyze
\ No newline at end of file
diff --git a/src/ai/.x/help/vision.image.analyze.file b/src/ai/.x/help/vision.image.analyze.file
new file mode 100644
index 00000000..4e1adf4e
--- /dev/null
+++ b/src/ai/.x/help/vision.image.analyze.file
@@ -0,0 +1,9 @@
+VISION IMAGE ANALYZE FILE
+
+The `--file` argument is used to specify the path to the image file to analyze. The file must be in one of the following formats: JPEG, PNG, GIF, BMP, WEBP, ICO, TIFF, or MPO. The file size must be less than 20 megabytes (MB), and the dimensions of the image must be greater than 50 x 50 pixels and less than 16,000 x 16,000 pixels.
+
+USAGE: ai vision image analyze --file [...other options]
+
+EXAMPLE
+
+ ai vision image analyze --file image.jpg --visual-features Caption
\ No newline at end of file
diff --git a/src/ai/.x/help/vision.image.analyze.genderneutralcaptions b/src/ai/.x/help/vision.image.analyze.genderneutralcaptions
new file mode 100644
index 00000000..8d526ddc
--- /dev/null
+++ b/src/ai/.x/help/vision.image.analyze.genderneutralcaptions
@@ -0,0 +1,9 @@
+VISION IMAGE ANALYZE GENDER NEUTRAL CAPTIONS
+
+The `--gender-neutral-captions` argument is used to specify whether to use gender-neutral terms in the captions. If set to true, terms like 'person' and 'child' are used instead of 'man', 'woman', 'boy', 'girl', etc.
+
+USAGE: ai vision image analyze --gender-neutral-captions [...other options]
+
+EXAMPLE
+
+ ai vision image analyze --file image.jpg --visual-features Caption --gender-neutral-captions true
\ No newline at end of file
diff --git a/src/ai/.x/help/vision.image.analyze.language b/src/ai/.x/help/vision.image.analyze.language
new file mode 100644
index 00000000..6902abb6
--- /dev/null
+++ b/src/ai/.x/help/vision.image.analyze.language
@@ -0,0 +1,9 @@
+VISION IMAGE ANALYZE LANGUAGE
+
+The `--language` argument is used to specify the language to use for analysis. This affects the language of the generated captions and extracted text.
+
+USAGE: ai vision image analyze --language [...other options]
+
+EXAMPLE
+
+ ai vision image analyze --file image.jpg --visual-features Caption --language en
\ No newline at end of file
diff --git a/src/ai/.x/help/vision.image.analyze.modelversion b/src/ai/.x/help/vision.image.analyze.modelversion
new file mode 100644
index 00000000..8409a569
--- /dev/null
+++ b/src/ai/.x/help/vision.image.analyze.modelversion
@@ -0,0 +1,9 @@
+VISION IMAGE ANALYZE MODEL VERSION
+
+The `--model-version` argument is used to specify the version of the model to use for analysis. This allows you to use a specific version of the model, rather than the latest version.
+
+USAGE: ai vision image analyze --model-version "version" [...other options]
+
+EXAMPLE
+
+ ai vision image analyze --file image.jpg --visual-features Caption --model-version "latest"
\ No newline at end of file
diff --git a/src/ai/.x/help/vision.image.analyze.smartaspectratios b/src/ai/.x/help/vision.image.analyze.smartaspectratios
new file mode 100644
index 00000000..4261498c
--- /dev/null
+++ b/src/ai/.x/help/vision.image.analyze.smartaspectratios
@@ -0,0 +1,9 @@
+VISION IMAGE ANALYZE SMART ASPECT RATIOS
+
+The `--smart-crop-aspect-ratios` argument is used to specify the aspect ratios to use for smart cropping. Smart cropping is used to find a representative sub-region of the image for thumbnail generation, with priority given to include faces.
+
+USAGE: ai vision image analyze --smart-crop-aspect-ratios [...other options]
+
+EXAMPLE
+
+ ai vision image analyze --file image.jpg --visual-features SmartCrops --smart-crop-aspect-ratios 1:1,16:9
\ No newline at end of file
diff --git a/src/ai/.x/help/vision.image.analyze.url b/src/ai/.x/help/vision.image.analyze.url
new file mode 100644
index 00000000..0b61a51a
--- /dev/null
+++ b/src/ai/.x/help/vision.image.analyze.url
@@ -0,0 +1,9 @@
+VISION IMAGE ANALYZE URL
+
+The `--url` argument is used to specify the URL of the image to analyze. The image must be publicly accessible.
+
+USAGE: ai vision image analyze --url [...other options]
+
+EXAMPLE
+
+ ai vision image analyze --url https://example.com/image.jpg --visual-features Caption
\ No newline at end of file
diff --git a/src/ai/.x/help/vision.image.analyze.visualfeatures b/src/ai/.x/help/vision.image.analyze.visualfeatures
new file mode 100644
index 00000000..22755253
--- /dev/null
+++ b/src/ai/.x/help/vision.image.analyze.visualfeatures
@@ -0,0 +1,19 @@
+VISION IMAGE ANALYZE VISUAL FEATURES
+
+The `--visual-features` argument is used to specify one or more visual features to extract from the image. The options are specified by a comma separated list.
+
+Here are the supported features:
+
+1. `Caption`: Generate a human-readable sentence that describes the content of an image.
+2. `Read`: Also known as Optical Character Recognition (OCR). Extract printed or handwritten text from images.
+3. `DenseCaptions`: Dense Captions provides more details by generating one-sentence captions for up to 10 different regions in the image, including one for the whole image.
+4. `Tags`: Extract content tags for thousands of recognizable objects, living beings, scenery, and actions that appear in images.
+5. `Objects`: Object detection. This is similar to tagging, but focuses on detecting physical objects in the image and returning their location.
+6. `SmartCrops`: Used to find a representative sub-region of the image for thumbnail generation, with priority given to include faces.
+7. `People`: Locate people in the image and return their location.
+
+USAGE: ai vision image analyze --visual-features [...other options]
+
+EXAMPLE
+
+ ai vision image analyze --file image.jpg --visual-features Caption,People
\ No newline at end of file
diff --git a/src/ai/.x/templates/helper_functions/HelperFunctionClass.cs b/src/ai/.x/templates/helper_functions/HelperFunctionClass.cs
index 94c3c46a..57a662c9 100644
--- a/src/ai/.x/templates/helper_functions/HelperFunctionClass.cs
+++ b/src/ai/.x/templates/helper_functions/HelperFunctionClass.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
<#@ template hostspecific="true" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ parameter type="System.String" name="ClassName" #>
diff --git a/src/ai/.x/templates/helper_functions/HelperFunctionsProject.csproj._ b/src/ai/.x/templates/helper_functions/HelperFunctionsProject.csproj._
index 3663e601..1609a440 100644
--- a/src/ai/.x/templates/helper_functions/HelperFunctionsProject.csproj._
+++ b/src/ai/.x/templates/helper_functions/HelperFunctionsProject.csproj._
@@ -12,10 +12,9 @@
-
-
+
diff --git a/src/ai/.x/templates/openai-chat-cs/OpenAIChatCompletions.csproj._ b/src/ai/.x/templates/openai-chat-cs/OpenAIChatCompletions.csproj._
index 6ee2c11d..30addbec 100644
--- a/src/ai/.x/templates/openai-chat-cs/OpenAIChatCompletions.csproj._
+++ b/src/ai/.x/templates/openai-chat-cs/OpenAIChatCompletions.csproj._
@@ -12,9 +12,8 @@
-
-
+
\ No newline at end of file
diff --git a/src/ai/.x/templates/openai-chat-cs/OpenAIChatCompletionsClass.cs b/src/ai/.x/templates/openai-chat-cs/OpenAIChatCompletionsClass.cs
index e352c9d0..f4fb03c3 100644
--- a/src/ai/.x/templates/openai-chat-cs/OpenAIChatCompletionsClass.cs
+++ b/src/ai/.x/templates/openai-chat-cs/OpenAIChatCompletionsClass.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
<#@ template hostspecific="true" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ parameter type="System.String" name="ClassName" #>
diff --git a/src/ai/.x/templates/openai-chat-cs/Program.cs b/src/ai/.x/templates/openai-chat-cs/Program.cs
index 021f22ef..690aef90 100644
--- a/src/ai/.x/templates/openai-chat-cs/Program.cs
+++ b/src/ai/.x/templates/openai-chat-cs/Program.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
<#@ template hostspecific="true" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ parameter type="System.String" name="ClassName" #>
diff --git a/src/ai/.x/templates/openai-chat-py/requirements.txt b/src/ai/.x/templates/openai-chat-py/requirements.txt
index 7a06be70..97b18c6d 100644
--- a/src/ai/.x/templates/openai-chat-py/requirements.txt
+++ b/src/ai/.x/templates/openai-chat-py/requirements.txt
@@ -1 +1 @@
-openai==1.0.0
+openai>1.0
\ No newline at end of file
diff --git a/src/ai/.x/templates/openai-chat-streaming-cs/OpenAIChatCompletionsStreaming.csproj._ b/src/ai/.x/templates/openai-chat-streaming-cs/OpenAIChatCompletionsStreaming.csproj._
index 6ee2c11d..30addbec 100644
--- a/src/ai/.x/templates/openai-chat-streaming-cs/OpenAIChatCompletionsStreaming.csproj._
+++ b/src/ai/.x/templates/openai-chat-streaming-cs/OpenAIChatCompletionsStreaming.csproj._
@@ -12,9 +12,8 @@
-
-
+
\ No newline at end of file
diff --git a/src/ai/.x/templates/openai-chat-streaming-cs/OpenAIChatCompletionsStreamingClass.cs b/src/ai/.x/templates/openai-chat-streaming-cs/OpenAIChatCompletionsStreamingClass.cs
index fe7120a2..1714c4ba 100644
--- a/src/ai/.x/templates/openai-chat-streaming-cs/OpenAIChatCompletionsStreamingClass.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-cs/OpenAIChatCompletionsStreamingClass.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
<#@ template hostspecific="true" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ parameter type="System.String" name="ClassName" #>
diff --git a/src/ai/.x/templates/openai-chat-streaming-cs/Program.cs b/src/ai/.x/templates/openai-chat-streaming-cs/Program.cs
index 46c8dffe..c1335d1b 100644
--- a/src/ai/.x/templates/openai-chat-streaming-cs/Program.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-cs/Program.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
<#@ template hostspecific="true" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ parameter type="System.String" name="ClassName" #>
diff --git a/src/ai/.x/templates/openai-chat-streaming-py/requirements.txt b/src/ai/.x/templates/openai-chat-streaming-py/requirements.txt
index 7a06be70..97b18c6d 100644
--- a/src/ai/.x/templates/openai-chat-streaming-py/requirements.txt
+++ b/src/ai/.x/templates/openai-chat-streaming-py/requirements.txt
@@ -1 +1 @@
-openai==1.0.0
+openai>1.0
\ No newline at end of file
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-data-cs/OpenAIChatCompletionsWithDataStreaming.csproj._ b/src/ai/.x/templates/openai-chat-streaming-with-data-cs/OpenAIChatCompletionsWithDataStreaming.csproj._
index 6ee2c11d..30addbec 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-data-cs/OpenAIChatCompletionsWithDataStreaming.csproj._
+++ b/src/ai/.x/templates/openai-chat-streaming-with-data-cs/OpenAIChatCompletionsWithDataStreaming.csproj._
@@ -12,9 +12,8 @@
-
-
+
\ No newline at end of file
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-data-cs/OpenAIChatCompletionsWithDataStreamingClass.cs b/src/ai/.x/templates/openai-chat-streaming-with-data-cs/OpenAIChatCompletionsWithDataStreamingClass.cs
index 0ae92a34..233a680a 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-data-cs/OpenAIChatCompletionsWithDataStreamingClass.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-with-data-cs/OpenAIChatCompletionsWithDataStreamingClass.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
<#@ template hostspecific="true" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ parameter type="System.String" name="ClassName" #>
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-data-cs/Program.cs b/src/ai/.x/templates/openai-chat-streaming-with-data-cs/Program.cs
index 34c93500..d89724c7 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-data-cs/Program.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-with-data-cs/Program.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
<#@ template hostspecific="true" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ parameter type="System.String" name="ClassName" #>
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-data-py/requirements.txt b/src/ai/.x/templates/openai-chat-streaming-with-data-py/requirements.txt
index 7a06be70..97b18c6d 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-data-py/requirements.txt
+++ b/src/ai/.x/templates/openai-chat-streaming-with-data-py/requirements.txt
@@ -1 +1 @@
-openai==1.0.0
+openai>1.0
\ No newline at end of file
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/FunctionCallContext.cs b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/FunctionCallContext.cs
index 0b5031ce..ec81143e 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/FunctionCallContext.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/FunctionCallContext.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using Azure.AI.OpenAI;
using System;
using System.Collections.Generic;
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/FunctionFactory.cs b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/FunctionFactory.cs
index 79b572e2..18add66b 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/FunctionFactory.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/FunctionFactory.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System.Reflection;
using Azure.AI.OpenAI;
using System.Collections;
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/HelperFunctionDescriptionAttribute.cs b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/HelperFunctionDescriptionAttribute.cs
index c6678de8..34da25d8 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/HelperFunctionDescriptionAttribute.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/HelperFunctionDescriptionAttribute.cs
@@ -1,4 +1,9 @@
-public class HelperFunctionDescriptionAttribute : Attribute
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+public class HelperFunctionDescriptionAttribute : Attribute
{
public HelperFunctionDescriptionAttribute()
{
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/HelperFunctionParameterDescriptionAttribute.cs b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/HelperFunctionParameterDescriptionAttribute.cs
index 36e672a4..f690d4dd 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/HelperFunctionParameterDescriptionAttribute.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/HelperFunctionParameterDescriptionAttribute.cs
@@ -1,4 +1,9 @@
-public class HelperFunctionParameterDescriptionAttribute : Attribute
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+public class HelperFunctionParameterDescriptionAttribute : Attribute
{
public HelperFunctionParameterDescriptionAttribute()
{
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsCustomFunctions.cs b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsCustomFunctions.cs
index 55b7daad..7d9a9351 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsCustomFunctions.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsCustomFunctions.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
public class OpenAIChatCompletionsCustomFunctions
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsFunctionsStreaming.csproj._ b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsFunctionsStreaming.csproj._
index 565629b9..e92bb51d 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsFunctionsStreaming.csproj._
+++ b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsFunctionsStreaming.csproj._
@@ -12,9 +12,8 @@
-
-
+
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsFunctionsStreamingClass.cs b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsFunctionsStreamingClass.cs
index a804e0c5..ee64e974 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsFunctionsStreamingClass.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/OpenAIChatCompletionsFunctionsStreamingClass.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
<#@ template hostspecific="true" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ parameter type="System.String" name="ClassName" #>
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/Program.cs b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/Program.cs
index 769c8e1b..03c3a2e4 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/Program.cs
+++ b/src/ai/.x/templates/openai-chat-streaming-with-functions-cs/Program.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
<#@ template hostspecific="true" #>
<#@ output extension=".cs" encoding="utf-8" #>
<#@ parameter type="System.String" name="ClassName" #>
diff --git a/src/ai/.x/templates/openai-chat-streaming-with-functions-py/requirements.txt b/src/ai/.x/templates/openai-chat-streaming-with-functions-py/requirements.txt
index 7a06be70..97b18c6d 100644
--- a/src/ai/.x/templates/openai-chat-streaming-with-functions-py/requirements.txt
+++ b/src/ai/.x/templates/openai-chat-streaming-with-functions-py/requirements.txt
@@ -1 +1 @@
-openai==1.0.0
+openai>1.0
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-continuous-reco-cs/Program.cs b/src/ai/.x/templates/speech-to-text-continuous-reco-cs/Program.cs
new file mode 100644
index 00000000..9d8625bb
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-continuous-reco-cs/Program.cs
@@ -0,0 +1,113 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".cs" encoding="utf-8" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_ENDPOINT" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_KEY" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_REGION" #>
+using System;
+using Microsoft.CognitiveServices.Speech;
+using Microsoft.CognitiveServices.Speech.Audio;
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ // Connection and configuration details required
+ var speechKey = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_KEY") ?? "<#= AZURE_AI_SPEECH_KEY #>";
+ var speechRegion = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_REGION") ?? "<#= AZURE_AI_SPEECH_REGION #>";
+ var speechLanguage = "en-US"; // BCP-47 language code
+
+ // Create instances of a speech config, source language config, and audio config
+ var config = SpeechConfig.FromSubscription(speechKey, speechRegion);
+ var sourceLanguageConfig = SourceLanguageConfig.FromLanguage(speechLanguage);
+ var audioConfig = AudioConfig.FromDefaultMicrophoneInput();
+
+ // Create the speech recognizer from the above configuration information
+ using (var recognizer = new SpeechRecognizer(config, sourceLanguageConfig, audioConfig))
+ {
+ // Subscribe to the Recognizing and Recognized events. As the user speaks individual
+ // utterances, intermediate recognition results are sent to the Recognizing event,
+ // and the final recognition results are sent to the Recognized event.
+ recognizer.Recognizing += (s, e) => HandleRecognizingEvent(e);
+ recognizer.Recognized += (s, e) => HandleRecognizedEvent(e);
+
+ // Create a task completion source to wait for the session to stop. This is needed in
+ // console apps to prevent the main thread from terminating while the recognition is
+ // running asynchronously on a separate background thread.
+ var sessionStoppedNoError = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ // Subscribe to SessionStarted and SessionStopped events. These events are useful for
+ // logging the start and end of a speech recognition session. In console apps, this is
+ // used to allow the application to block the main thread until recognition is complete.
+ recognizer.SessionStarted += (s, e) => HandleSessionStartedEvent(e);
+ recognizer.SessionStopped += (s, e) => HandleSessionStoppedEvent(e, sessionStoppedNoError);
+
+ // Subscribe to the Canceled event, which indicates that the recognition operation
+ // was stopped/canceled, likely due to an error of some kind.
+ recognizer.Canceled += (s, e) => HandleCanceledEvent(e, sessionStoppedNoError);
+
+ // Allow the user to press ENTER to stop recognition
+ Task.Run(() =>
+ {
+ while (Console.ReadKey().Key != ConsoleKey.Enter) { }
+ recognizer.StopContinuousRecognitionAsync();
+ });
+
+ // Start speech recognition
+ await recognizer.StartContinuousRecognitionAsync();
+ Console.WriteLine("Listening; press ENTER to stop ...\n");
+
+ // Wait for the session to stop. The Task will not complete until the recognition
+ // session stops, and the result will indicate whether the session completed
+ // or was canceled.
+ return await sessionStoppedNoError.Task ? 0 : 1;
+ }
+ }
+
+ private static void HandleRecognizingEvent(SpeechRecognitionEventArgs e)
+ {
+ Console.WriteLine($"RECOGNIZING: {e.Result.Text}");
+ }
+
+ private static void HandleRecognizedEvent(SpeechRecognitionEventArgs e)
+ {
+ if (e.Result.Reason == ResultReason.RecognizedSpeech && !string.IsNullOrEmpty(e.Result.Text))
+ {
+ Console.WriteLine($"RECOGNIZED: {e.Result.Text}\n");
+ }
+ else if (e.Result.Reason == ResultReason.NoMatch)
+ {
+ Console.WriteLine($"NOMATCH: Speech could not be recognized.\n");
+ }
+ }
+
+ private static void HandleSessionStartedEvent(SessionEventArgs e)
+ {
+ Console.WriteLine($"SESSION STARTED: {e.SessionId}.\n");
+ }
+
+ private static void HandleSessionStoppedEvent(SessionEventArgs e, TaskCompletionSource sessionStoppedNoError)
+ {
+ Console.WriteLine($"SESSION STOPPED: {e.SessionId}.");
+ sessionStoppedNoError.TrySetResult(true); // Set the result so the main thread can exit
+ }
+
+ private static void HandleCanceledEvent(SpeechRecognitionCanceledEventArgs e, TaskCompletionSource sessionStoppedNoError)
+ {
+ Console.WriteLine($"CANCELED: Reason={e.Reason}");
+
+ // Check the CancellationReason for more detailed information.
+ if (e.Reason == CancellationReason.EndOfStream)
+ {
+ Console.WriteLine($"CANCELED: End of the audio stream was reached.");
+ }
+ else if (e.Reason == CancellationReason.Error)
+ {
+ Console.WriteLine($"CANCELED: ErrorCode={e.ErrorCode}");
+ Console.WriteLine($"CANCELED: ErrorDetails={e.ErrorDetails}");
+ Console.WriteLine($"CANCELED: Did you update the subscription info?");
+ }
+
+ // Set the task completion source result so the main thread can exit
+ sessionStoppedNoError.TrySetResult(e.Reason != CancellationReason.Error);
+ }
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-continuous-reco-cs/SpeechToTextContinuousReco.csproj._ b/src/ai/.x/templates/speech-to-text-continuous-reco-cs/SpeechToTextContinuousReco.csproj._
new file mode 100644
index 00000000..b0925d48
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-continuous-reco-cs/SpeechToTextContinuousReco.csproj._
@@ -0,0 +1,17 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".csproj" encoding="utf-8" #>
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ Exe
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-continuous-reco-cs/_.json b/src/ai/.x/templates/speech-to-text-continuous-reco-cs/_.json
new file mode 100644
index 00000000..e90b716d
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-continuous-reco-cs/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Speech-to-text (w/ Continuous recognition)",
+ "_ShortName": "speech-to-text-continuous-reco",
+ "_Language": "C#",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-continuous-reco-py/_.json b/src/ai/.x/templates/speech-to-text-continuous-reco-py/_.json
new file mode 100644
index 00000000..ab272a72
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-continuous-reco-py/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Speech-to-text (w/ Continuous recognition)",
+ "_ShortName": "speech-to-text-continuous-reco",
+ "_Language": "Python",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-continuous-reco-py/main.py b/src/ai/.x/templates/speech-to-text-continuous-reco-py/main.py
new file mode 100644
index 00000000..f9466b0f
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-continuous-reco-py/main.py
@@ -0,0 +1,87 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".py" encoding="utf-8" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_ENDPOINT" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_KEY" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_REGION" #>
+from concurrent.futures import Future
+from azure.cognitiveservices.speech import SpeechConfig, SpeechRecognizer, AudioConfig, CancellationReason
+import threading
+import os
+
+# Connection and configuration details required
+speech_key = os.environ.get('AZURE_AI_SPEECH_KEY') or "<#= AZURE_AI_SPEECH_KEY #>"
+service_region = os.environ.get('AZURE_AI_SPEECH_REGION') or "<#= AZURE_AI_SPEECH_REGION #>"
+speech_language = "en-US"
+
+# Create instances of a speech config and audio config
+speech_config = SpeechConfig(subscription=speech_key, region=service_region, speech_recognition_language=speech_language)
+audio_config = AudioConfig(use_default_microphone=True)
+
+# Create the speech recognizer from the above configuration information
+speech_recognizer = SpeechRecognizer(speech_config=speech_config, audio_config=audio_config)
+
+# Subscribe to the recognizing and recognized events. As the user speaks individual
+# utterances, intermediate recognition results are sent to the Recognizing event,
+# and the final recognition results are sent to the Recognized event.
+def recognizing(args):
+ print("RECOGNIZING: {}".format(args.result.text))
+
+def recognized(args):
+ if args.result.reason.name == "RecognizedSpeech" and args.result.text != "":
+ print("RECOGNIZED: {}\n".format(args.result.text))
+ elif args.result.reason.name == "NoMatch":
+ print("NOMATCH: Speech could not be recognized.\n")
+
+speech_recognizer.recognizing.connect(recognizing)
+speech_recognizer.recognized.connect(recognized)
+
+# Create a future to wait for the session to stop. This is needed in console apps to
+# prevent the main thread from terminating while the recognition is running
+# asynchronously on a separate background thread.
+session_stopped_no_error = Future()
+
+# Subscribe to session_started and session_stopped events. These events are useful for
+# logging the start and end of a speech recognition session. In console apps, this is
+# used to allow the application to block the main thread until recognition is complete.
+def session_started(args):
+ print("SESSION STARTED: {}\n".format(args.session_id))
+
+def session_stopped(args):
+ print("SESSION STOPPED: {}".format(args.session_id))
+ session_stopped_no_error.set_result(True)
+
+speech_recognizer.session_started.connect(session_started)
+speech_recognizer.session_stopped.connect(session_stopped)
+
+# Subscribe to the canceled event, which indicates that the recognition operation
+# was stopped/canceled, likely due to an error of some kind.
+def canceled(args):
+ print("CANCELED: Reason={}".format(args.cancellation_details.reason))
+
+ # Check the CancellationReason for more detailed information.
+ if args.cancellation_details.reason == CancellationReason.EndOfStream:
+ print("CANCELED: End of the audio stream was reached.")
+ elif args.cancellation_details.reason == CancellationReason.Error:
+ print("CANCELED: ErrorDetails={}".format(args.cancellation_details.error_details))
+ print("CANCELED: Did you update the subscription info?")
+
+ # Set the future's result so the main thread can exit
+ session_stopped_no_error.set_result(args.cancellation_details.reason != CancellationReason.Error)
+
+speech_recognizer.canceled.connect(canceled)
+
+# Allow the user to press ENTER to stop recognition
+threading.Thread(target=lambda: (
+ input(""),
+ speech_recognizer.stop_continuous_recognition())
+).start()
+
+# Start speech recognition
+speech_recognizer.start_continuous_recognition()
+print("Listening, press ENTER to stop...")
+
+# Wait for the session to stop. result() will not return until the recognition
+# session stops, and the result will indicate whether the session completed
+# or was canceled.
+exit_code = 0 if session_stopped_no_error.result() == True else 1
+os._exit(exit_code)
diff --git a/src/ai/.x/templates/speech-to-text-continuous-reco-py/requirements.txt b/src/ai/.x/templates/speech-to-text-continuous-reco-py/requirements.txt
new file mode 100644
index 00000000..f33c7861
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-continuous-reco-py/requirements.txt
@@ -0,0 +1 @@
+azure-cognitiveservices-speech>=1.35.0
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-cs/Program.cs b/src/ai/.x/templates/speech-to-text-cs/Program.cs
new file mode 100644
index 00000000..0f19acf2
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-cs/Program.cs
@@ -0,0 +1,57 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".cs" encoding="utf-8" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_ENDPOINT" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_KEY" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_REGION" #>
+using System;
+using Microsoft.CognitiveServices.Speech;
+using Microsoft.CognitiveServices.Speech.Audio;
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ // Connection and configuration details required
+ var speechKey = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_KEY") ?? "<#= AZURE_AI_SPEECH_KEY #>";
+ var speechRegion = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_REGION") ?? "<#= AZURE_AI_SPEECH_REGION #>";
+ var speechLanguage = "en-US"; // BCP-47 language code
+
+ // Create instances of a speech config, source language config, and audio config
+ var config = SpeechConfig.FromSubscription(speechKey, speechRegion);
+ var sourceLanguageConfig = SourceLanguageConfig.FromLanguage(speechLanguage);
+ var audioConfig = AudioConfig.FromDefaultMicrophoneInput();
+
+ // Create the speech recognizer from the above configuration information
+ using (var recognizer = new SpeechRecognizer(config, sourceLanguageConfig, audioConfig))
+ {
+ Console.WriteLine("Listening ...\n");
+
+ // Start speech recognition, and return after a single utterance is recognized. The end of a
+ // single utterance is determined by listening for silence at the end or until a maximum of 15
+ // seconds of audio is processed.
+ var result = await recognizer.RecognizeOnceAsync();
+
+ // Check the result
+ if (result.Reason == ResultReason.RecognizedSpeech)
+ {
+ Console.WriteLine($"RECOGNIZED: {result.Text}");
+ }
+ else if (result.Reason == ResultReason.NoMatch)
+ {
+ Console.WriteLine($"NOMATCH: Speech could not be recognized.");
+ }
+ else if (result.Reason == ResultReason.Canceled)
+ {
+ var cancellation = CancellationDetails.FromResult(result);
+ Console.WriteLine($"CANCELED: Reason={cancellation.Reason}");
+
+ if (cancellation.Reason == CancellationReason.Error)
+ {
+ Console.WriteLine($"CANCELED: ErrorCode={cancellation.ErrorCode}");
+ Console.WriteLine($"CANCELED: ErrorDetails={cancellation.ErrorDetails}");
+ Console.WriteLine($"CANCELED: Did you update the subscription info?");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-cs/SpeechToText.csproj._ b/src/ai/.x/templates/speech-to-text-cs/SpeechToText.csproj._
new file mode 100644
index 00000000..b0925d48
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-cs/SpeechToText.csproj._
@@ -0,0 +1,17 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".csproj" encoding="utf-8" #>
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ Exe
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-cs/_.json b/src/ai/.x/templates/speech-to-text-cs/_.json
new file mode 100644
index 00000000..c8390ae0
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-cs/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Speech-to-text",
+ "_ShortName": "speech-to-text",
+ "_Language": "C#",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-py/_.json b/src/ai/.x/templates/speech-to-text-py/_.json
new file mode 100644
index 00000000..ac6d9760
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-py/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Speech-to-text",
+ "_ShortName": "speech-to-text",
+ "_Language": "Python",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-py/main.py b/src/ai/.x/templates/speech-to-text-py/main.py
new file mode 100644
index 00000000..674a8bed
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-py/main.py
@@ -0,0 +1,39 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".py" encoding="utf-8" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_ENDPOINT" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_KEY" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_REGION" #>
+from concurrent.futures import Future
+from azure.cognitiveservices.speech import SpeechConfig, SpeechRecognizer, AudioConfig, CancellationReason
+import threading
+import os
+
+# Connection and configuration details required
+speech_key = os.environ.get('AZURE_AI_SPEECH_KEY') or "<#= AZURE_AI_SPEECH_KEY #>"
+service_region = os.environ.get('AZURE_AI_SPEECH_REGION') or "<#= AZURE_AI_SPEECH_REGION #>"
+speech_language = "en-US"
+
+# Create instances of a speech config and audio config
+speech_config = SpeechConfig(subscription=speech_key, region=service_region, speech_recognition_language=speech_language)
+audio_config = AudioConfig(use_default_microphone=True)
+
+# Create the speech recognizer from the above configuration information
+speech_recognizer = SpeechRecognizer(speech_config=speech_config, audio_config=audio_config)
+
+# Start speech recognition, and return after a single utterance is recognized. The end of a
+# single utterance is determined by listening for silence at the end or until a maximum of 15
+# seconds of audio is processed.
+print("Listening ...\n")
+result = speech_recognizer.recognize_once()
+
+# Check the result
+if result.reason.name == "RecognizedSpeech" and result.text != "":
+ print("RECOGNIZED: {}".format(result.text))
+elif result.reason.name == "NoMatch":
+ print("NOMATCH: Speech could not be recognized.")
+elif result.reason.name == "Canceled":
+ cancellation_details = result.cancellation_details
+ print("CANCELED: Reason={}".format(cancellation_details.reason))
+ if cancellation_details.reason == CancellationReason.Error:
+ print("CANCELED: ErrorDetails={}".format(cancellation_details.error_details))
+ print("CANCELED: Did you update the subscription info?")
diff --git a/src/ai/.x/templates/speech-to-text-py/requirements.txt b/src/ai/.x/templates/speech-to-text-py/requirements.txt
new file mode 100644
index 00000000..f33c7861
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-py/requirements.txt
@@ -0,0 +1 @@
+azure-cognitiveservices-speech>=1.35.0
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-file-cs/Program.cs b/src/ai/.x/templates/speech-to-text-with-file-cs/Program.cs
new file mode 100644
index 00000000..3f6cd5b7
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-file-cs/Program.cs
@@ -0,0 +1,121 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".cs" encoding="utf-8" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_ENDPOINT" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_KEY" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_REGION" #>
+using System;
+using Microsoft.CognitiveServices.Speech;
+using Microsoft.CognitiveServices.Speech.Audio;
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ // Connection and configuration details required
+ var speechKey = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_KEY") ?? "<#= AZURE_AI_SPEECH_KEY #>";
+ var speechRegion = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_REGION") ?? "<#= AZURE_AI_SPEECH_REGION #>";
+ var speechLanguage = "en-US"; // BCP-47 language code
+ var inputFileName = args.Length == 1 ? args[0] : "audio.wav";
+
+ // Check to see if the input file exists
+ if (!File.Exists(inputFileName))
+ {
+ Console.WriteLine($"ERROR: Cannot find audio input file: {inputFileName}");
+ return 1;
+ }
+
+ // Create instances of a speech config, source language config, and audio config
+ var config = SpeechConfig.FromSubscription(speechKey, speechRegion);
+ var sourceLanguageConfig = SourceLanguageConfig.FromLanguage(speechLanguage);
+ var audioConfig = AudioConfig.FromWavFileInput(inputFileName);
+
+ // Create the speech recognizer from the above configuration information
+ using (var recognizer = new SpeechRecognizer(config, sourceLanguageConfig, audioConfig))
+ {
+ // Subscribe to the Recognizing and Recognized events. As the user speaks individual
+ // utterances, intermediate recognition results are sent to the Recognizing event,
+ // and the final recognition results are sent to the Recognized event.
+ recognizer.Recognizing += (s, e) => HandleRecognizingEvent(e);
+ recognizer.Recognized += (s, e) => HandleRecognizedEvent(e);
+
+ // Create a task completion source to wait for the session to stop. This is needed in
+ // console apps to prevent the main thread from terminating while the recognition is
+ // running asynchronously on a separate background thread.
+ var sessionStoppedNoError = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ // Subscribe to SessionStarted and SessionStopped events. These events are useful for
+ // logging the start and end of a speech recognition session. In console apps, this is
+ // used to allow the application to block the main thread until recognition is complete.
+ recognizer.SessionStarted += (s, e) => HandleSessionStartedEvent(e);
+ recognizer.SessionStopped += (s, e) => HandleSessionStoppedEvent(e, sessionStoppedNoError);
+
+ // Subscribe to the Canceled event, which indicates that the recognition operation
+ // was stopped/canceled, likely due to an error of some kind.
+ recognizer.Canceled += (s, e) => HandleCanceledEvent(e, sessionStoppedNoError);
+
+ // Allow the user to press ENTER to stop recognition
+ Task.Run(() =>
+ {
+ while (Console.ReadKey().Key != ConsoleKey.Enter) { }
+ recognizer.StopContinuousRecognitionAsync();
+ });
+
+ // Start speech recognition
+ await recognizer.StartContinuousRecognitionAsync();
+ Console.WriteLine("Listening; press ENTER to stop ...\n");
+
+ // Wait for the session to stop. The Task will not complete until the recognition
+ // session stops, and the result will indicate whether the session completed
+ // or was canceled.
+ return await sessionStoppedNoError.Task ? 0 : 1;
+ }
+ }
+
+ private static void HandleRecognizingEvent(SpeechRecognitionEventArgs e)
+ {
+ Console.WriteLine($"RECOGNIZING: {e.Result.Text}");
+ }
+
+ private static void HandleRecognizedEvent(SpeechRecognitionEventArgs e)
+ {
+ if (e.Result.Reason == ResultReason.RecognizedSpeech && !string.IsNullOrEmpty(e.Result.Text))
+ {
+ Console.WriteLine($"RECOGNIZED: {e.Result.Text}\n");
+ }
+ else if (e.Result.Reason == ResultReason.NoMatch)
+ {
+ Console.WriteLine($"NOMATCH: Speech could not be recognized.\n");
+ }
+ }
+
+ private static void HandleSessionStartedEvent(SessionEventArgs e)
+ {
+ Console.WriteLine($"SESSION STARTED: {e.SessionId}.\n");
+ }
+
+ private static void HandleSessionStoppedEvent(SessionEventArgs e, TaskCompletionSource sessionStoppedNoError)
+ {
+ Console.WriteLine($"SESSION STOPPED: {e.SessionId}.");
+ sessionStoppedNoError.TrySetResult(true); // Set the result so the main thread can exit
+ }
+
+ private static void HandleCanceledEvent(SpeechRecognitionCanceledEventArgs e, TaskCompletionSource sessionStoppedNoError)
+ {
+ Console.WriteLine($"CANCELED: Reason={e.Reason}");
+
+ // Check the CancellationReason for more detailed information.
+ if (e.Reason == CancellationReason.EndOfStream)
+ {
+ Console.WriteLine($"CANCELED: End of the audio stream was reached.");
+ }
+ else if (e.Reason == CancellationReason.Error)
+ {
+ Console.WriteLine($"CANCELED: ErrorCode={e.ErrorCode}");
+ Console.WriteLine($"CANCELED: ErrorDetails={e.ErrorDetails}");
+ Console.WriteLine($"CANCELED: Did you update the subscription info?");
+ }
+
+ // Set the task completion source result so the main thread can exit
+ sessionStoppedNoError.TrySetResult(e.Reason != CancellationReason.Error);
+ }
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-file-cs/SpeechToTextWithFile.csproj._ b/src/ai/.x/templates/speech-to-text-with-file-cs/SpeechToTextWithFile.csproj._
new file mode 100644
index 00000000..b0925d48
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-file-cs/SpeechToTextWithFile.csproj._
@@ -0,0 +1,17 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".csproj" encoding="utf-8" #>
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ Exe
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-file-cs/_.json b/src/ai/.x/templates/speech-to-text-with-file-cs/_.json
new file mode 100644
index 00000000..88a98f12
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-file-cs/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Speech-to-text (w/ File input)",
+ "_ShortName": "speech-to-text-with-file",
+ "_Language": "C#",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-file-py/_.json b/src/ai/.x/templates/speech-to-text-with-file-py/_.json
new file mode 100644
index 00000000..690f6420
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-file-py/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Speech-to-text (w/ File input)",
+ "_ShortName": "speech-to-text-with-file",
+ "_Language": "Python",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-file-py/main.py b/src/ai/.x/templates/speech-to-text-with-file-py/main.py
new file mode 100644
index 00000000..5ece4954
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-file-py/main.py
@@ -0,0 +1,94 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".py" encoding="utf-8" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_ENDPOINT" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_KEY" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_REGION" #>
+from concurrent.futures import Future
+from azure.cognitiveservices.speech import SpeechConfig, SpeechRecognizer, AudioConfig, CancellationReason
+import threading
+import os
+import sys
+
+# Connection and configuration details required
+speech_key = os.environ.get('AZURE_AI_SPEECH_KEY') or "<#= AZURE_AI_SPEECH_KEY #>"
+service_region = os.environ.get('AZURE_AI_SPEECH_REGION') or "<#= AZURE_AI_SPEECH_REGION #>"
+speech_language = "en-US"
+input_file = sys.argv[1] if len(sys.argv) == 2 else "audio.wav"
+
+# Check to see if the input file exists
+if not os.path.exists(input_file):
+ print("ERROR: Cannot find audio input file: {}".format(input_file))
+ sys.exit(1)
+
+# Create instances of a speech config and audio config
+speech_config = SpeechConfig(subscription=speech_key, region=service_region, speech_recognition_language=speech_language)
+audio_config = AudioConfig(filename=input_file)
+
+# Create the speech recognizer from the above configuration information
+speech_recognizer = SpeechRecognizer(speech_config=speech_config, audio_config=audio_config)
+
+# Subscribe to the recognizing and recognized events. As the user speaks individual
+# utterances, intermediate recognition results are sent to the Recognizing event,
+# and the final recognition results are sent to the Recognized event.
+def recognizing(args):
+ print("RECOGNIZING: {}".format(args.result.text))
+
+def recognized(args):
+ if args.result.reason.name == "RecognizedSpeech" and args.result.text != "":
+ print("RECOGNIZED: {}\n".format(args.result.text))
+ elif args.result.reason.name == "NoMatch":
+ print("NOMATCH: Speech could not be recognized.\n")
+
+speech_recognizer.recognizing.connect(recognizing)
+speech_recognizer.recognized.connect(recognized)
+
+# Create a future to wait for the session to stop. This is needed in console apps to
+# prevent the main thread from terminating while the recognition is running
+# asynchronously on a separate background thread.
+session_stopped_no_error = Future()
+
+# Subscribe to session_started and session_stopped events. These events are useful for
+# logging the start and end of a speech recognition session. In console apps, this is
+# used to allow the application to block the main thread until recognition is complete.
+def session_started(args):
+ print("SESSION STARTED: {}\n".format(args.session_id))
+
+def session_stopped(args):
+ print("SESSION STOPPED: {}".format(args.session_id))
+ session_stopped_no_error.set_result(True)
+
+speech_recognizer.session_started.connect(session_started)
+speech_recognizer.session_stopped.connect(session_stopped)
+
+# Subscribe to the canceled event, which indicates that the recognition operation
+# was stopped/canceled, likely due to an error of some kind.
+def canceled(args):
+ print("CANCELED: Reason={}".format(args.cancellation_details.reason))
+
+ # Check the CancellationReason for more detailed information.
+ if args.cancellation_details.reason == CancellationReason.EndOfStream:
+ print("CANCELED: End of the audio stream was reached.")
+ elif args.cancellation_details.reason == CancellationReason.Error:
+ print("CANCELED: ErrorDetails={}".format(args.cancellation_details.error_details))
+ print("CANCELED: Did you update the subscription info?")
+
+ # Set the future's result so the main thread can exit
+ session_stopped_no_error.set_result(args.cancellation_details.reason != CancellationReason.Error)
+
+speech_recognizer.canceled.connect(canceled)
+
+# Allow the user to press ENTER to stop recognition
+threading.Thread(target=lambda: (
+ input(""),
+ speech_recognizer.stop_continuous_recognition())
+).start()
+
+# Start speech recognition
+speech_recognizer.start_continuous_recognition()
+print("Listening, press ENTER to stop...")
+
+# Wait for the session to stop. result() will not return until the recognition
+# session stops, and the result will indicate whether the session completed
+# or was canceled.
+exit_code = 0 if session_stopped_no_error.result() == True else 1
+os._exit(exit_code)
diff --git a/src/ai/.x/templates/speech-to-text-with-file-py/requirements.txt b/src/ai/.x/templates/speech-to-text-with-file-py/requirements.txt
new file mode 100644
index 00000000..f33c7861
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-file-py/requirements.txt
@@ -0,0 +1 @@
+azure-cognitiveservices-speech>=1.35.0
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-keyword-cs/Program.cs b/src/ai/.x/templates/speech-to-text-with-keyword-cs/Program.cs
new file mode 100644
index 00000000..5ee7130f
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-keyword-cs/Program.cs
@@ -0,0 +1,136 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".cs" encoding="utf-8" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_ENDPOINT" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_KEY" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_REGION" #>
+using System;
+using Microsoft.CognitiveServices.Speech;
+using Microsoft.CognitiveServices.Speech.Audio;
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ // Connection and configuration details required
+ var speechKey = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_KEY") ?? "<#= AZURE_AI_SPEECH_KEY #>";
+ var speechRegion = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_REGION") ?? "<#= AZURE_AI_SPEECH_REGION #>";
+ var speechLanguage = "en-US"; // BCP-47 language code
+ var inputFileName = args.Length == 1 ? args[0] : null;
+ var keywordFileName = "keyword.table";
+
+ // Check to see if the input file exists
+ if (inputFileName != null && !File.Exists(inputFileName))
+ {
+ Console.WriteLine($"ERROR: Cannot find audio input file: {inputFileName}");
+ return 1;
+ }
+
+ // Check to see if the keyword file exists
+ if (!File.Exists(keywordFileName))
+ {
+ Console.WriteLine($"ERROR: Cannot find keyword file: {keywordFileName}");
+ return 1;
+ }
+
+ // Create instances of a speech config, source language config, and audio config
+ var config = SpeechConfig.FromSubscription(speechKey, speechRegion);
+ var sourceLanguageConfig = SourceLanguageConfig.FromLanguage(speechLanguage);
+ var audioConfig = inputFileName != null
+ ? AudioConfig.FromWavFileInput(inputFileName)
+ : AudioConfig.FromDefaultMicrophoneInput();
+
+ // Create the speech recognizer from the above configuration information
+ using (var recognizer = new SpeechRecognizer(config, sourceLanguageConfig, audioConfig))
+ {
+ // Subscribe to the Recognizing and Recognized events. As the user speaks individual
+ // utterances, intermediate recognition results are sent to the Recognizing event,
+ // and the final recognition results are sent to the Recognized event.
+ recognizer.Recognizing += (s, e) => HandleRecognizingEvent(e);
+ recognizer.Recognized += (s, e) => HandleRecognizedEvent(e);
+
+ // Create a task completion source to wait for the session to stop. This is needed in
+ // console apps to prevent the main thread from terminating while the recognition is
+ // running asynchronously on a separate background thread.
+ var sessionStoppedNoError = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ // Subscribe to SessionStarted and SessionStopped events. These events are useful for
+ // logging the start and end of a speech recognition session. In console apps, this is
+ // used to allow the application to block the main thread until recognition is complete.
+ recognizer.SessionStarted += (s, e) => HandleSessionStartedEvent(e);
+ recognizer.SessionStopped += (s, e) => HandleSessionStoppedEvent(e);
+
+ // Subscribe to the Canceled event, which indicates that the recognition operation
+ // was stopped/canceled, likely due to an error of some kind.
+ recognizer.Canceled += (s, e) => HandleCanceledEvent(e, sessionStoppedNoError);
+
+ // Allow the user to press ENTER to stop recognition
+ Task.Run(() =>
+ {
+ while (Console.ReadKey().Key != ConsoleKey.Enter) { }
+ recognizer.StopContinuousRecognitionAsync();
+ });
+
+ var keywordModel = KeywordRecognitionModel.FromFile(keywordFileName);
+
+ // Start keyword recognition
+ await recognizer.StartKeywordRecognitionAsync(keywordModel);
+ Console.WriteLine("Listening for keyword; press ENTER to stop ...\n");
+
+ // Wait for the session to stop. The Task will not complete until the recognition
+ // session stops, and the result will indicate whether the session completed
+ // or was canceled.
+ return await sessionStoppedNoError.Task ? 0 : 1;
+ }
+ }
+
+ private static void HandleRecognizingEvent(SpeechRecognitionEventArgs e)
+ {
+ Console.WriteLine($"RECOGNIZING: {e.Result.Text}");
+ }
+
+ private static void HandleRecognizedEvent(SpeechRecognitionEventArgs e)
+ {
+ if (e.Result.Reason == ResultReason.RecognizedKeyword && !string.IsNullOrEmpty(e.Result.Text))
+ {
+ Console.WriteLine($"RECOGNIZED: {e.Result.Text}\n");
+ }
+ else if (e.Result.Reason == ResultReason.RecognizedSpeech && !string.IsNullOrEmpty(e.Result.Text))
+ {
+ Console.WriteLine($"RECOGNIZED: {e.Result.Text}\n");
+ }
+ else if (e.Result.Reason == ResultReason.NoMatch)
+ {
+ Console.WriteLine($"NOMATCH: Speech could not be recognized.\n");
+ }
+ }
+
+ private static void HandleSessionStartedEvent(SessionEventArgs e)
+ {
+ Console.WriteLine($"SESSION STARTED: {e.SessionId}.\n");
+ }
+
+ private static void HandleSessionStoppedEvent(SessionEventArgs e)
+ {
+ Console.WriteLine($"SESSION STOPPED: {e.SessionId}.");
+ }
+
+ private static void HandleCanceledEvent(SpeechRecognitionCanceledEventArgs e, TaskCompletionSource sessionStoppedNoError)
+ {
+ Console.WriteLine($"CANCELED: Reason={e.Reason}");
+
+ // Check the CancellationReason for more detailed information.
+ if (e.Reason == CancellationReason.EndOfStream)
+ {
+ Console.WriteLine($"CANCELED: End of the audio stream was reached.");
+ }
+ else if (e.Reason == CancellationReason.Error)
+ {
+ Console.WriteLine($"CANCELED: ErrorCode={e.ErrorCode}");
+ Console.WriteLine($"CANCELED: ErrorDetails={e.ErrorDetails}");
+ Console.WriteLine($"CANCELED: Did you update the subscription info?");
+ }
+
+ // Set the task completion source result so the main thread can exit
+ sessionStoppedNoError.TrySetResult(e.Reason != CancellationReason.Error);
+ }
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-keyword-cs/SpeechToTextWithKeyword.csproj._ b/src/ai/.x/templates/speech-to-text-with-keyword-cs/SpeechToTextWithKeyword.csproj._
new file mode 100644
index 00000000..b0925d48
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-keyword-cs/SpeechToTextWithKeyword.csproj._
@@ -0,0 +1,17 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".csproj" encoding="utf-8" #>
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ Exe
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-keyword-cs/_.json b/src/ai/.x/templates/speech-to-text-with-keyword-cs/_.json
new file mode 100644
index 00000000..f6597d5d
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-keyword-cs/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Speech-to-text (w/ Keyword detection)",
+ "_ShortName": "speech-to-text-with-keyword",
+ "_Language": "C#",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-keyword-py/_.json b/src/ai/.x/templates/speech-to-text-with-keyword-py/_.json
new file mode 100644
index 00000000..7400e67e
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-keyword-py/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Speech-to-text (w/ Keyword detection)",
+ "_ShortName": "speech-to-text-with-keyword",
+ "_Language": "Python",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-keyword-py/main.py b/src/ai/.x/templates/speech-to-text-with-keyword-py/main.py
new file mode 100644
index 00000000..c90362d9
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-keyword-py/main.py
@@ -0,0 +1,97 @@
+from concurrent.futures import Future
+from azure.cognitiveservices.speech import SpeechConfig, SpeechRecognizer, AudioConfig, KeywordRecognitionModel, CancellationReason
+import threading
+import os
+import sys
+
+# Connection and configuration details required
+speech_key = os.environ.get('AZURE_AI_SPEECH_KEY') or ''
+service_region = os.environ.get('AZURE_AI_SPEECH_REGION') or ''
+speech_language = 'en-US'
+input_file = sys.argv[1] if len(sys.argv) == 2 else None
+keyword_file = 'keyword.table'
+
+# Check to see if the input file exists
+if input_file is not None and not os.path.exists(input_file):
+ print(f'ERROR: Cannot find audio input file: {input_file}')
+ sys.exit(1)
+
+# Check to see if the keyword file exists
+if not os.path.exists(keyword_file):
+ print(f'ERROR: Cannot find keyword file: {keyword_file}')
+ sys.exit(1)
+
+# Create instances of a speech config and audio config
+speech_config = SpeechConfig(subscription=speech_key, region=service_region, speech_recognition_language=speech_language)
+audio_config = AudioConfig(filename=input_file) if input_file is not None else AudioConfig(use_default_microphone=True)
+
+# Create the speech recognizer from the above configuration information
+speech_recognizer = SpeechRecognizer(speech_config=speech_config, audio_config=audio_config)
+
+# Subscribe to the recognizing and recognized events. As the user speaks individual
+# utterances, intermediate recognition results are sent to the recognizing event,
+# and the final recognition results are sent to the recognized event.
+def recognizing(args):
+ print(f'RECOGNIZING: {args.result.text}')
+
+def recognized(args):
+ if args.result.reason.name == 'RecognizedKeyword' and args.result.text != '':
+ print(f'RECOGNIZED: {args.result.text}\n')
+ elif args.result.reason.name == 'RecognizedSpeech' and args.result.text != '':
+ print(f'RECOGNIZED: {args.result.text}\n')
+ elif args.result.reason.name == 'NoMatch':
+ print('NOMATCH: Speech could not be recognized.\n')
+
+speech_recognizer.recognizing.connect(recognizing)
+speech_recognizer.recognized.connect(recognized)
+
+# Create a future to wait for the session to stop. This is needed in console apps to
+# prevent the main thread from terminating while the recognition is running
+# asynchronously on a separate background thread.
+session_stopped_no_error = Future()
+
+# Subscribe to session_started and session_stopped events. These events are useful for
+# logging the start and end of a speech recognition session. In console apps, this is
+# used to allow the application to block the main thread until recognition is complete.
+def session_started(args):
+ print(f'SESSION STARTED: {args.session_id}\n')
+
+def session_stopped(args):
+ print(f'SESSION STOPPED: {args.session_id}')
+
+speech_recognizer.session_started.connect(session_started)
+speech_recognizer.session_stopped.connect(session_stopped)
+
+# Subscribe to the canceled event, which indicates that the recognition operation
+# was stopped/canceled, likely due to an error of some kind.
+def canceled(args):
+ print(f'CANCELED: Reason={args.cancellation_details.reason}')
+
+ # Check the CancellationReason for more detailed information.
+ if args.cancellation_details.reason == CancellationReason.EndOfStream:
+ print('CANCELED: End of the audio stream was reached.')
+ elif args.cancellation_details.reason == CancellationReason.Error:
+ print(f'CANCELED: ErrorDetails={args.cancellation_details.error_details}')
+ print('CANCELED: Did you update the subscription info?')
+
+ # Set the future's result so the main thread can exit
+ session_stopped_no_error.set_result(args.cancellation_details.reason != CancellationReason.Error)
+
+speech_recognizer.canceled.connect(canceled)
+
+# Allow the user to press ENTER to stop recognition
+threading.Thread(target=lambda: (
+ input(''),
+ speech_recognizer.stop_continuous_recognition())
+).start()
+
+# Start keyword recognition
+keyword_model = KeywordRecognitionModel(filename=keyword_file)
+speech_recognizer.start_keyword_recognition(keyword_model)
+print('Listening for keyword; press ENTER to stop ...')
+
+# Wait for the session to stop. result() will not return until the recognition
+# session stops, and the result will indicate whether the session completed
+# or was canceled.
+exit_code = 0 if session_stopped_no_error.result() == True else 1
+os._exit(exit_code)
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-keyword-py/requirements.txt b/src/ai/.x/templates/speech-to-text-with-keyword-py/requirements.txt
new file mode 100644
index 00000000..f33c7861
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-keyword-py/requirements.txt
@@ -0,0 +1 @@
+azure-cognitiveservices-speech>=1.35.0
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-translation-cs/Program.cs b/src/ai/.x/templates/speech-to-text-with-translation-cs/Program.cs
new file mode 100644
index 00000000..e88f2bc2
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-translation-cs/Program.cs
@@ -0,0 +1,145 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".cs" encoding="utf-8" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_ENDPOINT" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_KEY" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_REGION" #>
+using System;
+using Microsoft.CognitiveServices.Speech;
+using Microsoft.CognitiveServices.Speech.Audio;
+using Microsoft.CognitiveServices.Speech.Translation;
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ // Connection and configuration details required
+ var speechKey = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_KEY") ?? "<#= AZURE_AI_SPEECH_KEY #>";
+ var speechRegion = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_REGION") ?? "<#= AZURE_AI_SPEECH_REGION #>";
+ var speechLanguage = "en-US"; // BCP-47 language code
+ var targetLanguages = new string[] { "de", "fr" };
+ var inputFileName = args.Length == 1 ? args[0] : null;
+
+ // Check to see if the input file exists
+ if (inputFileName != null && !File.Exists(inputFileName))
+ {
+ Console.WriteLine($"ERROR: Cannot find audio input file: {inputFileName}");
+ return 1;
+ }
+
+ // Create instances of a speech translation config and audio config
+ var config = SpeechTranslationConfig.FromSubscription(speechKey, speechRegion);
+ var audioConfig = inputFileName != null
+ ? AudioConfig.FromWavFileInput(inputFileName)
+ : AudioConfig.FromDefaultMicrophoneInput();
+
+ // Set the source and target language(s) for translation
+ config.SpeechRecognitionLanguage = speechLanguage;
+ foreach (var targetLanguage in targetLanguages)
+ {
+ config.AddTargetLanguage(targetLanguage);
+ }
+
+ // Create the speech recognizer from the above configuration information
+ using (var recognizer = new TranslationRecognizer(config, audioConfig))
+ {
+ // Subscribe to the Recognizing and Recognized events. As the user speaks individual
+ // utterances, intermediate recognition results are sent to the Recognizing event,
+ // and the final recognition results are sent to the Recognized event.
+ recognizer.Recognizing += (s, e) => HandleRecognizingEvent(e);
+ recognizer.Recognized += (s, e) => HandleRecognizedEvent(e);
+
+ // Create a task completion source to wait for the session to stop. This is needed in
+ // console apps to prevent the main thread from terminating while the recognition is
+ // running asynchronously on a separate background thread.
+ var sessionStoppedNoError = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ // Subscribe to SessionStarted and SessionStopped events. These events are useful for
+ // logging the start and end of a speech recognition session. In console apps, this is
+ // used to allow the application to block the main thread until recognition is complete.
+ recognizer.SessionStarted += (s, e) => HandleSessionStartedEvent(e);
+ recognizer.SessionStopped += (s, e) => HandleSessionStoppedEvent(e, sessionStoppedNoError);
+
+ // Subscribe to the Canceled event, which indicates that the recognition operation
+ // was stopped/canceled, likely due to an error of some kind.
+ recognizer.Canceled += (s, e) => HandleCanceledEvent(e, sessionStoppedNoError);
+
+ // Allow the user to press ENTER to stop recognition
+ Task.Run(() =>
+ {
+ while (Console.ReadKey().Key != ConsoleKey.Enter) { }
+ recognizer.StopContinuousRecognitionAsync();
+ });
+
+ // Start speech recognition
+ await recognizer.StartContinuousRecognitionAsync();
+ Console.WriteLine("Listening; press ENTER to stop ...\n");
+
+ // Wait for the session to stop. The Task will not complete until the recognition
+ // session stops, and the result will indicate whether the session completed
+ // or was canceled.
+ return await sessionStoppedNoError.Task ? 0 : 1;
+ }
+ }
+
+ private static void HandleRecognizingEvent(TranslationRecognitionEventArgs e)
+ {
+ Console.WriteLine($"RECOGNIZING: {e.Result.Text}");
+ foreach (var lang in e.Result.Translations.Keys)
+ {
+ Console.WriteLine($"TRANSLATING into '{lang}': {e.Result.Translations[lang]}");
+ }
+ Console.WriteLine();
+ }
+
+ private static void HandleRecognizedEvent(TranslationRecognitionEventArgs e)
+ {
+ if (e.Result.Reason == ResultReason.TranslatedSpeech && e.Result.Text.Length != 0)
+ {
+ Console.WriteLine($"RECOGNIZED: {e.Result.Text}");
+ foreach (var lang in e.Result.Translations.Keys)
+ {
+ Console.WriteLine($"TRANSLATED into '{lang}': {e.Result.Translations[lang]}");
+ }
+ Console.WriteLine();
+ }
+ else if (e.Result.Reason == ResultReason.RecognizedSpeech && e.Result.Text.Length != 0)
+ {
+ Console.WriteLine($"RECOGNIZED: {e.Result.Text} (text could not be translated)");
+ }
+ else if (e.Result.Reason == ResultReason.NoMatch)
+ {
+ Console.WriteLine($"NOMATCH: Speech could not be recognized.\n");
+ }
+ }
+
+ private static void HandleSessionStartedEvent(SessionEventArgs e)
+ {
+ Console.WriteLine($"SESSION STARTED: {e.SessionId}.\n");
+ }
+
+ private static void HandleSessionStoppedEvent(SessionEventArgs e, TaskCompletionSource sessionStoppedNoError)
+ {
+ Console.WriteLine($"SESSION STOPPED: {e.SessionId}.");
+ sessionStoppedNoError.TrySetResult(true); // Set the result so the main thread can exit
+ }
+
+ private static void HandleCanceledEvent(TranslationRecognitionCanceledEventArgs e, TaskCompletionSource sessionStoppedNoError)
+ {
+ Console.WriteLine($"CANCELED: Reason={e.Reason}");
+
+ // Check the CancellationReason for more detailed information.
+ if (e.Reason == CancellationReason.EndOfStream)
+ {
+ Console.WriteLine($"CANCELED: End of the audio stream was reached.");
+ }
+ else if (e.Reason == CancellationReason.Error)
+ {
+ Console.WriteLine($"CANCELED: ErrorCode={e.ErrorCode}");
+ Console.WriteLine($"CANCELED: ErrorDetails={e.ErrorDetails}");
+ Console.WriteLine($"CANCELED: Did you update the subscription info?");
+ }
+
+ // Set the task completion source result so the main thread can exit
+ sessionStoppedNoError.TrySetResult(e.Reason != CancellationReason.Error);
+ }
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-translation-cs/SpeechToTextWithTranslation.csproj._ b/src/ai/.x/templates/speech-to-text-with-translation-cs/SpeechToTextWithTranslation.csproj._
new file mode 100644
index 00000000..b0925d48
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-translation-cs/SpeechToTextWithTranslation.csproj._
@@ -0,0 +1,17 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".csproj" encoding="utf-8" #>
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ Exe
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-translation-cs/_.json b/src/ai/.x/templates/speech-to-text-with-translation-cs/_.json
new file mode 100644
index 00000000..b788a6fe
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-translation-cs/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Speech-to-text (w/ Translation)",
+ "_ShortName": "speech-to-text-with-translation",
+ "_Language": "C#",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-translation-py/_.json b/src/ai/.x/templates/speech-to-text-with-translation-py/_.json
new file mode 100644
index 00000000..75122daa
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-translation-py/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Speech-to-text (w/ Translation)",
+ "_ShortName": "speech-to-text-with-translation",
+ "_Language": "Python",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-translation-py/main.py b/src/ai/.x/templates/speech-to-text-with-translation-py/main.py
new file mode 100644
index 00000000..71e09edb
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-translation-py/main.py
@@ -0,0 +1,104 @@
+from concurrent.futures import Future
+from azure.cognitiveservices.speech.translation import SpeechTranslationConfig, TranslationRecognizer, ResultReason
+from azure.cognitiveservices.speech import AudioConfig, CancellationReason
+import threading
+import os
+import sys
+
+# Connection and configuration details required
+speech_key = os.environ.get('AZURE_AI_SPEECH_KEY') or ""
+service_region = os.environ.get('AZURE_AI_SPEECH_REGION') or ""
+speech_language = "en-US"
+target_languages = ['de', 'fr']
+input_file = sys.argv[1] if len(sys.argv) == 2 else None
+
+# Check to see if the input file exists
+if input_file is not None and not os.path.exists(input_file):
+ print("ERROR: Cannot find audio input file: {}".format(input_file))
+ sys.exit(1)
+
+# Create instances of a speech translation config and audio config
+speech_config = SpeechTranslationConfig(subscription=speech_key, region=service_region)
+audio_config = AudioConfig(filename=input_file) if input_file is not None else AudioConfig(use_default_microphone=True)
+
+# Set the source and target language(s) for translation
+speech_config.speech_recognition_language = speech_language
+for target_language in target_languages:
+ speech_config.add_target_language(target_language)
+
+# Create the speech recognizer from the above configuration information
+speech_recognizer = TranslationRecognizer(translation_config=speech_config, audio_config=audio_config)
+
+# Subscribe to the Recognizing and Recognized events. As the user speaks individual
+# utterances, intermediate recognition results are sent to the Recognizing event,
+# and the final recognition results are sent to the Recognized event.
+def recognizing(args):
+ print("RECOGNIZING: {}".format(args.result.text))
+ for lang in args.result.translations.keys():
+ print("TRANSLATING into '{}': {}".format(lang, args.result.translations[lang]))
+ print()
+
+def recognized(args):
+ if args.result.reason == ResultReason.TranslatedSpeech and args.result.text:
+ print("RECOGNIZED: {}".format(args.result.text))
+ for lang in args.result.translations.keys():
+ print("TRANSLATED into '{}': {}".format(lang, args.result.translations[lang]))
+ print()
+ elif args.result.reason == ResultReason.RecognizedSpeech and args.result.text:
+ print("RECOGNIZED: {} (text could not be translated)".format(args.result.text))
+ elif args.result.reason == ResultReason.NoMatch:
+ print("NOMATCH: Speech could not be recognized.\n")
+
+speech_recognizer.recognizing.connect(recognizing)
+speech_recognizer.recognized.connect(recognized)
+
+# Create a future to wait for the session to stop. This is needed in console apps to
+# prevent the main thread from terminating while the recognition is running
+# asynchronously on a separate background thread.
+session_stopped_no_error = Future()
+
+# Subscribe to session_started and session_stopped events. These events are useful for
+# logging the start and end of a speech recognition session. In console apps, this is
+# used to allow the application to block the main thread until recognition is complete.
+def session_started(args):
+ print("SESSION STARTED: {}\n".format(args.session_id))
+
+def session_stopped(args):
+ print("SESSION STOPPED: {}".format(args.session_id))
+ session_stopped_no_error.set_result(True)
+
+speech_recognizer.session_started.connect(session_started)
+speech_recognizer.session_stopped.connect(session_stopped)
+
+# Subscribe to the canceled event, which indicates that the recognition operation
+# was stopped/canceled, likely due to an error of some kind.
+def canceled(args):
+ print("CANCELED: Reason={}".format(args.cancellation_details.reason))
+
+ # Check the CancellationReason for more detailed information.
+ if args.cancellation_details.reason == CancellationReason.EndOfStream:
+ print("CANCELED: End of the audio stream was reached.")
+ elif args.cancellation_details.reason == CancellationReason.Error:
+ print("CANCELED: ErrorDetails={}".format(args.cancellation_details.error_details))
+ print("CANCELED: Did you update the subscription info?")
+
+ # Set the future's result so the main thread can exit
+ session_stopped_no_error.set_result(args.cancellation_details.reason != CancellationReason.Error)
+
+speech_recognizer.canceled.connect(canceled)
+
+# Allow the user to press ENTER to stop recognition
+threading.Thread(target=lambda: (
+ input(""),
+ speech_recognizer.stop_continuous_recognition())
+).start()
+
+# Start speech recognition
+speech_recognizer.start_continuous_recognition()
+print("Listening, press ENTER to stop...")
+
+# Wait for the session to stop. result() will not return until the recognition
+# session stops, and the result will indicate whether the session completed
+# or was canceled.
+exit_code = 0 if session_stopped_no_error.result() == True else 1
+os._exit(exit_code)
\ No newline at end of file
diff --git a/src/ai/.x/templates/speech-to-text-with-translation-py/requirements.txt b/src/ai/.x/templates/speech-to-text-with-translation-py/requirements.txt
new file mode 100644
index 00000000..f33c7861
--- /dev/null
+++ b/src/ai/.x/templates/speech-to-text-with-translation-py/requirements.txt
@@ -0,0 +1 @@
+azure-cognitiveservices-speech>=1.35.0
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-cs/Program.cs b/src/ai/.x/templates/text-to-speech-cs/Program.cs
new file mode 100644
index 00000000..5c27ba97
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-cs/Program.cs
@@ -0,0 +1,53 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".cs" encoding="utf-8" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_ENDPOINT" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_KEY" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_REGION" #>
+using System;
+using Microsoft.CognitiveServices.Speech;
+using Microsoft.CognitiveServices.Speech.Audio;
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ // Connection and configuration details required
+ var speechKey = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_KEY") ?? "<#= AZURE_AI_SPEECH_KEY #>";
+ var speechRegion = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_REGION") ?? "<#= AZURE_AI_SPEECH_REGION #>";
+ var voiceName = "en-US-AndrewNeural"; // You can list voice names with `ai speech synthesize --list-voices`
+
+ // Create instances of a speech config and audio config, and set the voice name to use
+ var config = SpeechConfig.FromSubscription(speechKey, speechRegion);
+ var audioConfig = AudioConfig.FromDefaultSpeakerOutput();
+ config.SpeechSynthesisVoiceName = voiceName;
+
+ // Create the speech synthesizer from the above configuration information
+ using (var synthesizer = new SpeechSynthesizer(config, audioConfig))
+ {
+ // Get text from the user to synthesize
+ Console.Write("Enter text: ");
+ var text = Console.ReadLine();
+
+ // Start speech synthesis, and return after it has completed
+ var result = await synthesizer.SpeakTextAsync(text);
+
+ // Check the result
+ if (result.Reason == ResultReason.SynthesizingAudioCompleted)
+ {
+ Console.WriteLine($"SYNTHESIZED: {result.AudioData.Length} byte(s)");
+ }
+ else if (result.Reason == ResultReason.Canceled)
+ {
+ var cancellation = SpeechSynthesisCancellationDetails.FromResult(result);
+ Console.WriteLine($"CANCELED: Reason={cancellation.Reason}");
+
+ if (cancellation.Reason == CancellationReason.Error)
+ {
+ Console.WriteLine($"CANCELED: ErrorCode={cancellation.ErrorCode}");
+ Console.WriteLine($"CANCELED: ErrorDetails={cancellation.ErrorDetails}");
+ Console.WriteLine($"CANCELED: Did you update the subscription info?");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-cs/TextToSpeech.csproj._ b/src/ai/.x/templates/text-to-speech-cs/TextToSpeech.csproj._
new file mode 100644
index 00000000..b0925d48
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-cs/TextToSpeech.csproj._
@@ -0,0 +1,17 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".csproj" encoding="utf-8" #>
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ Exe
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-cs/_.json b/src/ai/.x/templates/text-to-speech-cs/_.json
new file mode 100644
index 00000000..0bc74eeb
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-cs/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Text-to-speech",
+ "_ShortName": "text-to-speech",
+ "_Language": "C#",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-py/_.json b/src/ai/.x/templates/text-to-speech-py/_.json
new file mode 100644
index 00000000..f1cd721c
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-py/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Text-to-speech",
+ "_ShortName": "text-to-speech",
+ "_Language": "Python",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-py/main.py b/src/ai/.x/templates/text-to-speech-py/main.py
new file mode 100644
index 00000000..de07df82
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-py/main.py
@@ -0,0 +1,32 @@
+from azure.cognitiveservices.speech import SpeechConfig, SpeechSynthesizer, SpeechSynthesisResult, SpeechSynthesisCancellationDetails, CancellationReason, ResultReason
+from azure.cognitiveservices.speech.audio import AudioOutputConfig
+import os
+
+# Connection and configuration details required
+speech_key = os.environ.get('AZURE_AI_SPEECH_KEY') or ""
+service_region = os.environ.get('AZURE_AI_SPEECH_REGION') or ""
+voice_name = 'en-US-AndrewNeural'
+
+# Create instances of a speech config and audio config, and set the voice name to use
+speech_config = SpeechConfig(subscription=speech_key, region=service_region)
+speech_config.speech_synthesis_voice_name = voice_name
+audio_config = AudioOutputConfig(use_default_speaker=True)
+
+# Create the speech synthesizer from the above configuration information
+speech_synthesizer = SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)
+
+# Get text from the user to synthesize
+text = input('Enter text: ')
+
+# Start speech synthesis, and return after it has completed
+result = speech_synthesizer.speak_text_async(text).get()
+
+# Check the result
+if result.reason == ResultReason.SynthesizingAudioCompleted:
+ print('SYNTHESIZED: {} byte(s)'.format(len(result.audio_data)))
+elif result.reason == ResultReason.Canceled:
+ cancellation_details = result.cancellation_details
+ print('CANCELED: Reason={}'.format(cancellation_details.reason))
+ if cancellation_details.reason == CancellationReason.Error:
+ print('CANCELED: ErrorDetails={}'.format(cancellation_details.error_details))
+ print('CANCELED: Did you update the subscription info?')
diff --git a/src/ai/.x/templates/text-to-speech-py/requirements.txt b/src/ai/.x/templates/text-to-speech-py/requirements.txt
new file mode 100644
index 00000000..f33c7861
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-py/requirements.txt
@@ -0,0 +1 @@
+azure-cognitiveservices-speech>=1.35.0
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-with-file-cs/Program.cs b/src/ai/.x/templates/text-to-speech-with-file-cs/Program.cs
new file mode 100644
index 00000000..f4b90f2c
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-with-file-cs/Program.cs
@@ -0,0 +1,56 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".cs" encoding="utf-8" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_ENDPOINT" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_KEY" #>
+<#@ parameter type="System.String" name="AZURE_AI_SPEECH_REGION" #>
+using System;
+using Microsoft.CognitiveServices.Speech;
+using Microsoft.CognitiveServices.Speech.Audio;
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ // Connection and configuration details required
+ var speechKey = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_KEY") ?? "<#= AZURE_AI_SPEECH_KEY #>";
+ var speechRegion = Environment.GetEnvironmentVariable("AZURE_AI_SPEECH_REGION") ?? "<#= AZURE_AI_SPEECH_REGION #>";
+ var voiceName = "en-US-AndrewNeural"; // You can list voice names with `ai speech synthesize --list-voices`
+ var outputFileName = "output.wav";
+ var outputFormat = SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm;
+
+ // Create instances of an audio config, a speech config, setting the voice name and output format to use
+ var config = SpeechConfig.FromSubscription(speechKey, speechRegion);
+ var audioConfig = AudioConfig.FromWavFileOutput(outputFileName);
+ config.SetSpeechSynthesisOutputFormat(outputFormat);
+ config.SpeechSynthesisVoiceName = voiceName;
+
+ // Create the speech synthesizer from the above configuration information
+ using (var synthesizer = new SpeechSynthesizer(config, audioConfig))
+ {
+ // Get text from the user to synthesize
+ Console.Write("Enter text: ");
+ var text = Console.ReadLine();
+
+ // Start speech synthesis, and return after it has completed
+ var result = await synthesizer.SpeakTextAsync(text);
+
+ // Check the result
+ if (result.Reason == ResultReason.SynthesizingAudioCompleted)
+ {
+ Console.WriteLine($"SYNTHESIZED: {result.AudioData.Length} byte(s) to {outputFileName}");
+ }
+ else if (result.Reason == ResultReason.Canceled)
+ {
+ var cancellation = SpeechSynthesisCancellationDetails.FromResult(result);
+ Console.WriteLine($"CANCELED: Reason={cancellation.Reason}");
+
+ if (cancellation.Reason == CancellationReason.Error)
+ {
+ Console.WriteLine($"CANCELED: ErrorCode={cancellation.ErrorCode}");
+ Console.WriteLine($"CANCELED: ErrorDetails={cancellation.ErrorDetails}");
+ Console.WriteLine($"CANCELED: Did you update the subscription info?");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-with-file-cs/TextToSpeechWithFile.csproj._ b/src/ai/.x/templates/text-to-speech-with-file-cs/TextToSpeechWithFile.csproj._
new file mode 100644
index 00000000..b0925d48
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-with-file-cs/TextToSpeechWithFile.csproj._
@@ -0,0 +1,17 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".csproj" encoding="utf-8" #>
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ Exe
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-with-file-cs/_.json b/src/ai/.x/templates/text-to-speech-with-file-cs/_.json
new file mode 100644
index 00000000..94a8971c
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-with-file-cs/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Text-to-speech (w/ File output)",
+ "_ShortName": "text-to-speech-with-file",
+ "_Language": "C#",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-with-file-py/_.json b/src/ai/.x/templates/text-to-speech-with-file-py/_.json
new file mode 100644
index 00000000..7c03037e
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-with-file-py/_.json
@@ -0,0 +1,8 @@
+{
+ "_LongName": "Text-to-speech (w/ File output)",
+ "_ShortName": "text-to-speech-with-file",
+ "_Language": "Python",
+ "AZURE_AI_SPEECH_ENDPOINT": "",
+ "AZURE_AI_SPEECH_KEY": "",
+ "AZURE_AI_SPEECH_REGION": ""
+}
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-with-file-py/main.py b/src/ai/.x/templates/text-to-speech-with-file-py/main.py
new file mode 100644
index 00000000..5ecfed72
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-with-file-py/main.py
@@ -0,0 +1,35 @@
+from azure.cognitiveservices.speech import SpeechConfig, SpeechSynthesizer, SpeechSynthesisResult, SpeechSynthesisCancellationDetails, CancellationReason, ResultReason, SpeechSynthesisOutputFormat
+from azure.cognitiveservices.speech.audio import AudioOutputConfig
+import os
+
+# Connection and configuration details required
+speech_key = os.environ.get('AZURE_AI_SPEECH_KEY') or ""
+service_region = os.environ.get('AZURE_AI_SPEECH_REGION') or ""
+voice_name = 'en-US-AndrewNeural'
+output_file_name = 'output.wav'
+output_format = SpeechSynthesisOutputFormat['Riff16Khz16BitMonoPcm']
+
+# Create instances of a speech config and audio config, and set the voice name and output format to use
+speech_config = SpeechConfig(subscription=speech_key, region=service_region)
+speech_config.speech_synthesis_output_format = output_format
+speech_config.speech_synthesis_voice_name = voice_name
+audio_config = AudioOutputConfig(filename=output_file_name)
+
+# Create the speech synthesizer from the above configuration information
+speech_synthesizer = SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)
+
+# Get text from the user to synthesize
+text = input('Enter text: ')
+
+# Start speech synthesis, and return after it has completed
+result = speech_synthesizer.speak_text_async(text).get()
+
+# Check the result
+if result.reason == ResultReason.SynthesizingAudioCompleted:
+ print('SYNTHESIZED: {} byte(s) to {}'.format(len(result.audio_data), output_file_name))
+elif result.reason == ResultReason.Canceled:
+ cancellation_details = result.cancellation_details
+ print('CANCELED: Reason={}'.format(cancellation_details.reason))
+ if cancellation_details.reason == CancellationReason.Error:
+ print('CANCELED: ErrorDetails={}'.format(cancellation_details.error_details))
+ print('CANCELED: Did you update the subscription info?')
\ No newline at end of file
diff --git a/src/ai/.x/templates/text-to-speech-with-file-py/requirements.txt b/src/ai/.x/templates/text-to-speech-with-file-py/requirements.txt
new file mode 100644
index 00000000..f33c7861
--- /dev/null
+++ b/src/ai/.x/templates/text-to-speech-with-file-py/requirements.txt
@@ -0,0 +1 @@
+azure-cognitiveservices-speech>=1.35.0
\ No newline at end of file
diff --git a/src/ai/Program_AI.cs b/src/ai/Program_AI.cs
index b71b31c9..d5dcf72c 100644
--- a/src/ai/Program_AI.cs
+++ b/src/ai/Program_AI.cs
@@ -119,11 +119,11 @@ public AiProgramData()
#endregion
#region help command data
- public string HelpCommandTokens => "wizard;dev;init;config;chat;flow;speech;vision;language;search;service;tool;samples;eval;run";
+ public string HelpCommandTokens => "wizard;dev;test;init;config;chat;flow;speech;vision;language;search;service;tool;samples;eval;run";
#endregion
#region config command data
- public string ConfigScopeTokens => $"wizard;dev;init;chat;flow;speech;vision;language;search;service;tool;samples;eval;run;*";
+ public string ConfigScopeTokens => $"wizard;dev;test;init;chat;flow;speech;vision;language;search;service;tool;samples;eval;run;*";
#endregion
#region zip option data
@@ -187,7 +187,10 @@ public bool DispatchRunCommand(ICommandValues values)
"eval" => (new EvalCommand(values)).RunCommand(),
"wizard" => (new ScenarioWizardCommand(values)).RunCommand(),
"dev" => (new DevCommand(values)).RunCommand(),
+ "test" => (new TestCommand(values)).RunCommand(),
"run" => (new RunJobCommand(values)).RunCommand(),
+ "version" => (new VersionCommand(values)).RunCommand(),
+ "update" => (new VersionCommand(values)).RunCommand(),
_ => false
};
}
@@ -212,7 +215,10 @@ public bool DispatchParseCommand(INamedValueTokens tokens, ICommandValues values
"service" => ServiceCommandParser.ParseCommand(tokens, values),
"tool" => ToolCommandParser.ParseCommand(tokens, values),
"wizard" => ScenarioWizardCommandParser.ParseCommand(tokens, values),
+ "version" => VersionCommandParser.ParseCommand(tokens, values),
+ "update" => UpdateCommandParser.ParseCommand(tokens, values),
"dev" => DevCommandParser.ParseCommand(tokens, values),
+ "test" => TestCommandParser.ParseCommand(tokens, values),
"run" => RunJobCommandParser.ParseCommand(tokens, values),
_ => false
};
@@ -236,7 +242,10 @@ public bool DispatchParseCommandValues(INamedValueTokens tokens, ICommandValues
"tool" => ToolCommandParser.ParseCommandValues(tokens, values),
"wizard" => ScenarioWizardCommandParser.ParseCommandValues(tokens, values),
"dev" => DevCommandParser.ParseCommandValues(tokens, values),
+ "test" => TestCommandParser.ParseCommandValues(tokens, values),
"run" => RunJobCommandParser.ParseCommandValues(tokens, values),
+ "version" => VersionCommandParser.ParseCommandValues(tokens, values),
+ "update" => UpdateCommandParser.ParseCommandValues(tokens, values),
_ => false
};
}
diff --git a/src/ai/ai-cli.csproj b/src/ai/ai-cli.csproj
index 3f6fb4cd..cc598f34 100644
--- a/src/ai/ai-cli.csproj
+++ b/src/ai/ai-cli.csproj
@@ -118,23 +118,25 @@
-
+
-
+
-
+
+
+
diff --git a/src/ai/commands/AiCliTestFrameworkLogger.cs b/src/ai/commands/AiCliTestFrameworkLogger.cs
new file mode 100644
index 00000000..58af85cf
--- /dev/null
+++ b/src/ai/commands/AiCliTestFrameworkLogger.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using Azure.AI.Details.Common.CLI.TestFramework;
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class AiCliTestFrameworkLogger : IYamlTestFrameworkLogger
+ {
+ public void LogVerbose(string text)
+ {
+ AI.TRACE_VERBOSE(text);
+ }
+
+ public void LogInfo(string text)
+ {
+ AI.TRACE_INFO(text);
+ }
+
+ public void LogWarning(string text)
+ {
+ AI.TRACE_WARNING(text);
+ }
+
+ public void LogError(string text)
+ {
+ AI.TRACE_ERROR(text);
+ }
+ }
+}
diff --git a/src/ai/commands/CaptionAudioSpeechToTextQuickStart.cs b/src/ai/commands/CaptionAudioSpeechToTextQuickStart.cs
index df1dff75..de7f4079 100644
--- a/src/ai/commands/CaptionAudioSpeechToTextQuickStart.cs
+++ b/src/ai/commands/CaptionAudioSpeechToTextQuickStart.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
diff --git a/src/ai/commands/chat_command.cs b/src/ai/commands/chat_command.cs
index 3338d274..b048c471 100644
--- a/src/ai/commands/chat_command.cs
+++ b/src/ai/commands/chat_command.cs
@@ -3,27 +3,26 @@
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
-using System;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using System.Net;
-using Azure.AI.OpenAI;
using Azure.AI.Details.Common.CLI.ConsoleGui;
-
+using Azure.AI.Details.Common.CLI.Extensions.HelperFunctions;
+using Azure.AI.OpenAI;
+using Azure.Core.Diagnostics;
+using Microsoft.CognitiveServices.Speech;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch;
using Microsoft.SemanticKernel.Memory;
-using Azure.Core.Diagnostics;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.ClientModel.Primitives;
+using System.Collections.Generic;
using System.Diagnostics.Tracing;
-using Microsoft.CognitiveServices.Speech;
-using Azure.AI.Details.Common.CLI.Extensions.HelperFunctions;
+using System.IO;
+using System.Linq;
using System.Reflection;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
namespace Azure.AI.Details.Common.CLI
{
@@ -375,8 +374,10 @@ private async Task>> GetChatFunctionTextHandler(string
var systemPrompt = _values.GetOrDefault("chat.message.system.prompt", DefaultSystemPrompt);
messages.Add(new ChatRequestSystemMessage(systemPrompt));
- return await Task.Run(() => {
- Func> handler = (string text) => {
+ return await Task.Run(() =>
+ {
+ Func> handler = (string text) =>
+ {
messages.Add(new ChatRequestUserMessage(text));
@@ -391,12 +392,13 @@ private async Task>> GetChatFunctionTextHandler(string
"--parameters", ConvertMessagesToJson(messages)),
addToEnvironment: env);
});
- var questionFunc = new Func(() => {
- return PythonRunner.RunEmbeddedPythonScript(_values, "function_call",
- CliHelpers.BuildCliArgs(
- "--function", function,
- "--parameters", $"{{\"question\": \"{text}\"}}"),
- addToEnvironment: env);
+ var questionFunc = new Func(() =>
+ {
+ return PythonRunner.RunEmbeddedPythonScript(_values, "function_call",
+ CliHelpers.BuildCliArgs(
+ "--function", function,
+ "--parameters", $"{{\"question\": \"{text}\"}}"),
+ addToEnvironment: env);
});
var output = TryCatchHelpers.TryCatchNoThrow(chatProtocolFunc, null, out var ex1);
@@ -481,7 +483,7 @@ private async Task GetSpeechInputAsync()
{
Console.Write("\r");
DisplayUserChatPromptLabel();
- Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.ForegroundColor = ColorHelpers.MapColor(ConsoleColor.DarkGray);
var text = "(listening)";
Console.Write($"{text} ...");
@@ -493,7 +495,7 @@ private async Task GetSpeechInputAsync()
{
Console.Write("\r");
DisplayUserChatPromptLabel();
- Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.ForegroundColor = ColorHelpers.MapColor(ConsoleColor.DarkGray);
Console.Write($"{e.Result.Text} ...");
if (e.Result.Text.Length < lastTextDisplayed.Length) Console.Write(new string(' ', lastTextDisplayed.Length - e.Result.Text.Length));
@@ -590,19 +592,22 @@ private void DisplayAssistantFunctionCall(HelperFunctionCallContext context, str
Console.Write("\rassistant-function");
Console.ForegroundColor = ConsoleColor.White;
Console.Write(": ");
- Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.ForegroundColor = ColorHelpers.MapColor(ConsoleColor.DarkGray);
Console.WriteLine($"{context.FunctionName}({context.Arguments}) = {result}");
DisplayAssistantPromptLabel();
- Console.ForegroundColor = ConsoleColor.Gray;
+ Console.ForegroundColor = ColorHelpers.MapColor(ConsoleColor.DarkGray);
}
}
private async Task> GetChatCompletionsAsync(OpenAIClient client, ChatCompletionsOptions options, HelperFunctionCallContext funcContext, string text)
{
- options.Messages.Add(new ChatRequestUserMessage(text));
-
+ var requestMessage = new ChatRequestUserMessage(text);
+ options.Messages.Add(requestMessage);
+
+ CheckWriteChatHistoryOutputFile(options);
DisplayAssistantPromptLabel();
+
Console.ForegroundColor = ConsoleColor.Gray;
string contentComplete = string.Empty;
@@ -627,13 +632,17 @@ private async Task> GetChatCom
{
DisplayAssistantFunctionCall(funcContext, result);
funcContext.Reset();
+ CheckWriteChatHistoryOutputFile(options);
continue;
}
DisplayAssistantPromptTextStreamingDone();
CheckWriteChatAnswerOutputFile(contentComplete);
- options.Messages.Add(new ChatRequestAssistantMessage(contentComplete));
+ var currentContent = new ChatRequestAssistantMessage(contentComplete);
+ options.Messages.Add(currentContent);
+
+ CheckWriteChatHistoryOutputFile(options);
return response;
}
@@ -649,6 +658,26 @@ private void CheckWriteChatAnswerOutputFile(string completeResponse)
}
}
+ private void ClearMessageHistory()
+ {
+ var outputHistoryFile = OutputChatHistoryFileToken.Data().GetOrDefault(_values);
+ if (!string.IsNullOrEmpty(outputHistoryFile))
+ {
+ var fileName = FileHelpers.GetOutputDataFileName(outputHistoryFile, _values);
+ FileHelpers.WriteAllText(fileName, "", Encoding.UTF8);
+ }
+ }
+
+ private void CheckWriteChatHistoryOutputFile(ChatCompletionsOptions options)
+ {
+ var outputHistoryFile = OutputChatHistoryFileToken.Data().GetOrDefault(_values);
+ if (!string.IsNullOrEmpty(outputHistoryFile))
+ {
+ var fileName = FileHelpers.GetOutputDataFileName(outputHistoryFile, _values);
+ options.SaveChatHistoryToFile(fileName);
+ }
+ }
+
private ChatCompletionsOptions CreateChatCompletionOptions(string deployment)
{
var options = new ChatCompletionsOptions();
@@ -658,6 +687,14 @@ private ChatCompletionsOptions CreateChatCompletionOptions(string deployment)
options.Messages.Add(new ChatRequestSystemMessage(systemPrompt));
var textFile = _values["chat.message.history.text.file"];
+ var jsonFile = InputChatHistoryJsonFileToken.Data().GetOrDefault(_values);
+
+ if(!string.IsNullOrEmpty(jsonFile) && !string.IsNullOrEmpty(textFile))
+ {
+ _values.AddThrowError("chat.message.history.text.file", "chat.message.history.json.file", "Only one of these options can be specified");
+ }
+
+ if (!string.IsNullOrEmpty(jsonFile)) options.ReadChatHistoryFromFile(jsonFile);
if (!string.IsNullOrEmpty(textFile)) AddChatMessagesFromTextFile(options.Messages, textFile);
var maxTokens = _values["chat.options.max.tokens"];
@@ -972,6 +1009,7 @@ private static string ConvertMessagesToJson(IList messages)
var system = message as ChatRequestSystemMessage;
var assistant = message as ChatRequestAssistantMessage;
var content = system?.Content ?? user?.Content ?? assistant?.Content;
+ content = content.Replace("\\", "\u005C").Replace("\"", "");
var ok = !string.IsNullOrEmpty(content);
if (!ok) continue;
@@ -1060,6 +1098,7 @@ private void StopCommand()
// OutputHelper _output = null;
// DisplayHelper _display = null;
+#nullable enable
private int? TryParse(string? s, int? defaultValue)
{
return !string.IsNullOrEmpty(s) && int.TryParse(s, out var parsed) ? parsed : defaultValue;
@@ -1099,78 +1138,80 @@ private float TryParse(string? s, float defaultValue)
return kernelWithACS;
}
+#nullable disable
private async Task StoreMemoryAsync(IKernel kernel, string index)
{
- if (!_quiet) Console.Write("Storing files in semantic memory...");
- var githubFiles = SampleData();
- foreach (var entry in githubFiles)
- {
- await kernel.Memory.SaveReferenceAsync(
- collection: index,
- externalSourceName: "GitHub",
- externalId: entry.Key,
- description: entry.Value,
- text: entry.Value);
-
- if (!_quiet) Console.Write(".");
- }
-
- if (!_quiet) Console.WriteLine(". Done!\n");
+ if (!_quiet) Console.Write("Storing files in semantic memory...");
+ var githubFiles = SampleData();
+ foreach (var entry in githubFiles)
+ {
+ await kernel.Memory.SaveReferenceAsync(
+ collection: index,
+ externalSourceName: "GitHub",
+ externalId: entry.Key,
+ description: entry.Value,
+ text: entry.Value);
+
+ if (!_quiet) Console.Write(".");
+ }
+
+ if (!_quiet) Console.WriteLine(". Done!\n");
}
+#nullable enable
private static async Task SearchMemoryAsync(IKernel? kernel, string collection, string text)
{
- if (kernel == null) return null;
-
- var sb = new StringBuilder();
- var memories = kernel.Memory.SearchAsync(collection, text, limit: 2, minRelevanceScore: 0.5);
- int i = 0;
- await foreach (var memory in memories)
- {
- i++;
- sb.AppendLine($"[{memory.Metadata.Id}]: {memory.Metadata.Description}");
- }
-
- var result = i > 0 ? sb.ToString().Trim() : null;
-
- // Console.ForegroundColor = ConsoleColor.DarkGray;
+ if (kernel == null) return null;
+
+ var sb = new StringBuilder();
+ var memories = kernel.Memory.SearchAsync(collection, text, limit: 2, minRelevanceScore: 0.5);
+ int i = 0;
+ await foreach (var memory in memories)
+ {
+ i++;
+ sb.AppendLine($"[{memory.Metadata.Id}]: {memory.Metadata.Description}");
+ }
+
+ var result = i > 0 ? sb.ToString().Trim() : null;
+ // Console.ForegroundColor = ColorHelpers.MapColor(ConsoleColor.DarkGray);
// Console.WriteLine("Relevant?\n" + result + "\n");
// Console.ResetColor();
- return result;
+ return result;
}
+#nullable disable
private string UpdateUserInputWithSearchResultInfo(string input, string searchResults)
{
- var sb = new StringBuilder();
- sb.Append("The below might be relevant information.\n[START INFO]\n");
- sb.Append(searchResults);
- sb.Append("\n[END INFO]");
- sb.Append("\nEach source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, e.g. [info1.txt]. Don't combine sources, list each source separately, e.g. [info1.txt][info2.pdf].");
- sb.Append($"\n{input}");
- return sb.ToString();
+ var sb = new StringBuilder();
+ sb.Append("The below might be relevant information.\n[START INFO]\n");
+ sb.Append(searchResults);
+ sb.Append("\n[END INFO]");
+ sb.Append("\nEach source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, e.g. [info1.txt]. Don't combine sources, list each source separately, e.g. [info1.txt][info2.pdf].");
+ sb.Append($"\n{input}");
+ return sb.ToString();
}
private static Dictionary SampleData()
{
- return new Dictionary
- {
- ["https://github.com/microsoft/semantic-kernel/blob/main/README.md"]
- = "README: Installation, getting started, and how to contribute",
- ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/02-running-prompts-from-file.ipynb"]
- = "Jupyter notebook describing how to pass prompts from a file to a semantic skill or function",
- ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/00-getting-started.ipynb"]
- = "Jupyter notebook describing how to get started with the Semantic Kernel",
- ["https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT"]
- = "Sample demonstrating how to create a chat skill interfacing with ChatGPT",
- ["https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/VolatileMemoryStore.cs"]
- = "C# class that defines a volatile embedding store",
- ["https://github.com/microsoft/semantic-kernel/blob/main/samples/dotnet/KernelHttpServer/README.md"]
- = "README: How to set up a Semantic Kernel Service API using Azure Function Runtime v4",
- ["https://github.com/microsoft/semantic-kernel/blob/main/samples/apps/chat-summary-webapp-react/README.md"]
- = "README: README associated with a sample chat summary react-based webapp",
- };
+ return new Dictionary
+ {
+ ["https://github.com/microsoft/semantic-kernel/blob/main/README.md"]
+ = "README: Installation, getting started, and how to contribute",
+ ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/02-running-prompts-from-file.ipynb"]
+ = "Jupyter notebook describing how to pass prompts from a file to a semantic skill or function",
+ ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/00-getting-started.ipynb"]
+ = "Jupyter notebook describing how to get started with the Semantic Kernel",
+ ["https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT"]
+ = "Sample demonstrating how to create a chat skill interfacing with ChatGPT",
+ ["https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/VolatileMemoryStore.cs"]
+ = "C# class that defines a volatile embedding store",
+ ["https://github.com/microsoft/semantic-kernel/blob/main/samples/dotnet/KernelHttpServer/README.md"]
+ = "README: How to set up a Semantic Kernel Service API using Azure Function Runtime v4",
+ ["https://github.com/microsoft/semantic-kernel/blob/main/samples/apps/chat-summary-webapp-react/README.md"]
+ = "README: README associated with a sample chat summary react-based webapp",
+ };
}
public const string DefaultSystemPrompt = "You are an AI assistant that helps people find information regarding Azure AI.";
diff --git a/src/ai/commands/dev_command.cs b/src/ai/commands/dev_command.cs
index cda027cb..83dff8da 100644
--- a/src/ai/commands/dev_command.cs
+++ b/src/ai/commands/dev_command.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
@@ -104,37 +104,33 @@ private void DoNewList()
private void DoDevShell()
{
- DisplayBanner("dev.shell");
+ var genericScript = RunCommandScriptToken.Data().GetOrDefault(_values);
+ var bashScript = RunBashScriptToken.Data().GetOrDefault(_values);
- var fileName = !OS.IsWindows() ? "bash" : "cmd.exe";
- var arguments = !OS.IsWindows() ? "-li" : "/k PROMPT (ai dev shell) %PROMPT%& title (ai dev shell)";
+ var genericScriptOk = !string.IsNullOrEmpty(genericScript) ;
+ var bashScriptOk = !string.IsNullOrEmpty(bashScript);
+ var providedBothScripts = genericScriptOk && bashScriptOk;
+ if (providedBothScripts)
+ {
+ _values.AddThrowError("ERROR:", "Cannot specify both --script and --bash");
+ }
+
+ DisplayBanner("dev.shell");
Console.WriteLine("Environment populated:\n");
var env = ConfigEnvironmentHelpers.GetEnvironment(_values);
ConfigEnvironmentHelpers.PrintEnvironment(env);
- ConfigEnvironmentHelpers.SetEnvironment(env);
Console.WriteLine();
- var runCommand = RunCommandScriptToken.Data().GetOrDefault(_values);
-
- // var processOutput = string.IsNullOrEmpty(runCommand)
- // ? ProcessHelpers.RunShellCommandAsync(fileName, arguments, env, null, null, null, false).Result
- // : ProcessHelpers.RunShellCommandAsync(runCommand, env, null, null, null, false).Result;
+ var runScript = genericScriptOk || bashScriptOk;
+ var script = genericScriptOk ? genericScript : bashScript;
- // var exitCode = processOutput.ExitCode;
+ var task = runScript
+ ? ProcessHelpers.RunShellScriptAsync(script, scriptIsBash: bashScriptOk, env, null, null, null, false)
+ : ProcessHelpers.RunShellInteractiveAsync(env, null, null, null, false);
- UpdateFileNameArguments(runCommand, ref fileName, ref arguments, out var deleteWhenDone);
-
- var process = ProcessHelpers.StartProcess(fileName, arguments, env, false);
- process.WaitForExit();
-
- if (!string.IsNullOrEmpty(deleteWhenDone))
- {
- File.Delete(deleteWhenDone);
- }
-
- var exitCode = process.ExitCode;
+ var exitCode = task.Result.ExitCode;
if (exitCode != 0)
{
Console.WriteLine("\n(ai dev shell) FAILED!\n");
@@ -146,41 +142,6 @@ private void DoDevShell()
}
}
- private static void UpdateFileNameArguments(string runCommand, ref string fileName, ref string arguments, out string? deleteTempFileWhenDone)
- {
- deleteTempFileWhenDone = null;
-
- if (!string.IsNullOrEmpty(runCommand))
- {
- var isSingleLine = !runCommand.Contains('\n') && !runCommand.Contains('\r');
- if (isSingleLine)
- {
- var parts = runCommand.Split(new char[] { ' ' }, 2);
- var inPath = FileHelpers.FileExistsInOsPath(parts[0]) || (OS.IsWindows() && FileHelpers.FileExistsInOsPath(parts[0] + ".exe"));
-
- var filePart = parts[0];
- var argsPart = parts.Length == 2 ? parts[1] : null;
-
- fileName = inPath ? filePart : fileName;
- arguments = inPath ? argsPart : (OS.IsLinux()
- ? $"-lic \"{runCommand}\""
- : $"/c \"{runCommand}\"");
-
- Console.WriteLine($"Running command: {runCommand}\n");
- }
- else
- {
- deleteTempFileWhenDone = Path.GetTempFileName() + (OS.IsWindows() ? ".cmd" : ".sh");
- File.WriteAllText(deleteTempFileWhenDone, runCommand);
-
- fileName = OS.IsLinux() ? "bash" : "cmd.exe";
- arguments = OS.IsLinux() ? $"-lic \"{deleteTempFileWhenDone}\"" : $"/c \"{deleteTempFileWhenDone}\"";
-
- Console.WriteLine($"Running script:\n\n{runCommand}\n");
- }
- }
- }
-
private void DisplayBanner(string which)
{
if (_quiet) return;
diff --git a/src/ai/commands/eval_command.cs b/src/ai/commands/eval_command.cs
index ed6d653b..813baf42 100644
--- a/src/ai/commands/eval_command.cs
+++ b/src/ai/commands/eval_command.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
diff --git a/src/ai/commands/flow_command.cs b/src/ai/commands/flow_command.cs
index 58564a4f..7218ea90 100644
--- a/src/ai/commands/flow_command.cs
+++ b/src/ai/commands/flow_command.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
diff --git a/src/ai/commands/language_command.cs b/src/ai/commands/language_command.cs
index d81b7f4d..5abf565a 100644
--- a/src/ai/commands/language_command.cs
+++ b/src/ai/commands/language_command.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
diff --git a/src/ai/commands/parsers/chat_command_parser.cs b/src/ai/commands/parsers/chat_command_parser.cs
index f29ee82f..6a135744 100644
--- a/src/ai/commands/parsers/chat_command_parser.cs
+++ b/src/ai/commands/parsers/chat_command_parser.cs
@@ -120,6 +120,8 @@ public CommonChatNamedValueTokenParsers() : base(
new TrueFalseNamedValueTokenParser("chat.speech.input", "010"),
OutputChatAnswerFileToken.Parser(),
+ OutputChatHistoryFileToken.Parser(),
+ InputChatHistoryJsonFileToken.Parser(),
// new TrueFalseRequiredPrefixNamedValueTokenParser("output", "all.answer", "01"),
// new TrueFalseRequiredPrefixNamedValueTokenParser("output", "each.answer", "11")
diff --git a/src/ai/commands/parsers/dev_command_parser.cs b/src/ai/commands/parsers/dev_command_parser.cs
index 5ddc0ab4..c4152b4e 100644
--- a/src/ai/commands/parsers/dev_command_parser.cs
+++ b/src/ai/commands/parsers/dev_command_parser.cs
@@ -84,7 +84,8 @@ public CommonDevNamedValueTokenParsers() : base(
private static INamedValueTokenParser[] _devShellParsers = {
new CommonDevNamedValueTokenParsers(),
- RunCommandScriptToken.Parser()
+ RunCommandScriptToken.Parser(),
+ RunBashScriptToken.Parser()
};
}
}
diff --git a/src/ai/commands/parsers/speech_command_parser.cs b/src/ai/commands/parsers/speech_command_parser.cs
index 3e662eb2..9f1e0186 100644
--- a/src/ai/commands/parsers/speech_command_parser.cs
+++ b/src/ai/commands/parsers/speech_command_parser.cs
@@ -106,22 +106,23 @@ private static readonly (string name, bool valuesRequired)[] _commands = {
private static IEnumerable GetCommandParsers(ICommandValues values)
{
- var commandName = values.GetCommand();
- switch (commandName)
+ var check = string.Join(".", values.GetCommand()
+ .Split('.')
+ .Take(2)
+ .ToArray());
+
+ switch (check)
{
- case "speech.recognize": return speechRecognizeParsers;
- case "speech.synthesize": return speechSynthesizeParsers;
- case "speech.intent": return speechIntentParsers;
- case "speech.translate": return speechTranslateParsers;
- case "speech.batch": return speechBatchParsers;
- case "speech.csr": return speechCsrParsers;
- case "speech.profile": return speechProfileParsers;
- case "speech.speaker": return speechSpeakerParsers;
+ case "speech.recognize": return RecognizeCommandParser.GetCommandParsers();
+ case "speech.synthesize": return SynthesizeCommandParser.GetCommandParsers();
+ case "speech.intent": return IntentCommandParser.GetCommandParsers();
+ case "speech.translate": return TranslateCommandParser.GetCommandParsers();
+ case "speech.batch": return BatchCommandParser.GetCommandParsers(values);
+ case "speech.csr": return CustomSpeechRecognitionCommandParser.GetCommandParsers(values);
+ case "speech.profile": return ProfileCommandParser.GetCommandParsers(values);
+ case "speech.speaker": return ProfileCommandParser.GetCommandParsers(values);
}
- if (commandName.StartsWith("speech.batch")) return speechBatchParsers;
- if (commandName.StartsWith("speech.csr")) return speechCsrParsers;
-
return null;
}
diff --git a/src/ai/commands/parsers/test_command_parser.cs b/src/ai/commands/parsers/test_command_parser.cs
new file mode 100644
index 00000000..925073ca
--- /dev/null
+++ b/src/ai/commands/parsers/test_command_parser.cs
@@ -0,0 +1,95 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Collections.Generic;
+
+namespace Azure.AI.Details.Common.CLI
+{
+ class TestCommandParser : CommandParser
+ {
+ public static bool ParseCommand(INamedValueTokens tokens, ICommandValues values)
+ {
+ return ParseCommands(_commands, _partialCommands, tokens, values, x => GetCommandParsers(x));
+ }
+
+ public static bool ParseCommandValues(INamedValueTokens tokens, ICommandValues values)
+ {
+ return ParseCommandValues("test", GetCommandParsers(values), tokens, values);
+ }
+
+ private static readonly (string name, bool valuesRequired)[] _commands = {
+ ("test.list", false),
+ ("test.run", false),
+ };
+
+ private static readonly string[] _partialCommands = {
+ "test"
+ };
+
+ private static IEnumerable GetCommandParsers(ICommandValues values)
+ {
+ var commandName = values.GetCommand();
+
+ switch (commandName)
+ {
+ case "test.list": return _testListParsers;
+ case "test.run": return _testRunParsers;
+ }
+
+ foreach (var command in _commands)
+ {
+ if (commandName == command.name)
+ {
+ return _testPlaceHolderParsers;
+ }
+ }
+
+ return null;
+ }
+
+ public class CommonTestNamedValueTokenParsers : NamedValueTokenParserList
+ {
+ public CommonTestNamedValueTokenParsers() : base(
+
+ new NamedValueTokenParser(null, "x.command", "11", "1"),
+
+ new ExpectOutputTokenParser(),
+ new DiagnosticLogTokenParser(),
+ new CommonNamedValueTokenParsers(),
+
+ new NamedValueTokenParser("--ini", "ini.file", "10", "1", "@"),
+
+ new NamedValueTokenParser(null, "x.command.expand.file.name", "11111", "1"),
+
+ FileOptionXToken.Parser(),
+ FilesOptionXToken.Parser(),
+
+ TestOptionXToken.Parser(),
+ TestsOptionXToken.Parser(),
+
+ ContainsOptionXToken.Parser(),
+ RemoveOptionXToken.Parser(),
+
+ SearchOptionXToken.Parser()
+ )
+ {
+ }
+ }
+
+ private static INamedValueTokenParser[] _testPlaceHolderParsers = {
+ new CommonTestNamedValueTokenParsers()
+ };
+
+ private static INamedValueTokenParser[] _testListParsers = {
+ new CommonTestNamedValueTokenParsers()
+ };
+
+ private static INamedValueTokenParser[] _testRunParsers = {
+ new CommonTestNamedValueTokenParsers(),
+ OutputResultsFormatToken.Parser(),
+ OutputResultsFileToken.Parser()
+ };
+ }
+}
diff --git a/src/ai/commands/parsers/update_command_parser.cs b/src/ai/commands/parsers/update_command_parser.cs
new file mode 100644
index 00000000..1802bdd1
--- /dev/null
+++ b/src/ai/commands/parsers/update_command_parser.cs
@@ -0,0 +1,71 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System;
+using System.Collections.Generic;
+
+namespace Azure.AI.Details.Common.CLI
+{
+ class UpdateCommandParser : CommandParser
+ {
+ public static bool ParseCommand(INamedValueTokens tokens, ICommandValues values)
+ {
+ values.Add("display.update", "true");
+ return ParseCommands(_commands, _partialCommands, tokens, values, x => GetCommandParsers(x));
+ }
+
+ public static bool ParseCommandValues(INamedValueTokens tokens, ICommandValues values)
+ {
+ return ParseCommandValues("update", GetCommandParsers(values), tokens, values);
+ }
+
+ private static readonly (string name, bool valuesRequired)[] _commands = {
+ ("update", false),
+ };
+
+ private static readonly string[] _partialCommands = {
+ };
+
+ private static IEnumerable GetCommandParsers(ICommandValues values)
+ {
+ var commandName = values.GetCommand();
+
+ switch (commandName)
+ {
+ case "update": return _updateCommandParsers;
+ }
+
+ return null;
+ }
+
+
+ #region private data
+
+ public class CommonUpdateNamedValueTokenParsers : NamedValueTokenParserList
+ {
+ public CommonUpdateNamedValueTokenParsers() : base(
+
+ new NamedValueTokenParser(null, "x.command", "11", "1", "update"),
+
+ new ExpectOutputTokenParser(),
+ new DiagnosticLogTokenParser(),
+ new CommonNamedValueTokenParsers(),
+
+ new NamedValueTokenParser("--ini", "ini.file", "10", "1", "@"),
+ new NamedValueTokenParser(null, "x.command.expand.file.name", "11111", "1")
+ )
+ {
+ }
+ }
+
+ private static INamedValueTokenParser[] _updateCommandParsers = {
+
+ new CommonUpdateNamedValueTokenParsers(),
+
+ };
+
+ #endregion
+ }
+}
diff --git a/src/ai/commands/parsers/version_command_parser.cs b/src/ai/commands/parsers/version_command_parser.cs
new file mode 100644
index 00000000..78ccb2cd
--- /dev/null
+++ b/src/ai/commands/parsers/version_command_parser.cs
@@ -0,0 +1,71 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System;
+using System.Collections.Generic;
+
+namespace Azure.AI.Details.Common.CLI
+{
+ class VersionCommandParser : CommandParser
+ {
+ public static bool ParseCommand(INamedValueTokens tokens, ICommandValues values)
+ {
+ values.Add("display.version", "true");
+ return ParseCommands(_commands, _partialCommands, tokens, values, x => GetCommandParsers(x));
+ }
+
+ public static bool ParseCommandValues(INamedValueTokens tokens, ICommandValues values)
+ {
+ return ParseCommandValues("version", GetCommandParsers(values), tokens, values);
+ }
+
+ private static readonly (string name, bool valuesRequired)[] _commands = {
+ ("version", false),
+ };
+
+ private static readonly string[] _partialCommands = {
+ };
+
+ private static IEnumerable GetCommandParsers(ICommandValues values)
+ {
+ var commandName = values.GetCommand();
+
+ switch (commandName)
+ {
+ case "version": return _versionCommandParsers;
+ }
+
+ return null;
+ }
+
+
+ #region private data
+
+ public class CommonVersionNamedValueTokenParsers : NamedValueTokenParserList
+ {
+ public CommonVersionNamedValueTokenParsers() : base(
+
+ new NamedValueTokenParser(null, "x.command", "11", "1", "version"),
+
+ new ExpectOutputTokenParser(),
+ new DiagnosticLogTokenParser(),
+ new CommonNamedValueTokenParsers(),
+
+ new NamedValueTokenParser("--ini", "ini.file", "10", "1", "@"),
+ new NamedValueTokenParser(null, "x.command.expand.file.name", "11111", "1")
+ )
+ {
+ }
+ }
+
+ private static INamedValueTokenParser[] _versionCommandParsers = {
+
+ new CommonVersionNamedValueTokenParsers(),
+
+ };
+
+ #endregion
+ }
+}
diff --git a/src/ai/commands/parsers/vision_command_parser.cs b/src/ai/commands/parsers/vision_command_parser.cs
index 549bc14a..d1b07aa6 100644
--- a/src/ai/commands/parsers/vision_command_parser.cs
+++ b/src/ai/commands/parsers/vision_command_parser.cs
@@ -62,12 +62,11 @@ private static readonly (string name, bool valuesRequired)[] _commands = {
private static IEnumerable GetCommandParsers(ICommandValues values)
{
var commandName = values.GetCommand();
- foreach (var command in _commands)
+ switch (commandName)
{
- if (commandName == command.name)
- {
- return _visionPlaceHolderParsers;
- }
+ case "vision.image":
+ case "vision.image.analyze":
+ return imageAnalyzeParsers;
}
return null;
@@ -84,7 +83,10 @@ public CommonVisionNamedValueTokenParsers() : base(
new CommonNamedValueTokenParsers(),
new NamedValueTokenParser("--ini", "ini.file", "10", "1", "@"),
new NamedValueTokenParser(null, "x.command.expand.file.name", "11111", "1"),
-
+ new NamedValueTokenParser(null, "service.config.endpoint.uri", "0011", "1"),
+ new NamedValueTokenParser("--url", "vision.input.url", "001", "1", null, null, "url", "vision.input.type"),
+ new NamedValueTokenParser("--file", "vision.input.file", "001", "1", null, null, "file", "vision.input.type"),
+ new NamedValueTokenParser("--outputtype", "vision.output.type", "011", "1"),
new ExpectConsoleOutputTokenParser(),
new DiagnosticLogTokenParser()
@@ -99,6 +101,15 @@ public CommonVisionNamedValueTokenParsers() : base(
};
+ private static INamedValueTokenParser[] imageAnalyzeParsers = [
+ new CommonVisionNamedValueTokenParsers(),
+ new NamedValueTokenParser("--language", "vision.image.language", "001", "1"),
+ new NamedValueTokenParser("--gender-neutral-captions", "vision.image.gender.neutral.captions", "00111", "1"),
+ new NamedValueTokenParser("--smart-crop-aspect-ratios", "vision.image.smart.crop.aspect.ratios", "001111", "1"),
+ new NamedValueTokenParser("--model-version", "vision.image.model.version", "0011", "1"),
+ new NamedValueTokenParser("--visual-features", "vision.image.visual.features", "0011", "1")
+ ];
+
#endregion
}
}
diff --git a/src/ai/commands/scenario_wizard_command.cs b/src/ai/commands/scenario_wizard_command.cs
index 4dc89db4..01735cb9 100644
--- a/src/ai/commands/scenario_wizard_command.cs
+++ b/src/ai/commands/scenario_wizard_command.cs
@@ -385,6 +385,7 @@ private static bool ListBoxPickYesNo()
return (picked == 0);
}
+#nullable enable
private IKernel? CreateSemanticKernel(string searchEndpoint, string searchApiKey, string embeddingsEndpoint, string embeddingsDeployment, string embeddingsApiKey)
{
var store = new AzureCognitiveSearchMemoryStore(searchEndpoint, searchApiKey);
@@ -395,6 +396,7 @@ private static bool ListBoxPickYesNo()
return kernelWithACS;
}
+#nullable disable
private static async Task StoreMemoryAsync(IKernel kernel, string index, IEnumerable> kvps)
{
diff --git a/src/ai/commands/search_command.cs b/src/ai/commands/search_command.cs
index 445e216d..b35bfa67 100644
--- a/src/ai/commands/search_command.cs
+++ b/src/ai/commands/search_command.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
@@ -161,7 +161,11 @@ private void DoIndexUpdate()
private string DoIndexUpdateWithGenAi(string subscription, string groupName, string projectName, string indexName, string embeddingModelDeployment, string embeddingModelName, string dataFiles, string externalSourceUrl)
{
- return PythonSDKWrapper.UpdateMLIndex(_values, subscription, groupName, projectName, indexName, embeddingModelDeployment, embeddingModelName, dataFiles, externalSourceUrl);
+ // work around issue with Py GenAI SDK needing this var to be set; do not set any additional values... See Hanchi Wang for more info.
+ var env = ConfigEnvironmentHelpers.GetEnvironment(_values);
+ env = new Dictionary(env.Where(x => x.Key == "AZURE_OPENAI_KEY"));
+
+ return PythonSDKWrapper.UpdateMLIndex(_values, subscription, groupName, projectName, indexName, embeddingModelDeployment, embeddingModelName, dataFiles, externalSourceUrl, env);
}
private async Task DoIndexUpdateWithAISearch(string aiServicesApiKey, string searchEndpoint, string searchApiKey, string embeddingsEndpoint, string embeddingsDeployment, string embeddingsApiKey, string searchIndexName, string dataSourceConnectionName, string blobContainer, string pattern, string skillsetName, string indexerName, string idFieldName, string contentFieldName, string vectorFieldName)
@@ -490,6 +494,7 @@ private void DoIndexUpdateWithSK(string searchEndpoint, string searchApiKey, str
StoreMemoryAsync(kernel, searchIndexName, kvps).Wait();
}
+#nullable enable
private IKernel? CreateSemanticKernel(string searchEndpoint, string searchApiKey, string embeddingsEndpoint, string embeddingsDeployment, string embeddingsApiKey)
{
var store = new AzureCognitiveSearchMemoryStore(searchEndpoint, searchApiKey);
@@ -500,6 +505,7 @@ private void DoIndexUpdateWithSK(string searchEndpoint, string searchApiKey, str
return kernelWithACS;
}
+#nullable disable
private static async Task StoreMemoryAsync(IKernel kernel, string index, IEnumerable> kvps)
{
diff --git a/src/ai/commands/service_command.cs b/src/ai/commands/service_command.cs
index b5409ad5..eac86f47 100644
--- a/src/ai/commands/service_command.cs
+++ b/src/ai/commands/service_command.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
diff --git a/src/ai/commands/speech_command.cs b/src/ai/commands/speech_command.cs
index 3ae412d9..2be25fc4 100644
--- a/src/ai/commands/speech_command.cs
+++ b/src/ai/commands/speech_command.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
@@ -53,8 +53,42 @@ private void DoCommand(string command)
{
CheckPath();
- switch (command)
+ var check = string.Join(".", command
+ .Split('.')
+ .Take(2)
+ .ToArray());
+
+ switch (check)
{
+ case "speech.synthesize":
+ new SynthesizeCommand(_values).RunCommand();
+ break;
+
+ case "speech.recognize":
+ new RecognizeCommand(_values).RunCommand();
+ break;
+
+ case "speech.intent":
+ new IntentCommand(_values).RunCommand();
+ break;
+
+ case "speech.translate":
+ new TranslateCommand(_values).RunCommand();
+ break;
+
+ case "speech.batch":
+ new BatchCommand(_values).RunCommand();
+ break;
+
+ case "speech.csr":
+ new CustomSpeechRecognitionCommand(_values).RunCommand();
+ break;
+
+ case "speech.profile":
+ case "speech.speaker":
+ new ProfileCommand(_values).RunCommand();
+ break;
+
default:
_values.AddThrowError("WARNING:", $"'{command.Replace('.', ' ')}' NOT YET IMPLEMENTED!!");
break;
diff --git a/src/ai/commands/test_command.cs b/src/ai/commands/test_command.cs
new file mode 100644
index 00000000..1158849a
--- /dev/null
+++ b/src/ai/commands/test_command.cs
@@ -0,0 +1,246 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using Azure.AI.Details.Common.CLI.Extensions.Templates;
+using Azure.AI.Details.Common.CLI.TestFramework;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class TestCommand : Command
+ {
+ internal TestCommand(ICommandValues values)
+ {
+ _values = values.ReplaceValues();
+ _quiet = _values.GetOrDefault("x.quiet", false);
+ _verbose = _values.GetOrDefault("x.verbose", false);
+ }
+
+ internal bool RunCommand()
+ {
+ try
+ {
+ RunTestCommand();
+ }
+ catch (WebException ex)
+ {
+ ConsoleHelpers.WriteLineError($"\n ERROR: {ex.Message}");
+ JsonHelpers.PrintJson(HttpHelpers.ReadWriteJson(ex.Response, _values, "test"));
+ }
+
+ return _values.GetOrDefault("passed", true);
+ }
+
+ private bool RunTestCommand()
+ {
+ DoCommand(_values.GetCommand());
+ return _values.GetOrDefault("passed", true);
+ }
+
+ private void DoCommand(string command)
+ {
+ StartCommand();
+
+ switch (command)
+ {
+ case "test.list": DoTestList(); break;
+ case "test.run": DoTestRun(); break;
+
+ default:
+ _values.AddThrowError("WARNING:", $"'{command.Replace('.', ' ')}' NOT YET IMPLEMENTED!!");
+ break;
+ }
+
+ StopCommand();
+ DisposeAfterStop();
+ DeleteTemporaryFiles();
+ }
+
+ private void DoTestList()
+ {
+ var tests = FindAndFilterTests();
+
+ Console.ForegroundColor = ColorHelpers.MapColor(ConsoleColor.DarkGray);
+ foreach (var test in tests)
+ {
+ Console.WriteLine(test.FullyQualifiedName);
+ }
+ Console.ResetColor();
+
+ if (!_quiet)
+ {
+ Console.WriteLine(tests.Count() == 1
+ ? $"\nFound {tests.Count()} test..."
+ : $"\nFound {tests.Count()} tests...");
+ }
+ }
+
+ private void DoTestRun()
+ {
+ var tests = FindAndFilterTests();
+
+ if (!_quiet)
+ {
+ Console.WriteLine(tests.Count() == 1
+ ? $"Found {tests.Count()} test...\n"
+ : $"Found {tests.Count()} tests...\n");
+ }
+
+ var consoleHost = new YamlTestFrameworkConsoleHost();
+ var resultsByTestCaseId = YamlTestFramework.RunTests(tests, consoleHost);
+
+ GetOutputFileAndFormat(out var file, out var format);
+ consoleHost.Finish(resultsByTestCaseId, format, file);
+ }
+
+ private IList FindAndFilterTests()
+ {
+ var files = FindTestFiles();
+ var filters = GetTestFilters();
+
+ var atLeastOneFileSpecified = files.Any();
+ var tests = atLeastOneFileSpecified
+ ? files.SelectMany(file => YamlTestFramework.GetTestsFromYaml(file.FullName, file))
+ : YamlTestFramework.GetTestsFromDirectory("ai test", new DirectoryInfo("."));
+
+ var filtered = YamlTestCaseFilter.FilterTestCases(tests, filters).ToList();
+
+ if (tests.Count() == 0)
+ {
+ _values.AddThrowError("WARNING:", !atLeastOneFileSpecified
+ ? "No tests found"
+ : files.Count() == 1
+ ? $"No tests found in {files.Count()} file"
+ : $"No tests found in {files.Count()} files");
+ }
+
+ if (filtered.Count() == 0)
+ {
+ Console.WriteLine(atLeastOneFileSpecified
+ ? $"Found {tests.Count()} tests in {files.Count()} files\n"
+ : $"Found {tests.Count()} tests\n");
+
+ _values.AddThrowError("WARNING:", "No tests matching criteria.");
+ }
+
+ return filtered;
+ }
+
+ private List GetTestFilters()
+ {
+ var filters = new List();
+
+ var options = SearchOptionXToken.GetOptions(_values).ToList();
+ options.AddRange(TestOptionXToken.GetOptions(_values).ToList());
+ options.AddRange(TestsOptionXToken.GetOptions(_values));
+ foreach (var item in options)
+ {
+ filters.Add(item);
+ }
+
+ options = ContainsOptionXToken.GetOptions(_values).ToList();
+ foreach (var item in options)
+ {
+ filters.Add($"+{item}");
+ }
+
+ options = RemoveOptionXToken.GetOptions(_values).ToList();
+ foreach (var item in options)
+ {
+ filters.Add($"-{item}");
+ }
+
+ return filters;
+ }
+
+ private List FindTestFiles()
+ {
+ var files = new List();
+
+ var options = FileOptionXToken.GetOptions(_values).ToList();
+ options.AddRange(FilesOptionXToken.GetOptions(_values).ToList());
+ foreach (var item in options)
+ {
+ var patterns = item.Split(';', StringSplitOptions.RemoveEmptyEntries);
+ foreach (var pattern in patterns)
+ {
+ AddFindFiles(files, pattern);
+ }
+ }
+
+ return files;
+ }
+
+ private void AddFindFiles(List filesAsList, string pattern)
+ {
+ var found = FindFiles(pattern);
+ if (found.Count() == 0)
+ {
+ _values.AddThrowError("WARNING:", $"No files found: {pattern}");
+ }
+ filesAsList.AddRange(found);
+ }
+
+ private static IList FindFiles(string pattern)
+ {
+ var files = FileHelpers.FindFiles(Directory.GetCurrentDirectory(), pattern, null, false, false);
+ return files.Select(x => new FileInfo(x)).ToList();
+ }
+
+ private void GetOutputFileAndFormat(out string file, out string format)
+ {
+ format = OutputResultsFormatToken.Data().GetOrDefault(_values, "trx");
+ var ext = format switch
+ {
+ "trx" => "trx",
+ "junit" => "xml",
+ _ => throw new Exception($"Unknown format: {format}")
+ };
+
+ file = OutputResultsFileToken.Data().GetOrDefault(_values, null);
+ file ??= $"test-results.{ext}";
+ if (!file.EndsWith($".{ext}"))
+ {
+ file += $".{ext}";
+ }
+ }
+
+ private void StartCommand()
+ {
+ CheckPath();
+ LogHelpers.EnsureStartLogFile(_values);
+ Logger.Log(new AiCliTestFrameworkLogger());
+
+ // _display = new DisplayHelper(_values);
+
+ // _output = new OutputHelper(_values);
+ // _output.StartOutput();
+
+ _lock = new SpinLock();
+ _lock.StartLock();
+ }
+
+ private void StopCommand()
+ {
+ _lock.StopLock(5000);
+
+ // LogHelpers.EnsureStopLogFile(_values);
+ // _output.CheckOutput();
+ // _output.StopOutput();
+
+ _stopEvent.Set();
+ }
+
+ private SpinLock _lock = null;
+ private readonly bool _quiet;
+ private readonly bool _verbose;
+ }
+}
diff --git a/src/ai/commands/version_command.cs b/src/ai/commands/version_command.cs
new file mode 100644
index 00000000..1deedeb7
--- /dev/null
+++ b/src/ai/commands/version_command.cs
@@ -0,0 +1,97 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using Azure.AI.Details.Common.CLI.ConsoleGui;
+using Azure.AI.Details.Common.CLI.Extensions.HelperFunctions;
+using Azure.Core.Diagnostics;
+using System;
+using System.Diagnostics.Tracing;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class VersionCommand : Command
+ {
+ internal VersionCommand(ICommandValues values)
+ {
+ _values = values.ReplaceValues();
+ _quiet = _values.GetOrDefault("x.quiet", false);
+ _verbose = _values.GetOrDefault("x.verbose", true);
+ }
+
+ internal bool RunCommand()
+ {
+ DoCommand(_values.GetCommand());
+ return _values.GetOrDefault("passed", true);
+ }
+
+ private void DoCommand(string command)
+ {
+ StartCommand();
+
+ switch (command)
+ {
+ case "version":
+ case "update":
+ DoVersionUpdate();
+ break;
+
+ default:
+ _values.AddThrowError("WARNING:", $"'{command.Replace('.', ' ')}' NOT YET IMPLEMENTED!!");
+ break;
+ }
+
+ StopCommand();
+ DisposeAfterStop();
+ DeleteTemporaryFiles();
+ }
+ private void StartCommand()
+ {
+ CheckPath();
+ // CheckChatInput();
+ LogHelpers.EnsureStartLogFile(_values);
+
+ // _display = new DisplayHelper(_values);
+
+ // _output = new OutputHelper(_values);
+ // _output.StartOutput();
+
+ // var id = _values["chat.input.id"];
+ // _output.EnsureOutputAll("chat.input.id", id);
+ // _output.EnsureOutputEach("chat.input.id", id);
+
+ _lock = new SpinLock();
+ _lock.StartLock();
+ }
+
+ private void StopCommand()
+ {
+ _lock.StopLock(5000);
+
+ LogHelpers.EnsureStopLogFile(_values);
+ // _output.CheckOutput();
+ // _output.StopOutput();
+
+ _stopEvent.Set();
+ }
+
+ private SpinLock _lock = null;
+ private readonly bool _quiet = false;
+ private readonly bool _verbose = false;
+
+ // private AzureEventSourceListener _azureEventSourceListener;
+
+ // OutputHelper _output = null;
+ // DisplayHelper _display = null;
+
+ private void DoVersionUpdate()
+ {
+ // Handled by adding "display.help" to values in parser
+ }
+ }
+}
diff --git a/src/ai/commands/vision_command.cs b/src/ai/commands/vision_command.cs
index f03d6f93..cb3871c9 100644
--- a/src/ai/commands/vision_command.cs
+++ b/src/ai/commands/vision_command.cs
@@ -5,17 +5,10 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
-using Newtonsoft.Json.Linq;
+using Azure.AI.Vision.ImageAnalysis;
namespace Azure.AI.Details.Common.CLI
{
@@ -55,12 +48,147 @@ private void DoCommand(string command)
switch (command)
{
+ case "vision.image.analyze":
+ RunImageAnalyzeCommand(_values);
+ break;
default:
_values.AddThrowError("WARNING:", $"'{command.Replace('.', ' ')}' NOT YET IMPLEMENTED!!");
break;
}
}
+ private void RunImageAnalyzeCommand(ICommandValues values)
+ {
+ var featuresString = _values["vision.image.visual.features"];
+ var featuresSplit = featuresString.Split(',');
+ VisualFeatures features = VisualFeatures.None;
+ foreach (var feature in featuresSplit)
+ {
+ features |= (VisualFeatures)Enum.Parse(typeof(VisualFeatures), feature, true);
+ }
+
+ var client = new ImageAnalysisClient(new Uri(_values["service.config.endpoint.uri"]), new AzureKeyCredential(_values["service.config.key"]));
+
+ var options = BuildIAOptions(values);
+
+ ImageAnalysisResult result = null;
+ switch (_values["vision.input.type"])
+ {
+ case "url":
+ var url = _values["vision.input.url"];
+ result = client.Analyze(new Uri(url), features, options);
+ break;
+ case "file":
+ var path = _values["vision.input.file"];
+ result = client.Analyze(BinaryData.FromBytes(System.IO.File.ReadAllBytes(path)), features, options);
+ break;
+ }
+
+ var outputType = _values.GetOrDefault("vision.output.type", "text");
+ switch(outputType)
+ {
+ case "text":
+ PrintIAResult(result, Console.Out);
+ break;
+ case "json":
+ Console.Write(System.ClientModel.Primitives.ModelReaderWriter.Write(result).ToString());
+ break;
+ default:
+ _values.AddThrowError("WARNING:", $"'{outputType}' NOT YET IMPLEMENTED!!");
+ break;
+ }
+ }
+
+ private ImageAnalysisOptions BuildIAOptions(ICommandValues values)
+ {
+ var options = new ImageAnalysisOptions();
+ if (values.Contains("vision.image.language"))
+ {
+ options.Language = values.Get("vision.image.language");
+ }
+ if(values.Contains("vision.image.gender.neutral.captions"))
+ {
+ options.GenderNeutralCaption = bool.Parse(values.Get("vision.image.gender.neutral.captions"));
+ }
+ if(values.Contains("vision.image.model.version"))
+ {
+ options.ModelVersion = values.Get("vision.image.model.version");
+ }
+ if(values.Contains("vision.image.smart.crop.aspect.ratios"))
+ {
+ List aspectRatios = new List();
+ var ratios = values.Get("vision.image.smart.crop.aspect.ratios").Split(',');
+ foreach(var ratio in ratios)
+ {
+ aspectRatios.Add(float.Parse(ratio));
+ }
+ options.SmartCropsAspectRatios = aspectRatios;
+ }
+ return options;
+ }
+
+ private void PrintIAResult(ImageAnalysisResult result, TextWriter writer)
+ {
+ writer.WriteLine($"Image analysis results:");
+ writer.WriteLine($" Metadata: Model: {result.ModelVersion} Image dimensions: {result.Metadata.Width} x {result.Metadata.Height}");
+
+ if (result.Caption != null)
+ {
+ writer.WriteLine($" Caption: {result.Caption.Text}, Confidence: {result.Caption.Confidence:F4}");
+ }
+ if (result.DenseCaptions != null)
+ {
+ writer.WriteLine($" Dense Captions:");
+ foreach (DenseCaption denseCaption in result.DenseCaptions.Values)
+ {
+ writer.WriteLine($" Region: '{denseCaption.Text}', Confidence: {denseCaption.Confidence:F4}, Bounding box: {denseCaption.BoundingBox}");
+ }
+ }
+ if (result.Tags != null)
+ {
+ writer.WriteLine($" Tags:");
+ foreach (DetectedTag tag in result.Tags.Values)
+ {
+ writer.WriteLine($" '{tag.Name}', Confidence: {tag.Confidence:F4}");
+ }
+ }
+ if (result.Objects != null)
+ {
+ writer.WriteLine($" Objects:");
+ foreach (DetectedObject detectedObject in result.Objects?.Values)
+ {
+ writer.WriteLine($" Object: '{detectedObject.Tags.First().Name}', Bounding box: {detectedObject.BoundingBox.ToString()}");
+ }
+ }
+ if (result.SmartCrops != null)
+ {
+ writer.WriteLine($" SmartCrops:");
+ foreach (CropRegion cropRegion in result.SmartCrops?.Values)
+ {
+ writer.WriteLine($" Aspect ratio: {cropRegion.AspectRatio}, Bounding box: {cropRegion.BoundingBox}");
+ }
+ }
+ if (result.People != null)
+ {
+ writer.WriteLine($" People:");
+ foreach (DetectedPerson person in result.People?.Values)
+ {
+ writer.WriteLine($" Person: Bounding box {person.BoundingBox.ToString()}, Confidence: {person.Confidence:F4}");
+ }
+ }
+ if (result.Read != null)
+ {
+ writer.WriteLine($" Read:");
+ foreach (var line in result.Read?.Blocks.SelectMany(block => block.Lines))
+ {
+ writer.WriteLine($" Line: '{line.Text}', Bounding Polygon: [{string.Join(" ", line.BoundingPolygon)}]");
+ foreach (DetectedTextWord word in line.Words)
+ {
+ writer.WriteLine($" Word: '{word.Text}', Confidence {word.Confidence.ToString("#.####")}, Bounding Polygon: [{string.Join(" ", word.BoundingPolygon)}]");
+ }
+ }
+ }
+ }
private bool _quiet = false;
private bool _verbose = false;
}
diff --git a/src/ai/helpers/config_environment_helpers.cs b/src/ai/helpers/config_environment_helpers.cs
index f2d0efe1..cd77b4c2 100644
--- a/src/ai/helpers/config_environment_helpers.cs
+++ b/src/ai/helpers/config_environment_helpers.cs
@@ -24,6 +24,7 @@ public static Dictionary GetEnvironment(INamedValues values)
env.Add("AZURE_OPENAI_ENDPOINT", ReadConfig(values, "chat.endpoint"));
env.Add("AZURE_OPENAI_KEY", ReadConfig(values, "chat.key"));
+ env.Add("AZURE_OPENAI_API_KEY", ReadConfig(values, "chat.key"));
env.Add("AZURE_OPENAI_API_VERSION", ChatCommand.GetOpenAIClientVersionNumber());
env.Add("AZURE_OPENAI_CHAT_DEPLOYMENT", ReadConfig(values, "chat.deployment"));
diff --git a/src/spx/.x/config/batch.default.config b/src/clis/spx/.x/config/batch.default.config
similarity index 100%
rename from src/spx/.x/config/batch.default.config
rename to src/clis/spx/.x/config/batch.default.config
diff --git a/src/spx/.x/config/batch.default.connection b/src/clis/spx/.x/config/batch.default.connection
similarity index 100%
rename from src/spx/.x/config/batch.default.connection
rename to src/clis/spx/.x/config/batch.default.connection
diff --git a/src/spx/.x/config/batch.default.output b/src/clis/spx/.x/config/batch.default.output
similarity index 100%
rename from src/spx/.x/config/batch.default.output
rename to src/clis/spx/.x/config/batch.default.output
diff --git a/src/spx/.x/config/connection b/src/clis/spx/.x/config/connection
similarity index 100%
rename from src/spx/.x/config/connection
rename to src/clis/spx/.x/config/connection
diff --git a/src/spx/.x/config/connection.from.endpoint b/src/clis/spx/.x/config/connection.from.endpoint
similarity index 100%
rename from src/spx/.x/config/connection.from.endpoint
rename to src/clis/spx/.x/config/connection.from.endpoint
diff --git a/src/spx/.x/config/connection.from.host b/src/clis/spx/.x/config/connection.from.host
similarity index 100%
rename from src/spx/.x/config/connection.from.host
rename to src/clis/spx/.x/config/connection.from.host
diff --git a/src/spx/.x/config/connection.from.region b/src/clis/spx/.x/config/connection.from.region
similarity index 100%
rename from src/spx/.x/config/connection.from.region
rename to src/clis/spx/.x/config/connection.from.region
diff --git a/src/spx/.x/config/csr.default.config b/src/clis/spx/.x/config/csr.default.config
similarity index 100%
rename from src/spx/.x/config/csr.default.config
rename to src/clis/spx/.x/config/csr.default.config
diff --git a/src/spx/.x/config/csr.default.connection b/src/clis/spx/.x/config/csr.default.connection
similarity index 100%
rename from src/spx/.x/config/csr.default.connection
rename to src/clis/spx/.x/config/csr.default.connection
diff --git a/src/spx/.x/config/csr.default.output b/src/clis/spx/.x/config/csr.default.output
similarity index 100%
rename from src/spx/.x/config/csr.default.output
rename to src/clis/spx/.x/config/csr.default.output
diff --git a/src/spx/.x/config/default.log b/src/clis/spx/.x/config/default.log
similarity index 100%
rename from src/spx/.x/config/default.log
rename to src/clis/spx/.x/config/default.log
diff --git a/src/spx/.x/config/default.path b/src/clis/spx/.x/config/default.path
similarity index 100%
rename from src/spx/.x/config/default.path
rename to src/clis/spx/.x/config/default.path
diff --git a/src/spx/.x/config/defaults b/src/clis/spx/.x/config/defaults
similarity index 100%
rename from src/spx/.x/config/defaults
rename to src/clis/spx/.x/config/defaults
diff --git a/src/spx/.x/config/dialog.default.config b/src/clis/spx/.x/config/dialog.default.config
similarity index 100%
rename from src/spx/.x/config/dialog.default.config
rename to src/clis/spx/.x/config/dialog.default.config
diff --git a/src/spx/.x/config/dialog.default.connection b/src/clis/spx/.x/config/dialog.default.connection
similarity index 100%
rename from src/spx/.x/config/dialog.default.connection
rename to src/clis/spx/.x/config/dialog.default.connection
diff --git a/src/spx/.x/config/dialog.default.output b/src/clis/spx/.x/config/dialog.default.output
similarity index 100%
rename from src/spx/.x/config/dialog.default.output
rename to src/clis/spx/.x/config/dialog.default.output
diff --git a/src/spx/.x/config/endpoint b/src/clis/spx/.x/config/endpoint
similarity index 100%
rename from src/spx/.x/config/endpoint
rename to src/clis/spx/.x/config/endpoint
diff --git a/src/spx/.x/config/fiddler b/src/clis/spx/.x/config/fiddler
similarity index 100%
rename from src/spx/.x/config/fiddler
rename to src/clis/spx/.x/config/fiddler
diff --git a/src/spx/.x/config/host b/src/clis/spx/.x/config/host
similarity index 100%
rename from src/spx/.x/config/host
rename to src/clis/spx/.x/config/host
diff --git a/src/spx/.x/config/identify.default.config b/src/clis/spx/.x/config/identify.default.config
similarity index 100%
rename from src/spx/.x/config/identify.default.config
rename to src/clis/spx/.x/config/identify.default.config
diff --git a/src/spx/.x/config/identify.default.connection b/src/clis/spx/.x/config/identify.default.connection
similarity index 100%
rename from src/spx/.x/config/identify.default.connection
rename to src/clis/spx/.x/config/identify.default.connection
diff --git a/src/spx/.x/config/identify.default.output b/src/clis/spx/.x/config/identify.default.output
similarity index 100%
rename from src/spx/.x/config/identify.default.output
rename to src/clis/spx/.x/config/identify.default.output
diff --git a/src/spx/.x/config/init.default.config b/src/clis/spx/.x/config/init.default.config
similarity index 100%
rename from src/spx/.x/config/init.default.config
rename to src/clis/spx/.x/config/init.default.config
diff --git a/src/spx/.x/config/init.default.output b/src/clis/spx/.x/config/init.default.output
similarity index 100%
rename from src/spx/.x/config/init.default.output
rename to src/clis/spx/.x/config/init.default.output
diff --git a/src/spx/.x/config/input.path b/src/clis/spx/.x/config/input.path
similarity index 100%
rename from src/spx/.x/config/input.path
rename to src/clis/spx/.x/config/input.path
diff --git a/src/spx/.x/config/intent.default.config b/src/clis/spx/.x/config/intent.default.config
similarity index 100%
rename from src/spx/.x/config/intent.default.config
rename to src/clis/spx/.x/config/intent.default.config
diff --git a/src/spx/.x/config/intent.default.connection b/src/clis/spx/.x/config/intent.default.connection
similarity index 100%
rename from src/spx/.x/config/intent.default.connection
rename to src/clis/spx/.x/config/intent.default.connection
diff --git a/src/spx/.x/config/intent.default.output b/src/clis/spx/.x/config/intent.default.output
similarity index 100%
rename from src/spx/.x/config/intent.default.output
rename to src/clis/spx/.x/config/intent.default.output
diff --git a/src/spx/.x/config/key b/src/clis/spx/.x/config/key
similarity index 100%
rename from src/spx/.x/config/key
rename to src/clis/spx/.x/config/key
diff --git a/src/spx/.x/config/language b/src/clis/spx/.x/config/language
similarity index 100%
rename from src/spx/.x/config/language
rename to src/clis/spx/.x/config/language
diff --git a/src/spx/.x/config/log.pid b/src/clis/spx/.x/config/log.pid
similarity index 100%
rename from src/spx/.x/config/log.pid
rename to src/clis/spx/.x/config/log.pid
diff --git a/src/spx/.x/config/log.time b/src/clis/spx/.x/config/log.time
similarity index 100%
rename from src/spx/.x/config/log.time
rename to src/clis/spx/.x/config/log.time
diff --git a/src/spx/.x/config/log.time.pid b/src/clis/spx/.x/config/log.time.pid
similarity index 100%
rename from src/spx/.x/config/log.time.pid
rename to src/clis/spx/.x/config/log.time.pid
diff --git a/src/spx/.x/config/my.defaults b/src/clis/spx/.x/config/my.defaults
similarity index 100%
rename from src/spx/.x/config/my.defaults
rename to src/clis/spx/.x/config/my.defaults
diff --git a/src/spx/.x/config/none b/src/clis/spx/.x/config/none
similarity index 100%
rename from src/spx/.x/config/none
rename to src/clis/spx/.x/config/none
diff --git a/src/spx/.x/config/output b/src/clis/spx/.x/config/output
similarity index 100%
rename from src/spx/.x/config/output
rename to src/clis/spx/.x/config/output
diff --git a/src/spx/.x/config/output.all.detailed b/src/clis/spx/.x/config/output.all.detailed
similarity index 100%
rename from src/spx/.x/config/output.all.detailed
rename to src/clis/spx/.x/config/output.all.detailed
diff --git a/src/spx/.x/config/output.all.latency b/src/clis/spx/.x/config/output.all.latency
similarity index 100%
rename from src/spx/.x/config/output.all.latency
rename to src/clis/spx/.x/config/output.all.latency
diff --git a/src/spx/.x/config/output.all.standard b/src/clis/spx/.x/config/output.all.standard
similarity index 100%
rename from src/spx/.x/config/output.all.standard
rename to src/clis/spx/.x/config/output.all.standard
diff --git a/src/spx/.x/config/output.all.transcript.display b/src/clis/spx/.x/config/output.all.transcript.display
similarity index 100%
rename from src/spx/.x/config/output.all.transcript.display
rename to src/clis/spx/.x/config/output.all.transcript.display
diff --git a/src/spx/.x/config/output.all.transcript.lexical b/src/clis/spx/.x/config/output.all.transcript.lexical
similarity index 100%
rename from src/spx/.x/config/output.all.transcript.lexical
rename to src/clis/spx/.x/config/output.all.transcript.lexical
diff --git a/src/spx/.x/config/output.each.detailed b/src/clis/spx/.x/config/output.each.detailed
similarity index 100%
rename from src/spx/.x/config/output.each.detailed
rename to src/clis/spx/.x/config/output.each.detailed
diff --git a/src/spx/.x/config/output.each.event b/src/clis/spx/.x/config/output.each.event
similarity index 100%
rename from src/spx/.x/config/output.each.event
rename to src/clis/spx/.x/config/output.each.event
diff --git a/src/spx/.x/config/output.each.latency b/src/clis/spx/.x/config/output.each.latency
similarity index 100%
rename from src/spx/.x/config/output.each.latency
rename to src/clis/spx/.x/config/output.each.latency
diff --git a/src/spx/.x/config/profile.default.config b/src/clis/spx/.x/config/profile.default.config
similarity index 100%
rename from src/spx/.x/config/profile.default.config
rename to src/clis/spx/.x/config/profile.default.config
diff --git a/src/spx/.x/config/profile.default.connection b/src/clis/spx/.x/config/profile.default.connection
similarity index 100%
rename from src/spx/.x/config/profile.default.connection
rename to src/clis/spx/.x/config/profile.default.connection
diff --git a/src/spx/.x/config/profile.default.output b/src/clis/spx/.x/config/profile.default.output
similarity index 100%
rename from src/spx/.x/config/profile.default.output
rename to src/clis/spx/.x/config/profile.default.output
diff --git a/src/spx/.x/config/recognize.default.config b/src/clis/spx/.x/config/recognize.default.config
similarity index 100%
rename from src/spx/.x/config/recognize.default.config
rename to src/clis/spx/.x/config/recognize.default.config
diff --git a/src/spx/.x/config/recognize.default.connection b/src/clis/spx/.x/config/recognize.default.connection
similarity index 100%
rename from src/spx/.x/config/recognize.default.connection
rename to src/clis/spx/.x/config/recognize.default.connection
diff --git a/src/spx/.x/config/recognize.default.output b/src/clis/spx/.x/config/recognize.default.output
similarity index 100%
rename from src/spx/.x/config/recognize.default.output
rename to src/clis/spx/.x/config/recognize.default.output
diff --git a/src/spx/.x/config/region b/src/clis/spx/.x/config/region
similarity index 100%
rename from src/spx/.x/config/region
rename to src/clis/spx/.x/config/region
diff --git a/src/spx/.x/config/run.default.config b/src/clis/spx/.x/config/run.default.config
similarity index 100%
rename from src/spx/.x/config/run.default.config
rename to src/clis/spx/.x/config/run.default.config
diff --git a/src/spx/.x/config/speaker.default.config b/src/clis/spx/.x/config/speaker.default.config
similarity index 100%
rename from src/spx/.x/config/speaker.default.config
rename to src/clis/spx/.x/config/speaker.default.config
diff --git a/src/spx/.x/config/speaker.default.connection b/src/clis/spx/.x/config/speaker.default.connection
similarity index 100%
rename from src/spx/.x/config/speaker.default.connection
rename to src/clis/spx/.x/config/speaker.default.connection
diff --git a/src/spx/.x/config/speaker.default.output b/src/clis/spx/.x/config/speaker.default.output
similarity index 100%
rename from src/spx/.x/config/speaker.default.output
rename to src/clis/spx/.x/config/speaker.default.output
diff --git a/src/spx/.x/config/spx.defaults b/src/clis/spx/.x/config/spx.defaults
similarity index 100%
rename from src/spx/.x/config/spx.defaults
rename to src/clis/spx/.x/config/spx.defaults
diff --git a/src/spx/.x/config/synthesize.default.config b/src/clis/spx/.x/config/synthesize.default.config
similarity index 100%
rename from src/spx/.x/config/synthesize.default.config
rename to src/clis/spx/.x/config/synthesize.default.config
diff --git a/src/spx/.x/config/synthesize.default.connection b/src/clis/spx/.x/config/synthesize.default.connection
similarity index 100%
rename from src/spx/.x/config/synthesize.default.connection
rename to src/clis/spx/.x/config/synthesize.default.connection
diff --git a/src/spx/.x/config/synthesize.default.output b/src/clis/spx/.x/config/synthesize.default.output
similarity index 100%
rename from src/spx/.x/config/synthesize.default.output
rename to src/clis/spx/.x/config/synthesize.default.output
diff --git a/src/spx/.x/config/synthesize.output.all.standard b/src/clis/spx/.x/config/synthesize.output.all.standard
similarity index 100%
rename from src/spx/.x/config/synthesize.output.all.standard
rename to src/clis/spx/.x/config/synthesize.output.all.standard
diff --git a/src/spx/.x/config/transcribe.default.config b/src/clis/spx/.x/config/transcribe.default.config
similarity index 100%
rename from src/spx/.x/config/transcribe.default.config
rename to src/clis/spx/.x/config/transcribe.default.config
diff --git a/src/spx/.x/config/transcribe.default.connection b/src/clis/spx/.x/config/transcribe.default.connection
similarity index 100%
rename from src/spx/.x/config/transcribe.default.connection
rename to src/clis/spx/.x/config/transcribe.default.connection
diff --git a/src/spx/.x/config/transcribe.default.output b/src/clis/spx/.x/config/transcribe.default.output
similarity index 100%
rename from src/spx/.x/config/transcribe.default.output
rename to src/clis/spx/.x/config/transcribe.default.output
diff --git a/src/spx/.x/config/translate.default.config b/src/clis/spx/.x/config/translate.default.config
similarity index 100%
rename from src/spx/.x/config/translate.default.config
rename to src/clis/spx/.x/config/translate.default.config
diff --git a/src/spx/.x/config/translate.default.connection b/src/clis/spx/.x/config/translate.default.connection
similarity index 100%
rename from src/spx/.x/config/translate.default.connection
rename to src/clis/spx/.x/config/translate.default.connection
diff --git a/src/spx/.x/config/translate.default.output b/src/clis/spx/.x/config/translate.default.output
similarity index 100%
rename from src/spx/.x/config/translate.default.output
rename to src/clis/spx/.x/config/translate.default.output
diff --git a/src/spx/.x/config/translate.output.all.translated.text b/src/clis/spx/.x/config/translate.output.all.translated.text
similarity index 100%
rename from src/spx/.x/config/translate.output.all.translated.text
rename to src/clis/spx/.x/config/translate.output.all.translated.text
diff --git a/src/spx/.x/config/translate.output.each.translated.text b/src/clis/spx/.x/config/translate.output.each.translated.text
similarity index 100%
rename from src/spx/.x/config/translate.output.each.translated.text
rename to src/clis/spx/.x/config/translate.output.each.translated.text
diff --git a/src/spx/.x/config/webjob.default.config b/src/clis/spx/.x/config/webjob.default.config
similarity index 100%
rename from src/spx/.x/config/webjob.default.config
rename to src/clis/spx/.x/config/webjob.default.config
diff --git a/src/spx/.x/config/webjob.default.connection b/src/clis/spx/.x/config/webjob.default.connection
similarity index 100%
rename from src/spx/.x/config/webjob.default.connection
rename to src/clis/spx/.x/config/webjob.default.connection
diff --git a/src/spx/.x/config/webjob.endpoint b/src/clis/spx/.x/config/webjob.endpoint
similarity index 100%
rename from src/spx/.x/config/webjob.endpoint
rename to src/clis/spx/.x/config/webjob.endpoint
diff --git a/src/spx/.x/config/webjob.password b/src/clis/spx/.x/config/webjob.password
similarity index 100%
rename from src/spx/.x/config/webjob.password
rename to src/clis/spx/.x/config/webjob.password
diff --git a/src/spx/.x/help/advanced.setup b/src/clis/spx/.x/help/advanced.setup
similarity index 100%
rename from src/spx/.x/help/advanced.setup
rename to src/clis/spx/.x/help/advanced.setup
diff --git a/src/spx/.x/help/batch b/src/clis/spx/.x/help/batch
similarity index 100%
rename from src/spx/.x/help/batch
rename to src/clis/spx/.x/help/batch
diff --git a/src/spx/.x/help/batch.connection b/src/clis/spx/.x/help/batch.connection
similarity index 100%
rename from src/spx/.x/help/batch.connection
rename to src/clis/spx/.x/help/batch.connection
diff --git a/src/spx/.x/help/batch.download b/src/clis/spx/.x/help/batch.download
similarity index 100%
rename from src/spx/.x/help/batch.download
rename to src/clis/spx/.x/help/batch.download
diff --git a/src/spx/.x/help/batch.download.advanced b/src/clis/spx/.x/help/batch.download.advanced
similarity index 100%
rename from src/spx/.x/help/batch.download.advanced
rename to src/clis/spx/.x/help/batch.download.advanced
diff --git a/src/spx/.x/help/batch.download.file b/src/clis/spx/.x/help/batch.download.file
similarity index 100%
rename from src/spx/.x/help/batch.download.file
rename to src/clis/spx/.x/help/batch.download.file
diff --git a/src/spx/.x/help/batch.download.output.file b/src/clis/spx/.x/help/batch.download.output.file
similarity index 100%
rename from src/spx/.x/help/batch.download.output.file
rename to src/clis/spx/.x/help/batch.download.output.file
diff --git a/src/spx/.x/help/batch.download.url b/src/clis/spx/.x/help/batch.download.url
similarity index 100%
rename from src/spx/.x/help/batch.download.url
rename to src/clis/spx/.x/help/batch.download.url
diff --git a/src/spx/.x/help/batch.examples b/src/clis/spx/.x/help/batch.examples
similarity index 100%
rename from src/spx/.x/help/batch.examples
rename to src/clis/spx/.x/help/batch.examples
diff --git a/src/spx/.x/help/batch.foreach b/src/clis/spx/.x/help/batch.foreach
similarity index 100%
rename from src/spx/.x/help/batch.foreach
rename to src/clis/spx/.x/help/batch.foreach
diff --git a/src/spx/.x/help/batch.input.path b/src/clis/spx/.x/help/batch.input.path
similarity index 100%
rename from src/spx/.x/help/batch.input.path
rename to src/clis/spx/.x/help/batch.input.path
diff --git a/src/spx/.x/help/batch.key b/src/clis/spx/.x/help/batch.key
similarity index 100%
rename from src/spx/.x/help/batch.key
rename to src/clis/spx/.x/help/batch.key
diff --git a/src/spx/.x/help/batch.list b/src/clis/spx/.x/help/batch.list
similarity index 100%
rename from src/spx/.x/help/batch.list
rename to src/clis/spx/.x/help/batch.list
diff --git a/src/spx/.x/help/batch.list.advanced b/src/clis/spx/.x/help/batch.list.advanced
similarity index 100%
rename from src/spx/.x/help/batch.list.advanced
rename to src/clis/spx/.x/help/batch.list.advanced
diff --git a/src/spx/.x/help/batch.output b/src/clis/spx/.x/help/batch.output
similarity index 100%
rename from src/spx/.x/help/batch.output
rename to src/clis/spx/.x/help/batch.output
diff --git a/src/spx/.x/help/batch.output.id b/src/clis/spx/.x/help/batch.output.id
similarity index 100%
rename from src/spx/.x/help/batch.output.id
rename to src/clis/spx/.x/help/batch.output.id
diff --git a/src/spx/.x/help/batch.output.ids b/src/clis/spx/.x/help/batch.output.ids
similarity index 100%
rename from src/spx/.x/help/batch.output.ids
rename to src/clis/spx/.x/help/batch.output.ids
diff --git a/src/spx/.x/help/batch.output.json b/src/clis/spx/.x/help/batch.output.json
similarity index 100%
rename from src/spx/.x/help/batch.output.json
rename to src/clis/spx/.x/help/batch.output.json
diff --git a/src/spx/.x/help/batch.output.path b/src/clis/spx/.x/help/batch.output.path
similarity index 100%
rename from src/spx/.x/help/batch.output.path
rename to src/clis/spx/.x/help/batch.output.path
diff --git a/src/spx/.x/help/batch.output.request b/src/clis/spx/.x/help/batch.output.request
similarity index 100%
rename from src/spx/.x/help/batch.output.request
rename to src/clis/spx/.x/help/batch.output.request
diff --git a/src/spx/.x/help/batch.output.url b/src/clis/spx/.x/help/batch.output.url
similarity index 100%
rename from src/spx/.x/help/batch.output.url
rename to src/clis/spx/.x/help/batch.output.url
diff --git a/src/spx/.x/help/batch.output.urls b/src/clis/spx/.x/help/batch.output.urls
similarity index 100%
rename from src/spx/.x/help/batch.output.urls
rename to src/clis/spx/.x/help/batch.output.urls
diff --git a/src/spx/.x/help/batch.processes b/src/clis/spx/.x/help/batch.processes
similarity index 100%
rename from src/spx/.x/help/batch.processes
rename to src/clis/spx/.x/help/batch.processes
diff --git a/src/spx/.x/help/batch.region b/src/clis/spx/.x/help/batch.region
similarity index 100%
rename from src/spx/.x/help/batch.region
rename to src/clis/spx/.x/help/batch.region
diff --git a/src/spx/.x/help/batch.save b/src/clis/spx/.x/help/batch.save
similarity index 100%
rename from src/spx/.x/help/batch.save
rename to src/clis/spx/.x/help/batch.save
diff --git a/src/spx/.x/help/batch.threads b/src/clis/spx/.x/help/batch.threads
similarity index 100%
rename from src/spx/.x/help/batch.threads
rename to src/clis/spx/.x/help/batch.threads
diff --git a/src/spx/.x/help/batch.transcription b/src/clis/spx/.x/help/batch.transcription
similarity index 100%
rename from src/spx/.x/help/batch.transcription
rename to src/clis/spx/.x/help/batch.transcription
diff --git a/src/spx/.x/help/batch.transcription.create b/src/clis/spx/.x/help/batch.transcription.create
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create
rename to src/clis/spx/.x/help/batch.transcription.create
diff --git a/src/spx/.x/help/batch.transcription.create.advanced b/src/clis/spx/.x/help/batch.transcription.create.advanced
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create.advanced
rename to src/clis/spx/.x/help/batch.transcription.create.advanced
diff --git a/src/spx/.x/help/batch.transcription.create.content b/src/clis/spx/.x/help/batch.transcription.create.content
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create.content
rename to src/clis/spx/.x/help/batch.transcription.create.content
diff --git a/src/spx/.x/help/batch.transcription.create.dataset b/src/clis/spx/.x/help/batch.transcription.create.dataset
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create.dataset
rename to src/clis/spx/.x/help/batch.transcription.create.dataset
diff --git a/src/spx/.x/help/batch.transcription.create.diarization b/src/clis/spx/.x/help/batch.transcription.create.diarization
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create.diarization
rename to src/clis/spx/.x/help/batch.transcription.create.diarization
diff --git a/src/spx/.x/help/batch.transcription.create.language b/src/clis/spx/.x/help/batch.transcription.create.language
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create.language
rename to src/clis/spx/.x/help/batch.transcription.create.language
diff --git a/src/spx/.x/help/batch.transcription.create.model b/src/clis/spx/.x/help/batch.transcription.create.model
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create.model
rename to src/clis/spx/.x/help/batch.transcription.create.model
diff --git a/src/spx/.x/help/batch.transcription.create.profanity.filter b/src/clis/spx/.x/help/batch.transcription.create.profanity.filter
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create.profanity.filter
rename to src/clis/spx/.x/help/batch.transcription.create.profanity.filter
diff --git a/src/spx/.x/help/batch.transcription.create.properties b/src/clis/spx/.x/help/batch.transcription.create.properties
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create.properties
rename to src/clis/spx/.x/help/batch.transcription.create.properties
diff --git a/src/spx/.x/help/batch.transcription.create.punctuation.mode b/src/clis/spx/.x/help/batch.transcription.create.punctuation.mode
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create.punctuation.mode
rename to src/clis/spx/.x/help/batch.transcription.create.punctuation.mode
diff --git a/src/spx/.x/help/batch.transcription.create.word.level.timing b/src/clis/spx/.x/help/batch.transcription.create.word.level.timing
similarity index 100%
rename from src/spx/.x/help/batch.transcription.create.word.level.timing
rename to src/clis/spx/.x/help/batch.transcription.create.word.level.timing
diff --git a/src/spx/.x/help/batch.transcription.delete b/src/clis/spx/.x/help/batch.transcription.delete
similarity index 100%
rename from src/spx/.x/help/batch.transcription.delete
rename to src/clis/spx/.x/help/batch.transcription.delete
diff --git a/src/spx/.x/help/batch.transcription.delete.advanced b/src/clis/spx/.x/help/batch.transcription.delete.advanced
similarity index 100%
rename from src/spx/.x/help/batch.transcription.delete.advanced
rename to src/clis/spx/.x/help/batch.transcription.delete.advanced
diff --git a/src/spx/.x/help/batch.transcription.description b/src/clis/spx/.x/help/batch.transcription.description
similarity index 100%
rename from src/spx/.x/help/batch.transcription.description
rename to src/clis/spx/.x/help/batch.transcription.description
diff --git a/src/spx/.x/help/batch.transcription.download b/src/clis/spx/.x/help/batch.transcription.download
similarity index 100%
rename from src/spx/.x/help/batch.transcription.download
rename to src/clis/spx/.x/help/batch.transcription.download
diff --git a/src/spx/.x/help/batch.transcription.download.advanced b/src/clis/spx/.x/help/batch.transcription.download.advanced
similarity index 100%
rename from src/spx/.x/help/batch.transcription.download.advanced
rename to src/clis/spx/.x/help/batch.transcription.download.advanced
diff --git a/src/spx/.x/help/batch.transcription.examples b/src/clis/spx/.x/help/batch.transcription.examples
similarity index 100%
rename from src/spx/.x/help/batch.transcription.examples
rename to src/clis/spx/.x/help/batch.transcription.examples
diff --git a/src/spx/.x/help/batch.transcription.list b/src/clis/spx/.x/help/batch.transcription.list
similarity index 100%
rename from src/spx/.x/help/batch.transcription.list
rename to src/clis/spx/.x/help/batch.transcription.list
diff --git a/src/spx/.x/help/batch.transcription.list.advanced b/src/clis/spx/.x/help/batch.transcription.list.advanced
similarity index 100%
rename from src/spx/.x/help/batch.transcription.list.advanced
rename to src/clis/spx/.x/help/batch.transcription.list.advanced
diff --git a/src/spx/.x/help/batch.transcription.list.files b/src/clis/spx/.x/help/batch.transcription.list.files
similarity index 100%
rename from src/spx/.x/help/batch.transcription.list.files
rename to src/clis/spx/.x/help/batch.transcription.list.files
diff --git a/src/spx/.x/help/batch.transcription.list.languages b/src/clis/spx/.x/help/batch.transcription.list.languages
similarity index 100%
rename from src/spx/.x/help/batch.transcription.list.languages
rename to src/clis/spx/.x/help/batch.transcription.list.languages
diff --git a/src/spx/.x/help/batch.transcription.list.transcriptions b/src/clis/spx/.x/help/batch.transcription.list.transcriptions
similarity index 100%
rename from src/spx/.x/help/batch.transcription.list.transcriptions
rename to src/clis/spx/.x/help/batch.transcription.list.transcriptions
diff --git a/src/spx/.x/help/batch.transcription.more.examples b/src/clis/spx/.x/help/batch.transcription.more.examples
similarity index 100%
rename from src/spx/.x/help/batch.transcription.more.examples
rename to src/clis/spx/.x/help/batch.transcription.more.examples
diff --git a/src/spx/.x/help/batch.transcription.name b/src/clis/spx/.x/help/batch.transcription.name
similarity index 100%
rename from src/spx/.x/help/batch.transcription.name
rename to src/clis/spx/.x/help/batch.transcription.name
diff --git a/src/spx/.x/help/batch.transcription.onprem b/src/clis/spx/.x/help/batch.transcription.onprem
similarity index 100%
rename from src/spx/.x/help/batch.transcription.onprem
rename to src/clis/spx/.x/help/batch.transcription.onprem
diff --git a/src/spx/.x/help/batch.transcription.onprem.create b/src/clis/spx/.x/help/batch.transcription.onprem.create
similarity index 100%
rename from src/spx/.x/help/batch.transcription.onprem.create
rename to src/clis/spx/.x/help/batch.transcription.onprem.create
diff --git a/src/spx/.x/help/batch.transcription.onprem.delete b/src/clis/spx/.x/help/batch.transcription.onprem.delete
similarity index 100%
rename from src/spx/.x/help/batch.transcription.onprem.delete
rename to src/clis/spx/.x/help/batch.transcription.onprem.delete
diff --git a/src/spx/.x/help/batch.transcription.onprem.endpoints b/src/clis/spx/.x/help/batch.transcription.onprem.endpoints
similarity index 100%
rename from src/spx/.x/help/batch.transcription.onprem.endpoints
rename to src/clis/spx/.x/help/batch.transcription.onprem.endpoints
diff --git a/src/spx/.x/help/batch.transcription.onprem.list b/src/clis/spx/.x/help/batch.transcription.onprem.list
similarity index 100%
rename from src/spx/.x/help/batch.transcription.onprem.list
rename to src/clis/spx/.x/help/batch.transcription.onprem.list
diff --git a/src/spx/.x/help/batch.transcription.onprem.status b/src/clis/spx/.x/help/batch.transcription.onprem.status
similarity index 100%
rename from src/spx/.x/help/batch.transcription.onprem.status
rename to src/clis/spx/.x/help/batch.transcription.onprem.status
diff --git a/src/spx/.x/help/batch.transcription.project b/src/clis/spx/.x/help/batch.transcription.project
similarity index 100%
rename from src/spx/.x/help/batch.transcription.project
rename to src/clis/spx/.x/help/batch.transcription.project
diff --git a/src/spx/.x/help/batch.transcription.reference b/src/clis/spx/.x/help/batch.transcription.reference
similarity index 100%
rename from src/spx/.x/help/batch.transcription.reference
rename to src/clis/spx/.x/help/batch.transcription.reference
diff --git a/src/spx/.x/help/batch.transcription.status b/src/clis/spx/.x/help/batch.transcription.status
similarity index 100%
rename from src/spx/.x/help/batch.transcription.status
rename to src/clis/spx/.x/help/batch.transcription.status
diff --git a/src/spx/.x/help/batch.transcription.status.advanced b/src/clis/spx/.x/help/batch.transcription.status.advanced
similarity index 100%
rename from src/spx/.x/help/batch.transcription.status.advanced
rename to src/clis/spx/.x/help/batch.transcription.status.advanced
diff --git a/src/spx/.x/help/batch.transcription.update b/src/clis/spx/.x/help/batch.transcription.update
similarity index 100%
rename from src/spx/.x/help/batch.transcription.update
rename to src/clis/spx/.x/help/batch.transcription.update
diff --git a/src/spx/.x/help/batch.transcription.update.advanced b/src/clis/spx/.x/help/batch.transcription.update.advanced
similarity index 100%
rename from src/spx/.x/help/batch.transcription.update.advanced
rename to src/clis/spx/.x/help/batch.transcription.update.advanced
diff --git a/src/spx/.x/help/batch.transcription.wait b/src/clis/spx/.x/help/batch.transcription.wait
similarity index 100%
rename from src/spx/.x/help/batch.transcription.wait
rename to src/clis/spx/.x/help/batch.transcription.wait
diff --git a/src/spx/.x/help/batch.zip b/src/clis/spx/.x/help/batch.zip
similarity index 100%
rename from src/spx/.x/help/batch.zip
rename to src/clis/spx/.x/help/batch.zip
diff --git a/src/spx/.x/help/check b/src/clis/spx/.x/help/check
similarity index 100%
rename from src/spx/.x/help/check
rename to src/clis/spx/.x/help/check
diff --git a/src/spx/.x/help/check.advanced b/src/clis/spx/.x/help/check.advanced
similarity index 100%
rename from src/spx/.x/help/check.advanced
rename to src/clis/spx/.x/help/check.advanced
diff --git a/src/spx/.x/help/check.examples b/src/clis/spx/.x/help/check.examples
similarity index 100%
rename from src/spx/.x/help/check.examples
rename to src/clis/spx/.x/help/check.examples
diff --git a/src/spx/.x/help/check.result b/src/clis/spx/.x/help/check.result
similarity index 100%
rename from src/spx/.x/help/check.result
rename to src/clis/spx/.x/help/check.result
diff --git a/src/spx/.x/help/config b/src/clis/spx/.x/help/config
similarity index 100%
rename from src/spx/.x/help/config
rename to src/clis/spx/.x/help/config
diff --git a/src/spx/.x/help/config.add b/src/clis/spx/.x/help/config.add
similarity index 100%
rename from src/spx/.x/help/config.add
rename to src/clis/spx/.x/help/config.add
diff --git a/src/spx/.x/help/config.advanced b/src/clis/spx/.x/help/config.advanced
similarity index 100%
rename from src/spx/.x/help/config.advanced
rename to src/clis/spx/.x/help/config.advanced
diff --git a/src/spx/.x/help/config.clear b/src/clis/spx/.x/help/config.clear
similarity index 100%
rename from src/spx/.x/help/config.clear
rename to src/clis/spx/.x/help/config.clear
diff --git a/src/spx/.x/help/config.command b/src/clis/spx/.x/help/config.command
similarity index 100%
rename from src/spx/.x/help/config.command
rename to src/clis/spx/.x/help/config.command
diff --git a/src/spx/.x/help/config.examples b/src/clis/spx/.x/help/config.examples
similarity index 100%
rename from src/spx/.x/help/config.examples
rename to src/clis/spx/.x/help/config.examples
diff --git a/src/spx/.x/help/config.find b/src/clis/spx/.x/help/config.find
similarity index 100%
rename from src/spx/.x/help/config.find
rename to src/clis/spx/.x/help/config.find
diff --git a/src/spx/.x/help/config.hive b/src/clis/spx/.x/help/config.hive
similarity index 100%
rename from src/spx/.x/help/config.hive
rename to src/clis/spx/.x/help/config.hive
diff --git a/src/spx/.x/help/config.region b/src/clis/spx/.x/help/config.region
similarity index 100%
rename from src/spx/.x/help/config.region
rename to src/clis/spx/.x/help/config.region
diff --git a/src/spx/.x/help/config.scope b/src/clis/spx/.x/help/config.scope
similarity index 100%
rename from src/spx/.x/help/config.scope
rename to src/clis/spx/.x/help/config.scope
diff --git a/src/spx/.x/help/config.set b/src/clis/spx/.x/help/config.set
similarity index 100%
rename from src/spx/.x/help/config.set
rename to src/clis/spx/.x/help/config.set
diff --git a/src/spx/.x/help/connect b/src/clis/spx/.x/help/connect
similarity index 100%
rename from src/spx/.x/help/connect
rename to src/clis/spx/.x/help/connect
diff --git a/src/spx/.x/help/continuous b/src/clis/spx/.x/help/continuous
similarity index 100%
rename from src/spx/.x/help/continuous
rename to src/clis/spx/.x/help/continuous
diff --git a/src/spx/.x/help/conversation b/src/clis/spx/.x/help/conversation
similarity index 100%
rename from src/spx/.x/help/conversation
rename to src/clis/spx/.x/help/conversation
diff --git a/src/spx/.x/help/conversation.id b/src/clis/spx/.x/help/conversation.id
similarity index 100%
rename from src/spx/.x/help/conversation.id
rename to src/clis/spx/.x/help/conversation.id
diff --git a/src/spx/.x/help/csr b/src/clis/spx/.x/help/csr
similarity index 100%
rename from src/spx/.x/help/csr
rename to src/clis/spx/.x/help/csr
diff --git a/src/spx/.x/help/csr.dataset b/src/clis/spx/.x/help/csr.dataset
similarity index 100%
rename from src/spx/.x/help/csr.dataset
rename to src/clis/spx/.x/help/csr.dataset
diff --git a/src/spx/.x/help/csr.dataset.create b/src/clis/spx/.x/help/csr.dataset.create
similarity index 100%
rename from src/spx/.x/help/csr.dataset.create
rename to src/clis/spx/.x/help/csr.dataset.create
diff --git a/src/spx/.x/help/csr.dataset.create.content b/src/clis/spx/.x/help/csr.dataset.create.content
similarity index 100%
rename from src/spx/.x/help/csr.dataset.create.content
rename to src/clis/spx/.x/help/csr.dataset.create.content
diff --git a/src/spx/.x/help/csr.dataset.create.kind b/src/clis/spx/.x/help/csr.dataset.create.kind
similarity index 100%
rename from src/spx/.x/help/csr.dataset.create.kind
rename to src/clis/spx/.x/help/csr.dataset.create.kind
diff --git a/src/spx/.x/help/csr.dataset.create.language b/src/clis/spx/.x/help/csr.dataset.create.language
similarity index 100%
rename from src/spx/.x/help/csr.dataset.create.language
rename to src/clis/spx/.x/help/csr.dataset.create.language
diff --git a/src/spx/.x/help/csr.dataset.delete b/src/clis/spx/.x/help/csr.dataset.delete
similarity index 100%
rename from src/spx/.x/help/csr.dataset.delete
rename to src/clis/spx/.x/help/csr.dataset.delete
diff --git a/src/spx/.x/help/csr.dataset.delete.dataset b/src/clis/spx/.x/help/csr.dataset.delete.dataset
similarity index 100%
rename from src/spx/.x/help/csr.dataset.delete.dataset
rename to src/clis/spx/.x/help/csr.dataset.delete.dataset
diff --git a/src/spx/.x/help/csr.dataset.description b/src/clis/spx/.x/help/csr.dataset.description
similarity index 100%
rename from src/spx/.x/help/csr.dataset.description
rename to src/clis/spx/.x/help/csr.dataset.description
diff --git a/src/spx/.x/help/csr.dataset.download b/src/clis/spx/.x/help/csr.dataset.download
similarity index 100%
rename from src/spx/.x/help/csr.dataset.download
rename to src/clis/spx/.x/help/csr.dataset.download
diff --git a/src/spx/.x/help/csr.dataset.download.file b/src/clis/spx/.x/help/csr.dataset.download.file
similarity index 100%
rename from src/spx/.x/help/csr.dataset.download.file
rename to src/clis/spx/.x/help/csr.dataset.download.file
diff --git a/src/spx/.x/help/csr.dataset.list b/src/clis/spx/.x/help/csr.dataset.list
similarity index 100%
rename from src/spx/.x/help/csr.dataset.list
rename to src/clis/spx/.x/help/csr.dataset.list
diff --git a/src/spx/.x/help/csr.dataset.list.datasets b/src/clis/spx/.x/help/csr.dataset.list.datasets
similarity index 100%
rename from src/spx/.x/help/csr.dataset.list.datasets
rename to src/clis/spx/.x/help/csr.dataset.list.datasets
diff --git a/src/spx/.x/help/csr.dataset.list.files b/src/clis/spx/.x/help/csr.dataset.list.files
similarity index 100%
rename from src/spx/.x/help/csr.dataset.list.files
rename to src/clis/spx/.x/help/csr.dataset.list.files
diff --git a/src/spx/.x/help/csr.dataset.list.languages b/src/clis/spx/.x/help/csr.dataset.list.languages
similarity index 100%
rename from src/spx/.x/help/csr.dataset.list.languages
rename to src/clis/spx/.x/help/csr.dataset.list.languages
diff --git a/src/spx/.x/help/csr.dataset.list.project b/src/clis/spx/.x/help/csr.dataset.list.project
similarity index 100%
rename from src/spx/.x/help/csr.dataset.list.project
rename to src/clis/spx/.x/help/csr.dataset.list.project
diff --git a/src/spx/.x/help/csr.dataset.name b/src/clis/spx/.x/help/csr.dataset.name
similarity index 100%
rename from src/spx/.x/help/csr.dataset.name
rename to src/clis/spx/.x/help/csr.dataset.name
diff --git a/src/spx/.x/help/csr.dataset.project b/src/clis/spx/.x/help/csr.dataset.project
similarity index 100%
rename from src/spx/.x/help/csr.dataset.project
rename to src/clis/spx/.x/help/csr.dataset.project
diff --git a/src/spx/.x/help/csr.dataset.status b/src/clis/spx/.x/help/csr.dataset.status
similarity index 100%
rename from src/spx/.x/help/csr.dataset.status
rename to src/clis/spx/.x/help/csr.dataset.status
diff --git a/src/spx/.x/help/csr.dataset.status.dataset b/src/clis/spx/.x/help/csr.dataset.status.dataset
similarity index 100%
rename from src/spx/.x/help/csr.dataset.status.dataset
rename to src/clis/spx/.x/help/csr.dataset.status.dataset
diff --git a/src/spx/.x/help/csr.dataset.update b/src/clis/spx/.x/help/csr.dataset.update
similarity index 100%
rename from src/spx/.x/help/csr.dataset.update
rename to src/clis/spx/.x/help/csr.dataset.update
diff --git a/src/spx/.x/help/csr.dataset.update.dataset b/src/clis/spx/.x/help/csr.dataset.update.dataset
similarity index 100%
rename from src/spx/.x/help/csr.dataset.update.dataset
rename to src/clis/spx/.x/help/csr.dataset.update.dataset
diff --git a/src/spx/.x/help/csr.dataset.upload b/src/clis/spx/.x/help/csr.dataset.upload
similarity index 100%
rename from src/spx/.x/help/csr.dataset.upload
rename to src/clis/spx/.x/help/csr.dataset.upload
diff --git a/src/spx/.x/help/csr.dataset.upload.data b/src/clis/spx/.x/help/csr.dataset.upload.data
similarity index 100%
rename from src/spx/.x/help/csr.dataset.upload.data
rename to src/clis/spx/.x/help/csr.dataset.upload.data
diff --git a/src/spx/.x/help/csr.dataset.upload.kind b/src/clis/spx/.x/help/csr.dataset.upload.kind
similarity index 100%
rename from src/spx/.x/help/csr.dataset.upload.kind
rename to src/clis/spx/.x/help/csr.dataset.upload.kind
diff --git a/src/spx/.x/help/csr.dataset.upload.language b/src/clis/spx/.x/help/csr.dataset.upload.language
similarity index 100%
rename from src/spx/.x/help/csr.dataset.upload.language
rename to src/clis/spx/.x/help/csr.dataset.upload.language
diff --git a/src/spx/.x/help/csr.download b/src/clis/spx/.x/help/csr.download
similarity index 100%
rename from src/spx/.x/help/csr.download
rename to src/clis/spx/.x/help/csr.download
diff --git a/src/spx/.x/help/csr.download.dataset.file b/src/clis/spx/.x/help/csr.download.dataset.file
similarity index 100%
rename from src/spx/.x/help/csr.download.dataset.file
rename to src/clis/spx/.x/help/csr.download.dataset.file
diff --git a/src/spx/.x/help/csr.download.endpoint.log b/src/clis/spx/.x/help/csr.download.endpoint.log
similarity index 100%
rename from src/spx/.x/help/csr.download.endpoint.log
rename to src/clis/spx/.x/help/csr.download.endpoint.log
diff --git a/src/spx/.x/help/csr.download.file b/src/clis/spx/.x/help/csr.download.file
similarity index 100%
rename from src/spx/.x/help/csr.download.file
rename to src/clis/spx/.x/help/csr.download.file
diff --git a/src/spx/.x/help/csr.download.url b/src/clis/spx/.x/help/csr.download.url
similarity index 100%
rename from src/spx/.x/help/csr.download.url
rename to src/clis/spx/.x/help/csr.download.url
diff --git a/src/spx/.x/help/csr.endpoint b/src/clis/spx/.x/help/csr.endpoint
similarity index 100%
rename from src/spx/.x/help/csr.endpoint
rename to src/clis/spx/.x/help/csr.endpoint
diff --git a/src/spx/.x/help/csr.endpoint.create b/src/clis/spx/.x/help/csr.endpoint.create
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.create
rename to src/clis/spx/.x/help/csr.endpoint.create
diff --git a/src/spx/.x/help/csr.endpoint.create.language b/src/clis/spx/.x/help/csr.endpoint.create.language
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.create.language
rename to src/clis/spx/.x/help/csr.endpoint.create.language
diff --git a/src/spx/.x/help/csr.endpoint.create.model b/src/clis/spx/.x/help/csr.endpoint.create.model
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.create.model
rename to src/clis/spx/.x/help/csr.endpoint.create.model
diff --git a/src/spx/.x/help/csr.endpoint.create.text b/src/clis/spx/.x/help/csr.endpoint.create.text
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.create.text
rename to src/clis/spx/.x/help/csr.endpoint.create.text
diff --git a/src/spx/.x/help/csr.endpoint.delete b/src/clis/spx/.x/help/csr.endpoint.delete
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.delete
rename to src/clis/spx/.x/help/csr.endpoint.delete
diff --git a/src/spx/.x/help/csr.endpoint.delete.endpoint b/src/clis/spx/.x/help/csr.endpoint.delete.endpoint
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.delete.endpoint
rename to src/clis/spx/.x/help/csr.endpoint.delete.endpoint
diff --git a/src/spx/.x/help/csr.endpoint.delete.endpoint.log b/src/clis/spx/.x/help/csr.endpoint.delete.endpoint.log
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.delete.endpoint.log
rename to src/clis/spx/.x/help/csr.endpoint.delete.endpoint.log
diff --git a/src/spx/.x/help/csr.endpoint.description b/src/clis/spx/.x/help/csr.endpoint.description
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.description
rename to src/clis/spx/.x/help/csr.endpoint.description
diff --git a/src/spx/.x/help/csr.endpoint.download b/src/clis/spx/.x/help/csr.endpoint.download
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.download
rename to src/clis/spx/.x/help/csr.endpoint.download
diff --git a/src/spx/.x/help/csr.endpoint.download.endpoint.log b/src/clis/spx/.x/help/csr.endpoint.download.endpoint.log
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.download.endpoint.log
rename to src/clis/spx/.x/help/csr.endpoint.download.endpoint.log
diff --git a/src/spx/.x/help/csr.endpoint.examples b/src/clis/spx/.x/help/csr.endpoint.examples
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.examples
rename to src/clis/spx/.x/help/csr.endpoint.examples
diff --git a/src/spx/.x/help/csr.endpoint.list b/src/clis/spx/.x/help/csr.endpoint.list
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.list
rename to src/clis/spx/.x/help/csr.endpoint.list
diff --git a/src/spx/.x/help/csr.endpoint.list.endpoint.languages b/src/clis/spx/.x/help/csr.endpoint.list.endpoint.languages
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.list.endpoint.languages
rename to src/clis/spx/.x/help/csr.endpoint.list.endpoint.languages
diff --git a/src/spx/.x/help/csr.endpoint.list.endpoint.logs b/src/clis/spx/.x/help/csr.endpoint.list.endpoint.logs
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.list.endpoint.logs
rename to src/clis/spx/.x/help/csr.endpoint.list.endpoint.logs
diff --git a/src/spx/.x/help/csr.endpoint.list.endpoints b/src/clis/spx/.x/help/csr.endpoint.list.endpoints
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.list.endpoints
rename to src/clis/spx/.x/help/csr.endpoint.list.endpoints
diff --git a/src/spx/.x/help/csr.endpoint.list.project b/src/clis/spx/.x/help/csr.endpoint.list.project
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.list.project
rename to src/clis/spx/.x/help/csr.endpoint.list.project
diff --git a/src/spx/.x/help/csr.endpoint.name b/src/clis/spx/.x/help/csr.endpoint.name
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.name
rename to src/clis/spx/.x/help/csr.endpoint.name
diff --git a/src/spx/.x/help/csr.endpoint.project b/src/clis/spx/.x/help/csr.endpoint.project
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.project
rename to src/clis/spx/.x/help/csr.endpoint.project
diff --git a/src/spx/.x/help/csr.endpoint.status b/src/clis/spx/.x/help/csr.endpoint.status
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.status
rename to src/clis/spx/.x/help/csr.endpoint.status
diff --git a/src/spx/.x/help/csr.endpoint.status.endpoint b/src/clis/spx/.x/help/csr.endpoint.status.endpoint
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.status.endpoint
rename to src/clis/spx/.x/help/csr.endpoint.status.endpoint
diff --git a/src/spx/.x/help/csr.endpoint.update b/src/clis/spx/.x/help/csr.endpoint.update
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.update
rename to src/clis/spx/.x/help/csr.endpoint.update
diff --git a/src/spx/.x/help/csr.endpoint.update.endpoint b/src/clis/spx/.x/help/csr.endpoint.update.endpoint
similarity index 100%
rename from src/spx/.x/help/csr.endpoint.update.endpoint
rename to src/clis/spx/.x/help/csr.endpoint.update.endpoint
diff --git a/src/spx/.x/help/csr.evaluation b/src/clis/spx/.x/help/csr.evaluation
similarity index 100%
rename from src/spx/.x/help/csr.evaluation
rename to src/clis/spx/.x/help/csr.evaluation
diff --git a/src/spx/.x/help/csr.evaluation.create b/src/clis/spx/.x/help/csr.evaluation.create
similarity index 100%
rename from src/spx/.x/help/csr.evaluation.create
rename to src/clis/spx/.x/help/csr.evaluation.create
diff --git a/src/spx/.x/help/csr.evaluation.delete b/src/clis/spx/.x/help/csr.evaluation.delete
similarity index 100%
rename from src/spx/.x/help/csr.evaluation.delete
rename to src/clis/spx/.x/help/csr.evaluation.delete
diff --git a/src/spx/.x/help/csr.evaluation.examples b/src/clis/spx/.x/help/csr.evaluation.examples
similarity index 100%
rename from src/spx/.x/help/csr.evaluation.examples
rename to src/clis/spx/.x/help/csr.evaluation.examples
diff --git a/src/spx/.x/help/csr.evaluation.list b/src/clis/spx/.x/help/csr.evaluation.list
similarity index 100%
rename from src/spx/.x/help/csr.evaluation.list
rename to src/clis/spx/.x/help/csr.evaluation.list
diff --git a/src/spx/.x/help/csr.evaluation.status b/src/clis/spx/.x/help/csr.evaluation.status
similarity index 100%
rename from src/spx/.x/help/csr.evaluation.status
rename to src/clis/spx/.x/help/csr.evaluation.status
diff --git a/src/spx/.x/help/csr.evaluation.update b/src/clis/spx/.x/help/csr.evaluation.update
similarity index 100%
rename from src/spx/.x/help/csr.evaluation.update
rename to src/clis/spx/.x/help/csr.evaluation.update
diff --git a/src/spx/.x/help/csr.examples b/src/clis/spx/.x/help/csr.examples
similarity index 100%
rename from src/spx/.x/help/csr.examples
rename to src/clis/spx/.x/help/csr.examples
diff --git a/src/spx/.x/help/csr.foreach b/src/clis/spx/.x/help/csr.foreach
similarity index 100%
rename from src/spx/.x/help/csr.foreach
rename to src/clis/spx/.x/help/csr.foreach
diff --git a/src/spx/.x/help/csr.input.path b/src/clis/spx/.x/help/csr.input.path
similarity index 100%
rename from src/spx/.x/help/csr.input.path
rename to src/clis/spx/.x/help/csr.input.path
diff --git a/src/spx/.x/help/csr.key b/src/clis/spx/.x/help/csr.key
similarity index 100%
rename from src/spx/.x/help/csr.key
rename to src/clis/spx/.x/help/csr.key
diff --git a/src/spx/.x/help/csr.list b/src/clis/spx/.x/help/csr.list
similarity index 100%
rename from src/spx/.x/help/csr.list
rename to src/clis/spx/.x/help/csr.list
diff --git a/src/spx/.x/help/csr.list.base.models b/src/clis/spx/.x/help/csr.list.base.models
similarity index 100%
rename from src/spx/.x/help/csr.list.base.models
rename to src/clis/spx/.x/help/csr.list.base.models
diff --git a/src/spx/.x/help/csr.list.dataset.languages b/src/clis/spx/.x/help/csr.list.dataset.languages
similarity index 100%
rename from src/spx/.x/help/csr.list.dataset.languages
rename to src/clis/spx/.x/help/csr.list.dataset.languages
diff --git a/src/spx/.x/help/csr.list.datasets b/src/clis/spx/.x/help/csr.list.datasets
similarity index 100%
rename from src/spx/.x/help/csr.list.datasets
rename to src/clis/spx/.x/help/csr.list.datasets
diff --git a/src/spx/.x/help/csr.list.endpoint.languages b/src/clis/spx/.x/help/csr.list.endpoint.languages
similarity index 100%
rename from src/spx/.x/help/csr.list.endpoint.languages
rename to src/clis/spx/.x/help/csr.list.endpoint.languages
diff --git a/src/spx/.x/help/csr.list.endpoints b/src/clis/spx/.x/help/csr.list.endpoints
similarity index 100%
rename from src/spx/.x/help/csr.list.endpoints
rename to src/clis/spx/.x/help/csr.list.endpoints
diff --git a/src/spx/.x/help/csr.list.model.languages b/src/clis/spx/.x/help/csr.list.model.languages
similarity index 100%
rename from src/spx/.x/help/csr.list.model.languages
rename to src/clis/spx/.x/help/csr.list.model.languages
diff --git a/src/spx/.x/help/csr.list.models b/src/clis/spx/.x/help/csr.list.models
similarity index 100%
rename from src/spx/.x/help/csr.list.models
rename to src/clis/spx/.x/help/csr.list.models
diff --git a/src/spx/.x/help/csr.list.project.languages b/src/clis/spx/.x/help/csr.list.project.languages
similarity index 100%
rename from src/spx/.x/help/csr.list.project.languages
rename to src/clis/spx/.x/help/csr.list.project.languages
diff --git a/src/spx/.x/help/csr.list.projects b/src/clis/spx/.x/help/csr.list.projects
similarity index 100%
rename from src/spx/.x/help/csr.list.projects
rename to src/clis/spx/.x/help/csr.list.projects
diff --git a/src/spx/.x/help/csr.model b/src/clis/spx/.x/help/csr.model
similarity index 100%
rename from src/spx/.x/help/csr.model
rename to src/clis/spx/.x/help/csr.model
diff --git a/src/spx/.x/help/csr.model.copy b/src/clis/spx/.x/help/csr.model.copy
similarity index 100%
rename from src/spx/.x/help/csr.model.copy
rename to src/clis/spx/.x/help/csr.model.copy
diff --git a/src/spx/.x/help/csr.model.copy.target b/src/clis/spx/.x/help/csr.model.copy.target
similarity index 100%
rename from src/spx/.x/help/csr.model.copy.target
rename to src/clis/spx/.x/help/csr.model.copy.target
diff --git a/src/spx/.x/help/csr.model.create b/src/clis/spx/.x/help/csr.model.create
similarity index 100%
rename from src/spx/.x/help/csr.model.create
rename to src/clis/spx/.x/help/csr.model.create
diff --git a/src/spx/.x/help/csr.model.create.base b/src/clis/spx/.x/help/csr.model.create.base
similarity index 100%
rename from src/spx/.x/help/csr.model.create.base
rename to src/clis/spx/.x/help/csr.model.create.base
diff --git a/src/spx/.x/help/csr.model.create.dataset b/src/clis/spx/.x/help/csr.model.create.dataset
similarity index 100%
rename from src/spx/.x/help/csr.model.create.dataset
rename to src/clis/spx/.x/help/csr.model.create.dataset
diff --git a/src/spx/.x/help/csr.model.create.datasets b/src/clis/spx/.x/help/csr.model.create.datasets
similarity index 100%
rename from src/spx/.x/help/csr.model.create.datasets
rename to src/clis/spx/.x/help/csr.model.create.datasets
diff --git a/src/spx/.x/help/csr.model.create.language b/src/clis/spx/.x/help/csr.model.create.language
similarity index 100%
rename from src/spx/.x/help/csr.model.create.language
rename to src/clis/spx/.x/help/csr.model.create.language
diff --git a/src/spx/.x/help/csr.model.create.text b/src/clis/spx/.x/help/csr.model.create.text
similarity index 100%
rename from src/spx/.x/help/csr.model.create.text
rename to src/clis/spx/.x/help/csr.model.create.text
diff --git a/src/spx/.x/help/csr.model.delete b/src/clis/spx/.x/help/csr.model.delete
similarity index 100%
rename from src/spx/.x/help/csr.model.delete
rename to src/clis/spx/.x/help/csr.model.delete
diff --git a/src/spx/.x/help/csr.model.delete.model b/src/clis/spx/.x/help/csr.model.delete.model
similarity index 100%
rename from src/spx/.x/help/csr.model.delete.model
rename to src/clis/spx/.x/help/csr.model.delete.model
diff --git a/src/spx/.x/help/csr.model.description b/src/clis/spx/.x/help/csr.model.description
similarity index 100%
rename from src/spx/.x/help/csr.model.description
rename to src/clis/spx/.x/help/csr.model.description
diff --git a/src/spx/.x/help/csr.model.examples b/src/clis/spx/.x/help/csr.model.examples
similarity index 100%
rename from src/spx/.x/help/csr.model.examples
rename to src/clis/spx/.x/help/csr.model.examples
diff --git a/src/spx/.x/help/csr.model.list b/src/clis/spx/.x/help/csr.model.list
similarity index 100%
rename from src/spx/.x/help/csr.model.list
rename to src/clis/spx/.x/help/csr.model.list
diff --git a/src/spx/.x/help/csr.model.list.base.models b/src/clis/spx/.x/help/csr.model.list.base.models
similarity index 100%
rename from src/spx/.x/help/csr.model.list.base.models
rename to src/clis/spx/.x/help/csr.model.list.base.models
diff --git a/src/spx/.x/help/csr.model.list.model.languages b/src/clis/spx/.x/help/csr.model.list.model.languages
similarity index 100%
rename from src/spx/.x/help/csr.model.list.model.languages
rename to src/clis/spx/.x/help/csr.model.list.model.languages
diff --git a/src/spx/.x/help/csr.model.list.model.project b/src/clis/spx/.x/help/csr.model.list.model.project
similarity index 100%
rename from src/spx/.x/help/csr.model.list.model.project
rename to src/clis/spx/.x/help/csr.model.list.model.project
diff --git a/src/spx/.x/help/csr.model.list.models b/src/clis/spx/.x/help/csr.model.list.models
similarity index 100%
rename from src/spx/.x/help/csr.model.list.models
rename to src/clis/spx/.x/help/csr.model.list.models
diff --git a/src/spx/.x/help/csr.model.name b/src/clis/spx/.x/help/csr.model.name
similarity index 100%
rename from src/spx/.x/help/csr.model.name
rename to src/clis/spx/.x/help/csr.model.name
diff --git a/src/spx/.x/help/csr.model.project b/src/clis/spx/.x/help/csr.model.project
similarity index 100%
rename from src/spx/.x/help/csr.model.project
rename to src/clis/spx/.x/help/csr.model.project
diff --git a/src/spx/.x/help/csr.model.status b/src/clis/spx/.x/help/csr.model.status
similarity index 100%
rename from src/spx/.x/help/csr.model.status
rename to src/clis/spx/.x/help/csr.model.status
diff --git a/src/spx/.x/help/csr.model.status.model b/src/clis/spx/.x/help/csr.model.status.model
similarity index 100%
rename from src/spx/.x/help/csr.model.status.model
rename to src/clis/spx/.x/help/csr.model.status.model
diff --git a/src/spx/.x/help/csr.model.update b/src/clis/spx/.x/help/csr.model.update
similarity index 100%
rename from src/spx/.x/help/csr.model.update
rename to src/clis/spx/.x/help/csr.model.update
diff --git a/src/spx/.x/help/csr.model.update.model b/src/clis/spx/.x/help/csr.model.update.model
similarity index 100%
rename from src/spx/.x/help/csr.model.update.model
rename to src/clis/spx/.x/help/csr.model.update.model
diff --git a/src/spx/.x/help/csr.more.examples b/src/clis/spx/.x/help/csr.more.examples
similarity index 100%
rename from src/spx/.x/help/csr.more.examples
rename to src/clis/spx/.x/help/csr.more.examples
diff --git a/src/spx/.x/help/csr.output.file b/src/clis/spx/.x/help/csr.output.file
similarity index 100%
rename from src/spx/.x/help/csr.output.file
rename to src/clis/spx/.x/help/csr.output.file
diff --git a/src/spx/.x/help/csr.output.id b/src/clis/spx/.x/help/csr.output.id
similarity index 100%
rename from src/spx/.x/help/csr.output.id
rename to src/clis/spx/.x/help/csr.output.id
diff --git a/src/spx/.x/help/csr.output.json b/src/clis/spx/.x/help/csr.output.json
similarity index 100%
rename from src/spx/.x/help/csr.output.json
rename to src/clis/spx/.x/help/csr.output.json
diff --git a/src/spx/.x/help/csr.output.path b/src/clis/spx/.x/help/csr.output.path
similarity index 100%
rename from src/spx/.x/help/csr.output.path
rename to src/clis/spx/.x/help/csr.output.path
diff --git a/src/spx/.x/help/csr.output.url b/src/clis/spx/.x/help/csr.output.url
similarity index 100%
rename from src/spx/.x/help/csr.output.url
rename to src/clis/spx/.x/help/csr.output.url
diff --git a/src/spx/.x/help/csr.project b/src/clis/spx/.x/help/csr.project
similarity index 100%
rename from src/spx/.x/help/csr.project
rename to src/clis/spx/.x/help/csr.project
diff --git a/src/spx/.x/help/csr.project.create b/src/clis/spx/.x/help/csr.project.create
similarity index 100%
rename from src/spx/.x/help/csr.project.create
rename to src/clis/spx/.x/help/csr.project.create
diff --git a/src/spx/.x/help/csr.project.create.language b/src/clis/spx/.x/help/csr.project.create.language
similarity index 100%
rename from src/spx/.x/help/csr.project.create.language
rename to src/clis/spx/.x/help/csr.project.create.language
diff --git a/src/spx/.x/help/csr.project.delete b/src/clis/spx/.x/help/csr.project.delete
similarity index 100%
rename from src/spx/.x/help/csr.project.delete
rename to src/clis/spx/.x/help/csr.project.delete
diff --git a/src/spx/.x/help/csr.project.delete.project b/src/clis/spx/.x/help/csr.project.delete.project
similarity index 100%
rename from src/spx/.x/help/csr.project.delete.project
rename to src/clis/spx/.x/help/csr.project.delete.project
diff --git a/src/spx/.x/help/csr.project.description b/src/clis/spx/.x/help/csr.project.description
similarity index 100%
rename from src/spx/.x/help/csr.project.description
rename to src/clis/spx/.x/help/csr.project.description
diff --git a/src/spx/.x/help/csr.project.examples b/src/clis/spx/.x/help/csr.project.examples
similarity index 100%
rename from src/spx/.x/help/csr.project.examples
rename to src/clis/spx/.x/help/csr.project.examples
diff --git a/src/spx/.x/help/csr.project.list b/src/clis/spx/.x/help/csr.project.list
similarity index 100%
rename from src/spx/.x/help/csr.project.list
rename to src/clis/spx/.x/help/csr.project.list
diff --git a/src/spx/.x/help/csr.project.list.project.languages b/src/clis/spx/.x/help/csr.project.list.project.languages
similarity index 100%
rename from src/spx/.x/help/csr.project.list.project.languages
rename to src/clis/spx/.x/help/csr.project.list.project.languages
diff --git a/src/spx/.x/help/csr.project.list.projects b/src/clis/spx/.x/help/csr.project.list.projects
similarity index 100%
rename from src/spx/.x/help/csr.project.list.projects
rename to src/clis/spx/.x/help/csr.project.list.projects
diff --git a/src/spx/.x/help/csr.project.name b/src/clis/spx/.x/help/csr.project.name
similarity index 100%
rename from src/spx/.x/help/csr.project.name
rename to src/clis/spx/.x/help/csr.project.name
diff --git a/src/spx/.x/help/csr.project.status b/src/clis/spx/.x/help/csr.project.status
similarity index 100%
rename from src/spx/.x/help/csr.project.status
rename to src/clis/spx/.x/help/csr.project.status
diff --git a/src/spx/.x/help/csr.project.status.project b/src/clis/spx/.x/help/csr.project.status.project
similarity index 100%
rename from src/spx/.x/help/csr.project.status.project
rename to src/clis/spx/.x/help/csr.project.status.project
diff --git a/src/spx/.x/help/csr.project.update b/src/clis/spx/.x/help/csr.project.update
similarity index 100%
rename from src/spx/.x/help/csr.project.update
rename to src/clis/spx/.x/help/csr.project.update
diff --git a/src/spx/.x/help/csr.project.update.project b/src/clis/spx/.x/help/csr.project.update.project
similarity index 100%
rename from src/spx/.x/help/csr.project.update.project
rename to src/clis/spx/.x/help/csr.project.update.project
diff --git a/src/spx/.x/help/csr.region b/src/clis/spx/.x/help/csr.region
similarity index 100%
rename from src/spx/.x/help/csr.region
rename to src/clis/spx/.x/help/csr.region
diff --git a/src/spx/.x/help/csr.save b/src/clis/spx/.x/help/csr.save
similarity index 100%
rename from src/spx/.x/help/csr.save
rename to src/clis/spx/.x/help/csr.save
diff --git a/src/spx/.x/help/csr.url b/src/clis/spx/.x/help/csr.url
similarity index 100%
rename from src/spx/.x/help/csr.url
rename to src/clis/spx/.x/help/csr.url
diff --git a/src/spx/.x/help/csr.wait b/src/clis/spx/.x/help/csr.wait
similarity index 100%
rename from src/spx/.x/help/csr.wait
rename to src/clis/spx/.x/help/csr.wait
diff --git a/src/spx/.x/help/csr.zip b/src/clis/spx/.x/help/csr.zip
similarity index 100%
rename from src/spx/.x/help/csr.zip
rename to src/clis/spx/.x/help/csr.zip
diff --git a/src/spx/.x/help/customization b/src/clis/spx/.x/help/customization
similarity index 100%
rename from src/spx/.x/help/customization
rename to src/clis/spx/.x/help/customization
diff --git a/src/spx/.x/help/customize.categoryid b/src/clis/spx/.x/help/customize.categoryid
similarity index 100%
rename from src/spx/.x/help/customize.categoryid
rename to src/clis/spx/.x/help/customize.categoryid
diff --git a/src/spx/.x/help/customize.endpointid b/src/clis/spx/.x/help/customize.endpointid
similarity index 100%
rename from src/spx/.x/help/customize.endpointid
rename to src/clis/spx/.x/help/customize.endpointid
diff --git a/src/spx/.x/help/defaults b/src/clis/spx/.x/help/defaults
similarity index 100%
rename from src/spx/.x/help/defaults
rename to src/clis/spx/.x/help/defaults
diff --git a/src/spx/.x/help/dialog b/src/clis/spx/.x/help/dialog
similarity index 100%
rename from src/spx/.x/help/dialog
rename to src/clis/spx/.x/help/dialog
diff --git a/src/spx/.x/help/dialog.advanced b/src/clis/spx/.x/help/dialog.advanced
similarity index 100%
rename from src/spx/.x/help/dialog.advanced
rename to src/clis/spx/.x/help/dialog.advanced
diff --git a/src/spx/.x/help/dialog.bot b/src/clis/spx/.x/help/dialog.bot
similarity index 100%
rename from src/spx/.x/help/dialog.bot
rename to src/clis/spx/.x/help/dialog.bot
diff --git a/src/spx/.x/help/dialog.bot.continuous b/src/clis/spx/.x/help/dialog.bot.continuous
similarity index 100%
rename from src/spx/.x/help/dialog.bot.continuous
rename to src/clis/spx/.x/help/dialog.bot.continuous
diff --git a/src/spx/.x/help/dialog.bot.id b/src/clis/spx/.x/help/dialog.bot.id
similarity index 100%
rename from src/spx/.x/help/dialog.bot.id
rename to src/clis/spx/.x/help/dialog.bot.id
diff --git a/src/spx/.x/help/dialog.bot.input b/src/clis/spx/.x/help/dialog.bot.input
similarity index 100%
rename from src/spx/.x/help/dialog.bot.input
rename to src/clis/spx/.x/help/dialog.bot.input
diff --git a/src/spx/.x/help/dialog.bot.keyword b/src/clis/spx/.x/help/dialog.bot.keyword
similarity index 100%
rename from src/spx/.x/help/dialog.bot.keyword
rename to src/clis/spx/.x/help/dialog.bot.keyword
diff --git a/src/spx/.x/help/dialog.bot.once b/src/clis/spx/.x/help/dialog.bot.once
similarity index 100%
rename from src/spx/.x/help/dialog.bot.once
rename to src/clis/spx/.x/help/dialog.bot.once
diff --git a/src/spx/.x/help/dialog.bot.output b/src/clis/spx/.x/help/dialog.bot.output
similarity index 100%
rename from src/spx/.x/help/dialog.bot.output
rename to src/clis/spx/.x/help/dialog.bot.output
diff --git a/src/spx/.x/help/dialog.cc b/src/clis/spx/.x/help/dialog.cc
similarity index 100%
rename from src/spx/.x/help/dialog.cc
rename to src/clis/spx/.x/help/dialog.cc
diff --git a/src/spx/.x/help/dialog.customcommands b/src/clis/spx/.x/help/dialog.customcommands
similarity index 100%
rename from src/spx/.x/help/dialog.customcommands
rename to src/clis/spx/.x/help/dialog.customcommands
diff --git a/src/spx/.x/help/dialog.customcommands.appid b/src/clis/spx/.x/help/dialog.customcommands.appid
similarity index 100%
rename from src/spx/.x/help/dialog.customcommands.appid
rename to src/clis/spx/.x/help/dialog.customcommands.appid
diff --git a/src/spx/.x/help/dialog.customcommands.continuous b/src/clis/spx/.x/help/dialog.customcommands.continuous
similarity index 100%
rename from src/spx/.x/help/dialog.customcommands.continuous
rename to src/clis/spx/.x/help/dialog.customcommands.continuous
diff --git a/src/spx/.x/help/dialog.customcommands.input b/src/clis/spx/.x/help/dialog.customcommands.input
similarity index 100%
rename from src/spx/.x/help/dialog.customcommands.input
rename to src/clis/spx/.x/help/dialog.customcommands.input
diff --git a/src/spx/.x/help/dialog.customcommands.keyword b/src/clis/spx/.x/help/dialog.customcommands.keyword
similarity index 100%
rename from src/spx/.x/help/dialog.customcommands.keyword
rename to src/clis/spx/.x/help/dialog.customcommands.keyword
diff --git a/src/spx/.x/help/dialog.customcommands.once b/src/clis/spx/.x/help/dialog.customcommands.once
similarity index 100%
rename from src/spx/.x/help/dialog.customcommands.once
rename to src/clis/spx/.x/help/dialog.customcommands.once
diff --git a/src/spx/.x/help/dialog.customcommands.output b/src/clis/spx/.x/help/dialog.customcommands.output
similarity index 100%
rename from src/spx/.x/help/dialog.customcommands.output
rename to src/clis/spx/.x/help/dialog.customcommands.output
diff --git a/src/spx/.x/help/dialog.examples b/src/clis/spx/.x/help/dialog.examples
similarity index 100%
rename from src/spx/.x/help/dialog.examples
rename to src/clis/spx/.x/help/dialog.examples
diff --git a/src/spx/.x/help/display.post.processing b/src/clis/spx/.x/help/display.post.processing
similarity index 100%
rename from src/spx/.x/help/display.post.processing
rename to src/clis/spx/.x/help/display.post.processing
diff --git a/src/spx/.x/help/documentation b/src/clis/spx/.x/help/documentation
similarity index 100%
rename from src/spx/.x/help/documentation
rename to src/clis/spx/.x/help/documentation
diff --git a/src/spx/.x/help/endpoint b/src/clis/spx/.x/help/endpoint
similarity index 100%
rename from src/spx/.x/help/endpoint
rename to src/clis/spx/.x/help/endpoint
diff --git a/src/spx/.x/help/endpoint.examples b/src/clis/spx/.x/help/endpoint.examples
similarity index 100%
rename from src/spx/.x/help/endpoint.examples
rename to src/clis/spx/.x/help/endpoint.examples
diff --git a/src/spx/.x/help/examples b/src/clis/spx/.x/help/examples
similarity index 100%
rename from src/spx/.x/help/examples
rename to src/clis/spx/.x/help/examples
diff --git a/src/spx/.x/help/foreach b/src/clis/spx/.x/help/foreach
similarity index 100%
rename from src/spx/.x/help/foreach
rename to src/clis/spx/.x/help/foreach
diff --git a/src/spx/.x/help/help b/src/clis/spx/.x/help/help
similarity index 100%
rename from src/spx/.x/help/help
rename to src/clis/spx/.x/help/help
diff --git a/src/spx/.x/help/hive b/src/clis/spx/.x/help/hive
similarity index 100%
rename from src/spx/.x/help/hive
rename to src/clis/spx/.x/help/hive
diff --git a/src/spx/.x/help/include.create.resource b/src/clis/spx/.x/help/include.create.resource
similarity index 100%
rename from src/spx/.x/help/include.create.resource
rename to src/clis/spx/.x/help/include.create.resource
diff --git a/src/spx/.x/help/include.create.resource.extra b/src/clis/spx/.x/help/include.create.resource.extra
similarity index 100%
rename from src/spx/.x/help/include.create.resource.extra
rename to src/clis/spx/.x/help/include.create.resource.extra
diff --git a/src/spx/.x/help/include.spx.ascii.logo b/src/clis/spx/.x/help/include.spx.ascii.logo
similarity index 100%
rename from src/spx/.x/help/include.spx.ascii.logo
rename to src/clis/spx/.x/help/include.spx.ascii.logo
diff --git a/src/spx/.x/help/include.spx.init.ascii.logo b/src/clis/spx/.x/help/include.spx.init.ascii.logo
similarity index 100%
rename from src/spx/.x/help/include.spx.init.ascii.logo
rename to src/clis/spx/.x/help/include.spx.init.ascii.logo
diff --git a/src/spx/.x/help/include.the.azure.speech.service b/src/clis/spx/.x/help/include.the.azure.speech.service
similarity index 100%
rename from src/spx/.x/help/include.the.azure.speech.service
rename to src/clis/spx/.x/help/include.the.azure.speech.service
diff --git a/src/spx/.x/help/include.the.azure.speech.service.long b/src/clis/spx/.x/help/include.the.azure.speech.service.long
similarity index 100%
rename from src/spx/.x/help/include.the.azure.speech.service.long
rename to src/clis/spx/.x/help/include.the.azure.speech.service.long
diff --git a/src/spx/.x/help/include.the.config.command b/src/clis/spx/.x/help/include.the.config.command
similarity index 100%
rename from src/spx/.x/help/include.the.config.command
rename to src/clis/spx/.x/help/include.the.config.command
diff --git a/src/spx/.x/help/include.the.csr.dataset.command b/src/clis/spx/.x/help/include.the.csr.dataset.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.dataset.command
rename to src/clis/spx/.x/help/include.the.csr.dataset.command
diff --git a/src/spx/.x/help/include.the.csr.dataset.create.command b/src/clis/spx/.x/help/include.the.csr.dataset.create.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.dataset.create.command
rename to src/clis/spx/.x/help/include.the.csr.dataset.create.command
diff --git a/src/spx/.x/help/include.the.csr.dataset.delete.command b/src/clis/spx/.x/help/include.the.csr.dataset.delete.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.dataset.delete.command
rename to src/clis/spx/.x/help/include.the.csr.dataset.delete.command
diff --git a/src/spx/.x/help/include.the.csr.dataset.download.command b/src/clis/spx/.x/help/include.the.csr.dataset.download.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.dataset.download.command
rename to src/clis/spx/.x/help/include.the.csr.dataset.download.command
diff --git a/src/spx/.x/help/include.the.csr.dataset.list.command b/src/clis/spx/.x/help/include.the.csr.dataset.list.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.dataset.list.command
rename to src/clis/spx/.x/help/include.the.csr.dataset.list.command
diff --git a/src/spx/.x/help/include.the.csr.dataset.status.command b/src/clis/spx/.x/help/include.the.csr.dataset.status.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.dataset.status.command
rename to src/clis/spx/.x/help/include.the.csr.dataset.status.command
diff --git a/src/spx/.x/help/include.the.csr.dataset.update.command b/src/clis/spx/.x/help/include.the.csr.dataset.update.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.dataset.update.command
rename to src/clis/spx/.x/help/include.the.csr.dataset.update.command
diff --git a/src/spx/.x/help/include.the.csr.dataset.upload.command b/src/clis/spx/.x/help/include.the.csr.dataset.upload.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.dataset.upload.command
rename to src/clis/spx/.x/help/include.the.csr.dataset.upload.command
diff --git a/src/spx/.x/help/include.the.csr.endpoint.command b/src/clis/spx/.x/help/include.the.csr.endpoint.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.endpoint.command
rename to src/clis/spx/.x/help/include.the.csr.endpoint.command
diff --git a/src/spx/.x/help/include.the.csr.endpoint.create.command b/src/clis/spx/.x/help/include.the.csr.endpoint.create.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.endpoint.create.command
rename to src/clis/spx/.x/help/include.the.csr.endpoint.create.command
diff --git a/src/spx/.x/help/include.the.csr.endpoint.delete.command b/src/clis/spx/.x/help/include.the.csr.endpoint.delete.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.endpoint.delete.command
rename to src/clis/spx/.x/help/include.the.csr.endpoint.delete.command
diff --git a/src/spx/.x/help/include.the.csr.endpoint.download.command b/src/clis/spx/.x/help/include.the.csr.endpoint.download.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.endpoint.download.command
rename to src/clis/spx/.x/help/include.the.csr.endpoint.download.command
diff --git a/src/spx/.x/help/include.the.csr.endpoint.list.command b/src/clis/spx/.x/help/include.the.csr.endpoint.list.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.endpoint.list.command
rename to src/clis/spx/.x/help/include.the.csr.endpoint.list.command
diff --git a/src/spx/.x/help/include.the.csr.endpoint.status.command b/src/clis/spx/.x/help/include.the.csr.endpoint.status.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.endpoint.status.command
rename to src/clis/spx/.x/help/include.the.csr.endpoint.status.command
diff --git a/src/spx/.x/help/include.the.csr.endpoint.update.command b/src/clis/spx/.x/help/include.the.csr.endpoint.update.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.endpoint.update.command
rename to src/clis/spx/.x/help/include.the.csr.endpoint.update.command
diff --git a/src/spx/.x/help/include.the.csr.evaluation.command b/src/clis/spx/.x/help/include.the.csr.evaluation.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.evaluation.command
rename to src/clis/spx/.x/help/include.the.csr.evaluation.command
diff --git a/src/spx/.x/help/include.the.csr.evaluation.create.command b/src/clis/spx/.x/help/include.the.csr.evaluation.create.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.evaluation.create.command
rename to src/clis/spx/.x/help/include.the.csr.evaluation.create.command
diff --git a/src/spx/.x/help/include.the.csr.evaluation.delete.command b/src/clis/spx/.x/help/include.the.csr.evaluation.delete.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.evaluation.delete.command
rename to src/clis/spx/.x/help/include.the.csr.evaluation.delete.command
diff --git a/src/spx/.x/help/include.the.csr.evaluation.list.command b/src/clis/spx/.x/help/include.the.csr.evaluation.list.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.evaluation.list.command
rename to src/clis/spx/.x/help/include.the.csr.evaluation.list.command
diff --git a/src/spx/.x/help/include.the.csr.evaluation.status.command b/src/clis/spx/.x/help/include.the.csr.evaluation.status.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.evaluation.status.command
rename to src/clis/spx/.x/help/include.the.csr.evaluation.status.command
diff --git a/src/spx/.x/help/include.the.csr.evaluation.update.command b/src/clis/spx/.x/help/include.the.csr.evaluation.update.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.evaluation.update.command
rename to src/clis/spx/.x/help/include.the.csr.evaluation.update.command
diff --git a/src/spx/.x/help/include.the.csr.model.command b/src/clis/spx/.x/help/include.the.csr.model.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.model.command
rename to src/clis/spx/.x/help/include.the.csr.model.command
diff --git a/src/spx/.x/help/include.the.csr.model.copy.command b/src/clis/spx/.x/help/include.the.csr.model.copy.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.model.copy.command
rename to src/clis/spx/.x/help/include.the.csr.model.copy.command
diff --git a/src/spx/.x/help/include.the.csr.model.create.command b/src/clis/spx/.x/help/include.the.csr.model.create.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.model.create.command
rename to src/clis/spx/.x/help/include.the.csr.model.create.command
diff --git a/src/spx/.x/help/include.the.csr.model.delete.command b/src/clis/spx/.x/help/include.the.csr.model.delete.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.model.delete.command
rename to src/clis/spx/.x/help/include.the.csr.model.delete.command
diff --git a/src/spx/.x/help/include.the.csr.model.status.command b/src/clis/spx/.x/help/include.the.csr.model.status.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.model.status.command
rename to src/clis/spx/.x/help/include.the.csr.model.status.command
diff --git a/src/spx/.x/help/include.the.csr.model.update.command b/src/clis/spx/.x/help/include.the.csr.model.update.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.model.update.command
rename to src/clis/spx/.x/help/include.the.csr.model.update.command
diff --git a/src/spx/.x/help/include.the.csr.project.command b/src/clis/spx/.x/help/include.the.csr.project.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.project.command
rename to src/clis/spx/.x/help/include.the.csr.project.command
diff --git a/src/spx/.x/help/include.the.csr.project.create.command b/src/clis/spx/.x/help/include.the.csr.project.create.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.project.create.command
rename to src/clis/spx/.x/help/include.the.csr.project.create.command
diff --git a/src/spx/.x/help/include.the.csr.project.delete.command b/src/clis/spx/.x/help/include.the.csr.project.delete.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.project.delete.command
rename to src/clis/spx/.x/help/include.the.csr.project.delete.command
diff --git a/src/spx/.x/help/include.the.csr.project.list.command b/src/clis/spx/.x/help/include.the.csr.project.list.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.project.list.command
rename to src/clis/spx/.x/help/include.the.csr.project.list.command
diff --git a/src/spx/.x/help/include.the.csr.project.status.command b/src/clis/spx/.x/help/include.the.csr.project.status.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.project.status.command
rename to src/clis/spx/.x/help/include.the.csr.project.status.command
diff --git a/src/spx/.x/help/include.the.csr.project.update.command b/src/clis/spx/.x/help/include.the.csr.project.update.command
similarity index 100%
rename from src/spx/.x/help/include.the.csr.project.update.command
rename to src/clis/spx/.x/help/include.the.csr.project.update.command
diff --git a/src/spx/.x/help/include.the.dialog.command b/src/clis/spx/.x/help/include.the.dialog.command
similarity index 100%
rename from src/spx/.x/help/include.the.dialog.command
rename to src/clis/spx/.x/help/include.the.dialog.command
diff --git a/src/spx/.x/help/include.the.dialog.command.long b/src/clis/spx/.x/help/include.the.dialog.command.long
similarity index 100%
rename from src/spx/.x/help/include.the.dialog.command.long
rename to src/clis/spx/.x/help/include.the.dialog.command.long
diff --git a/src/spx/.x/help/include.the.init.command.long b/src/clis/spx/.x/help/include.the.init.command.long
similarity index 100%
rename from src/spx/.x/help/include.the.init.command.long
rename to src/clis/spx/.x/help/include.the.init.command.long
diff --git a/src/spx/.x/help/include.the.intent.command b/src/clis/spx/.x/help/include.the.intent.command
similarity index 100%
rename from src/spx/.x/help/include.the.intent.command
rename to src/clis/spx/.x/help/include.the.intent.command
diff --git a/src/spx/.x/help/include.the.intent.command.long b/src/clis/spx/.x/help/include.the.intent.command.long
similarity index 100%
rename from src/spx/.x/help/include.the.intent.command.long
rename to src/clis/spx/.x/help/include.the.intent.command.long
diff --git a/src/spx/.x/help/include.the.intent.method b/src/clis/spx/.x/help/include.the.intent.method
similarity index 100%
rename from src/spx/.x/help/include.the.intent.method
rename to src/clis/spx/.x/help/include.the.intent.method
diff --git a/src/spx/.x/help/include.the.key.option b/src/clis/spx/.x/help/include.the.key.option
similarity index 100%
rename from src/spx/.x/help/include.the.key.option
rename to src/clis/spx/.x/help/include.the.key.option
diff --git a/src/spx/.x/help/include.the.key.option.where b/src/clis/spx/.x/help/include.the.key.option.where
similarity index 100%
rename from src/spx/.x/help/include.the.key.option.where
rename to src/clis/spx/.x/help/include.the.key.option.where
diff --git a/src/spx/.x/help/include.the.log.option b/src/clis/spx/.x/help/include.the.log.option
similarity index 100%
rename from src/spx/.x/help/include.the.log.option
rename to src/clis/spx/.x/help/include.the.log.option
diff --git a/src/spx/.x/help/include.the.luis.option b/src/clis/spx/.x/help/include.the.luis.option
similarity index 100%
rename from src/spx/.x/help/include.the.luis.option
rename to src/clis/spx/.x/help/include.the.luis.option
diff --git a/src/spx/.x/help/include.the.model.list.command b/src/clis/spx/.x/help/include.the.model.list.command
similarity index 100%
rename from src/spx/.x/help/include.the.model.list.command
rename to src/clis/spx/.x/help/include.the.model.list.command
diff --git a/src/spx/.x/help/include.the.profile.command b/src/clis/spx/.x/help/include.the.profile.command
similarity index 100%
rename from src/spx/.x/help/include.the.profile.command
rename to src/clis/spx/.x/help/include.the.profile.command
diff --git a/src/spx/.x/help/include.the.profile.command.long b/src/clis/spx/.x/help/include.the.profile.command.long
similarity index 100%
rename from src/spx/.x/help/include.the.profile.command.long
rename to src/clis/spx/.x/help/include.the.profile.command.long
diff --git a/src/spx/.x/help/include.the.recognize.command b/src/clis/spx/.x/help/include.the.recognize.command
similarity index 100%
rename from src/spx/.x/help/include.the.recognize.command
rename to src/clis/spx/.x/help/include.the.recognize.command
diff --git a/src/spx/.x/help/include.the.recognize.command.long b/src/clis/spx/.x/help/include.the.recognize.command.long
similarity index 100%
rename from src/spx/.x/help/include.the.recognize.command.long
rename to src/clis/spx/.x/help/include.the.recognize.command.long
diff --git a/src/spx/.x/help/include.the.recognize.method b/src/clis/spx/.x/help/include.the.recognize.method
similarity index 100%
rename from src/spx/.x/help/include.the.recognize.method
rename to src/clis/spx/.x/help/include.the.recognize.method
diff --git a/src/spx/.x/help/include.the.region.option b/src/clis/spx/.x/help/include.the.region.option
similarity index 100%
rename from src/spx/.x/help/include.the.region.option
rename to src/clis/spx/.x/help/include.the.region.option
diff --git a/src/spx/.x/help/include.the.region.option.where b/src/clis/spx/.x/help/include.the.region.option.where
similarity index 100%
rename from src/spx/.x/help/include.the.region.option.where
rename to src/clis/spx/.x/help/include.the.region.option.where
diff --git a/src/spx/.x/help/include.the.run.command b/src/clis/spx/.x/help/include.the.run.command
similarity index 100%
rename from src/spx/.x/help/include.the.run.command
rename to src/clis/spx/.x/help/include.the.run.command
diff --git a/src/spx/.x/help/include.the.save.option b/src/clis/spx/.x/help/include.the.save.option
similarity index 100%
rename from src/spx/.x/help/include.the.save.option
rename to src/clis/spx/.x/help/include.the.save.option
diff --git a/src/spx/.x/help/include.the.speaker.command b/src/clis/spx/.x/help/include.the.speaker.command
similarity index 100%
rename from src/spx/.x/help/include.the.speaker.command
rename to src/clis/spx/.x/help/include.the.speaker.command
diff --git a/src/spx/.x/help/include.the.speaker.command.long b/src/clis/spx/.x/help/include.the.speaker.command.long
similarity index 100%
rename from src/spx/.x/help/include.the.speaker.command.long
rename to src/clis/spx/.x/help/include.the.speaker.command.long
diff --git a/src/spx/.x/help/include.the.synthesize.command b/src/clis/spx/.x/help/include.the.synthesize.command
similarity index 100%
rename from src/spx/.x/help/include.the.synthesize.command
rename to src/clis/spx/.x/help/include.the.synthesize.command
diff --git a/src/spx/.x/help/include.the.synthesize.command.long b/src/clis/spx/.x/help/include.the.synthesize.command.long
similarity index 100%
rename from src/spx/.x/help/include.the.synthesize.command.long
rename to src/clis/spx/.x/help/include.the.synthesize.command.long
diff --git a/src/spx/.x/help/include.the.token.option b/src/clis/spx/.x/help/include.the.token.option
similarity index 100%
rename from src/spx/.x/help/include.the.token.option
rename to src/clis/spx/.x/help/include.the.token.option
diff --git a/src/spx/.x/help/include.the.token.option.where b/src/clis/spx/.x/help/include.the.token.option.where
similarity index 100%
rename from src/spx/.x/help/include.the.token.option.where
rename to src/clis/spx/.x/help/include.the.token.option.where
diff --git a/src/spx/.x/help/include.the.translate.command b/src/clis/spx/.x/help/include.the.translate.command
similarity index 100%
rename from src/spx/.x/help/include.the.translate.command
rename to src/clis/spx/.x/help/include.the.translate.command
diff --git a/src/spx/.x/help/include.the.translate.command.long b/src/clis/spx/.x/help/include.the.translate.command.long
similarity index 100%
rename from src/spx/.x/help/include.the.translate.command.long
rename to src/clis/spx/.x/help/include.the.translate.command.long
diff --git a/src/spx/.x/help/include.the.webjob.command b/src/clis/spx/.x/help/include.the.webjob.command
similarity index 100%
rename from src/spx/.x/help/include.the.webjob.command
rename to src/clis/spx/.x/help/include.the.webjob.command
diff --git a/src/spx/.x/help/include.the.webjob.command.long b/src/clis/spx/.x/help/include.the.webjob.command.long
similarity index 100%
rename from src/spx/.x/help/include.the.webjob.command.long
rename to src/clis/spx/.x/help/include.the.webjob.command.long
diff --git a/src/spx/.x/help/include.the.zip.option b/src/clis/spx/.x/help/include.the.zip.option
similarity index 100%
rename from src/spx/.x/help/include.the.zip.option
rename to src/clis/spx/.x/help/include.the.zip.option
diff --git a/src/spx/.x/help/init b/src/clis/spx/.x/help/init
similarity index 100%
rename from src/spx/.x/help/init
rename to src/clis/spx/.x/help/init
diff --git a/src/spx/.x/help/init.advanced b/src/clis/spx/.x/help/init.advanced
similarity index 100%
rename from src/spx/.x/help/init.advanced
rename to src/clis/spx/.x/help/init.advanced
diff --git a/src/spx/.x/help/init.examples b/src/clis/spx/.x/help/init.examples
similarity index 100%
rename from src/spx/.x/help/init.examples
rename to src/clis/spx/.x/help/init.examples
diff --git a/src/spx/.x/help/input b/src/clis/spx/.x/help/input
similarity index 100%
rename from src/spx/.x/help/input
rename to src/clis/spx/.x/help/input
diff --git a/src/spx/.x/help/input.id b/src/clis/spx/.x/help/input.id
similarity index 100%
rename from src/spx/.x/help/input.id
rename to src/clis/spx/.x/help/input.id
diff --git a/src/spx/.x/help/intent b/src/clis/spx/.x/help/intent
similarity index 100%
rename from src/spx/.x/help/intent
rename to src/clis/spx/.x/help/intent
diff --git a/src/spx/.x/help/intent.advanced b/src/clis/spx/.x/help/intent.advanced
similarity index 100%
rename from src/spx/.x/help/intent.advanced
rename to src/clis/spx/.x/help/intent.advanced
diff --git a/src/spx/.x/help/intent.connection b/src/clis/spx/.x/help/intent.connection
similarity index 100%
rename from src/spx/.x/help/intent.connection
rename to src/clis/spx/.x/help/intent.connection
diff --git a/src/spx/.x/help/intent.continuous b/src/clis/spx/.x/help/intent.continuous
similarity index 100%
rename from src/spx/.x/help/intent.continuous
rename to src/clis/spx/.x/help/intent.continuous
diff --git a/src/spx/.x/help/intent.endpoint b/src/clis/spx/.x/help/intent.endpoint
similarity index 100%
rename from src/spx/.x/help/intent.endpoint
rename to src/clis/spx/.x/help/intent.endpoint
diff --git a/src/spx/.x/help/intent.examples b/src/clis/spx/.x/help/intent.examples
similarity index 100%
rename from src/spx/.x/help/intent.examples
rename to src/clis/spx/.x/help/intent.examples
diff --git a/src/spx/.x/help/intent.file b/src/clis/spx/.x/help/intent.file
similarity index 100%
rename from src/spx/.x/help/intent.file
rename to src/clis/spx/.x/help/intent.file
diff --git a/src/spx/.x/help/intent.files b/src/clis/spx/.x/help/intent.files
similarity index 100%
rename from src/spx/.x/help/intent.files
rename to src/clis/spx/.x/help/intent.files
diff --git a/src/spx/.x/help/intent.foreach b/src/clis/spx/.x/help/intent.foreach
similarity index 100%
rename from src/spx/.x/help/intent.foreach
rename to src/clis/spx/.x/help/intent.foreach
diff --git a/src/spx/.x/help/intent.format b/src/clis/spx/.x/help/intent.format
similarity index 100%
rename from src/spx/.x/help/intent.format
rename to src/clis/spx/.x/help/intent.format
diff --git a/src/spx/.x/help/intent.input b/src/clis/spx/.x/help/intent.input
similarity index 100%
rename from src/spx/.x/help/intent.input
rename to src/clis/spx/.x/help/intent.input
diff --git a/src/spx/.x/help/intent.key b/src/clis/spx/.x/help/intent.key
similarity index 100%
rename from src/spx/.x/help/intent.key
rename to src/clis/spx/.x/help/intent.key
diff --git a/src/spx/.x/help/intent.keyword b/src/clis/spx/.x/help/intent.keyword
similarity index 100%
rename from src/spx/.x/help/intent.keyword
rename to src/clis/spx/.x/help/intent.keyword
diff --git a/src/spx/.x/help/intent.language b/src/clis/spx/.x/help/intent.language
similarity index 100%
rename from src/spx/.x/help/intent.language
rename to src/clis/spx/.x/help/intent.language
diff --git a/src/spx/.x/help/intent.languages b/src/clis/spx/.x/help/intent.languages
similarity index 100%
rename from src/spx/.x/help/intent.languages
rename to src/clis/spx/.x/help/intent.languages
diff --git a/src/spx/.x/help/intent.log b/src/clis/spx/.x/help/intent.log
similarity index 100%
rename from src/spx/.x/help/intent.log
rename to src/clis/spx/.x/help/intent.log
diff --git a/src/spx/.x/help/intent.luis b/src/clis/spx/.x/help/intent.luis
similarity index 100%
rename from src/spx/.x/help/intent.luis
rename to src/clis/spx/.x/help/intent.luis
diff --git a/src/spx/.x/help/intent.luis.allintents b/src/clis/spx/.x/help/intent.luis.allintents
similarity index 100%
rename from src/spx/.x/help/intent.luis.allintents
rename to src/clis/spx/.x/help/intent.luis.allintents
diff --git a/src/spx/.x/help/intent.luis.intent b/src/clis/spx/.x/help/intent.luis.intent
similarity index 100%
rename from src/spx/.x/help/intent.luis.intent
rename to src/clis/spx/.x/help/intent.luis.intent
diff --git a/src/spx/.x/help/intent.luis.key b/src/clis/spx/.x/help/intent.luis.key
similarity index 100%
rename from src/spx/.x/help/intent.luis.key
rename to src/clis/spx/.x/help/intent.luis.key
diff --git a/src/spx/.x/help/intent.luis.region b/src/clis/spx/.x/help/intent.luis.region
similarity index 100%
rename from src/spx/.x/help/intent.luis.region
rename to src/clis/spx/.x/help/intent.luis.region
diff --git a/src/spx/.x/help/intent.microphone b/src/clis/spx/.x/help/intent.microphone
similarity index 100%
rename from src/spx/.x/help/intent.microphone
rename to src/clis/spx/.x/help/intent.microphone
diff --git a/src/spx/.x/help/intent.once b/src/clis/spx/.x/help/intent.once
similarity index 100%
rename from src/spx/.x/help/intent.once
rename to src/clis/spx/.x/help/intent.once
diff --git a/src/spx/.x/help/intent.output b/src/clis/spx/.x/help/intent.output
similarity index 100%
rename from src/spx/.x/help/intent.output
rename to src/clis/spx/.x/help/intent.output
diff --git a/src/spx/.x/help/intent.output.accuracy b/src/clis/spx/.x/help/intent.output.accuracy
similarity index 100%
rename from src/spx/.x/help/intent.output.accuracy
rename to src/clis/spx/.x/help/intent.output.accuracy
diff --git a/src/spx/.x/help/intent.output.all b/src/clis/spx/.x/help/intent.output.all
similarity index 100%
rename from src/spx/.x/help/intent.output.all
rename to src/clis/spx/.x/help/intent.output.all
diff --git a/src/spx/.x/help/intent.output.all.example b/src/clis/spx/.x/help/intent.output.all.example
similarity index 100%
rename from src/spx/.x/help/intent.output.all.example
rename to src/clis/spx/.x/help/intent.output.all.example
diff --git a/src/spx/.x/help/intent.output.all.file b/src/clis/spx/.x/help/intent.output.all.file
similarity index 100%
rename from src/spx/.x/help/intent.output.all.file
rename to src/clis/spx/.x/help/intent.output.all.file
diff --git a/src/spx/.x/help/intent.output.all.files b/src/clis/spx/.x/help/intent.output.all.files
similarity index 100%
rename from src/spx/.x/help/intent.output.all.files
rename to src/clis/spx/.x/help/intent.output.all.files
diff --git a/src/spx/.x/help/intent.output.all.json b/src/clis/spx/.x/help/intent.output.all.json
similarity index 100%
rename from src/spx/.x/help/intent.output.all.json
rename to src/clis/spx/.x/help/intent.output.all.json
diff --git a/src/spx/.x/help/intent.output.all.timing b/src/clis/spx/.x/help/intent.output.all.timing
similarity index 100%
rename from src/spx/.x/help/intent.output.all.timing
rename to src/clis/spx/.x/help/intent.output.all.timing
diff --git a/src/spx/.x/help/intent.output.batch b/src/clis/spx/.x/help/intent.output.batch
similarity index 100%
rename from src/spx/.x/help/intent.output.batch
rename to src/clis/spx/.x/help/intent.output.batch
diff --git a/src/spx/.x/help/intent.output.each b/src/clis/spx/.x/help/intent.output.each
similarity index 100%
rename from src/spx/.x/help/intent.output.each
rename to src/clis/spx/.x/help/intent.output.each
diff --git a/src/spx/.x/help/intent.output.examples b/src/clis/spx/.x/help/intent.output.examples
similarity index 100%
rename from src/spx/.x/help/intent.output.examples
rename to src/clis/spx/.x/help/intent.output.examples
diff --git a/src/spx/.x/help/intent.output.file b/src/clis/spx/.x/help/intent.output.file
similarity index 100%
rename from src/spx/.x/help/intent.output.file
rename to src/clis/spx/.x/help/intent.output.file
diff --git a/src/spx/.x/help/intent.output.text b/src/clis/spx/.x/help/intent.output.text
similarity index 100%
rename from src/spx/.x/help/intent.output.text
rename to src/clis/spx/.x/help/intent.output.text
diff --git a/src/spx/.x/help/intent.output.vtt b/src/clis/spx/.x/help/intent.output.vtt
similarity index 100%
rename from src/spx/.x/help/intent.output.vtt
rename to src/clis/spx/.x/help/intent.output.vtt
diff --git a/src/spx/.x/help/intent.output.wer b/src/clis/spx/.x/help/intent.output.wer
similarity index 100%
rename from src/spx/.x/help/intent.output.wer
rename to src/clis/spx/.x/help/intent.output.wer
diff --git a/src/spx/.x/help/intent.pattern b/src/clis/spx/.x/help/intent.pattern
similarity index 100%
rename from src/spx/.x/help/intent.pattern
rename to src/clis/spx/.x/help/intent.pattern
diff --git a/src/spx/.x/help/intent.patterns b/src/clis/spx/.x/help/intent.patterns
similarity index 100%
rename from src/spx/.x/help/intent.patterns
rename to src/clis/spx/.x/help/intent.patterns
diff --git a/src/spx/.x/help/intent.phrases b/src/clis/spx/.x/help/intent.phrases
similarity index 100%
rename from src/spx/.x/help/intent.phrases
rename to src/clis/spx/.x/help/intent.phrases
diff --git a/src/spx/.x/help/intent.processes b/src/clis/spx/.x/help/intent.processes
similarity index 100%
rename from src/spx/.x/help/intent.processes
rename to src/clis/spx/.x/help/intent.processes
diff --git a/src/spx/.x/help/intent.profanity b/src/clis/spx/.x/help/intent.profanity
similarity index 100%
rename from src/spx/.x/help/intent.profanity
rename to src/clis/spx/.x/help/intent.profanity
diff --git a/src/spx/.x/help/intent.proxy b/src/clis/spx/.x/help/intent.proxy
similarity index 100%
rename from src/spx/.x/help/intent.proxy
rename to src/clis/spx/.x/help/intent.proxy
diff --git a/src/spx/.x/help/intent.region b/src/clis/spx/.x/help/intent.region
similarity index 100%
rename from src/spx/.x/help/intent.region
rename to src/clis/spx/.x/help/intent.region
diff --git a/src/spx/.x/help/intent.save b/src/clis/spx/.x/help/intent.save
similarity index 100%
rename from src/spx/.x/help/intent.save
rename to src/clis/spx/.x/help/intent.save
diff --git a/src/spx/.x/help/intent.testing b/src/clis/spx/.x/help/intent.testing
similarity index 100%
rename from src/spx/.x/help/intent.testing
rename to src/clis/spx/.x/help/intent.testing
diff --git a/src/spx/.x/help/intent.threads b/src/clis/spx/.x/help/intent.threads
similarity index 100%
rename from src/spx/.x/help/intent.threads
rename to src/clis/spx/.x/help/intent.threads
diff --git a/src/spx/.x/help/intent.token b/src/clis/spx/.x/help/intent.token
similarity index 100%
rename from src/spx/.x/help/intent.token
rename to src/clis/spx/.x/help/intent.token
diff --git a/src/spx/.x/help/intent.transcript b/src/clis/spx/.x/help/intent.transcript
similarity index 100%
rename from src/spx/.x/help/intent.transcript
rename to src/clis/spx/.x/help/intent.transcript
diff --git a/src/spx/.x/help/intent.url b/src/clis/spx/.x/help/intent.url
similarity index 100%
rename from src/spx/.x/help/intent.url
rename to src/clis/spx/.x/help/intent.url
diff --git a/src/spx/.x/help/intent.urls b/src/clis/spx/.x/help/intent.urls
similarity index 100%
rename from src/spx/.x/help/intent.urls
rename to src/clis/spx/.x/help/intent.urls
diff --git a/src/spx/.x/help/intent.wer b/src/clis/spx/.x/help/intent.wer
similarity index 100%
rename from src/spx/.x/help/intent.wer
rename to src/clis/spx/.x/help/intent.wer
diff --git a/src/spx/.x/help/intent.zip b/src/clis/spx/.x/help/intent.zip
similarity index 100%
rename from src/spx/.x/help/intent.zip
rename to src/clis/spx/.x/help/intent.zip
diff --git a/src/spx/.x/help/internal b/src/clis/spx/.x/help/internal
similarity index 100%
rename from src/spx/.x/help/internal
rename to src/clis/spx/.x/help/internal
diff --git a/src/spx/.x/help/key b/src/clis/spx/.x/help/key
similarity index 100%
rename from src/spx/.x/help/key
rename to src/clis/spx/.x/help/key
diff --git a/src/spx/.x/help/key.overview b/src/clis/spx/.x/help/key.overview
similarity index 100%
rename from src/spx/.x/help/key.overview
rename to src/clis/spx/.x/help/key.overview
diff --git a/src/spx/.x/help/keyword b/src/clis/spx/.x/help/keyword
similarity index 100%
rename from src/spx/.x/help/keyword
rename to src/clis/spx/.x/help/keyword
diff --git a/src/spx/.x/help/language b/src/clis/spx/.x/help/language
similarity index 100%
rename from src/spx/.x/help/language
rename to src/clis/spx/.x/help/language
diff --git a/src/spx/.x/help/log b/src/clis/spx/.x/help/log
similarity index 100%
rename from src/spx/.x/help/log
rename to src/clis/spx/.x/help/log
diff --git a/src/spx/.x/help/luis.documentation b/src/clis/spx/.x/help/luis.documentation
similarity index 100%
rename from src/spx/.x/help/luis.documentation
rename to src/clis/spx/.x/help/luis.documentation
diff --git a/src/spx/.x/help/microphone b/src/clis/spx/.x/help/microphone
similarity index 100%
rename from src/spx/.x/help/microphone
rename to src/clis/spx/.x/help/microphone
diff --git a/src/spx/.x/help/once b/src/clis/spx/.x/help/once
similarity index 100%
rename from src/spx/.x/help/once
rename to src/clis/spx/.x/help/once
diff --git a/src/spx/.x/help/output b/src/clis/spx/.x/help/output
similarity index 100%
rename from src/spx/.x/help/output
rename to src/clis/spx/.x/help/output
diff --git a/src/spx/.x/help/output.all b/src/clis/spx/.x/help/output.all
similarity index 100%
rename from src/spx/.x/help/output.all
rename to src/clis/spx/.x/help/output.all
diff --git a/src/spx/.x/help/output.each b/src/clis/spx/.x/help/output.each
similarity index 100%
rename from src/spx/.x/help/output.each
rename to src/clis/spx/.x/help/output.each
diff --git a/src/spx/.x/help/phrases b/src/clis/spx/.x/help/phrases
similarity index 100%
rename from src/spx/.x/help/phrases
rename to src/clis/spx/.x/help/phrases
diff --git a/src/spx/.x/help/profanity b/src/clis/spx/.x/help/profanity
similarity index 100%
rename from src/spx/.x/help/profanity
rename to src/clis/spx/.x/help/profanity
diff --git a/src/spx/.x/help/profile b/src/clis/spx/.x/help/profile
similarity index 100%
rename from src/spx/.x/help/profile
rename to src/clis/spx/.x/help/profile
diff --git a/src/spx/.x/help/profile.connection b/src/clis/spx/.x/help/profile.connection
similarity index 100%
rename from src/spx/.x/help/profile.connection
rename to src/clis/spx/.x/help/profile.connection
diff --git a/src/spx/.x/help/profile.create b/src/clis/spx/.x/help/profile.create
similarity index 100%
rename from src/spx/.x/help/profile.create
rename to src/clis/spx/.x/help/profile.create
diff --git a/src/spx/.x/help/profile.delete b/src/clis/spx/.x/help/profile.delete
similarity index 100%
rename from src/spx/.x/help/profile.delete
rename to src/clis/spx/.x/help/profile.delete
diff --git a/src/spx/.x/help/profile.documentation b/src/clis/spx/.x/help/profile.documentation
similarity index 100%
rename from src/spx/.x/help/profile.documentation
rename to src/clis/spx/.x/help/profile.documentation
diff --git a/src/spx/.x/help/profile.enroll b/src/clis/spx/.x/help/profile.enroll
similarity index 100%
rename from src/spx/.x/help/profile.enroll
rename to src/clis/spx/.x/help/profile.enroll
diff --git a/src/spx/.x/help/profile.examples b/src/clis/spx/.x/help/profile.examples
similarity index 100%
rename from src/spx/.x/help/profile.examples
rename to src/clis/spx/.x/help/profile.examples
diff --git a/src/spx/.x/help/profile.file b/src/clis/spx/.x/help/profile.file
similarity index 100%
rename from src/spx/.x/help/profile.file
rename to src/clis/spx/.x/help/profile.file
diff --git a/src/spx/.x/help/profile.id b/src/clis/spx/.x/help/profile.id
similarity index 100%
rename from src/spx/.x/help/profile.id
rename to src/clis/spx/.x/help/profile.id
diff --git a/src/spx/.x/help/profile.key b/src/clis/spx/.x/help/profile.key
similarity index 100%
rename from src/spx/.x/help/profile.key
rename to src/clis/spx/.x/help/profile.key
diff --git a/src/spx/.x/help/profile.kind b/src/clis/spx/.x/help/profile.kind
similarity index 100%
rename from src/spx/.x/help/profile.kind
rename to src/clis/spx/.x/help/profile.kind
diff --git a/src/spx/.x/help/profile.list b/src/clis/spx/.x/help/profile.list
similarity index 100%
rename from src/spx/.x/help/profile.list
rename to src/clis/spx/.x/help/profile.list
diff --git a/src/spx/.x/help/profile.output b/src/clis/spx/.x/help/profile.output
similarity index 100%
rename from src/spx/.x/help/profile.output
rename to src/clis/spx/.x/help/profile.output
diff --git a/src/spx/.x/help/profile.output.file b/src/clis/spx/.x/help/profile.output.file
similarity index 100%
rename from src/spx/.x/help/profile.output.file
rename to src/clis/spx/.x/help/profile.output.file
diff --git a/src/spx/.x/help/profile.output.id b/src/clis/spx/.x/help/profile.output.id
similarity index 100%
rename from src/spx/.x/help/profile.output.id
rename to src/clis/spx/.x/help/profile.output.id
diff --git a/src/spx/.x/help/profile.output.json b/src/clis/spx/.x/help/profile.output.json
similarity index 100%
rename from src/spx/.x/help/profile.output.json
rename to src/clis/spx/.x/help/profile.output.json
diff --git a/src/spx/.x/help/profile.region b/src/clis/spx/.x/help/profile.region
similarity index 100%
rename from src/spx/.x/help/profile.region
rename to src/clis/spx/.x/help/profile.region
diff --git a/src/spx/.x/help/profile.status b/src/clis/spx/.x/help/profile.status
similarity index 100%
rename from src/spx/.x/help/profile.status
rename to src/clis/spx/.x/help/profile.status
diff --git a/src/spx/.x/help/proxy b/src/clis/spx/.x/help/proxy
similarity index 100%
rename from src/spx/.x/help/proxy
rename to src/clis/spx/.x/help/proxy
diff --git a/src/spx/.x/help/query.string b/src/clis/spx/.x/help/query.string
similarity index 100%
rename from src/spx/.x/help/query.string
rename to src/clis/spx/.x/help/query.string
diff --git a/src/spx/.x/help/recognize b/src/clis/spx/.x/help/recognize
similarity index 100%
rename from src/spx/.x/help/recognize
rename to src/clis/spx/.x/help/recognize
diff --git a/src/spx/.x/help/recognize.advanced b/src/clis/spx/.x/help/recognize.advanced
similarity index 100%
rename from src/spx/.x/help/recognize.advanced
rename to src/clis/spx/.x/help/recognize.advanced
diff --git a/src/spx/.x/help/recognize.check.text b/src/clis/spx/.x/help/recognize.check.text
similarity index 100%
rename from src/spx/.x/help/recognize.check.text
rename to src/clis/spx/.x/help/recognize.check.text
diff --git a/src/spx/.x/help/recognize.check.wer b/src/clis/spx/.x/help/recognize.check.wer
similarity index 100%
rename from src/spx/.x/help/recognize.check.wer
rename to src/clis/spx/.x/help/recognize.check.wer
diff --git a/src/spx/.x/help/recognize.connect b/src/clis/spx/.x/help/recognize.connect
similarity index 100%
rename from src/spx/.x/help/recognize.connect
rename to src/clis/spx/.x/help/recognize.connect
diff --git a/src/spx/.x/help/recognize.connection b/src/clis/spx/.x/help/recognize.connection
similarity index 100%
rename from src/spx/.x/help/recognize.connection
rename to src/clis/spx/.x/help/recognize.connection
diff --git a/src/spx/.x/help/recognize.continuous b/src/clis/spx/.x/help/recognize.continuous
similarity index 100%
rename from src/spx/.x/help/recognize.continuous
rename to src/clis/spx/.x/help/recognize.continuous
diff --git a/src/spx/.x/help/recognize.custom.speech b/src/clis/spx/.x/help/recognize.custom.speech
similarity index 100%
rename from src/spx/.x/help/recognize.custom.speech
rename to src/clis/spx/.x/help/recognize.custom.speech
diff --git a/src/spx/.x/help/recognize.default.connection.example b/src/clis/spx/.x/help/recognize.default.connection.example
similarity index 100%
rename from src/spx/.x/help/recognize.default.connection.example
rename to src/clis/spx/.x/help/recognize.default.connection.example
diff --git a/src/spx/.x/help/recognize.disconnect b/src/clis/spx/.x/help/recognize.disconnect
similarity index 100%
rename from src/spx/.x/help/recognize.disconnect
rename to src/clis/spx/.x/help/recognize.disconnect
diff --git a/src/spx/.x/help/recognize.endpoint b/src/clis/spx/.x/help/recognize.endpoint
similarity index 100%
rename from src/spx/.x/help/recognize.endpoint
rename to src/clis/spx/.x/help/recognize.endpoint
diff --git a/src/spx/.x/help/recognize.examples b/src/clis/spx/.x/help/recognize.examples
similarity index 100%
rename from src/spx/.x/help/recognize.examples
rename to src/clis/spx/.x/help/recognize.examples
diff --git a/src/spx/.x/help/recognize.file b/src/clis/spx/.x/help/recognize.file
similarity index 100%
rename from src/spx/.x/help/recognize.file
rename to src/clis/spx/.x/help/recognize.file
diff --git a/src/spx/.x/help/recognize.files b/src/clis/spx/.x/help/recognize.files
similarity index 100%
rename from src/spx/.x/help/recognize.files
rename to src/clis/spx/.x/help/recognize.files
diff --git a/src/spx/.x/help/recognize.foreach b/src/clis/spx/.x/help/recognize.foreach
similarity index 100%
rename from src/spx/.x/help/recognize.foreach
rename to src/clis/spx/.x/help/recognize.foreach
diff --git a/src/spx/.x/help/recognize.format b/src/clis/spx/.x/help/recognize.format
similarity index 100%
rename from src/spx/.x/help/recognize.format
rename to src/clis/spx/.x/help/recognize.format
diff --git a/src/spx/.x/help/recognize.improve.accuracy b/src/clis/spx/.x/help/recognize.improve.accuracy
similarity index 100%
rename from src/spx/.x/help/recognize.improve.accuracy
rename to src/clis/spx/.x/help/recognize.improve.accuracy
diff --git a/src/spx/.x/help/recognize.input b/src/clis/spx/.x/help/recognize.input
similarity index 100%
rename from src/spx/.x/help/recognize.input
rename to src/clis/spx/.x/help/recognize.input
diff --git a/src/spx/.x/help/recognize.key b/src/clis/spx/.x/help/recognize.key
similarity index 100%
rename from src/spx/.x/help/recognize.key
rename to src/clis/spx/.x/help/recognize.key
diff --git a/src/spx/.x/help/recognize.keyword b/src/clis/spx/.x/help/recognize.keyword
similarity index 100%
rename from src/spx/.x/help/recognize.keyword
rename to src/clis/spx/.x/help/recognize.keyword
diff --git a/src/spx/.x/help/recognize.language b/src/clis/spx/.x/help/recognize.language
similarity index 100%
rename from src/spx/.x/help/recognize.language
rename to src/clis/spx/.x/help/recognize.language
diff --git a/src/spx/.x/help/recognize.languages b/src/clis/spx/.x/help/recognize.languages
similarity index 100%
rename from src/spx/.x/help/recognize.languages
rename to src/clis/spx/.x/help/recognize.languages
diff --git a/src/spx/.x/help/recognize.log b/src/clis/spx/.x/help/recognize.log
similarity index 100%
rename from src/spx/.x/help/recognize.log
rename to src/clis/spx/.x/help/recognize.log
diff --git a/src/spx/.x/help/recognize.microphone b/src/clis/spx/.x/help/recognize.microphone
similarity index 100%
rename from src/spx/.x/help/recognize.microphone
rename to src/clis/spx/.x/help/recognize.microphone
diff --git a/src/spx/.x/help/recognize.once b/src/clis/spx/.x/help/recognize.once
similarity index 100%
rename from src/spx/.x/help/recognize.once
rename to src/clis/spx/.x/help/recognize.once
diff --git a/src/spx/.x/help/recognize.output b/src/clis/spx/.x/help/recognize.output
similarity index 100%
rename from src/spx/.x/help/recognize.output
rename to src/clis/spx/.x/help/recognize.output
diff --git a/src/spx/.x/help/recognize.output.all b/src/clis/spx/.x/help/recognize.output.all
similarity index 100%
rename from src/spx/.x/help/recognize.output.all
rename to src/clis/spx/.x/help/recognize.output.all
diff --git a/src/spx/.x/help/recognize.output.all.file b/src/clis/spx/.x/help/recognize.output.all.file
similarity index 100%
rename from src/spx/.x/help/recognize.output.all.file
rename to src/clis/spx/.x/help/recognize.output.all.file
diff --git a/src/spx/.x/help/recognize.output.batch b/src/clis/spx/.x/help/recognize.output.batch
similarity index 100%
rename from src/spx/.x/help/recognize.output.batch
rename to src/clis/spx/.x/help/recognize.output.batch
diff --git a/src/spx/.x/help/recognize.output.batch.file b/src/clis/spx/.x/help/recognize.output.batch.file
similarity index 100%
rename from src/spx/.x/help/recognize.output.batch.file
rename to src/clis/spx/.x/help/recognize.output.batch.file
diff --git a/src/spx/.x/help/recognize.output.batch.json b/src/clis/spx/.x/help/recognize.output.batch.json
similarity index 100%
rename from src/spx/.x/help/recognize.output.batch.json
rename to src/clis/spx/.x/help/recognize.output.batch.json
diff --git a/src/spx/.x/help/recognize.output.each b/src/clis/spx/.x/help/recognize.output.each
similarity index 100%
rename from src/spx/.x/help/recognize.output.each
rename to src/clis/spx/.x/help/recognize.output.each
diff --git a/src/spx/.x/help/recognize.output.each.file b/src/clis/spx/.x/help/recognize.output.each.file
similarity index 100%
rename from src/spx/.x/help/recognize.output.each.file
rename to src/clis/spx/.x/help/recognize.output.each.file
diff --git a/src/spx/.x/help/recognize.output.examples b/src/clis/spx/.x/help/recognize.output.examples
similarity index 100%
rename from src/spx/.x/help/recognize.output.examples
rename to src/clis/spx/.x/help/recognize.output.examples
diff --git a/src/spx/.x/help/recognize.output.vtt b/src/clis/spx/.x/help/recognize.output.vtt
similarity index 100%
rename from src/spx/.x/help/recognize.output.vtt
rename to src/clis/spx/.x/help/recognize.output.vtt
diff --git a/src/spx/.x/help/recognize.phrases b/src/clis/spx/.x/help/recognize.phrases
similarity index 100%
rename from src/spx/.x/help/recognize.phrases
rename to src/clis/spx/.x/help/recognize.phrases
diff --git a/src/spx/.x/help/recognize.processes b/src/clis/spx/.x/help/recognize.processes
similarity index 100%
rename from src/spx/.x/help/recognize.processes
rename to src/clis/spx/.x/help/recognize.processes
diff --git a/src/spx/.x/help/recognize.profanity b/src/clis/spx/.x/help/recognize.profanity
similarity index 100%
rename from src/spx/.x/help/recognize.profanity
rename to src/clis/spx/.x/help/recognize.profanity
diff --git a/src/spx/.x/help/recognize.proxy b/src/clis/spx/.x/help/recognize.proxy
similarity index 100%
rename from src/spx/.x/help/recognize.proxy
rename to src/clis/spx/.x/help/recognize.proxy
diff --git a/src/spx/.x/help/recognize.region b/src/clis/spx/.x/help/recognize.region
similarity index 100%
rename from src/spx/.x/help/recognize.region
rename to src/clis/spx/.x/help/recognize.region
diff --git a/src/spx/.x/help/recognize.save b/src/clis/spx/.x/help/recognize.save
similarity index 100%
rename from src/spx/.x/help/recognize.save
rename to src/clis/spx/.x/help/recognize.save
diff --git a/src/spx/.x/help/recognize.testing b/src/clis/spx/.x/help/recognize.testing
similarity index 100%
rename from src/spx/.x/help/recognize.testing
rename to src/clis/spx/.x/help/recognize.testing
diff --git a/src/spx/.x/help/recognize.threads b/src/clis/spx/.x/help/recognize.threads
similarity index 100%
rename from src/spx/.x/help/recognize.threads
rename to src/clis/spx/.x/help/recognize.threads
diff --git a/src/spx/.x/help/recognize.token b/src/clis/spx/.x/help/recognize.token
similarity index 100%
rename from src/spx/.x/help/recognize.token
rename to src/clis/spx/.x/help/recognize.token
diff --git a/src/spx/.x/help/recognize.transcript b/src/clis/spx/.x/help/recognize.transcript
similarity index 100%
rename from src/spx/.x/help/recognize.transcript
rename to src/clis/spx/.x/help/recognize.transcript
diff --git a/src/spx/.x/help/recognize.url b/src/clis/spx/.x/help/recognize.url
similarity index 100%
rename from src/spx/.x/help/recognize.url
rename to src/clis/spx/.x/help/recognize.url
diff --git a/src/spx/.x/help/recognize.urls b/src/clis/spx/.x/help/recognize.urls
similarity index 100%
rename from src/spx/.x/help/recognize.urls
rename to src/clis/spx/.x/help/recognize.urls
diff --git a/src/spx/.x/help/recognize.wer b/src/clis/spx/.x/help/recognize.wer
similarity index 100%
rename from src/spx/.x/help/recognize.wer
rename to src/clis/spx/.x/help/recognize.wer
diff --git a/src/spx/.x/help/recognize.word.level.timing b/src/clis/spx/.x/help/recognize.word.level.timing
similarity index 100%
rename from src/spx/.x/help/recognize.word.level.timing
rename to src/clis/spx/.x/help/recognize.word.level.timing
diff --git a/src/spx/.x/help/recognize.zip b/src/clis/spx/.x/help/recognize.zip
similarity index 100%
rename from src/spx/.x/help/recognize.zip
rename to src/clis/spx/.x/help/recognize.zip
diff --git a/src/spx/.x/help/region b/src/clis/spx/.x/help/region
similarity index 100%
rename from src/spx/.x/help/region
rename to src/clis/spx/.x/help/region
diff --git a/src/spx/.x/help/region.overview b/src/clis/spx/.x/help/region.overview
similarity index 100%
rename from src/spx/.x/help/region.overview
rename to src/clis/spx/.x/help/region.overview
diff --git a/src/spx/.x/help/run b/src/clis/spx/.x/help/run
similarity index 100%
rename from src/spx/.x/help/run
rename to src/clis/spx/.x/help/run
diff --git a/src/spx/.x/help/run.advanced b/src/clis/spx/.x/help/run.advanced
similarity index 100%
rename from src/spx/.x/help/run.advanced
rename to src/clis/spx/.x/help/run.advanced
diff --git a/src/spx/.x/help/run.csx b/src/clis/spx/.x/help/run.csx
similarity index 100%
rename from src/spx/.x/help/run.csx
rename to src/clis/spx/.x/help/run.csx
diff --git a/src/spx/.x/help/run.examples b/src/clis/spx/.x/help/run.examples
similarity index 100%
rename from src/spx/.x/help/run.examples
rename to src/clis/spx/.x/help/run.examples
diff --git a/src/spx/.x/help/setup b/src/clis/spx/.x/help/setup
similarity index 100%
rename from src/spx/.x/help/setup
rename to src/clis/spx/.x/help/setup
diff --git a/src/spx/.x/help/source b/src/clis/spx/.x/help/source
similarity index 100%
rename from src/spx/.x/help/source
rename to src/clis/spx/.x/help/source
diff --git a/src/spx/.x/help/speaker b/src/clis/spx/.x/help/speaker
similarity index 100%
rename from src/spx/.x/help/speaker
rename to src/clis/spx/.x/help/speaker
diff --git a/src/spx/.x/help/speaker.connection b/src/clis/spx/.x/help/speaker.connection
similarity index 100%
rename from src/spx/.x/help/speaker.connection
rename to src/clis/spx/.x/help/speaker.connection
diff --git a/src/spx/.x/help/speaker.documentation b/src/clis/spx/.x/help/speaker.documentation
similarity index 100%
rename from src/spx/.x/help/speaker.documentation
rename to src/clis/spx/.x/help/speaker.documentation
diff --git a/src/spx/.x/help/speaker.examples b/src/clis/spx/.x/help/speaker.examples
similarity index 100%
rename from src/spx/.x/help/speaker.examples
rename to src/clis/spx/.x/help/speaker.examples
diff --git a/src/spx/.x/help/speaker.file b/src/clis/spx/.x/help/speaker.file
similarity index 100%
rename from src/spx/.x/help/speaker.file
rename to src/clis/spx/.x/help/speaker.file
diff --git a/src/spx/.x/help/speaker.id b/src/clis/spx/.x/help/speaker.id
similarity index 100%
rename from src/spx/.x/help/speaker.id
rename to src/clis/spx/.x/help/speaker.id
diff --git a/src/spx/.x/help/speaker.identify b/src/clis/spx/.x/help/speaker.identify
similarity index 100%
rename from src/spx/.x/help/speaker.identify
rename to src/clis/spx/.x/help/speaker.identify
diff --git a/src/spx/.x/help/speaker.key b/src/clis/spx/.x/help/speaker.key
similarity index 100%
rename from src/spx/.x/help/speaker.key
rename to src/clis/spx/.x/help/speaker.key
diff --git a/src/spx/.x/help/speaker.kind b/src/clis/spx/.x/help/speaker.kind
similarity index 100%
rename from src/spx/.x/help/speaker.kind
rename to src/clis/spx/.x/help/speaker.kind
diff --git a/src/spx/.x/help/speaker.language b/src/clis/spx/.x/help/speaker.language
similarity index 100%
rename from src/spx/.x/help/speaker.language
rename to src/clis/spx/.x/help/speaker.language
diff --git a/src/spx/.x/help/speaker.region b/src/clis/spx/.x/help/speaker.region
similarity index 100%
rename from src/spx/.x/help/speaker.region
rename to src/clis/spx/.x/help/speaker.region
diff --git a/src/spx/.x/help/speaker.verify b/src/clis/spx/.x/help/speaker.verify
similarity index 100%
rename from src/spx/.x/help/speaker.verify
rename to src/clis/spx/.x/help/speaker.verify
diff --git a/src/spx/.x/help/synthesize b/src/clis/spx/.x/help/synthesize
similarity index 100%
rename from src/spx/.x/help/synthesize
rename to src/clis/spx/.x/help/synthesize
diff --git a/src/spx/.x/help/synthesize.advanced b/src/clis/spx/.x/help/synthesize.advanced
similarity index 100%
rename from src/spx/.x/help/synthesize.advanced
rename to src/clis/spx/.x/help/synthesize.advanced
diff --git a/src/spx/.x/help/synthesize.audio b/src/clis/spx/.x/help/synthesize.audio
similarity index 100%
rename from src/spx/.x/help/synthesize.audio
rename to src/clis/spx/.x/help/synthesize.audio
diff --git a/src/spx/.x/help/synthesize.audio.output b/src/clis/spx/.x/help/synthesize.audio.output
similarity index 100%
rename from src/spx/.x/help/synthesize.audio.output
rename to src/clis/spx/.x/help/synthesize.audio.output
diff --git a/src/spx/.x/help/synthesize.audio.speakers b/src/clis/spx/.x/help/synthesize.audio.speakers
similarity index 100%
rename from src/spx/.x/help/synthesize.audio.speakers
rename to src/clis/spx/.x/help/synthesize.audio.speakers
diff --git a/src/spx/.x/help/synthesize.connection b/src/clis/spx/.x/help/synthesize.connection
similarity index 100%
rename from src/spx/.x/help/synthesize.connection
rename to src/clis/spx/.x/help/synthesize.connection
diff --git a/src/spx/.x/help/synthesize.endpoint b/src/clis/spx/.x/help/synthesize.endpoint
similarity index 100%
rename from src/spx/.x/help/synthesize.endpoint
rename to src/clis/spx/.x/help/synthesize.endpoint
diff --git a/src/spx/.x/help/synthesize.examples b/src/clis/spx/.x/help/synthesize.examples
similarity index 100%
rename from src/spx/.x/help/synthesize.examples
rename to src/clis/spx/.x/help/synthesize.examples
diff --git a/src/spx/.x/help/synthesize.file b/src/clis/spx/.x/help/synthesize.file
similarity index 100%
rename from src/spx/.x/help/synthesize.file
rename to src/clis/spx/.x/help/synthesize.file
diff --git a/src/spx/.x/help/synthesize.files b/src/clis/spx/.x/help/synthesize.files
similarity index 100%
rename from src/spx/.x/help/synthesize.files
rename to src/clis/spx/.x/help/synthesize.files
diff --git a/src/spx/.x/help/synthesize.foreach b/src/clis/spx/.x/help/synthesize.foreach
similarity index 100%
rename from src/spx/.x/help/synthesize.foreach
rename to src/clis/spx/.x/help/synthesize.foreach
diff --git a/src/spx/.x/help/synthesize.format b/src/clis/spx/.x/help/synthesize.format
similarity index 100%
rename from src/spx/.x/help/synthesize.format
rename to src/clis/spx/.x/help/synthesize.format
diff --git a/src/spx/.x/help/synthesize.format.mp3 b/src/clis/spx/.x/help/synthesize.format.mp3
similarity index 100%
rename from src/spx/.x/help/synthesize.format.mp3
rename to src/clis/spx/.x/help/synthesize.format.mp3
diff --git a/src/spx/.x/help/synthesize.format.ogg b/src/clis/spx/.x/help/synthesize.format.ogg
similarity index 100%
rename from src/spx/.x/help/synthesize.format.ogg
rename to src/clis/spx/.x/help/synthesize.format.ogg
diff --git a/src/spx/.x/help/synthesize.format.opus b/src/clis/spx/.x/help/synthesize.format.opus
similarity index 100%
rename from src/spx/.x/help/synthesize.format.opus
rename to src/clis/spx/.x/help/synthesize.format.opus
diff --git a/src/spx/.x/help/synthesize.format.raw b/src/clis/spx/.x/help/synthesize.format.raw
similarity index 100%
rename from src/spx/.x/help/synthesize.format.raw
rename to src/clis/spx/.x/help/synthesize.format.raw
diff --git a/src/spx/.x/help/synthesize.format.siren b/src/clis/spx/.x/help/synthesize.format.siren
similarity index 100%
rename from src/spx/.x/help/synthesize.format.siren
rename to src/clis/spx/.x/help/synthesize.format.siren
diff --git a/src/spx/.x/help/synthesize.format.wav b/src/clis/spx/.x/help/synthesize.format.wav
similarity index 100%
rename from src/spx/.x/help/synthesize.format.wav
rename to src/clis/spx/.x/help/synthesize.format.wav
diff --git a/src/spx/.x/help/synthesize.format.webm b/src/clis/spx/.x/help/synthesize.format.webm
similarity index 100%
rename from src/spx/.x/help/synthesize.format.webm
rename to src/clis/spx/.x/help/synthesize.format.webm
diff --git a/src/spx/.x/help/synthesize.input b/src/clis/spx/.x/help/synthesize.input
similarity index 100%
rename from src/spx/.x/help/synthesize.input
rename to src/clis/spx/.x/help/synthesize.input
diff --git a/src/spx/.x/help/synthesize.interactive b/src/clis/spx/.x/help/synthesize.interactive
similarity index 100%
rename from src/spx/.x/help/synthesize.interactive
rename to src/clis/spx/.x/help/synthesize.interactive
diff --git a/src/spx/.x/help/synthesize.key b/src/clis/spx/.x/help/synthesize.key
similarity index 100%
rename from src/spx/.x/help/synthesize.key
rename to src/clis/spx/.x/help/synthesize.key
diff --git a/src/spx/.x/help/synthesize.list.voices b/src/clis/spx/.x/help/synthesize.list.voices
similarity index 100%
rename from src/spx/.x/help/synthesize.list.voices
rename to src/clis/spx/.x/help/synthesize.list.voices
diff --git a/src/spx/.x/help/synthesize.log b/src/clis/spx/.x/help/synthesize.log
similarity index 100%
rename from src/spx/.x/help/synthesize.log
rename to src/clis/spx/.x/help/synthesize.log
diff --git a/src/spx/.x/help/synthesize.mp3 b/src/clis/spx/.x/help/synthesize.mp3
similarity index 100%
rename from src/spx/.x/help/synthesize.mp3
rename to src/clis/spx/.x/help/synthesize.mp3
diff --git a/src/spx/.x/help/synthesize.ogg b/src/clis/spx/.x/help/synthesize.ogg
similarity index 100%
rename from src/spx/.x/help/synthesize.ogg
rename to src/clis/spx/.x/help/synthesize.ogg
diff --git a/src/spx/.x/help/synthesize.opus b/src/clis/spx/.x/help/synthesize.opus
similarity index 100%
rename from src/spx/.x/help/synthesize.opus
rename to src/clis/spx/.x/help/synthesize.opus
diff --git a/src/spx/.x/help/synthesize.output b/src/clis/spx/.x/help/synthesize.output
similarity index 100%
rename from src/spx/.x/help/synthesize.output
rename to src/clis/spx/.x/help/synthesize.output
diff --git a/src/spx/.x/help/synthesize.output.all b/src/clis/spx/.x/help/synthesize.output.all
similarity index 100%
rename from src/spx/.x/help/synthesize.output.all
rename to src/clis/spx/.x/help/synthesize.output.all
diff --git a/src/spx/.x/help/synthesize.output.all.file b/src/clis/spx/.x/help/synthesize.output.all.file
similarity index 100%
rename from src/spx/.x/help/synthesize.output.all.file
rename to src/clis/spx/.x/help/synthesize.output.all.file
diff --git a/src/spx/.x/help/synthesize.output.each b/src/clis/spx/.x/help/synthesize.output.each
similarity index 100%
rename from src/spx/.x/help/synthesize.output.each
rename to src/clis/spx/.x/help/synthesize.output.each
diff --git a/src/spx/.x/help/synthesize.output.each.file b/src/clis/spx/.x/help/synthesize.output.each.file
similarity index 100%
rename from src/spx/.x/help/synthesize.output.each.file
rename to src/clis/spx/.x/help/synthesize.output.each.file
diff --git a/src/spx/.x/help/synthesize.output.examples b/src/clis/spx/.x/help/synthesize.output.examples
similarity index 100%
rename from src/spx/.x/help/synthesize.output.examples
rename to src/clis/spx/.x/help/synthesize.output.examples
diff --git a/src/spx/.x/help/synthesize.processes b/src/clis/spx/.x/help/synthesize.processes
similarity index 100%
rename from src/spx/.x/help/synthesize.processes
rename to src/clis/spx/.x/help/synthesize.processes
diff --git a/src/spx/.x/help/synthesize.proxy b/src/clis/spx/.x/help/synthesize.proxy
similarity index 100%
rename from src/spx/.x/help/synthesize.proxy
rename to src/clis/spx/.x/help/synthesize.proxy
diff --git a/src/spx/.x/help/synthesize.raw b/src/clis/spx/.x/help/synthesize.raw
similarity index 100%
rename from src/spx/.x/help/synthesize.raw
rename to src/clis/spx/.x/help/synthesize.raw
diff --git a/src/spx/.x/help/synthesize.region b/src/clis/spx/.x/help/synthesize.region
similarity index 100%
rename from src/spx/.x/help/synthesize.region
rename to src/clis/spx/.x/help/synthesize.region
diff --git a/src/spx/.x/help/synthesize.save b/src/clis/spx/.x/help/synthesize.save
similarity index 100%
rename from src/spx/.x/help/synthesize.save
rename to src/clis/spx/.x/help/synthesize.save
diff --git a/src/spx/.x/help/synthesize.siren b/src/clis/spx/.x/help/synthesize.siren
similarity index 100%
rename from src/spx/.x/help/synthesize.siren
rename to src/clis/spx/.x/help/synthesize.siren
diff --git a/src/spx/.x/help/synthesize.ssml b/src/clis/spx/.x/help/synthesize.ssml
similarity index 100%
rename from src/spx/.x/help/synthesize.ssml
rename to src/clis/spx/.x/help/synthesize.ssml
diff --git a/src/spx/.x/help/synthesize.text b/src/clis/spx/.x/help/synthesize.text
similarity index 100%
rename from src/spx/.x/help/synthesize.text
rename to src/clis/spx/.x/help/synthesize.text
diff --git a/src/spx/.x/help/synthesize.threads b/src/clis/spx/.x/help/synthesize.threads
similarity index 100%
rename from src/spx/.x/help/synthesize.threads
rename to src/clis/spx/.x/help/synthesize.threads
diff --git a/src/spx/.x/help/synthesize.token b/src/clis/spx/.x/help/synthesize.token
similarity index 100%
rename from src/spx/.x/help/synthesize.token
rename to src/clis/spx/.x/help/synthesize.token
diff --git a/src/spx/.x/help/synthesize.url b/src/clis/spx/.x/help/synthesize.url
similarity index 100%
rename from src/spx/.x/help/synthesize.url
rename to src/clis/spx/.x/help/synthesize.url
diff --git a/src/spx/.x/help/synthesize.urls b/src/clis/spx/.x/help/synthesize.urls
similarity index 100%
rename from src/spx/.x/help/synthesize.urls
rename to src/clis/spx/.x/help/synthesize.urls
diff --git a/src/spx/.x/help/synthesize.voice b/src/clis/spx/.x/help/synthesize.voice
similarity index 100%
rename from src/spx/.x/help/synthesize.voice
rename to src/clis/spx/.x/help/synthesize.voice
diff --git a/src/spx/.x/help/synthesize.wav b/src/clis/spx/.x/help/synthesize.wav
similarity index 100%
rename from src/spx/.x/help/synthesize.wav
rename to src/clis/spx/.x/help/synthesize.wav
diff --git a/src/spx/.x/help/synthesize.webm b/src/clis/spx/.x/help/synthesize.webm
similarity index 100%
rename from src/spx/.x/help/synthesize.webm
rename to src/clis/spx/.x/help/synthesize.webm
diff --git a/src/spx/.x/help/synthesize.zip b/src/clis/spx/.x/help/synthesize.zip
similarity index 100%
rename from src/spx/.x/help/synthesize.zip
rename to src/clis/spx/.x/help/synthesize.zip
diff --git a/src/spx/.x/help/target b/src/clis/spx/.x/help/target
similarity index 100%
rename from src/spx/.x/help/target
rename to src/clis/spx/.x/help/target
diff --git a/src/spx/.x/help/token b/src/clis/spx/.x/help/token
similarity index 100%
rename from src/spx/.x/help/token
rename to src/clis/spx/.x/help/token
diff --git a/src/spx/.x/help/traffic.type b/src/clis/spx/.x/help/traffic.type
similarity index 100%
rename from src/spx/.x/help/traffic.type
rename to src/clis/spx/.x/help/traffic.type
diff --git a/src/spx/.x/help/transcribe b/src/clis/spx/.x/help/transcribe
similarity index 100%
rename from src/spx/.x/help/transcribe
rename to src/clis/spx/.x/help/transcribe
diff --git a/src/spx/.x/help/transcribe.advanced b/src/clis/spx/.x/help/transcribe.advanced
similarity index 100%
rename from src/spx/.x/help/transcribe.advanced
rename to src/clis/spx/.x/help/transcribe.advanced
diff --git a/src/spx/.x/help/transcribe.connect b/src/clis/spx/.x/help/transcribe.connect
similarity index 100%
rename from src/spx/.x/help/transcribe.connect
rename to src/clis/spx/.x/help/transcribe.connect
diff --git a/src/spx/.x/help/transcribe.connection b/src/clis/spx/.x/help/transcribe.connection
similarity index 100%
rename from src/spx/.x/help/transcribe.connection
rename to src/clis/spx/.x/help/transcribe.connection
diff --git a/src/spx/.x/help/transcribe.conversation.id b/src/clis/spx/.x/help/transcribe.conversation.id
similarity index 100%
rename from src/spx/.x/help/transcribe.conversation.id
rename to src/clis/spx/.x/help/transcribe.conversation.id
diff --git a/src/spx/.x/help/transcribe.endpoint b/src/clis/spx/.x/help/transcribe.endpoint
similarity index 100%
rename from src/spx/.x/help/transcribe.endpoint
rename to src/clis/spx/.x/help/transcribe.endpoint
diff --git a/src/spx/.x/help/transcribe.examples b/src/clis/spx/.x/help/transcribe.examples
similarity index 100%
rename from src/spx/.x/help/transcribe.examples
rename to src/clis/spx/.x/help/transcribe.examples
diff --git a/src/spx/.x/help/transcribe.file b/src/clis/spx/.x/help/transcribe.file
similarity index 100%
rename from src/spx/.x/help/transcribe.file
rename to src/clis/spx/.x/help/transcribe.file
diff --git a/src/spx/.x/help/transcribe.files b/src/clis/spx/.x/help/transcribe.files
similarity index 100%
rename from src/spx/.x/help/transcribe.files
rename to src/clis/spx/.x/help/transcribe.files
diff --git a/src/spx/.x/help/transcribe.foreach b/src/clis/spx/.x/help/transcribe.foreach
similarity index 100%
rename from src/spx/.x/help/transcribe.foreach
rename to src/clis/spx/.x/help/transcribe.foreach
diff --git a/src/spx/.x/help/transcribe.input b/src/clis/spx/.x/help/transcribe.input
similarity index 100%
rename from src/spx/.x/help/transcribe.input
rename to src/clis/spx/.x/help/transcribe.input
diff --git a/src/spx/.x/help/transcribe.key b/src/clis/spx/.x/help/transcribe.key
similarity index 100%
rename from src/spx/.x/help/transcribe.key
rename to src/clis/spx/.x/help/transcribe.key
diff --git a/src/spx/.x/help/transcribe.language b/src/clis/spx/.x/help/transcribe.language
similarity index 100%
rename from src/spx/.x/help/transcribe.language
rename to src/clis/spx/.x/help/transcribe.language
diff --git a/src/spx/.x/help/transcribe.log b/src/clis/spx/.x/help/transcribe.log
similarity index 100%
rename from src/spx/.x/help/transcribe.log
rename to src/clis/spx/.x/help/transcribe.log
diff --git a/src/spx/.x/help/transcribe.output b/src/clis/spx/.x/help/transcribe.output
similarity index 100%
rename from src/spx/.x/help/transcribe.output
rename to src/clis/spx/.x/help/transcribe.output
diff --git a/src/spx/.x/help/transcribe.output.all b/src/clis/spx/.x/help/transcribe.output.all
similarity index 100%
rename from src/spx/.x/help/transcribe.output.all
rename to src/clis/spx/.x/help/transcribe.output.all
diff --git a/src/spx/.x/help/transcribe.output.each b/src/clis/spx/.x/help/transcribe.output.each
similarity index 100%
rename from src/spx/.x/help/transcribe.output.each
rename to src/clis/spx/.x/help/transcribe.output.each
diff --git a/src/spx/.x/help/transcribe.phrases b/src/clis/spx/.x/help/transcribe.phrases
similarity index 100%
rename from src/spx/.x/help/transcribe.phrases
rename to src/clis/spx/.x/help/transcribe.phrases
diff --git a/src/spx/.x/help/transcribe.proxy b/src/clis/spx/.x/help/transcribe.proxy
similarity index 100%
rename from src/spx/.x/help/transcribe.proxy
rename to src/clis/spx/.x/help/transcribe.proxy
diff --git a/src/spx/.x/help/transcribe.region b/src/clis/spx/.x/help/transcribe.region
similarity index 100%
rename from src/spx/.x/help/transcribe.region
rename to src/clis/spx/.x/help/transcribe.region
diff --git a/src/spx/.x/help/transcribe.save b/src/clis/spx/.x/help/transcribe.save
similarity index 100%
rename from src/spx/.x/help/transcribe.save
rename to src/clis/spx/.x/help/transcribe.save
diff --git a/src/spx/.x/help/transcribe.source b/src/clis/spx/.x/help/transcribe.source
similarity index 100%
rename from src/spx/.x/help/transcribe.source
rename to src/clis/spx/.x/help/transcribe.source
diff --git a/src/spx/.x/help/transcribe.token b/src/clis/spx/.x/help/transcribe.token
similarity index 100%
rename from src/spx/.x/help/transcribe.token
rename to src/clis/spx/.x/help/transcribe.token
diff --git a/src/spx/.x/help/transcribe.zip b/src/clis/spx/.x/help/transcribe.zip
similarity index 100%
rename from src/spx/.x/help/transcribe.zip
rename to src/clis/spx/.x/help/transcribe.zip
diff --git a/src/spx/.x/help/translate b/src/clis/spx/.x/help/translate
similarity index 100%
rename from src/spx/.x/help/translate
rename to src/clis/spx/.x/help/translate
diff --git a/src/spx/.x/help/translate.advanced b/src/clis/spx/.x/help/translate.advanced
similarity index 100%
rename from src/spx/.x/help/translate.advanced
rename to src/clis/spx/.x/help/translate.advanced
diff --git a/src/spx/.x/help/translate.check.text b/src/clis/spx/.x/help/translate.check.text
similarity index 100%
rename from src/spx/.x/help/translate.check.text
rename to src/clis/spx/.x/help/translate.check.text
diff --git a/src/spx/.x/help/translate.check.wer b/src/clis/spx/.x/help/translate.check.wer
similarity index 100%
rename from src/spx/.x/help/translate.check.wer
rename to src/clis/spx/.x/help/translate.check.wer
diff --git a/src/spx/.x/help/translate.connect b/src/clis/spx/.x/help/translate.connect
similarity index 100%
rename from src/spx/.x/help/translate.connect
rename to src/clis/spx/.x/help/translate.connect
diff --git a/src/spx/.x/help/translate.connection b/src/clis/spx/.x/help/translate.connection
similarity index 100%
rename from src/spx/.x/help/translate.connection
rename to src/clis/spx/.x/help/translate.connection
diff --git a/src/spx/.x/help/translate.continuous b/src/clis/spx/.x/help/translate.continuous
similarity index 100%
rename from src/spx/.x/help/translate.continuous
rename to src/clis/spx/.x/help/translate.continuous
diff --git a/src/spx/.x/help/translate.custom.speech b/src/clis/spx/.x/help/translate.custom.speech
similarity index 100%
rename from src/spx/.x/help/translate.custom.speech
rename to src/clis/spx/.x/help/translate.custom.speech
diff --git a/src/spx/.x/help/translate.custom.translation b/src/clis/spx/.x/help/translate.custom.translation
similarity index 100%
rename from src/spx/.x/help/translate.custom.translation
rename to src/clis/spx/.x/help/translate.custom.translation
diff --git a/src/spx/.x/help/translate.disconnect b/src/clis/spx/.x/help/translate.disconnect
similarity index 100%
rename from src/spx/.x/help/translate.disconnect
rename to src/clis/spx/.x/help/translate.disconnect
diff --git a/src/spx/.x/help/translate.endpoint b/src/clis/spx/.x/help/translate.endpoint
similarity index 100%
rename from src/spx/.x/help/translate.endpoint
rename to src/clis/spx/.x/help/translate.endpoint
diff --git a/src/spx/.x/help/translate.examples b/src/clis/spx/.x/help/translate.examples
similarity index 100%
rename from src/spx/.x/help/translate.examples
rename to src/clis/spx/.x/help/translate.examples
diff --git a/src/spx/.x/help/translate.file b/src/clis/spx/.x/help/translate.file
similarity index 100%
rename from src/spx/.x/help/translate.file
rename to src/clis/spx/.x/help/translate.file
diff --git a/src/spx/.x/help/translate.files b/src/clis/spx/.x/help/translate.files
similarity index 100%
rename from src/spx/.x/help/translate.files
rename to src/clis/spx/.x/help/translate.files
diff --git a/src/spx/.x/help/translate.foreach b/src/clis/spx/.x/help/translate.foreach
similarity index 100%
rename from src/spx/.x/help/translate.foreach
rename to src/clis/spx/.x/help/translate.foreach
diff --git a/src/spx/.x/help/translate.format b/src/clis/spx/.x/help/translate.format
similarity index 100%
rename from src/spx/.x/help/translate.format
rename to src/clis/spx/.x/help/translate.format
diff --git a/src/spx/.x/help/translate.input b/src/clis/spx/.x/help/translate.input
similarity index 100%
rename from src/spx/.x/help/translate.input
rename to src/clis/spx/.x/help/translate.input
diff --git a/src/spx/.x/help/translate.key b/src/clis/spx/.x/help/translate.key
similarity index 100%
rename from src/spx/.x/help/translate.key
rename to src/clis/spx/.x/help/translate.key
diff --git a/src/spx/.x/help/translate.keyword b/src/clis/spx/.x/help/translate.keyword
similarity index 100%
rename from src/spx/.x/help/translate.keyword
rename to src/clis/spx/.x/help/translate.keyword
diff --git a/src/spx/.x/help/translate.language b/src/clis/spx/.x/help/translate.language
similarity index 100%
rename from src/spx/.x/help/translate.language
rename to src/clis/spx/.x/help/translate.language
diff --git a/src/spx/.x/help/translate.log b/src/clis/spx/.x/help/translate.log
similarity index 100%
rename from src/spx/.x/help/translate.log
rename to src/clis/spx/.x/help/translate.log
diff --git a/src/spx/.x/help/translate.microphone b/src/clis/spx/.x/help/translate.microphone
similarity index 100%
rename from src/spx/.x/help/translate.microphone
rename to src/clis/spx/.x/help/translate.microphone
diff --git a/src/spx/.x/help/translate.once b/src/clis/spx/.x/help/translate.once
similarity index 100%
rename from src/spx/.x/help/translate.once
rename to src/clis/spx/.x/help/translate.once
diff --git a/src/spx/.x/help/translate.output b/src/clis/spx/.x/help/translate.output
similarity index 100%
rename from src/spx/.x/help/translate.output
rename to src/clis/spx/.x/help/translate.output
diff --git a/src/spx/.x/help/translate.output.all b/src/clis/spx/.x/help/translate.output.all
similarity index 100%
rename from src/spx/.x/help/translate.output.all
rename to src/clis/spx/.x/help/translate.output.all
diff --git a/src/spx/.x/help/translate.output.all.file b/src/clis/spx/.x/help/translate.output.all.file
similarity index 100%
rename from src/spx/.x/help/translate.output.all.file
rename to src/clis/spx/.x/help/translate.output.all.file
diff --git a/src/spx/.x/help/translate.output.batch b/src/clis/spx/.x/help/translate.output.batch
similarity index 100%
rename from src/spx/.x/help/translate.output.batch
rename to src/clis/spx/.x/help/translate.output.batch
diff --git a/src/spx/.x/help/translate.output.batch.file b/src/clis/spx/.x/help/translate.output.batch.file
similarity index 100%
rename from src/spx/.x/help/translate.output.batch.file
rename to src/clis/spx/.x/help/translate.output.batch.file
diff --git a/src/spx/.x/help/translate.output.batch.json b/src/clis/spx/.x/help/translate.output.batch.json
similarity index 100%
rename from src/spx/.x/help/translate.output.batch.json
rename to src/clis/spx/.x/help/translate.output.batch.json
diff --git a/src/spx/.x/help/translate.output.each b/src/clis/spx/.x/help/translate.output.each
similarity index 100%
rename from src/spx/.x/help/translate.output.each
rename to src/clis/spx/.x/help/translate.output.each
diff --git a/src/spx/.x/help/translate.output.each.file b/src/clis/spx/.x/help/translate.output.each.file
similarity index 100%
rename from src/spx/.x/help/translate.output.each.file
rename to src/clis/spx/.x/help/translate.output.each.file
diff --git a/src/spx/.x/help/translate.output.examples b/src/clis/spx/.x/help/translate.output.examples
similarity index 100%
rename from src/spx/.x/help/translate.output.examples
rename to src/clis/spx/.x/help/translate.output.examples
diff --git a/src/spx/.x/help/translate.phrases b/src/clis/spx/.x/help/translate.phrases
similarity index 100%
rename from src/spx/.x/help/translate.phrases
rename to src/clis/spx/.x/help/translate.phrases
diff --git a/src/spx/.x/help/translate.processes b/src/clis/spx/.x/help/translate.processes
similarity index 100%
rename from src/spx/.x/help/translate.processes
rename to src/clis/spx/.x/help/translate.processes
diff --git a/src/spx/.x/help/translate.profanity b/src/clis/spx/.x/help/translate.profanity
similarity index 100%
rename from src/spx/.x/help/translate.profanity
rename to src/clis/spx/.x/help/translate.profanity
diff --git a/src/spx/.x/help/translate.proxy b/src/clis/spx/.x/help/translate.proxy
similarity index 100%
rename from src/spx/.x/help/translate.proxy
rename to src/clis/spx/.x/help/translate.proxy
diff --git a/src/spx/.x/help/translate.region b/src/clis/spx/.x/help/translate.region
similarity index 100%
rename from src/spx/.x/help/translate.region
rename to src/clis/spx/.x/help/translate.region
diff --git a/src/spx/.x/help/translate.save b/src/clis/spx/.x/help/translate.save
similarity index 100%
rename from src/spx/.x/help/translate.save
rename to src/clis/spx/.x/help/translate.save
diff --git a/src/spx/.x/help/translate.threads b/src/clis/spx/.x/help/translate.threads
similarity index 100%
rename from src/spx/.x/help/translate.threads
rename to src/clis/spx/.x/help/translate.threads
diff --git a/src/spx/.x/help/translate.token b/src/clis/spx/.x/help/translate.token
similarity index 100%
rename from src/spx/.x/help/translate.token
rename to src/clis/spx/.x/help/translate.token
diff --git a/src/spx/.x/help/translate.transcript b/src/clis/spx/.x/help/translate.transcript
similarity index 100%
rename from src/spx/.x/help/translate.transcript
rename to src/clis/spx/.x/help/translate.transcript
diff --git a/src/spx/.x/help/translate.url b/src/clis/spx/.x/help/translate.url
similarity index 100%
rename from src/spx/.x/help/translate.url
rename to src/clis/spx/.x/help/translate.url
diff --git a/src/spx/.x/help/translate.urls b/src/clis/spx/.x/help/translate.urls
similarity index 100%
rename from src/spx/.x/help/translate.urls
rename to src/clis/spx/.x/help/translate.urls
diff --git a/src/spx/.x/help/translate.word.level.timing b/src/clis/spx/.x/help/translate.word.level.timing
similarity index 100%
rename from src/spx/.x/help/translate.word.level.timing
rename to src/clis/spx/.x/help/translate.word.level.timing
diff --git a/src/spx/.x/help/translate.zip b/src/clis/spx/.x/help/translate.zip
similarity index 100%
rename from src/spx/.x/help/translate.zip
rename to src/clis/spx/.x/help/translate.zip
diff --git a/src/spx/.x/help/tutorials b/src/clis/spx/.x/help/tutorials
similarity index 100%
rename from src/spx/.x/help/tutorials
rename to src/clis/spx/.x/help/tutorials
diff --git a/src/spx/.x/help/usage b/src/clis/spx/.x/help/usage
similarity index 100%
rename from src/spx/.x/help/usage
rename to src/clis/spx/.x/help/usage
diff --git a/src/spx/.x/help/webjob b/src/clis/spx/.x/help/webjob
similarity index 100%
rename from src/spx/.x/help/webjob
rename to src/clis/spx/.x/help/webjob
diff --git a/src/spx/.x/help/webjob.advanced b/src/clis/spx/.x/help/webjob.advanced
similarity index 100%
rename from src/spx/.x/help/webjob.advanced
rename to src/clis/spx/.x/help/webjob.advanced
diff --git a/src/spx/.x/help/webjob.connection b/src/clis/spx/.x/help/webjob.connection
similarity index 100%
rename from src/spx/.x/help/webjob.connection
rename to src/clis/spx/.x/help/webjob.connection
diff --git a/src/spx/.x/help/webjob.delete b/src/clis/spx/.x/help/webjob.delete
similarity index 100%
rename from src/spx/.x/help/webjob.delete
rename to src/clis/spx/.x/help/webjob.delete
diff --git a/src/spx/.x/help/webjob.download b/src/clis/spx/.x/help/webjob.download
similarity index 100%
rename from src/spx/.x/help/webjob.download
rename to src/clis/spx/.x/help/webjob.download
diff --git a/src/spx/.x/help/webjob.endpoint b/src/clis/spx/.x/help/webjob.endpoint
similarity index 100%
rename from src/spx/.x/help/webjob.endpoint
rename to src/clis/spx/.x/help/webjob.endpoint
diff --git a/src/spx/.x/help/webjob.examples b/src/clis/spx/.x/help/webjob.examples
similarity index 100%
rename from src/spx/.x/help/webjob.examples
rename to src/clis/spx/.x/help/webjob.examples
diff --git a/src/spx/.x/help/webjob.execution b/src/clis/spx/.x/help/webjob.execution
similarity index 100%
rename from src/spx/.x/help/webjob.execution
rename to src/clis/spx/.x/help/webjob.execution
diff --git a/src/spx/.x/help/webjob.foreach b/src/clis/spx/.x/help/webjob.foreach
similarity index 100%
rename from src/spx/.x/help/webjob.foreach
rename to src/clis/spx/.x/help/webjob.foreach
diff --git a/src/spx/.x/help/webjob.list b/src/clis/spx/.x/help/webjob.list
similarity index 100%
rename from src/spx/.x/help/webjob.list
rename to src/clis/spx/.x/help/webjob.list
diff --git a/src/spx/.x/help/webjob.list.jobs b/src/clis/spx/.x/help/webjob.list.jobs
similarity index 100%
rename from src/spx/.x/help/webjob.list.jobs
rename to src/clis/spx/.x/help/webjob.list.jobs
diff --git a/src/spx/.x/help/webjob.password b/src/clis/spx/.x/help/webjob.password
similarity index 100%
rename from src/spx/.x/help/webjob.password
rename to src/clis/spx/.x/help/webjob.password
diff --git a/src/spx/.x/help/webjob.resources b/src/clis/spx/.x/help/webjob.resources
similarity index 100%
rename from src/spx/.x/help/webjob.resources
rename to src/clis/spx/.x/help/webjob.resources
diff --git a/src/spx/.x/help/webjob.results b/src/clis/spx/.x/help/webjob.results
similarity index 100%
rename from src/spx/.x/help/webjob.results
rename to src/clis/spx/.x/help/webjob.results
diff --git a/src/spx/.x/help/webjob.run b/src/clis/spx/.x/help/webjob.run
similarity index 100%
rename from src/spx/.x/help/webjob.run
rename to src/clis/spx/.x/help/webjob.run
diff --git a/src/spx/.x/help/webjob.setup b/src/clis/spx/.x/help/webjob.setup
similarity index 100%
rename from src/spx/.x/help/webjob.setup
rename to src/clis/spx/.x/help/webjob.setup
diff --git a/src/spx/.x/help/webjob.status b/src/clis/spx/.x/help/webjob.status
similarity index 100%
rename from src/spx/.x/help/webjob.status
rename to src/clis/spx/.x/help/webjob.status
diff --git a/src/spx/.x/help/webjob.upload b/src/clis/spx/.x/help/webjob.upload
similarity index 100%
rename from src/spx/.x/help/webjob.upload
rename to src/clis/spx/.x/help/webjob.upload
diff --git a/src/spx/.x/help/websocket.messages b/src/clis/spx/.x/help/websocket.messages
similarity index 100%
rename from src/spx/.x/help/websocket.messages
rename to src/clis/spx/.x/help/websocket.messages
diff --git a/src/spx/.x/help/zip b/src/clis/spx/.x/help/zip
similarity index 100%
rename from src/spx/.x/help/zip
rename to src/clis/spx/.x/help/zip
diff --git a/src/spx/BuildCommon.targets b/src/clis/spx/BuildCommon.targets
similarity index 100%
rename from src/spx/BuildCommon.targets
rename to src/clis/spx/BuildCommon.targets
diff --git a/src/spx/HowToCompileWithInternalSpeechSDK.md b/src/clis/spx/HowToCompileWithInternalSpeechSDK.md
similarity index 100%
rename from src/spx/HowToCompileWithInternalSpeechSDK.md
rename to src/clis/spx/HowToCompileWithInternalSpeechSDK.md
diff --git a/src/spx/HowToCompileWithPrivateSpeechSDK.md b/src/clis/spx/HowToCompileWithPrivateSpeechSDK.md
similarity index 100%
rename from src/spx/HowToCompileWithPrivateSpeechSDK.md
rename to src/clis/spx/HowToCompileWithPrivateSpeechSDK.md
diff --git a/src/spx/HowToRunEmbeddedSpeech.md b/src/clis/spx/HowToRunEmbeddedSpeech.md
similarity index 100%
rename from src/spx/HowToRunEmbeddedSpeech.md
rename to src/clis/spx/HowToRunEmbeddedSpeech.md
diff --git a/src/spx/LICENSE.txt b/src/clis/spx/LICENSE.txt
similarity index 100%
rename from src/spx/LICENSE.txt
rename to src/clis/spx/LICENSE.txt
diff --git a/src/spx/Microsoft.CognitiveServices.Speech.CLI-Dependency.targets b/src/clis/spx/Microsoft.CognitiveServices.Speech.CLI-Dependency.targets
similarity index 100%
rename from src/spx/Microsoft.CognitiveServices.Speech.CLI-Dependency.targets
rename to src/clis/spx/Microsoft.CognitiveServices.Speech.CLI-Dependency.targets
diff --git a/src/spx/NuGet.Config b/src/clis/spx/NuGet.Config
similarity index 100%
rename from src/spx/NuGet.Config
rename to src/clis/spx/NuGet.Config
diff --git a/src/spx/Program_SPX.cs b/src/clis/spx/Program_SPX.cs
similarity index 100%
rename from src/spx/Program_SPX.cs
rename to src/clis/spx/Program_SPX.cs
diff --git a/src/spx/Properties/PublishProfiles/folder publish spx-cli debug (linux-x64).pubxml b/src/clis/spx/Properties/PublishProfiles/folder publish spx-cli debug (linux-x64).pubxml
similarity index 100%
rename from src/spx/Properties/PublishProfiles/folder publish spx-cli debug (linux-x64).pubxml
rename to src/clis/spx/Properties/PublishProfiles/folder publish spx-cli debug (linux-x64).pubxml
diff --git a/src/spx/Properties/PublishProfiles/folder publish spx-cli debug (osx-64).pubxml b/src/clis/spx/Properties/PublishProfiles/folder publish spx-cli debug (osx-64).pubxml
similarity index 100%
rename from src/spx/Properties/PublishProfiles/folder publish spx-cli debug (osx-64).pubxml
rename to src/clis/spx/Properties/PublishProfiles/folder publish spx-cli debug (osx-64).pubxml
diff --git a/src/spx/Properties/PublishProfiles/folder publish spx-cli debug (win-x64).pubxml b/src/clis/spx/Properties/PublishProfiles/folder publish spx-cli debug (win-x64).pubxml
similarity index 100%
rename from src/spx/Properties/PublishProfiles/folder publish spx-cli debug (win-x64).pubxml
rename to src/clis/spx/Properties/PublishProfiles/folder publish spx-cli debug (win-x64).pubxml
diff --git a/src/spx/Properties/PublishProfiles/folder publish spx-cli release (linux-x64).pubxml b/src/clis/spx/Properties/PublishProfiles/folder publish spx-cli release (linux-x64).pubxml
similarity index 100%
rename from src/spx/Properties/PublishProfiles/folder publish spx-cli release (linux-x64).pubxml
rename to src/clis/spx/Properties/PublishProfiles/folder publish spx-cli release (linux-x64).pubxml
diff --git a/src/spx/Properties/PublishProfiles/folder publish spx-cli release (osx-64).pubxml b/src/clis/spx/Properties/PublishProfiles/folder publish spx-cli release (osx-64).pubxml
similarity index 100%
rename from src/spx/Properties/PublishProfiles/folder publish spx-cli release (osx-64).pubxml
rename to src/clis/spx/Properties/PublishProfiles/folder publish spx-cli release (osx-64).pubxml
diff --git a/src/spx/Properties/PublishProfiles/folder publish spx-cli release (win-x64).pubxml b/src/clis/spx/Properties/PublishProfiles/folder publish spx-cli release (win-x64).pubxml
similarity index 100%
rename from src/spx/Properties/PublishProfiles/folder publish spx-cli release (win-x64).pubxml
rename to src/clis/spx/Properties/PublishProfiles/folder publish spx-cli release (win-x64).pubxml
diff --git a/src/spx/THIRD_PARTY_NOTICE.txt b/src/clis/spx/THIRD_PARTY_NOTICE.txt
similarity index 100%
rename from src/spx/THIRD_PARTY_NOTICE.txt
rename to src/clis/spx/THIRD_PARTY_NOTICE.txt
diff --git a/src/spx/app.manifest b/src/clis/spx/app.manifest
similarity index 100%
rename from src/spx/app.manifest
rename to src/clis/spx/app.manifest
diff --git a/src/spx/ci/Dockerfile b/src/clis/spx/ci/Dockerfile
similarity index 100%
rename from src/spx/ci/Dockerfile
rename to src/clis/spx/ci/Dockerfile
diff --git a/src/spx/ci/build-all.sh b/src/clis/spx/ci/build-all.sh
similarity index 100%
rename from src/spx/ci/build-all.sh
rename to src/clis/spx/ci/build-all.sh
diff --git a/src/spx/ci/build-local-from-cmake-output.cmd b/src/clis/spx/ci/build-local-from-cmake-output.cmd
similarity index 100%
rename from src/spx/ci/build-local-from-cmake-output.cmd
rename to src/clis/spx/ci/build-local-from-cmake-output.cmd
diff --git a/src/spx/ci/build-local-from-nuget-download.cmd b/src/clis/spx/ci/build-local-from-nuget-download.cmd
similarity index 100%
rename from src/spx/ci/build-local-from-nuget-download.cmd
rename to src/clis/spx/ci/build-local-from-nuget-download.cmd
diff --git a/src/spx/ci/help-topic-crosscheck.sh b/src/clis/spx/ci/help-topic-crosscheck.sh
similarity index 100%
rename from src/spx/ci/help-topic-crosscheck.sh
rename to src/clis/spx/ci/help-topic-crosscheck.sh
diff --git a/src/spx/ci/spx-build-pipeline.yml b/src/clis/spx/ci/spx-build-pipeline.yml
similarity index 100%
rename from src/spx/ci/spx-build-pipeline.yml
rename to src/clis/spx/ci/spx-build-pipeline.yml
diff --git a/src/spx/ci/spx-test-pipeline.yml b/src/clis/spx/ci/spx-test-pipeline.yml
similarity index 100%
rename from src/spx/ci/spx-test-pipeline.yml
rename to src/clis/spx/ci/spx-test-pipeline.yml
diff --git a/src/spx/images/SPX_Build_Variables.jpg b/src/clis/spx/images/SPX_Build_Variables.jpg
similarity index 100%
rename from src/spx/images/SPX_Build_Variables.jpg
rename to src/clis/spx/images/SPX_Build_Variables.jpg
diff --git a/src/spx/packages.lock.json b/src/clis/spx/packages.lock.json
similarity index 100%
rename from src/spx/packages.lock.json
rename to src/clis/spx/packages.lock.json
diff --git a/src/spx/spx-cli.csproj b/src/clis/spx/spx-cli.csproj
similarity index 100%
rename from src/spx/spx-cli.csproj
rename to src/clis/spx/spx-cli.csproj
diff --git a/src/spx/spx-cli.sln b/src/clis/spx/spx-cli.sln
similarity index 100%
rename from src/spx/spx-cli.sln
rename to src/clis/spx/spx-cli.sln
diff --git a/src/spx/spx.dev.new.todo.md b/src/clis/spx/spx.dev.new.todo.md
similarity index 100%
rename from src/spx/spx.dev.new.todo.md
rename to src/clis/spx/spx.dev.new.todo.md
diff --git a/src/spx/tests/audio_and_trans.zip b/src/clis/spx/tests/audio_and_trans.zip
similarity index 100%
rename from src/spx/tests/audio_and_trans.zip
rename to src/clis/spx/tests/audio_and_trans.zip
diff --git a/src/spx/tests/check-all.cmd b/src/clis/spx/tests/check-all.cmd
similarity index 100%
rename from src/spx/tests/check-all.cmd
rename to src/clis/spx/tests/check-all.cmd
diff --git a/src/spx/tests/check-batch.cmd b/src/clis/spx/tests/check-batch.cmd
similarity index 100%
rename from src/spx/tests/check-batch.cmd
rename to src/clis/spx/tests/check-batch.cmd
diff --git a/src/spx/tests/check-cli.cmd b/src/clis/spx/tests/check-cli.cmd
similarity index 100%
rename from src/spx/tests/check-cli.cmd
rename to src/clis/spx/tests/check-cli.cmd
diff --git a/src/spx/tests/check-csr.cmd b/src/clis/spx/tests/check-csr.cmd
similarity index 100%
rename from src/spx/tests/check-csr.cmd
rename to src/clis/spx/tests/check-csr.cmd
diff --git a/src/spx/tests/check-help.cmd b/src/clis/spx/tests/check-help.cmd
similarity index 100%
rename from src/spx/tests/check-help.cmd
rename to src/clis/spx/tests/check-help.cmd
diff --git a/src/spx/tests/check-stress-test.cmd b/src/clis/spx/tests/check-stress-test.cmd
similarity index 100%
rename from src/spx/tests/check-stress-test.cmd
rename to src/clis/spx/tests/check-stress-test.cmd
diff --git a/src/spx/tests/cli/commands/config/config.local.name227266.1.set.test b/src/clis/spx/tests/cli/commands/config/config.local.name227266.1.set.test
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.local.name227266.1.set.test
rename to src/clis/spx/tests/cli/commands/config/config.local.name227266.1.set.test
diff --git a/src/spx/tests/cli/commands/config/config.local.name227266.1.set.test.run.output.expected b/src/clis/spx/tests/cli/commands/config/config.local.name227266.1.set.test.run.output.expected
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.local.name227266.1.set.test.run.output.expected
rename to src/clis/spx/tests/cli/commands/config/config.local.name227266.1.set.test.run.output.expected
diff --git a/src/spx/tests/cli/commands/config/config.local.name227266.2.get.test b/src/clis/spx/tests/cli/commands/config/config.local.name227266.2.get.test
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.local.name227266.2.get.test
rename to src/clis/spx/tests/cli/commands/config/config.local.name227266.2.get.test
diff --git a/src/spx/tests/cli/commands/config/config.local.name227266.2.get.test.run.output.expected b/src/clis/spx/tests/cli/commands/config/config.local.name227266.2.get.test.run.output.expected
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.local.name227266.2.get.test.run.output.expected
rename to src/clis/spx/tests/cli/commands/config/config.local.name227266.2.get.test.run.output.expected
diff --git a/src/spx/tests/cli/commands/config/config.local.name227266.3.find.test b/src/clis/spx/tests/cli/commands/config/config.local.name227266.3.find.test
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.local.name227266.3.find.test
rename to src/clis/spx/tests/cli/commands/config/config.local.name227266.3.find.test
diff --git a/src/spx/tests/cli/commands/config/config.local.name227266.3.find.test.run.output.expected b/src/clis/spx/tests/cli/commands/config/config.local.name227266.3.find.test.run.output.expected
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.local.name227266.3.find.test.run.output.expected
rename to src/clis/spx/tests/cli/commands/config/config.local.name227266.3.find.test.run.output.expected
diff --git a/src/spx/tests/cli/commands/config/config.local.name227266.4.add.test b/src/clis/spx/tests/cli/commands/config/config.local.name227266.4.add.test
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.local.name227266.4.add.test
rename to src/clis/spx/tests/cli/commands/config/config.local.name227266.4.add.test
diff --git a/src/spx/tests/cli/commands/config/config.local.name227266.4.add.test.run.output.expected b/src/clis/spx/tests/cli/commands/config/config.local.name227266.4.add.test.run.output.expected
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.local.name227266.4.add.test.run.output.expected
rename to src/clis/spx/tests/cli/commands/config/config.local.name227266.4.add.test.run.output.expected
diff --git a/src/spx/tests/cli/commands/config/config.local.name227266.5.clear.test b/src/clis/spx/tests/cli/commands/config/config.local.name227266.5.clear.test
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.local.name227266.5.clear.test
rename to src/clis/spx/tests/cli/commands/config/config.local.name227266.5.clear.test
diff --git a/src/spx/tests/cli/commands/config/config.local.name227266.5.clear.test.run.output.expected b/src/clis/spx/tests/cli/commands/config/config.local.name227266.5.clear.test.run.output.expected
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.local.name227266.5.clear.test.run.output.expected
rename to src/clis/spx/tests/cli/commands/config/config.local.name227266.5.clear.test.run.output.expected
diff --git a/src/spx/tests/cli/commands/config/config.usage.test b/src/clis/spx/tests/cli/commands/config/config.usage.test
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.usage.test
rename to src/clis/spx/tests/cli/commands/config/config.usage.test
diff --git a/src/spx/tests/cli/commands/config/config.usage.test.run.output.expected b/src/clis/spx/tests/cli/commands/config/config.usage.test.run.output.expected
similarity index 100%
rename from src/spx/tests/cli/commands/config/config.usage.test.run.output.expected
rename to src/clis/spx/tests/cli/commands/config/config.usage.test.run.output.expected
diff --git a/src/spx/tests/cli/commands/webjob/webjob.download.name.id.request.test b/src/clis/spx/tests/cli/commands/webjob/webjob.download.name.id.request.test
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.download.name.id.request.test
rename to src/clis/spx/tests/cli/commands/webjob/webjob.download.name.id.request.test
diff --git a/src/spx/tests/cli/commands/webjob/webjob.download.name.id.request.test.run.output.expect b/src/clis/spx/tests/cli/commands/webjob/webjob.download.name.id.request.test.run.output.expect
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.download.name.id.request.test.run.output.expect
rename to src/clis/spx/tests/cli/commands/webjob/webjob.download.name.id.request.test.run.output.expect
diff --git a/src/spx/tests/cli/commands/webjob/webjob.download.name.request.test b/src/clis/spx/tests/cli/commands/webjob/webjob.download.name.request.test
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.download.name.request.test
rename to src/clis/spx/tests/cli/commands/webjob/webjob.download.name.request.test
diff --git a/src/spx/tests/cli/commands/webjob/webjob.download.name.request.test.run.output.expect b/src/clis/spx/tests/cli/commands/webjob/webjob.download.name.request.test.run.output.expect
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.download.name.request.test.run.output.expect
rename to src/clis/spx/tests/cli/commands/webjob/webjob.download.name.request.test.run.output.expect
diff --git a/src/spx/tests/cli/commands/webjob/webjob.download.null.request.test b/src/clis/spx/tests/cli/commands/webjob/webjob.download.null.request.test
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.download.null.request.test
rename to src/clis/spx/tests/cli/commands/webjob/webjob.download.null.request.test
diff --git a/src/spx/tests/cli/commands/webjob/webjob.download.null.request.test.run.output.expect b/src/clis/spx/tests/cli/commands/webjob/webjob.download.null.request.test.run.output.expect
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.download.null.request.test.run.output.expect
rename to src/clis/spx/tests/cli/commands/webjob/webjob.download.null.request.test.run.output.expect
diff --git a/src/spx/tests/cli/commands/webjob/webjob.list.job.run.files.request.test b/src/clis/spx/tests/cli/commands/webjob/webjob.list.job.run.files.request.test
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.list.job.run.files.request.test
rename to src/clis/spx/tests/cli/commands/webjob/webjob.list.job.run.files.request.test
diff --git a/src/spx/tests/cli/commands/webjob/webjob.list.job.run.files.request.test.run.output.expect b/src/clis/spx/tests/cli/commands/webjob/webjob.list.job.run.files.request.test.run.output.expect
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.list.job.run.files.request.test.run.output.expect
rename to src/clis/spx/tests/cli/commands/webjob/webjob.list.job.run.files.request.test.run.output.expect
diff --git a/src/spx/tests/cli/commands/webjob/webjob.list.job.runs.request.test b/src/clis/spx/tests/cli/commands/webjob/webjob.list.job.runs.request.test
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.list.job.runs.request.test
rename to src/clis/spx/tests/cli/commands/webjob/webjob.list.job.runs.request.test
diff --git a/src/spx/tests/cli/commands/webjob/webjob.list.job.runs.request.test.run.output.expect b/src/clis/spx/tests/cli/commands/webjob/webjob.list.job.runs.request.test.run.output.expect
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.list.job.runs.request.test.run.output.expect
rename to src/clis/spx/tests/cli/commands/webjob/webjob.list.job.runs.request.test.run.output.expect
diff --git a/src/spx/tests/cli/commands/webjob/webjob.list.jobs.request.test b/src/clis/spx/tests/cli/commands/webjob/webjob.list.jobs.request.test
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.list.jobs.request.test
rename to src/clis/spx/tests/cli/commands/webjob/webjob.list.jobs.request.test
diff --git a/src/spx/tests/cli/commands/webjob/webjob.list.jobs.request.test.run.output.expect b/src/clis/spx/tests/cli/commands/webjob/webjob.list.jobs.request.test.run.output.expect
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.list.jobs.request.test.run.output.expect
rename to src/clis/spx/tests/cli/commands/webjob/webjob.list.jobs.request.test.run.output.expect
diff --git a/src/spx/tests/cli/commands/webjob/webjob.run.request.test b/src/clis/spx/tests/cli/commands/webjob/webjob.run.request.test
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.run.request.test
rename to src/clis/spx/tests/cli/commands/webjob/webjob.run.request.test
diff --git a/src/spx/tests/cli/commands/webjob/webjob.run.request.test.run.output.expect b/src/clis/spx/tests/cli/commands/webjob/webjob.run.request.test.run.output.expect
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.run.request.test.run.output.expect
rename to src/clis/spx/tests/cli/commands/webjob/webjob.run.request.test.run.output.expect
diff --git a/src/spx/tests/cli/commands/webjob/webjob.status.request.test b/src/clis/spx/tests/cli/commands/webjob/webjob.status.request.test
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.status.request.test
rename to src/clis/spx/tests/cli/commands/webjob/webjob.status.request.test
diff --git a/src/spx/tests/cli/commands/webjob/webjob.status.request.test.run.output.expect b/src/clis/spx/tests/cli/commands/webjob/webjob.status.request.test.run.output.expect
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.status.request.test.run.output.expect
rename to src/clis/spx/tests/cli/commands/webjob/webjob.status.request.test.run.output.expect
diff --git a/src/spx/tests/cli/commands/webjob/webjob.upload.request.step1.test.run.output.expect b/src/clis/spx/tests/cli/commands/webjob/webjob.upload.request.step1.test.run.output.expect
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.upload.request.step1.test.run.output.expect
rename to src/clis/spx/tests/cli/commands/webjob/webjob.upload.request.step1.test.run.output.expect
diff --git a/src/spx/tests/cli/commands/webjob/webjob.upload.request.step1.test.skip b/src/clis/spx/tests/cli/commands/webjob/webjob.upload.request.step1.test.skip
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.upload.request.step1.test.skip
rename to src/clis/spx/tests/cli/commands/webjob/webjob.upload.request.step1.test.skip
diff --git a/src/spx/tests/cli/commands/webjob/webjob.upload.request.step2.test.run.output.expect b/src/clis/spx/tests/cli/commands/webjob/webjob.upload.request.step2.test.run.output.expect
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.upload.request.step2.test.run.output.expect
rename to src/clis/spx/tests/cli/commands/webjob/webjob.upload.request.step2.test.run.output.expect
diff --git a/src/spx/tests/cli/commands/webjob/webjob.upload.request.step2.test.skip b/src/clis/spx/tests/cli/commands/webjob/webjob.upload.request.step2.test.skip
similarity index 100%
rename from src/spx/tests/cli/commands/webjob/webjob.upload.request.step2.test.skip
rename to src/clis/spx/tests/cli/commands/webjob/webjob.upload.request.step2.test.skip
diff --git a/src/spx/tests/cli/diagnostics/recognize.hello.path.term.dtor.test b/src/clis/spx/tests/cli/diagnostics/recognize.hello.path.term.dtor.test
similarity index 100%
rename from src/spx/tests/cli/diagnostics/recognize.hello.path.term.dtor.test
rename to src/clis/spx/tests/cli/diagnostics/recognize.hello.path.term.dtor.test
diff --git a/src/spx/tests/cli/diagnostics/recognize.hello.path.term.dtor.test.x.command.diagnostics.log.expected b/src/clis/spx/tests/cli/diagnostics/recognize.hello.path.term.dtor.test.x.command.diagnostics.log.expected
similarity index 100%
rename from src/spx/tests/cli/diagnostics/recognize.hello.path.term.dtor.test.x.command.diagnostics.log.expected
rename to src/clis/spx/tests/cli/diagnostics/recognize.hello.path.term.dtor.test.x.command.diagnostics.log.expected
diff --git a/src/spx/tests/cli/diagnostics/translate.hello.path.term.dtor.test b/src/clis/spx/tests/cli/diagnostics/translate.hello.path.term.dtor.test
similarity index 100%
rename from src/spx/tests/cli/diagnostics/translate.hello.path.term.dtor.test
rename to src/clis/spx/tests/cli/diagnostics/translate.hello.path.term.dtor.test
diff --git a/src/spx/tests/cli/diagnostics/translate.hello.path.term.dtor.test.x.command.diagnostics.log.expect b/src/clis/spx/tests/cli/diagnostics/translate.hello.path.term.dtor.test.x.command.diagnostics.log.expect
similarity index 100%
rename from src/spx/tests/cli/diagnostics/translate.hello.path.term.dtor.test.x.command.diagnostics.log.expect
rename to src/clis/spx/tests/cli/diagnostics/translate.hello.path.term.dtor.test.x.command.diagnostics.log.expect
diff --git a/src/spx/tests/pronunciation.txt b/src/clis/spx/tests/pronunciation.txt
similarity index 100%
rename from src/spx/tests/pronunciation.txt
rename to src/clis/spx/tests/pronunciation.txt
diff --git a/src/spx/tests/related_text.txt b/src/clis/spx/tests/related_text.txt
similarity index 100%
rename from src/spx/tests/related_text.txt
rename to src/clis/spx/tests/related_text.txt
diff --git a/src/spx/tests/sentences.txt b/src/clis/spx/tests/sentences.txt
similarity index 100%
rename from src/spx/tests/sentences.txt
rename to src/clis/spx/tests/sentences.txt
diff --git a/src/spx/tests/stress-text.txt b/src/clis/spx/tests/stress-text.txt
similarity index 100%
rename from src/spx/tests/stress-text.txt
rename to src/clis/spx/tests/stress-text.txt
diff --git a/src/spx/tests/test b/src/clis/spx/tests/test
similarity index 100%
rename from src/spx/tests/test
rename to src/clis/spx/tests/test
diff --git a/src/spx/tests/test.batch b/src/clis/spx/tests/test.batch
similarity index 100%
rename from src/spx/tests/test.batch
rename to src/clis/spx/tests/test.batch
diff --git a/src/spx/tests/test.batch.transcription.1.job b/src/clis/spx/tests/test.batch.transcription.1.job
similarity index 100%
rename from src/spx/tests/test.batch.transcription.1.job
rename to src/clis/spx/tests/test.batch.transcription.1.job
diff --git a/src/spx/tests/test.batch.transcription.2.job b/src/clis/spx/tests/test.batch.transcription.2.job
similarity index 100%
rename from src/spx/tests/test.batch.transcription.2.job
rename to src/clis/spx/tests/test.batch.transcription.2.job
diff --git a/src/spx/tests/test.batch.transcription.2.job.foreach.0.tsv.file b/src/clis/spx/tests/test.batch.transcription.2.job.foreach.0.tsv.file
similarity index 100%
rename from src/spx/tests/test.batch.transcription.2.job.foreach.0.tsv.file
rename to src/clis/spx/tests/test.batch.transcription.2.job.foreach.0.tsv.file
diff --git a/src/spx/tests/test.config.clear.test.1.job b/src/clis/spx/tests/test.config.clear.test.1.job
similarity index 100%
rename from src/spx/tests/test.config.clear.test.1.job
rename to src/clis/spx/tests/test.config.clear.test.1.job
diff --git a/src/spx/tests/test.config.clear.test.2.job b/src/clis/spx/tests/test.config.clear.test.2.job
similarity index 100%
rename from src/spx/tests/test.config.clear.test.2.job
rename to src/clis/spx/tests/test.config.clear.test.2.job
diff --git a/src/spx/tests/test.config.clear.test.3.job b/src/clis/spx/tests/test.config.clear.test.3.job
similarity index 100%
rename from src/spx/tests/test.config.clear.test.3.job
rename to src/clis/spx/tests/test.config.clear.test.3.job
diff --git a/src/spx/tests/test.config.clear.test.4.job b/src/clis/spx/tests/test.config.clear.test.4.job
similarity index 100%
rename from src/spx/tests/test.config.clear.test.4.job
rename to src/clis/spx/tests/test.config.clear.test.4.job
diff --git a/src/spx/tests/test.config.find.1.job b/src/clis/spx/tests/test.config.find.1.job
similarity index 100%
rename from src/spx/tests/test.config.find.1.job
rename to src/clis/spx/tests/test.config.find.1.job
diff --git a/src/spx/tests/test.config.find.2.job b/src/clis/spx/tests/test.config.find.2.job
similarity index 100%
rename from src/spx/tests/test.config.find.2.job
rename to src/clis/spx/tests/test.config.find.2.job
diff --git a/src/spx/tests/test.config.find.3.job b/src/clis/spx/tests/test.config.find.3.job
similarity index 100%
rename from src/spx/tests/test.config.find.3.job
rename to src/clis/spx/tests/test.config.find.3.job
diff --git a/src/spx/tests/test.config.find.4.job b/src/clis/spx/tests/test.config.find.4.job
similarity index 100%
rename from src/spx/tests/test.config.find.4.job
rename to src/clis/spx/tests/test.config.find.4.job
diff --git a/src/spx/tests/test.config.find.5.job b/src/clis/spx/tests/test.config.find.5.job
similarity index 100%
rename from src/spx/tests/test.config.find.5.job
rename to src/clis/spx/tests/test.config.find.5.job
diff --git a/src/spx/tests/test.config.find.6.job b/src/clis/spx/tests/test.config.find.6.job
similarity index 100%
rename from src/spx/tests/test.config.find.6.job
rename to src/clis/spx/tests/test.config.find.6.job
diff --git a/src/spx/tests/test.config.find.7.job b/src/clis/spx/tests/test.config.find.7.job
similarity index 100%
rename from src/spx/tests/test.config.find.7.job
rename to src/clis/spx/tests/test.config.find.7.job
diff --git a/src/spx/tests/test.foreach.stress.tsv.file b/src/clis/spx/tests/test.foreach.stress.tsv.file
similarity index 100%
rename from src/spx/tests/test.foreach.stress.tsv.file
rename to src/clis/spx/tests/test.foreach.stress.tsv.file
diff --git a/src/spx/tests/test.intent.1.job b/src/clis/spx/tests/test.intent.1.job
similarity index 100%
rename from src/spx/tests/test.intent.1.job
rename to src/clis/spx/tests/test.intent.1.job
diff --git a/src/spx/tests/test.intent.2.job b/src/clis/spx/tests/test.intent.2.job
similarity index 100%
rename from src/spx/tests/test.intent.2.job
rename to src/clis/spx/tests/test.intent.2.job
diff --git a/src/spx/tests/test.intent.3.job b/src/clis/spx/tests/test.intent.3.job
similarity index 100%
rename from src/spx/tests/test.intent.3.job
rename to src/clis/spx/tests/test.intent.3.job
diff --git a/src/spx/tests/test.intent.4.job b/src/clis/spx/tests/test.intent.4.job
similarity index 100%
rename from src/spx/tests/test.intent.4.job
rename to src/clis/spx/tests/test.intent.4.job
diff --git a/src/spx/tests/test.intent.setup.ChangeReco b/src/clis/spx/tests/test.intent.setup.ChangeReco
similarity index 100%
rename from src/spx/tests/test.intent.setup.ChangeReco
rename to src/clis/spx/tests/test.intent.setup.ChangeReco
diff --git a/src/spx/tests/test.stress b/src/clis/spx/tests/test.stress
similarity index 100%
rename from src/spx/tests/test.stress
rename to src/clis/spx/tests/test.stress
diff --git a/src/spx/tests/test.stress.recognize.1.job b/src/clis/spx/tests/test.stress.recognize.1.job
similarity index 100%
rename from src/spx/tests/test.stress.recognize.1.job
rename to src/clis/spx/tests/test.stress.recognize.1.job
diff --git a/src/spx/tests/test.stress.synthesize.1.job b/src/clis/spx/tests/test.stress.synthesize.1.job
similarity index 100%
rename from src/spx/tests/test.stress.synthesize.1.job
rename to src/clis/spx/tests/test.stress.synthesize.1.job
diff --git a/src/spx/tests/test.stress.synthesize.1.job.foreach.0.tsv.file b/src/clis/spx/tests/test.stress.synthesize.1.job.foreach.0.tsv.file
similarity index 100%
rename from src/spx/tests/test.stress.synthesize.1.job.foreach.0.tsv.file
rename to src/clis/spx/tests/test.stress.synthesize.1.job.foreach.0.tsv.file
diff --git a/src/spx/tests/test.stress.translate.1.job b/src/clis/spx/tests/test.stress.translate.1.job
similarity index 100%
rename from src/spx/tests/test.stress.translate.1.job
rename to src/clis/spx/tests/test.stress.translate.1.job
diff --git a/src/spx/tests/test.tsv b/src/clis/spx/tests/test.tsv
similarity index 100%
rename from src/spx/tests/test.tsv
rename to src/clis/spx/tests/test.tsv
diff --git a/src/spx/tests/this.is.a.test.of.the.emergency.broadcast.system.txt b/src/clis/spx/tests/this.is.a.test.of.the.emergency.broadcast.system.txt
similarity index 100%
rename from src/spx/tests/this.is.a.test.of.the.emergency.broadcast.system.txt
rename to src/clis/spx/tests/this.is.a.test.of.the.emergency.broadcast.system.txt
diff --git a/src/spx/tests/this.is.a.test.txt b/src/clis/spx/tests/this.is.a.test.txt
similarity index 100%
rename from src/spx/tests/this.is.a.test.txt
rename to src/clis/spx/tests/this.is.a.test.txt
diff --git a/src/spx/todo.md b/src/clis/spx/todo.md
similarity index 100%
rename from src/spx/todo.md
rename to src/clis/spx/todo.md
diff --git a/src/vz/.x/config/connection b/src/clis/vz/.x/config/connection
similarity index 100%
rename from src/vz/.x/config/connection
rename to src/clis/vz/.x/config/connection
diff --git a/src/vz/.x/config/connection.from.endpoint b/src/clis/vz/.x/config/connection.from.endpoint
similarity index 100%
rename from src/vz/.x/config/connection.from.endpoint
rename to src/clis/vz/.x/config/connection.from.endpoint
diff --git a/src/vz/.x/config/connection.from.host b/src/clis/vz/.x/config/connection.from.host
similarity index 100%
rename from src/vz/.x/config/connection.from.host
rename to src/clis/vz/.x/config/connection.from.host
diff --git a/src/vz/.x/config/connection.from.region b/src/clis/vz/.x/config/connection.from.region
similarity index 100%
rename from src/vz/.x/config/connection.from.region
rename to src/clis/vz/.x/config/connection.from.region
diff --git a/src/vz/.x/config/default.log b/src/clis/vz/.x/config/default.log
similarity index 100%
rename from src/vz/.x/config/default.log
rename to src/clis/vz/.x/config/default.log
diff --git a/src/vz/.x/config/default.path b/src/clis/vz/.x/config/default.path
similarity index 100%
rename from src/vz/.x/config/default.path
rename to src/clis/vz/.x/config/default.path
diff --git a/src/vz/.x/config/defaults b/src/clis/vz/.x/config/defaults
similarity index 100%
rename from src/vz/.x/config/defaults
rename to src/clis/vz/.x/config/defaults
diff --git a/src/vz/.x/config/endpoint b/src/clis/vz/.x/config/endpoint
similarity index 100%
rename from src/vz/.x/config/endpoint
rename to src/clis/vz/.x/config/endpoint
diff --git a/src/vz/.x/config/fiddler b/src/clis/vz/.x/config/fiddler
similarity index 100%
rename from src/vz/.x/config/fiddler
rename to src/clis/vz/.x/config/fiddler
diff --git a/src/vz/.x/config/host b/src/clis/vz/.x/config/host
similarity index 100%
rename from src/vz/.x/config/host
rename to src/clis/vz/.x/config/host
diff --git a/src/vz/.x/config/image.default.config b/src/clis/vz/.x/config/image.default.config
similarity index 100%
rename from src/vz/.x/config/image.default.config
rename to src/clis/vz/.x/config/image.default.config
diff --git a/src/vz/.x/config/image.default.connection b/src/clis/vz/.x/config/image.default.connection
similarity index 100%
rename from src/vz/.x/config/image.default.connection
rename to src/clis/vz/.x/config/image.default.connection
diff --git a/src/vz/.x/config/image.default.output b/src/clis/vz/.x/config/image.default.output
similarity index 100%
rename from src/vz/.x/config/image.default.output
rename to src/clis/vz/.x/config/image.default.output
diff --git a/src/vz/.x/config/image.output.all.standard b/src/clis/vz/.x/config/image.output.all.standard
similarity index 100%
rename from src/vz/.x/config/image.output.all.standard
rename to src/clis/vz/.x/config/image.output.all.standard
diff --git a/src/vz/.x/config/image.region b/src/clis/vz/.x/config/image.region
similarity index 100%
rename from src/vz/.x/config/image.region
rename to src/clis/vz/.x/config/image.region
diff --git a/src/vz/.x/config/init.default.config b/src/clis/vz/.x/config/init.default.config
similarity index 100%
rename from src/vz/.x/config/init.default.config
rename to src/clis/vz/.x/config/init.default.config
diff --git a/src/vz/.x/config/init.default.output b/src/clis/vz/.x/config/init.default.output
similarity index 100%
rename from src/vz/.x/config/init.default.output
rename to src/clis/vz/.x/config/init.default.output
diff --git a/src/vz/.x/config/input.path b/src/clis/vz/.x/config/input.path
similarity index 100%
rename from src/vz/.x/config/input.path
rename to src/clis/vz/.x/config/input.path
diff --git a/src/vz/.x/config/key b/src/clis/vz/.x/config/key
similarity index 100%
rename from src/vz/.x/config/key
rename to src/clis/vz/.x/config/key
diff --git a/src/vz/.x/config/log.pid b/src/clis/vz/.x/config/log.pid
similarity index 100%
rename from src/vz/.x/config/log.pid
rename to src/clis/vz/.x/config/log.pid
diff --git a/src/vz/.x/config/log.time b/src/clis/vz/.x/config/log.time
similarity index 100%
rename from src/vz/.x/config/log.time
rename to src/clis/vz/.x/config/log.time
diff --git a/src/vz/.x/config/log.time.pid b/src/clis/vz/.x/config/log.time.pid
similarity index 100%
rename from src/vz/.x/config/log.time.pid
rename to src/clis/vz/.x/config/log.time.pid
diff --git a/src/vz/.x/config/my.defaults b/src/clis/vz/.x/config/my.defaults
similarity index 100%
rename from src/vz/.x/config/my.defaults
rename to src/clis/vz/.x/config/my.defaults
diff --git a/src/vz/.x/config/none b/src/clis/vz/.x/config/none
similarity index 100%
rename from src/vz/.x/config/none
rename to src/clis/vz/.x/config/none
diff --git a/src/vz/.x/config/output b/src/clis/vz/.x/config/output
similarity index 100%
rename from src/vz/.x/config/output
rename to src/clis/vz/.x/config/output
diff --git a/src/vz/.x/config/output.all.standard b/src/clis/vz/.x/config/output.all.standard
similarity index 100%
rename from src/vz/.x/config/output.all.standard
rename to src/clis/vz/.x/config/output.all.standard
diff --git a/src/vz/.x/config/person.default.config b/src/clis/vz/.x/config/person.default.config
similarity index 100%
rename from src/vz/.x/config/person.default.config
rename to src/clis/vz/.x/config/person.default.config
diff --git a/src/vz/.x/config/person.default.connection b/src/clis/vz/.x/config/person.default.connection
similarity index 100%
rename from src/vz/.x/config/person.default.connection
rename to src/clis/vz/.x/config/person.default.connection
diff --git a/src/vz/.x/config/person.default.output b/src/clis/vz/.x/config/person.default.output
similarity index 100%
rename from src/vz/.x/config/person.default.output
rename to src/clis/vz/.x/config/person.default.output
diff --git a/src/vz/.x/config/person.output.all.standard b/src/clis/vz/.x/config/person.output.all.standard
similarity index 100%
rename from src/vz/.x/config/person.output.all.standard
rename to src/clis/vz/.x/config/person.output.all.standard
diff --git a/src/vz/.x/config/region b/src/clis/vz/.x/config/region
similarity index 100%
rename from src/vz/.x/config/region
rename to src/clis/vz/.x/config/region
diff --git a/src/vz/.x/config/run.default.config b/src/clis/vz/.x/config/run.default.config
similarity index 100%
rename from src/vz/.x/config/run.default.config
rename to src/clis/vz/.x/config/run.default.config
diff --git a/src/vz/.x/config/vz.defaults b/src/clis/vz/.x/config/vz.defaults
similarity index 100%
rename from src/vz/.x/config/vz.defaults
rename to src/clis/vz/.x/config/vz.defaults
diff --git a/src/vz/.x/help/config b/src/clis/vz/.x/help/config
similarity index 100%
rename from src/vz/.x/help/config
rename to src/clis/vz/.x/help/config
diff --git a/src/vz/.x/help/config.add b/src/clis/vz/.x/help/config.add
similarity index 100%
rename from src/vz/.x/help/config.add
rename to src/clis/vz/.x/help/config.add
diff --git a/src/vz/.x/help/config.advanced b/src/clis/vz/.x/help/config.advanced
similarity index 100%
rename from src/vz/.x/help/config.advanced
rename to src/clis/vz/.x/help/config.advanced
diff --git a/src/vz/.x/help/config.clear b/src/clis/vz/.x/help/config.clear
similarity index 100%
rename from src/vz/.x/help/config.clear
rename to src/clis/vz/.x/help/config.clear
diff --git a/src/vz/.x/help/config.command b/src/clis/vz/.x/help/config.command
similarity index 100%
rename from src/vz/.x/help/config.command
rename to src/clis/vz/.x/help/config.command
diff --git a/src/vz/.x/help/config.examples b/src/clis/vz/.x/help/config.examples
similarity index 100%
rename from src/vz/.x/help/config.examples
rename to src/clis/vz/.x/help/config.examples
diff --git a/src/vz/.x/help/config.find b/src/clis/vz/.x/help/config.find
similarity index 100%
rename from src/vz/.x/help/config.find
rename to src/clis/vz/.x/help/config.find
diff --git a/src/vz/.x/help/config.hive b/src/clis/vz/.x/help/config.hive
similarity index 100%
rename from src/vz/.x/help/config.hive
rename to src/clis/vz/.x/help/config.hive
diff --git a/src/vz/.x/help/config.region b/src/clis/vz/.x/help/config.region
similarity index 100%
rename from src/vz/.x/help/config.region
rename to src/clis/vz/.x/help/config.region
diff --git a/src/vz/.x/help/config.scope b/src/clis/vz/.x/help/config.scope
similarity index 100%
rename from src/vz/.x/help/config.scope
rename to src/clis/vz/.x/help/config.scope
diff --git a/src/vz/.x/help/config.set b/src/clis/vz/.x/help/config.set
similarity index 100%
rename from src/vz/.x/help/config.set
rename to src/clis/vz/.x/help/config.set
diff --git a/src/vz/.x/help/documentation b/src/clis/vz/.x/help/documentation
similarity index 100%
rename from src/vz/.x/help/documentation
rename to src/clis/vz/.x/help/documentation
diff --git a/src/vz/.x/help/endpoint b/src/clis/vz/.x/help/endpoint
similarity index 100%
rename from src/vz/.x/help/endpoint
rename to src/clis/vz/.x/help/endpoint
diff --git a/src/vz/.x/help/examples b/src/clis/vz/.x/help/examples
similarity index 100%
rename from src/vz/.x/help/examples
rename to src/clis/vz/.x/help/examples
diff --git a/src/vz/.x/help/face b/src/clis/vz/.x/help/face
similarity index 100%
rename from src/vz/.x/help/face
rename to src/clis/vz/.x/help/face
diff --git a/src/vz/.x/help/face.identify b/src/clis/vz/.x/help/face.identify
similarity index 100%
rename from src/vz/.x/help/face.identify
rename to src/clis/vz/.x/help/face.identify
diff --git a/src/vz/.x/help/face.identify.examples b/src/clis/vz/.x/help/face.identify.examples
similarity index 100%
rename from src/vz/.x/help/face.identify.examples
rename to src/clis/vz/.x/help/face.identify.examples
diff --git a/src/vz/.x/help/face.verify b/src/clis/vz/.x/help/face.verify
similarity index 100%
rename from src/vz/.x/help/face.verify
rename to src/clis/vz/.x/help/face.verify
diff --git a/src/vz/.x/help/face.verify.examples b/src/clis/vz/.x/help/face.verify.examples
similarity index 100%
rename from src/vz/.x/help/face.verify.examples
rename to src/clis/vz/.x/help/face.verify.examples
diff --git a/src/vz/.x/help/help b/src/clis/vz/.x/help/help
similarity index 100%
rename from src/vz/.x/help/help
rename to src/clis/vz/.x/help/help
diff --git a/src/vz/.x/help/hive b/src/clis/vz/.x/help/hive
similarity index 100%
rename from src/vz/.x/help/hive
rename to src/clis/vz/.x/help/hive
diff --git a/src/vz/.x/help/image b/src/clis/vz/.x/help/image
similarity index 100%
rename from src/vz/.x/help/image
rename to src/clis/vz/.x/help/image
diff --git a/src/vz/.x/help/image.analyze b/src/clis/vz/.x/help/image.analyze
similarity index 100%
rename from src/vz/.x/help/image.analyze
rename to src/clis/vz/.x/help/image.analyze
diff --git a/src/vz/.x/help/image.analyze.examples b/src/clis/vz/.x/help/image.analyze.examples
similarity index 100%
rename from src/vz/.x/help/image.analyze.examples
rename to src/clis/vz/.x/help/image.analyze.examples
diff --git a/src/vz/.x/help/image.read b/src/clis/vz/.x/help/image.read
similarity index 100%
rename from src/vz/.x/help/image.read
rename to src/clis/vz/.x/help/image.read
diff --git a/src/vz/.x/help/image.read.examples b/src/clis/vz/.x/help/image.read.examples
similarity index 100%
rename from src/vz/.x/help/image.read.examples
rename to src/clis/vz/.x/help/image.read.examples
diff --git a/src/vz/.x/help/include.create.resource b/src/clis/vz/.x/help/include.create.resource
similarity index 100%
rename from src/vz/.x/help/include.create.resource
rename to src/clis/vz/.x/help/include.create.resource
diff --git a/src/vz/.x/help/include.create.resource.extra b/src/clis/vz/.x/help/include.create.resource.extra
similarity index 100%
rename from src/vz/.x/help/include.create.resource.extra
rename to src/clis/vz/.x/help/include.create.resource.extra
diff --git a/src/vz/.x/help/include.the.azure.vision.service b/src/clis/vz/.x/help/include.the.azure.vision.service
similarity index 100%
rename from src/vz/.x/help/include.the.azure.vision.service
rename to src/clis/vz/.x/help/include.the.azure.vision.service
diff --git a/src/vz/.x/help/include.the.azure.vision.service.long b/src/clis/vz/.x/help/include.the.azure.vision.service.long
similarity index 100%
rename from src/vz/.x/help/include.the.azure.vision.service.long
rename to src/clis/vz/.x/help/include.the.azure.vision.service.long
diff --git a/src/vz/.x/help/include.the.config.command b/src/clis/vz/.x/help/include.the.config.command
similarity index 100%
rename from src/vz/.x/help/include.the.config.command
rename to src/clis/vz/.x/help/include.the.config.command
diff --git a/src/vz/.x/help/include.the.face.command.long b/src/clis/vz/.x/help/include.the.face.command.long
similarity index 100%
rename from src/vz/.x/help/include.the.face.command.long
rename to src/clis/vz/.x/help/include.the.face.command.long
diff --git a/src/vz/.x/help/include.the.face.identify.command b/src/clis/vz/.x/help/include.the.face.identify.command
similarity index 100%
rename from src/vz/.x/help/include.the.face.identify.command
rename to src/clis/vz/.x/help/include.the.face.identify.command
diff --git a/src/vz/.x/help/include.the.face.identify.command.long b/src/clis/vz/.x/help/include.the.face.identify.command.long
similarity index 100%
rename from src/vz/.x/help/include.the.face.identify.command.long
rename to src/clis/vz/.x/help/include.the.face.identify.command.long
diff --git a/src/vz/.x/help/include.the.face.verify.command b/src/clis/vz/.x/help/include.the.face.verify.command
similarity index 100%
rename from src/vz/.x/help/include.the.face.verify.command
rename to src/clis/vz/.x/help/include.the.face.verify.command
diff --git a/src/vz/.x/help/include.the.face.verify.command.long b/src/clis/vz/.x/help/include.the.face.verify.command.long
similarity index 100%
rename from src/vz/.x/help/include.the.face.verify.command.long
rename to src/clis/vz/.x/help/include.the.face.verify.command.long
diff --git a/src/vz/.x/help/include.the.image.analyze.command b/src/clis/vz/.x/help/include.the.image.analyze.command
similarity index 100%
rename from src/vz/.x/help/include.the.image.analyze.command
rename to src/clis/vz/.x/help/include.the.image.analyze.command
diff --git a/src/vz/.x/help/include.the.image.analyze.command.long b/src/clis/vz/.x/help/include.the.image.analyze.command.long
similarity index 100%
rename from src/vz/.x/help/include.the.image.analyze.command.long
rename to src/clis/vz/.x/help/include.the.image.analyze.command.long
diff --git a/src/vz/.x/help/include.the.image.command.long b/src/clis/vz/.x/help/include.the.image.command.long
similarity index 100%
rename from src/vz/.x/help/include.the.image.command.long
rename to src/clis/vz/.x/help/include.the.image.command.long
diff --git a/src/vz/.x/help/include.the.image.read.command b/src/clis/vz/.x/help/include.the.image.read.command
similarity index 100%
rename from src/vz/.x/help/include.the.image.read.command
rename to src/clis/vz/.x/help/include.the.image.read.command
diff --git a/src/vz/.x/help/include.the.image.read.command.long b/src/clis/vz/.x/help/include.the.image.read.command.long
similarity index 100%
rename from src/vz/.x/help/include.the.image.read.command.long
rename to src/clis/vz/.x/help/include.the.image.read.command.long
diff --git a/src/vz/.x/help/include.the.init.command.long b/src/clis/vz/.x/help/include.the.init.command.long
similarity index 100%
rename from src/vz/.x/help/include.the.init.command.long
rename to src/clis/vz/.x/help/include.the.init.command.long
diff --git a/src/vz/.x/help/include.the.key.option b/src/clis/vz/.x/help/include.the.key.option
similarity index 100%
rename from src/vz/.x/help/include.the.key.option
rename to src/clis/vz/.x/help/include.the.key.option
diff --git a/src/vz/.x/help/include.the.key.option.where b/src/clis/vz/.x/help/include.the.key.option.where
similarity index 100%
rename from src/vz/.x/help/include.the.key.option.where
rename to src/clis/vz/.x/help/include.the.key.option.where
diff --git a/src/vz/.x/help/include.the.log.option b/src/clis/vz/.x/help/include.the.log.option
similarity index 100%
rename from src/vz/.x/help/include.the.log.option
rename to src/clis/vz/.x/help/include.the.log.option
diff --git a/src/vz/.x/help/include.the.person.create.command b/src/clis/vz/.x/help/include.the.person.create.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.create.command
rename to src/clis/vz/.x/help/include.the.person.create.command
diff --git a/src/vz/.x/help/include.the.person.delete.command b/src/clis/vz/.x/help/include.the.person.delete.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.delete.command
rename to src/clis/vz/.x/help/include.the.person.delete.command
diff --git a/src/vz/.x/help/include.the.person.face.add.command b/src/clis/vz/.x/help/include.the.person.face.add.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.face.add.command
rename to src/clis/vz/.x/help/include.the.person.face.add.command
diff --git a/src/vz/.x/help/include.the.person.face.delete.command b/src/clis/vz/.x/help/include.the.person.face.delete.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.face.delete.command
rename to src/clis/vz/.x/help/include.the.person.face.delete.command
diff --git a/src/vz/.x/help/include.the.person.face.list.command b/src/clis/vz/.x/help/include.the.person.face.list.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.face.list.command
rename to src/clis/vz/.x/help/include.the.person.face.list.command
diff --git a/src/vz/.x/help/include.the.person.face.update.command b/src/clis/vz/.x/help/include.the.person.face.update.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.face.update.command
rename to src/clis/vz/.x/help/include.the.person.face.update.command
diff --git a/src/vz/.x/help/include.the.person.group.create.command b/src/clis/vz/.x/help/include.the.person.group.create.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.group.create.command
rename to src/clis/vz/.x/help/include.the.person.group.create.command
diff --git a/src/vz/.x/help/include.the.person.group.delete.command b/src/clis/vz/.x/help/include.the.person.group.delete.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.group.delete.command
rename to src/clis/vz/.x/help/include.the.person.group.delete.command
diff --git a/src/vz/.x/help/include.the.person.group.list.command b/src/clis/vz/.x/help/include.the.person.group.list.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.group.list.command
rename to src/clis/vz/.x/help/include.the.person.group.list.command
diff --git a/src/vz/.x/help/include.the.person.group.status.command b/src/clis/vz/.x/help/include.the.person.group.status.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.group.status.command
rename to src/clis/vz/.x/help/include.the.person.group.status.command
diff --git a/src/vz/.x/help/include.the.person.group.train.command b/src/clis/vz/.x/help/include.the.person.group.train.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.group.train.command
rename to src/clis/vz/.x/help/include.the.person.group.train.command
diff --git a/src/vz/.x/help/include.the.person.group.update.command b/src/clis/vz/.x/help/include.the.person.group.update.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.group.update.command
rename to src/clis/vz/.x/help/include.the.person.group.update.command
diff --git a/src/vz/.x/help/include.the.person.list.command b/src/clis/vz/.x/help/include.the.person.list.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.list.command
rename to src/clis/vz/.x/help/include.the.person.list.command
diff --git a/src/vz/.x/help/include.the.person.update.command b/src/clis/vz/.x/help/include.the.person.update.command
similarity index 100%
rename from src/vz/.x/help/include.the.person.update.command
rename to src/clis/vz/.x/help/include.the.person.update.command
diff --git a/src/vz/.x/help/include.the.region.option b/src/clis/vz/.x/help/include.the.region.option
similarity index 100%
rename from src/vz/.x/help/include.the.region.option
rename to src/clis/vz/.x/help/include.the.region.option
diff --git a/src/vz/.x/help/include.the.region.option.where b/src/clis/vz/.x/help/include.the.region.option.where
similarity index 100%
rename from src/vz/.x/help/include.the.region.option.where
rename to src/clis/vz/.x/help/include.the.region.option.where
diff --git a/src/vz/.x/help/include.the.run.command b/src/clis/vz/.x/help/include.the.run.command
similarity index 100%
rename from src/vz/.x/help/include.the.run.command
rename to src/clis/vz/.x/help/include.the.run.command
diff --git a/src/vz/.x/help/include.the.save.option b/src/clis/vz/.x/help/include.the.save.option
similarity index 100%
rename from src/vz/.x/help/include.the.save.option
rename to src/clis/vz/.x/help/include.the.save.option
diff --git a/src/vz/.x/help/include.the.token.option b/src/clis/vz/.x/help/include.the.token.option
similarity index 100%
rename from src/vz/.x/help/include.the.token.option
rename to src/clis/vz/.x/help/include.the.token.option
diff --git a/src/vz/.x/help/include.the.token.option.where b/src/clis/vz/.x/help/include.the.token.option.where
similarity index 100%
rename from src/vz/.x/help/include.the.token.option.where
rename to src/clis/vz/.x/help/include.the.token.option.where
diff --git a/src/vz/.x/help/include.the.zip.option b/src/clis/vz/.x/help/include.the.zip.option
similarity index 100%
rename from src/vz/.x/help/include.the.zip.option
rename to src/clis/vz/.x/help/include.the.zip.option
diff --git a/src/vz/.x/help/include.vz.ascii.logo b/src/clis/vz/.x/help/include.vz.ascii.logo
similarity index 100%
rename from src/vz/.x/help/include.vz.ascii.logo
rename to src/clis/vz/.x/help/include.vz.ascii.logo
diff --git a/src/vz/.x/help/include.vz.init.ascii.logo b/src/clis/vz/.x/help/include.vz.init.ascii.logo
similarity index 100%
rename from src/vz/.x/help/include.vz.init.ascii.logo
rename to src/clis/vz/.x/help/include.vz.init.ascii.logo
diff --git a/src/vz/.x/help/init b/src/clis/vz/.x/help/init
similarity index 100%
rename from src/vz/.x/help/init
rename to src/clis/vz/.x/help/init
diff --git a/src/vz/.x/help/init.advanced b/src/clis/vz/.x/help/init.advanced
similarity index 100%
rename from src/vz/.x/help/init.advanced
rename to src/clis/vz/.x/help/init.advanced
diff --git a/src/vz/.x/help/init.examples b/src/clis/vz/.x/help/init.examples
similarity index 100%
rename from src/vz/.x/help/init.examples
rename to src/clis/vz/.x/help/init.examples
diff --git a/src/vz/.x/help/key b/src/clis/vz/.x/help/key
similarity index 100%
rename from src/vz/.x/help/key
rename to src/clis/vz/.x/help/key
diff --git a/src/vz/.x/help/key.overview b/src/clis/vz/.x/help/key.overview
similarity index 100%
rename from src/vz/.x/help/key.overview
rename to src/clis/vz/.x/help/key.overview
diff --git a/src/vz/.x/help/person b/src/clis/vz/.x/help/person
similarity index 100%
rename from src/vz/.x/help/person
rename to src/clis/vz/.x/help/person
diff --git a/src/vz/.x/help/person.create b/src/clis/vz/.x/help/person.create
similarity index 100%
rename from src/vz/.x/help/person.create
rename to src/clis/vz/.x/help/person.create
diff --git a/src/vz/.x/help/person.create.advanced b/src/clis/vz/.x/help/person.create.advanced
similarity index 100%
rename from src/vz/.x/help/person.create.advanced
rename to src/clis/vz/.x/help/person.create.advanced
diff --git a/src/vz/.x/help/person.delete b/src/clis/vz/.x/help/person.delete
similarity index 100%
rename from src/vz/.x/help/person.delete
rename to src/clis/vz/.x/help/person.delete
diff --git a/src/vz/.x/help/person.delete.advanced b/src/clis/vz/.x/help/person.delete.advanced
similarity index 100%
rename from src/vz/.x/help/person.delete.advanced
rename to src/clis/vz/.x/help/person.delete.advanced
diff --git a/src/vz/.x/help/person.examples b/src/clis/vz/.x/help/person.examples
similarity index 100%
rename from src/vz/.x/help/person.examples
rename to src/clis/vz/.x/help/person.examples
diff --git a/src/vz/.x/help/person.face b/src/clis/vz/.x/help/person.face
similarity index 100%
rename from src/vz/.x/help/person.face
rename to src/clis/vz/.x/help/person.face
diff --git a/src/vz/.x/help/person.face.add b/src/clis/vz/.x/help/person.face.add
similarity index 100%
rename from src/vz/.x/help/person.face.add
rename to src/clis/vz/.x/help/person.face.add
diff --git a/src/vz/.x/help/person.face.add.advanced b/src/clis/vz/.x/help/person.face.add.advanced
similarity index 100%
rename from src/vz/.x/help/person.face.add.advanced
rename to src/clis/vz/.x/help/person.face.add.advanced
diff --git a/src/vz/.x/help/person.face.delete b/src/clis/vz/.x/help/person.face.delete
similarity index 100%
rename from src/vz/.x/help/person.face.delete
rename to src/clis/vz/.x/help/person.face.delete
diff --git a/src/vz/.x/help/person.face.delete.advanced b/src/clis/vz/.x/help/person.face.delete.advanced
similarity index 100%
rename from src/vz/.x/help/person.face.delete.advanced
rename to src/clis/vz/.x/help/person.face.delete.advanced
diff --git a/src/vz/.x/help/person.face.list b/src/clis/vz/.x/help/person.face.list
similarity index 100%
rename from src/vz/.x/help/person.face.list
rename to src/clis/vz/.x/help/person.face.list
diff --git a/src/vz/.x/help/person.face.list.advanced b/src/clis/vz/.x/help/person.face.list.advanced
similarity index 100%
rename from src/vz/.x/help/person.face.list.advanced
rename to src/clis/vz/.x/help/person.face.list.advanced
diff --git a/src/vz/.x/help/person.face.update b/src/clis/vz/.x/help/person.face.update
similarity index 100%
rename from src/vz/.x/help/person.face.update
rename to src/clis/vz/.x/help/person.face.update
diff --git a/src/vz/.x/help/person.face.update.advanced b/src/clis/vz/.x/help/person.face.update.advanced
similarity index 100%
rename from src/vz/.x/help/person.face.update.advanced
rename to src/clis/vz/.x/help/person.face.update.advanced
diff --git a/src/vz/.x/help/person.group b/src/clis/vz/.x/help/person.group
similarity index 100%
rename from src/vz/.x/help/person.group
rename to src/clis/vz/.x/help/person.group
diff --git a/src/vz/.x/help/person.group.create b/src/clis/vz/.x/help/person.group.create
similarity index 100%
rename from src/vz/.x/help/person.group.create
rename to src/clis/vz/.x/help/person.group.create
diff --git a/src/vz/.x/help/person.group.create.advanced b/src/clis/vz/.x/help/person.group.create.advanced
similarity index 100%
rename from src/vz/.x/help/person.group.create.advanced
rename to src/clis/vz/.x/help/person.group.create.advanced
diff --git a/src/vz/.x/help/person.group.delete b/src/clis/vz/.x/help/person.group.delete
similarity index 100%
rename from src/vz/.x/help/person.group.delete
rename to src/clis/vz/.x/help/person.group.delete
diff --git a/src/vz/.x/help/person.group.delete.advanced b/src/clis/vz/.x/help/person.group.delete.advanced
similarity index 100%
rename from src/vz/.x/help/person.group.delete.advanced
rename to src/clis/vz/.x/help/person.group.delete.advanced
diff --git a/src/vz/.x/help/person.group.list b/src/clis/vz/.x/help/person.group.list
similarity index 100%
rename from src/vz/.x/help/person.group.list
rename to src/clis/vz/.x/help/person.group.list
diff --git a/src/vz/.x/help/person.group.list.advanced b/src/clis/vz/.x/help/person.group.list.advanced
similarity index 100%
rename from src/vz/.x/help/person.group.list.advanced
rename to src/clis/vz/.x/help/person.group.list.advanced
diff --git a/src/vz/.x/help/person.group.status b/src/clis/vz/.x/help/person.group.status
similarity index 100%
rename from src/vz/.x/help/person.group.status
rename to src/clis/vz/.x/help/person.group.status
diff --git a/src/vz/.x/help/person.group.status.advanced b/src/clis/vz/.x/help/person.group.status.advanced
similarity index 100%
rename from src/vz/.x/help/person.group.status.advanced
rename to src/clis/vz/.x/help/person.group.status.advanced
diff --git a/src/vz/.x/help/person.group.train b/src/clis/vz/.x/help/person.group.train
similarity index 100%
rename from src/vz/.x/help/person.group.train
rename to src/clis/vz/.x/help/person.group.train
diff --git a/src/vz/.x/help/person.group.train.advanced b/src/clis/vz/.x/help/person.group.train.advanced
similarity index 100%
rename from src/vz/.x/help/person.group.train.advanced
rename to src/clis/vz/.x/help/person.group.train.advanced
diff --git a/src/vz/.x/help/person.group.update b/src/clis/vz/.x/help/person.group.update
similarity index 100%
rename from src/vz/.x/help/person.group.update
rename to src/clis/vz/.x/help/person.group.update
diff --git a/src/vz/.x/help/person.group.update.advanced b/src/clis/vz/.x/help/person.group.update.advanced
similarity index 100%
rename from src/vz/.x/help/person.group.update.advanced
rename to src/clis/vz/.x/help/person.group.update.advanced
diff --git a/src/vz/.x/help/person.list b/src/clis/vz/.x/help/person.list
similarity index 100%
rename from src/vz/.x/help/person.list
rename to src/clis/vz/.x/help/person.list
diff --git a/src/vz/.x/help/person.list.advanced b/src/clis/vz/.x/help/person.list.advanced
similarity index 100%
rename from src/vz/.x/help/person.list.advanced
rename to src/clis/vz/.x/help/person.list.advanced
diff --git a/src/vz/.x/help/person.update b/src/clis/vz/.x/help/person.update
similarity index 100%
rename from src/vz/.x/help/person.update
rename to src/clis/vz/.x/help/person.update
diff --git a/src/vz/.x/help/person.update.advanced b/src/clis/vz/.x/help/person.update.advanced
similarity index 100%
rename from src/vz/.x/help/person.update.advanced
rename to src/clis/vz/.x/help/person.update.advanced
diff --git a/src/vz/.x/help/region b/src/clis/vz/.x/help/region
similarity index 100%
rename from src/vz/.x/help/region
rename to src/clis/vz/.x/help/region
diff --git a/src/vz/.x/help/region.overview b/src/clis/vz/.x/help/region.overview
similarity index 100%
rename from src/vz/.x/help/region.overview
rename to src/clis/vz/.x/help/region.overview
diff --git a/src/vz/.x/help/run b/src/clis/vz/.x/help/run
similarity index 100%
rename from src/vz/.x/help/run
rename to src/clis/vz/.x/help/run
diff --git a/src/vz/.x/help/run.advanced b/src/clis/vz/.x/help/run.advanced
similarity index 100%
rename from src/vz/.x/help/run.advanced
rename to src/clis/vz/.x/help/run.advanced
diff --git a/src/vz/.x/help/run.examples b/src/clis/vz/.x/help/run.examples
similarity index 100%
rename from src/vz/.x/help/run.examples
rename to src/clis/vz/.x/help/run.examples
diff --git a/src/vz/.x/help/setup b/src/clis/vz/.x/help/setup
similarity index 100%
rename from src/vz/.x/help/setup
rename to src/clis/vz/.x/help/setup
diff --git a/src/vz/.x/help/usage b/src/clis/vz/.x/help/usage
similarity index 100%
rename from src/vz/.x/help/usage
rename to src/clis/vz/.x/help/usage
diff --git a/src/vz/BuildCommon.targets b/src/clis/vz/BuildCommon.targets
similarity index 100%
rename from src/vz/BuildCommon.targets
rename to src/clis/vz/BuildCommon.targets
diff --git a/src/vz/LICENSE.txt b/src/clis/vz/LICENSE.txt
similarity index 100%
rename from src/vz/LICENSE.txt
rename to src/clis/vz/LICENSE.txt
diff --git a/src/vz/NuGet.Config b/src/clis/vz/NuGet.Config
similarity index 100%
rename from src/vz/NuGet.Config
rename to src/clis/vz/NuGet.Config
diff --git a/src/vz/Program_VZ.cs b/src/clis/vz/Program_VZ.cs
similarity index 100%
rename from src/vz/Program_VZ.cs
rename to src/clis/vz/Program_VZ.cs
diff --git a/src/vz/Properties/PublishProfiles/folder publish vz-cli debug (linux-x64).pubxml b/src/clis/vz/Properties/PublishProfiles/folder publish vz-cli debug (linux-x64).pubxml
similarity index 100%
rename from src/vz/Properties/PublishProfiles/folder publish vz-cli debug (linux-x64).pubxml
rename to src/clis/vz/Properties/PublishProfiles/folder publish vz-cli debug (linux-x64).pubxml
diff --git a/src/vz/Properties/PublishProfiles/folder publish vz-cli debug (osx-64).pubxml b/src/clis/vz/Properties/PublishProfiles/folder publish vz-cli debug (osx-64).pubxml
similarity index 100%
rename from src/vz/Properties/PublishProfiles/folder publish vz-cli debug (osx-64).pubxml
rename to src/clis/vz/Properties/PublishProfiles/folder publish vz-cli debug (osx-64).pubxml
diff --git a/src/vz/Properties/PublishProfiles/folder publish vz-cli debug (win-x64).pubxml b/src/clis/vz/Properties/PublishProfiles/folder publish vz-cli debug (win-x64).pubxml
similarity index 100%
rename from src/vz/Properties/PublishProfiles/folder publish vz-cli debug (win-x64).pubxml
rename to src/clis/vz/Properties/PublishProfiles/folder publish vz-cli debug (win-x64).pubxml
diff --git a/src/vz/Properties/PublishProfiles/folder publish vz-cli release (linux-x64).pubxml b/src/clis/vz/Properties/PublishProfiles/folder publish vz-cli release (linux-x64).pubxml
similarity index 100%
rename from src/vz/Properties/PublishProfiles/folder publish vz-cli release (linux-x64).pubxml
rename to src/clis/vz/Properties/PublishProfiles/folder publish vz-cli release (linux-x64).pubxml
diff --git a/src/vz/Properties/PublishProfiles/folder publish vz-cli release (osx-64).pubxml b/src/clis/vz/Properties/PublishProfiles/folder publish vz-cli release (osx-64).pubxml
similarity index 100%
rename from src/vz/Properties/PublishProfiles/folder publish vz-cli release (osx-64).pubxml
rename to src/clis/vz/Properties/PublishProfiles/folder publish vz-cli release (osx-64).pubxml
diff --git a/src/vz/Properties/PublishProfiles/folder publish vz-cli release (win-x64).pubxml b/src/clis/vz/Properties/PublishProfiles/folder publish vz-cli release (win-x64).pubxml
similarity index 100%
rename from src/vz/Properties/PublishProfiles/folder publish vz-cli release (win-x64).pubxml
rename to src/clis/vz/Properties/PublishProfiles/folder publish vz-cli release (win-x64).pubxml
diff --git a/src/vz/Properties/launchSettings.json b/src/clis/vz/Properties/launchSettings.json
similarity index 100%
rename from src/vz/Properties/launchSettings.json
rename to src/clis/vz/Properties/launchSettings.json
diff --git a/src/vz/THIRD_PARTY_NOTICE.txt b/src/clis/vz/THIRD_PARTY_NOTICE.txt
similarity index 100%
rename from src/vz/THIRD_PARTY_NOTICE.txt
rename to src/clis/vz/THIRD_PARTY_NOTICE.txt
diff --git a/src/vz/app.manifest b/src/clis/vz/app.manifest
similarity index 100%
rename from src/vz/app.manifest
rename to src/clis/vz/app.manifest
diff --git a/src/vz/ci/build-all.cmd b/src/clis/vz/ci/build-all.cmd
similarity index 100%
rename from src/vz/ci/build-all.cmd
rename to src/clis/vz/ci/build-all.cmd
diff --git a/src/vz/ci/build-all.sh b/src/clis/vz/ci/build-all.sh
similarity index 100%
rename from src/vz/ci/build-all.sh
rename to src/clis/vz/ci/build-all.sh
diff --git a/src/vz/ci/build-linux-x64.cmd b/src/clis/vz/ci/build-linux-x64.cmd
similarity index 100%
rename from src/vz/ci/build-linux-x64.cmd
rename to src/clis/vz/ci/build-linux-x64.cmd
diff --git a/src/vz/ci/build-win-x64.cmd b/src/clis/vz/ci/build-win-x64.cmd
similarity index 100%
rename from src/vz/ci/build-win-x64.cmd
rename to src/clis/vz/ci/build-win-x64.cmd
diff --git a/src/vz/ci/publish-linux-x64.cmd b/src/clis/vz/ci/publish-linux-x64.cmd
similarity index 100%
rename from src/vz/ci/publish-linux-x64.cmd
rename to src/clis/vz/ci/publish-linux-x64.cmd
diff --git a/src/vz/ci/publish-win-x64.cmd b/src/clis/vz/ci/publish-win-x64.cmd
similarity index 100%
rename from src/vz/ci/publish-win-x64.cmd
rename to src/clis/vz/ci/publish-win-x64.cmd
diff --git a/src/vz/ci/vz-build-pipeline.yml b/src/clis/vz/ci/vz-build-pipeline.yml
similarity index 100%
rename from src/vz/ci/vz-build-pipeline.yml
rename to src/clis/vz/ci/vz-build-pipeline.yml
diff --git a/src/vz/ci/vz-test-pipeline.yml b/src/clis/vz/ci/vz-test-pipeline.yml
similarity index 100%
rename from src/vz/ci/vz-test-pipeline.yml
rename to src/clis/vz/ci/vz-test-pipeline.yml
diff --git a/src/vz/commands/face_command.cs b/src/clis/vz/commands/face_command.cs
similarity index 100%
rename from src/vz/commands/face_command.cs
rename to src/clis/vz/commands/face_command.cs
diff --git a/src/vz/commands/image_command.cs b/src/clis/vz/commands/image_command.cs
similarity index 100%
rename from src/vz/commands/image_command.cs
rename to src/clis/vz/commands/image_command.cs
diff --git a/src/vz/commands/parsers/common_vision_token_parsers.cs b/src/clis/vz/commands/parsers/common_vision_token_parsers.cs
similarity index 100%
rename from src/vz/commands/parsers/common_vision_token_parsers.cs
rename to src/clis/vz/commands/parsers/common_vision_token_parsers.cs
diff --git a/src/vz/commands/parsers/face_command_parser.cs b/src/clis/vz/commands/parsers/face_command_parser.cs
similarity index 100%
rename from src/vz/commands/parsers/face_command_parser.cs
rename to src/clis/vz/commands/parsers/face_command_parser.cs
diff --git a/src/vz/commands/parsers/image_command_parser.cs b/src/clis/vz/commands/parsers/image_command_parser.cs
similarity index 100%
rename from src/vz/commands/parsers/image_command_parser.cs
rename to src/clis/vz/commands/parsers/image_command_parser.cs
diff --git a/src/vz/commands/parsers/person_command_parser.cs b/src/clis/vz/commands/parsers/person_command_parser.cs
similarity index 100%
rename from src/vz/commands/parsers/person_command_parser.cs
rename to src/clis/vz/commands/parsers/person_command_parser.cs
diff --git a/src/vz/commands/person_command.cs b/src/clis/vz/commands/person_command.cs
similarity index 100%
rename from src/vz/commands/person_command.cs
rename to src/clis/vz/commands/person_command.cs
diff --git a/src/vz/face.cli.person.ideas.md b/src/clis/vz/face.cli.person.ideas.md
similarity index 100%
rename from src/vz/face.cli.person.ideas.md
rename to src/clis/vz/face.cli.person.ideas.md
diff --git a/src/vz/helpers/display_helpers.cs b/src/clis/vz/helpers/display_helpers.cs
similarity index 100%
rename from src/vz/helpers/display_helpers.cs
rename to src/clis/vz/helpers/display_helpers.cs
diff --git a/src/vz/helpers/event_logger_helpers.cs b/src/clis/vz/helpers/event_logger_helpers.cs
similarity index 100%
rename from src/vz/helpers/event_logger_helpers.cs
rename to src/clis/vz/helpers/event_logger_helpers.cs
diff --git a/src/vz/packages.lock.json b/src/clis/vz/packages.lock.json
similarity index 100%
rename from src/vz/packages.lock.json
rename to src/clis/vz/packages.lock.json
diff --git a/src/vz/tests/check-all.cmd b/src/clis/vz/tests/check-all.cmd
similarity index 100%
rename from src/vz/tests/check-all.cmd
rename to src/clis/vz/tests/check-all.cmd
diff --git a/src/vz/tests/check-cli.cmd b/src/clis/vz/tests/check-cli.cmd
similarity index 100%
rename from src/vz/tests/check-cli.cmd
rename to src/clis/vz/tests/check-cli.cmd
diff --git a/src/vz/tests/cli/base/help.documentation.test b/src/clis/vz/tests/cli/base/help.documentation.test
similarity index 100%
rename from src/vz/tests/cli/base/help.documentation.test
rename to src/clis/vz/tests/cli/base/help.documentation.test
diff --git a/src/vz/tests/cli/base/help.documentation.test.run.output.expected b/src/clis/vz/tests/cli/base/help.documentation.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/base/help.documentation.test.run.output.expected
rename to src/clis/vz/tests/cli/base/help.documentation.test.run.output.expected
diff --git a/src/vz/tests/cli/base/help.examples.test b/src/clis/vz/tests/cli/base/help.examples.test
similarity index 100%
rename from src/vz/tests/cli/base/help.examples.test
rename to src/clis/vz/tests/cli/base/help.examples.test
diff --git a/src/vz/tests/cli/base/help.examples.test.run.output.expected b/src/clis/vz/tests/cli/base/help.examples.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/base/help.examples.test.run.output.expected
rename to src/clis/vz/tests/cli/base/help.examples.test.run.output.expected
diff --git a/src/vz/tests/cli/base/help.setup.test b/src/clis/vz/tests/cli/base/help.setup.test
similarity index 100%
rename from src/vz/tests/cli/base/help.setup.test
rename to src/clis/vz/tests/cli/base/help.setup.test
diff --git a/src/vz/tests/cli/base/help.setup.test.run.output.expected b/src/clis/vz/tests/cli/base/help.setup.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/base/help.setup.test.run.output.expected
rename to src/clis/vz/tests/cli/base/help.setup.test.run.output.expected
diff --git a/src/vz/tests/cli/base/help.test b/src/clis/vz/tests/cli/base/help.test
similarity index 100%
rename from src/vz/tests/cli/base/help.test
rename to src/clis/vz/tests/cli/base/help.test
diff --git a/src/vz/tests/cli/base/help.test.run.output.expected b/src/clis/vz/tests/cli/base/help.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/base/help.test.run.output.expected
rename to src/clis/vz/tests/cli/base/help.test.run.output.expected
diff --git a/src/vz/tests/cli/base/usage.test b/src/clis/vz/tests/cli/base/usage.test
similarity index 100%
rename from src/vz/tests/cli/base/usage.test
rename to src/clis/vz/tests/cli/base/usage.test
diff --git a/src/vz/tests/cli/base/usage.test.run.output.expected b/src/clis/vz/tests/cli/base/usage.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/base/usage.test.run.output.expected
rename to src/clis/vz/tests/cli/base/usage.test.run.output.expected
diff --git a/src/vz/tests/cli/commands/config/config.local.name227266.1.set.test b/src/clis/vz/tests/cli/commands/config/config.local.name227266.1.set.test
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.local.name227266.1.set.test
rename to src/clis/vz/tests/cli/commands/config/config.local.name227266.1.set.test
diff --git a/src/vz/tests/cli/commands/config/config.local.name227266.1.set.test.run.output.expected b/src/clis/vz/tests/cli/commands/config/config.local.name227266.1.set.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.local.name227266.1.set.test.run.output.expected
rename to src/clis/vz/tests/cli/commands/config/config.local.name227266.1.set.test.run.output.expected
diff --git a/src/vz/tests/cli/commands/config/config.local.name227266.2.get.test b/src/clis/vz/tests/cli/commands/config/config.local.name227266.2.get.test
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.local.name227266.2.get.test
rename to src/clis/vz/tests/cli/commands/config/config.local.name227266.2.get.test
diff --git a/src/vz/tests/cli/commands/config/config.local.name227266.2.get.test.run.output.expected b/src/clis/vz/tests/cli/commands/config/config.local.name227266.2.get.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.local.name227266.2.get.test.run.output.expected
rename to src/clis/vz/tests/cli/commands/config/config.local.name227266.2.get.test.run.output.expected
diff --git a/src/vz/tests/cli/commands/config/config.local.name227266.3.find.test b/src/clis/vz/tests/cli/commands/config/config.local.name227266.3.find.test
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.local.name227266.3.find.test
rename to src/clis/vz/tests/cli/commands/config/config.local.name227266.3.find.test
diff --git a/src/vz/tests/cli/commands/config/config.local.name227266.3.find.test.run.output.expected b/src/clis/vz/tests/cli/commands/config/config.local.name227266.3.find.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.local.name227266.3.find.test.run.output.expected
rename to src/clis/vz/tests/cli/commands/config/config.local.name227266.3.find.test.run.output.expected
diff --git a/src/vz/tests/cli/commands/config/config.local.name227266.4.add.test b/src/clis/vz/tests/cli/commands/config/config.local.name227266.4.add.test
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.local.name227266.4.add.test
rename to src/clis/vz/tests/cli/commands/config/config.local.name227266.4.add.test
diff --git a/src/vz/tests/cli/commands/config/config.local.name227266.4.add.test.run.output.expected b/src/clis/vz/tests/cli/commands/config/config.local.name227266.4.add.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.local.name227266.4.add.test.run.output.expected
rename to src/clis/vz/tests/cli/commands/config/config.local.name227266.4.add.test.run.output.expected
diff --git a/src/vz/tests/cli/commands/config/config.local.name227266.5.clear.test b/src/clis/vz/tests/cli/commands/config/config.local.name227266.5.clear.test
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.local.name227266.5.clear.test
rename to src/clis/vz/tests/cli/commands/config/config.local.name227266.5.clear.test
diff --git a/src/vz/tests/cli/commands/config/config.local.name227266.5.clear.test.run.output.expected b/src/clis/vz/tests/cli/commands/config/config.local.name227266.5.clear.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.local.name227266.5.clear.test.run.output.expected
rename to src/clis/vz/tests/cli/commands/config/config.local.name227266.5.clear.test.run.output.expected
diff --git a/src/vz/tests/cli/commands/config/config.usage.test b/src/clis/vz/tests/cli/commands/config/config.usage.test
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.usage.test
rename to src/clis/vz/tests/cli/commands/config/config.usage.test
diff --git a/src/vz/tests/cli/commands/config/config.usage.test.run.output.expected b/src/clis/vz/tests/cli/commands/config/config.usage.test.run.output.expected
similarity index 100%
rename from src/vz/tests/cli/commands/config/config.usage.test.run.output.expected
rename to src/clis/vz/tests/cli/commands/config/config.usage.test.run.output.expected
diff --git a/src/vz/vz-cli.csproj b/src/clis/vz/vz-cli.csproj
similarity index 100%
rename from src/vz/vz-cli.csproj
rename to src/clis/vz/vz-cli.csproj
diff --git a/src/common/Program.cs b/src/common/Program.cs
index 1e3c4484..8211fd60 100644
--- a/src/common/Program.cs
+++ b/src/common/Program.cs
@@ -5,8 +5,10 @@
using System.Reflection;
using System.Text;
+using System.Text.Json;
using Azure.AI.Details.Common.CLI.Telemetry;
using Azure.AI.Details.Common.CLI.Telemetry.Events;
+using System.Threading.Tasks;
namespace Azure.AI.Details.Common.CLI
{
@@ -14,6 +16,9 @@ public class Program
{
public static bool Debug { get; internal set; }
+ private static int usingResource = 0;
+ private static string updateMessage = "";
+
public static int Main(IProgramData data, string[] mainArgs)
{
_data = data;
@@ -37,9 +42,18 @@ public static int Main(IProgramData data, string[] mainArgs)
int exitCode = ParseCommand(tokens, values);
if (exitCode == 0 && !values.DisplayHelpRequested())
{
- DisplayBanner(values);
- DisplayParsedValues(values);
+ if (!values.DisplayVersionRequested() && !values.DisplayUpdateRequested())
+ {
+ var __ = Task.Run(() => DisplayVersion(values, true));
+ DisplayBanner(values);
+ DisplayParsedValues(values);
+ }
exitCode = RunCommand(values) ? 0 : 1;
+ var updateMessage = GetUpdateMessage();
+ if (!String.IsNullOrEmpty(updateMessage))
+ {
+ ConsoleHelpers.WriteLineWithHighlight(updateMessage);
+ }
}
if (values.GetOrDefault("x.pause", false))
@@ -118,6 +132,31 @@ internal static string GetDisplayBannerText()
return $"{Program.Name.ToUpper()} - {Program.DisplayName}" + version;
}
+ private static string GetUpdateMessage()
+ {
+ string message = "";
+ //0 indicates that the method is not in use.
+ if(0 == Interlocked.Exchange(ref usingResource, 1))
+ {
+ message = updateMessage;
+
+ //Release the lock
+ Interlocked.Exchange(ref usingResource, 0);
+ }
+ return message;
+ }
+
+ private static void SetUpdateMessage(string message)
+ {
+ if(0 == Interlocked.Exchange(ref usingResource, 1))
+ {
+ updateMessage = message;
+
+ //Release the lock
+ Interlocked.Exchange(ref usingResource, 0);
+ }
+ }
+
private static void DisplayBanner(ICommandValues values)
{
if (values.GetOrDefault("x.quiet", false)) return;
@@ -135,7 +174,7 @@ private static void DisplayBanner(ICommandValues values)
}
}
- private static string GetVersionFromAssembly()
+ public static string GetVersionFromAssembly()
{
var sdkAssembly = Program.BindingAssemblySdkType?.Assembly;
var sdkVersionAttribute = sdkAssembly?.GetCustomAttribute();
@@ -161,6 +200,46 @@ private static void DisplayCommandHelp(INamedValueTokens tokens, INamedValues va
HelpCommandParser.DisplayHelp(tokens, values);
}
+ public static void DisplayVersion(INamedValues values, bool setUpdateMessage = false)
+ {
+ var currentVersion = GetVersionFromAssembly().Split("-")[0];
+
+ if (!setUpdateMessage)
+ {
+ Console.WriteLine(values.DisplayUpdateRequested() ?
+ $"Current Version: {currentVersion}"
+ : currentVersion);
+ }
+
+ var domain = values.DisplayUpdateRequested() ? "update" : "version";
+ var latestVersion = HttpHelpers.GetLatestVersionInfo(values, domain);
+ if (latestVersion != null)
+ {
+ var currentVersionNumbers = currentVersion.Split(".");
+ var latestVersionNumbers = latestVersion.Split("-")[0].Split(".");
+ if (StringHelpers.UpdateNeeded(currentVersionNumbers, latestVersionNumbers))
+ {
+ var line1 = $"\n`#e_;Update available, Latest Version: {latestVersion}`\n";
+ var line2 = @"Go to `#e0;https://aka.ms/azure-ai-cli-update` for more information.";
+ if (setUpdateMessage)
+ {
+ SetUpdateMessage(line1 + line2);
+ }
+ else
+ {
+ ConsoleHelpers.WriteLineWithHighlight(line1);
+ ConsoleHelpers.WriteLineWithHighlight(line2);
+ }
+ return;
+ }
+ }
+ if (values.DisplayUpdateRequested())
+ {
+ Console.WriteLine("No update available");
+ }
+ }
+
+
private static void DisplayParsedValues(INamedValues values)
{
if (values.GetOrDefault("x.quiet", true)) return;
@@ -178,6 +257,7 @@ private static void DisplayParsedValues(INamedValues values)
{
if (key == "error") continue;
if (key == "display.help") continue;
+ if (key == "display.version") continue;
var value = values[key];
var obfuscateValue = key.EndsWith(passwordPostfix) ||
@@ -255,6 +335,11 @@ private static int DisplayParseErrorHelpOrException(INamedValueTokens tokens, IC
DisplayCommandHelp(tokens, values);
return values.GetOrDefault("display.help.exit.code", 0);
}
+ else if (values.DisplayVersionRequested() || values.DisplayUpdateRequested())
+ {
+ DisplayVersion(values);
+ return 0;
+ }
else if (ex != null)
{
if (displayBanner) DisplayBanner(values);
diff --git a/src/common/Properties/launchSettings.json b/src/common/Properties/launchSettings.json
new file mode 100644
index 00000000..2966e39e
--- /dev/null
+++ b/src/common/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "common": {
+ "commandName": "Project",
+ "commandLineArgs": "chat --interactive --question hello --output-chat-history-file history.json"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/common/common.csproj b/src/common/common.csproj
index 31e67cab..ea206fe9 100644
--- a/src/common/common.csproj
+++ b/src/common/common.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/src/common/details/ai_python_generative_sdk/python_sdk_wrapper.cs b/src/common/details/ai_python_generative_sdk/python_sdk_wrapper.cs
index d74cfdb0..54c0b0d6 100644
--- a/src/common/details/ai_python_generative_sdk/python_sdk_wrapper.cs
+++ b/src/common/details/ai_python_generative_sdk/python_sdk_wrapper.cs
@@ -71,7 +71,7 @@ static public string GetConnection(ICommandValues values, string subscription, s
return DoGetConnectionViaPython(values, subscription, group, projectName, connectionName);
}
- static public string UpdateMLIndex(ICommandValues values, string subscription, string group, string projectName, string indexName, string embeddingModelDeployment, string embeddingModelName, string dataFiles, string externalSourceUrl)
+ static public string UpdateMLIndex(ICommandValues values, string subscription, string group, string projectName, string indexName, string embeddingModelDeployment, string embeddingModelName, string dataFiles, string externalSourceUrl, Dictionary addToEnvironment)
{
Action stdErrVerbose = x => Console.Error.WriteLine(x);
Action stdErrStandard = x => {
@@ -93,7 +93,7 @@ static public string UpdateMLIndex(ICommandValues values, string subscription, s
"--embedding-model-name", embeddingModelName,
"--data-files", dataFiles,
"--external-source-url", externalSourceUrl),
- null, null, stdErr);
+ addToEnvironment, null, stdErr);
}
private static string DoCreateResourceViaPython(ICommandValues values, string subscription, string group, string name, string location, string displayName, string description, string openAiResourceId, string openAiResourceKind)
diff --git a/src/common/details/azcli/AzCli.cs b/src/common/details/azcli/AzCli.cs
index a8f8ae1e..770b3b59 100644
--- a/src/common/details/azcli/AzCli.cs
+++ b/src/common/details/azcli/AzCli.cs
@@ -74,6 +74,17 @@ public struct CognitiveServicesSpeechResourceInfo
public string Key;
}
+ public struct CognitiveServicesVisionResourceInfo
+ {
+ public string Id;
+ public string Group;
+ public string Name;
+ public string Kind;
+ public string RegionLocation;
+ public string Endpoint;
+
+ public string Key;
+ }
public struct CognitiveServicesKeyInfo
{
diff --git a/src/common/details/azcli/AzCliConsoleGui_CognitiveServicesResourceDeploymentPicker.cs b/src/common/details/azcli/AzCliConsoleGui_CognitiveServicesResourceDeploymentPicker.cs
index 077a33f2..2c0f67a2 100644
--- a/src/common/details/azcli/AzCliConsoleGui_CognitiveServicesResourceDeploymentPicker.cs
+++ b/src/common/details/azcli/AzCliConsoleGui_CognitiveServicesResourceDeploymentPicker.cs
@@ -41,14 +41,8 @@ public partial class AzCliConsoleGui
{
var allowCreateDeployment = !string.IsNullOrEmpty(allowCreateDeploymentOption);
- Console.Write($"Name: *** Loading choices ***");
- var response = await AzCli.ListCognitiveServicesDeployments(subscriptionId, groupName, resourceName, "OpenAI");
-
- Console.Write($"\rName: ");
- if (string.IsNullOrEmpty(response.Output.StdOutput) && !string.IsNullOrEmpty(response.Output.StdError))
- {
- throw new ApplicationException($"ERROR: Loading deployments:\n{response.Output.StdError}");
- }
+ var listDeploymentsFunc = async () => await AzCli.ListCognitiveServicesDeployments(subscriptionId, groupName, resourceName, "OpenAI");
+ var response = await LoginHelpers.GetResponseOnLogin(interactive, "deployment", listDeploymentsFunc);
var lookForChatCompletionCapable = deploymentExtra.ToLower() == "chat" || deploymentExtra.ToLower() == "evaluation";
var lookForEmbeddingCapable = deploymentExtra.ToLower() == "embeddings";
diff --git a/src/common/details/azcli/AzCliConsoleGui_CognitiveServicesResourcePicker.cs b/src/common/details/azcli/AzCliConsoleGui_CognitiveServicesResourcePicker.cs
index 73c10e83..b7b3179e 100644
--- a/src/common/details/azcli/AzCliConsoleGui_CognitiveServicesResourcePicker.cs
+++ b/src/common/details/azcli/AzCliConsoleGui_CognitiveServicesResourcePicker.cs
@@ -58,14 +58,8 @@ public partial class AzCliConsoleGui
{
var allowCreateResource = !string.IsNullOrEmpty(allowCreateResourceOption);
- Console.Write("\rName: *** Loading choices ***");
- var response = await AzCli.ListCognitiveServicesResources(subscriptionId, kinds);
-
- Console.Write("\rName: ");
- if (string.IsNullOrEmpty(response.Output.StdOutput) && !string.IsNullOrEmpty(response.Output.StdError))
- {
- throw new ApplicationException($"ERROR: Loading Cognitive Services resources: {response.Output.StdError}");
- }
+ var listResourcesFunc = async () => await AzCli.ListCognitiveServicesResources(subscriptionId, kinds);
+ var response = await LoginHelpers.GetResponseOnLogin(interactive, "resource", listResourcesFunc);
var resources = response.Payload
.Where(x => MatchResourceFilter(x, regionLocationFilter, groupFilter, resourceFilter))
diff --git a/src/common/details/azcli/AzCliConsoleGui_PickOrCreateAndConfig_CognitiveServicesResource_SpeechKind.cs b/src/common/details/azcli/AzCliConsoleGui_PickOrCreateAndConfig_CognitiveServicesResource_SpeechKind.cs
index 1c728e96..efd35926 100644
--- a/src/common/details/azcli/AzCliConsoleGui_PickOrCreateAndConfig_CognitiveServicesResource_SpeechKind.cs
+++ b/src/common/details/azcli/AzCliConsoleGui_PickOrCreateAndConfig_CognitiveServicesResource_SpeechKind.cs
@@ -42,5 +42,28 @@ public partial class AzCliConsoleGui
Key = keys.Key1,
};
}
+ public static async Task PickOrCreateAndConfigCognitiveServicesComputerVisionKindResource(bool interactive, string subscriptionId, string regionFilter = null, string groupFilter = null, string resourceFilter = null, string kinds = null, string sku = null, bool yes = false)
+ {
+ kinds ??= "ComputerVision";
+ var sectionHeader = "VISION RESOURCE";
+
+ var regionLocation = !string.IsNullOrEmpty(regionFilter) ? await AzCliConsoleGui.PickRegionLocationAsync(interactive, regionFilter) : new AzCli.AccountRegionLocationInfo();
+ var resource = await AzCliConsoleGui.PickOrCreateCognitiveResource(sectionHeader, interactive, subscriptionId, regionLocation.Name, groupFilter, resourceFilter, kinds, sku, yes);
+
+ var keys = await AzCliConsoleGui.LoadCognitiveServicesResourceKeys(sectionHeader, subscriptionId, resource);
+
+ ConfigSetHelpers.ConfigVisionResource(subscriptionId, resource.RegionLocation, resource.Endpoint, keys.Key1);
+
+ return new AzCli.CognitiveServicesVisionResourceInfo
+ {
+ Id = resource.Id,
+ Group = resource.Group,
+ Name = resource.Name,
+ Kind = resource.Kind,
+ RegionLocation = resource.RegionLocation,
+ Endpoint = resource.Endpoint,
+ Key = keys.Key1,
+ };
+ }
}
}
diff --git a/src/common/details/azcli/AzCliConsoleGui_ResourceGroupPicker.cs b/src/common/details/azcli/AzCliConsoleGui_ResourceGroupPicker.cs
index bb11aa33..ab40fe17 100644
--- a/src/common/details/azcli/AzCliConsoleGui_ResourceGroupPicker.cs
+++ b/src/common/details/azcli/AzCliConsoleGui_ResourceGroupPicker.cs
@@ -45,14 +45,8 @@ public partial class AzCliConsoleGui
{
var allowCreateGroup = !string.IsNullOrEmpty(allowCreateGroupOption);
- Console.Write("\rGroup: *** Loading choices ***");
- var response = await AzCli.ListResourceGroups(subscription, regionLocation);
-
- Console.Write("\rGroup: ");
- if (string.IsNullOrEmpty(response.Output.StdOutput) && !string.IsNullOrEmpty(response.Output.StdError))
- {
- throw new ApplicationException($"ERROR: Loading resource groups: {response.Output.StdError}");
- }
+ var listResourcesFunc = async () => await AzCli.ListResourceGroups(subscription, regionLocation);
+ var response = await LoginHelpers.GetResponseOnLogin(interactive, "group", listResourcesFunc, "Group");
var groups = response.Payload
.Where(x => MatchGroupFilter(x, groupFilter))
diff --git a/src/common/details/azcli/AzCliConsoleGui_SubscriptionPicker.cs b/src/common/details/azcli/AzCliConsoleGui_SubscriptionPicker.cs
index 34836abf..0d022cf7 100644
--- a/src/common/details/azcli/AzCliConsoleGui_SubscriptionPicker.cs
+++ b/src/common/details/azcli/AzCliConsoleGui_SubscriptionPicker.cs
@@ -77,48 +77,10 @@ public static async Task PickSubscriptionIdAsync(bool allowInteractiveLo
throw new ApplicationException($"*** ERROR: Loading subscriptions ***\n{response.Output.StdError}");
}
- var needLogin = response.Output.StdError != null && (response.Output.StdError.Split('\'', '"').Contains("az login") || response.Output.StdError.Contains("refresh token"));
+ var needLogin = response.Output.StdError != null && LoginHelpers.HasLoginError(response.Output.StdError);
if (needLogin)
{
- bool cancelLogin = !allowInteractiveLogin;
- bool useDeviceCode = false;
- if (allowInteractiveLogin)
- {
- ConsoleHelpers.WriteError("*** WARNING: `az login` required ***");
- Console.Write(" ");
-
- var selection = 0;
- var choices = new List() {
- "LAUNCH: `az login` (interactive device code)",
- "CANCEL: `az login ...` (non-interactive)",
- };
-
- if (!OS.IsCodeSpaces())
- {
- choices.Insert(0, "LAUNCH: `az login` (interactive browser)");
- selection = OS.IsWindows() ? 0 : 1;
- }
-
- var picked = ListBoxPicker.PickIndexOf(choices.ToArray(), selection);
-
- cancelLogin = picked < 0 || picked == choices.Count() - 1;
- useDeviceCode = picked == choices.Count() - 2;
- }
- else
- {
- throw new ApplicationException("Login required");
- }
-
- if (cancelLogin)
- {
- Console.Write($"\r{subscriptionLabel}: ");
- ConsoleHelpers.WriteLineError("*** Please run `az login` and try again ***");
- throw new OperationCanceledException("Login was canceled");
- }
-
- Console.Write($"\r{subscriptionLabel}: *** Launching `az login` (interactive) ***");
- response = await AzCli.Login(useDeviceCode);
- Console.Write($"\r{subscriptionLabel}: ");
+ response = await LoginHelpers.AttemptLogin(allowInteractiveLogin, subscriptionLabel);
}
var subscriptions = response.Payload
diff --git a/src/common/details/commands/init_command.cs b/src/common/details/commands/init_command.cs
index 1afc57a4..9621fdfe 100644
--- a/src/common/details/commands/init_command.cs
+++ b/src/common/details/commands/init_command.cs
@@ -78,6 +78,7 @@ private async Task DoCommand(string command)
case "init.openai": await DoInitRootOpenAi(interactive); break;
case "init.search": await DoInitRootSearch(interactive); break;
case "init.speech": await DoInitRootSpeech(interactive); break;
+ case "init.vision": await DoInitRootVision(interactive); break;
// POST-IGNITE: TODO: add ability to init deployments
// TODO: ensure that deployments in "openai" flow can be skipped
@@ -322,7 +323,7 @@ private async Task DoInitStandaloneResources(bool interactive)
Console.WriteLine("\rInitialize: CANCELED (no selection)");
return;
}
-
+
var part = choiceToPart[choices[picked]];
var display = partToLabelDisplay[part];
Console.WriteLine($"\rInitialize: {display}");
@@ -576,6 +577,11 @@ private async Task DoInitRootSpeech(bool interactive)
await DoInitSubscriptionId(interactive);
await DoInitSpeech(interactive);
}
+ private async Task DoInitRootVision(bool interactive)
+ {
+ await DoInitSubscriptionId(interactive);
+ await DoInitVision(interactive);
+ }
private async Task DoInitSpeech(bool interactive)
{
@@ -592,6 +598,21 @@ private async Task DoInitSpeech(bool interactive)
SubscriptionToken.Data().Set(_values, subscriptionId);
}
+ private async Task DoInitVision(bool interactive)
+ {
+ var subscriptionId = SubscriptionToken.Data().GetOrDefault(_values, "");
+ var regionFilter = _values.GetOrDefault("init.service.resource.region.name", "");
+ var groupFilter = _values.GetOrDefault("init.service.resource.group.name", "");
+ var resourceFilter = _values.GetOrDefault("init.service.cognitiveservices.resource.name", "");
+ var kind = _values.GetOrDefault("init.service.cognitiveservices.resource.kind", "ComputerVision");
+ var sku = _values.GetOrDefault("init.service.cognitiveservices.resource.sku", "S0");
+ var yes = _values.GetOrDefault("init.service.cognitiveservices.terms.agree", false);
+
+ var resource = await AzCliConsoleGui.PickOrCreateAndConfigCognitiveServicesComputerVisionKindResource(interactive, subscriptionId, regionFilter, groupFilter, resourceFilter, kind, sku, yes);
+
+ SubscriptionToken.Data().Set(_values, subscriptionId);
+ }
+
private void DisplayInitServiceBanner()
{
if (_quiet) return;
diff --git a/src/common/details/commands/parsers/command_parser.cs b/src/common/details/commands/parsers/command_parser.cs
index 4ad80d6b..90f9d09b 100644
--- a/src/common/details/commands/parsers/command_parser.cs
+++ b/src/common/details/commands/parsers/command_parser.cs
@@ -45,6 +45,13 @@ public static bool DispatchParseCommand(INamedValueTokens tokens, ICommandValues
values.AddDisplayHelpRequest();
break;
+ case "-v":
+ case "--v":
+ case "--version":
+ values.AddDisplayVersionRequest();
+ break;
+
+
default:
values.AddError("ERROR:", $"Unknown command: {command}");
values.AddDisplayHelpRequest();
@@ -177,7 +184,7 @@ protected static bool ParseCommandDotName(string commandName, INamedValueTokens
protected static bool ParseCommandDefaults(string commandName, IEnumerable parsers, INamedValueTokens tokens, ICommandValues values)
{
var noDefaults = values.GetOrDefault("x.command.nodefaults", false);
- if (commandName != "config" && commandName != "help" && !noDefaults)
+ if (ExpectsDefaults(commandName) && !noDefaults)
{
var token = tokens.PeekNextToken();
if (token == "--nodefaults")
@@ -252,6 +259,11 @@ private static bool ParseNextCommandValue(IEnumerable pa
return parsed;
}
+ private static bool ExpectsDefaults(string commandName)
+ {
+ return commandName != "config" && commandName != "help" && commandName != "update" && commandName != "version";
+ }
+
private static bool ParseAtFileToken(IEnumerable parsers, INamedValueTokens tokens, ICommandValues values)
{
if (tokens.PeekNextToken().StartsWith("@"))
diff --git a/src/common/details/commands/parsers/common_token_parsers.cs b/src/common/details/commands/parsers/common_token_parsers.cs
index 98ad8345..d510ca45 100644
--- a/src/common/details/commands/parsers/common_token_parsers.cs
+++ b/src/common/details/commands/parsers/common_token_parsers.cs
@@ -10,10 +10,40 @@
namespace Azure.AI.Details.Common.CLI
{
- public class OutputFileNamedValueTokenParser : NamedValueTokenParser
+ public enum FileDirection
+ {
+ input,
+ output
+ }
+
+ public class FileNamedValueTokenParser : NamedValueTokenParser
+ {
+ public FileNamedValueTokenParser(string fullName, FileDirection direction, string requiredParts, string defaultValue = "-") :
+ base(null, $"{direction}.{fullName}.file", $"1{requiredParts}0", "1;0", "@@", null, defaultValue)
+ {
+ }
+ }
+
+ public class InputFileNamedValueTokenParser : FileNamedValueTokenParser
+ {
+ public InputFileNamedValueTokenParser(string fullName, string requiredParts, string defaultValue = "-") :
+ base(fullName, FileDirection.input, requiredParts, defaultValue)
+ {
+ }
+ }
+
+ public class InputFileOptionalPrefixNamedValueTokenParser : InputFileNamedValueTokenParser
+ {
+ public InputFileOptionalPrefixNamedValueTokenParser(string optionalPrefix, string fullName, string fullNameRequiredParts, string defaultValue = "-") :
+ base($"{optionalPrefix}.{fullName}", $"{NotRequired(optionalPrefix)}{fullNameRequiredParts}", defaultValue)
+ {
+ }
+ }
+
+ public class OutputFileNamedValueTokenParser : FileNamedValueTokenParser
{
public OutputFileNamedValueTokenParser(string fullName, string requiredParts, string defaultValue = "-") :
- base(null, $"output.{fullName}.file", $"1{requiredParts}0", "1;0", "@@", null, defaultValue)
+ base(fullName, FileDirection.output, requiredParts, defaultValue)
{
}
}
@@ -64,6 +94,7 @@ public class CommonNamedValueTokenParsers : NamedValueTokenParserList
public CommonNamedValueTokenParsers(bool includeKeyAndRegion = true) : base(
new NamedValueTokenParser("--help", "--?", "1", "0", null, null, "true", "display.help"),
+ new NamedValueTokenParser("--version", "--v", "1", "0", null, null, "true", "display.version"),
new NamedValueTokenParser("--cls", "x.cls", "01", "1;0", "true;false", null, "true"),
new NamedValueTokenParser("--pause", "x.pause", "01", "1;0", "true;false", null, "true"),
new NamedValueTokenParser("--quiet", "x.quiet", "01", "1;0", "true;false", null, "true"),
diff --git a/src/common/details/commands/parsers/init_command_parser.cs b/src/common/details/commands/parsers/init_command_parser.cs
index 5c5ba615..189fe1ba 100644
--- a/src/common/details/commands/parsers/init_command_parser.cs
+++ b/src/common/details/commands/parsers/init_command_parser.cs
@@ -26,6 +26,7 @@ private static readonly (string name, bool valuesRequired)[] _commands = {
("init.openai", false),
("init.search", false),
("init.speech", false),
+ ("init.vision", false),
("init.project.new", false),
("init.project.select", false),
("init.project", false),
@@ -47,6 +48,7 @@ private static INamedValueTokenParser[] GetCommandParsers(ICommandValues values)
case "init.openai":
case "init.search":
case "init.speech":
+ case "init.vision":
case "init.project":
case "init.project.new":
case "init.project.select":
@@ -87,8 +89,8 @@ public CommonInitNamedValueTokenParsers() : base(
new NamedValueTokenParser("--interactive", "init.service.interactive", "001", "1;0", "true;false", null, "true")
- // new NamedValueTokenParser(null, "init.output.azcli.command.file", "01100", "1", "@@"),
- // new NamedValueTokenParser(null, "init.output.azcli.json.file", "01110", "1", "@@")
+ // new NamedValueTokenParser(null, "init.output.azcli.command.file", "01100", "1", "@@"),
+ // new NamedValueTokenParser(null, "init.output.azcli.json.file", "01110", "1", "@@")
)
{
}
diff --git a/src/common/details/console/ColorHelpers.cs b/src/common/details/console/ColorHelpers.cs
index 4c7c6553..35ea2663 100644
--- a/src/common/details/console/ColorHelpers.cs
+++ b/src/common/details/console/ColorHelpers.cs
@@ -17,6 +17,11 @@ public static void ResetColor()
Console.ResetColor();
}
+ public static ConsoleColor MapColor(ConsoleColor color)
+ {
+ return colorMap[color];
+ }
+
public static void SetHighlightColors()
{
Console.BackgroundColor = colors[0];
@@ -234,7 +239,37 @@ private static ConsoleColor[] InitBestColors()
return GetBestColors();
}
+ private static Dictionary InitColorMap()
+ {
+ var map = new Dictionary();
+
+ var xterm = Environment.GetEnvironmentVariable("TERM");
+ var is256 = xterm != null && xterm.Contains("256");
+ var isWindows = OS.IsWindows();
+
+ map[ConsoleColor.Black] = ConsoleColor.Black;
+ map[ConsoleColor.DarkBlue] = ConsoleColor.DarkBlue;
+ map[ConsoleColor.DarkGreen] = ConsoleColor.DarkGreen;
+ map[ConsoleColor.DarkCyan] = ConsoleColor.DarkCyan;
+ map[ConsoleColor.DarkRed] = ConsoleColor.DarkRed;
+ map[ConsoleColor.DarkMagenta] = ConsoleColor.DarkMagenta;
+ map[ConsoleColor.DarkYellow] = ConsoleColor.DarkYellow;
+ map[ConsoleColor.Gray] = ConsoleColor.Gray;
+ map[ConsoleColor.DarkGray] = is256 || isWindows ? ConsoleColor.DarkGray : ConsoleColor.Gray;
+ map[ConsoleColor.Blue] = ConsoleColor.Blue;
+ map[ConsoleColor.Green] = ConsoleColor.Green;
+ map[ConsoleColor.Cyan] = ConsoleColor.Cyan;
+ map[ConsoleColor.Red] = ConsoleColor.Red;
+ map[ConsoleColor.Magenta] = ConsoleColor.Magenta;
+ map[ConsoleColor.Yellow] = ConsoleColor.Yellow;
+ map[ConsoleColor.White] = ConsoleColor.White;
+
+ return map;
+ }
+
private static ConsoleColor[] colors = InitBestColors();
+ private static Dictionary colorMap = InitColorMap();
+
}
}
diff --git a/src/common/details/helpers/config_set_helpers.cs b/src/common/details/helpers/config_set_helpers.cs
index fc89149e..be530468 100644
--- a/src/common/details/helpers/config_set_helpers.cs
+++ b/src/common/details/helpers/config_set_helpers.cs
@@ -119,6 +119,21 @@ public static void ConfigSpeechResource(string subscriptionId, string region, st
actions.ForEach(x => x?.Invoke(maxLabelWidth));
}
+ public static void ConfigVisionResource(string subscriptionId, string region, string endpoint, string key)
+ {
+ ConsoleHelpers.WriteLineWithHighlight($"\n`CONFIG VISION RESOURCE`");
+ Console.WriteLine();
+
+ int maxLabelWidth = 0;
+ var actions = new List>(new Action[] {
+ ConfigSetLambda("@subscription", subscriptionId, "Subscription", subscriptionId, ref maxLabelWidth),
+ ConfigSetLambda("@vision.endpoint", endpoint, "Endpoint (vision)", endpoint, ref maxLabelWidth),
+ ConfigSetLambda("@vision.key", key, "Key (vision)", key.Substring(0, 4) + "****************************", ref maxLabelWidth),
+ ConfigSetLambda("@vision.region", region, "Region (vision)", region, ref maxLabelWidth),
+ });
+ actions.ForEach(x => x?.Invoke(maxLabelWidth));
+ }
+
public static void ConfigSearchResource(string endpoint, string key)
{
ConsoleHelpers.WriteLineWithHighlight($"\n`CONFIG AI SEARCH RESOURCE`");
diff --git a/src/common/details/helpers/http_helpers.cs b/src/common/details/helpers/http_helpers.cs
index 5dd6bbbf..b22fb7e8 100644
--- a/src/common/details/helpers/http_helpers.cs
+++ b/src/common/details/helpers/http_helpers.cs
@@ -272,5 +272,26 @@ public static string ReadWriteResponse(WebResponse response, string fileName, st
return FileHelpers.ReadWriteAllStream(stream, fileName, message, returnAsText);
}
+ public static string GetLatestVersionInfo(INamedValues values, string domain)
+ {
+ try
+ {
+ var uri = "https://api.nuget.org/v3-flatcontainer/azure.ai.cli/index.json";
+ var request = (HttpWebRequest)WebRequest.Create(uri);
+ request.Method = WebRequestMethods.Http.Get;
+ var response = HttpHelpers.GetWebResponse(request);
+ var json = HttpHelpers.ReadWriteJson(response, values, domain);
+ var info = JObject.Parse(json);
+ var versionList = (JArray)info["versions"];
+
+ return versionList.Last().ToString();
+ }
+ catch (Exception)
+ {
+ // Report no exception, this is a non-critical operation
+ }
+ return null;
+ }
+
}
}
diff --git a/src/common/details/helpers/login_helpers.cs b/src/common/details/helpers/login_helpers.cs
new file mode 100644
index 00000000..68c6e34f
--- /dev/null
+++ b/src/common/details/helpers/login_helpers.cs
@@ -0,0 +1,80 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Runtime.CompilerServices;
+using System.Text;
+using Azure.AI.Details.Common.CLI.ConsoleGui;
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class LoginHelpers
+ {
+ public static async Task> GetResponseOnLogin(bool allowInteractiveLogin, string label, Func>> getResponse, string titleLabel = "Name")
+ {
+ Console.Write($"\r{titleLabel}: *** Loading choices ***");
+ var response = await getResponse();
+
+ Console.Write($"\r{titleLabel}: ");
+ if (string.IsNullOrEmpty(response.Output.StdOutput) && !string.IsNullOrEmpty(response.Output.StdError))
+ {
+ if (LoginHelpers.HasLoginError(response.Output.StdError))
+ {
+ var loginResponse = await LoginHelpers.AttemptLogin(allowInteractiveLogin, $"{label}s");
+ if (!loginResponse.Equals(default(ParsedJsonProcessOutput)))
+ {
+ response = await getResponse();
+ }
+ }
+ if (string.IsNullOrEmpty(response.Output.StdOutput) && !string.IsNullOrEmpty(response.Output.StdError))
+ {
+ throw new ApplicationException($"ERROR: Loading resource {label}s: {response.Output.StdError}");
+ }
+ }
+ return response;
+ }
+
+ public static async Task> AttemptLogin(bool allowInteractiveLogin, string label)
+ {
+ bool cancelLogin = !allowInteractiveLogin;
+ bool useDeviceCode = false;
+ if (allowInteractiveLogin)
+ {
+ ConsoleHelpers.WriteError("*** WARNING: `az login` required ***");
+ Console.Write(" ");
+
+ var selection = 0;
+ var choices = new List() {
+ "LAUNCH: `az login` (interactive device code)",
+ "CANCEL: `az login ...` (non-interactive)",
+ };
+
+ if (!OS.IsCodeSpaces())
+ {
+ choices.Insert(0, "LAUNCH: `az login` (interactive browser)");
+ selection = OS.IsWindows() ? 0 : 1;
+ }
+
+ var picked = ListBoxPicker.PickIndexOf(choices.ToArray(), selection);
+
+ cancelLogin = picked < 0 || picked == choices.Count() - 1;
+ useDeviceCode = picked == choices.Count() - 2;
+ }
+
+ if (cancelLogin)
+ {
+ Console.Write($"\r{label}: ");
+ ConsoleHelpers.WriteLineError("*** Please run `az login` and try again ***");
+ return default;
+ }
+
+ Console.Write($"\r{label}: *** Launching `az login` (interactive) ***");
+ var response = await AzCli.Login(useDeviceCode);
+ Console.Write($"\r{label}: ");
+ return response;
+ }
+
+ public static bool HasLoginError(string errorMessage) => errorMessage.Split('\'', '"').Contains("az login") || errorMessage.Contains("refresh token");
+ }
+}
diff --git a/src/common/details/helpers/os.cs b/src/common/details/helpers/os.cs
index 17ef223b..ddc40228 100644
--- a/src/common/details/helpers/os.cs
+++ b/src/common/details/helpers/os.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
using System.Runtime.InteropServices;
diff --git a/src/common/details/helpers/process_helpers.cs b/src/common/details/helpers/process_helpers.cs
index 84abc836..b50057df 100644
--- a/src/common/details/helpers/process_helpers.cs
+++ b/src/common/details/helpers/process_helpers.cs
@@ -66,12 +66,51 @@ public static Process StartProcess(string fileName, string arguments, Dictionary
return Process.Start(start);
}
- public static async Task RunShellCommandAsync(string commandLine, Dictionary addToEnvironment = null, Action stdOutHandler = null, Action stdErrHandler = null, Action mergedOutputHandler = null, bool captureOutput = true)
+ public static async Task RunShellScriptAsync(string inlineScriptOrFileName, bool scriptIsBash = false, Dictionary addToEnvironment = null, Action stdOutHandler = null, Action stdErrHandler = null, Action mergedOutputHandler = null, bool captureOutput = true, bool interactive = false)
{
- var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- var command = isWindows ? "cmd" : "bash";
- var arguments = isWindows ? $"/c \"{commandLine}\"" : $"-li \"{commandLine}\"";
- return await RunShellCommandAsync(command, arguments, addToEnvironment, stdOutHandler, stdErrHandler, mergedOutputHandler, captureOutput);
+ ProcessOutput processOutput;
+ var useBinBash = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ if (useBinBash)
+ {
+ var binBashArguments = interactive
+ ? $"-lic \"{inlineScriptOrFileName}\""
+ : $"-lc \"{inlineScriptOrFileName}\"";
+ processOutput = await RunShellCommandAsync("/bin/bash", binBashArguments, addToEnvironment, stdOutHandler, stdErrHandler, mergedOutputHandler, captureOutput);
+ }
+ else if (scriptIsBash)
+ {
+ var cmdFile = Path.GetTempFileName() + ".cmd";
+ File.WriteAllText(cmdFile, inlineScriptOrFileName);
+
+ var git = FindCacheGitBashExe();
+ var gitBashCommand = interactive
+ ? $"@\"{git}\" -li \"{cmdFile}\""
+ : $"@\"{git}\" -l \"{cmdFile}\"";
+ var gitBashCmdFile = Path.GetTempFileName() + ".cmd";
+ File.WriteAllText(gitBashCmdFile, gitBashCommand);
+
+ processOutput = await RunShellCommandAsync(gitBashCmdFile, "", addToEnvironment, stdOutHandler, stdErrHandler, mergedOutputHandler, captureOutput);
+ File.Delete(gitBashCmdFile);
+ File.Delete(cmdFile);
+ }
+ else
+ {
+ var cmdFile = Path.GetTempFileName() + ".cmd";
+ File.WriteAllText(cmdFile, inlineScriptOrFileName);
+
+ processOutput = await RunShellCommandAsync(cmdFile, "", addToEnvironment, stdOutHandler, stdErrHandler, mergedOutputHandler, captureOutput);
+ File.Delete(cmdFile);
+ }
+
+ return processOutput;
+ }
+
+ public static async Task RunShellInteractiveAsync(Dictionary addToEnvironment = null, Action stdOutHandler = null, Action stdErrHandler = null, Action mergedOutputHandler = null, bool captureOutput = true)
+ {
+ var interactiveShellFileName = !OS.IsWindows() ? "bash" : "cmd.exe";
+ var interactiveShellArguments = !OS.IsWindows() ? "-li" : "/k PROMPT (ai dev shell) %PROMPT%& title (ai dev shell)";
+
+ return await RunShellCommandAsync(interactiveShellFileName, interactiveShellArguments, addToEnvironment, stdOutHandler, stdErrHandler, mergedOutputHandler, captureOutput);
}
public static async Task RunShellCommandAsync(string command, string arguments, Dictionary addToEnvironment = null, Action stdOutHandler = null, Action stdErrHandler = null, Action mergedOutputHandler = null, bool captureOutput = true)
@@ -197,5 +236,37 @@ private static string DictionaryToString(Dictionary dictionary)
}
return string.Join(' ', kvps);
}
+
+ private static string EnsureFindCacheGetBashExe()
+ {
+ var gitBash = FindCacheGitBashExe();
+ if (gitBash == null || gitBash == "bash.exe")
+ {
+ throw new Exception("Could not find Git for Windows bash.exe in PATH!");
+ }
+ return gitBash;
+ }
+
+ private static string FindCacheGitBashExe()
+ {
+ var bashExe = "bash.exe";
+ if (_cliCache.ContainsKey(bashExe))
+ {
+ return _cliCache[bashExe];
+ }
+
+ var found = FindGitBashExe();
+ _cliCache[bashExe] = found;
+
+ return found;
+ }
+
+ private static string FindGitBashExe()
+ {
+ var found = FileHelpers.FindFilesInOsPath("bash.exe");
+ return found.Where(x => x.ToLower().Contains("git")).FirstOrDefault() ?? "bash.exe";
+ }
+
+ private static Dictionary _cliCache = new Dictionary();
}
}
diff --git a/src/common/details/helpers/string_helpers.cs b/src/common/details/helpers/string_helpers.cs
index 29624696..3a010f47 100644
--- a/src/common/details/helpers/string_helpers.cs
+++ b/src/common/details/helpers/string_helpers.cs
@@ -92,6 +92,13 @@ private static bool ContainsAllCharsInOrder(string s, string chars, out int last
lastCharAt--;
return lastCharAt >= 0;
}
+
+ public static bool UpdateNeeded(string[] current, string[] latest)
+ {
+ return Int32.Parse(current[0]) < Int32.Parse(latest[0])
+ || Int32.Parse(current[1]) < Int32.Parse(latest[1])
+ || Int32.Parse(current[2]) < Int32.Parse(latest[2]);
+ }
}
public static class StringExtensions
diff --git a/src/common/details/helpers/value_helpers.cs b/src/common/details/helpers/value_helpers.cs
index d24a685f..42bd08a8 100644
--- a/src/common/details/helpers/value_helpers.cs
+++ b/src/common/details/helpers/value_helpers.cs
@@ -43,7 +43,7 @@ public static string ReplaceValues(this string text, ICommandValues values, bool
private static int Interpolate(int cchPrefix, StringBuilder sb, int start, ICommandValues values, bool deleteUnresolved, int level = 0)
{
- if (sb[start] != '{') throw new InvalidOperationException($"Interpolate() '{{' not found; pos={start}");
+ if (sb[start + cchPrefix - 1] != '{') throw new InvalidOperationException($"Interpolate() '{{' not found; pos={start}");
var i = cchPrefix;
diff --git a/src/common/details/named_values/named_value_tokens.cs b/src/common/details/named_values/named_value_tokens.cs
index 7b90d739..64d755d9 100644
--- a/src/common/details/named_values/named_value_tokens.cs
+++ b/src/common/details/named_values/named_value_tokens.cs
@@ -1,4 +1,9 @@
-namespace Azure.AI.Details.Common.CLI
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
{
public abstract class INamedValueTokens
{
diff --git a/src/common/details/named_values/named_values.cs b/src/common/details/named_values/named_values.cs
index 7b363951..7fa80799 100644
--- a/src/common/details/named_values/named_values.cs
+++ b/src/common/details/named_values/named_values.cs
@@ -167,6 +167,11 @@ public static void AddDisplayHelpRequest(this INamedValues values)
values.Add("display.help", "true");
}
+ public static void AddDisplayVersionRequest(this INamedValues values)
+ {
+ values.Add("display.version", "true");
+ }
+
public static void AddExpandHelpRequest(this INamedValues values, bool value = true)
{
values.Add("display.help.expand", value ? "true" : "false");
@@ -177,6 +182,16 @@ public static void AddDumpHelpRequest(this INamedValues values, bool value = tru
values.Add("display.help.dump", value ? "true" : "false");
}
+ public static bool DisplayVersionRequested(this INamedValues values)
+ {
+ return values.GetOrDefault("display.version", false);
+ }
+
+ public static bool DisplayUpdateRequested(this INamedValues values)
+ {
+ return values.GetOrDefault("display.update", false);
+ }
+
public static bool DisplayHelpRequested(this INamedValues values)
{
return values.GetOrDefault("display.help", false);
diff --git a/src/common/details/named_values/parsers/named_value_token_parser.cs b/src/common/details/named_values/parsers/named_value_token_parser.cs
index a0d1971a..87fa32b0 100644
--- a/src/common/details/named_values/parsers/named_value_token_parser.cs
+++ b/src/common/details/named_values/parsers/named_value_token_parser.cs
@@ -15,6 +15,17 @@ public abstract class INamedValueTokenParser
public class NamedValueTokenParser : INamedValueTokenParser
{
+ ///
+ /// Constructor
+ ///
+ /// The short name if you want --shortname
+ /// The full name of this property.
+ /// A string based bitmap for the fullName. i.e. 101 turns my.property.name into --myname
+ /// The number of values accepted from 1 instance of the argument on the command line.
+ /// a semi-colon deliminated list of possible values
+ /// The long name my.property.name of the internal property to set from this argument. If unset fullName is used.
+ /// The default value, unless pinnedValueKey is non-null, then the value passed to a property with the name whose value is pinnedValueKey
+ /// The name of the property value pinnedValue will be set to.
public NamedValueTokenParser(
string name, string fullName, string requiredParts,
string valueCount, string validValues = null, string valueKey = null,
@@ -79,83 +90,15 @@ private bool ParseNameAndValues(INamedValueTokens tokens, INamedValues values, s
private bool MatchShortName(INamedValueTokens tokens, INamedValues values, out int nameTokenCount)
{
- nameTokenCount = 0;
-
- var nameToken = tokens.PeekNextToken(0);
- if (nameToken != null && !nameToken.StartsWith(tokens.NamePrefixRequired())) return false;
-
- var tokenOk = nameToken != null && nameToken.Equals(ShortName, StringComparison.InvariantCultureIgnoreCase);
- if (tokenOk)
- {
- nameTokenCount = 1;
- }
-
- return nameTokenCount > 0;
+ return NamedValueTokenParserHelpers.MatchShortName(ShortName, tokens, values, out nameTokenCount);
}
private bool MatchFullName(INamedValueTokens tokens, INamedValues values, string partsRequired, out int nameTokenCount)
{
- nameTokenCount = 0;
-
- // Break the "FullName" into parts, some of which will be required, some of which are optional
- var prefix = tokens.NamePrefixRequired();
- var fullNameParts = FullName.Split('.');
- var fullNamePartsOk = fullNameParts.Length == partsRequired.Length;
-
- // Break the first token into parts, so we can start checking it
- var iPeekToken = 0;
- var token = tokens.PeekNextToken(iPeekToken);
- var tokenParts = token != null ? new List(token.Split('.')) : new List();
- var iTokenPart = 0;
-
- // For each FullName "part", check to see if it matches the next token "part" and check if that FullName part was required or not
- for (int iNamePart = 0; fullNamePartsOk && iNamePart < fullNameParts.Length; iNamePart++)
- {
- // Do they match?
- var tokenPart = tokenParts.Count > iTokenPart ? tokenParts[iTokenPart] : null;
- var fullNamePart = prefix + fullNameParts[iNamePart];
- var fullNamePartWild = fullNamePart == "*";
- var partsMatch = tokenPart != null && (fullNamePartWild || tokenPart.Equals(fullNamePart, StringComparison.InvariantCultureIgnoreCase));
-
- // If so, we'll increment the number of parts we've matched (which is also tracking the next token to use)
- if (partsMatch)
- {
- iTokenPart += 1;
- prefix = "";
- }
-
- // If we matched as a wildcard, keep track of the wildcard value
- if (partsMatch && fullNamePartWild)
- {
- SetWild(values, fullNamePart, tokenPart);
- }
-
- // Check to see if they either matched, or if the name part was optional
- var namePartOptional = iNamePart < partsRequired.Length && partsRequired[iNamePart] == '0';
- var thisNamePartOk = partsMatch || namePartOptional;
- fullNamePartsOk = thisNamePartOk;
-
- if (!fullNamePartsOk) break; // didn't match... we're outta here...
-
- if (partsMatch && iTokenPart == tokenParts.Count) // fully matched all the token parts, skip to the next token
- {
- iPeekToken += 1;
- token = tokens.PeekNextToken(iPeekToken);
- tokenParts = token != null ? new List(token.Split('.')) : new List();
- iTokenPart = 0;
- }
- }
-
- if (fullNamePartsOk)
- {
- nameTokenCount = iPeekToken;
- return true;
- }
-
- return false;
+ return NamedValueTokenParserHelpers.MatchFullName(FullName, tokens, values, partsRequired, out nameTokenCount);
}
- private bool ParseValues(INamedValueTokens tokens, INamedValues values, int skipNameTokens)
+ private bool ParseValues(INamedValueTokens tokens, INamedValues values, int skipNameTokens)
{
foreach (var valueCount in ValueCount.Split(';'))
{
@@ -258,40 +201,7 @@ private bool ParseValues(INamedValueTokens tokens, INamedValues values, int coun
private bool ValueMatchesValidValue(string peekToken, string peekTokenValue, bool skipAtAt = false)
{
- if (peekToken == null) return false;
- if (peekTokenValue == null) return false;
- if (ValidValues == null) return true;
-
- bool checkAtAt = ValidValues == "@@";
- if (checkAtAt && skipAtAt) return false;
-
- bool atAtOk = !string.IsNullOrEmpty(peekToken);
- if (checkAtAt) return atAtOk;
-
- bool checkAt = ValidValues.Length <= 3 && ValidValues.Contains("@");
- bool atOk = peekToken.StartsWith("@") || (peekToken.StartsWith("=@") && !peekTokenValue.StartsWith("=@"));
- if (checkAt && atOk) return true;
-
- bool checkSemi = ValidValues.Length <= 3 && ValidValues.Contains(";");
- bool semiOk = peekToken.Contains(";");
- if (checkSemi && semiOk) return true;
-
- bool checkTab = ValidValues.Length <= 3 && ValidValues.Contains("\t");
- bool semiTab = peekToken.Contains("\t");
- if (checkTab && semiTab) return true;
-
- foreach (var validValue in ValidValues.Split(';'))
- {
- if (peekTokenValue == validValue) return true;
- }
-
- return false;
- }
-
- private void SetWild(INamedValues values, string fullNamePart, string tokenPart)
- {
- values.Reset(fullNamePart);
- values.Add(fullNamePart, tokenPart);
+ return NamedValueTokenParserHelpers.ValueMatchesValidValue(ValidValues, peekToken, peekTokenValue, skipAtAt);
}
private void ResetWild(INamedValues values)
diff --git a/src/common/details/named_values/parsers/named_value_token_parser_helpers.cs b/src/common/details/named_values/parsers/named_value_token_parser_helpers.cs
new file mode 100644
index 00000000..56f4dab3
--- /dev/null
+++ b/src/common/details/named_values/parsers/named_value_token_parser_helpers.cs
@@ -0,0 +1,126 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class NamedValueTokenParserHelpers
+ {
+ public static bool MatchShortName(string shortName, INamedValueTokens tokens, INamedValues values, out int nameTokenCount)
+ {
+ nameTokenCount = 0;
+
+ var nameToken = tokens.PeekNextToken(0);
+ if (nameToken != null && !nameToken.StartsWith(tokens.NamePrefixRequired())) return false;
+
+ var tokenOk = nameToken != null && nameToken.Equals(shortName, StringComparison.InvariantCultureIgnoreCase);
+ if (tokenOk)
+ {
+ nameTokenCount = 1;
+ }
+
+ return nameTokenCount > 0;
+ }
+
+ public static bool MatchFullName(string fullName, INamedValueTokens tokens, INamedValues values, string partsRequired, out int nameTokenCount)
+ {
+ nameTokenCount = 0;
+
+ // Break the "FullName" into parts, some of which will be required, some of which are optional
+ var prefix = tokens.NamePrefixRequired();
+ var fullNameParts = fullName.Split('.');
+ var fullNamePartsOk = fullNameParts.Length == partsRequired.Length;
+
+ // Break the first token into parts, so we can start checking it
+ var iPeekToken = 0;
+ var token = tokens.PeekNextToken(iPeekToken);
+ var tokenParts = token != null ? new List(token.Split('.')) : new List();
+ var iTokenPart = 0;
+
+ // For each FullName "part", check to see if it matches the next token "part" and check if that FullName part was required or not
+ for (int iNamePart = 0; fullNamePartsOk && iNamePart < fullNameParts.Length; iNamePart++)
+ {
+ // Do they match?
+ var tokenPart = tokenParts.Count > iTokenPart ? tokenParts[iTokenPart] : null;
+ var fullNamePart = prefix + fullNameParts[iNamePart];
+ var fullNamePartWild = fullNamePart == "*";
+ var partsMatch = tokenPart != null && (fullNamePartWild || tokenPart.Equals(fullNamePart, StringComparison.InvariantCultureIgnoreCase));
+
+ // If so, we'll increment the number of parts we've matched (which is also tracking the next token to use)
+ if (partsMatch)
+ {
+ iTokenPart += 1;
+ prefix = "";
+ }
+
+ // If we matched as a wildcard, keep track of the wildcard value
+ if (partsMatch && fullNamePartWild)
+ {
+ SetWild(values, fullNamePart, tokenPart);
+ }
+
+ // Check to see if they either matched, or if the name part was optional
+ var namePartOptional = iNamePart < partsRequired.Length && partsRequired[iNamePart] == '0';
+ var thisNamePartOk = partsMatch || namePartOptional;
+ fullNamePartsOk = thisNamePartOk;
+
+ if (!fullNamePartsOk) break; // didn't match... we're outta here...
+
+ if (partsMatch && iTokenPart == tokenParts.Count) // fully matched all the token parts, skip to the next token
+ {
+ iPeekToken += 1;
+ token = tokens.PeekNextToken(iPeekToken);
+ tokenParts = token != null ? new List(token.Split('.')) : new List();
+ iTokenPart = 0;
+ }
+ }
+
+ if (fullNamePartsOk)
+ {
+ nameTokenCount = iPeekToken;
+ return true;
+ }
+
+ return false;
+ }
+
+ public static bool ValueMatchesValidValue(string validValues, string peekToken, string peekTokenValue, bool skipAtAt = false)
+ {
+ if (peekToken == null) return false;
+ if (peekTokenValue == null) return false;
+ if (validValues == null) return true;
+
+ bool checkAtAt = validValues == "@@";
+ if (checkAtAt && skipAtAt) return false;
+
+ bool atAtOk = !string.IsNullOrEmpty(peekToken);
+ if (checkAtAt) return atAtOk;
+
+ bool checkAt = validValues.Length <= 3 && validValues.Contains("@");
+ bool atOk = peekToken.StartsWith("@") || (peekToken.StartsWith("=@") && !peekTokenValue.StartsWith("=@"));
+ if (checkAt && atOk) return true;
+
+ bool checkSemi = validValues.Length <= 3 && validValues.Contains(";");
+ bool semiOk = peekToken.Contains(";");
+ if (checkSemi && semiOk) return true;
+
+ bool checkTab = validValues.Length <= 3 && validValues.Contains("\t");
+ bool semiTab = peekToken.Contains("\t");
+ if (checkTab && semiTab) return true;
+
+ foreach (var validValue in validValues.Split(';'))
+ {
+ if (peekTokenValue == validValue) return true;
+ }
+
+ return false;
+ }
+
+ private static void SetWild(INamedValues values, string fullNamePart, string tokenPart)
+ {
+ values.Reset(fullNamePart);
+ values.Add(fullNamePart, tokenPart);
+ }
+ }
+}
diff --git a/src/common/details/named_values/parsers/option_x_token_parser.cs b/src/common/details/named_values/parsers/option_x_token_parser.cs
new file mode 100644
index 00000000..0023332b
--- /dev/null
+++ b/src/common/details/named_values/parsers/option_x_token_parser.cs
@@ -0,0 +1,96 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Security.Cryptography;
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class OptionXTokenParser : INamedValueTokenParser
+ {
+ private string _shortName;
+ private string _fullName;
+ private string _partsRequired;
+
+ public OptionXTokenParser(string name, string fullName, string partsRequired)
+ {
+ _shortName = name;
+ _fullName = fullName;
+ _partsRequired = partsRequired;
+ }
+
+ public override bool Parse(INamedValueTokens tokens, INamedValues values)
+ {
+ var nameTokensMatched = 0;
+ if (NamedValueTokenParserHelpers.MatchShortName(_shortName, tokens, values, out nameTokensMatched) ||
+ NamedValueTokenParserHelpers.MatchFullName(_fullName, tokens, values, _partsRequired, out nameTokensMatched))
+ {
+ var prefix = tokens.NamePrefixRequired();
+ return prefix.Length > 0
+ ? ParseValuesUntilNextTokenPrefixed(tokens, values, nameTokensMatched, prefix)
+ : ParseOneIndexedValue(tokens, values, nameTokensMatched); // e.g. {_fullName}.{index}=value
+ }
+
+ return false;
+ }
+
+ private bool ParseValuesUntilNextTokenPrefixed(INamedValueTokens tokens, INamedValues values, int nameTokensMatched, string prefix)
+ {
+ var queue = new Queue();
+
+ var iPeekToken = nameTokensMatched;
+ while (true)
+ {
+ var peekToken = tokens.PeekNextToken(iPeekToken);
+ if (peekToken == null) break;
+ if (peekToken.StartsWith(prefix)) break;
+
+ var peekTokenValue = tokens.ValueFromToken(peekToken, values);
+ queue.Enqueue(peekTokenValue);
+
+ iPeekToken++;
+ }
+
+ var parsedValueCount = queue.Count();
+ if (parsedValueCount > 0)
+ {
+ for (int i = 0; queue.Count() > 0; i++)
+ {
+ var argName = $"{_fullName}.{i}";
+ if (values.Contains(argName)) continue;
+
+ values.Add(argName, queue.Dequeue());
+ }
+ }
+
+ if (parsedValueCount > 0)
+ {
+ tokens.SkipTokens(nameTokensMatched + parsedValueCount);
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool ParseOneIndexedValue(INamedValueTokens tokens, INamedValues values, int nameTokensMatched)
+ {
+ var peekToken = tokens.PeekNextToken(nameTokensMatched);
+ if (peekToken == null) return false;
+
+ var ok = int.TryParse(peekToken, out var index);
+ if (!ok) return false;
+
+ var argName = $"{_fullName}.{index}";
+ if (values.Contains(argName)) return false;
+
+ var value = tokens.PeekNextTokenValue(nameTokensMatched + 1, values);
+ if (value == null) return false;
+
+ values.Add(argName, value);
+ tokens.SkipTokens(nameTokensMatched + 2);
+
+ return true;
+ }
+ }
+}
diff --git a/src/common/details/named_values/tokens/OutputResultsFileToken.cs b/src/common/details/named_values/tokens/OutputResultsFileToken.cs
new file mode 100644
index 00000000..446ddd25
--- /dev/null
+++ b/src/common/details/named_values/tokens/OutputResultsFileToken.cs
@@ -0,0 +1,18 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class OutputResultsFileToken
+ {
+ public static NamedValueTokenData Data() => new NamedValueTokenData(_optionName, _fullName, _optionExample, _requiredDisplayName);
+ public static INamedValueTokenParser Parser() => new NamedValueTokenParser(_optionName, _fullName, "011;101", "1");
+
+ private const string _requiredDisplayName = "output results file";
+ private const string _optionName = "--results-file";
+ private const string _optionExample = "FILE";
+ private const string _fullName = "output.results.file";
+ }
+}
diff --git a/src/common/details/named_values/tokens/OutputResultsFormatToken.cs b/src/common/details/named_values/tokens/OutputResultsFormatToken.cs
new file mode 100644
index 00000000..d1d3d3fa
--- /dev/null
+++ b/src/common/details/named_values/tokens/OutputResultsFormatToken.cs
@@ -0,0 +1,18 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class OutputResultsFormatToken
+ {
+ public static NamedValueTokenData Data() => new NamedValueTokenData(_optionName, _fullName, _optionExample, _requiredDisplayName);
+ public static INamedValueTokenParser Parser() => new NamedValueTokenParser(_optionName, _fullName, "011;101", "1", "trx;junit");
+
+ private const string _requiredDisplayName = "output results format";
+ private const string _optionName = "--results-format";
+ private const string _optionExample = "trx|junit";
+ private const string _fullName = "output.results.format";
+ }
+}
diff --git a/src/common/details/named_values/tokens/conatins_option_x_token.cs b/src/common/details/named_values/tokens/conatins_option_x_token.cs
new file mode 100644
index 00000000..34629d05
--- /dev/null
+++ b/src/common/details/named_values/tokens/conatins_option_x_token.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class ContainsOptionXToken
+ {
+ public static INamedValueTokenParser Parser() => new OptionXTokenParser(_optionName, _fullName, "1");
+
+ public static string GetOption(ICommandValues values, int x) => values.GetOrDefault($"{_fullName}.{x}", null);
+
+ public static IEnumerable GetOptions(ICommandValues values)
+ {
+ for (int i = 0; true; i++)
+ {
+ var arg = GetOption(values, i);
+ if (arg == null) yield break;
+ yield return arg;
+ }
+ }
+
+ private const string _requiredDisplayName = "contains text";
+ private const string _optionName = null;
+ private const string _optionExample = "TEXT1 [...]";
+ private const string _fullName = "contains";
+ }
+}
diff --git a/src/common/details/named_values/tokens/file_option_x_token.cs b/src/common/details/named_values/tokens/file_option_x_token.cs
new file mode 100644
index 00000000..35ea998f
--- /dev/null
+++ b/src/common/details/named_values/tokens/file_option_x_token.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class FileOptionXToken
+ {
+ public static INamedValueTokenParser Parser() => new OptionXTokenParser(_optionName, _fullName, "1");
+
+ public static string GetOption(ICommandValues values, int x) => values.GetOrDefault($"{_fullName}.{x}", null);
+
+ public static IEnumerable GetOptions(ICommandValues values)
+ {
+ for (int i = 0; true; i++)
+ {
+ var arg = GetOption(values, i);
+ if (arg == null) yield break;
+ yield return arg;
+ }
+ }
+
+ private const string _requiredDisplayName = "file name";
+ private const string _optionName = null;
+ private const string _optionExample = "FILE";
+ private const string _fullName = "file";
+ }
+}
diff --git a/src/common/details/named_values/tokens/files_option_x_token.cs b/src/common/details/named_values/tokens/files_option_x_token.cs
new file mode 100644
index 00000000..1dafbb62
--- /dev/null
+++ b/src/common/details/named_values/tokens/files_option_x_token.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class FilesOptionXToken
+ {
+ public static INamedValueTokenParser Parser() => new OptionXTokenParser(_optionName, _fullName, "1");
+
+ public static string GetOption(ICommandValues values, int x) => values.GetOrDefault($"{_fullName}.{x}", null);
+
+ public static IEnumerable GetOptions(ICommandValues values)
+ {
+ for (int i = 0; true; i++)
+ {
+ var arg = GetOption(values, i);
+ if (arg == null) yield break;
+ yield return arg;
+ }
+ }
+
+ private const string _requiredDisplayName = "file names";
+ private const string _optionName = null;
+ private const string _optionExample = "FILE1 [...]";
+ private const string _fullName = "files";
+ }
+}
diff --git a/src/common/details/named_values/tokens/input_chat_history_file_token.cs b/src/common/details/named_values/tokens/input_chat_history_file_token.cs
new file mode 100644
index 00000000..190bc4c9
--- /dev/null
+++ b/src/common/details/named_values/tokens/input_chat_history_file_token.cs
@@ -0,0 +1,27 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class InputChatHistoryJsonFileToken
+ {
+ public static NamedValueTokenData Data() => new NamedValueTokenData(_optionName, _parser.FullName, _optionExample, _requiredDisplayName);
+ public static INamedValueTokenParser Parser() => _parser;
+
+ private static _Parser _parser = new();
+ private class _Parser : InputFileOptionalPrefixNamedValueTokenParser
+ {
+ public _Parser() : base("chat", "history", "1")
+ {
+ }
+
+ public new string FullName => base.FullName;
+ }
+
+ private const string _requiredDisplayName = "chat history input file";
+ private const string _optionName = "--input-chat-history-file";
+ private const string _optionExample = "FILE";
+ }
+}
\ No newline at end of file
diff --git a/src/common/details/named_values/tokens/output_chat_history_file_token.cs b/src/common/details/named_values/tokens/output_chat_history_file_token.cs
new file mode 100644
index 00000000..794e51ea
--- /dev/null
+++ b/src/common/details/named_values/tokens/output_chat_history_file_token.cs
@@ -0,0 +1,27 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class OutputChatHistoryFileToken
+ {
+ public static NamedValueTokenData Data() => new NamedValueTokenData(_optionName, _parser.FullName, _optionExample, _requiredDisplayName);
+ public static INamedValueTokenParser Parser() => _parser;
+
+ private static _Parser _parser = new();
+ private class _Parser : OutputFileOptionalPrefixNamedValueTokenParser
+ {
+ public _Parser() : base("chat", "history", "1")
+ {
+ }
+
+ public new string FullName => base.FullName;
+ }
+
+ private const string _requiredDisplayName = "chat history output file";
+ private const string _optionName = "--output-chat-history-file";
+ private const string _optionExample = "FILE";
+ }
+}
\ No newline at end of file
diff --git a/src/common/details/named_values/tokens/remove_option_x_token.cs b/src/common/details/named_values/tokens/remove_option_x_token.cs
new file mode 100644
index 00000000..dfc63449
--- /dev/null
+++ b/src/common/details/named_values/tokens/remove_option_x_token.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class RemoveOptionXToken
+ {
+ public static INamedValueTokenParser Parser() => new OptionXTokenParser(_optionName, _fullName, "1");
+
+ public static string GetOption(ICommandValues values, int x) => values.GetOrDefault($"{_fullName}.{x}", null);
+
+ public static IEnumerable GetOptions(ICommandValues values)
+ {
+ for (int i = 0; true; i++)
+ {
+ var arg = GetOption(values, i);
+ if (arg == null) yield break;
+ yield return arg;
+ }
+ }
+
+ private const string _requiredDisplayName = "remove text";
+ private const string _optionName = null;
+ private const string _optionExample = "TEXT1 [...]";
+ private const string _fullName = "remove";
+ }
+}
diff --git a/src/common/details/named_values/tokens/run_bash_script_token.cs b/src/common/details/named_values/tokens/run_bash_script_token.cs
new file mode 100644
index 00000000..a06bf88b
--- /dev/null
+++ b/src/common/details/named_values/tokens/run_bash_script_token.cs
@@ -0,0 +1,18 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class RunBashScriptToken
+ {
+ public static NamedValueTokenData Data() => new NamedValueTokenData(_optionName, _fullName, _optionExample, _requiredDisplayName);
+ public static INamedValueTokenParser Parser() => new NamedValueTokenParser(_optionName, _fullName, "010", "1");
+
+ private const string _requiredDisplayName = "run bash script";
+ private const string _optionName = "--bash";
+ private const string _optionExample = "BASH SCRIPT";
+ private const string _fullName = "run.bash.script";
+ }
+}
diff --git a/src/common/details/named_values/tokens/search_option_x_token.cs b/src/common/details/named_values/tokens/search_option_x_token.cs
new file mode 100644
index 00000000..1c13917e
--- /dev/null
+++ b/src/common/details/named_values/tokens/search_option_x_token.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class SearchOptionXToken
+ {
+ public static INamedValueTokenParser Parser() => new OptionXTokenParser(_optionName, _fullName, "1");
+
+ public static string GetOption(ICommandValues values, int x) => values.GetOrDefault($"{_fullName}.{x}", null);
+
+ public static IEnumerable GetOptions(ICommandValues values)
+ {
+ for (int i = 0; true; i++)
+ {
+ var arg = GetOption(values, i);
+ if (arg == null) yield break;
+ yield return arg;
+ }
+ }
+
+ private const string _requiredDisplayName = "search term";
+ private const string _optionName = null;
+ private const string _optionExample = "TERM";
+ private const string _fullName = "search";
+ }
+}
diff --git a/src/common/details/named_values/tokens/test_option_x_token.cs b/src/common/details/named_values/tokens/test_option_x_token.cs
new file mode 100644
index 00000000..3cbb359c
--- /dev/null
+++ b/src/common/details/named_values/tokens/test_option_x_token.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class TestOptionXToken
+ {
+ public static INamedValueTokenParser Parser() => new OptionXTokenParser(_optionName, _fullName, "1");
+
+ public static string GetOption(ICommandValues values, int x) => values.GetOrDefault($"{_fullName}.{x}", null);
+
+ public static IEnumerable GetOptions(ICommandValues values)
+ {
+ for (int i = 0; true; i++)
+ {
+ var arg = GetOption(values, i);
+ if (arg == null) yield break;
+ yield return arg;
+ }
+ }
+
+ private const string _requiredDisplayName = "test";
+ private const string _optionName = null;
+ private const string _optionExample = "TEST";
+ private const string _fullName = "test";
+ }
+}
diff --git a/src/common/details/named_values/tokens/tests_option_x_token.cs b/src/common/details/named_values/tokens/tests_option_x_token.cs
new file mode 100644
index 00000000..12f52e46
--- /dev/null
+++ b/src/common/details/named_values/tokens/tests_option_x_token.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class TestsOptionXToken
+ {
+ public static INamedValueTokenParser Parser() => new OptionXTokenParser(_optionName, _fullName, "1");
+
+ public static string GetOption(ICommandValues values, int x) => values.GetOrDefault($"{_fullName}.{x}", null);
+
+ public static IEnumerable GetOptions(ICommandValues values)
+ {
+ for (int i = 0; true; i++)
+ {
+ var arg = GetOption(values, i);
+ if (arg == null) yield break;
+ yield return arg;
+ }
+ }
+
+ private const string _requiredDisplayName = "tests";
+ private const string _optionName = null;
+ private const string _optionExample = "TEST1 [...]";
+ private const string _fullName = "tests";
+ }
+}
diff --git a/src/common/details/native_logging/AIDEBUG.cs b/src/common/details/native_logging/AIDEBUG.cs
index ce457c99..90a45f2c 100644
--- a/src/common/details/native_logging/AIDEBUG.cs
+++ b/src/common/details/native_logging/AIDEBUG.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System.Globalization;
using System.Runtime.CompilerServices;
diff --git a/src/common/details/native_logging/DiagnosticsInterop.cs b/src/common/details/native_logging/DiagnosticsInterop.cs
index d5ed197e..3ef525c0 100644
--- a/src/common/details/native_logging/DiagnosticsInterop.cs
+++ b/src/common/details/native_logging/DiagnosticsInterop.cs
@@ -1,4 +1,9 @@
-using System.IO;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.IO;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
diff --git a/src/common/details/native_logging/DisposableBase.cs b/src/common/details/native_logging/DisposableBase.cs
index 55060b0a..192806d6 100644
--- a/src/common/details/native_logging/DisposableBase.cs
+++ b/src/common/details/native_logging/DisposableBase.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
using System.Threading;
diff --git a/src/common/details/native_logging/NativeMemory.cs b/src/common/details/native_logging/NativeMemory.cs
index 6f7a8dd7..e6531e5f 100644
--- a/src/common/details/native_logging/NativeMemory.cs
+++ b/src/common/details/native_logging/NativeMemory.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
using System.Runtime.InteropServices;
diff --git a/src/common/details/native_logging/NativeUtils.cs b/src/common/details/native_logging/NativeUtils.cs
index 0047ab50..055e9266 100644
--- a/src/common/details/native_logging/NativeUtils.cs
+++ b/src/common/details/native_logging/NativeUtils.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
using System.Runtime.InteropServices;
using System.Text;
@@ -6,6 +11,7 @@ namespace Azure.AI.Details.Common.CLI
{
internal static class NativeUtils
{
+ #nullable enable
public static NativeMemory ToNativeNullTerminatedUtf8String(string? str)
{
if (str == null)
@@ -33,5 +39,6 @@ public static NativeMemory ToNativeNullTerminatedUtf8String(string? str)
throw;
}
}
+ #nullable disable
}
}
\ No newline at end of file
diff --git a/src/common/details/native_logging/SpeechNativeMethods.cs b/src/common/details/native_logging/SpeechNativeMethods.cs
index 5afc984c..96345c9b 100644
--- a/src/common/details/native_logging/SpeechNativeMethods.cs
+++ b/src/common/details/native_logging/SpeechNativeMethods.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
using System.Runtime.InteropServices;
diff --git a/src/extensions/helper_functions_extension/CalculatorHelperFunctions.cs b/src/extensions/helper_functions_extension/CalculatorHelperFunctions.cs
index d0150f3d..82f559ec 100644
--- a/src/extensions/helper_functions_extension/CalculatorHelperFunctions.cs
+++ b/src/extensions/helper_functions_extension/CalculatorHelperFunctions.cs
@@ -1,4 +1,9 @@
-using Azure.AI.OpenAI;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using Azure.AI.OpenAI;
namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions
{
diff --git a/src/extensions/helper_functions_extension/DateTimeHelperFunctions.cs b/src/extensions/helper_functions_extension/DateTimeHelperFunctions.cs
index a92b5693..b29becfc 100644
--- a/src/extensions/helper_functions_extension/DateTimeHelperFunctions.cs
+++ b/src/extensions/helper_functions_extension/DateTimeHelperFunctions.cs
@@ -1,4 +1,9 @@
-using System.Text;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Text;
using Azure.AI.OpenAI;
namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions
diff --git a/src/extensions/helper_functions_extension/FileHelperFunctions.cs b/src/extensions/helper_functions_extension/FileHelperFunctions.cs
index da22af35..6940df14 100644
--- a/src/extensions/helper_functions_extension/FileHelperFunctions.cs
+++ b/src/extensions/helper_functions_extension/FileHelperFunctions.cs
@@ -1,4 +1,9 @@
-using System.Text;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Text;
using Azure.AI.OpenAI;
namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions
diff --git a/src/extensions/helper_functions_extension/HelperFunctionCallContext.cs b/src/extensions/helper_functions_extension/HelperFunctionCallContext.cs
index 424fa9e1..7341274c 100644
--- a/src/extensions/helper_functions_extension/HelperFunctionCallContext.cs
+++ b/src/extensions/helper_functions_extension/HelperFunctionCallContext.cs
@@ -1,4 +1,9 @@
-using Azure.AI.OpenAI;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using Azure.AI.OpenAI;
namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions
{
diff --git a/src/extensions/helper_functions_extension/HelperFunctionDescriptionAttribute.cs b/src/extensions/helper_functions_extension/HelperFunctionDescriptionAttribute.cs
index 00c3b1a7..9f6ad5f4 100644
--- a/src/extensions/helper_functions_extension/HelperFunctionDescriptionAttribute.cs
+++ b/src/extensions/helper_functions_extension/HelperFunctionDescriptionAttribute.cs
@@ -1,4 +1,9 @@
-namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions
{
public class HelperFunctionDescriptionAttribute : Attribute
{
diff --git a/src/extensions/helper_functions_extension/HelperFunctionFactory.cs b/src/extensions/helper_functions_extension/HelperFunctionFactory.cs
index ee2aeb09..f53ec4bb 100644
--- a/src/extensions/helper_functions_extension/HelperFunctionFactory.cs
+++ b/src/extensions/helper_functions_extension/HelperFunctionFactory.cs
@@ -1,4 +1,9 @@
-using System.Reflection;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Reflection;
using Newtonsoft.Json;
using Azure.AI.OpenAI;
using Newtonsoft.Json.Linq;
diff --git a/src/extensions/helper_functions_extension/HelperFunctionFactoryExtensions.cs b/src/extensions/helper_functions_extension/HelperFunctionFactoryExtensions.cs
index 9dffea19..1bc6cb74 100644
--- a/src/extensions/helper_functions_extension/HelperFunctionFactoryExtensions.cs
+++ b/src/extensions/helper_functions_extension/HelperFunctionFactoryExtensions.cs
@@ -1,9 +1,13 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.Serialization;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using Azure.AI.OpenAI;
-using DevLab.JmesPath.Utils;
-using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.ClientModel.Primitives;
+using System.Runtime.CompilerServices;
namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions
{
@@ -25,5 +29,69 @@ public static bool TryCallFunction(this ChatCompletionsOptions options, HelperFu
{
return context.TryCallFunction(options, out result);
}
+
+ public static void SaveChatHistoryToFile(this ChatCompletionsOptions options, string fileName)
+ {
+ var historyFile = new StringBuilder();
+
+ foreach (var message in options.Messages)
+ {
+ var messageText = message switch
+ {
+ ChatRequestUserMessage userMessage => ModelReaderWriter.Write(userMessage, ModelReaderWriterOptions.Json).ToString(),
+ ChatRequestAssistantMessage assistantMessage => ModelReaderWriter.Write(assistantMessage, ModelReaderWriterOptions.Json).ToString(),
+ ChatRequestFunctionMessage functionMessage => ModelReaderWriter.Write(functionMessage, ModelReaderWriterOptions.Json).ToString(),
+ ChatRequestSystemMessage systemMessage => ModelReaderWriter.Write(systemMessage, ModelReaderWriterOptions.Json).ToString(),
+ ChatRequestToolMessage toolMessage => ModelReaderWriter.Write(toolMessage, ModelReaderWriterOptions.Json).ToString(),
+ _ => null
+ };
+
+ if (!string.IsNullOrEmpty(messageText))
+ {
+ historyFile.AppendLine(messageText);
+ }
+ }
+
+ FileHelpers.WriteAllText(fileName, historyFile.ToString(), Encoding.UTF8);
+ }
+
+ public static void ReadChatHistoryFromFile(this ChatCompletionsOptions options, string fileName)
+ {
+ var historyFile = FileHelpers.ReadAllText(fileName, Encoding.UTF8);
+
+ var historyFileLines = historyFile.Split(Environment.NewLine);
+ var clearIfSystem = () =>
+ {
+ options.Messages.Clear();
+ return typeof(ChatRequestSystemMessage);
+ };
+
+ foreach (var line in historyFileLines)
+ {
+ var jsonObject = JsonDocument.Parse(line);
+ JsonElement roleObj;
+
+ if (!jsonObject.RootElement.TryGetProperty("role", out roleObj))
+ {
+ continue;
+ }
+
+ var role = roleObj.GetString();
+
+ var type = role?.ToLowerInvariant() switch
+ {
+ "user" => typeof(ChatRequestUserMessage),
+ "assistant" => typeof(ChatRequestAssistantMessage),
+ "function" => typeof(ChatRequestFunctionMessage),
+ "system" => clearIfSystem(),
+ "tool" => typeof(ChatRequestToolMessage),
+ _ => throw new Exception($"Unknown chat role {role}")
+ };
+
+ var message = ModelReaderWriter.Read(BinaryData.FromString(line), type, ModelReaderWriterOptions.Json);
+
+ options.Messages.Add(message as ChatRequestMessage);
+ }
+ }
}
}
diff --git a/src/extensions/helper_functions_extension/HelperFunctionParameterDescriptionAttribute.cs b/src/extensions/helper_functions_extension/HelperFunctionParameterDescriptionAttribute.cs
index a0678f63..a38e67d6 100644
--- a/src/extensions/helper_functions_extension/HelperFunctionParameterDescriptionAttribute.cs
+++ b/src/extensions/helper_functions_extension/HelperFunctionParameterDescriptionAttribute.cs
@@ -1,4 +1,9 @@
-namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions
{
public class HelperFunctionParameterDescriptionAttribute : Attribute
{
diff --git a/src/extensions/helper_functions_extension/RunPythonHelperFunction.cs b/src/extensions/helper_functions_extension/RunPythonHelperFunction.cs
index e41b6791..af55c650 100644
--- a/src/extensions/helper_functions_extension/RunPythonHelperFunction.cs
+++ b/src/extensions/helper_functions_extension/RunPythonHelperFunction.cs
@@ -1,4 +1,9 @@
-// using System.Diagnostics;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+// using System.Diagnostics;
// namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions
// {
diff --git a/src/extensions/helper_functions_extension/helper_functions_extension.csproj b/src/extensions/helper_functions_extension/helper_functions_extension.csproj
index f7bb9259..0fbb2382 100644
--- a/src/extensions/helper_functions_extension/helper_functions_extension.csproj
+++ b/src/extensions/helper_functions_extension/helper_functions_extension.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/src/extensions/test_helper_functions_extension/BuildCommon.targets b/src/extensions/speech_extension/BuildCommon.targets
similarity index 100%
rename from src/extensions/test_helper_functions_extension/BuildCommon.targets
rename to src/extensions/speech_extension/BuildCommon.targets
diff --git a/src/spx/commands/base/recognize_commands_base.cs b/src/extensions/speech_extension/commands/base/recognize_commands_base.cs
similarity index 100%
rename from src/spx/commands/base/recognize_commands_base.cs
rename to src/extensions/speech_extension/commands/base/recognize_commands_base.cs
diff --git a/src/spx/commands/batch_command.cs b/src/extensions/speech_extension/commands/batch_command.cs
similarity index 99%
rename from src/spx/commands/batch_command.cs
rename to src/extensions/speech_extension/commands/batch_command.cs
index dcc7f478..36afb30f 100644
--- a/src/spx/commands/batch_command.cs
+++ b/src/extensions/speech_extension/commands/batch_command.cs
@@ -23,14 +23,14 @@ namespace Azure.AI.Details.Common.CLI
{
public class BatchCommand : Command
{
- internal BatchCommand(ICommandValues values)
+ public BatchCommand(ICommandValues values)
{
_values = values.ReplaceValues();
_quiet = _values.GetOrDefault("x.quiet", false);
_verbose = _values.GetOrDefault("x.verbose", true);
}
- internal bool RunCommand()
+ public bool RunCommand()
{
try
{
@@ -55,7 +55,7 @@ private void DoCommand(string command)
{
CheckPath();
- switch (command)
+ switch (command.Replace("speech.", ""))
{
case "batch.list": DoList(); break;
case "batch.download": DoDownload(); break;
diff --git a/src/spx/commands/transcribe_conversation_command.cs b/src/extensions/speech_extension/commands/conversation_transcription_command.cs
similarity index 95%
rename from src/spx/commands/transcribe_conversation_command.cs
rename to src/extensions/speech_extension/commands/conversation_transcription_command.cs
index 055a380c..4fc311bf 100644
--- a/src/spx/commands/transcribe_conversation_command.cs
+++ b/src/extensions/speech_extension/commands/conversation_transcription_command.cs
@@ -18,9 +18,9 @@
namespace Azure.AI.Details.Common.CLI
{
- public class TranscribeConversationCommand : Command
+ public class ConversationTranscriptionCommand : Command
{
- internal TranscribeConversationCommand(ICommandValues values)
+ internal ConversationTranscriptionCommand(ICommandValues values)
{
_values = values.ReplaceValues();
}
@@ -73,12 +73,9 @@ private void RecognizeContinuous()
private ConversationTranscriber CreateConversationTranscriber()
{
SpeechConfig config = CreateSpeechConfig();
- Conversation conversation = CreateConversation(config);
AudioConfig audioConfig = ConfigHelpers.CreateAudioConfig(_values);
- var transcriber = new ConversationTranscriber(audioConfig);
-
- transcriber.JoinConversationAsync(conversation).Wait();
+ var transcriber = new ConversationTranscriber(config, audioConfig);
_disposeAfterStop.Add(audioConfig);
_disposeAfterStop.Add(transcriber);
@@ -186,7 +183,6 @@ private void SetSpeechConfigProperties(SpeechConfig config)
// var inRoom = _values.GetOrDefault("conversation.in.room", false);
// if (inRoom) config.SetProperty("ConversationTranscriptionInRoomAndOnline", "true");
- config.SetProperty("ConversationTranscriptionInRoomAndOnline", "true");
CheckNotYetImplementedConfigProperties();
}
@@ -213,15 +209,6 @@ private void CheckNotYetImplementedConfigProperties()
}
}
- private Conversation CreateConversation(SpeechConfig config)
- {
- var conversationId = _values.GetOrDefault("conversation.id", Guid.NewGuid().ToString());
- var task = Conversation.CreateConversationAsync(config, conversationId);
-
- const int maxTimeToWait = 30000;
- return task.Wait(maxTimeToWait) ? task.Result : null;
- }
-
private void CheckAudioInput()
{
var id = _values["audio.input.id"];
diff --git a/src/spx/commands/custom_speech_recognition_command.cs b/src/extensions/speech_extension/commands/custom_speech_recognition_command.cs
similarity index 99%
rename from src/spx/commands/custom_speech_recognition_command.cs
rename to src/extensions/speech_extension/commands/custom_speech_recognition_command.cs
index f44e1747..710f4f31 100644
--- a/src/spx/commands/custom_speech_recognition_command.cs
+++ b/src/extensions/speech_extension/commands/custom_speech_recognition_command.cs
@@ -20,14 +20,14 @@ namespace Azure.AI.Details.Common.CLI
{
public class CustomSpeechRecognitionCommand : Command
{
- internal CustomSpeechRecognitionCommand(ICommandValues values)
+ public CustomSpeechRecognitionCommand(ICommandValues values)
{
_values = values.ReplaceValues();
_quiet = _values.GetOrDefault("x.quiet", false);
_verbose = _values.GetOrDefault("x.verbose", true);
}
- internal bool RunCommand()
+ public bool RunCommand()
{
try
{
@@ -52,7 +52,7 @@ private void DoCommand(string command)
{
CheckPath();
- switch (command)
+ switch (command.Replace("speech.", ""))
{
case "csr.list": DoList(); break;
case "csr.download": DoDownload(); break;
diff --git a/src/spx/commands/dialog_command.cs b/src/extensions/speech_extension/commands/dialog_command.cs
similarity index 100%
rename from src/spx/commands/dialog_command.cs
rename to src/extensions/speech_extension/commands/dialog_command.cs
diff --git a/src/spx/commands/intent_command.cs b/src/extensions/speech_extension/commands/intent_command.cs
similarity index 99%
rename from src/spx/commands/intent_command.cs
rename to src/extensions/speech_extension/commands/intent_command.cs
index c886b4d7..322b1371 100644
--- a/src/spx/commands/intent_command.cs
+++ b/src/extensions/speech_extension/commands/intent_command.cs
@@ -25,12 +25,12 @@ namespace Azure.AI.Details.Common.CLI
public class IntentCommand : Command
{
- internal IntentCommand(ICommandValues values)
+ public IntentCommand(ICommandValues values)
{
_values = values.ReplaceValues();
}
- internal bool RunCommand()
+ public bool RunCommand()
{
Recognize(_values["recognize.method"]);
return _values.GetOrDefault("passed", true);
diff --git a/src/extensions/speech_extension/commands/meeting_transcription_command.cs b/src/extensions/speech_extension/commands/meeting_transcription_command.cs
new file mode 100644
index 00000000..78776dc5
--- /dev/null
+++ b/src/extensions/speech_extension/commands/meeting_transcription_command.cs
@@ -0,0 +1,520 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CognitiveServices.Speech;
+using Microsoft.CognitiveServices.Speech.Audio;
+using Microsoft.CognitiveServices.Speech.Transcription;
+
+using System.Collections.Generic;
+
+namespace Azure.AI.Details.Common.CLI
+{
+ public class MeetingTranscriptionCommand : Command
+ {
+ internal MeetingTranscriptionCommand(ICommandValues values)
+ {
+ _values = values.ReplaceValues();
+ }
+
+ internal bool RunCommand()
+ {
+ Recognize(_values["recognize.method"]);
+ return _values.GetOrDefault("passed", true);
+ }
+
+ private void Recognize(string recognize)
+ {
+ switch (recognize)
+ {
+ case "":
+ case null:
+ case "continuous": RecognizeContinuous(); break;
+ }
+ }
+
+ private void RecognizeContinuous()
+ {
+ StartCommand();
+
+ MeetingTranscriber transcriber = CreateMeetingTranscriber();
+ PrepRecognizerConnection(transcriber, true);
+ PrepRecognizerGrammars(transcriber);
+
+ transcriber.SessionStarted += SessionStarted;
+ transcriber.SessionStopped += SessionStopped;
+ transcriber.Transcribing += Transcribing;
+ transcriber.Transcribed += Transcribed;
+ transcriber.Canceled += Canceled;
+
+ transcriber.StartTranscribingAsync().Wait();
+ if (_microphone) { Console.WriteLine("Transcribing; press ENTER to stop ...\n"); }
+
+ var timeout = _values.GetOrDefault("recognize.timeout", int.MaxValue);
+ WaitForContinuousStopCancelKeyOrTimeout(transcriber, timeout);
+
+ transcriber.StopTranscribingAsync().Wait();
+
+ if (_disconnect) RecognizerConnectionDisconnect(transcriber);
+
+ StopCommand();
+ DisposeAfterStop();
+ DeleteTemporaryFiles();
+ }
+
+ private MeetingTranscriber CreateMeetingTranscriber()
+ {
+ SpeechConfig config = CreateSpeechConfig();
+ Meeting meeting = CreateMeeting(config);
+
+ AudioConfig audioConfig = ConfigHelpers.CreateAudioConfig(_values);
+ var transcriber = new MeetingTranscriber(audioConfig);
+
+ transcriber.JoinMeetingAsync(meeting).Wait();
+
+ _disposeAfterStop.Add(audioConfig);
+ _disposeAfterStop.Add(transcriber);
+
+ _output.EnsureCachePropertyCollection("recognizer", transcriber.Properties);
+
+ return transcriber;
+ }
+
+ private SpeechConfig CreateSpeechConfig()
+ {
+ var key = _values["service.config.key"];
+ var host = _values["service.config.host"];
+ var region = _values["service.config.region"];
+ var endpoint = _values["service.config.endpoint.uri"];
+ var tokenValue = _values["service.config.token.value"];
+
+ if (string.IsNullOrEmpty(endpoint) && string.IsNullOrEmpty(region) && string.IsNullOrEmpty(host))
+ {
+ _values.AddThrowError("ERROR:", $"Creating SpeechConfig; requires one of: region, endpoint, or host.");
+ }
+ else if (!string.IsNullOrEmpty(region) && string.IsNullOrEmpty(tokenValue) && string.IsNullOrEmpty(key))
+ {
+ _values.AddThrowError("ERROR:", $"Creating SpeechConfig; use of region requires one of: key or token.");
+ }
+
+ SpeechConfig config = null;
+ if (!string.IsNullOrEmpty(endpoint))
+ {
+ config = string.IsNullOrEmpty(key)
+ ? SpeechConfig.FromEndpoint(new Uri(endpoint))
+ : SpeechConfig.FromEndpoint(new Uri(endpoint), key);
+ }
+ else if (!string.IsNullOrEmpty(host))
+ {
+ config = string.IsNullOrEmpty(key)
+ ? SpeechConfig.FromHost(new Uri(host))
+ : SpeechConfig.FromHost(new Uri(host), key);
+ }
+ else // if (!string.IsNullOrEmpty(region))
+ {
+ config = string.IsNullOrEmpty(tokenValue)
+ ? SpeechConfig.FromSubscription(key, region)
+ : SpeechConfig.FromAuthorizationToken(tokenValue, region);
+ }
+
+ if (!string.IsNullOrEmpty(tokenValue))
+ {
+ config.AuthorizationToken = tokenValue;
+ }
+
+ SetSpeechConfigProperties(config);
+ return config;
+ }
+
+ private void SetSpeechConfigProperties(SpeechConfig config)
+ {
+ ConfigHelpers.SetupLogFile(config, _values);
+
+ var language = _values["source.language.config"];
+ if (!string.IsNullOrEmpty(language)) config.SpeechRecognitionLanguage = language;
+
+ var proxyHost = _values["service.config.proxy.host"];
+ if (!string.IsNullOrEmpty(proxyHost)) config.SetProxy(proxyHost, _values.GetOrDefault("service.config.proxy.port", 80));
+
+ var endpointId = _values["service.config.endpoint.id"];
+ if (!string.IsNullOrEmpty(endpointId)) config.EndpointId = endpointId;
+
+ var needDetailedText = _output.NeedsLexicalText() || _output.NeedsItnText();
+ if (needDetailedText) config.OutputFormat = OutputFormat.Detailed;
+
+ var profanity = _values["service.output.config.profanity.option"];
+ if (profanity == "removed") config.SetProfanity(ProfanityOption.Removed);
+ if (profanity == "masked") config.SetProfanity(ProfanityOption.Masked);
+ if (profanity == "raw") config.SetProfanity(ProfanityOption.Raw);
+
+ var wordTimings = _values.GetOrDefault("service.output.config.word.level.timing", false);
+ if (wordTimings) config.RequestWordLevelTimestamps();
+
+ var contentLogging = _values.GetOrDefault("service.config.content.logging.enabled", false);
+ if (contentLogging) config.EnableAudioLogging();
+
+ var trafficType = _values.GetOrDefault("service.config.endpoint.traffic.type", "spx");
+ config.SetServiceProperty("traffictype", trafficType, ServicePropertyChannel.UriQueryParameter);
+
+ var endpointParam = _values.GetOrDefault("service.config.endpoint.query.string", "");
+ if (!string.IsNullOrEmpty(endpointParam)) ConfigHelpers.SetEndpointParams(config, endpointParam);
+
+ var httpHeader = _values.GetOrDefault("service.config.endpoint.http.header", "");
+ if (!string.IsNullOrEmpty(httpHeader)) SetHttpHeaderProperty(config, httpHeader);
+
+ var rtf = _values.GetOrDefault("audio.input.real.time.factor", -1);
+ if (rtf >= 0) config.SetProperty("SPEECH-AudioThrottleAsPercentageOfRealTime", rtf.ToString());
+
+ var fastLane = _values.GetOrDefault("audio.input.fast.lane", rtf >= 0 ? 0 : -1);
+ if (fastLane >= 0) config.SetProperty("SPEECH-TransmitLengthBeforThrottleMs", fastLane.ToString());
+
+ var stringProperty = _values.GetOrDefault("config.string.property", "");
+ if (!string.IsNullOrEmpty(stringProperty)) ConfigHelpers.SetStringProperty(config, stringProperty);
+
+ var stringProperties = _values.GetOrDefault("config.string.properties", "");
+ if (!string.IsNullOrEmpty(stringProperties)) ConfigHelpers.SetStringProperties(config, stringProperties);
+
+ // config.SetProperty("AudioConfig_NumberOfChannelsForCapture", "8");
+
+ // var inRoom = _values.GetOrDefault("conversation.in.room", false);
+ // if (inRoom) config.SetProperty("ConversationTranscriptionInRoomAndOnline", "true");
+ config.SetProperty("ConversationTranscriptionInRoomAndOnline", "true");
+
+ CheckNotYetImplementedConfigProperties();
+ }
+
+ private static void SetHttpHeaderProperty(SpeechConfig config, string httpHeader)
+ {
+ string name = "", value = "";
+ if (StringHelpers.SplitNameValue(httpHeader, out name, out value)) config.SetServiceProperty(name, value, ServicePropertyChannel.HttpHeader);
+ }
+
+ private void CheckNotYetImplementedConfigProperties()
+ {
+ var notYetImplemented =
+ ";config.token.type;config.token.password;config.token.username" +
+ ";config.language.target;transcriber.property";
+
+ foreach (var key in notYetImplemented.Split(';'))
+ {
+ var value = _values[key];
+ if (!string.IsNullOrEmpty(value))
+ {
+ _values.AddThrowError("WARNING:", $"'{key}={value}' NOT YET IMPLEMENTED!!");
+ }
+ }
+ }
+
+ private Meeting CreateMeeting(SpeechConfig config)
+ {
+ var meetingId = _values.GetOrDefault("meeting.id", Guid.NewGuid().ToString());
+ var task = Meeting.CreateMeetingAsync(config, meetingId);
+
+ const int maxTimeToWait = 30000;
+ return task.Wait(maxTimeToWait) ? task.Result : null;
+ }
+
+ private void CheckAudioInput()
+ {
+ var id = _values["audio.input.id"];
+ var device = _values["audio.input.microphone.device"];
+ var input = _values["audio.input.type"];
+ var file = _values["audio.input.file"];
+ var url = "";
+
+ if (!string.IsNullOrEmpty(file) && file.StartsWith("http"))
+ {
+ file = DownloadInputFile(url = file, "audio.input.file", "audio input");
+ }
+
+ if (string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(url))
+ {
+ id = GetIdFromInputUrl(url, "audio.input.id");
+ }
+
+ if (string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(file))
+ {
+ id = GetIdFromAudioInputFile(input, file);
+ }
+
+ if (string.IsNullOrEmpty(input) && !string.IsNullOrEmpty(id))
+ {
+ input = GetAudioInputFromId(id);
+ }
+
+ if (input == "file" && string.IsNullOrEmpty(file) && !string.IsNullOrEmpty(id))
+ {
+ file = GetAudioInputFileFromId(id);
+ }
+
+ _microphone = (input == "microphone" || string.IsNullOrEmpty(input));
+ }
+
+ private string GetIdFromAudioInputFile(string input, string file)
+ {
+ string id;
+ if (input == "microphone" || string.IsNullOrEmpty(input))
+ {
+ id = "microphone";
+ }
+ else if (input == "file" && !string.IsNullOrEmpty(file))
+ {
+ var existing = FileHelpers.DemandFindFileInDataPath(file, _values, "audio input");
+ id = Path.GetFileNameWithoutExtension(existing);
+ }
+ else
+ {
+ id = "error";
+ }
+
+ _values.Add("audio.input.id", id);
+ return id;
+ }
+
+ private string GetAudioInputFromId(string id)
+ {
+ string input;
+ if (id == "microphone")
+ {
+ input = "microphone";
+ }
+ else if (FileHelpers.FileExistsInDataPath(id, _values) || FileHelpers.FileExistsInDataPath(id + ".wav", _values))
+ {
+ input = "file";
+ }
+ else if (_values.Contains("audio.input.id.url"))
+ {
+ input = "file";
+ }
+ else
+ {
+ _values.AddThrowError("ERROR:", $"Cannot find audio input file: \"{id}.wav\"");
+ return null;
+ }
+
+ _values.Add("audio.input.type", input);
+ return input;
+ }
+
+ private string GetAudioInputFileFromId(string id)
+ {
+ string file;
+ var existing = FileHelpers.FindFileInDataPath(id, _values);
+ if (existing == null) existing = FileHelpers.FindFileInDataPath(id + ".wav", _values);
+
+ if (existing == null)
+ {
+ var url = _values["audio.input.id.url"];
+ if (!string.IsNullOrEmpty(url))
+ {
+ url = url.Replace("{id}", id);
+ existing = HttpHelpers.DownloadFileWithRetry(url);
+ }
+ }
+
+ file = existing;
+ _values.Add("audio.input.file", file);
+ return file;
+ }
+
+ private KeywordRecognitionModel LoadKeywordModel()
+ {
+ var fileName = _values["recognize.keyword.file"];
+ var existing = FileHelpers.DemandFindFileInDataPath(fileName, _values, "keyword model");
+
+ var keywordModel = KeywordRecognitionModel.FromFile(existing);
+ return keywordModel;
+ }
+
+ private void PrepRecognizerGrammars(MeetingTranscriber transcriber)
+ {
+ var phrases = _values["grammar.phrase.list"];
+ if (!string.IsNullOrEmpty(phrases)) PrepRecognizerPhraseListGrammar(transcriber, phrases.Split('\r', '\n', ';'));
+
+ var partialPhraseRecognitionFactor = _values["grammar.recognition.factor.phrase"];
+ if (!string.IsNullOrEmpty(partialPhraseRecognitionFactor)) PrepGrammarList(transcriber, double.Parse(partialPhraseRecognitionFactor));
+ }
+
+ private void PrepGrammarList(MeetingTranscriber recognizer, double partialPhraseRecognitionFactor)
+ {
+ var grammarList = GrammarList.FromRecognizer(recognizer);
+ grammarList.SetRecognitionFactor(partialPhraseRecognitionFactor, RecognitionFactorScope.PartialPhrase);
+ }
+
+ private void PrepRecognizerPhraseListGrammar(MeetingTranscriber transcriber, IEnumerable phrases)
+ {
+ var grammar = PhraseListGrammar.FromRecognizer(transcriber);
+ foreach (var phrase in phrases)
+ {
+ if (!string.IsNullOrEmpty(phrase))
+ {
+ grammar.AddPhrase(phrase);
+ }
+ }
+ }
+
+ private void PrepRecognizerConnection(MeetingTranscriber transcriber, bool continuous)
+ {
+ var connection = Connection.FromRecognizer(transcriber);
+ _disposeAfterStop.Add(connection);
+
+ connection.Connected += Connected;
+ connection.Disconnected += Disconnected;
+ connection.MessageReceived += ConnectionMessageReceived;
+
+ var connect = _values["connection.connect"];
+ var disconnect = _values["connection.disconnect"];
+
+ _connect = !string.IsNullOrEmpty(connect) && connect == "true";
+ _disconnect = !string.IsNullOrEmpty(disconnect) && disconnect == "true";
+
+ if (_connect) RecognizerConnectionConnect(connection, continuous);
+ ConnectionHelpers.SetConnectionMessageProperties(connection, _values);
+ }
+
+ private void RecognizerConnectionConnect(Connection connection, bool continuous)
+ {
+ connection.Open(continuous);
+ }
+
+ private void RecognizerConnectionDisconnect(MeetingTranscriber transcriber)
+ {
+ _lock.EnterReaderLockOnce(ref _expectDisconnected);
+
+ var connection = Connection.FromRecognizer(transcriber);
+ connection.Close();
+ }
+
+ private void Connected(object sender, ConnectionEventArgs e)
+ {
+ _display.DisplayConnected(e);
+ _output.Connected(e);
+ }
+
+ private void Disconnected(object sender, ConnectionEventArgs e)
+ {
+ _display.DisplayDisconnected(e);
+ _output.Disconnected(e);
+
+ _lock.ExitReaderLockOnce(ref _expectDisconnected);
+ }
+
+ private void ConnectionMessageReceived(object sender, ConnectionMessageEventArgs e)
+ {
+ _display.DisplayMessageReceived(e);
+ _output.ConnectionMessageReceived(e);
+ }
+
+ private void SessionStarted(object sender, SessionEventArgs e)
+ {
+ _lock.EnterReaderLockOnce(ref _expectSessionStopped);
+ _stopEvent.Reset();
+
+ _display.DisplaySessionStarted(e);
+ _output.SessionStarted(e);
+ }
+
+ private void SessionStopped(object sender, SessionEventArgs e)
+ {
+ _display.DisplaySessionStopped(e);
+ _output.SessionStopped(e);
+
+ _stopEvent.Set();
+ _lock.ExitReaderLockOnce(ref _expectSessionStopped);
+ }
+
+ private void Transcribing(object sender, MeetingTranscriptionEventArgs e)
+ {
+ _lock.EnterReaderLockOnce(ref _expectRecognized);
+
+ _display.DisplayTranscribing(e);
+ _output.Transcribing(e);
+ }
+
+ private void Transcribed(object sender, MeetingTranscriptionEventArgs e)
+ {
+ _display.DisplayTranscribed(e);
+ _output.Transcribed(e);
+
+ _lock.ExitReaderLockOnce(ref _expectRecognized);
+ }
+
+ private void Canceled(object sender, MeetingTranscriptionCanceledEventArgs e)
+ {
+ _display.DisplayCanceled(e);
+ _output.Canceled(e);
+ _canceledEvent.Set();
+ }
+
+ private void WaitForContinuousStopCancelKeyOrTimeout(MeetingTranscriber transcriber, int timeout)
+ {
+ var interval = 100;
+
+ while (timeout > 0)
+ {
+ timeout -= interval;
+ if (_stopEvent.WaitOne(interval)) break;
+ if (_canceledEvent.WaitOne(0)) break;
+ if (_microphone && Console.KeyAvailable)
+ {
+ transcriber.StopTranscribingAsync().Wait();
+ break;
+ }
+ }
+ }
+
+ private void StartCommand()
+ {
+ CheckPath();
+ CheckAudioInput();
+
+ _display = new DisplayHelper(_values);
+
+ _output = new OutputHelper(_values);
+ _output.StartOutput();
+
+ var id = _values["audio.input.id"];
+ _output.EnsureOutputAll("audio.input.id", id);
+ _output.EnsureOutputEach("audio.input.id", id);
+ _output.EnsureCacheProperty("audio.input.id", id);
+
+ var file = _values["audio.input.file"];
+ _output.EnsureCacheProperty("audio.input.file", file);
+
+ _lock = new SpinLock();
+ _lock.StartLock();
+
+ _expectRecognized = 0;
+ _expectSessionStopped = 0;
+ _expectDisconnected = 0;
+ }
+
+ private void StopCommand()
+ {
+ _lock.StopLock(5000);
+
+ _output.CheckOutput();
+ _output.StopOutput();
+ }
+
+ private bool _microphone = false;
+ private bool _connect = false;
+ private bool _disconnect = false;
+
+ private SpinLock _lock = null;
+ private int _expectRecognized = 0;
+ private int _expectSessionStopped = 0;
+ private int _expectDisconnected = 0;
+
+ OutputHelper _output = null;
+ DisplayHelper _display = null;
+ }
+}
diff --git a/src/spx/commands/parsers/batch_command_parser.cs b/src/extensions/speech_extension/commands/parsers/batch_command_parser.cs
similarity index 98%
rename from src/spx/commands/parsers/batch_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/batch_command_parser.cs
index b2f807bb..b378c110 100644
--- a/src/spx/commands/parsers/batch_command_parser.cs
+++ b/src/extensions/speech_extension/commands/parsers/batch_command_parser.cs
@@ -9,7 +9,7 @@
namespace Azure.AI.Details.Common.CLI
{
- class BatchCommandParser : CommandParser
+ public class BatchCommandParser : CommandParser
{
public static bool ParseCommand(INamedValueTokens tokens, ICommandValues values)
{
@@ -42,10 +42,10 @@ private static readonly (string name, bool valuesRequired)[] _commands = {
"batch"
};
- private static IEnumerable GetCommandParsers(ICommandValues values)
+ public static IEnumerable GetCommandParsers(ICommandValues values)
{
var commandName = values.GetCommand();
- switch (commandName)
+ switch (commandName.Replace("speech.", ""))
{
case "batch.list":
case "batch.transcription.list":
diff --git a/src/spx/commands/parsers/bot_command_parser.cs b/src/extensions/speech_extension/commands/parsers/bot_command_parser.cs
similarity index 100%
rename from src/spx/commands/parsers/bot_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/bot_command_parser.cs
diff --git a/src/spx/commands/parsers/common_output_token_parsers.cs b/src/extensions/speech_extension/commands/parsers/common_output_token_parsers.cs
similarity index 100%
rename from src/spx/commands/parsers/common_output_token_parsers.cs
rename to src/extensions/speech_extension/commands/parsers/common_output_token_parsers.cs
diff --git a/src/spx/commands/parsers/common_speech_token_parsers.cs b/src/extensions/speech_extension/commands/parsers/common_speech_token_parsers.cs
similarity index 100%
rename from src/spx/commands/parsers/common_speech_token_parsers.cs
rename to src/extensions/speech_extension/commands/parsers/common_speech_token_parsers.cs
diff --git a/src/spx/commands/parsers/custom_speech_recognition_command_parser.cs b/src/extensions/speech_extension/commands/parsers/custom_speech_recognition_command_parser.cs
similarity index 98%
rename from src/spx/commands/parsers/custom_speech_recognition_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/custom_speech_recognition_command_parser.cs
index 595e0287..0b01f831 100644
--- a/src/spx/commands/parsers/custom_speech_recognition_command_parser.cs
+++ b/src/extensions/speech_extension/commands/parsers/custom_speech_recognition_command_parser.cs
@@ -8,7 +8,7 @@
namespace Azure.AI.Details.Common.CLI
{
- class CustomSpeechRecognitionCommandParser : CommandParser
+ public class CustomSpeechRecognitionCommandParser : CommandParser
{
public static bool ParseCommand(INamedValueTokens tokens, ICommandValues values)
{
@@ -64,9 +64,13 @@ private static readonly (string name, bool valuesRequired)[] _commands = {
"csr"
};
- private static INamedValueTokenParser[] GetCommandParsers(ICommandValues values)
+ public static INamedValueTokenParser[] GetCommandParsers(ICommandValues values)
{
- var commandName = values.GetCommand();
+ var commandName = string.Join('.', values.GetCommand()
+ .Split('.')
+ .SkipWhile(x => x == "speech")
+ .ToArray());
+
switch (commandName)
{
case "csr.list": return listCommandParsers;
diff --git a/src/spx/commands/parsers/dialog_command_parser.cs b/src/extensions/speech_extension/commands/parsers/dialog_command_parser.cs
similarity index 100%
rename from src/spx/commands/parsers/dialog_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/dialog_command_parser.cs
diff --git a/src/spx/commands/parsers/intent_command_parser.cs b/src/extensions/speech_extension/commands/parsers/intent_command_parser.cs
similarity index 96%
rename from src/spx/commands/parsers/intent_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/intent_command_parser.cs
index 4e17e564..92cfd618 100644
--- a/src/spx/commands/parsers/intent_command_parser.cs
+++ b/src/extensions/speech_extension/commands/parsers/intent_command_parser.cs
@@ -1,8 +1,13 @@
-using System.Linq;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Linq;
namespace Azure.AI.Details.Common.CLI
{
- class IntentCommandParser : CommandParser
+ public class IntentCommandParser : CommandParser
{
public static bool ParseCommand(INamedValueTokens tokens, ICommandValues values)
{
@@ -14,6 +19,11 @@ public static bool ParseCommandValues(INamedValueTokens tokens, ICommandValues v
return ParseCommandValues("intent", intentCommandParsers, tokens, values);
}
+ public static IEnumerable GetCommandParsers()
+ {
+ return intentCommandParsers;
+ }
+
#region private data
private static INamedValueTokenParser[] intentCommandParsers = {
diff --git a/src/spx/commands/parsers/profile_command_parser.cs b/src/extensions/speech_extension/commands/parsers/profile_command_parser.cs
similarity index 91%
rename from src/spx/commands/parsers/profile_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/profile_command_parser.cs
index c71122b7..5d53c5b5 100644
--- a/src/spx/commands/parsers/profile_command_parser.cs
+++ b/src/extensions/speech_extension/commands/parsers/profile_command_parser.cs
@@ -1,9 +1,14 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System.Collections.Generic;
using System.Linq;
namespace Azure.AI.Details.Common.CLI
{
- class ProfileCommandParser : CommandParser
+ public class ProfileCommandParser : CommandParser
{
public static bool ParseCommand(INamedValueTokens tokens, ICommandValues values)
{
@@ -31,10 +36,10 @@ private static readonly (string name, bool valuesRequired)[] _commands = {
"speaker"
};
- private static IEnumerable GetCommandParsers(ICommandValues values)
+ public static IEnumerable GetCommandParsers(ICommandValues values)
{
var commandName = values.GetCommand();
- switch (commandName)
+ switch (commandName.Replace("speech.", ""))
{
case "profile.list":
case "profile.create":
diff --git a/src/spx/commands/parsers/recognize_command_parser.cs b/src/extensions/speech_extension/commands/parsers/recognize_command_parser.cs
similarity index 96%
rename from src/spx/commands/parsers/recognize_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/recognize_command_parser.cs
index 1fdce8dc..31f4e727 100644
--- a/src/spx/commands/parsers/recognize_command_parser.cs
+++ b/src/extensions/speech_extension/commands/parsers/recognize_command_parser.cs
@@ -1,8 +1,13 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System.Linq;
namespace Azure.AI.Details.Common.CLI
{
- class RecognizeCommandParser : CommandParser
+ public class RecognizeCommandParser : CommandParser
{
public static bool ParseCommand(INamedValueTokens tokens, ICommandValues values)
{
@@ -14,6 +19,11 @@ public static bool ParseCommandValues(INamedValueTokens tokens, ICommandValues v
return ParseCommandValues("recognize", recognizeCommandParsers, tokens, values);
}
+ public static IEnumerable GetCommandParsers()
+ {
+ return recognizeCommandParsers;
+ }
+
#region private data
private static INamedValueTokenParser[] recognizeCommandParsers = {
diff --git a/src/spx/commands/parsers/synthesize_command_parser.cs b/src/extensions/speech_extension/commands/parsers/synthesize_command_parser.cs
similarity index 96%
rename from src/spx/commands/parsers/synthesize_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/synthesize_command_parser.cs
index 3beb69f8..83571ba5 100644
--- a/src/spx/commands/parsers/synthesize_command_parser.cs
+++ b/src/extensions/speech_extension/commands/parsers/synthesize_command_parser.cs
@@ -1,8 +1,13 @@
-using System.Linq;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Linq;
namespace Azure.AI.Details.Common.CLI
{
- class SynthesizeCommandParser : CommandParser
+ public class SynthesizeCommandParser : CommandParser
{
public static bool ParseCommand(INamedValueTokens tokens, ICommandValues values)
{
@@ -14,6 +19,11 @@ public static bool ParseCommandValues(INamedValueTokens tokens, ICommandValues v
return ParseCommandValues("synthesize", synthesizeCommandParsers, tokens, values);
}
+ public static IEnumerable GetCommandParsers()
+ {
+ return synthesizeCommandParsers;
+ }
+
#region private data
private static INamedValueTokenParser[] synthesizeCommandParsers = {
diff --git a/src/spx/commands/parsers/transcribe_conversation_command_parser.cs b/src/extensions/speech_extension/commands/parsers/transcribe_conversation_command_parser.cs
similarity index 98%
rename from src/spx/commands/parsers/transcribe_conversation_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/transcribe_conversation_command_parser.cs
index 61d27f43..6d35d648 100644
--- a/src/spx/commands/parsers/transcribe_conversation_command_parser.cs
+++ b/src/extensions/speech_extension/commands/parsers/transcribe_conversation_command_parser.cs
@@ -1,4 +1,9 @@
-using System.Linq;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Linq;
namespace Azure.AI.Details.Common.CLI
{
diff --git a/src/spx/commands/parsers/translate_command_parser.cs b/src/extensions/speech_extension/commands/parsers/translate_command_parser.cs
similarity index 97%
rename from src/spx/commands/parsers/translate_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/translate_command_parser.cs
index f8966f33..2a3fa932 100644
--- a/src/spx/commands/parsers/translate_command_parser.cs
+++ b/src/extensions/speech_extension/commands/parsers/translate_command_parser.cs
@@ -1,8 +1,13 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System.Linq;
namespace Azure.AI.Details.Common.CLI
{
- class TranslateCommandParser : CommandParser
+ public class TranslateCommandParser : CommandParser
{
public static bool ParseCommand(INamedValueTokens tokens, ICommandValues values)
{
@@ -14,6 +19,11 @@ public static bool ParseCommandValues(INamedValueTokens tokens, ICommandValues v
return ParseCommandValues("translate", translateCommandParsers, tokens, values);
}
+ public static IEnumerable GetCommandParsers()
+ {
+ return translateCommandParsers;
+ }
+
#region private data
private static INamedValueTokenParser[] translateCommandParsers = {
diff --git a/src/spx/commands/parsers/webjob_command_parser.cs b/src/extensions/speech_extension/commands/parsers/webjob_command_parser.cs
similarity index 100%
rename from src/spx/commands/parsers/webjob_command_parser.cs
rename to src/extensions/speech_extension/commands/parsers/webjob_command_parser.cs
diff --git a/src/spx/commands/profile_command.cs b/src/extensions/speech_extension/commands/profile_command.cs
similarity index 99%
rename from src/spx/commands/profile_command.cs
rename to src/extensions/speech_extension/commands/profile_command.cs
index 07df6c1b..2da2a204 100644
--- a/src/spx/commands/profile_command.cs
+++ b/src/extensions/speech_extension/commands/profile_command.cs
@@ -21,14 +21,14 @@ namespace Azure.AI.Details.Common.CLI
{
public class ProfileCommand : Command
{
- internal ProfileCommand(ICommandValues values)
+ public ProfileCommand(ICommandValues values)
{
_values = values.ReplaceValues();
_quiet = _values.GetOrDefault("x.quiet", false);
_verbose = _values.GetOrDefault("x.verbose", true);
}
- internal bool RunCommand()
+ public bool RunCommand()
{
try
{
@@ -53,7 +53,7 @@ private void DoCommand(string command)
{
CheckPath();
- switch (command)
+ switch (command.Replace("speech.", ""))
{
case "profile.list": DoList(); break;
case "profile.create": DoCreateProfile(); break;
@@ -95,7 +95,7 @@ private void WriteJsonToFile(string json)
saveAs = FileHelpers.GetOutputDataFileName(saveAs, _values);
var saveMessage = $"Saving as {saveAs} ...";
if (!_quiet) Console.WriteLine(saveMessage);
- FileHelpers.WriteAllText(saveAs, json, new UTF8Encoding(false))
+ FileHelpers.WriteAllText(saveAs, json, new UTF8Encoding(false));
if (!_quiet) Console.WriteLine($"{saveMessage} Done!\n");
}
}
diff --git a/src/spx/commands/recognize_command.cs b/src/extensions/speech_extension/commands/recognize_command.cs
similarity index 99%
rename from src/spx/commands/recognize_command.cs
rename to src/extensions/speech_extension/commands/recognize_command.cs
index c79eea7e..73897357 100644
--- a/src/spx/commands/recognize_command.cs
+++ b/src/extensions/speech_extension/commands/recognize_command.cs
@@ -21,12 +21,12 @@ namespace Azure.AI.Details.Common.CLI
{
public class RecognizeCommand : Command
{
- internal RecognizeCommand(ICommandValues values)
+ public RecognizeCommand(ICommandValues values)
{
_values = values.ReplaceValues();
}
- internal bool RunCommand()
+ public bool RunCommand()
{
Recognize(_values["recognize.method"]);
return _values.GetOrDefault("passed", true);
diff --git a/src/spx/commands/synthesize_command.cs b/src/extensions/speech_extension/commands/synthesize_command.cs
similarity index 95%
rename from src/spx/commands/synthesize_command.cs
rename to src/extensions/speech_extension/commands/synthesize_command.cs
index 75fe1db2..7df913ee 100644
--- a/src/spx/commands/synthesize_command.cs
+++ b/src/extensions/speech_extension/commands/synthesize_command.cs
@@ -21,12 +21,12 @@ namespace Azure.AI.Details.Common.CLI
{
public class SynthesizeCommand : Command
{
- internal SynthesizeCommand(ICommandValues values)
+ public SynthesizeCommand(ICommandValues values)
{
_values = values.ReplaceValues();
}
- internal bool RunCommand()
+ public bool RunCommand()
{
var listVoices = _values.GetOrDefault("synthesizer.list.voices", false);
if (listVoices) return DoListVoices();
@@ -110,6 +110,11 @@ private void SynthesizeInteractive(bool repeatedly = false)
synthesizer.Synthesizing += Synthesizing;
synthesizer.SynthesisCompleted += SynthesisCompleted;
synthesizer.SynthesisCanceled += SynthesisCanceled;
+ var wordboundary = _values["config.metadata.wordBoundaryEnabled"];
+ if (!string.IsNullOrWhiteSpace(wordboundary) && wordboundary == "true")
+ {
+ synthesizer.WordBoundary += SynthesisWordBoundary;
+ }
while (true)
{
@@ -151,6 +156,11 @@ private void SynthesizeText(string text)
synthesizer.Synthesizing += Synthesizing;
synthesizer.SynthesisCompleted += SynthesisCompleted;
synthesizer.SynthesisCanceled += SynthesisCanceled;
+ var wordboundary = _values["config.metadata.wordBoundaryEnabled"];
+ if (!string.IsNullOrWhiteSpace(wordboundary) && wordboundary == "true")
+ {
+ synthesizer.WordBoundary += SynthesisWordBoundary;
+ }
var task = synthesizer.SpeakTextAsync(text);
WaitForStopOrCancel(synthesizer, task);
@@ -187,6 +197,11 @@ private void SynthesizeSsml(string ssml)
synthesizer.Synthesizing += Synthesizing;
synthesizer.SynthesisCompleted += SynthesisCompleted;
synthesizer.SynthesisCanceled += SynthesisCanceled;
+ var wordboundary = _values["config.metadata.wordBoundaryEnabled"];
+ if (!string.IsNullOrWhiteSpace(wordboundary) && wordboundary == "true")
+ {
+ synthesizer.WordBoundary += SynthesisWordBoundary;
+ }
var task = synthesizer.SpeakSsmlAsync(ssml);
WaitForStopOrCancel(synthesizer, task);
@@ -521,6 +536,12 @@ private void SynthesisCanceled(object sender, SpeechSynthesisEventArgs e)
_canceledEvent.Set();
}
+ private void SynthesisWordBoundary(object sender, SpeechSynthesisWordBoundaryEventArgs e)
+ {
+ _display.DisplaySynthesisWordBoundary(e);
+ _output.SynthesisWordBoundary(e);
+ }
+
private void WaitForStopOrCancel(SpeechSynthesizer synthesizer, Task task)
{
var interval = 100;
diff --git a/src/spx/commands/translate_command.cs b/src/extensions/speech_extension/commands/translate_command.cs
similarity index 99%
rename from src/spx/commands/translate_command.cs
rename to src/extensions/speech_extension/commands/translate_command.cs
index c06b893f..badcee67 100644
--- a/src/spx/commands/translate_command.cs
+++ b/src/extensions/speech_extension/commands/translate_command.cs
@@ -19,12 +19,12 @@ namespace Azure.AI.Details.Common.CLI
{
public class TranslateCommand : Command
{
- internal TranslateCommand(ICommandValues values)
+ public TranslateCommand(ICommandValues values)
{
_values = values.ReplaceValues();
}
- internal bool RunCommand()
+ public bool RunCommand()
{
Recognize(_values["recognize.method"]);
return _values.GetOrDefault("passed", true);
diff --git a/src/spx/commands/webjob_command.cs b/src/extensions/speech_extension/commands/webjob_command.cs
similarity index 100%
rename from src/spx/commands/webjob_command.cs
rename to src/extensions/speech_extension/commands/webjob_command.cs
diff --git a/src/spx/helpers/audio_helpers.cs b/src/extensions/speech_extension/helpers/audio_helpers.cs
similarity index 100%
rename from src/spx/helpers/audio_helpers.cs
rename to src/extensions/speech_extension/helpers/audio_helpers.cs
diff --git a/src/spx/helpers/caption_helper.cs b/src/extensions/speech_extension/helpers/caption_helper.cs
similarity index 100%
rename from src/spx/helpers/caption_helper.cs
rename to src/extensions/speech_extension/helpers/caption_helper.cs
diff --git a/src/spx/helpers/config_helpers.cs b/src/extensions/speech_extension/helpers/config_helpers.cs
similarity index 100%
rename from src/spx/helpers/config_helpers.cs
rename to src/extensions/speech_extension/helpers/config_helpers.cs
diff --git a/src/spx/helpers/connection_helpers.cs b/src/extensions/speech_extension/helpers/connection_helpers.cs
similarity index 93%
rename from src/spx/helpers/connection_helpers.cs
rename to src/extensions/speech_extension/helpers/connection_helpers.cs
index c556b09c..a2f9d467 100644
--- a/src/spx/helpers/connection_helpers.cs
+++ b/src/extensions/speech_extension/helpers/connection_helpers.cs
@@ -1,4 +1,9 @@
-using System.Linq;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Linq;
using Microsoft.CognitiveServices.Speech;
using Newtonsoft.Json.Linq;
diff --git a/src/spx/helpers/display_helpers.cs b/src/extensions/speech_extension/helpers/display_helpers.cs
similarity index 85%
rename from src/spx/helpers/display_helpers.cs
rename to src/extensions/speech_extension/helpers/display_helpers.cs
index 9ea2d9eb..9be5dc97 100644
--- a/src/spx/helpers/display_helpers.cs
+++ b/src/extensions/speech_extension/helpers/display_helpers.cs
@@ -155,13 +155,19 @@ public void DisplayCanceled(IntentRecognitionCanceledEventArgs e)
Console.WriteLine();
}
+ public void DisplayTranscribing(MeetingTranscriptionEventArgs e)
+ {
+ if (_quiet) return;
+ Console.WriteLine($"TRANSCRIBING: {e.Result.Text}");
+ }
+
public void DisplayTranscribing(ConversationTranscriptionEventArgs e)
{
if (_quiet) return;
Console.WriteLine($"TRANSCRIBING: {e.Result.Text}");
}
- public void DisplayTranscribed(ConversationTranscriptionEventArgs e)
+ public void DisplayTranscribed(MeetingTranscriptionEventArgs e)
{
if (_quiet) return;
@@ -178,6 +184,44 @@ public void DisplayTranscribed(ConversationTranscriptionEventArgs e)
}
}
+ public void DisplayTranscribed(ConversationTranscriptionEventArgs e)
+ {
+ if (_quiet) return;
+
+ var result = e.Result;
+ if (result.Reason == ResultReason.RecognizedSpeech && result.Text.Length != 0)
+ {
+ Console.WriteLine($"TRANSCRIBED: {result.Text} (SpeakerId={result.SpeakerId})");
+ Console.WriteLine();
+ }
+ else if (result.Reason == ResultReason.NoMatch && _verbose)
+ {
+ Console.WriteLine($"NOMATCH: Speech could not be recognized.");
+ Console.WriteLine();
+ }
+ }
+
+ public void DisplayCanceled(MeetingTranscriptionCanceledEventArgs e)
+ {
+ if (_quiet) return;
+
+ var cancellation = CancellationDetails.FromResult(e.Result);
+
+ var normal = cancellation.Reason == CancellationReason.EndOfStream;
+ if (normal && !_verbose) return;
+
+ Console.WriteLine($"CANCELED: Reason={cancellation.Reason}");
+
+ if (cancellation.Reason == CancellationReason.Error)
+ {
+ Console.WriteLine($"CANCELED: ErrorCode={cancellation.ErrorCode}");
+ Console.WriteLine($"CANCELED: ErrorDetails={cancellation.ErrorDetails}");
+ Console.WriteLine($"CANCELED: Did you update the subscription info?");
+ }
+
+ Console.WriteLine();
+ }
+
public void DisplayCanceled(ConversationTranscriptionCanceledEventArgs e)
{
if (_quiet) return;
@@ -312,6 +356,13 @@ internal void DisplaySynthesisCanceled(SpeechSynthesisEventArgs e)
Console.WriteLine();
}
+ internal void DisplaySynthesisWordBoundary(SpeechSynthesisWordBoundaryEventArgs e)
+ {
+ if (_quiet) return;
+
+ Console.WriteLine($"METADATA: wordboundary=[ type: {e.BoundaryType}, length: {e.WordLength}, text: {e.Text}, offset: {e.AudioOffset}, duration: {e.Duration} ]");
+ }
+
private ICommandValues _values;
private bool _quiet;
private bool _verbose;
diff --git a/src/spx/helpers/event_logger_helpers.cs b/src/extensions/speech_extension/helpers/event_logger_helpers.cs
similarity index 100%
rename from src/spx/helpers/event_logger_helpers.cs
rename to src/extensions/speech_extension/helpers/event_logger_helpers.cs
diff --git a/src/spx/helpers/output_helper.cs b/src/extensions/speech_extension/helpers/output_helper.cs
similarity index 87%
rename from src/spx/helpers/output_helper.cs
rename to src/extensions/speech_extension/helpers/output_helper.cs
index 0066cf62..a221db6e 100644
--- a/src/spx/helpers/output_helper.cs
+++ b/src/extensions/speech_extension/helpers/output_helper.cs
@@ -157,6 +157,27 @@ public void Canceled(IntentRecognitionCanceledEventArgs e)
_lock.ExitReaderLock();
}
+ public void Transcribing(MeetingTranscriptionEventArgs e)
+ {
+ _lock.EnterReaderLock();
+ OutputTranscribing(e);
+ _lock.ExitReaderLock();
+ }
+
+ public void Transcribed(MeetingTranscriptionEventArgs e)
+ {
+ _lock.EnterReaderLock();
+ OutputTranscribed(e);
+ _lock.ExitReaderLock();
+ }
+
+ public void Canceled(MeetingTranscriptionCanceledEventArgs e)
+ {
+ _lock.EnterReaderLock();
+ OutputCanceled(e);
+ _lock.ExitReaderLock();
+ }
+
public void Transcribing(ConversationTranscriptionEventArgs e)
{
_lock.EnterReaderLock();
@@ -227,6 +248,13 @@ public void SynthesisCanceled(SpeechSynthesisEventArgs e)
_lock.ExitReaderLock();
}
+ public void SynthesisWordBoundary(SpeechSynthesisWordBoundaryEventArgs e)
+ {
+ _lock.EnterReaderLock();
+ OutputWordBoundary(e);
+ _lock.ExitReaderLock();
+ }
+
public void EnsureCachePropertyCollection(string name, PropertyCollection properties)
{
EnsureInitPropertyCollectionCache();
@@ -274,6 +302,11 @@ private void EnsureCacheOutputResult(IntentRecognitionResult result)
if (ShouldCacheOutputResult()) CacheOutputResult(result);
}
+ private void EnsureCacheOutputResult(MeetingTranscriptionResult result)
+ {
+ if (ShouldCacheOutputResult()) CacheOutputResult(result);
+ }
+
private void EnsureCacheOutputResult(ConversationTranscriptionResult result)
{
if (ShouldCacheOutputResult()) CacheOutputResult(result);
@@ -284,6 +317,11 @@ private void EnsureCacheOutputResult(TranslationRecognitionResult result)
if (ShouldCacheOutputResult()) CacheOutputResult(result);
}
+ private void EnsureCacheOutputResult(SpeechSynthesisResult result)
+ {
+ if (ShouldCacheOutputResult()) CacheOutputResult(result);
+ }
+
private void CacheProperty(string name, string value)
{
EnsureInitOutputResultCache();
@@ -302,6 +340,12 @@ private void CacheOutputResult(IntentRecognitionResult result)
_outputResultCache.Add(result);
}
+ private void CacheOutputResult(MeetingTranscriptionResult result)
+ {
+ EnsureInitOutputResultCache();
+ _outputResultCache.Add(result);
+ }
+
private void CacheOutputResult(ConversationTranscriptionResult result)
{
EnsureInitOutputResultCache();
@@ -314,6 +358,12 @@ private void CacheOutputResult(TranslationRecognitionResult result)
_outputResultCache.Add(result);
}
+ private void CacheOutputResult(SpeechSynthesisResult result)
+ {
+ EnsureInitOutputResultCache();
+ _outputResultCache.Add(result);
+ }
+
private void EnsureInitOutputResultCache()
{
if (_outputResultCache == null)
@@ -513,15 +563,18 @@ private string GetResultText(object result, string kind)
{
var resultSpeech = result as SpeechRecognitionResult;
var resultTranslation = result as TranslationRecognitionResult;
- var resultTranscription = result as ConversationTranscriptionResult;
+ var resultConversationTranscription = result as ConversationTranscriptionResult;
+ var resultMeetingTranscription = result as MeetingTranscriptionResult;
var text = resultSpeech != null
? resultSpeech.Properties.GetProperty(kind)
: resultTranslation != null
? resultTranslation.Properties.GetProperty(kind)
- : resultTranscription != null
- ? resultTranscription.Properties.GetProperty(kind)
- : "";
+ : resultConversationTranscription != null
+ ? resultConversationTranscription.Properties.GetProperty(kind)
+ : resultMeetingTranscription != null
+ ? resultMeetingTranscription.Properties.GetProperty(kind)
+ : "";
return !string.IsNullOrEmpty(text)
? text
@@ -529,9 +582,11 @@ private string GetResultText(object result, string kind)
? resultSpeech.Text
: resultTranslation != null
? resultTranslation.Text
- : resultTranscription != null
- ? resultTranscription.Text
- : "";
+ : resultConversationTranscription != null
+ ? resultConversationTranscription.Text
+ : resultMeetingTranscription != null
+ ? resultMeetingTranscription.Text
+ : "";
}
private JArray GetBatchAudioFileSegments()
@@ -552,15 +607,18 @@ private JObject GetBatchAudioFileSegment(object result)
var resultSpeech = result as SpeechRecognitionResult;
var resultTranslation = result as TranslationRecognitionResult;
- var resultTranscription = result as ConversationTranscriptionResult;
+ var resultConversationTranscription = result as ConversationTranscriptionResult;
+ var resultMeetingTranscription = result as MeetingTranscriptionResult;
return resultSpeech != null
? GetBatchAudioFileSegment(resultSpeech)
: resultTranslation != null
? GetBatchAudioFileSegment(resultTranslation)
- : resultTranscription != null
- ? GetBatchAudioFileSegment(resultTranscription)
- : null;
+ : resultConversationTranscription != null
+ ? GetBatchAudioFileSegment(resultConversationTranscription)
+ : resultMeetingTranscription != null
+ ? GetBatchAudioFileSegment(resultMeetingTranscription)
+ : null;
}
private JObject GetBatchAudioFileSegment(SpeechRecognitionResult result)
@@ -656,6 +714,37 @@ private JObject GetBatchAudioFileSegment(ConversationTranscriptionResult result)
return segment;
}
+ private JObject GetBatchAudioFileSegment(MeetingTranscriptionResult result)
+ {
+ var segment = new JObject();
+
+ if (result.Reason == ResultReason.Canceled)
+ {
+ var cancelDetails = CancellationDetails.FromResult(result);
+ if (cancelDetails.Reason == CancellationReason.EndOfStream) return null;
+
+ segment.Add("ErrorDetails", cancelDetails.ErrorDetails);
+ segment.Add("ErrorCode", cancelDetails.ErrorCode.ToString());
+ return segment;
+ }
+
+ var json = result.Properties.GetProperty(PropertyId.SpeechServiceResponse_JsonResult);
+ var parsed = !string.IsNullOrEmpty(json) ? JToken.Parse(json) : null;
+
+ segment.Add("RecognitionStatus", parsed["RecognitionStatus"] ?? result.Reason.ToString());
+
+ segment.Add("ChannelNumber", "0");
+ segment.Add("SpeakerId", null);
+ segment.Add("Offset", result.OffsetInTicks);
+ segment.Add("Duration", (long)(result.Duration.TotalMilliseconds * 10000));
+ segment.Add("OffsetInSeconds", Math.Round(1.0 * result.OffsetInTicks / 10000 / 1000, 2));
+ segment.Add("DurationInSeconds", Math.Round(result.Duration.TotalSeconds, 2));
+
+ segment.Add("NBest", parsed["NBest"] ?? new JArray());
+
+ return segment;
+ }
+
private double GetBatchAudioLengthInSeconds()
{
double length = 0;
@@ -673,22 +762,27 @@ private void GetResultOffsetAndDuration(object result, out double offset, out do
{
var resultSpeech = result as SpeechRecognitionResult;
var resultTranslation = result as TranslationRecognitionResult;
- var resultTranscription = result as ConversationTranscriptionResult;
+ var resultConversationTranscription = result as ConversationTranscriptionResult;
+ var resultMeetingTranscription = result as MeetingTranscriptionResult;
offset = (resultSpeech != null
? resultSpeech.OffsetInTicks
: resultTranslation != null
? resultTranslation.OffsetInTicks
- : resultTranscription != null
- ? resultTranscription.OffsetInTicks
- : 0) / 10000 / 1000;
+ : resultConversationTranscription != null
+ ? resultConversationTranscription.OffsetInTicks
+ : resultMeetingTranscription != null
+ ? resultMeetingTranscription.OffsetInTicks
+ : 0) / 10000 / 1000;
duration = (resultSpeech != null
? resultSpeech.Duration.TotalSeconds
: resultTranslation != null
? resultTranslation.Duration.TotalSeconds
- : resultTranscription != null
- ? resultTranscription.Duration.TotalSeconds
- : 0);
+ : resultConversationTranscription != null
+ ? resultConversationTranscription.Duration.TotalSeconds
+ : resultMeetingTranscription != null
+ ? resultMeetingTranscription.Duration.TotalSeconds
+ : 0);
}
private bool ShouldOutputAll(string name)
@@ -1935,6 +2029,181 @@ private void OutputResult(string namePrefix, ConversationTranscriptionResult res
EnsureOutputEachProperty(namePrefix, result.Properties);
}
+ private void OutputTranscribing(MeetingTranscriptionEventArgs e)
+ {
+ if (!_outputAll && !_outputEach) return;
+
+ var sessionid = e.SessionId;
+ var timestamp = this.CreateTimestamp();
+ EnsureCacheAll("recognizer.recognizing.sessionid", sessionid);
+ EnsureOutputEach("recognizer.recognizing.sessionid", sessionid);
+ EnsureCacheAll("recognizer.event.sessionid", sessionid);
+ EnsureOutputEach("recognizer.event.sessionid", sessionid);
+ EnsureCacheAll("event.sessionid", sessionid);
+ EnsureOutputEach("event.sessionid", sessionid);
+
+ EnsureCacheAll("recognizer.recognizing.timestamp", timestamp);
+ EnsureOutputEach("recognizer.recognizing.timestamp", timestamp);
+ EnsureCacheAll("recognizer.event.timestamp", timestamp);
+ EnsureOutputEach("recognizer.event.timestamp", timestamp);
+ EnsureCacheAll("event.timestamp", timestamp);
+ EnsureOutputEach("event.timestamp", timestamp);
+
+ EnsureOutputRecognizerProperties("recognizer.recognizing.recognizer");
+
+ var result = e.Result;
+ OutputResult("recognizer.recognizing.result", result, '\n');
+ OutputResult("result", result, '\n'); // Don't include this in global requests for results
+
+ var output = $"TRANSCRIBING";
+ EnsureCacheAll("recognizer.recognizing.events", output);
+ EnsureOutputEach("recognizer.recognizing.event", output);
+ EnsureCacheAll("recognizer.events", output);
+ EnsureOutputEach("recognizer.event", output);
+ EnsureCacheAll("events", output);
+ EnsureOutputEach("event", output);
+
+ FlushOutputEachCacheStage1();
+ }
+
+ private void OutputTranscribed(MeetingTranscriptionEventArgs e)
+ {
+ EnsureCacheOutputResult(e.Result);
+
+ if (!_outputAll && !_outputEach) return;
+
+ var sessionid = e.SessionId;
+ var timestamp = this.CreateTimestamp();
+ EnsureCacheAll("recognizer.recognized.sessionid", sessionid);
+ EnsureOutputEach("recognizer.recognized.sessionid", sessionid);
+ EnsureCacheAll("recognizer.event.sessionid", sessionid);
+ EnsureOutputEach("recognizer.event.sessionid", sessionid);
+ EnsureCacheAll("event.sessionid", sessionid);
+ EnsureOutputEach("event.sessionid", sessionid);
+
+ EnsureCacheAll("recognizer.recognized.timestamp", timestamp);
+ EnsureOutputEach("recognizer.recognized.timestamp", timestamp);
+ EnsureCacheAll("recognizer.event.timestamp", timestamp);
+ EnsureOutputEach("recognizer.event.timestamp", timestamp);
+ EnsureCacheAll("event.timestamp", timestamp);
+ EnsureOutputEach("event.timestamp", timestamp);
+
+ EnsureOutputRecognizerProperties("recognizer.recognized.recognizer");
+
+ var result = e.Result;
+ OutputResult("recognizer.recognized.result", result, ' ');
+ OutputResult("result", result, ' ');
+
+ var output = $"TRANSCRIBED";
+ EnsureCacheAll("recognizer.recognized.events", output);
+ EnsureOutputEach("recognizer.recognized.event", output);
+ EnsureCacheAll("recognizer.events", output);
+ EnsureOutputEach("recognizer.event", output);
+ EnsureCacheAll("events", output);
+ EnsureOutputEach("event", output);
+
+ FlushOutputEachCacheStage1();
+ }
+
+ private void OutputCanceled(MeetingTranscriptionCanceledEventArgs e)
+ {
+ EnsureCacheOutputResult(e.Result);
+
+ if (!_outputAll && !_outputEach) return;
+
+ var sessionid = e.SessionId;
+ var timestamp = this.CreateTimestamp();
+ EnsureCacheAll("recognizer.canceled.sessionid", sessionid);
+ EnsureOutputEach("recognizer.canceled.sessionid", sessionid);
+ EnsureCacheAll("recognizer.event.sessionid", sessionid);
+ EnsureOutputEach("recognizer.event.sessionid", sessionid);
+ EnsureCacheAll("event.sessionid", sessionid);
+ EnsureOutputEach("event.sessionid", sessionid);
+
+ EnsureCacheAll("recognizer.canceled.timestamp", timestamp);
+ EnsureOutputEach("recognizer.canceled.timestamp", timestamp);
+ EnsureCacheAll("recognizer.event.timestamp", timestamp);
+ EnsureOutputEach("recognizer.event.timestamp", timestamp);
+ EnsureCacheAll("event.timestamp", timestamp);
+ EnsureOutputEach("event.timestamp", timestamp);
+
+ var reason = e.Reason.ToString();
+ EnsureCacheAll("recognizer.canceled.reason", reason);
+ EnsureOutputEach("recognizer.canceled.reason", reason);
+
+ var code = (e.Reason == CancellationReason.Error) ? e.ErrorCode.ToString() : "0";
+ EnsureCacheAll("recognizer.canceled.error.code", code);
+ EnsureOutputEach("recognizer.canceled.error.code", code);
+ EnsureCacheAll("recognizer.canceled.error", code);
+ EnsureOutputEach("recognizer.canceled.error", code);
+
+ var details = (e.Reason == CancellationReason.Error) ? e.ErrorDetails : "";
+ EnsureCacheAll("recognizer.canceled.error.details", details);
+ EnsureOutputEach("recognizer.canceled.error.details", details);
+ EnsureCacheAll("recognizer.canceled.error", details);
+ EnsureOutputEach("recognizer.canceled.error", details);
+
+ EnsureOutputRecognizerProperties("recognizer.canceled.recognizer");
+
+ var result = e.Result;
+ OutputResult("recognizer.canceled.result", result, ' ');
+ OutputResult("result", result, ' ');
+
+ var output = $"CANCELED";
+ EnsureCacheAll("recognizer.session.events", output);
+ EnsureOutputEach("recognizer.session.event", output);
+ EnsureCacheAll("recognizer.canceled.events", output);
+ EnsureOutputEach("recognizer.canceled.event", output);
+ EnsureCacheAll("recognizer.events", output);
+ EnsureOutputEach("recognizer.event", output);
+ EnsureCacheAll("events", output);
+ EnsureOutputEach("event", output);
+
+ FlushOutputEachCacheStage1();
+ }
+
+ private void OutputResult(string namePrefix, MeetingTranscriptionResult result, char textSeparator)
+ {
+ var id = result.ResultId;
+ EnsureCacheAll(namePrefix + ".resultid", id);
+ EnsureOutputEach(namePrefix + ".resultid", id);
+
+ var reason = result.Reason.ToString();
+ EnsureCacheAll(namePrefix + ".reason", reason);
+ EnsureOutputEach(namePrefix + ".reason", reason);
+
+ var offset = result.OffsetInTicks.ToString();
+ EnsureCacheAll(namePrefix + ".offset", offset);
+ EnsureOutputEach(namePrefix + ".offset", offset);
+
+ var duration = (result.Duration.TotalMilliseconds * 10000).ToString();
+ EnsureCacheAll(namePrefix + ".duration", duration);
+ EnsureOutputEach(namePrefix + ".duration", duration);
+
+ var text = result.Text;
+ EnsureCacheAll(textSeparator, namePrefix + ".text", text);
+ EnsureOutputEach(namePrefix + ".text", text);
+
+ var itn = result.Properties.GetProperty("ITN");
+ EnsureCacheAll(textSeparator, namePrefix + ".itn.text", itn);
+ EnsureOutputEach(namePrefix + ".itn.text", itn);
+
+ var lexical = result.Properties.GetProperty("Lexical");
+ EnsureCacheAll(textSeparator, namePrefix + ".lexical.text", lexical);
+ EnsureOutputEach(namePrefix + ".lexical.text", lexical);
+
+ var latency = result.Properties.GetProperty(PropertyId.SpeechServiceResponse_RecognitionLatencyMs);
+ EnsureCacheAll(namePrefix + ".latency", latency);
+ EnsureOutputEach(namePrefix + ".latency", latency);
+
+ var json = result.Properties.GetProperty(PropertyId.SpeechServiceResponse_JsonResult);
+ EnsureCacheAll(namePrefix + ".json", json);
+ EnsureOutputEach(namePrefix + ".json", json);
+
+ EnsureOutputAllProperties(namePrefix, result.Properties);
+ EnsureOutputEachProperty(namePrefix, result.Properties);
+ }
+
private void OutputRecognizing(TranslationRecognitionEventArgs e)
{
EnsureCacheOutputResult(e.Result);
@@ -2128,6 +2397,8 @@ private void OutputResult(string namePrefix, TranslationRecognitionResult result
private void OutputSynthesisStarted(SpeechSynthesisEventArgs e)
{
+ EnsureCacheOutputResult(e.Result);
+
if (!_outputAll && !_outputEach) return;
var output = $"SYNTHESIS STARTED";
@@ -2147,6 +2418,8 @@ private void OutputSynthesisStarted(SpeechSynthesisEventArgs e)
private void OutputSynthesizing(SpeechSynthesisEventArgs e)
{
+ EnsureCacheOutputResult(e.Result);
+
if (!_outputAll && !_outputEach) return;
var output = $"SYNTHESIZING";
@@ -2166,6 +2439,8 @@ private void OutputSynthesizing(SpeechSynthesisEventArgs e)
private void OutputSynthesisCompleted(SpeechSynthesisEventArgs e)
{
+ EnsureCacheOutputResult(e.Result);
+
if (!_outputAll && !_outputEach) return;
var output = $"SYNTHESIS COMPLETED";
@@ -2185,6 +2460,8 @@ private void OutputSynthesisCompleted(SpeechSynthesisEventArgs e)
private void OutputSynthesisCanceled(SpeechSynthesisEventArgs e)
{
+ EnsureCacheOutputResult(e.Result);
+
if (!_outputAll && !_outputEach) return;
var cancelDetails = SpeechSynthesisCancellationDetails.FromResult(e.Result);
@@ -2220,6 +2497,47 @@ private void OutputSynthesisCanceled(SpeechSynthesisEventArgs e)
FlushOutputEachCacheStage1();
}
+ private void OutputWordBoundary(SpeechSynthesisWordBoundaryEventArgs e)
+ {
+ if (!_outputAll && !_outputEach) return;
+
+ var output = $"WORD BOUNDARY";
+ const string synthesizer = "synthesizer";
+ const string wordBoundaryNamePrefix = synthesizer + ".wordboundary";
+ EnsureCacheAll(wordBoundaryNamePrefix + ".events", output);
+ EnsureOutputEach(wordBoundaryNamePrefix + ".event", output);
+ EnsureCacheAll(synthesizer + ".events", output);
+ EnsureOutputEach(synthesizer + ".event", output);
+
+ const string wordBoundaryResultNamePrefix = wordBoundaryNamePrefix + ".result";
+
+ //// These are almost always the same value and don't really lend much value. If you find that you
+ //// need them for some test scenario, uncomment this
+ ////EnsureCacheAll(wordBoundaryResultNamePrefix + ".resultid", e.ResultId);
+ ////EnsureOutputEach(wordBoundaryResultNamePrefix + ".resultid", e.ResultId);
+
+ var wordLength = e.WordLength.ToString();
+ EnsureCacheAll(wordBoundaryResultNamePrefix + ".wordlength", wordLength);
+ EnsureOutputEach(wordBoundaryResultNamePrefix + ".wordlength", wordLength);
+
+ EnsureCacheAll(wordBoundaryResultNamePrefix + ".text", e.Text);
+ EnsureOutputEach(wordBoundaryResultNamePrefix + ".text", e.Text);
+
+ var boundaryType = e.BoundaryType.ToString();
+ EnsureCacheAll(wordBoundaryResultNamePrefix + ".type", boundaryType);
+ EnsureOutputEach(wordBoundaryResultNamePrefix + ".type", boundaryType);
+
+ var boundaryOffset = e.AudioOffset.ToString();
+ EnsureCacheAll(wordBoundaryResultNamePrefix + ".offset", boundaryOffset);
+ EnsureOutputEach(wordBoundaryResultNamePrefix + ".offset", boundaryOffset);
+
+ var boundaryDuration = e.Duration.ToString();
+ EnsureCacheAll(wordBoundaryResultNamePrefix + ".duration", boundaryDuration);
+ EnsureOutputEach(wordBoundaryResultNamePrefix + ".duration", boundaryDuration);
+
+ FlushOutputEachCacheStage1();
+ }
+
private void OutputResult(string namePrefix, SpeechSynthesisResult result, char textSeparator)
{
var id = result.ResultId;
diff --git a/src/spx/helpers/wer_helpers.cs b/src/extensions/speech_extension/helpers/wer_helpers.cs
similarity index 100%
rename from src/spx/helpers/wer_helpers.cs
rename to src/extensions/speech_extension/helpers/wer_helpers.cs
diff --git a/src/extensions/speech_extension/speech-extension.csproj b/src/extensions/speech_extension/speech-extension.csproj
new file mode 100644
index 00000000..219dbcf7
--- /dev/null
+++ b/src/extensions/speech_extension/speech-extension.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Azure.AI.CLI.Extensions.Speech
+ net8.0
+ Azure.AI.Details.Common.CLI.Extensions.Speech
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/extensions/template_extension/TemplateFactory.cs b/src/extensions/template_extension/TemplateFactory.cs
index 60cbee84..0465d6ef 100644
--- a/src/extensions/template_extension/TemplateFactory.cs
+++ b/src/extensions/template_extension/TemplateFactory.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using Newtonsoft.Json.Linq;
using Mono.TextTemplating;
using System.Collections.Generic;
diff --git a/tests/testframework/IYamlTestFrameworkHost.cs b/src/extensions/testframework/IYamlTestFrameworkHost.cs
similarity index 67%
rename from tests/testframework/IYamlTestFrameworkHost.cs
rename to src/extensions/testframework/IYamlTestFrameworkHost.cs
index 383b278d..02bef78a 100644
--- a/tests/testframework/IYamlTestFrameworkHost.cs
+++ b/src/extensions/testframework/IYamlTestFrameworkHost.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
namespace Azure.AI.Details.Common.CLI.TestFramework
diff --git a/src/extensions/testframework/IYamlTestFrameworkLogger.cs b/src/extensions/testframework/IYamlTestFrameworkLogger.cs
new file mode 100644
index 00000000..b64c6fb0
--- /dev/null
+++ b/src/extensions/testframework/IYamlTestFrameworkLogger.cs
@@ -0,0 +1,15 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+namespace Azure.AI.Details.Common.CLI.TestFramework
+{
+ public interface IYamlTestFrameworkLogger
+ {
+ void LogVerbose(string text);
+ void LogInfo(string text);
+ void LogWarning(string text);
+ void LogError(string text);
+ }
+}
diff --git a/src/extensions/testframework/JunitXmlTestReporter.cs b/src/extensions/testframework/JunitXmlTestReporter.cs
new file mode 100644
index 00000000..a92ce91e
--- /dev/null
+++ b/src/extensions/testframework/JunitXmlTestReporter.cs
@@ -0,0 +1,88 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System;
+using System.Linq;
+using System.Xml;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+
+namespace Azure.AI.Details.Common.CLI.TestFramework
+{
+ public static class JunitXmlTestReporter
+ {
+ public static string WriteResultsFile(TestRun testRun, string resultsFile = "test-results.xml")
+ {
+ var testCases = testRun.TestCases;
+ var testResults = testRun.TestResults;
+ var startTime = testRun.StartTime;
+ var endTime = testRun.EndTime;
+
+ XmlWriterSettings settings = new XmlWriterSettings();
+ settings.Indent = true;
+ settings.IndentChars = " ";
+ settings.NewLineChars = "\n";
+ settings.NewLineHandling = NewLineHandling.Replace;
+ settings.OmitXmlDeclaration = false;
+
+ var writer = XmlWriter.Create(resultsFile, settings);
+ writer.WriteStartDocument();
+ writer.WriteStartElement("testsuites");
+
+ writer.WriteStartElement("testsuite");
+ writer.WriteAttributeString("name", "TestSuite");
+ writer.WriteAttributeString("tests", testResults.Count.ToString());
+ writer.WriteAttributeString("failures", testResults.Count(r => r.Outcome == TestOutcome.Failed).ToString());
+ writer.WriteAttributeString("errors", "0");
+ writer.WriteAttributeString("time", testResults.Sum(r => r.Duration.TotalSeconds).ToString());
+ writer.WriteAttributeString("timestamp", endTime.ToString("yyyy-MM-ddTHH:mm:ss"));
+
+ foreach (var testResult in testResults)
+ {
+ writer.WriteStartElement("testcase");
+ writer.WriteAttributeString("name", testResult.TestCase.DisplayName);
+ writer.WriteAttributeString("classname", testResult.TestCase.FullyQualifiedName);
+ writer.WriteAttributeString("time", testResult.Duration.TotalSeconds.ToString());
+
+ var stdout = testResult.Messages.FirstOrDefault(x => x.Category == TestResultMessage.StandardOutCategory)?.Text;
+ var stderr = testResult.Messages.FirstOrDefault(x => x.Category == TestResultMessage.StandardErrorCategory)?.Text;
+ var debugTrace = testResult.Messages.FirstOrDefault(x => x.Category == TestResultMessage.DebugTraceCategory)?.Text;
+ var message = testResult.Messages.FirstOrDefault(x => x.Category == TestResultMessage.AdditionalInfoCategory)?.Text;
+
+ writer.WriteStartElement("system-out");
+ writer.WriteRaw(System.Security.SecurityElement
+ .Escape(stdout.Replace("\u001b", string.Empty))
+ .Replace("\r\n", "
\n"));
+ writer.WriteEndElement();
+
+ writer.WriteStartElement("system-err");
+ writer.WriteRaw(System.Security.SecurityElement
+ .Escape(stderr.Replace("\u001b", string.Empty))
+ .Replace("\r\n", "
\n"));
+ writer.WriteEndElement();
+
+ if (testResult.Outcome == TestOutcome.Failed)
+ {
+ writer.WriteStartElement("failure");
+ writer.WriteAttributeString("message", testResult.ErrorMessage);
+ writer.WriteAttributeString("type", "Failure");
+ writer.WriteCData(testResult.ErrorStackTrace);
+ writer.WriteEndElement();
+ }
+
+ writer.WriteEndElement();
+ }
+
+ writer.WriteEndElement();
+ writer.WriteEndElement();
+ writer.WriteEndDocument();
+
+ writer.Close();
+ writer.Dispose();
+
+ return resultsFile;
+ }
+ }
+}
+
diff --git a/src/extensions/testframework/Logger.cs b/src/extensions/testframework/Logger.cs
new file mode 100644
index 00000000..a6cf0c63
--- /dev/null
+++ b/src/extensions/testframework/Logger.cs
@@ -0,0 +1,80 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Azure.AI.Details.Common.CLI.TestFramework
+{
+ public class Logger
+ {
+ public static void Log(IYamlTestFrameworkLogger logger)
+ {
+ Logger.logger = logger;
+ }
+
+ public static void Log(string text)
+ {
+ using (var mutex = new Mutex(false, "Logger Mutex"))
+ {
+ mutex.WaitOne();
+#if DEBUG
+ logger?.LogInfo(text);
+#endif
+ mutex.ReleaseMutex();
+ }
+ }
+
+ public static void LogIf(bool log, string text)
+ {
+ if (log) Log(text);
+ }
+
+ #region log methods
+
+ public static void LogInfo(string text)
+ {
+ using (var mutex = new Mutex(false, "Logger Mutex"))
+ {
+ mutex.WaitOne();
+ logger?.LogInfo(text);
+ mutex.ReleaseMutex();
+ }
+ }
+
+ public static void LogWarning(string text)
+ {
+ using (var mutex = new Mutex(false, "Logger Mutex"))
+ {
+ mutex.WaitOne();
+ logger?.LogWarning(text);
+ mutex.ReleaseMutex();
+ }
+ }
+
+ public static void LogError(string text)
+ {
+ using (var mutex = new Mutex(false, "Logger Mutex"))
+ {
+ mutex.WaitOne();
+ logger?.LogError(text);
+ mutex.ReleaseMutex();
+ }
+ }
+
+ #endregion
+
+ #region private methods and data
+
+ private static IYamlTestFrameworkLogger logger = null;
+
+ #endregion
+ }
+}
diff --git a/tests/testframework/Properties/AssemblyInfo.cs b/src/extensions/testframework/Properties/AssemblyInfo.cs
similarity index 88%
rename from tests/testframework/Properties/AssemblyInfo.cs
rename to src/extensions/testframework/Properties/AssemblyInfo.cs
index c553fe3d..8a91a74b 100644
--- a/tests/testframework/Properties/AssemblyInfo.cs
+++ b/src/extensions/testframework/Properties/AssemblyInfo.cs
@@ -1,4 +1,9 @@
-using System.Reflection;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
diff --git a/src/extensions/testframework/README.md b/src/extensions/testframework/README.md
new file mode 100644
index 00000000..67dae499
--- /dev/null
+++ b/src/extensions/testframework/README.md
@@ -0,0 +1,244 @@
+# `ai test`
+
+`ai test` is a YAML-based test framework/runner that can be used to run tests on any command-line tool or script. It is designed to be simple to use and understand, and to be able to run tests in parallel.
+
+Examples:
+
+```yaml
+- name: Build search index
+ command: ai search index update --files "data/*.md" --index-name myindex
+ expect: |
+ Updating search index 'myindex' ...
+ Updating search index 'myindex' ... Done!
+
+- name: Evaluate chat model
+ command: ai chat evaluate --data-input questions.jsonl
+ expect-gpt: |
+ The GPT coherence score must be at least 4.2
+```
+
+The test case YAML file contains a list of test cases. Each test case is a dictionary with the following keys:
+
+* `name` (required): The name of the test case.
+* `command`, `script`, `bash` (required): The command or script to run.
+- `env` (optional): A dictionary of environment variables to set before running the command or script.
+- `input` (optional): The input to pass to the command or script.
+- `expect` (optional): A list of regular expressions that must be matched in the stdout/stderr output.
+- `expect-gpt` (optional): A string that instructs the LLM (e.g. GPT-4) to decide pass/fail based on stdout/stderr.
+- `not-expect` (optional): A list of regular expressions that must not be matched in the stdout/stderr output.
+- `parallelize` (optional): Whether the test case should run in parallel with other test cases.
+- `skipOnFailure` (optional): Whether the test case should be skipped when it fails.
+- `tag`/`tags` (optional): A list of tags to associate with the test case.
+- `timeout` (optional): The maximum time allowed to execute the test case, in milliseconds.
+- `workingDirectory` (optional): The working directory where the test will be run.
+
+Test cases can be organized into areas, sub-areas, and so on.
+
+```yaml
+- area: Area 1
+ tests:
+
+ - name: Test 1
+ command: echo "Hello, world!"
+
+ - name: Test 2
+ command: echo "Goodbye, world!"
+```
+
+Test cases can also be grouped into classes.
+
+```yaml
+- class: Class 1
+ tests:
+
+ - name: Test 1
+ command: echo "Hello, world!"
+
+ - name: Test 2
+ command: echo "Goodbye, world!"
+```
+
+If no class is specified, the default class is "TestCases".
+
+## `command`, `script`, `bash`
+
+Required.
+
+Represents how the test case will be run.
+
+If the specified command or script returns an error level of non-zero, the test will fail. If it returns zero, it will pass (given that all 'expect' conditions are also met).
+
+Example command:
+
+```yaml
+command: ai chat --interactive
+```
+
+Example for a bash script:
+
+```yaml
+bash: |
+ if [ -f /etc/os-release ]; then
+ python3 script.py
+ else
+ py script.py
+ fi
+```
+
+## `env`
+
+Optional. Inherits from parent.
+
+When present, a dictionary of environment variables to set before running the command or script.
+
+Example:
+
+```yaml
+env:
+ JAVA_HOME: /path/to/java
+```
+
+## `input`
+
+Optional.
+
+When present, will be passed to the command or script as stdin.
+
+Example:
+
+```yaml
+input: |
+ Tell me a joke
+ Tell me another
+ exit
+```
+
+## `expect`
+
+Optional.
+
+Each string (or line in multiline string) is a regular expression that must be matched in the stdout/stderr output.
+
+If any regular expression is not matched, the test will fail. If all expressions are matched, in order, the test will pass.
+
+Example:
+
+```yaml
+expect: |
+ Regex 1
+ Regex 2
+```
+
+## `expect-gpt`
+
+Optional.
+
+Represents instructions given to LLM (e.g. GPT-4) along with stdout/stderr to decide whether the test passes or fails.
+
+Example:
+
+```yaml
+expect-gpt: the output must have exactly two jokes
+```
+
+## `not-expect`
+
+Optional.
+
+When present, each string (or line in multiline string) is a regular expression that must not be matched in the stdout/stderr output.
+
+If any regular expression is matched, the test fails. If none match, the test passes.
+
+Example:
+
+```yaml
+not-expect: |
+ ERROR
+ curseword1
+ curseword2
+```
+
+## `parallelize`
+
+Optional.
+
+When present, specifies if the test cases should run in parallel or not.
+
+By default, it is set to `false` for all tests, except for the first step in a `steps` test sequence.
+
+Example:
+
+```yaml
+parallelize: true
+```
+
+## `skipOnFailure`
+
+Optional.
+
+When present, specifies if the test case should be skipped when it fails.
+
+By default, it is set to `false`.
+
+Example:
+
+```yaml
+skipOnFailure: true
+```
+
+## `tag`/`tags`
+
+Optional. Inherits from parent.
+
+When present, specifies a list of tags to associate with the test case.
+
+Tags accumulate from parent to child, so if a tag is specified in a parent, it will be inherited by all children.
+
+Examples:
+
+```yaml
+tag: skip
+```
+
+```yaml
+tags:
+- slow
+- performance
+- long-running
+```
+
+```yaml
+area: Area 1
+tags: [echo]
+tests:
+
+- name: Test 1
+ command: echo "Hello, world!"
+ tags: [hello]
+
+- name: Test 2
+ command: echo "Goodbye, world!"
+ tags: [bye]
+```
+
+## `timeout`
+
+Optional.
+
+When present, specifies the maximum time allowed to execute the test case, in milliseconds. Defaults to infinite.
+
+Example:
+
+```yaml
+timeout: 3000 # 3 seconds
+```
+
+
+## `workingDirectory`
+
+Optional. Inherits from parent.
+
+When present, specifies an absolute path or relative path where the test will be run.
+
+When specified as a relative path, it will be relative to the working directory of the parent, or if no parent exists, where the test case file is located.
+
diff --git a/tests/testframework/TestResultHelpers.cs b/src/extensions/testframework/TestResultHelpers.cs
similarity index 82%
rename from tests/testframework/TestResultHelpers.cs
rename to src/extensions/testframework/TestResultHelpers.cs
index 2416d953..44f786f3 100644
--- a/tests/testframework/TestResultHelpers.cs
+++ b/src/extensions/testframework/TestResultHelpers.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
diff --git a/src/extensions/testframework/TestRun.cs b/src/extensions/testframework/TestRun.cs
new file mode 100644
index 00000000..feda95fc
--- /dev/null
+++ b/src/extensions/testframework/TestRun.cs
@@ -0,0 +1,81 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+
+namespace Azure.AI.Details.Common.CLI.TestFramework
+{
+ public class TestRun
+ {
+ public void StartTest(TestCase testCase, Guid? guid = null)
+ {
+ _startTime ??= DateTime.Now;
+ _testCases.Add(testCase);
+ SetExecutionId(testCase, guid ?? Guid.NewGuid());
+ }
+
+ public void RecordTest(TestResult testResult)
+ {
+ _testResults.Add(testResult);
+ }
+
+ public void EndTest(TestCase testCase, TestOutcome outcome)
+ {
+ _endTime = DateTime.Now;
+ }
+
+ public void EndRun()
+ {
+ var now = DateTime.Now;
+ _startTime ??= now;
+ _endTime = now;
+ }
+
+ public IList TestCases => _testCases.ToList();
+
+ public IList TestResults => _testResults.ToList();
+
+ public TimeSpan Duration
+ {
+ get
+ {
+ return _startTime != null && _endTime != null
+ ? (_endTime.Value - _startTime.Value)
+ : TimeSpan.Zero;
+ }
+ }
+
+ public DateTime StartTime => _startTime ?? throw new InvalidOperationException("RunStartTime is not set");
+
+ public DateTime EndTime => _endTime ?? throw new InvalidOperationException("RunEndTime is not set");
+
+ public Guid GetExecutionId(TestCase testCase)
+ {
+ lock (_testToExecutionMap)
+ {
+ return _testToExecutionMap[testCase.Id];
+ }
+ }
+
+ private void SetExecutionId(TestCase testCase, Guid guid)
+ {
+ lock (_testToExecutionMap)
+ {
+ _testToExecutionMap[testCase.Id] = guid;
+ }
+ }
+
+ private DateTime? _startTime;
+ private DateTime? _endTime;
+
+ private List _testCases = new List();
+ private Dictionary _testToExecutionMap = new Dictionary();
+ private List _testResults = new List();
+ }
+}
+
diff --git a/src/extensions/testframework/TimeSpanFormatter.cs b/src/extensions/testframework/TimeSpanFormatter.cs
new file mode 100644
index 00000000..b378a896
--- /dev/null
+++ b/src/extensions/testframework/TimeSpanFormatter.cs
@@ -0,0 +1,23 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System;
+
+namespace Azure.AI.Details.Common.CLI.TestFramework
+{
+ public class TimeSpanFormatter
+ {
+ public static string FormatMsOrSeconds(TimeSpan timeSpan)
+ {
+ var ms = timeSpan.TotalMilliseconds;
+ var secs = ms / 1000;
+ var duration = ms >= 1000
+ ? secs.ToString("0.00") + " seconds"
+ : ms.ToString("0") + " ms";
+ return duration;
+ }
+ }
+}
+
diff --git a/tests/testrunner/YamlTestFrameworkConsoleHost.cs b/src/extensions/testframework/TrxXmlTestReporter.cs
similarity index 53%
rename from tests/testrunner/YamlTestFrameworkConsoleHost.cs
rename to src/extensions/testframework/TrxXmlTestReporter.cs
index 2494002e..117a0edc 100644
--- a/tests/testrunner/YamlTestFrameworkConsoleHost.cs
+++ b/src/extensions/testframework/TrxXmlTestReporter.cs
@@ -1,132 +1,34 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
-using System.Collections.Generic;
-using System.IO;
using System.Linq;
-using System.Text.RegularExpressions;
using System.Xml;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
-using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
namespace Azure.AI.Details.Common.CLI.TestFramework
{
- public class YamlTestFrameworkConsoleHost : IYamlTestFrameworkHost
+ public static class TrxXmlTestReporter
{
- public YamlTestFrameworkConsoleHost()
- {
- }
-
- public void RecordStart(TestCase testCase)
+ public static string WriteResultsFile(TestRun testRun, string resultsFile = "test-results.trx")
{
- _startTime ??= DateTime.Now;
- _testCases.Add(testCase);
- SetExecutionId(testCase, Guid.NewGuid());
-
- lock (this)
- {
- Console.ForegroundColor = ConsoleColor.DarkGray;
- Console.WriteLine("Starting test: " + testCase.FullyQualifiedName);
- Console.ResetColor();
- }
- }
-
- public void RecordResult(TestResult testResult)
- {
- _testResults.Add(testResult);
- PrintResult(testResult);
- }
-
- public void RecordEnd(TestCase testCase, TestOutcome outcome)
- {
- _endTime = DateTime.Now;
- }
-
- public bool Finish(IDictionary> resultsByTestCaseId)
- {
- var allResults = resultsByTestCaseId.Values.SelectMany(x => x);
- var failedResults = allResults.Where(x => x.Outcome == TestOutcome.Failed).ToList();
- var passedResults = allResults.Where(x => x.Outcome == TestOutcome.Passed).ToList();
- var skippedResults = allResults.Where(x => x.Outcome == TestOutcome.Skipped).ToList();
- var passed = failedResults.Count == 0;
-
- if (failedResults.Count > 0)
- {
- Console.ResetColor();
- Console.WriteLine();
- Console.BackgroundColor = ConsoleColor.Red;
- Console.ForegroundColor = ConsoleColor.White;
- Console.Write("FAILURE SUMMARY:");
- Console.ResetColor();
- Console.WriteLine();
- failedResults.ForEach(r => PrintResult(r));
- }
- else
- {
- Console.WriteLine();
- }
-
- var count = allResults.Count();
- var duration = _endTime != null && _startTime != null ? FormattedDuration((_endTime.Value - _startTime.Value).TotalMilliseconds) : "0 ms";
- Console.BackgroundColor = ConsoleColor.Blue;
- Console.ForegroundColor = ConsoleColor.White;
- Console.Write("TEST RESULT SUMMARY:");
- Console.ResetColor();
- Console.Write("\nTests: ");
- Console.ForegroundColor = ConsoleColor.Blue;
- Console.Write($"{count}");
- Console.ForegroundColor = ConsoleColor.DarkGray;
- Console.WriteLine($" ({duration})");
-
- var resultsFile = WriteResultFile();
-
- var fi = new FileInfo(resultsFile);
- Console.ResetColor();
- Console.Write("Results: ");
- Console.ForegroundColor = ConsoleColor.Blue;
- Console.Write(fi.FullName);
- Console.ResetColor();
- Console.WriteLine("\n");
-
- Console.ForegroundColor = ConsoleColor.Green;
- Console.Write($"Passed: {passedResults.Count}");
-
- if (failedResults.Count > 0)
- {
- Console.ForegroundColor = ConsoleColor.DarkGray;
- Console.Write(", ");
- Console.ForegroundColor = ConsoleColor.Red;
- Console.Write($"Failed: {failedResults.Count}");
- }
-
- if (skippedResults.Count > 0)
- {
- Console.ForegroundColor = ConsoleColor.DarkGray;
- Console.Write(", ");
- Console.ForegroundColor = ConsoleColor.Yellow;
- Console.Write($"Skipped: {skippedResults.Count}");
- }
-
- Console.ResetColor();
- Console.WriteLine("\n");
-
- return passed;
- }
+ var testCases = testRun.TestCases;
+ var testResults = testRun.TestResults;
+ var startTime = testRun.StartTime;
+ var endTime = testRun.EndTime;
- public string WriteResultFile()
- {
var assembly = typeof(YamlTestFrameworkConsoleHost).Assembly;
var assemblyPath = assembly.Location;
- _startTime ??= DateTime.Now;
- _endTime ??= DateTime.Now;
-
- var resultFile = "test-results.trx";
var testRunId = Guid.NewGuid().ToString();
var testListId = "8c84fa94-04c1-424b-9868-57a2d4851a1d";
var testType = "13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b";
var userName = Environment.UserName;
var machineName = Environment.MachineName;
var userAtMachine = userName.Split('\\', '/').Last() + "@" + machineName;
- var testRunName = userAtMachine + " " + _endTime.Value.ToString("yyyy-MM-dd HH:mm:ss");
+ var testRunName = userAtMachine + " " + endTime.ToString("yyyy-MM-dd HH:mm:ss");
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
@@ -135,7 +37,7 @@ public string WriteResultFile()
settings.NewLineHandling = NewLineHandling.Replace;
settings.OmitXmlDeclaration = false;
- var writer = XmlWriter.Create(resultFile, settings);
+ var writer = XmlWriter.Create(resultsFile, settings);
writer.WriteStartDocument();
writer.WriteStartElement("", "TestRun", "http://microsoft.com/schemas/VisualStudio/TeamTest/2010");
writer.WriteAttributeString("id", testRunId);
@@ -143,16 +45,16 @@ public string WriteResultFile()
writer.WriteAttributeString("runUser", userName);
writer.WriteStartElement("Times");
- writer.WriteAttributeString("creation", _endTime.Value.ToString("o"));
- writer.WriteAttributeString("queuing", _endTime.Value.ToString("o"));
- writer.WriteAttributeString("start", _startTime.Value.ToString("o"));
- writer.WriteAttributeString("finish", _endTime.Value.ToString("o"));
+ writer.WriteAttributeString("creation", endTime.ToString("o"));
+ writer.WriteAttributeString("queuing", endTime.ToString("o"));
+ writer.WriteAttributeString("start", startTime.ToString("o"));
+ writer.WriteAttributeString("finish", endTime.ToString("o"));
writer.WriteEndElement();
writer.WriteStartElement("Results");
- foreach (var testResult in _testResults)
+ foreach (var testResult in testResults)
{
- var executionId = GetExecutionId(testResult.TestCase).ToString();
+ var executionId = testRun.GetExecutionId(testResult.TestCase).ToString();
var stdout = testResult.Messages.FirstOrDefault(x => x.Category == TestResultMessage.StandardOutCategory)?.Text;
var stderr = testResult.Messages.FirstOrDefault(x => x.Category == TestResultMessage.StandardErrorCategory)?.Text;
var debugTrace = testResult.Messages.FirstOrDefault(x => x.Category == TestResultMessage.DebugTraceCategory)?.Text;
@@ -212,9 +114,9 @@ public string WriteResultFile()
writer.WriteEndElement();
writer.WriteStartElement("TestDefinitions");
- foreach (var testCase in _testCases)
+ foreach (var testCase in testCases)
{
- var executionId = GetExecutionId(testCase).ToString();
+ var executionId = testRun.GetExecutionId(testCase).ToString();
var qualifiedParts = testCase.FullyQualifiedName.Split('.');
var className = string.Join(".", qualifiedParts.Take(qualifiedParts.Length - 1));
var name = qualifiedParts.Last();
@@ -236,9 +138,9 @@ public string WriteResultFile()
writer.WriteEndElement();
writer.WriteStartElement("TestEntries");
- foreach (var testCase in _testCases)
+ foreach (var testCase in testCases)
{
- var executionId = GetExecutionId(testCase).ToString();
+ var executionId = testRun.GetExecutionId(testCase).ToString();
writer.WriteStartElement("TestEntry");
writer.WriteAttributeString("testId", testCase.Id.ToString());
writer.WriteAttributeString("executionId", executionId);
@@ -262,19 +164,19 @@ public string WriteResultFile()
writer.WriteAttributeString("outcome", "Completed");
writer.WriteStartElement("Counters");
- writer.WriteAttributeString("total", _testResults.Count.ToString());
- writer.WriteAttributeString("executed", _testResults.Count(r => IsExecuted(r)).ToString());
- writer.WriteAttributeString("passed", _testResults.Count(r => IsPassed(r)).ToString());
- writer.WriteAttributeString("failed", _testResults.Count(r => IsFailed(r)).ToString());
- writer.WriteAttributeString("error", _testResults.Count(r => IsError(r)).ToString());
- writer.WriteAttributeString("timeout", _testResults.Count(r => IsTimeout(r)).ToString());
- writer.WriteAttributeString("aborted", _testResults.Count(r => IsAborted(r)).ToString());
- writer.WriteAttributeString("inconclusive", _testResults.Count(r => IsInConclusive(r)).ToString());
- writer.WriteAttributeString("passedButRunAborted", _testResults.Count(r => IsPassedButRunaborted(r)).ToString());
- writer.WriteAttributeString("notRunnable", _testResults.Count(r => IsNotRunnable(r)).ToString());
- writer.WriteAttributeString("notExecuted", _testResults.Count(r => IsNotExecuted(r)).ToString());
- writer.WriteAttributeString("disconnected", _testResults.Count(r => IsDisconnected(r)).ToString());
- writer.WriteAttributeString("warning", _testResults.Count(r => IsWarning(r)).ToString());
+ writer.WriteAttributeString("total", testResults.Count.ToString());
+ writer.WriteAttributeString("executed", testResults.Count(r => IsExecuted(r)).ToString());
+ writer.WriteAttributeString("passed", testResults.Count(r => IsPassed(r)).ToString());
+ writer.WriteAttributeString("failed", testResults.Count(r => IsFailed(r)).ToString());
+ writer.WriteAttributeString("error", testResults.Count(r => IsError(r)).ToString());
+ writer.WriteAttributeString("timeout", testResults.Count(r => IsTimeout(r)).ToString());
+ writer.WriteAttributeString("aborted", testResults.Count(r => IsAborted(r)).ToString());
+ writer.WriteAttributeString("inconclusive", testResults.Count(r => IsInConclusive(r)).ToString());
+ writer.WriteAttributeString("passedButRunAborted", testResults.Count(r => IsPassedButRunaborted(r)).ToString());
+ writer.WriteAttributeString("notRunnable", testResults.Count(r => IsNotRunnable(r)).ToString());
+ writer.WriteAttributeString("notExecuted", testResults.Count(r => IsNotExecuted(r)).ToString());
+ writer.WriteAttributeString("disconnected", testResults.Count(r => IsDisconnected(r)).ToString());
+ writer.WriteAttributeString("warning", testResults.Count(r => IsWarning(r)).ToString());
writer.WriteAttributeString("completed", "0");
writer.WriteAttributeString("inProgress", "0");
writer.WriteAttributeString("pending", "0");
@@ -292,42 +194,7 @@ public string WriteResultFile()
writer.Close();
writer.Dispose();
- return resultFile;
- }
-
- private void PrintResult(TestResult testResult)
- {
- lock (this)
- {
- Console.ForegroundColor = ConsoleColor.DarkGray;
- if (testResult.Outcome == TestOutcome.Passed) Console.ForegroundColor = ConsoleColor.Green;
- if (testResult.Outcome == TestOutcome.Skipped) Console.ForegroundColor = ConsoleColor.Yellow;
- if (testResult.Outcome == TestOutcome.Failed) Console.ForegroundColor = ConsoleColor.Red;
-
- var duration = FormattedDuration(testResult.Duration.TotalMilliseconds);
- Console.WriteLine($"{testResult.Outcome} ({duration}): {testResult.TestCase.FullyQualifiedName}");
- Console.ResetColor();
-
- if (testResult.Outcome == TestOutcome.Failed)
- {
- var hasStack = !string.IsNullOrEmpty(testResult.ErrorStackTrace);
- if (hasStack) Console.WriteLine(testResult.ErrorStackTrace.Trim('\r', '\n'));
-
- var hasErr = !string.IsNullOrEmpty(testResult.ErrorMessage);
- if (hasErr) Console.WriteLine(testResult.ErrorMessage.Trim('\r', '\n'));
-
- if (hasErr || hasStack) Console.WriteLine();
- }
- }
- }
-
- private static string FormattedDuration(double ms)
- {
- var secs = ms / 1000;
- var duration = ms >= 1000
- ? secs.ToString("0.00") + " seconds"
- : ms.ToString("0") + " ms";
- return duration;
+ return resultsFile;
}
private static string OutcomeToString(TestOutcome outcome)
@@ -342,7 +209,7 @@ private static string OutcomeToString(TestOutcome outcome)
};
}
- private bool IsExecuted(TestResult r)
+ private static bool IsExecuted(TestResult r)
{
return IsPassed(r) || IsFailed(r);
}
@@ -410,29 +277,6 @@ private static bool IsWarning(TestResult r)
return false;
// return r.Outcome == TestOutcome.Warning;
}
-
- private void SetExecutionId(TestCase testCase, Guid guid)
- {
- lock (_testToExecutionMap)
- {
- _testToExecutionMap[testCase.Id] = guid;
- }
- }
-
- private Guid GetExecutionId(TestCase testCase)
- {
- lock (_testToExecutionMap)
- {
- return _testToExecutionMap[testCase.Id];
- }
- }
-
- private DateTime? _startTime;
- private DateTime? _endTime;
-
- private List _testCases = new List();
- private Dictionary _testToExecutionMap = new Dictionary();
- private List _testResults = new List();
}
}
diff --git a/src/extensions/testframework/YamlEnvHelpers.cs b/src/extensions/testframework/YamlEnvHelpers.cs
new file mode 100644
index 00000000..da23dad3
--- /dev/null
+++ b/src/extensions/testframework/YamlEnvHelpers.cs
@@ -0,0 +1,162 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using YamlDotNet.Helpers;
+using YamlDotNet.RepresentationModel;
+
+namespace Azure.AI.Details.Common.CLI.TestFramework
+{
+ public class YamlEnvHelpers
+ {
+ public static Dictionary GetDefaultEnvironment(bool fromCurrentProcess, string workingDirectory)
+ {
+ var env = new Dictionary();
+
+ if (fromCurrentProcess)
+ {
+ var environmentFromCurrentProcess = GetCurrentProcessEnvironment();
+ foreach (var key in environmentFromCurrentProcess.Keys)
+ {
+ env[key] = environmentFromCurrentProcess[key];
+ }
+ }
+
+ if (!string.IsNullOrEmpty(workingDirectory))
+ {
+ var environmentFromFile = GetEnvironmentFromDirectory(workingDirectory);
+ foreach (var key in environmentFromFile.Keys)
+ {
+ env[key] = environmentFromFile[key];
+ }
+ }
+
+ return env;
+ }
+
+ public static Dictionary GetEnvironmentFromDirectory(string workingDirectory)
+ {
+ var envFile = Path.Combine(workingDirectory, ".env");
+ return GetEnvironmentFromFile(envFile);
+ }
+
+ public static Dictionary GetEnvironmentFromFile(string envFile)
+ {
+ var fileOk = File.Exists(envFile);
+ if (!fileOk) return new Dictionary();
+
+ var content = File.ReadAllText(envFile);
+ return GetEnvironmentFromMultiLineString(content);
+ }
+
+ public static Dictionary GetEnvironmentFromMultiLineString(string content)
+ {
+ var lines = content.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
+ return lines
+ .Select(line => line.Split(new[] { '=' }, 2))
+ .ToDictionary(parts => parts[0], parts => parts[1]);
+ }
+
+ public static Dictionary GetCurrentProcessEnvironment()
+ {
+ var env = Environment.GetEnvironmentVariables();
+ return env.Keys.Cast().ToDictionary(key => key, key => env[key].ToString());
+ }
+
+ public static Dictionary UpdateCopyEnvironment(Dictionary environment, YamlMappingNode mapping)
+ {
+ var envNode = mapping.Children.ContainsKey("env") ? mapping.Children["env"] : null;
+ if (envNode == null) return environment;
+
+ var asMapping = envNode as YamlMappingNode;
+ var asSequence = envNode as YamlSequenceNode;
+ if (asMapping == null && asSequence == null) return environment;
+
+ var env = new Dictionary(environment);
+ if (asMapping != null)
+ {
+ UpdateEnvironment(env, asMapping.Children);
+ }
+ else if (asSequence != null)
+ {
+ foreach (var item in asSequence.Children)
+ {
+ var itemAsMapping = item as YamlMappingNode;
+ if (itemAsMapping != null)
+ {
+ UpdateEnvironment(env, itemAsMapping.Children);
+ }
+ }
+ }
+
+ return env;
+ }
+
+ public static Dictionary GetNewAndUpdatedEnvironmentVariables(Dictionary original, Dictionary check)
+ {
+ var newAndUpdated = new Dictionary();
+ foreach (var item in check)
+ {
+ var isNew = !original.ContainsKey(item.Key);
+ var isUpdated = !isNew && original[item.Key] != item.Value;
+ if (isNew || isUpdated)
+ {
+ newAndUpdated.Add(item.Key, item.Value);
+ }
+ }
+ return newAndUpdated;
+ }
+
+ private static void UpdateEnvironment(Dictionary env, IOrderedDictionary children)
+ {
+ foreach (var item in children)
+ {
+ var key = (item.Key as YamlScalarNode)?.Value;
+ var value = (item.Value as YamlScalarNode)?.Value;
+ if (key != null && value != null)
+ {
+ env[key] = value.Contains("$(") && value.Contains(')')
+ ? ExpandInlineEnvironmentVariables(env, value!)
+ : value!;
+ }
+ }
+ }
+
+ private static string ExpandInlineEnvironmentVariables(Dictionary env, string s)
+ {
+ var sb = new StringBuilder();
+
+ var i = 0;
+ while (i < s.Length)
+ {
+ if (s[i] == '$' && i + 1 < s.Length && s[i+1] == '(')
+ {
+ var closeAt = s.IndexOf(')', i + 1);
+ if (closeAt > 0 && closeAt < s.Length)
+ {
+ var nameLen = closeAt - i - 2;
+ var name = s.Substring(i + 2, nameLen);
+ if (env.ContainsKey(name))
+ {
+ sb.Append(env[name]);
+ i += nameLen + 3;
+ continue;
+ }
+ }
+ }
+
+ sb.Append(s[i]);
+ i++;
+ }
+
+ return sb.ToString();
+
+ }
+ }
+}
diff --git a/tests/testframework/YamlHelpers.cs b/src/extensions/testframework/YamlHelpers.cs
similarity index 92%
rename from tests/testframework/YamlHelpers.cs
rename to src/extensions/testframework/YamlHelpers.cs
index 33ce53b7..3db79e3b 100644
--- a/tests/testframework/YamlHelpers.cs
+++ b/src/extensions/testframework/YamlHelpers.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
using System.IO;
using YamlDotNet.RepresentationModel;
@@ -31,7 +36,6 @@ public static YamlStream ParseYamlStream(string fullName)
if (!string.IsNullOrEmpty(error))
{
Logger.LogError(error);
- Logger.TraceError(error);
}
return stream;
diff --git a/tests/testframework/YamlNodeExtensions.cs b/src/extensions/testframework/YamlNodeExtensions.cs
similarity index 96%
rename from tests/testframework/YamlNodeExtensions.cs
rename to src/extensions/testframework/YamlNodeExtensions.cs
index e8e8be3f..c8320cc1 100644
--- a/tests/testframework/YamlNodeExtensions.cs
+++ b/src/extensions/testframework/YamlNodeExtensions.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using System;
using System.Collections.Generic;
diff --git a/tests/testframework/YamlTagHelpers.cs b/src/extensions/testframework/YamlTagHelpers.cs
similarity index 79%
rename from tests/testframework/YamlTagHelpers.cs
rename to src/extensions/testframework/YamlTagHelpers.cs
index dd8fe868..305788d2 100644
--- a/tests/testframework/YamlTagHelpers.cs
+++ b/src/extensions/testframework/YamlTagHelpers.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -8,32 +13,36 @@ namespace Azure.AI.Details.Common.CLI.TestFramework
{
public class YamlTagHelpers
{
- public static FileInfo GetYamlDefaultTagsFullFileName(DirectoryInfo directory)
+ public static FileInfo FindDefaultTagsFile(DirectoryInfo directory)
{
var found = directory.GetFiles(YamlTestFramework.YamlDefaultTagsFileName);
return found.Length == 1
? found[0]
: directory.Parent != null
- ? GetYamlDefaultTagsFullFileName(directory.Parent)
+ ? FindDefaultTagsFile(directory.Parent)
: null;
}
- public static Dictionary> GetDefaultTags(DirectoryInfo directory)
+ public static Dictionary> FindAndGetDefaultTags(DirectoryInfo directory)
+ {
+ var defaultsFile = FindDefaultTagsFile(directory)?.FullName;
+ return defaultsFile != null
+ ? GetTagsFromFile(defaultsFile)
+ : new Dictionary>();
+ }
+
+ public static Dictionary> GetTagsFromFile(string defaultsFile)
{
var defaultTags = new Dictionary>();
- var defaultsFile = GetYamlDefaultTagsFullFileName(directory)?.FullName;
- if (defaultsFile != null)
+ Logger.Log($"Loading default tags from {defaultsFile}");
+ var parsed = YamlHelpers.ParseYamlStream(defaultsFile);
+ if (parsed.Documents.Count() > 0)
{
- Logger.Log($"Loading default tags from {defaultsFile}");
- var parsed = YamlHelpers.ParseYamlStream(defaultsFile);
- if (parsed.Documents.Count() > 0)
+ var tagsNode = parsed.Documents[0].RootNode;
+ if (tagsNode != null)
{
- var tagsNode = parsed.Documents[0].RootNode;
- if (tagsNode != null)
- {
- defaultTags = UpdateCopyTags(defaultTags, null, tagsNode);
- }
+ defaultTags = UpdateCopyTags(defaultTags, null, tagsNode);
}
}
diff --git a/tests/testframework/YamlTestCaseFilter.cs b/src/extensions/testframework/YamlTestCaseFilter.cs
similarity index 96%
rename from tests/testframework/YamlTestCaseFilter.cs
rename to src/extensions/testframework/YamlTestCaseFilter.cs
index 32b2837b..cf2299d2 100644
--- a/tests/testframework/YamlTestCaseFilter.cs
+++ b/src/extensions/testframework/YamlTestCaseFilter.cs
@@ -1,4 +1,9 @@
-using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
using System;
diff --git a/src/extensions/testframework/YamlTestCaseParseContext.cs b/src/extensions/testframework/YamlTestCaseParseContext.cs
new file mode 100644
index 00000000..f78e1282
--- /dev/null
+++ b/src/extensions/testframework/YamlTestCaseParseContext.cs
@@ -0,0 +1,23 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
+using System.Collections.Generic;
+using System.IO;
+
+namespace Azure.AI.Details.Common.CLI.TestFramework
+{
+ public struct YamlTestCaseParseContext
+ {
+ public string Source;
+ public FileInfo File;
+
+ public string Area;
+ public string Class;
+ public Dictionary> Tags;
+
+ public Dictionary Environment;
+ public string WorkingDirectory;
+ }
+}
diff --git a/tests/testframework/YamlTestCaseParser.cs b/src/extensions/testframework/YamlTestCaseParser.cs
similarity index 71%
rename from tests/testframework/YamlTestCaseParser.cs
rename to src/extensions/testframework/YamlTestCaseParser.cs
index 2b65ccb6..a4a15f3c 100644
--- a/tests/testframework/YamlTestCaseParser.cs
+++ b/src/extensions/testframework/YamlTestCaseParser.cs
@@ -1,3 +1,8 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
+//
+
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -5,29 +10,41 @@
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
-using YamlDotNet.Helpers;
using YamlDotNet.RepresentationModel;
namespace Azure.AI.Details.Common.CLI.TestFramework
{
- public class YamlTestCaseParser
+ public partial class YamlTestCaseParser
{
public static IEnumerable TestCasesFromYaml(string source, FileInfo file)
{
- var area = GetRootArea(file);
+ var defaultTags = YamlTagHelpers.FindAndGetDefaultTags(file.Directory);
+
+ var workingDirectory = GetScalarString(null, defaultTags, "workingDirectory");
+ workingDirectory = UpdateWorkingDirectory(file.Directory.FullName, workingDirectory);
+
+ var context = new YamlTestCaseParseContext() {
+ Source = source,
+ File = file,
+ Area = GetRootArea(file),
+ Class = GetScalarString(null, defaultTags, "class", defaultClassName),
+ Tags = defaultTags,
+ Environment = YamlEnvHelpers.GetDefaultEnvironment(true, workingDirectory),
+ WorkingDirectory = workingDirectory
+ };
+
var parsed = YamlHelpers.ParseYamlStream(file.FullName);
- return TestCasesFromYamlStream(source, file, area, parsed).ToList();
+ return TestCasesFromYamlStream(context, parsed).ToList();
}
#region private methods
- private static IEnumerable TestCasesFromYamlStream(string source, FileInfo file, string area, YamlStream parsed)
+ private static IEnumerable TestCasesFromYamlStream(YamlTestCaseParseContext context, YamlStream parsed)
{
var tests = new List();
- var defaultTags = YamlTagHelpers.GetDefaultTags(file.Directory);
foreach (var document in parsed?.Documents)
{
- var fromDocument = TestCasesFromYamlDocumentRootNode(source, file, document.RootNode, area, defaultClassName, defaultTags);
+ var fromDocument = TestCasesFromYamlDocumentRootNode(context, document.RootNode);
if (fromDocument != null)
{
tests.AddRange(fromDocument);
@@ -36,22 +53,22 @@ private static IEnumerable TestCasesFromYamlStream(string source, File
return tests;
}
- private static IEnumerable TestCasesFromYamlDocumentRootNode(string source, FileInfo file, YamlNode node, string area, string @class, Dictionary> tags)
+ private static IEnumerable TestCasesFromYamlDocumentRootNode(YamlTestCaseParseContext context, YamlNode node)
{
return node is YamlMappingNode
- ? TestCasesFromYamlMapping(source, file, node as YamlMappingNode, area, @class, tags)
- : TestCasesFromYamlSequence(source, file, node as YamlSequenceNode, area, @class, tags);
+ ? TestCasesFromYamlMapping(context, node as YamlMappingNode)
+ : TestCasesFromYamlSequence(context, node as YamlSequenceNode);
}
- private static IEnumerable TestCasesFromYamlMapping(string source, FileInfo file, YamlMappingNode mapping, string area, string @class, Dictionary> tags)
+ private static IEnumerable TestCasesFromYamlMapping(YamlTestCaseParseContext context, YamlMappingNode mapping)
{
- var children = CheckForChildren(source, file, mapping, area, @class, tags);
+ var children = CheckForChildren(context, mapping);
if (children != null)
{
return children;
}
- var test = GetTestFromNode(source, file, mapping, area, @class, tags);
+ var test = GetTestFromNode(context, mapping);
if (test != null)
{
return new[] { test };
@@ -60,14 +77,22 @@ private static IEnumerable TestCasesFromYamlMapping(string source, Fil
return null;
}
- private static IEnumerable TestCasesFromYamlSequence(string source, FileInfo file, YamlSequenceNode sequence, string area, string @class, Dictionary> tags)
+ private static IEnumerable TestCasesFromYamlSequence(YamlTestCaseParseContext context, YamlSequenceNode sequence)
{
var tests = new List();
if (sequence == null) return tests;
- foreach (YamlMappingNode mapping in sequence.Children)
+ foreach (var node in sequence.Children)
{
- var fromMapping = TestCasesFromYamlMapping(source, file, mapping, area, @class, tags);
+ var mapping = node as YamlMappingNode;
+ if (mapping == null)
+ {
+ var message = $"Error parsing YAML: expected mapping at {context.File.FullName}({node.Start.Line})";
+ Logger.LogError(message);
+ return null;
+ }
+
+ var fromMapping = TestCasesFromYamlMapping(context, mapping);
if (fromMapping != null)
{
tests.AddRange(fromMapping);
@@ -77,11 +102,12 @@ private static IEnumerable TestCasesFromYamlSequence(string source, Fi
return tests;
}
- private static TestCase GetTestFromNode(string source, FileInfo file, YamlMappingNode mapping, string area, string @class, Dictionary> tags, int stepNumber = 0)
+ private static TestCase GetTestFromNode(YamlTestCaseParseContext context, YamlMappingNode mapping, int stepNumber = 0)
{
- string cli = GetScalarString(mapping, tags, "cli");
- string parallelize = GetScalarString(mapping, tags, "parallelize");
- string skipOnFailure = GetScalarString(mapping, tags, "skipOnFailure");
+ string cli = GetScalarString(mapping, context.Tags, "cli");
+ string parallelize = GetScalarString(mapping, context.Tags, "parallelize");
+ string skipOnFailure = GetScalarString(mapping, context.Tags, "skipOnFailure");
+ string workingDirectory = UpdateWorkingDirectory(mapping, context.WorkingDirectory);
string simulate = GetScalarString(mapping, "simulate");
string command = GetScalarString(mapping, "command");
@@ -89,24 +115,23 @@ private static TestCase GetTestFromNode(string source, FileInfo file, YamlMappin
string bash = GetScalarString(mapping, "bash");
string fullyQualifiedName = command == null && script == null && bash == null
- ? GetFullyQualifiedNameAndCommandFromShortForm(mapping, area, @class, ref command, stepNumber)
- : GetFullyQualifiedName(mapping, area, @class, stepNumber);
- fullyQualifiedName ??= GetFullyQualifiedName(area, @class, $"Expected YAML node ('name') at {file.FullName}({mapping.Start.Line})", 0);
+ ? GetFullyQualifiedNameAndCommandFromShortForm(mapping, context.Area, context.Class, ref command, stepNumber)
+ : GetFullyQualifiedName(mapping, context.Area, context.Class, stepNumber);
+ fullyQualifiedName ??= GetFullyQualifiedName(context.Area, context.Class, $"Expected YAML node ('name') at {context.File.FullName}({mapping.Start.Line})", 0);
var simulating = !string.IsNullOrEmpty(simulate);
var neitherOrBoth = (command == null) == (script == null && bash == null);
if (neitherOrBoth && !simulating)
{
- var message = $"Error parsing YAML: expected/unexpected key ('name', 'command', 'script', 'bash', 'arguments') at {file.FullName}({mapping.Start.Line})";
+ var message = $"Error parsing YAML: expected/unexpected key ('name', 'command', 'script', 'bash', 'arguments') at {context.File.FullName}({mapping.Start.Line})";
Logger.LogError(message);
- Logger.TraceError(message);
return null;
}
Logger.Log($"YamlTestCaseParser.GetTests(): new TestCase('{fullyQualifiedName}')");
- var test = new TestCase(fullyQualifiedName, new Uri(YamlTestFramework.FakeExecutor), source)
+ var test = new TestCase(fullyQualifiedName, new Uri(YamlTestFramework.FakeExecutor), context.Source)
{
- CodeFilePath = file.FullName,
+ CodeFilePath = context.File.FullName,
LineNumber = mapping.Start.Line
};
@@ -118,12 +143,16 @@ private static TestCase GetTestFromNode(string source, FileInfo file, YamlMappin
SetTestCaseProperty(test, "parallelize", parallelize);
SetTestCaseProperty(test, "skipOnFailure", skipOnFailure);
- var timeout = GetScalarString(mapping, tags, "timeout", YamlTestFramework.DefaultTimeout);
+ var timeout = GetScalarString(mapping, context.Tags, "timeout", YamlTestFramework.DefaultTimeout);
SetTestCaseProperty(test, "timeout", timeout);
- var workingDirectory = GetScalarString(mapping, tags, "workingDirectory", file.Directory.FullName);
SetTestCaseProperty(test, "working-directory", workingDirectory);
+ var processEnv = YamlEnvHelpers.GetCurrentProcessEnvironment();
+ var testEnv = YamlEnvHelpers.UpdateCopyEnvironment(context.Environment, mapping);
+ testEnv = YamlEnvHelpers.GetNewAndUpdatedEnvironmentVariables(processEnv, testEnv);
+ SetTestCasePropertyMap(test, "env", testEnv);
+
SetTestCasePropertyMap(test, "foreach", mapping, "foreach", workingDirectory);
SetTestCasePropertyMap(test, "arguments", mapping, "arguments", workingDirectory);
SetTestCasePropertyMap(test, "input", mapping, "input", workingDirectory);
@@ -132,42 +161,46 @@ private static TestCase GetTestFromNode(string source, FileInfo file, YamlMappin
SetTestCaseProperty(test, "expect-gpt", mapping, "expect-gpt");
SetTestCaseProperty(test, "not-expect", mapping, "not-expect");
- SetTestCaseTagsAsTraits(test, YamlTagHelpers.UpdateCopyTags(tags, mapping));
+ SetTestCaseTagsAsTraits(test, YamlTagHelpers.UpdateCopyTags(context.Tags, mapping));
- CheckInvalidTestCaseNodes(file, mapping, test);
+ CheckInvalidTestCaseNodes(context, mapping, test);
return test;
}
- private static IEnumerable CheckForChildren(string source, FileInfo file, YamlMappingNode mapping, string area, string @class, Dictionary> tags)
+ private static IEnumerable CheckForChildren(YamlTestCaseParseContext context, YamlMappingNode mapping)
{
if (mapping.Children.ContainsKey("steps") && mapping.Children["steps"] is YamlSequenceNode stepsSequence)
{
- @class = GetScalarString(mapping, "class", @class);
- area = UpdateArea(mapping, area);
- tags = YamlTagHelpers.UpdateCopyTags(tags, mapping);
+ context.Class = GetScalarString(mapping, "class", context.Class);
+ context.Area = UpdateArea(mapping, context.Area);
+ context.Tags = YamlTagHelpers.UpdateCopyTags(context.Tags, mapping);
+ context.Environment = YamlEnvHelpers.UpdateCopyEnvironment(context.Environment, mapping);
+ context.WorkingDirectory = UpdateWorkingDirectory(mapping, context.WorkingDirectory);
- return TestCasesFromYamlSequenceOfSteps(source, file, stepsSequence, area, @class, tags);
+ return TestCasesFromYamlSequenceOfSteps(context, stepsSequence);
}
if (mapping.Children.ContainsKey("tests") && mapping.Children["tests"] is YamlSequenceNode testsSequence)
{
- @class = GetScalarString(mapping, "class", @class);
- area = UpdateArea(mapping, area);
- tags = YamlTagHelpers.UpdateCopyTags(tags, mapping);
+ context.Class = GetScalarString(mapping, "class", context.Class);
+ context.Area = UpdateArea(mapping, context.Area);
+ context.Tags = YamlTagHelpers.UpdateCopyTags(context.Tags, mapping);
+ context.Environment = YamlEnvHelpers.UpdateCopyEnvironment(context.Environment, mapping);
+ context.WorkingDirectory = UpdateWorkingDirectory(mapping, context.WorkingDirectory);
- return TestCasesFromYamlSequence(source, file, testsSequence, area, @class, tags).ToList();
+ return TestCasesFromYamlSequence(context, testsSequence).ToList();
}
return null;
}
- private static IEnumerable TestCasesFromYamlSequenceOfSteps(string source, FileInfo file, YamlSequenceNode sequence, string area, string @class, Dictionary> tags)
+ private static IEnumerable TestCasesFromYamlSequenceOfSteps(YamlTestCaseParseContext context, YamlSequenceNode sequence)
{
var tests = new List