impel-cli 0.18.13 → 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 +40 -2
- package/package.json +1 -1
- package/src/apps.js +6 -2
- package/src/commands/apps.js +3 -1
- package/src/commands/converge.js +20 -2
- package/src/commands/setup.js +20 -3
- package/src/commands/status.js +1 -1
- package/src/macSetup.js +26 -5
- package/src/platformSetup.js +23 -3
- package/src/shellEntries.js +26 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ impel update
|
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
On macOS and Linux, install the public npm package once per machine:
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
20
|
npm install --global impel-cli
|
|
@@ -22,17 +22,31 @@ npm install --global impel-cli
|
|
|
22
22
|
|
|
23
23
|
Node.js 18 or newer is required.
|
|
24
24
|
|
|
25
|
+
On Windows PowerShell, invoke npm's command shims explicitly:
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
npm.cmd install --global impel-cli
|
|
29
|
+
impel.cmd setup
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The explicit `.cmd` suffix prevents PowerShell from selecting npm's `.ps1`
|
|
33
|
+
shim when script execution is restricted. Impel does not change the machine's
|
|
34
|
+
or current user's PowerShell execution policy.
|
|
35
|
+
|
|
25
36
|
On macOS, setup and update verify Apple's Xcode Command Line Tools before any
|
|
26
37
|
profile, app, or skills work begins. If they are missing, Impel opens the
|
|
27
38
|
system installer with `xcode-select --install` and stops safely; finish that
|
|
28
39
|
installation, then rerun the same Impel command.
|
|
29
40
|
|
|
30
|
-
Then run setup and paste an Impel Personal Access Token when prompted
|
|
41
|
+
Then run setup and paste an Impel Personal Access Token when prompted. On
|
|
42
|
+
macOS and Linux:
|
|
31
43
|
|
|
32
44
|
```sh
|
|
33
45
|
impel setup
|
|
34
46
|
```
|
|
35
47
|
|
|
48
|
+
On Windows PowerShell, use `impel.cmd setup` as shown above.
|
|
49
|
+
|
|
36
50
|
Setup discovers every tenant available to the PAT and prepares all supported
|
|
37
51
|
surfaces for each tenant. You do not need to repeat setup tenant by tenant.
|
|
38
52
|
|
|
@@ -128,6 +142,12 @@ Run one command for routine maintenance:
|
|
|
128
142
|
impel update
|
|
129
143
|
```
|
|
130
144
|
|
|
145
|
+
On Windows PowerShell, keep the command shim explicit:
|
|
146
|
+
|
|
147
|
+
```powershell
|
|
148
|
+
impel.cmd update
|
|
149
|
+
```
|
|
150
|
+
|
|
131
151
|
Update:
|
|
132
152
|
|
|
133
153
|
1. On macOS, verifies the Xcode Command Line Tools and opens Apple's installer
|
|
@@ -261,6 +281,15 @@ compatibility for one release, but no command can enable native routing.
|
|
|
261
281
|
### Windows
|
|
262
282
|
|
|
263
283
|
- Missing official Claude Code and Codex CLIs can be installed during setup.
|
|
284
|
+
- Each CLI prompt explains that Impel will download, verify, and install the
|
|
285
|
+
official vendor CLI for the current user; each defaults to No.
|
|
286
|
+
- Run `impel.cmd setup` or `impel.cmd update` from PowerShell so restricted
|
|
287
|
+
script policy cannot select the npm-generated `impel.ps1` shim. Impel does
|
|
288
|
+
not modify persistent PowerShell execution policy.
|
|
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.
|
|
264
293
|
- Claude Desktop is downloaded from Anthropic as the exact x64 or arm64 MSIX
|
|
265
294
|
pinned by this CLI. Its SHA-256, Authenticode signature, package identity,
|
|
266
295
|
version, architecture, and required layout are checked before installation.
|
|
@@ -344,6 +373,15 @@ run a real package manager, or modify native personal profiles.
|
|
|
344
373
|
CI runs on Ubuntu, macOS, and Windows with supported Node versions. See
|
|
345
374
|
[RELEASING.md](RELEASING.md) for the npm release process.
|
|
346
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
|
+
|
|
347
385
|
The separate Windows vendor contract runs only on a disposable GitHub-hosted
|
|
348
386
|
Windows VM. It installs the exact newer Claude fixture that preceded the
|
|
349
387
|
current compatibility pin, invokes the production downloader and installer to
|
package/package.json
CHANGED
package/src/apps.js
CHANGED
|
@@ -232,7 +232,11 @@ export function managedAppIdentity(target, tenantId = null, tenantName = null) {
|
|
|
232
232
|
// 22: refresh GPT-5.6 model windows from the pinned ChatGPT Codex catalog.
|
|
233
233
|
// 23: select Codex's unelevated native Windows sandbox for managed profiles so
|
|
234
234
|
// enterprise machines do not enter the administrator-only setup flow.
|
|
235
|
-
|
|
235
|
+
// 24: thread the inspected Windows Cowork host capability into Claude config
|
|
236
|
+
// and the tenant manifest so unsupported hosts keep Chat and Code usable.
|
|
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;
|
|
236
240
|
|
|
237
241
|
// Identifies the bundle-BUILDING logic — the asar patches, plist rewrites,
|
|
238
242
|
// helper rebranding, and signing. A vendored bundle is rebuilt only when this
|
|
@@ -790,7 +794,7 @@ export function installManagedAppFiles({
|
|
|
790
794
|
}
|
|
791
795
|
|
|
792
796
|
writeAtomic(path.join(paths.tenantRoot, "manifest.json"), JSON.stringify({
|
|
793
|
-
schemaVersion:
|
|
797
|
+
schemaVersion: 6,
|
|
794
798
|
configVersion: CURRENT_CONFIG_VERSION,
|
|
795
799
|
modelCatalogVersion: 3,
|
|
796
800
|
gatewayUrl: config.gatewayUrl,
|
package/src/commands/apps.js
CHANGED
|
@@ -777,7 +777,9 @@ export async function cmdWindowsApps(argv, overrides = {}) {
|
|
|
777
777
|
managedChatGPT = io.stageChatGPTApp(vendorPaths.chatgpt, actionPaths.root);
|
|
778
778
|
}
|
|
779
779
|
|
|
780
|
-
const { userData } = configureWindowsApps(config, actionTargets, vendorPaths, catalog,
|
|
780
|
+
const { userData } = configureWindowsApps(config, actionTargets, vendorPaths, catalog, {
|
|
781
|
+
...io,
|
|
782
|
+
});
|
|
781
783
|
const tenantPaths = appPaths(io.homeDir, config.tenantId, {
|
|
782
784
|
claudeUserData: userData,
|
|
783
785
|
tenantName: config.tenantName,
|
package/src/commands/converge.js
CHANGED
|
@@ -123,6 +123,7 @@ export async function cmdConverge(argv = [], overrides = {}) {
|
|
|
123
123
|
confirmVendorInstall: confirmInstall,
|
|
124
124
|
}, overrides.reconcileOverrides || {});
|
|
125
125
|
|
|
126
|
+
const cliInstallDecisions = new Map();
|
|
126
127
|
let sharedFailure = null;
|
|
127
128
|
const prepareSharedClis = async ({ confirmedTools = null, inspectOnly = false } = {}) => {
|
|
128
129
|
try {
|
|
@@ -141,11 +142,20 @@ export async function cmdConverge(argv = [], overrides = {}) {
|
|
|
141
142
|
for (const tool of inspected.missingAfter) {
|
|
142
143
|
if (!inspected.installCommands?.[tool]) continue;
|
|
143
144
|
const label = tool === "claude" ? "Claude Code" : "Codex";
|
|
144
|
-
if (confirmed(await io.promptText(`
|
|
145
|
+
if (confirmed(await io.promptText(`Impel will download, verify, and install the official ${label} CLI for the current user. Continue? [y/N] `))) {
|
|
145
146
|
installTools.push(tool);
|
|
147
|
+
cliInstallDecisions.set(tool, "approved");
|
|
148
|
+
} else {
|
|
149
|
+
cliInstallDecisions.set(tool, "declined");
|
|
146
150
|
}
|
|
147
151
|
}
|
|
152
|
+
} else {
|
|
153
|
+
for (const tool of inspected.missingAfter) {
|
|
154
|
+
if (inspected.installCommands?.[tool]) cliInstallDecisions.set(tool, "confirmation-required");
|
|
155
|
+
}
|
|
148
156
|
}
|
|
157
|
+
} else {
|
|
158
|
+
for (const tool of installTools) cliInstallDecisions.set(tool, "approved");
|
|
149
159
|
}
|
|
150
160
|
if (installTools.length) {
|
|
151
161
|
prepared = await io.preparePlatformClis({
|
|
@@ -157,7 +167,15 @@ export async function cmdConverge(argv = [], overrides = {}) {
|
|
|
157
167
|
});
|
|
158
168
|
}
|
|
159
169
|
}
|
|
160
|
-
sharedFailure = prepared.missingAfter.length
|
|
170
|
+
sharedFailure = prepared.missingAfter.length
|
|
171
|
+
? describeCliFailure(prepared, {
|
|
172
|
+
commandName: "update",
|
|
173
|
+
cliExecutable: io.platform === "win32" ? "impel.cmd" : "impel",
|
|
174
|
+
isTTY: io.isTTY,
|
|
175
|
+
decisions: cliInstallDecisions,
|
|
176
|
+
skipClis,
|
|
177
|
+
})
|
|
178
|
+
: null;
|
|
161
179
|
return prepared;
|
|
162
180
|
} catch (error) {
|
|
163
181
|
sharedFailure = redactSecretText(error?.message || error);
|
package/src/commands/setup.js
CHANGED
|
@@ -253,6 +253,7 @@ export async function cmdSetup(argv, overrides = {}) {
|
|
|
253
253
|
return allowed;
|
|
254
254
|
};
|
|
255
255
|
|
|
256
|
+
const cliInstallDecisions = new Map();
|
|
256
257
|
let sharedFailure = null;
|
|
257
258
|
const prepareSharedClis = async ({ confirmedTools = null, inspectOnly = false } = {}) => {
|
|
258
259
|
try {
|
|
@@ -271,11 +272,20 @@ export async function cmdSetup(argv, overrides = {}) {
|
|
|
271
272
|
for (const tool of inspected.missingAfter) {
|
|
272
273
|
if (!inspected.installCommands?.[tool]) continue;
|
|
273
274
|
const label = tool === "claude" ? "Claude Code" : "Codex";
|
|
274
|
-
if (confirmed(await io.promptText(`
|
|
275
|
+
if (confirmed(await io.promptText(`Impel will download, verify, and install the official ${label} CLI for the current user. Continue? [y/N] `))) {
|
|
275
276
|
installTools.push(tool);
|
|
277
|
+
cliInstallDecisions.set(tool, "approved");
|
|
278
|
+
} else {
|
|
279
|
+
cliInstallDecisions.set(tool, "declined");
|
|
276
280
|
}
|
|
277
281
|
}
|
|
282
|
+
} else {
|
|
283
|
+
for (const tool of inspected.missingAfter) {
|
|
284
|
+
if (inspected.installCommands?.[tool]) cliInstallDecisions.set(tool, "confirmation-required");
|
|
285
|
+
}
|
|
278
286
|
}
|
|
287
|
+
} else {
|
|
288
|
+
for (const tool of installTools) cliInstallDecisions.set(tool, "approved");
|
|
279
289
|
}
|
|
280
290
|
if (installTools.length) {
|
|
281
291
|
prepared = await io.preparePlatformClis({
|
|
@@ -288,7 +298,13 @@ export async function cmdSetup(argv, overrides = {}) {
|
|
|
288
298
|
}
|
|
289
299
|
}
|
|
290
300
|
if (prepared.missingAfter.length) {
|
|
291
|
-
sharedFailure = describeCliFailure(prepared
|
|
301
|
+
sharedFailure = describeCliFailure(prepared, {
|
|
302
|
+
commandName: "setup",
|
|
303
|
+
cliExecutable: io.platform === "win32" ? "impel.cmd" : "impel",
|
|
304
|
+
isTTY: io.isTTY,
|
|
305
|
+
decisions: cliInstallDecisions,
|
|
306
|
+
skipClis: flags["skip-clis"],
|
|
307
|
+
});
|
|
292
308
|
return prepared;
|
|
293
309
|
}
|
|
294
310
|
sharedFailure = null;
|
|
@@ -476,7 +492,8 @@ export async function cmdSetup(argv, overrides = {}) {
|
|
|
476
492
|
: "Launch: no provider CLI is currently available for this tenant");
|
|
477
493
|
if (!report.passed || sharedFailure) {
|
|
478
494
|
if (sharedFailure) console.error(`Shared setup failure: ${sharedFailure}`);
|
|
479
|
-
|
|
495
|
+
const setupCommand = io.platform === "win32" ? "impel.cmd setup" : "impel setup";
|
|
496
|
+
console.error(`Setup is incomplete. Fix the reported issue or rerun \`${setupCommand}\`.`);
|
|
480
497
|
process.exitCode = 1;
|
|
481
498
|
return report;
|
|
482
499
|
}
|
package/src/commands/status.js
CHANGED
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/platformSetup.js
CHANGED
|
@@ -31,19 +31,39 @@ export async function preparePlatformClis(options = {}, dependencies = {}) {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function describeCliFailure(prepared
|
|
34
|
+
export function describeCliFailure(prepared, {
|
|
35
|
+
commandName = "setup",
|
|
36
|
+
cliExecutable = "impel",
|
|
37
|
+
isTTY = true,
|
|
38
|
+
decisions = {},
|
|
39
|
+
skipClis = false,
|
|
40
|
+
} = {}) {
|
|
35
41
|
const labels = { claude: "Claude Code", codex: "Codex" };
|
|
36
42
|
const details = [];
|
|
37
43
|
for (const tool of prepared.missingAfter || []) {
|
|
38
44
|
const installation = prepared.installations?.[tool];
|
|
39
45
|
const failure = installation?.failure;
|
|
46
|
+
const decision = decisions instanceof Map ? decisions.get(tool) : decisions?.[tool];
|
|
47
|
+
const attempted = Boolean(
|
|
48
|
+
installation?.attempted
|
|
49
|
+
|| failure
|
|
50
|
+
|| installation?.succeeded === false,
|
|
51
|
+
);
|
|
40
52
|
const reason = failure?.message || failure?.code
|
|
41
53
|
|| (Number.isInteger(failure?.status)
|
|
42
54
|
? `exit ${failure.status}`
|
|
43
|
-
:
|
|
55
|
+
: attempted
|
|
56
|
+
? "not discoverable after installation"
|
|
57
|
+
: decision === "declined"
|
|
58
|
+
? "automatic installation was declined"
|
|
59
|
+
: skipClis
|
|
60
|
+
? "installation was skipped by --skip-clis"
|
|
61
|
+
: !isTTY && prepared.installCommands?.[tool]
|
|
62
|
+
? `interactive confirmation required; rerun \`${cliExecutable} ${commandName}\` interactively (or use \`--skip-clis\` to leave it uninstalled)`
|
|
63
|
+
: "not installed or discoverable");
|
|
44
64
|
details.push(`${labels[tool] || tool}: ${redactSecretText(reason)}`);
|
|
45
65
|
const command = installation?.command || prepared.installCommands?.[tool];
|
|
46
|
-
if (command) details.push(`manual ${tool}
|
|
66
|
+
if (attempted && command) details.push(`manual ${tool} recovery command: ${redactSecretText(command)}`);
|
|
47
67
|
}
|
|
48
68
|
return details.join("; ") || `missing vendor CLIs: ${(prepared.missingAfter || []).join(", ")}`;
|
|
49
69
|
}
|
package/src/shellEntries.js
CHANGED
|
@@ -8,7 +8,11 @@ import { redactSecretText } from "./config.js";
|
|
|
8
8
|
import { environmentValue, nativeCommandInvocation, resolveNativeBinary } from "./nativeProcess.js";
|
|
9
9
|
import { managedCliEntrypoint } from "./selfInvocation.js";
|
|
10
10
|
import { normalizeTenantId } from "./tenants.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
findManagedWindowsChatGPTApp,
|
|
13
|
+
findWindowsClaudeApp,
|
|
14
|
+
windowsClaudeUserData,
|
|
15
|
+
} from "./windowsApps.js";
|
|
12
16
|
import { RUNTIME_BRAND } from "./runtimeBrand.js";
|
|
13
17
|
|
|
14
18
|
const LSREGISTER = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
|
|
@@ -151,6 +155,8 @@ export function registerTenantShellEntries({
|
|
|
151
155
|
run = spawnSync,
|
|
152
156
|
writeManifest = writeShellEntryManifest,
|
|
153
157
|
shortcut = registerWindowsTenantShortcut,
|
|
158
|
+
findClaudeApp = findWindowsClaudeApp,
|
|
159
|
+
findChatGPTApp = findManagedWindowsChatGPTApp,
|
|
154
160
|
} = {}) {
|
|
155
161
|
const id = normalizeTenantId(tenantId);
|
|
156
162
|
const paths = appPaths(homeDir, id, {
|
|
@@ -175,9 +181,27 @@ export function registerTenantShellEntries({
|
|
|
175
181
|
};
|
|
176
182
|
for (const product of targets) {
|
|
177
183
|
if (!available[product]) throw new Error(`managed ${product} profile is missing for tenant ${id}`);
|
|
184
|
+
let iconExecutable;
|
|
185
|
+
try {
|
|
186
|
+
iconExecutable = product === "claude"
|
|
187
|
+
? findClaudeApp(environment)
|
|
188
|
+
: findChatGPTApp(paths.root);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
throw new Error(`could not resolve ${product} shortcut icon (${redactSecretText(error?.message || String(error))})`);
|
|
191
|
+
}
|
|
192
|
+
if (!iconExecutable || !path.win32.isAbsolute(iconExecutable)) {
|
|
193
|
+
throw new Error(`managed ${product} shortcut icon executable is unavailable for tenant ${id}`);
|
|
194
|
+
}
|
|
178
195
|
registered.push({
|
|
179
196
|
product,
|
|
180
|
-
path: shortcut({
|
|
197
|
+
path: shortcut({
|
|
198
|
+
product,
|
|
199
|
+
tenantId: id,
|
|
200
|
+
tenantName,
|
|
201
|
+
environment,
|
|
202
|
+
run,
|
|
203
|
+
iconPath: `${path.win32.normalize(iconExecutable)},0`,
|
|
204
|
+
}),
|
|
181
205
|
status: "ready",
|
|
182
206
|
});
|
|
183
207
|
}
|