impel-cli 0.20.54 → 0.20.55

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/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Release notes
2
2
 
3
+ ## 0.20.55 — Route managed CLIs through the signed runner
4
+
5
+ - Launches `impel codex` and `impel claude` through the tenant-agnostic macOS
6
+ managed runner when the corresponding signed descriptor is installed.
7
+ - Binds each launch to the exact logical application ID and leaves descriptor
8
+ signature, trust-anchor, runtime-lock, and payload verification with the
9
+ signed runner before any vendor CLI starts.
10
+ - Prefers the standalone Codex MSI on Windows while retaining the legacy
11
+ ChatGPT-embedded Codex package only as a compatibility fallback.
12
+ - Stops newly managed Codex sessions from reaching retired macOS wrappers that
13
+ injected an invalid `--console` argument into no-argument launches.
14
+
3
15
  ## 0.20.54 — Restore ChatGPT Store compatibility and scope skills per tenant
4
16
 
5
17
  - Restores managed ChatGPT/Codex installation on Windows by pinning Store
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.20.54",
3
+ "version": "0.20.55",
4
4
  "description": "Prepare isolated Claude and Codex workspaces for every accessible Impel tenant",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,7 +4,12 @@ import path from "node:path";
4
4
  import { spawn } from "node:child_process";
5
5
 
6
6
  const TENANT = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
7
- const APPLICATION_BY_TOOL = Object.freeze({ claude: "claude", codex: "chatgpt" });
7
+ const APPLICATIONS_BY_TOOL = Object.freeze({
8
+ claude: ["claude"],
9
+ codex: ["codex", "chatgpt"],
10
+ });
11
+ const MAC_RUNNER = "/Applications/Impel Managed App Runner.app/Contents/Library/LaunchServices/impel-managed-runner";
12
+ const MAC_DESCRIPTOR_ROOT = "/Library/Application Support/Impel/Managed App Runner/descriptors";
8
13
 
9
14
  function regularFile(file) {
10
15
  try {
@@ -15,7 +20,7 @@ function regularFile(file) {
15
20
  }
16
21
  }
17
22
 
18
- function parseCandidate(manifestPath, launcher, tool, platform, home) {
23
+ function parseCandidate(manifestPath, launcher, applicationId, platform, home) {
19
24
  if (!regularFile(manifestPath) || !regularFile(launcher)) return null;
20
25
  let manifest;
21
26
  try {
@@ -25,7 +30,6 @@ function parseCandidate(manifestPath, launcher, tool, platform, home) {
25
30
  } catch {
26
31
  return null;
27
32
  }
28
- const applicationId = APPLICATION_BY_TOOL[tool];
29
33
  const tenantId = manifest?.tenant?.id;
30
34
  const expectedPlatform = platform === "win32" ? "windows" : "macos";
31
35
  if (
@@ -46,11 +50,12 @@ function parseCandidate(manifestPath, launcher, tool, platform, home) {
46
50
  tenantId,
47
51
  tenantDisplayName: typeof manifest.tenant.displayName === "string" ? manifest.tenant.displayName : tenantId,
48
52
  launcher,
53
+ logicalApplicationId: manifest.logicalApplicationId,
49
54
  manifestPath,
50
55
  };
51
56
  }
52
57
 
53
- function macCandidates(tool, home, applicationsRoot) {
58
+ function legacyMacCandidates(tool, home, applicationsRoot) {
54
59
  let entries;
55
60
  try {
56
61
  entries = fs.readdirSync(applicationsRoot, { withFileTypes: true });
@@ -58,13 +63,14 @@ function macCandidates(tool, home, applicationsRoot) {
58
63
  return [];
59
64
  }
60
65
  const candidates = [];
66
+ const applicationId = tool === "codex" ? "chatgpt" : tool;
61
67
  for (const entry of entries) {
62
68
  if (!entry.isDirectory() || !entry.name.endsWith(".app")) continue;
63
69
  const root = path.join(applicationsRoot, entry.name, "Contents");
64
70
  const candidate = parseCandidate(
65
71
  path.join(root, "Resources", "app-manifest.json"),
66
72
  path.join(root, "MacOS", "impel-apps-launcher"),
67
- tool,
73
+ applicationId,
68
74
  "darwin",
69
75
  home,
70
76
  );
@@ -73,6 +79,78 @@ function macCandidates(tool, home, applicationsRoot) {
73
79
  return candidates;
74
80
  }
75
81
 
82
+ function runnerEnvelopeCandidate(envelopePath, launcher, tool, home) {
83
+ if (!regularFile(envelopePath) || !regularFile(launcher)) return null;
84
+ let envelope;
85
+ try {
86
+ const raw = fs.readFileSync(envelopePath, "utf8");
87
+ if (Buffer.byteLength(raw, "utf8") > 512 * 1024) return null;
88
+ envelope = JSON.parse(raw);
89
+ } catch {
90
+ return null;
91
+ }
92
+ const payload = envelope?.payload;
93
+ const tenantId = payload?.tenant?.id;
94
+ if (
95
+ envelope?.schemaVersion !== 1
96
+ || payload?.kind !== "com.useimpel.managed-application-descriptor"
97
+ || payload?.runtimeContract?.entrypoint !== "engine/main.mjs"
98
+ || payload?.application?.id !== tool
99
+ || payload?.application?.adapter !== tool
100
+ || !TENANT.test(tenantId || "")
101
+ || String(tenantId).includes("..")
102
+ || payload?.logicalApplicationId !== `${tenantId}.${tool}`
103
+ ) return null;
104
+ const credential = path.join(home, ".config", "impel", "apps", "credentials", `${tenantId}.json`);
105
+ if (!regularFile(credential)) return null;
106
+ return {
107
+ tenantId,
108
+ tenantDisplayName: typeof payload.tenant.displayName === "string" ? payload.tenant.displayName : tenantId,
109
+ launcher,
110
+ logicalApplicationId: payload.logicalApplicationId,
111
+ managedRunner: true,
112
+ };
113
+ }
114
+
115
+ function managedRunnerMacCandidates(tool, home, descriptorRoot, runnerLauncher) {
116
+ if (!regularFile(runnerLauncher)) return [];
117
+ let entries;
118
+ try {
119
+ entries = fs.readdirSync(descriptorRoot, { withFileTypes: true });
120
+ } catch {
121
+ return [];
122
+ }
123
+ const candidates = [];
124
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
125
+ if (!entry.isDirectory() || !entry.name.endsWith(`.${tool}`)) continue;
126
+ const directory = path.join(descriptorRoot, entry.name);
127
+ let envelopes;
128
+ try {
129
+ envelopes = fs.readdirSync(directory, { withFileTypes: true });
130
+ } catch {
131
+ continue;
132
+ }
133
+ for (const envelope of envelopes.sort((left, right) => left.name.localeCompare(right.name))) {
134
+ if (!envelope.isFile() || !envelope.name.endsWith(".json")) continue;
135
+ const candidate = runnerEnvelopeCandidate(path.join(directory, envelope.name), runnerLauncher, tool, home);
136
+ if (!candidate || candidate.logicalApplicationId !== entry.name) continue;
137
+ candidates.push(candidate);
138
+ break;
139
+ }
140
+ }
141
+ return candidates;
142
+ }
143
+
144
+ function macCandidates(tool, home, applicationsRoot, descriptorRoot, runnerLauncher) {
145
+ const preferred = managedRunnerMacCandidates(tool, home, descriptorRoot, runnerLauncher);
146
+ const legacy = legacyMacCandidates(tool, home, applicationsRoot);
147
+ const byTenant = new Map();
148
+ for (const candidate of [...preferred, ...legacy]) {
149
+ if (!byTenant.has(candidate.tenantId)) byTenant.set(candidate.tenantId, candidate);
150
+ }
151
+ return [...byTenant.values()];
152
+ }
153
+
76
154
  function windowsCandidates(tool, home, applicationsRoot) {
77
155
  const candidates = [];
78
156
  let tenants;
@@ -83,15 +161,19 @@ function windowsCandidates(tool, home, applicationsRoot) {
83
161
  }
84
162
  for (const tenant of tenants) {
85
163
  if (!tenant.isDirectory() || !TENANT.test(tenant.name) || tenant.name.includes("..")) continue;
86
- const root = path.join(applicationsRoot, tenant.name, APPLICATION_BY_TOOL[tool]);
87
- const candidate = parseCandidate(
88
- path.join(root, "resources", "app-manifest.json"),
89
- path.join(root, "impel-apps-launcher.exe"),
90
- tool,
91
- "win32",
92
- home,
93
- );
94
- if (candidate) candidates.push(candidate);
164
+ for (const applicationId of APPLICATIONS_BY_TOOL[tool]) {
165
+ const root = path.join(applicationsRoot, tenant.name, applicationId);
166
+ const candidate = parseCandidate(
167
+ path.join(root, "resources", "app-manifest.json"),
168
+ path.join(root, "impel-apps-launcher.exe"),
169
+ applicationId,
170
+ "win32",
171
+ home,
172
+ );
173
+ if (!candidate) continue;
174
+ candidates.push(candidate);
175
+ break;
176
+ }
95
177
  }
96
178
  return candidates;
97
179
  }
@@ -102,11 +184,15 @@ export function installedManagedAppCandidates(tool, {
102
184
  applicationsRoot = platform === "win32"
103
185
  ? path.join(process.env.ProgramFiles || "C:\\Program Files", "Impel", "Apps")
104
186
  : "/Applications",
187
+ descriptorRoot = MAC_DESCRIPTOR_ROOT,
188
+ runnerLauncher = MAC_RUNNER,
105
189
  } = {}) {
106
- if (!APPLICATION_BY_TOOL[tool]) return [];
190
+ if (!APPLICATIONS_BY_TOOL[tool]) return [];
107
191
  const candidates = platform === "win32"
108
192
  ? windowsCandidates(tool, home, applicationsRoot)
109
- : platform === "darwin" ? macCandidates(tool, home, applicationsRoot) : [];
193
+ : platform === "darwin"
194
+ ? macCandidates(tool, home, applicationsRoot, descriptorRoot, runnerLauncher)
195
+ : [];
110
196
  return candidates.sort((left, right) => left.tenantId.localeCompare(right.tenantId));
111
197
  }
112
198
 
@@ -170,8 +256,11 @@ export function runManagedAppCli(candidate, tool, argv, {
170
256
  cwd = process.cwd(),
171
257
  spawnProcess = spawn,
172
258
  } = {}) {
259
+ const args = candidate.managedRunner === true
260
+ ? ["--descriptor", candidate.logicalApplicationId, "--managed-cli", tool, "--", ...argv]
261
+ : ["--managed-cli", tool, "--", ...argv];
173
262
  return new Promise((resolve) => {
174
- const child = spawnProcess(candidate.launcher, ["--managed-cli", tool, "--", ...argv], {
263
+ const child = spawnProcess(candidate.launcher, args, {
175
264
  cwd,
176
265
  env: environment,
177
266
  stdio: "inherit",
@@ -191,4 +280,3 @@ export function runManagedAppCli(candidate, tool, argv, {
191
280
  });
192
281
  });
193
282
  }
194
-