impel-cli 0.18.4 → 0.18.5
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 +12 -3
- package/package.json +3 -2
- package/src/apps.js +42 -35
- package/src/cliProfiles.js +36 -33
- package/src/commands/apps.js +35 -28
- package/src/commands/auth.js +6 -4
- package/src/commands/launch.js +12 -8
- package/src/commands/pat.js +46 -37
- package/src/commands/setup.js +11 -8
- package/src/commands/status.js +7 -5
- package/src/commands/token.js +3 -2
- package/src/config.js +14 -10
- package/src/extension/index.js +81 -0
- package/src/provisioning.js +5 -4
- package/src/runtimeBrand.js +140 -0
- package/src/selfInvocation.js +7 -3
- package/src/shellEntries.js +5 -2
- package/src/stableEntrypoint.js +18 -12
- package/src/tenants.js +15 -7
- package/src/windowsApps.js +7 -6
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
const SAFE_ID = /^[a-z][a-z0-9-]{1,63}$/u;
|
|
4
|
+
const SAFE_NAMESPACE = /^[a-z][a-z0-9_-]{1,63}$/u;
|
|
5
|
+
const SAFE_PREFIX = /^[a-z][a-z0-9_]{2,63}_$/u;
|
|
6
|
+
const SAFE_ENV = /^[A-Z][A-Z0-9_]{1,63}$/u;
|
|
7
|
+
const SAFE_TENANT = /^[A-Za-z0-9_.-]{1,128}$/u;
|
|
8
|
+
const SAFE_PACKAGE = /^(?:@[a-z0-9][a-z0-9._-]{0,62}\/)?[a-z0-9][a-z0-9._-]{0,126}$/u;
|
|
9
|
+
const SAFE_BUNDLE_PREFIX = /^[A-Za-z0-9]+(?:[.-][A-Za-z0-9]+)+$/u;
|
|
10
|
+
const CONTROL_RE = /[\u0000-\u001F\u007F-\u009F]/u;
|
|
11
|
+
const SUPPORTED_COMMANDS = new Set(["setup", "auth", "pat", "app", "claude", "codex", "status", "token"]);
|
|
12
|
+
|
|
13
|
+
const DEFAULT = Object.freeze({
|
|
14
|
+
schemaVersion: 1,
|
|
15
|
+
product: Object.freeze({ id: "impel", displayName: "Impel" }),
|
|
16
|
+
cli: Object.freeze({
|
|
17
|
+
command: "impel",
|
|
18
|
+
packageName: "impel-cli",
|
|
19
|
+
configNamespace: "impel",
|
|
20
|
+
providerId: "impel",
|
|
21
|
+
managedMarker: "impel-cli",
|
|
22
|
+
environmentPrefix: "IMPEL",
|
|
23
|
+
}),
|
|
24
|
+
auth: Object.freeze({ patPrefix: "impel_pat_", tenantPrefix: "impel_tenant_" }),
|
|
25
|
+
tenant: Object.freeze({ defaultId: null, displayName: null }),
|
|
26
|
+
gateway: Object.freeze({ defaultOrigin: "https://gateway.useimpel.com" }),
|
|
27
|
+
controlPlane: Object.freeze({ defaultOrigin: "https://www.useimpel.com" }),
|
|
28
|
+
apps: Object.freeze({
|
|
29
|
+
displayPrefix: "Impel",
|
|
30
|
+
bundleIdentifierPrefix: "com.useimpel",
|
|
31
|
+
windowsStartMenuFolder: "Impel",
|
|
32
|
+
}),
|
|
33
|
+
capabilities: Object.freeze({ commands: Object.freeze([...SUPPORTED_COMMANDS]), agentPats: true }),
|
|
34
|
+
features: Object.freeze({ sessions: true, mcp: true, skills: true, agents: true }),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function text(name, value, pattern = null) {
|
|
38
|
+
if (typeof value !== "string" || !value || value.trim() !== value || value.length > 128 || CONTROL_RE.test(value)) {
|
|
39
|
+
throw new Error(`impel-cli runtime brand ${name} is invalid`);
|
|
40
|
+
}
|
|
41
|
+
if (pattern && !pattern.test(value)) throw new Error(`impel-cli runtime brand ${name} is invalid`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function origin(name, value) {
|
|
46
|
+
const parsed = new URL(text(name, value));
|
|
47
|
+
const local = parsed.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname);
|
|
48
|
+
if ((parsed.protocol !== "https:" && !local) || parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
49
|
+
throw new Error(`impel-cli runtime brand ${name} must be a bare HTTPS origin`);
|
|
50
|
+
}
|
|
51
|
+
return parsed.toString().replace(/\/$/u, "");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function pathSegment(name, value) {
|
|
55
|
+
const result = text(name, value);
|
|
56
|
+
if (result === "." || result === ".." || /[\\/:]/u.test(result)) {
|
|
57
|
+
throw new Error(`impel-cli runtime brand ${name} is invalid`);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function validateRuntimeBrand(input) {
|
|
63
|
+
if (!input || Array.isArray(input) || typeof input !== "object" || input.schemaVersion !== 1) {
|
|
64
|
+
throw new Error("impel-cli runtime brand schemaVersion must be 1");
|
|
65
|
+
}
|
|
66
|
+
const command = text("cli.command", input.cli?.command, SAFE_ID);
|
|
67
|
+
const packageName = text("cli.packageName", input.cli?.packageName || command, SAFE_PACKAGE);
|
|
68
|
+
if (path.isAbsolute(packageName)) throw new Error("impel-cli runtime brand cli.packageName is invalid");
|
|
69
|
+
const defaultTenant = input.tenant?.defaultId == null ? null : text("tenant.defaultId", input.tenant.defaultId, SAFE_TENANT);
|
|
70
|
+
const requestedCommands = input.capabilities?.commands;
|
|
71
|
+
if (!Array.isArray(requestedCommands) || requestedCommands.length === 0) {
|
|
72
|
+
throw new Error("impel-cli runtime brand capabilities.commands must be a non-empty array");
|
|
73
|
+
}
|
|
74
|
+
const commands = [...new Set(requestedCommands.map((commandName) => text("capabilities.commands", commandName, SAFE_ID)))];
|
|
75
|
+
if (commands.some((commandName) => !SUPPORTED_COMMANDS.has(commandName))) {
|
|
76
|
+
throw new Error("impel-cli runtime brand capabilities.commands contains an unsupported command");
|
|
77
|
+
}
|
|
78
|
+
return Object.freeze({
|
|
79
|
+
schemaVersion: 1,
|
|
80
|
+
product: Object.freeze({
|
|
81
|
+
id: text("product.id", input.product?.id, SAFE_ID),
|
|
82
|
+
displayName: text("product.displayName", input.product?.displayName),
|
|
83
|
+
}),
|
|
84
|
+
cli: Object.freeze({
|
|
85
|
+
command,
|
|
86
|
+
packageName,
|
|
87
|
+
configNamespace: text("cli.configNamespace", input.cli?.configNamespace, SAFE_NAMESPACE),
|
|
88
|
+
providerId: text("cli.providerId", input.cli?.providerId, SAFE_NAMESPACE),
|
|
89
|
+
managedMarker: text("cli.managedMarker", input.cli?.managedMarker, SAFE_NAMESPACE),
|
|
90
|
+
environmentPrefix: text("cli.environmentPrefix", input.cli?.environmentPrefix, SAFE_ENV),
|
|
91
|
+
}),
|
|
92
|
+
auth: Object.freeze({
|
|
93
|
+
patPrefix: text("auth.patPrefix", input.auth?.patPrefix, SAFE_PREFIX),
|
|
94
|
+
tenantPrefix: text("auth.tenantPrefix", input.auth?.tenantPrefix, SAFE_PREFIX),
|
|
95
|
+
}),
|
|
96
|
+
tenant: Object.freeze({
|
|
97
|
+
defaultId: defaultTenant,
|
|
98
|
+
displayName: input.tenant?.displayName == null ? defaultTenant : text("tenant.displayName", input.tenant.displayName),
|
|
99
|
+
}),
|
|
100
|
+
gateway: Object.freeze({ defaultOrigin: origin("gateway.defaultOrigin", input.gateway?.defaultOrigin) }),
|
|
101
|
+
controlPlane: Object.freeze({ defaultOrigin: origin("controlPlane.defaultOrigin", input.controlPlane?.defaultOrigin) }),
|
|
102
|
+
apps: Object.freeze({
|
|
103
|
+
displayPrefix: pathSegment("apps.displayPrefix", input.apps?.displayPrefix || input.product?.displayName),
|
|
104
|
+
bundleIdentifierPrefix: text("apps.bundleIdentifierPrefix", input.apps?.bundleIdentifierPrefix, SAFE_BUNDLE_PREFIX),
|
|
105
|
+
windowsStartMenuFolder: pathSegment("apps.windowsStartMenuFolder", input.apps?.windowsStartMenuFolder || input.product?.displayName),
|
|
106
|
+
}),
|
|
107
|
+
capabilities: Object.freeze({
|
|
108
|
+
commands: Object.freeze(commands),
|
|
109
|
+
agentPats: input.capabilities?.agentPats === true,
|
|
110
|
+
}),
|
|
111
|
+
features: Object.freeze({
|
|
112
|
+
sessions: input.features?.sessions === true,
|
|
113
|
+
mcp: input.features?.mcp === true,
|
|
114
|
+
skills: input.features?.skills === true,
|
|
115
|
+
agents: input.features?.agents === true,
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function loadRuntimeBrand() {
|
|
121
|
+
const encoded = process.env.IMPEL_CLI_RUNTIME_BRAND;
|
|
122
|
+
if (!encoded) return DEFAULT;
|
|
123
|
+
let parsed;
|
|
124
|
+
try { parsed = JSON.parse(encoded); }
|
|
125
|
+
catch { throw new Error("IMPEL_CLI_RUNTIME_BRAND must contain valid JSON"); }
|
|
126
|
+
return validateRuntimeBrand(parsed);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const RUNTIME_BRAND = loadRuntimeBrand();
|
|
130
|
+
|
|
131
|
+
export function brandedEnvironmentName(suffix) {
|
|
132
|
+
return `${RUNTIME_BRAND.cli.environmentPrefix}_${suffix}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function brandedText(value) {
|
|
136
|
+
return String(value)
|
|
137
|
+
.replaceAll("impel-cli", `${RUNTIME_BRAND.cli.command}-cli`)
|
|
138
|
+
.replaceAll("Impel", RUNTIME_BRAND.product.displayName)
|
|
139
|
+
.replaceAll("impel", RUNTIME_BRAND.cli.command);
|
|
140
|
+
}
|
package/src/selfInvocation.js
CHANGED
|
@@ -2,9 +2,11 @@ import path from "node:path";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
|
|
4
4
|
import { environmentValue } from "./nativeProcess.js";
|
|
5
|
+
import { RUNTIME_BRAND } from "./runtimeBrand.js";
|
|
5
6
|
|
|
6
7
|
/** The running package's own bin script, used directly for live child spawns. */
|
|
7
|
-
export const IMPEL_CLI_ENTRYPOINT =
|
|
8
|
+
export const IMPEL_CLI_ENTRYPOINT = process.env.IMPEL_CLI_EXTENSION_ENTRYPOINT
|
|
9
|
+
|| fileURLToPath(new URL("../bin/impel.js", import.meta.url));
|
|
8
10
|
|
|
9
11
|
// Matches a real package install (npm/pnpm/volta all place the package under
|
|
10
12
|
// node_modules/impel-cli; an unsupported project-local install matches too,
|
|
@@ -12,7 +14,9 @@ export const IMPEL_CLI_ENTRYPOINT = fileURLToPath(new URL("../bin/impel.js", imp
|
|
|
12
14
|
// install). A git checkout or worktree never matches, so development runs
|
|
13
15
|
// keep baking their own path instead of repointing the machine-wide stable
|
|
14
16
|
// entry at a checkout.
|
|
15
|
-
const
|
|
17
|
+
const escapedPackageName = RUNTIME_BRAND.cli.packageName.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&").replaceAll("/", "[\\\\/]");
|
|
18
|
+
const escapedCommand = RUNTIME_BRAND.cli.command.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
19
|
+
const GLOBAL_INSTALL_RE = new RegExp(`[\\\\/]node_modules[\\\\/]${escapedPackageName}[\\\\/]bin[\\\\/]${escapedCommand}\\.js$`, "iu");
|
|
16
20
|
|
|
17
21
|
export function runningFromGlobalInstall(entrypoint = IMPEL_CLI_ENTRYPOINT) {
|
|
18
22
|
return GLOBAL_INSTALL_RE.test(entrypoint);
|
|
@@ -22,7 +26,7 @@ export function runningFromGlobalInstall(entrypoint = IMPEL_CLI_ENTRYPOINT) {
|
|
|
22
26
|
export function windowsStableEntrypointPath(environment = process.env) {
|
|
23
27
|
const localAppData = environmentValue(environment, "LOCALAPPDATA");
|
|
24
28
|
if (typeof localAppData !== "string" || !localAppData.trim()) return null;
|
|
25
|
-
return path.join(localAppData,
|
|
29
|
+
return path.join(localAppData, RUNTIME_BRAND.apps.windowsStartMenuFolder, "bin", `${RUNTIME_BRAND.cli.command}-entry.cjs`);
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
/** The directory `impel nuke` removes to erase the stable entry point. */
|
package/src/shellEntries.js
CHANGED
|
@@ -9,6 +9,7 @@ import { environmentValue, nativeCommandInvocation, resolveNativeBinary } from "
|
|
|
9
9
|
import { managedCliEntrypoint } from "./selfInvocation.js";
|
|
10
10
|
import { normalizeTenantId } from "./tenants.js";
|
|
11
11
|
import { windowsClaudeUserData } from "./windowsApps.js";
|
|
12
|
+
import { RUNTIME_BRAND } from "./runtimeBrand.js";
|
|
12
13
|
|
|
13
14
|
const LSREGISTER = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
|
|
14
15
|
|
|
@@ -42,7 +43,9 @@ export function windowsShortcutArgument(value) {
|
|
|
42
43
|
export function windowsTenantShortcutName(product, tenantId, tenantName) {
|
|
43
44
|
const id = normalizeTenantId(tenantId);
|
|
44
45
|
const name = safeShortcutSegment(tenantName, id);
|
|
45
|
-
const label = product === "claude"
|
|
46
|
+
const label = product === "claude"
|
|
47
|
+
? `${RUNTIME_BRAND.apps.displayPrefix} Claude`
|
|
48
|
+
: `${RUNTIME_BRAND.apps.displayPrefix} ChatGPT`;
|
|
46
49
|
return `${label} (${name} · ${id}).lnk`;
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -59,7 +62,7 @@ export function registerWindowsTenantShortcut({
|
|
|
59
62
|
}) {
|
|
60
63
|
const appData = environmentValue(environment, "APPDATA");
|
|
61
64
|
if (!appData) throw new Error("Windows APPDATA is unavailable; Start menu entry was not created");
|
|
62
|
-
const shortcutDirectory = path.win32.join(appData, "Microsoft", "Windows", "Start Menu", "Programs",
|
|
65
|
+
const shortcutDirectory = path.win32.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", RUNTIME_BRAND.apps.windowsStartMenuFolder);
|
|
63
66
|
mkdirSync(shortcutDirectory, { recursive: true });
|
|
64
67
|
const shortcutPath = path.win32.join(
|
|
65
68
|
shortcutDirectory,
|
package/src/stableEntrypoint.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
windowsStableEntrypointPath,
|
|
8
8
|
} from "./selfInvocation.js";
|
|
9
9
|
import { redactSecretText } from "./config.js";
|
|
10
|
+
import { brandedEnvironmentName, RUNTIME_BRAND } from "./runtimeBrand.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Render the stable Windows entry point (`%LOCALAPPDATA%\Impel\bin\impel-entry.cjs`).
|
|
@@ -22,12 +23,15 @@ import { redactSecretText } from "./config.js";
|
|
|
22
23
|
* supported Node (>=18). Output is deterministic for a given baked path.
|
|
23
24
|
*/
|
|
24
25
|
export function renderStableEntrypoint(cliEntrypoint = IMPEL_CLI_ENTRYPOINT) {
|
|
26
|
+
const packageParts = RUNTIME_BRAND.cli.packageName.split("/");
|
|
27
|
+
const cliSuffixParts = ["node_modules", ...packageParts, "bin", `${RUNTIME_BRAND.cli.command}.js`];
|
|
28
|
+
const completeInstallFile = RUNTIME_BRAND.cli.packageName === "impel-cli" ? path.join("src", "cli.js") : null;
|
|
25
29
|
return `#!/usr/bin/env node
|
|
26
30
|
"use strict";
|
|
27
|
-
// Managed by
|
|
31
|
+
// Managed by ${RUNTIME_BRAND.cli.command}-cli — stable ${RUNTIME_BRAND.product.displayName} entry point.
|
|
28
32
|
//
|
|
29
|
-
// Vendor apps (
|
|
30
|
-
// this file in their managed profiles. It resolves the current
|
|
33
|
+
// Vendor apps (${RUNTIME_BRAND.apps.displayPrefix} Claude, ${RUNTIME_BRAND.apps.displayPrefix} ChatGPT) persist absolute invocations of
|
|
34
|
+
// this file in their managed profiles. It resolves the current ${RUNTIME_BRAND.cli.packageName}
|
|
31
35
|
// global install at run time, so replacing or moving the npm global package
|
|
32
36
|
// never strands the invocations baked into those profiles.
|
|
33
37
|
|
|
@@ -37,10 +41,12 @@ const { pathToFileURL } = require("node:url");
|
|
|
37
41
|
const { spawnSync } = require("node:child_process");
|
|
38
42
|
|
|
39
43
|
const BAKED_CLI = ${JSON.stringify(cliEntrypoint)};
|
|
40
|
-
const CLI_SUFFIX = path.join(
|
|
44
|
+
const CLI_SUFFIX = path.join(${cliSuffixParts.map((part) => JSON.stringify(part)).join(", ")});
|
|
45
|
+
const PACKAGE_NAME = ${JSON.stringify(RUNTIME_BRAND.cli.packageName)};
|
|
46
|
+
const REQUIRED_FILE = ${JSON.stringify(completeInstallFile)};
|
|
41
47
|
|
|
42
48
|
function waitBudgetMs() {
|
|
43
|
-
const raw = Number.parseInt(process.env
|
|
49
|
+
const raw = Number.parseInt(process.env.${brandedEnvironmentName("ENTRYPOINT_WAIT_MS")} || "", 10);
|
|
44
50
|
if (Number.isFinite(raw) && raw >= 0) return Math.min(raw, 60000);
|
|
45
51
|
return 8000;
|
|
46
52
|
}
|
|
@@ -73,8 +79,8 @@ function isFile(candidate) {
|
|
|
73
79
|
function isCompleteInstall(cliPath) {
|
|
74
80
|
try {
|
|
75
81
|
const root = path.dirname(path.dirname(cliPath));
|
|
76
|
-
if (!isFile(cliPath) || !isFile(path.join(root,
|
|
77
|
-
return JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).name ===
|
|
82
|
+
if (!isFile(cliPath) || (REQUIRED_FILE && !isFile(path.join(root, REQUIRED_FILE)))) return false;
|
|
83
|
+
return JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).name === PACKAGE_NAME;
|
|
78
84
|
} catch {
|
|
79
85
|
return false;
|
|
80
86
|
}
|
|
@@ -106,8 +112,8 @@ async function main() {
|
|
|
106
112
|
if (!cli) cli = candidateCliPaths().find(isFile) || null;
|
|
107
113
|
if (!cli) {
|
|
108
114
|
process.stderr.write(
|
|
109
|
-
"
|
|
110
|
-
"Run \`npm install -g
|
|
115
|
+
"${RUNTIME_BRAND.cli.command}-entry: no ${RUNTIME_BRAND.cli.packageName} install found (checked " + candidateCliPaths().join("; ") + "). " +
|
|
116
|
+
"Run \`npm install -g ${RUNTIME_BRAND.cli.packageName}\`, then \`${RUNTIME_BRAND.cli.command} setup\`.\\n"
|
|
111
117
|
);
|
|
112
118
|
process.exit(1);
|
|
113
119
|
}
|
|
@@ -125,7 +131,7 @@ async function main() {
|
|
|
125
131
|
});
|
|
126
132
|
if (typeof rerun.status === "number") process.exit(rerun.status);
|
|
127
133
|
process.stderr.write(
|
|
128
|
-
"
|
|
134
|
+
"${RUNTIME_BRAND.cli.command}-entry: could not start " + cli + " (" +
|
|
129
135
|
(error && error.message ? error.message : String(error)) + ")\\n"
|
|
130
136
|
);
|
|
131
137
|
process.exit(1);
|
|
@@ -134,7 +140,7 @@ async function main() {
|
|
|
134
140
|
|
|
135
141
|
main().catch((error) => {
|
|
136
142
|
process.stderr.write(
|
|
137
|
-
"
|
|
143
|
+
"${RUNTIME_BRAND.cli.command}-entry: " + (error && error.message ? error.message : String(error)) + "\\n"
|
|
138
144
|
);
|
|
139
145
|
process.exit(1);
|
|
140
146
|
});
|
|
@@ -187,7 +193,7 @@ export function ensureWindowsStableEntrypointQuietly(options = {}) {
|
|
|
187
193
|
return ensureWindowsStableEntrypoint(options);
|
|
188
194
|
} catch (error) {
|
|
189
195
|
console.error(
|
|
190
|
-
|
|
196
|
+
`${RUNTIME_BRAND.cli.command}: could not refresh the stable Windows entry point (${redactSecretText(error?.message || error)})`
|
|
191
197
|
);
|
|
192
198
|
return null;
|
|
193
199
|
}
|
package/src/tenants.js
CHANGED
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
resolveDefaultAppUrl,
|
|
5
5
|
saveConfig,
|
|
6
6
|
} from "./config.js";
|
|
7
|
+
import { RUNTIME_BRAND } from "./runtimeBrand.js";
|
|
7
8
|
|
|
8
|
-
export const TENANT_CREDENTIAL_PREFIX =
|
|
9
|
+
export const TENANT_CREDENTIAL_PREFIX = RUNTIME_BRAND.auth.tenantPrefix;
|
|
9
10
|
export const PRODUCT_ACCESS_WORKSPACE = "workspace";
|
|
10
11
|
export const PRODUCT_ACCESS_IDENTITY = "identity";
|
|
11
12
|
export const PRODUCT_ACCESS_GATEWAY = "gateway";
|
|
@@ -85,15 +86,15 @@ function normalizeTenantName(value, fallback) {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
export function tenantCredential(pat, tenantId) {
|
|
88
|
-
if (!String(pat || "").startsWith(
|
|
89
|
-
throw new Error(
|
|
89
|
+
if (!String(pat || "").startsWith(RUNTIME_BRAND.auth.patPrefix)) {
|
|
90
|
+
throw new Error(`a ${RUNTIME_BRAND.product.displayName} PAT is required to create a tenant credential`);
|
|
90
91
|
}
|
|
91
92
|
const encodedTenant = Buffer.from(normalizeTenantId(tenantId), "utf8").toString("base64url");
|
|
92
93
|
return `${TENANT_CREDENTIAL_PREFIX}${encodedTenant}.${pat}`;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
export async function fetchTenants(config, fetchImpl = fetch) {
|
|
96
|
-
if (!config?.pat) throw new Error(
|
|
97
|
+
if (!config?.pat) throw new Error(`not authenticated; run \`${RUNTIME_BRAND.cli.command} setup\` (or \`${RUNTIME_BRAND.cli.command} auth\`) first`);
|
|
97
98
|
const appUrl = normalizeGatewayUrl(config.appUrl || resolveDefaultAppUrl());
|
|
98
99
|
const controller = new AbortController();
|
|
99
100
|
const timeout = setTimeout(() => controller.abort(), 7_000);
|
|
@@ -141,9 +142,16 @@ export async function fetchTenants(config, fetchImpl = fetch) {
|
|
|
141
142
|
if (!tenants.some((tenant) => tenant.id === defaultTenantId)) {
|
|
142
143
|
throw new Error("tenant API default is not in the available tenant list");
|
|
143
144
|
}
|
|
145
|
+
const fixedTenant = RUNTIME_BRAND.tenant.defaultId;
|
|
146
|
+
const visibleTenants = fixedTenant
|
|
147
|
+
? tenants.filter((tenant) => tenant.id === fixedTenant || tenant.slug === fixedTenant)
|
|
148
|
+
: tenants;
|
|
149
|
+
if (fixedTenant && visibleTenants.length !== 1) {
|
|
150
|
+
throw new Error(`the required ${RUNTIME_BRAND.product.displayName} tenant is not available`);
|
|
151
|
+
}
|
|
144
152
|
return {
|
|
145
|
-
tenants,
|
|
146
|
-
defaultTenantId,
|
|
153
|
+
tenants: visibleTenants,
|
|
154
|
+
defaultTenantId: fixedTenant ? visibleTenants[0].id : defaultTenantId,
|
|
147
155
|
patTenantId: payload.patTenantId || null,
|
|
148
156
|
productAccess,
|
|
149
157
|
scopes,
|
|
@@ -151,7 +159,7 @@ export async function fetchTenants(config, fetchImpl = fetch) {
|
|
|
151
159
|
}
|
|
152
160
|
|
|
153
161
|
export async function ensureTenantSelection(config, { refresh = false } = {}) {
|
|
154
|
-
if (!config?.pat) throw new Error(
|
|
162
|
+
if (!config?.pat) throw new Error(`not authenticated; run \`${RUNTIME_BRAND.cli.command} setup\` (or \`${RUNTIME_BRAND.cli.command} auth\`) first`);
|
|
155
163
|
if (config.tenantId && !refresh) {
|
|
156
164
|
const tenantId = normalizeTenantId(config.tenantId);
|
|
157
165
|
return {
|
package/src/windowsApps.js
CHANGED
|
@@ -9,6 +9,7 @@ import { pipeline } from "node:stream/promises";
|
|
|
9
9
|
import { PINNED_VENDOR_APPS } from "./apps.js";
|
|
10
10
|
import { environmentValue, nativeCommandInvocation } from "./nativeProcess.js";
|
|
11
11
|
import { normalizeTenantId } from "./tenants.js";
|
|
12
|
+
import { brandedEnvironmentName, RUNTIME_BRAND } from "./runtimeBrand.js";
|
|
12
13
|
|
|
13
14
|
export const WINDOWS_CHATGPT_PACKAGE = "9PLM9XGG6VKS";
|
|
14
15
|
export const WINDOWS_CHATGPT_PACKAGE_NAME = "OpenAI.Codex";
|
|
@@ -22,7 +23,7 @@ export function windowsClaudeUserData(environment = process.env, tenantId = null
|
|
|
22
23
|
return path.win32.join(
|
|
23
24
|
localAppData,
|
|
24
25
|
"Claude-3p",
|
|
25
|
-
|
|
26
|
+
RUNTIME_BRAND.apps.windowsStartMenuFolder,
|
|
26
27
|
normalizeTenantId(tenantId || "default"),
|
|
27
28
|
);
|
|
28
29
|
}
|
|
@@ -85,7 +86,7 @@ function installedMsixChatGPT(environment, run = spawnSync) {
|
|
|
85
86
|
/** Candidate paths for Anthropic's signed per-user Windows desktop install. */
|
|
86
87
|
export function windowsClaudeAppCandidates(environment = process.env, dependencies = {}) {
|
|
87
88
|
const io = { readDirectory: fs.readdirSync, ...dependencies };
|
|
88
|
-
const overridden = environmentValue(environment, "
|
|
89
|
+
const overridden = environmentValue(environment, brandedEnvironmentName("CLAUDE_APP_BIN"));
|
|
89
90
|
const localAppData = environmentValue(environment, "LOCALAPPDATA")
|
|
90
91
|
|| path.win32.join(environmentValue(environment, "USERPROFILE") || os.homedir(), "AppData", "Local");
|
|
91
92
|
const programFiles = environmentValue(environment, "ProgramFiles");
|
|
@@ -114,8 +115,8 @@ export function findWindowsClaudeApp(environment = process.env, dependencies = {
|
|
|
114
115
|
|
|
115
116
|
/** Candidate paths for an unpackaged OpenAI ChatGPT/Codex desktop install. */
|
|
116
117
|
export function windowsChatGPTAppCandidates(environment = process.env) {
|
|
117
|
-
const overridden = environmentValue(environment, "
|
|
118
|
-
|| environmentValue(environment, "
|
|
118
|
+
const overridden = environmentValue(environment, brandedEnvironmentName("CHATGPT_APP_BIN"))
|
|
119
|
+
|| environmentValue(environment, brandedEnvironmentName("CODEX_APP_BIN"));
|
|
119
120
|
const localAppData = environmentValue(environment, "LOCALAPPDATA")
|
|
120
121
|
|| path.win32.join(environmentValue(environment, "USERPROFILE") || os.homedir(), "AppData", "Local");
|
|
121
122
|
const programFiles = environmentValue(environment, "ProgramFiles");
|
|
@@ -300,8 +301,8 @@ function installPinnedWindowsClaudeMsix(packagePath, environment, pin, architect
|
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
function windowsClaudeCacheRoot(homeDir, environment, version, architecture) {
|
|
303
|
-
const appsRoot = environmentValue(environment, "
|
|
304
|
-
|| path.join(homeDir, ".config",
|
|
304
|
+
const appsRoot = environmentValue(environment, brandedEnvironmentName("APP_HOME"))
|
|
305
|
+
|| path.join(homeDir, ".config", RUNTIME_BRAND.cli.configNamespace, "apps");
|
|
305
306
|
return path.join(appsRoot, "vendor-cache", "claude", version, architecture);
|
|
306
307
|
}
|
|
307
308
|
|