Merge branch 'master' into NLPCRAFT-376
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCli.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCli.scala
index 549372d..0d67d5d 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCli.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCli.scala
@@ -2460,12 +2460,8 @@
             override def complete(reader: LineReader, line: ParsedLine, candidates: java.util.List[Candidate]): Unit = {
                 val words = line.words().asScala
 
-                // Don't complete if the line starts with '$'.
-                if (words.nonEmpty && words.head.nonEmpty && words.head.head == '$') {
-                    // No-op.
-                }
                 // Complete command names.
-                else if (words.isEmpty || (words.size == 1 && !cmds.map(_._1).contains(words.head))) {
+                if (words.isEmpty || (words.size == 1 && !cmds.map(_._1).contains(words.head))) {
                     // Add all commands as a candidates.
                     candidates.addAll(cmds.map(n => {
                         val name = n._1
@@ -2480,7 +2476,37 @@
                         )
                     }).asJava)
                 }
-                // Complete paths.
+                // Complete path for OS commands (starting with '$').
+                else if (words.nonEmpty && words.head.nonEmpty && words.head.head == '$' && words.last.contains(PATH_SEP_STR)) {
+                    var path = words.last // Potential path.
+
+                    var prefix = ""
+                    var suffix = ""
+
+                    if (path.nonEmpty) {
+                        val first = path.head
+                        val last = path.last
+
+                        if (first == '"' || first == '\'') {
+                            prefix = first.toString
+                            path = path.drop(1)
+                        }
+                        if (last == '"' || last == '\'') {
+                            suffix = last.toString
+                            path = path.dropRight(1)
+                        }
+                    }
+
+                    fsCompleter.fillCandidates(
+                        reader,
+                        null,
+                        path,
+                        prefix,
+                        suffix,
+                        candidates
+                    )
+                }
+                // Complete paths for commands.
                 else if (words.size > 1 && isFsPath(words.head, words.last))
                     splitEqParam(words.last) match {
                         case Some((name, p)) =>
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCliCommands.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCliCommands.scala
index 3b43ef0..d095348 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCliCommands.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCliCommands.scala
@@ -159,7 +159,7 @@
             group = "2. REST Commands",
             synopsis = s"Wrapper for ${y("'/signin'")} REST call.",
             desc = Some(
-                s"See ${REST_SPEC_URL} for REST call specification. " +
+                s"See $REST_SPEC_URL for REST call specification. " +
                 s"If no arguments provided, it signs in with the " +
                 s"default 'admin@admin.com' user account. NOTE: please make sure to remove this account when " +
                 s"running in production."
@@ -197,7 +197,7 @@
             group = "2. REST Commands",
             synopsis = s"Wrapper for ${y("'/signout'")} REST call in REPL mode.",
             desc = Some(
-                s"See ${REST_SPEC_URL} for REST call specification. " +
+                s"See $REST_SPEC_URL for REST call specification. " +
                 s"Signs out currently signed in user. Note that this command makes sense only in REPL mode."
             ),
             body = NCCli.cmdSignOut,
@@ -277,7 +277,7 @@
             group = "2. REST Commands",
             synopsis = s"Wrapper for ${y("'/ask/sync'")} REST call.",
             desc = Some(
-                s"See ${REST_SPEC_URL} for REST call specification. " +
+                s"See $REST_SPEC_URL for REST call specification. " +
                 s"Requires user to be already signed in. This command ${bo("only makes sense in the REPL mode")} as " +
                 s"it requires user to be signed in. REPL session keeps the currently active access " +
                 s"token after user signed in. For command line mode, use ${y("'rest'")} command with " +
@@ -550,7 +550,7 @@
             group = "2. REST Commands",
             synopsis = s"Wrapper for ${y("'/model/info'")} REST call.",
             desc = Some(
-                s"See ${REST_SPEC_URL} for REST call specification. " +
+                s"See $REST_SPEC_URL for REST call specification. " +
                 s"Requires user to be already signed in. This command ${bo("only makes sense in the REPL mode")} as " +
                 s"it requires user to be signed in. REPL session keeps the currently active access " +
                 s"token after user signed in. For command line mode, use ${y("'rest'")} command with " +
@@ -586,7 +586,7 @@
             group = "2. REST Commands",
             synopsis = s"Wrapper for ${y("'/model/syns'")} REST call.",
             desc = Some(
-                s"See ${REST_SPEC_URL} for REST call specification. " +
+                s"See $REST_SPEC_URL for REST call specification. " +
                 s"Requires user to be already signed in. This command ${bo("only makes sense in the REPL mode")} as " +
                 s"it requires user to be signed in. REPL session keeps the currently active access " +
                 s"token after user signed in. For command line mode, use ${y("'rest'")} command with " +
@@ -630,7 +630,7 @@
             group = "2. REST Commands",
             synopsis = s"Wrapper for ${y("'/model/sugsyn'")} REST call.",
             desc = Some(
-                s"See ${REST_SPEC_URL} for REST call specification. " +
+                s"See $REST_SPEC_URL for REST call specification. " +
                 s"Requires user to be already signed in. This command ${bo("only makes sense in the REPL mode")} as " +
                 s"it requires user to be signed in. REPL session keeps the currently active access " +
                 s"token after user signed in. For command line mode, use ${y("'rest'")} command with " +
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCliFileNameCompleter.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCliFileNameCompleter.java
index 8c69e1c..5071a10 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCliFileNameCompleter.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCliFileNameCompleter.java
@@ -38,7 +38,7 @@
     /**
      *
      * @param reader Line reader for JLine.
-     * @param paramName Name of the parameter.
+     * @param paramName Optional name of the parameter.
      * @param pathBuf Current path string.
      * @param prefix Path prefix (like a quote).
      * @param suffix Path suffix (like a quote).
@@ -79,11 +79,12 @@
             try (DirectoryStream<Path> dir = Files.newDirectoryStream(curPath, this::accept)) {
                 dir.forEach(path -> {
                     String value = curBuf + path.getFileName().toString();
+                    String paramEq = paramName != null ? paramName + "=" : "";
 
                     if (Files.isDirectory(path)) {
                         candidates.add(
                             new Candidate(
-                                paramName + "=" + prefix + value + (reader.isSet(LineReader.Option.AUTO_PARAM_SLASH) ? sep : "") + suffix,
+                                paramEq + prefix + value + (reader.isSet(LineReader.Option.AUTO_PARAM_SLASH) ? sep : "") + suffix,
                                 getDisplay(reader.getTerminal(), path, resolver, sep),
                                 null,
                                 null,
@@ -95,7 +96,7 @@
                     } else {
                         candidates.add(
                             new Candidate(
-                                paramName + "=" + prefix + value + suffix,
+                                paramEq + prefix + value + suffix,
                                 getDisplay(reader.getTerminal(), path, resolver, sep),
                                 null,
                                 null,