@awak-app/simy-cli 0.5.20 → 0.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/cli-contract.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./durable-local-task-steps-contract.js";
|
|
14
14
|
import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
|
|
15
15
|
|
|
16
|
-
export const CLI_VERSION = "0.5.
|
|
16
|
+
export const CLI_VERSION = "0.5.21";
|
|
17
17
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
18
18
|
|
|
19
19
|
export function withCliContract(
|
package/src/desktop-host.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createWindowsDesktopUpdater } from "./windows-desktop-updater.js";
|
|
1
2
|
import { spawn, execFile } from "node:child_process";
|
|
2
3
|
import { promisify } from "node:util";
|
|
3
4
|
import { createInterface } from "node:readline";
|
|
@@ -70,7 +71,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
70
71
|
if (!candidate) {
|
|
71
72
|
await (dependencies.assertNoLegacyDaemon || assertNoLegacyCliDaemon)(webOrigin);
|
|
72
73
|
}
|
|
73
|
-
updater = await (dependencies.createUpdater || createCliAutoUpdater)({
|
|
74
|
+
updater = await (dependencies.createUpdater || (process.platform === "win32" ? (() => createWindowsDesktopUpdater({ version: packageInfo.version, disabled: env.SIMY_AUTO_UPDATE === "0" })) : createCliAutoUpdater))({
|
|
74
75
|
daemon: true, interactive: true, webOrigin,
|
|
75
76
|
disabled: env.SIMY_AUTO_UPDATE === "0",
|
|
76
77
|
dependencies: { packageInfo, logger() {} },
|
|
@@ -194,6 +195,13 @@ function validCandidateStatus(status, version, webOrigin) {
|
|
|
194
195
|
async function openCodexLogin(root) {
|
|
195
196
|
const codex = await inspectBackendExecutable("codex");
|
|
196
197
|
if (!codex.available || !codex.executable) throw new Error("Codex is not installed.");
|
|
198
|
+
if (process.platform === "win32") {
|
|
199
|
+
const child = spawn(join(process.env.SystemRoot || "C:\\Windows", "System32/WindowsPowerShell/v1.0/powershell.exe"),
|
|
200
|
+
["-NoProfile", "-NoExit", "-Command", "& $env:SIMY_CODEX_LOGIN login"],
|
|
201
|
+
{ detached: true, stdio: "ignore", windowsHide: false, env: { ...process.env, SIMY_CODEX_LOGIN: codex.executable } });
|
|
202
|
+
await new Promise((resolve, reject) => { child.once("spawn", resolve); child.once("error", reject); });
|
|
203
|
+
child.unref(); return;
|
|
204
|
+
}
|
|
197
205
|
const directory = await mkdtemp(join(root, "codex-login-"));
|
|
198
206
|
const script = join(directory, "Sign in to Codex.command");
|
|
199
207
|
const quote = (s) => `'${s.replaceAll("'", "'\\''")}'`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, realpath, rm, writeFile, stat } from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { windowsProcessStart, windowsUserProcesses } from "./windows-processes.js";
|
|
3
4
|
import { randomBytes } from "node:crypto";
|
|
4
5
|
import { execFile } from "node:child_process";
|
|
5
6
|
import { promisify } from "node:util";
|
|
@@ -161,8 +162,8 @@ export async function stopConfirmedCliConflicts(webOrigin, approved, { force = f
|
|
|
161
162
|
|
|
162
163
|
export function parseLegacyAgentArguments(args) {
|
|
163
164
|
const argv = Array.isArray(args) ? args : recoverProcessArguments(args);
|
|
164
|
-
if (argv.length < 2 || !["node", "nodejs"].includes(
|
|
165
|
-
const entryIndex = argv.findIndex((value, index) => index > 0 && /(?:^|\/)src\/index\.js$/.test(value));
|
|
165
|
+
if (argv.length < 2 || !["node", "nodejs", "node.exe", "nodejs.exe"].includes(argv[0].replaceAll("\\", "/").split("/").at(-1).toLowerCase())) return null;
|
|
166
|
+
const entryIndex = argv.findIndex((value, index) => index > 0 && /(?:^|\/)src\/index\.js$/.test(value.replaceAll("\\", "/")));
|
|
166
167
|
if (entryIndex < 1) return null;
|
|
167
168
|
const agentArgs = argv.slice(entryIndex + 1);
|
|
168
169
|
if (agentArgs.includes("--help") || agentArgs.includes("-h")) return null;
|
|
@@ -224,6 +225,7 @@ export async function validateDesktopSupervisor(root, nonce) {
|
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
async function processStart(pid) {
|
|
228
|
+
if (process.platform === "win32") return windowsProcessStart(pid);
|
|
227
229
|
const { stdout } = await promisify(execFile)("/bin/ps", ["-p", String(pid), "-o", "lstart="], { timeout: 2_000 });
|
|
228
230
|
return stdout.trim();
|
|
229
231
|
}
|
|
@@ -233,6 +235,7 @@ function processAlive(pid) {
|
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
async function listSameUserProcesses() {
|
|
238
|
+
if (process.platform === "win32") return windowsUserProcesses();
|
|
236
239
|
const uid = process.getuid?.();
|
|
237
240
|
if (!Number.isSafeInteger(uid)) return [];
|
|
238
241
|
const { stdout } = await promisify(execFile)("/bin/ps", ["-ww", "-axo", "pid=,uid=,command="], {
|
package/src/windows-bootstrap.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
3
4
|
import { spawn } from 'node:child_process';
|
|
4
5
|
import { maintainWindowsUpdate, selectedWindowsRoot } from './windows-update.js';
|
|
5
6
|
const installation = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
@@ -18,8 +19,11 @@ try {
|
|
|
18
19
|
if (updating) {
|
|
19
20
|
if (!process.exitCode) console.log(`SIMY Windows installation ready: ${root}`);
|
|
20
21
|
} else {
|
|
21
|
-
const
|
|
22
|
-
|
|
22
|
+
const desktop = argv[0] === '--desktop';
|
|
23
|
+
if (desktop && !existsSync(path.join(root, 'simy-desktop.exe'))) root = installation;
|
|
24
|
+
const child = spawn(path.join(root, desktop ? 'simy-desktop.exe' : 'runtime/node.exe'),
|
|
25
|
+
desktop ? ['--selected'] : [path.join(root, 'cli/src/index.js'), ...argv], {
|
|
26
|
+
stdio: 'inherit', env: { ...process.env, ...(desktop ? {} : { SIMY_AUTO_UPDATE: '0' }) },
|
|
23
27
|
});
|
|
24
28
|
// Windows console signals are already delivered to both processes.
|
|
25
29
|
process.on('SIGINT', () => {});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { WINDOWS_MANIFEST_URL, validateWindowsRelease, compareWindowsVersions } from './windows-update.js';
|
|
3
|
+
import { openAuthorizationUrl } from './browser.js';
|
|
4
|
+
|
|
5
|
+
// Windows selects verified immutable releases at startup. Never replace a live
|
|
6
|
+
// desktop/agent during a task; the UI offers the installer for immediate upgrades.
|
|
7
|
+
export function createWindowsDesktopUpdater({ version, fetchImpl = fetch, open = openAuthorizationUrl, disabled = false }) {
|
|
8
|
+
const events = new EventEmitter();
|
|
9
|
+
let timer, initial, release;
|
|
10
|
+
let state = { state: disabled ? 'disabled' : 'idle', current_version: version, automatic: false, install_mode: 'windows_installer',
|
|
11
|
+
message: 'Updates are applied when SIMY starts. Check now to download the latest installer.' };
|
|
12
|
+
const update = values => { state = { ...state, ...values }; events.emit('change'); };
|
|
13
|
+
let pending;
|
|
14
|
+
events.snapshot = () => structuredClone(state);
|
|
15
|
+
events.start = () => {
|
|
16
|
+
if (disabled || timer) return;
|
|
17
|
+
initial = setTimeout(() => void events.checkNow(), 15000); initial.unref?.();
|
|
18
|
+
timer = setInterval(() => void events.checkNow(), 3600000); timer.unref?.();
|
|
19
|
+
};
|
|
20
|
+
events.stop = () => { clearTimeout(initial); clearInterval(timer); timer = null; };
|
|
21
|
+
events.checkNow = () => pending ||= (async () => {
|
|
22
|
+
update({ state: 'checking', message: 'Checking for updates…' });
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetchImpl(WINDOWS_MANIFEST_URL, { signal: AbortSignal.timeout(15000), redirect: 'error' });
|
|
25
|
+
if (!response.ok) throw new Error('Update service unavailable');
|
|
26
|
+
release = validateWindowsRelease(await response.json());
|
|
27
|
+
const newer = compareWindowsVersions(release.version, version) > 0;
|
|
28
|
+
update({ state: newer ? 'update_available' : 'up_to_date', target_version: newer ? release.version : null,
|
|
29
|
+
message: newer ? `SIMY ${release.version} is available. Install it after your tasks finish.` : 'SIMY is up to date.',
|
|
30
|
+
action: newer ? { url: release.artifacts[0].url } : null });
|
|
31
|
+
} catch { update({ state: 'failed', message: 'Could not check for updates. Try again.', action: null }); }
|
|
32
|
+
return events.snapshot();
|
|
33
|
+
})().finally(() => { pending = null; });
|
|
34
|
+
events.performAction = async () => { if (state.action?.url) await open(state.action.url); else await events.checkNow(); };
|
|
35
|
+
events.maybeApply = events.performAction;
|
|
36
|
+
events.beginWork = () => () => {};
|
|
37
|
+
events.acceptsNewWork = () => true;
|
|
38
|
+
return events;
|
|
39
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const run = promisify(execFile);
|
|
5
|
+
export async function windowsProcessStart(pid) {
|
|
6
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) throw new Error('Invalid process');
|
|
7
|
+
const { stdout } = await powershell('(Get-Process -Id ([int]$env:SIMY_INSPECT_PID) -ErrorAction Stop).StartTime.ToUniversalTime().ToString("o")', { SIMY_INSPECT_PID: String(pid) });
|
|
8
|
+
return stdout.trim();
|
|
9
|
+
}
|
|
10
|
+
export async function windowsUserProcesses() {
|
|
11
|
+
const { stdout } = await powershell(`$ErrorActionPreference='Stop';$sid=[Security.Principal.WindowsIdentity]::GetCurrent().User.Value;$rows=@(Get-CimInstance Win32_Process -Filter "Name='node.exe' OR Name='nodejs.exe'" | ForEach-Object {$owner=Invoke-CimMethod -InputObject $_ -MethodName GetOwnerSid;if($owner.Sid -eq $sid){@{pid=$_.ProcessId;command=$_.CommandLine}}});ConvertTo-Json -InputObject $rows -Compress`);
|
|
12
|
+
return JSON.parse(stdout || '[]').map(row => ({ pid: Number(row.pid), args: splitWindowsArguments(row.command || '') }));
|
|
13
|
+
}
|
|
14
|
+
function powershell(script, env = {}) {
|
|
15
|
+
return run(path.join(process.env.SystemRoot || 'C:\\Windows', 'System32/WindowsPowerShell/v1.0/powershell.exe'),
|
|
16
|
+
['-NoProfile', '-NonInteractive', '-Command', script], { timeout: 15000, windowsHide: true, maxBuffer: 2*1024*1024, env: { ...process.env, ...env } });
|
|
17
|
+
}
|
|
18
|
+
// CommandLineToArgvW backslash/quote rules; never shell-unescape Windows paths.
|
|
19
|
+
export function splitWindowsArguments(command) {
|
|
20
|
+
const result = []; let i = 0;
|
|
21
|
+
while (i < command.length) {
|
|
22
|
+
while (/\s/.test(command[i] || '') && i < command.length) i++;
|
|
23
|
+
if (i === command.length) break;
|
|
24
|
+
let value = '', quoted = false;
|
|
25
|
+
while (i < command.length && (quoted || !/\s/.test(command[i]))) {
|
|
26
|
+
let slashes = 0;
|
|
27
|
+
while (command[i] === '\\') { slashes++; i++; }
|
|
28
|
+
if (command[i] === '"') {
|
|
29
|
+
value += '\\'.repeat(Math.floor(slashes / 2));
|
|
30
|
+
if (slashes % 2) value += '"'; else quoted = !quoted;
|
|
31
|
+
i++;
|
|
32
|
+
} else { value += '\\'.repeat(slashes); if (i < command.length) value += command[i++]; }
|
|
33
|
+
}
|
|
34
|
+
result.push(value);
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
package/src/windows-update.js
CHANGED
|
@@ -83,7 +83,7 @@ async function verifyWindowsCandidate(candidate, marker) {
|
|
|
83
83
|
if (marker?.signed) {
|
|
84
84
|
if (!/^[A-Fa-f0-9]{40}$/.test(marker.thumbprint || '')) throw new Error('Missing trusted signer');
|
|
85
85
|
// Paths are passed through environment values, never inserted into shell source.
|
|
86
|
-
await exec(powershell(), ['-NoProfile', '-NonInteractive', '-Command', "$ErrorActionPreference='Stop';foreach($file in @('simy.exe','runtime/node.exe')){$s=Get-AuthenticodeSignature (Join-Path $env:SIMY_CANDIDATE $file);if($s.Status -ne 'Valid'){throw 'Invalid signature'};if($file -
|
|
86
|
+
await exec(powershell(), ['-NoProfile', '-NonInteractive', '-Command', "$ErrorActionPreference='Stop';foreach($file in @('simy.exe','simy-desktop.exe','runtime/node.exe')){$s=Get-AuthenticodeSignature (Join-Path $env:SIMY_CANDIDATE $file);if($s.Status -ne 'Valid'){throw 'Invalid signature'};if($file -ne 'runtime/node.exe' -and $s.SignerCertificate.Thumbprint -ne $env:SIMY_SIGNER){throw 'Unexpected signer'}}"], { env: { ...process.env, SIMY_CANDIDATE: candidate, SIMY_SIGNER: marker.thumbprint }, timeout: 30_000, windowsHide: true });
|
|
87
87
|
}
|
|
88
88
|
const probeHome = path.join(candidate, '.update-probe');
|
|
89
89
|
try {
|