impel-cli 0.18.14-beta.0 → 0.18.14-beta.1
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/README.md +13 -10
- package/package.json +1 -1
- package/src/apps.js +10 -39
- package/src/commands/apps.js +0 -20
- package/src/commands/status.js +0 -8
- package/src/macSetup.js +26 -5
- package/src/provisioning.js +0 -11
- package/src/windowsCapabilities.js +0 -334
package/README.md
CHANGED
|
@@ -286,16 +286,10 @@ compatibility for one release, but no command can enable native routing.
|
|
|
286
286
|
- Run `impel.cmd setup` or `impel.cmd update` from PowerShell so restricted
|
|
287
287
|
script policy cannot select the npm-generated `impel.ps1` shim. Impel does
|
|
288
288
|
not modify persistent PowerShell execution policy.
|
|
289
|
-
- Claude Chat and
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
- When those Cowork prerequisites are missing or cannot be read, Impel hides
|
|
294
|
-
the Cowork tab and reports the reason and remediation in setup, update, and
|
|
295
|
-
status. Impel never enables Windows features, restarts Windows, provisions a
|
|
296
|
-
machine-wide package, or elevates itself.
|
|
297
|
-
- Machine-wide deployment requirements are documented by Anthropic in
|
|
298
|
-
[Deploy Claude Desktop for Windows](https://support.claude.com/en/articles/12622703-deploy-claude-desktop-for-windows).
|
|
289
|
+
- Claude Chat, Code, and Cowork remain enabled through the per-user managed
|
|
290
|
+
profile. Impel does not infer Cowork runtime support during setup or update.
|
|
291
|
+
- If Cowork reports a problem, open Claude Desktop's **Help > Troubleshooting >
|
|
292
|
+
Show Logs** and use the vendor diagnostics for that exact runtime failure.
|
|
299
293
|
- Claude Desktop is downloaded from Anthropic as the exact x64 or arm64 MSIX
|
|
300
294
|
pinned by this CLI. Its SHA-256, Authenticode signature, package identity,
|
|
301
295
|
version, architecture, and required layout are checked before installation.
|
|
@@ -379,6 +373,15 @@ run a real package manager, or modify native personal profiles.
|
|
|
379
373
|
CI runs on Ubuntu, macOS, and Windows with supported Node versions. See
|
|
380
374
|
[RELEASING.md](RELEASING.md) for the npm release process.
|
|
381
375
|
|
|
376
|
+
The pinned macOS CLI installer contract follows the official Claude and Codex
|
|
377
|
+
installer redirects with the production downloader, then verifies their
|
|
378
|
+
reviewed final origins, size, shell-script marker, and SHA-256 without executing
|
|
379
|
+
either script or touching a local profile. Run it explicitly with:
|
|
380
|
+
|
|
381
|
+
```sh
|
|
382
|
+
IMPEL_VALIDATE_PINNED_MAC_CLI_INSTALLERS=1 node --test test/mac-installer-contract.test.js
|
|
383
|
+
```
|
|
384
|
+
|
|
382
385
|
The separate Windows vendor contract runs only on a disposable GitHub-hosted
|
|
383
386
|
Windows VM. It installs the exact newer Claude fixture that preceded the
|
|
384
387
|
current compatibility pin, invokes the production downloader and installer to
|
package/package.json
CHANGED
package/src/apps.js
CHANGED
|
@@ -57,7 +57,6 @@ export const PINNED_VENDOR_APPS = Object.freeze({
|
|
|
57
57
|
packageName: "Claude",
|
|
58
58
|
packageVersion: "1.24012.9.0",
|
|
59
59
|
publisher: 'CN="Anthropic, PBC", O="Anthropic, PBC", L=San Francisco, S=California, C=US, SERIALNUMBER=4860621, OID.2.5.4.15=Private Organization, OID.1.3.6.1.4.1.311.60.2.1.2=Delaware, OID.1.3.6.1.4.1.311.60.2.1.3=US',
|
|
60
|
-
publisherId: "pzs8sxrjxfjjc",
|
|
61
60
|
executable: "app\\Claude.exe",
|
|
62
61
|
downloads: Object.freeze({
|
|
63
62
|
arm64: Object.freeze({
|
|
@@ -235,7 +234,9 @@ export function managedAppIdentity(target, tenantId = null, tenantName = null) {
|
|
|
235
234
|
// enterprise machines do not enter the administrator-only setup flow.
|
|
236
235
|
// 24: thread the inspected Windows Cowork host capability into Claude config
|
|
237
236
|
// and the tenant manifest so unsupported hosts keep Chat and Code usable.
|
|
238
|
-
|
|
237
|
+
// 25: remove the unproven Windows Cowork capability gate from beta.0 profiles
|
|
238
|
+
// and restore the reviewed always-enabled managed policy.
|
|
239
|
+
export const CURRENT_CONFIG_VERSION = 25;
|
|
239
240
|
|
|
240
241
|
// Identifies the bundle-BUILDING logic — the asar patches, plist rewrites,
|
|
241
242
|
// helper rebranding, and signing. A vendored bundle is rebuilt only when this
|
|
@@ -713,7 +714,6 @@ export function installManagedAppFiles({
|
|
|
713
714
|
writeTokenHelperFile = true,
|
|
714
715
|
claudeUserData = null,
|
|
715
716
|
chatgptInvocations = null,
|
|
716
|
-
windowsCoworkCapability = null,
|
|
717
717
|
generatedAt = new Date().toISOString(),
|
|
718
718
|
}) {
|
|
719
719
|
// true = rebuild every target's bundle; false = configs only; an array
|
|
@@ -772,7 +772,7 @@ export function installManagedAppFiles({
|
|
|
772
772
|
migrateLegacyClaudeAppSessions(paths.claude.userData, homeDir);
|
|
773
773
|
writeClaudeNative3PSelection(paths);
|
|
774
774
|
writeClaudeCodeSettings(paths);
|
|
775
|
-
writeClaudeConfig(paths, config, models
|
|
775
|
+
writeClaudeConfig(paths, config, models);
|
|
776
776
|
if (RUNTIME_BRAND.features.sessions) ensureClaudeSessionHooks(paths.claude.userData, config.tenantId, "claude_desktop");
|
|
777
777
|
}
|
|
778
778
|
else {
|
|
@@ -793,33 +793,8 @@ export function installManagedAppFiles({
|
|
|
793
793
|
});
|
|
794
794
|
}
|
|
795
795
|
|
|
796
|
-
const manifestCoworkCapability = windowsCoworkCapability
|
|
797
|
-
? {
|
|
798
|
-
schemaVersion: 1,
|
|
799
|
-
status: ["available", "unavailable", "unknown"].includes(windowsCoworkCapability.status)
|
|
800
|
-
? windowsCoworkCapability.status
|
|
801
|
-
: "unknown",
|
|
802
|
-
enabled: windowsCoworkCapability.enabled === true,
|
|
803
|
-
checks: {
|
|
804
|
-
virtualMachinePlatform: windowsCoworkCapability.checks?.virtualMachinePlatform ?? "unknown",
|
|
805
|
-
restartPending: windowsCoworkCapability.checks?.restartPending ?? null,
|
|
806
|
-
vmcompute: windowsCoworkCapability.checks?.vmcompute ?? "unknown",
|
|
807
|
-
hns: windowsCoworkCapability.checks?.hns ?? "unknown",
|
|
808
|
-
vfpext: windowsCoworkCapability.checks?.vfpext ?? "unknown",
|
|
809
|
-
machineProvisioned: windowsCoworkCapability.checks?.machineProvisioned ?? null,
|
|
810
|
-
virtualMachine: windowsCoworkCapability.checks?.virtualMachine ?? null,
|
|
811
|
-
nestedVirtualization: windowsCoworkCapability.checks?.nestedVirtualization ?? null,
|
|
812
|
-
},
|
|
813
|
-
reasonCodes: Array.isArray(windowsCoworkCapability.reasonCodes)
|
|
814
|
-
? [...windowsCoworkCapability.reasonCodes]
|
|
815
|
-
: ["inspector-malformed-output"],
|
|
816
|
-
remediation: Array.isArray(windowsCoworkCapability.remediation)
|
|
817
|
-
? [...windowsCoworkCapability.remediation]
|
|
818
|
-
: [],
|
|
819
|
-
}
|
|
820
|
-
: null;
|
|
821
796
|
writeAtomic(path.join(paths.tenantRoot, "manifest.json"), JSON.stringify({
|
|
822
|
-
schemaVersion:
|
|
797
|
+
schemaVersion: 6,
|
|
823
798
|
configVersion: CURRENT_CONFIG_VERSION,
|
|
824
799
|
modelCatalogVersion: 3,
|
|
825
800
|
gatewayUrl: config.gatewayUrl,
|
|
@@ -832,7 +807,6 @@ export function installManagedAppFiles({
|
|
|
832
807
|
models,
|
|
833
808
|
config,
|
|
834
809
|
),
|
|
835
|
-
...(manifestCoworkCapability ? { capabilities: { claudeCowork: manifestCoworkCapability } } : {}),
|
|
836
810
|
models: models.map((model) => model.id),
|
|
837
811
|
updatedAt: generatedAt,
|
|
838
812
|
}, null, 2) + "\n", 0o600);
|
|
@@ -1059,7 +1033,7 @@ export function removeManagedApps(targets, {
|
|
|
1059
1033
|
}
|
|
1060
1034
|
}
|
|
1061
1035
|
|
|
1062
|
-
function writeClaudeConfig(paths, config, models
|
|
1036
|
+
function writeClaudeConfig(paths, config, models) {
|
|
1063
1037
|
const dir = path.join(paths.claude.userData, "configLibrary");
|
|
1064
1038
|
const experimental = crossAppModelsEnabled(config);
|
|
1065
1039
|
const claudeModels = experimental
|
|
@@ -1074,13 +1048,10 @@ function writeClaudeConfig(paths, config, models, windowsCoworkCapability = null
|
|
|
1074
1048
|
inferenceGatewayAuthScheme: "bearer",
|
|
1075
1049
|
modelDiscoveryEnabled: false,
|
|
1076
1050
|
inferenceModels: claudeModels,
|
|
1077
|
-
// These are Anthropic's native managed 3P surface controls.
|
|
1078
|
-
//
|
|
1079
|
-
//
|
|
1080
|
-
|
|
1081
|
-
coworkTabEnabled: windowsCoworkCapability
|
|
1082
|
-
? windowsCoworkCapability.enabled === true
|
|
1083
|
-
: true,
|
|
1051
|
+
// These are Anthropic's native managed 3P surface controls. Cowork and
|
|
1052
|
+
// Code default to enabled in the pinned build, but state them explicitly
|
|
1053
|
+
// so an older generated profile cannot leave either surface disabled.
|
|
1054
|
+
coworkTabEnabled: true,
|
|
1084
1055
|
isClaudeCodeForDesktopEnabled: true,
|
|
1085
1056
|
// Chat is the only opt-in surface in the pinned build.
|
|
1086
1057
|
chatTabEnabled: true,
|
package/src/commands/apps.js
CHANGED
|
@@ -60,11 +60,6 @@ import {
|
|
|
60
60
|
windowsManagedChatGPTRoot,
|
|
61
61
|
} from "../windowsApps.js";
|
|
62
62
|
import { RUNTIME_BRAND } from "../runtimeBrand.js";
|
|
63
|
-
import {
|
|
64
|
-
formatWindowsClaudeCoworkCapability,
|
|
65
|
-
inspectWindowsClaudeCoworkCapability,
|
|
66
|
-
unknownWindowsClaudeCoworkCapability,
|
|
67
|
-
} from "../windowsCapabilities.js";
|
|
68
63
|
|
|
69
64
|
const CLAUDE_KEYCHAIN_NOTICE = "Claude Keychain: enter your Mac login password and choose Always Allow on the first prompt for this tenant; Allow is temporary.";
|
|
70
65
|
|
|
@@ -324,7 +319,6 @@ function configureWindowsApps(config, targets, vendorPaths, catalog, io) {
|
|
|
324
319
|
writeTokenHelperFile: false,
|
|
325
320
|
claudeUserData: userData,
|
|
326
321
|
chatgptInvocations: { auth, mcp },
|
|
327
|
-
windowsCoworkCapability: io.windowsCoworkCapability || null,
|
|
328
322
|
});
|
|
329
323
|
return { catalog, userData };
|
|
330
324
|
}
|
|
@@ -359,10 +353,7 @@ export async function reconcileWindowsTenantApps({
|
|
|
359
353
|
confirmVendorInstall = async () => true,
|
|
360
354
|
homeDir = os.homedir(),
|
|
361
355
|
environment = process.env,
|
|
362
|
-
windowsCoworkCapability = null,
|
|
363
356
|
} = {}, overrides = {}) {
|
|
364
|
-
const effectiveCoworkCapability = windowsCoworkCapability
|
|
365
|
-
|| unknownWindowsClaudeCoworkCapability();
|
|
366
357
|
const io = {
|
|
367
358
|
findClaudeApp: findWindowsClaudeApp,
|
|
368
359
|
ensureClaudeApp: ensureWindowsClaudeApp,
|
|
@@ -444,7 +435,6 @@ export async function reconcileWindowsTenantApps({
|
|
|
444
435
|
...io,
|
|
445
436
|
homeDir,
|
|
446
437
|
environment,
|
|
447
|
-
windowsCoworkCapability: effectiveCoworkCapability,
|
|
448
438
|
});
|
|
449
439
|
}
|
|
450
440
|
const agentTargets = [];
|
|
@@ -638,7 +628,6 @@ export async function cmdWindowsApps(argv, overrides = {}) {
|
|
|
638
628
|
syncSkills: syncSkillsSafe,
|
|
639
629
|
syncAgents: syncAgentProfilesSafe,
|
|
640
630
|
selectedConfig: selectedAppConfig,
|
|
641
|
-
inspectCoworkCapability: inspectWindowsClaudeCoworkCapability,
|
|
642
631
|
...overrides,
|
|
643
632
|
};
|
|
644
633
|
// Preserve the original Windows-Claude override names used by embedders and
|
|
@@ -667,10 +656,6 @@ export async function cmdWindowsApps(argv, overrides = {}) {
|
|
|
667
656
|
console.log(` profile: ${fs.existsSync(claudeConfigPath) ? paths.claude.userData : "not installed"}`);
|
|
668
657
|
console.log(` vendor: ${io.findClaudeApp(io.environment) || "not installed"}`);
|
|
669
658
|
console.log(" open: impel app open claude");
|
|
670
|
-
const manifest = localTenantId ? readTenantManifest(io.homeDir, localTenantId) : null;
|
|
671
|
-
for (const line of formatWindowsClaudeCoworkCapability(manifest?.capabilities?.claudeCowork)) {
|
|
672
|
-
console.log(` ${line}`);
|
|
673
|
-
}
|
|
674
659
|
}
|
|
675
660
|
if (targets.includes("chatgpt")) {
|
|
676
661
|
console.log("Impel ChatGPT:");
|
|
@@ -792,13 +777,8 @@ export async function cmdWindowsApps(argv, overrides = {}) {
|
|
|
792
777
|
managedChatGPT = io.stageChatGPTApp(vendorPaths.chatgpt, actionPaths.root);
|
|
793
778
|
}
|
|
794
779
|
|
|
795
|
-
const windowsCoworkCapability = io.inspectCoworkCapability({
|
|
796
|
-
environment: io.environment,
|
|
797
|
-
platform: "win32",
|
|
798
|
-
});
|
|
799
780
|
const { userData } = configureWindowsApps(config, actionTargets, vendorPaths, catalog, {
|
|
800
781
|
...io,
|
|
801
|
-
windowsCoworkCapability,
|
|
802
782
|
});
|
|
803
783
|
const tenantPaths = appPaths(io.homeDir, config.tenantId, {
|
|
804
784
|
claudeUserData: userData,
|
package/src/commands/status.js
CHANGED
|
@@ -17,7 +17,6 @@ import { installedVersion, maybePrintUpdateNotice } from "../updates.js";
|
|
|
17
17
|
import { windowsClaudeUserData } from "../windowsApps.js";
|
|
18
18
|
import { RUNTIME_BRAND } from "../runtimeBrand.js";
|
|
19
19
|
import { cachedProviderReadiness, providerAvailable } from "../providerReadiness.js";
|
|
20
|
-
import { formatWindowsClaudeCoworkCapability } from "../windowsCapabilities.js";
|
|
21
20
|
|
|
22
21
|
function readShellManifest(paths) {
|
|
23
22
|
try {
|
|
@@ -38,7 +37,6 @@ export function desktopReadiness(tenant, {
|
|
|
38
37
|
return {
|
|
39
38
|
claude: { app: "unavailable", shell: "unavailable" },
|
|
40
39
|
codex: { app: "unavailable", shell: "unavailable" },
|
|
41
|
-
cowork: null,
|
|
42
40
|
};
|
|
43
41
|
}
|
|
44
42
|
const claudeUserData = platform === "win32" ? windowsClaudeUserData(environment, tenant.id) : null;
|
|
@@ -72,7 +70,6 @@ export function desktopReadiness(tenant, {
|
|
|
72
70
|
shell: shellReady ? "ready" : "missing",
|
|
73
71
|
};
|
|
74
72
|
}
|
|
75
|
-
states.cowork = manifest?.capabilities?.claudeCowork || null;
|
|
76
73
|
return states;
|
|
77
74
|
}
|
|
78
75
|
|
|
@@ -128,8 +125,6 @@ export async function cmdStatus(overrides = {}) {
|
|
|
128
125
|
};
|
|
129
126
|
console.log("Tenant readiness:");
|
|
130
127
|
let incomplete = false;
|
|
131
|
-
let currentCowork = null;
|
|
132
|
-
let fallbackCowork = null;
|
|
133
128
|
for (const tenant of [...selected.tenants].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
134
129
|
const profiles = io.cliReadiness(tenant.id);
|
|
135
130
|
const desktop = io.desktopReadiness(tenant, overrides);
|
|
@@ -150,15 +145,12 @@ export async function cmdStatus(overrides = {}) {
|
|
|
150
145
|
});
|
|
151
146
|
const states = [claudeCli, codexCli, desktop.claude.app, desktop.codex.app, desktop.claude.shell, desktop.codex.shell];
|
|
152
147
|
if (states.includes("missing")) incomplete = true;
|
|
153
|
-
fallbackCowork ||= desktop.cowork || null;
|
|
154
|
-
if (tenant.id === selected.tenantId) currentCowork = desktop.cowork || null;
|
|
155
148
|
console.log(
|
|
156
149
|
` ${tenant.id}${tenant.id === selected.tenantId ? " (current)" : ""}: `
|
|
157
150
|
+ `Claude CLI ${claudeCli}, Codex CLI ${codexCli}, `
|
|
158
151
|
+ `Claude app ${desktop.claude.app}, ChatGPT app ${desktop.codex.app}`,
|
|
159
152
|
);
|
|
160
153
|
}
|
|
161
|
-
for (const line of formatWindowsClaudeCoworkCapability(currentCowork || fallbackCowork)) console.log(line);
|
|
162
154
|
if (incomplete) {
|
|
163
155
|
console.log(RUNTIME_BRAND.tenant.defaultId
|
|
164
156
|
? `Repair or finish missing tenant surfaces with: ${RUNTIME_BRAND.cli.command} setup`
|
package/src/macSetup.js
CHANGED
|
@@ -20,13 +20,21 @@ const MAC_SIGNING_TEAMS = Object.freeze({ claude: "Q6L2SF6YDW", codex: "2DC432GL
|
|
|
20
20
|
export const MAC_CLI_INSTALLERS = Object.freeze({
|
|
21
21
|
claude: Object.freeze({
|
|
22
22
|
url: "https://claude.ai/install.sh",
|
|
23
|
-
|
|
23
|
+
trustedOrigins: Object.freeze([
|
|
24
|
+
"https://claude.ai",
|
|
25
|
+
"https://downloads.claude.ai",
|
|
26
|
+
]),
|
|
27
|
+
sha256: "cde4f1702d3b1695f92b73d26888364e17bca476e17f0fd676484c951d36c125",
|
|
24
28
|
command: "curl -fsSL https://claude.ai/install.sh | bash -s stable",
|
|
25
29
|
args: Object.freeze(["stable"]),
|
|
26
30
|
environment: Object.freeze({}),
|
|
27
31
|
}),
|
|
28
32
|
codex: Object.freeze({
|
|
29
33
|
url: "https://chatgpt.com/codex/install.sh",
|
|
34
|
+
trustedOrigins: Object.freeze([
|
|
35
|
+
"https://chatgpt.com",
|
|
36
|
+
"https://releases.openai.com",
|
|
37
|
+
]),
|
|
30
38
|
sha256: "1154e9daf713aacd1534efca8042bfd6665ad24bc1d1dfd86b8f439fe60a7a5d",
|
|
31
39
|
command: "curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 CODEX_RELEASE=0.144.6 sh",
|
|
32
40
|
args: Object.freeze([]),
|
|
@@ -75,7 +83,10 @@ function detectMacClis(find, environment, verify) {
|
|
|
75
83
|
return { claude: detect("claude"), codex: detect("codex") };
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
async function
|
|
86
|
+
export async function fetchVerifiedMacCliInstaller(tool, {
|
|
87
|
+
fetchImpl = fetch,
|
|
88
|
+
installers = MAC_CLI_INSTALLERS,
|
|
89
|
+
} = {}) {
|
|
79
90
|
const installer = installers[tool];
|
|
80
91
|
if (!installer) throw new Error(`unsupported macOS CLI installer: ${tool}`);
|
|
81
92
|
const controller = new AbortController();
|
|
@@ -87,9 +98,14 @@ async function downloadPinnedInstaller(tool, { fetchImpl, fsImpl, installers })
|
|
|
87
98
|
clearTimeout(timeout);
|
|
88
99
|
}
|
|
89
100
|
if (!response.ok) throw new Error(`official ${tool} installer returned HTTP ${response.status}`);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
let finalUrl;
|
|
102
|
+
try {
|
|
103
|
+
finalUrl = new URL(response.url || installer.url);
|
|
104
|
+
} catch {
|
|
105
|
+
throw new Error(`official ${tool} installer returned an invalid final URL`);
|
|
106
|
+
}
|
|
107
|
+
if (finalUrl.protocol !== "https:" || !installer.trustedOrigins?.includes(finalUrl.origin)) {
|
|
108
|
+
throw new Error(`official ${tool} installer redirected to untrusted origin ${finalUrl.origin}`);
|
|
93
109
|
}
|
|
94
110
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
95
111
|
if (bytes.length === 0 || bytes.length > MAX_INSTALLER_BYTES) {
|
|
@@ -101,6 +117,11 @@ async function downloadPinnedInstaller(tool, { fetchImpl, fsImpl, installers })
|
|
|
101
117
|
}
|
|
102
118
|
const source = bytes.toString("utf8");
|
|
103
119
|
if (!source.startsWith("#!")) throw new Error(`official ${tool} installer was not a shell script`);
|
|
120
|
+
return { bytes, finalUrl: finalUrl.href, sha256: digest, status: response.status };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function downloadPinnedInstaller(tool, { fetchImpl, fsImpl, installers }) {
|
|
124
|
+
const { bytes } = await fetchVerifiedMacCliInstaller(tool, { fetchImpl, installers });
|
|
104
125
|
const directory = fsImpl.mkdtempSync(path.join(os.tmpdir(), `impel-${tool}-installer-`));
|
|
105
126
|
const script = path.join(directory, "install.sh");
|
|
106
127
|
fsImpl.writeFileSync(script, bytes, { mode: 0o700 });
|
package/src/provisioning.js
CHANGED
|
@@ -17,10 +17,6 @@ import {
|
|
|
17
17
|
} from "./tenants.js";
|
|
18
18
|
import { reconcileTenantApps } from "./commands/apps.js";
|
|
19
19
|
import { RUNTIME_BRAND } from "./runtimeBrand.js";
|
|
20
|
-
import {
|
|
21
|
-
formatWindowsClaudeCoworkCapability,
|
|
22
|
-
inspectWindowsClaudeCoworkCapability,
|
|
23
|
-
} from "./windowsCapabilities.js";
|
|
24
20
|
|
|
25
21
|
export function selectDefaultTenant(listing, { requested = null, currentTenantId = null } = {}) {
|
|
26
22
|
const normalized = requested ? normalizeTenantId(requested) : null;
|
|
@@ -189,7 +185,6 @@ export async function reconcileAllTenants({
|
|
|
189
185
|
reconcileApps: reconcileTenantApps,
|
|
190
186
|
registerShellEntries: registerTenantShellEntries,
|
|
191
187
|
knownTenantIds: locallyKnownTenantIds,
|
|
192
|
-
inspectCoworkCapability: inspectWindowsClaudeCoworkCapability,
|
|
193
188
|
...overrides,
|
|
194
189
|
};
|
|
195
190
|
// Dependency-injection compatibility for older embedders/tests. Production
|
|
@@ -213,9 +208,6 @@ export async function reconcileAllTenants({
|
|
|
213
208
|
claude: io.findBinary("claude", environment, platform),
|
|
214
209
|
codex: io.findBinary("codex", environment, platform),
|
|
215
210
|
};
|
|
216
|
-
const windowsCoworkCapability = includeApps && platform === "win32"
|
|
217
|
-
? io.inspectCoworkCapability({ environment, platform })
|
|
218
|
-
: null;
|
|
219
211
|
const results = new Map(liveTenants.map((tenant) => [tenant.id, {
|
|
220
212
|
tenantId: tenant.id,
|
|
221
213
|
tenantName: tenant.name,
|
|
@@ -270,7 +262,6 @@ export async function reconcileAllTenants({
|
|
|
270
262
|
homeDir,
|
|
271
263
|
environment,
|
|
272
264
|
confirmVendorInstall,
|
|
273
|
-
windowsCoworkCapability,
|
|
274
265
|
}, overrides.appOverrides || {});
|
|
275
266
|
supported = new Set(installed.targets || []);
|
|
276
267
|
const failedApps = installed.failed || [];
|
|
@@ -342,7 +333,6 @@ export async function reconcileAllTenants({
|
|
|
342
333
|
selectedTenantId: config.tenantId,
|
|
343
334
|
tenants: values,
|
|
344
335
|
inaccessible,
|
|
345
|
-
...(windowsCoworkCapability ? { capabilities: { claudeCowork: windowsCoworkCapability } } : {}),
|
|
346
336
|
passed: values.every((result) => (
|
|
347
337
|
(result.cli === "ready" || result.cli === "unavailable")
|
|
348
338
|
&& (result.apps === "ready" || result.apps === "unavailable")
|
|
@@ -376,7 +366,6 @@ export function printReconciliationSummary(report, log = console.log) {
|
|
|
376
366
|
if (report.inaccessible.length) {
|
|
377
367
|
log(` Retained local data for inaccessible tenants: ${report.inaccessible.join(", ")}`);
|
|
378
368
|
}
|
|
379
|
-
for (const line of formatWindowsClaudeCoworkCapability(report.capabilities?.claudeCowork)) log(line);
|
|
380
369
|
}
|
|
381
370
|
|
|
382
371
|
export function tenantCliPathsReady(tenantId, existsSync = fs.existsSync) {
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import crypto from "node:crypto";
|
|
3
|
-
|
|
4
|
-
import { PINNED_VENDOR_APPS } from "./apps.js";
|
|
5
|
-
import { environmentValue, nativeCommandInvocation } from "./nativeProcess.js";
|
|
6
|
-
|
|
7
|
-
export const WINDOWS_COWORK_REMEDIATION = Object.freeze({
|
|
8
|
-
enableVirtualMachinePlatform: "Enable the Windows Virtual Machine Platform optional feature, then restart Windows.",
|
|
9
|
-
restartWindows: "Restart Windows to finish Virtual Machine Platform and service provisioning.",
|
|
10
|
-
restoreHcs: "Ask an administrator or endpoint manager to restore the vmcompute, hns, and vfpext Windows components.",
|
|
11
|
-
provisionClaude: "Ask an administrator or endpoint manager to machine-provision the exact pinned Claude MSIX with Add-AppxProvisionedPackage.",
|
|
12
|
-
enableNestedVirtualization: "Enable nested virtualization for this VM, or use a physical Windows host for Claude Cowork.",
|
|
13
|
-
inspectHost: "Ask an administrator to verify Virtual Machine Platform, HCS services, vfpext, and machine-wide Claude provisioning.",
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const WINDOWS_PACKAGE_BASE32 = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
17
|
-
|
|
18
|
-
/** Derive the stable Package Family Name publisher id from the exact DN. */
|
|
19
|
-
export function windowsPackagePublisherId(publisher) {
|
|
20
|
-
const hash = crypto.createHash("sha256").update(Buffer.from(String(publisher), "utf16le")).digest();
|
|
21
|
-
let bits = "";
|
|
22
|
-
for (const byte of hash.subarray(0, 8)) bits += byte.toString(2).padStart(8, "0");
|
|
23
|
-
bits += "0";
|
|
24
|
-
let publisherId = "";
|
|
25
|
-
for (let offset = 0; offset < 65; offset += 5) {
|
|
26
|
-
publisherId += WINDOWS_PACKAGE_BASE32[Number.parseInt(bits.slice(offset, offset + 5), 2)];
|
|
27
|
-
}
|
|
28
|
-
return publisherId;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function windowsPackageFullName({ name, version, architecture, publisherId }) {
|
|
32
|
-
return `${name}_${version}_${String(architecture).toLowerCase()}__${publisherId}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function windowsProvisionedPackageIdentityMatches(candidate, expected) {
|
|
36
|
-
if (!candidate || !expected) return false;
|
|
37
|
-
const expectedFullName = windowsPackageFullName(expected);
|
|
38
|
-
return candidate.displayName === expected.name
|
|
39
|
-
&& candidate.version === expected.version
|
|
40
|
-
&& String(candidate.architecture).toLowerCase() === String(expected.architecture).toLowerCase()
|
|
41
|
-
&& candidate.packageName === expectedFullName;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Fixed, read-only host observation. Expected package metadata is passed only
|
|
45
|
-
// through fixed environment keys; no tenant, user, or path is interpolated
|
|
46
|
-
// into PowerShell source.
|
|
47
|
-
export const WINDOWS_COWORK_INSPECTION_SCRIPT = String.raw`
|
|
48
|
-
$ErrorActionPreference = 'Stop'
|
|
49
|
-
function Read-ServiceState([string]$Name) {
|
|
50
|
-
try {
|
|
51
|
-
$service = Get-Service -Name $Name -ErrorAction SilentlyContinue
|
|
52
|
-
if ($null -eq $service) { return 'missing' }
|
|
53
|
-
if ($service.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Running) { return 'present-running' }
|
|
54
|
-
return 'present-stopped'
|
|
55
|
-
} catch { return 'unknown' }
|
|
56
|
-
}
|
|
57
|
-
function Read-DriverState([string]$Name) {
|
|
58
|
-
try {
|
|
59
|
-
$driver = Get-CimInstance -ClassName Win32_SystemDriver -Filter "Name='$Name'" -ErrorAction SilentlyContinue
|
|
60
|
-
if ($null -eq $driver) { return 'missing' }
|
|
61
|
-
if ($driver.State -eq 'Running') { return 'present-running' }
|
|
62
|
-
return 'present-stopped'
|
|
63
|
-
} catch { return 'unknown' }
|
|
64
|
-
}
|
|
65
|
-
$vmp = 'unknown'
|
|
66
|
-
try {
|
|
67
|
-
$feature = Get-CimInstance -ClassName Win32_OptionalFeature -Filter "Name='VirtualMachinePlatform'" -ErrorAction Stop
|
|
68
|
-
if ($null -eq $feature) { $vmp = 'absent' }
|
|
69
|
-
elseif ($feature.InstallState -eq 1) { $vmp = 'enabled' }
|
|
70
|
-
elseif ($feature.InstallState -eq 2) { $vmp = 'disabled' }
|
|
71
|
-
elseif ($feature.InstallState -eq 3) { $vmp = 'absent' }
|
|
72
|
-
} catch { $vmp = 'unknown' }
|
|
73
|
-
$restartPending = $null
|
|
74
|
-
try {
|
|
75
|
-
$cbs = Test-Path -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
|
|
76
|
-
$wu = Test-Path -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
|
|
77
|
-
$restartPending = [bool]($cbs -or $wu)
|
|
78
|
-
} catch { $restartPending = $null }
|
|
79
|
-
$machineProvisioned = $null
|
|
80
|
-
try {
|
|
81
|
-
$expectedName = $env:IMPEL_COWORK_EXPECTED_PACKAGE_NAME
|
|
82
|
-
$expectedVersion = $env:IMPEL_COWORK_EXPECTED_PACKAGE_VERSION
|
|
83
|
-
$expectedArchitecture = $env:IMPEL_COWORK_EXPECTED_PACKAGE_ARCHITECTURE
|
|
84
|
-
$expectedPublisherId = $env:IMPEL_COWORK_EXPECTED_PACKAGE_PUBLISHER_ID
|
|
85
|
-
$expectedPackageName = $expectedName + '_' + $expectedVersion + '_' + $expectedArchitecture.ToLowerInvariant() + '__' + $expectedPublisherId
|
|
86
|
-
$packages = Get-AppxProvisionedPackage -Online -ErrorAction Stop
|
|
87
|
-
$exact = $packages | Where-Object {
|
|
88
|
-
$displayMatches = ([string]$_.DisplayName -ceq $expectedName)
|
|
89
|
-
$versionMatches = ([string]$_.Version -ceq $expectedVersion)
|
|
90
|
-
$architectureMatches = ([string]$_.Architecture -ieq $expectedArchitecture)
|
|
91
|
-
$packageNameMatches = ([string]$_.PackageName -ceq $expectedPackageName)
|
|
92
|
-
$displayMatches -and $versionMatches -and $architectureMatches -and $packageNameMatches
|
|
93
|
-
} | Select-Object -First 1
|
|
94
|
-
$machineProvisioned = [bool]($null -ne $exact)
|
|
95
|
-
} catch { $machineProvisioned = $null }
|
|
96
|
-
$virtualMachine = $null
|
|
97
|
-
$nestedVirtualization = $null
|
|
98
|
-
try {
|
|
99
|
-
$computer = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
|
|
100
|
-
$identity = (([string]$computer.Manufacturer) + ' ' + ([string]$computer.Model))
|
|
101
|
-
$virtualMachine = [bool]($identity -match 'Virtual|VMware|KVM|HVM|Hyper-V|Parallels|Xen')
|
|
102
|
-
if ($virtualMachine) {
|
|
103
|
-
$processors = @(Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop)
|
|
104
|
-
$nestedVirtualization = [bool]($processors.Count -gt 0 -and ($processors | Where-Object {
|
|
105
|
-
-not ($_.VirtualizationFirmwareEnabled -and $_.VMMonitorModeExtensions)
|
|
106
|
-
}).Count -eq 0)
|
|
107
|
-
} else {
|
|
108
|
-
$nestedVirtualization = $true
|
|
109
|
-
}
|
|
110
|
-
} catch {
|
|
111
|
-
$virtualMachine = $null
|
|
112
|
-
$nestedVirtualization = $null
|
|
113
|
-
}
|
|
114
|
-
[ordered]@{
|
|
115
|
-
schemaVersion = 1
|
|
116
|
-
virtualMachinePlatform = $vmp
|
|
117
|
-
restartPending = $restartPending
|
|
118
|
-
vmcompute = Read-ServiceState 'vmcompute'
|
|
119
|
-
hns = Read-ServiceState 'hns'
|
|
120
|
-
vfpext = Read-DriverState 'vfpext'
|
|
121
|
-
machineProvisioned = $machineProvisioned
|
|
122
|
-
virtualMachine = $virtualMachine
|
|
123
|
-
nestedVirtualization = $nestedVirtualization
|
|
124
|
-
} | ConvertTo-Json -Compress -Depth 3
|
|
125
|
-
`.trim();
|
|
126
|
-
|
|
127
|
-
const VMP_STATES = new Set(["enabled", "disabled", "absent", "unknown"]);
|
|
128
|
-
const COMPONENT_STATES = new Set(["present-running", "present-stopped", "missing", "unknown"]);
|
|
129
|
-
|
|
130
|
-
function emptyChecks() {
|
|
131
|
-
return {
|
|
132
|
-
virtualMachinePlatform: "unknown",
|
|
133
|
-
restartPending: null,
|
|
134
|
-
vmcompute: "unknown",
|
|
135
|
-
hns: "unknown",
|
|
136
|
-
vfpext: "unknown",
|
|
137
|
-
machineProvisioned: null,
|
|
138
|
-
virtualMachine: null,
|
|
139
|
-
nestedVirtualization: null,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function normalizedFailure(reasonCode) {
|
|
144
|
-
return {
|
|
145
|
-
schemaVersion: 1,
|
|
146
|
-
status: "unknown",
|
|
147
|
-
enabled: false,
|
|
148
|
-
checks: emptyChecks(),
|
|
149
|
-
reasonCodes: [reasonCode],
|
|
150
|
-
remediation: [WINDOWS_COWORK_REMEDIATION.inspectHost],
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function unknownWindowsClaudeCoworkCapability(reasonCode = "inspector-not-run") {
|
|
155
|
-
return normalizedFailure(reasonCode);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function nullableBoolean(value) {
|
|
159
|
-
return value === null || typeof value === "boolean";
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function normalizeWindowsClaudeCoworkCapability(observation) {
|
|
163
|
-
if (
|
|
164
|
-
!observation
|
|
165
|
-
|| observation.schemaVersion !== 1
|
|
166
|
-
|| !VMP_STATES.has(observation.virtualMachinePlatform)
|
|
167
|
-
|| !COMPONENT_STATES.has(observation.vmcompute)
|
|
168
|
-
|| !COMPONENT_STATES.has(observation.hns)
|
|
169
|
-
|| !COMPONENT_STATES.has(observation.vfpext)
|
|
170
|
-
|| !nullableBoolean(observation.restartPending)
|
|
171
|
-
|| !nullableBoolean(observation.machineProvisioned)
|
|
172
|
-
|| !nullableBoolean(observation.virtualMachine)
|
|
173
|
-
|| !nullableBoolean(observation.nestedVirtualization)
|
|
174
|
-
) return normalizedFailure("inspector-malformed-output");
|
|
175
|
-
|
|
176
|
-
const checks = {
|
|
177
|
-
virtualMachinePlatform: observation.virtualMachinePlatform,
|
|
178
|
-
restartPending: observation.restartPending,
|
|
179
|
-
vmcompute: observation.vmcompute,
|
|
180
|
-
hns: observation.hns,
|
|
181
|
-
vfpext: observation.vfpext,
|
|
182
|
-
machineProvisioned: observation.machineProvisioned,
|
|
183
|
-
virtualMachine: observation.virtualMachine,
|
|
184
|
-
nestedVirtualization: observation.nestedVirtualization,
|
|
185
|
-
};
|
|
186
|
-
const reasonCodes = [];
|
|
187
|
-
const knownUnavailable = [];
|
|
188
|
-
const unknown = [];
|
|
189
|
-
const addKnown = (reason) => {
|
|
190
|
-
reasonCodes.push(reason);
|
|
191
|
-
knownUnavailable.push(reason);
|
|
192
|
-
};
|
|
193
|
-
const addUnknown = (reason) => {
|
|
194
|
-
reasonCodes.push(reason);
|
|
195
|
-
unknown.push(reason);
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
if (checks.virtualMachinePlatform === "disabled") addKnown("virtual-machine-platform-disabled");
|
|
199
|
-
else if (checks.virtualMachinePlatform === "absent") addKnown("virtual-machine-platform-absent");
|
|
200
|
-
else if (checks.virtualMachinePlatform === "unknown") addUnknown("virtual-machine-platform-unknown");
|
|
201
|
-
|
|
202
|
-
if (checks.virtualMachinePlatform === "enabled") {
|
|
203
|
-
for (const component of ["vmcompute", "hns", "vfpext"]) {
|
|
204
|
-
if (checks[component] === "missing") addKnown(`${component}-missing`);
|
|
205
|
-
else if (checks[component] === "unknown") addUnknown(`${component}-unknown`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
const componentsReady = [checks.vmcompute, checks.hns, checks.vfpext]
|
|
209
|
-
.every((state) => state.startsWith("present-"));
|
|
210
|
-
if (checks.restartPending === true && (
|
|
211
|
-
checks.virtualMachinePlatform !== "enabled" || !componentsReady
|
|
212
|
-
)) addKnown("restart-required");
|
|
213
|
-
else if (checks.restartPending === null && (
|
|
214
|
-
checks.virtualMachinePlatform !== "enabled" || !componentsReady
|
|
215
|
-
)) addUnknown("restart-state-unknown");
|
|
216
|
-
|
|
217
|
-
if (checks.machineProvisioned === false) addKnown("claude-machine-provisioning-missing");
|
|
218
|
-
else if (checks.machineProvisioned === null) addUnknown("claude-machine-provisioning-unknown");
|
|
219
|
-
|
|
220
|
-
if (checks.virtualMachine === null) addUnknown("virtualization-environment-unknown");
|
|
221
|
-
else if (checks.virtualMachine && checks.nestedVirtualization === false) addKnown("nested-virtualization-unavailable");
|
|
222
|
-
else if (checks.virtualMachine && checks.nestedVirtualization === null) addUnknown("nested-virtualization-unknown");
|
|
223
|
-
|
|
224
|
-
const enabled = checks.virtualMachinePlatform === "enabled"
|
|
225
|
-
&& componentsReady
|
|
226
|
-
&& checks.machineProvisioned === true
|
|
227
|
-
&& checks.virtualMachine !== null
|
|
228
|
-
&& (!checks.virtualMachine || checks.nestedVirtualization === true);
|
|
229
|
-
if (enabled) {
|
|
230
|
-
return {
|
|
231
|
-
schemaVersion: 1,
|
|
232
|
-
status: "available",
|
|
233
|
-
enabled: true,
|
|
234
|
-
checks,
|
|
235
|
-
reasonCodes: [],
|
|
236
|
-
remediation: [],
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const remediation = [];
|
|
241
|
-
const addRemediation = (message) => {
|
|
242
|
-
if (!remediation.includes(message)) remediation.push(message);
|
|
243
|
-
};
|
|
244
|
-
if (reasonCodes.some((reason) => reason.startsWith("virtual-machine-platform-"))) {
|
|
245
|
-
addRemediation(WINDOWS_COWORK_REMEDIATION.enableVirtualMachinePlatform);
|
|
246
|
-
}
|
|
247
|
-
if (reasonCodes.includes("restart-required")) addRemediation(WINDOWS_COWORK_REMEDIATION.restartWindows);
|
|
248
|
-
if (reasonCodes.some((reason) => /^(?:vmcompute|hns|vfpext)-missing$/u.test(reason))) {
|
|
249
|
-
addRemediation(WINDOWS_COWORK_REMEDIATION.restoreHcs);
|
|
250
|
-
}
|
|
251
|
-
if (reasonCodes.includes("claude-machine-provisioning-missing")) {
|
|
252
|
-
addRemediation(WINDOWS_COWORK_REMEDIATION.provisionClaude);
|
|
253
|
-
}
|
|
254
|
-
if (reasonCodes.includes("nested-virtualization-unavailable")) {
|
|
255
|
-
addRemediation(WINDOWS_COWORK_REMEDIATION.enableNestedVirtualization);
|
|
256
|
-
}
|
|
257
|
-
if (unknown.length) addRemediation(WINDOWS_COWORK_REMEDIATION.inspectHost);
|
|
258
|
-
|
|
259
|
-
return {
|
|
260
|
-
schemaVersion: 1,
|
|
261
|
-
status: knownUnavailable.length ? "unavailable" : "unknown",
|
|
262
|
-
enabled: false,
|
|
263
|
-
checks,
|
|
264
|
-
reasonCodes,
|
|
265
|
-
remediation,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export function inspectWindowsClaudeCoworkCapability({
|
|
270
|
-
environment = process.env,
|
|
271
|
-
platform = process.platform,
|
|
272
|
-
run = spawnSync,
|
|
273
|
-
architecture = process.arch,
|
|
274
|
-
} = {}) {
|
|
275
|
-
if (platform !== "win32") return normalizedFailure("inspector-not-windows");
|
|
276
|
-
const expectedArchitecture = /arm64/iu.test(
|
|
277
|
-
environmentValue(environment, "PROCESSOR_ARCHITEW6432")
|
|
278
|
-
|| environmentValue(environment, "PROCESSOR_ARCHITECTURE")
|
|
279
|
-
|| architecture,
|
|
280
|
-
) ? "arm64" : "x64";
|
|
281
|
-
const pin = PINNED_VENDOR_APPS.claude.windows;
|
|
282
|
-
if (windowsPackagePublisherId(pin.publisher) !== pin.publisherId) {
|
|
283
|
-
return normalizedFailure("inspector-pinned-publisher-invalid");
|
|
284
|
-
}
|
|
285
|
-
let invocation;
|
|
286
|
-
try {
|
|
287
|
-
invocation = nativeCommandInvocation("powershell", [
|
|
288
|
-
"-NoLogo",
|
|
289
|
-
"-NoProfile",
|
|
290
|
-
"-NonInteractive",
|
|
291
|
-
"-ExecutionPolicy",
|
|
292
|
-
"Bypass",
|
|
293
|
-
"-Command",
|
|
294
|
-
WINDOWS_COWORK_INSPECTION_SCRIPT,
|
|
295
|
-
], environment, "win32");
|
|
296
|
-
} catch {
|
|
297
|
-
return normalizedFailure("inspector-powershell-failed");
|
|
298
|
-
}
|
|
299
|
-
let result;
|
|
300
|
-
try {
|
|
301
|
-
result = run(invocation.command, invocation.args, {
|
|
302
|
-
encoding: "utf8",
|
|
303
|
-
env: {
|
|
304
|
-
...environment,
|
|
305
|
-
IMPEL_COWORK_EXPECTED_PACKAGE_NAME: pin.packageName,
|
|
306
|
-
IMPEL_COWORK_EXPECTED_PACKAGE_VERSION: pin.packageVersion,
|
|
307
|
-
IMPEL_COWORK_EXPECTED_PACKAGE_ARCHITECTURE: expectedArchitecture,
|
|
308
|
-
IMPEL_COWORK_EXPECTED_PACKAGE_PUBLISHER_ID: pin.publisherId,
|
|
309
|
-
},
|
|
310
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
311
|
-
windowsHide: true,
|
|
312
|
-
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
|
|
313
|
-
});
|
|
314
|
-
} catch {
|
|
315
|
-
return normalizedFailure("inspector-powershell-failed");
|
|
316
|
-
}
|
|
317
|
-
if (result?.error || result?.status !== 0) return normalizedFailure("inspector-powershell-failed");
|
|
318
|
-
try {
|
|
319
|
-
return normalizeWindowsClaudeCoworkCapability(JSON.parse(String(result.stdout || "").trim()));
|
|
320
|
-
} catch {
|
|
321
|
-
return normalizedFailure("inspector-malformed-output");
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function formatWindowsClaudeCoworkCapability(capability) {
|
|
326
|
-
if (!capability || capability.enabled === true) return [];
|
|
327
|
-
const reasons = Array.isArray(capability.reasonCodes) && capability.reasonCodes.length
|
|
328
|
-
? capability.reasonCodes.join(", ")
|
|
329
|
-
: "host-prerequisites-unknown";
|
|
330
|
-
return [
|
|
331
|
-
`Claude Cowork ${capability.status || "unknown"} (${reasons}). Chat and Code remain available.`,
|
|
332
|
-
...(Array.isArray(capability.remediation) ? capability.remediation.map((message) => ` ${message}`) : []),
|
|
333
|
-
];
|
|
334
|
-
}
|