microsoft/TypeAgent

Public

mirrored fromhttps://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/collate-todos-into-todo-md

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

dotnet/autoShell/AutoShell.cs

152lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System;
5using System.IO;
6using System.Runtime.InteropServices;
7using autoShell.Logging;
8using autoShell.Services.Interop;
9using Newtonsoft.Json;
10using Newtonsoft.Json.Linq;
11
12namespace autoShell;
13
14/// <summary>
15/// Entry point for the autoShell Windows automation console application.
16/// Reads JSON commands from stdin (interactive mode) or command-line arguments
17/// and dispatches them to the appropriate handler via <see cref="CommandDispatcher"/>.
18/// </summary>
19/// <remarks>
20/// Each JSON command is a single object where property names are command names
21/// and values are parameters, e.g. <c>{"Volume":50}</c> or <c>{"Mute":true}</c>.
22/// Multiple commands can be batched in one object: <c>{"Volume":50,"Mute":false}</c>.
23/// The special command <c>"quit"</c> exits the application.
24/// </remarks>
25internal class AutoShell
26{
27 #region P/Invoke
28
29 [DllImport(NativeDlls.Kernel32, CharSet = CharSet.Unicode)]
30 private static extern IntPtr GetCommandLineW();
31
32 #endregion P/Invoke
33
34 private static readonly ConsoleLogger s_logger = new();
35 private static readonly CommandDispatcher s_dispatcher = CommandDispatcher.Create(s_logger);
36
37 /// <summary>
38 /// Program entry point. Runs in one of two modes:
39 /// <list type="bullet">
40 /// <item><description>Command-line mode: executes the JSON command(s) passed as arguments and exits.</description></item>
41 /// <item><description>Interactive mode (no args): reads JSON commands from stdin in a loop until "quit" or EOF.</description></item>
42 /// </list>
43 /// </summary>
44 private static void Main(string[] args)
45 {
46 if (args.Length > 0)
47 {
48 RunFromCommandLine();
49 }
50 else
51 {
52 RunInteractive();
53 }
54 }
55
56 /// <summary>
57 /// Executes JSON command(s) from command-line arguments and exits.
58 /// Accepts a single JSON object (<c>{"Volume":50}</c>) or an array
59 /// (<c>[{"Volume":50},{"Mute":true}]</c>).
60 /// </summary>
61 /// <remarks>
62 /// Uses raw command line via P/Invoke to preserve original quoting and spacing,
63 /// since the CLR args array strips quotes and splits on spaces.
64 /// </remarks>
65 private static void RunFromCommandLine()
66 {
67 string rawCmdLine = Marshal.PtrToStringUni(GetCommandLineW());
68 string cmdLine = StripExecutableName(rawCmdLine);
69
70 try
71 {
72 // Try parsing as a JSON array of commands
73 JArray commands = JArray.Parse(cmdLine);
74 foreach (JObject jo in commands.Children<JObject>())
75 {
76 ExecLine(jo);
77 }
78 }
79 catch (JsonReaderException)
80 {
81 // Not an array — treat as a single JSON object
82 ExecLine(JObject.Parse(cmdLine));
83 }
84 }
85
86 /// <summary>
87 /// Reads JSON commands from stdin line by line until "quit" or EOF.
88 /// This is the primary mode when autoShell is launched as a child process
89 /// by the TypeAgent desktop connector.
90 /// </summary>
91 private static void RunInteractive()
92 {
93 bool quit = false;
94 while (!quit)
95 {
96 try
97 {
98 string line = Console.ReadLine();
99
100 // Null means stdin was closed (e.g., parent process exited)
101 if (line == null)
102 {
103 break;
104 }
105
106 // Each line is a JSON object with one or more command keys
107 JObject root = JObject.Parse(line);
108 quit = ExecLine(root);
109 }
110 catch (Exception ex)
111 {
112 s_logger.Error(ex);
113 }
114 }
115 }
116
117 /// <summary>
118 /// Strips the executable name/path from the raw command line string,
119 /// leaving only the arguments portion.
120 /// </summary>
121 private static string StripExecutableName(string rawCmdLine)
122 {
123 // Try quoted path first: "C:\path\autoShell.exe"
124 string exe = $"\"{Environment.ProcessPath}\"";
125 string cmdLine = rawCmdLine.Replace(exe, "");
126
127 if (cmdLine.StartsWith(exe, StringComparison.OrdinalIgnoreCase))
128 {
129 return cmdLine[exe.Length..];
130 }
131
132 // Try unquoted filename: autoShell.exe
133 var processFileName = Path.GetFileName(Environment.ProcessPath);
134 if (cmdLine.StartsWith(processFileName, StringComparison.OrdinalIgnoreCase))
135 {
136 return cmdLine[processFileName.Length..];
137 }
138
139 // Try filename without extension: autoShell
140 var processFileNameNoExt = Path.GetFileNameWithoutExtension(processFileName);
141 return cmdLine.StartsWith(processFileNameNoExt, StringComparison.OrdinalIgnoreCase)
142 ? cmdLine[processFileNameNoExt.Length..]
143 : cmdLine;
144 }
145
146 /// <summary>
147 /// Dispatches a parsed JSON command object to the appropriate handler.
148 /// </summary>
149 /// <returns><c>true</c> if the application should exit (quit command received).</returns>
150 private static bool ExecLine(JObject root)
151 => s_dispatcher.Dispatch(root);
152}
153