feat: add tran win skills discovery
This commit is contained in:
+95
-15
@@ -943,22 +943,22 @@ function parseWinRouteWorkspace(providerId: string, tail: string): string | null
|
||||
function parseWinRouteArgs(route: ParsedSshRoute, args: string[]): ParsedSshArgs {
|
||||
const operation = args[0] ?? "";
|
||||
if (operation.length === 0) {
|
||||
throw new Error(`ssh ${route.raw} requires a Windows operation, for example: ssh ${route.providerId}:win cmd ver`);
|
||||
throw new Error(`ssh ${route.raw} requires a Windows operation, for example: ssh ${route.providerId}:win cmd ver or ssh ${route.providerId}:win skills`);
|
||||
}
|
||||
if (operation === "skills" || operation === "skill-discover" || operation === "discover-skills") {
|
||||
return {
|
||||
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsSkillsDiscoveryScript(args.slice(1))),
|
||||
requiresStdin: false,
|
||||
invocationKind: "helper",
|
||||
};
|
||||
}
|
||||
if (operation !== "cmd" && operation !== "cmd.exe") {
|
||||
throw new Error(`unsupported ssh win operation: ${operation}; use ssh ${route.providerId}:win cmd <command-line>`);
|
||||
throw new Error(`unsupported ssh win operation: ${operation}; use ssh ${route.providerId}:win cmd <command-line> or ssh ${route.providerId}:win skills`);
|
||||
}
|
||||
const commandArgs = args[1] === "--" ? args.slice(2) : args.slice(1);
|
||||
if (commandArgs.length === 0) throw new Error(`ssh ${route.raw} cmd requires a command line, for example: ssh ${route.providerId}:win cmd ver`);
|
||||
return {
|
||||
remoteCommand: shellArgv([
|
||||
windowsPowerShellExePath,
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-EncodedCommand",
|
||||
buildWindowsPowerShellEncodedCommand(buildWindowsCmdLine(commandArgs.join(" "), route.workspace)),
|
||||
]),
|
||||
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsCmdLauncherScript(buildWindowsCmdLine(commandArgs.join(" "), route.workspace))),
|
||||
requiresStdin: false,
|
||||
invocationKind: "argv",
|
||||
};
|
||||
@@ -967,8 +967,8 @@ function parseWinRouteArgs(route: ParsedSshRoute, args: string[]): ParsedSshArgs
|
||||
function buildWindowsCmdLine(userCommand: string, cwd: string | null): string {
|
||||
const parts = [
|
||||
"chcp 65001>nul",
|
||||
"set PYTHONUTF8=1",
|
||||
"set PYTHONIOENCODING=utf-8",
|
||||
'set "PYTHONUTF8=1"',
|
||||
'set "PYTHONIOENCODING=utf-8"',
|
||||
];
|
||||
if (cwd !== null) parts.push(`cd /d ${windowsCmdQuote(cwd)}`);
|
||||
parts.push(userCommand);
|
||||
@@ -980,12 +980,82 @@ function windowsCmdQuote(value: string): string {
|
||||
return `"${value}"`;
|
||||
}
|
||||
|
||||
function parseWindowsSkillsOptions(args: string[]): { limit: number; scopes: Array<"agents" | "codex"> } {
|
||||
let limit = 100;
|
||||
let scope: "agents" | "codex" | "all" = "agents";
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index] ?? "";
|
||||
if (arg === "--limit") {
|
||||
const value = args[index + 1];
|
||||
if (value === undefined) throw new Error("ssh win skills --limit requires a value");
|
||||
limit = positiveInt(value, "ssh win skills --limit");
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--scope") {
|
||||
const value = args[index + 1];
|
||||
if (value === undefined) throw new Error("ssh win skills --scope requires a value");
|
||||
if (value !== "agents" && value !== "codex" && value !== "all") throw new Error("ssh win skills --scope must be one of: agents, codex, all");
|
||||
scope = value;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--all") {
|
||||
scope = "all";
|
||||
continue;
|
||||
}
|
||||
throw new Error(`unsupported ssh win skills option: ${arg}`);
|
||||
}
|
||||
return { limit, scopes: scope === "all" ? ["agents", "codex"] : [scope] };
|
||||
}
|
||||
|
||||
function buildWindowsSkillsDiscoveryScript(args: string[]): string {
|
||||
const options = parseWindowsSkillsOptions(args);
|
||||
const rootEntries = options.scopes.map((scope) => {
|
||||
const relative = scope === "agents" ? ".agents\\skills" : ".codex\\skills";
|
||||
return `@{ Scope = ${powerShellSingleQuote(scope)}; Path = (Join-Path $env:USERPROFILE ${powerShellSingleQuote(relative)}) }`;
|
||||
}).join(", ");
|
||||
return [
|
||||
"$ErrorActionPreference = 'Stop';",
|
||||
"$ProgressPreference = 'SilentlyContinue';",
|
||||
"[Console]::InputEncoding = [System.Text.UTF8Encoding]::new();",
|
||||
"[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new();",
|
||||
"$OutputEncoding = [System.Text.UTF8Encoding]::new();",
|
||||
`$limit = ${options.limit};`,
|
||||
`$roots = @(${rootEntries});`,
|
||||
"$rootRecords = @();",
|
||||
"$skills = @();",
|
||||
"foreach ($root in $roots) {",
|
||||
" $exists = Test-Path -LiteralPath $root.Path -PathType Container;",
|
||||
" $rootRecords += [pscustomobject]@{ scope = $root.Scope; path = $root.Path; exists = $exists };",
|
||||
" if (-not $exists) { continue };",
|
||||
" foreach ($dir in @(Get-ChildItem -LiteralPath $root.Path -Directory -Force | Sort-Object Name)) {",
|
||||
" if ($skills.Count -ge $limit) { break };",
|
||||
" $skillFile = Join-Path $dir.FullName 'SKILL.md';",
|
||||
" $hasSkillMd = Test-Path -LiteralPath $skillFile -PathType Leaf;",
|
||||
" if (-not $hasSkillMd) { continue };",
|
||||
" $name = $dir.Name;",
|
||||
" $description = '';",
|
||||
" if ($hasSkillMd) {",
|
||||
" foreach ($line in @(Get-Content -LiteralPath $skillFile -Encoding UTF8 -TotalCount 80)) {",
|
||||
" if ($line -match '^name:\\s*(.+)$') { $name = $Matches[1].Trim(); continue };",
|
||||
" if ($line -match '^description:\\s*(.+)$') { $description = $Matches[1].Trim(); continue };",
|
||||
" }",
|
||||
" }",
|
||||
" $skills += [pscustomobject]@{ scope = $root.Scope; name = $name; directoryName = $dir.Name; path = $dir.FullName; skillFile = $skillFile; hasSkillMd = $hasSkillMd; description = $description };",
|
||||
" }",
|
||||
"}",
|
||||
"$payload = [pscustomobject]@{ ok = $true; command = 'unidesk ssh win skills'; generatedAt = (Get-Date).ToUniversalTime().ToString('o'); user = $env:USERNAME; userProfile = $env:USERPROFILE; counts = [pscustomobject]@{ roots = $rootRecords.Count; skills = $skills.Count; limit = $limit }; roots = $rootRecords; skills = @($skills) };",
|
||||
"$payload | ConvertTo-Json -Depth 6;",
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
function powerShellSingleQuote(value: string): string {
|
||||
return `'${value.replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
function buildWindowsPowerShellEncodedCommand(cmdLine: string): string {
|
||||
const script = [
|
||||
function buildWindowsCmdLauncherScript(cmdLine: string): string {
|
||||
return [
|
||||
"$ErrorActionPreference = 'Stop';",
|
||||
"[Console]::InputEncoding = [System.Text.UTF8Encoding]::new();",
|
||||
"[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new();",
|
||||
@@ -995,7 +1065,17 @@ function buildWindowsPowerShellEncodedCommand(cmdLine: string): string {
|
||||
`& ${powerShellSingleQuote(windowsCmdExeNativePath)} /d /s /c ${powerShellSingleQuote(cmdLine)};`,
|
||||
"exit $LASTEXITCODE;",
|
||||
].join(" ");
|
||||
return Buffer.from(script, "utf16le").toString("base64");
|
||||
}
|
||||
|
||||
function buildWindowsPowerShellInvocation(script: string): string {
|
||||
return shellArgv([
|
||||
windowsPowerShellExePath,
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-EncodedCommand",
|
||||
Buffer.from(script, "utf16le").toString("base64"),
|
||||
]);
|
||||
}
|
||||
|
||||
export function sshRoutePayloadCwd(route: ParsedSshRoute): string | undefined {
|
||||
|
||||
Reference in New Issue
Block a user