impel-cli 0.13.0 → 0.13.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 +3 -1
- package/package.json +1 -1
- package/src/apps.js +1 -1
- package/src/codesign.js +54 -3
- package/src/commands/update.js +10 -5
package/README.md
CHANGED
|
@@ -803,7 +803,9 @@ retains OpenAI's signature.
|
|
|
803
803
|
|
|
804
804
|
By default the CLI signs each rebuild with a **stable per-machine identity** — a
|
|
805
805
|
self-signed certificate created once in a dedicated keychain under
|
|
806
|
-
`~/.config/impel/codesign` (no Apple Developer account is involved).
|
|
806
|
+
`~/.config/impel/codesign` (no Apple Developer account is involved). The
|
|
807
|
+
certificate is trusted locally for the code-signing policy only; it is not a
|
|
808
|
+
TLS or document-signing authority. macOS ties
|
|
807
809
|
every privacy (TCC) grant — microphone, screen recording, folder access,
|
|
808
810
|
Automation, accessibility — to the app's code-signing designated requirement. A
|
|
809
811
|
stable identity keeps that requirement constant across rebuilds, so the grants
|
package/package.json
CHANGED
package/src/apps.js
CHANGED
|
@@ -152,7 +152,7 @@ export const CURRENT_CONFIG_VERSION = 10;
|
|
|
152
152
|
// — which is what made every `impel update` re-trigger macOS permission
|
|
153
153
|
// prompts. Bump this ONLY when a code change alters the bytes of a built
|
|
154
154
|
// bundle; leave it alone for changes that don't touch bundle contents.
|
|
155
|
-
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-16.
|
|
155
|
+
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-16.2";
|
|
156
156
|
|
|
157
157
|
/** Parse the tenant's install manifest, or null when absent/corrupt. */
|
|
158
158
|
export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
|
package/src/codesign.js
CHANGED
|
@@ -143,17 +143,28 @@ function ensureLocalSigningIdentity({ run, configDir, fsImpl, randomBytes }) {
|
|
|
143
143
|
if (fsImpl.existsSync(keychain) && fsImpl.existsSync(passwordFile)) {
|
|
144
144
|
const password = fsImpl.readFileSync(passwordFile, "utf8").trim();
|
|
145
145
|
unlockKeychain(run, keychain, password);
|
|
146
|
+
ensureKeychainInSearchList(run, keychain);
|
|
146
147
|
const existing = findLocalIdentitySha(run, keychain);
|
|
147
148
|
if (existing) return localIdentity(existing, keychain);
|
|
149
|
+
|
|
150
|
+
// v0.13.0 imported a self-signed certificate but never established code-
|
|
151
|
+
// signing trust for it. `security find-identity -v` therefore reported
|
|
152
|
+
// zero valid identities and every rebuild fell back to ad-hoc signing.
|
|
153
|
+
// Repair that exact state in place so the already-generated identity stays
|
|
154
|
+
// stable instead of accumulating a second certificate with the same name.
|
|
155
|
+
trustExistingLocalCertificate({ run, keychain, fsImpl });
|
|
156
|
+
const repaired = findLocalIdentitySha(run, keychain);
|
|
157
|
+
if (repaired) return localIdentity(repaired, keychain);
|
|
158
|
+
throw new Error("existing signing identity could not be trusted");
|
|
148
159
|
}
|
|
149
160
|
|
|
150
161
|
const password = randomBytes(24).toString("hex");
|
|
151
162
|
createLocalSigningIdentity({ run, dir, keychain, password, fsImpl });
|
|
152
163
|
fsImpl.writeFileSync(passwordFile, `${password}\n`, { mode: 0o600 });
|
|
153
164
|
|
|
165
|
+
ensureKeychainInSearchList(run, keychain);
|
|
154
166
|
const sha = findLocalIdentitySha(run, keychain);
|
|
155
167
|
if (!sha) throw new Error("created signing identity was not found in its keychain");
|
|
156
|
-
ensureKeychainInSearchList(run, keychain);
|
|
157
168
|
return localIdentity(sha, keychain);
|
|
158
169
|
}
|
|
159
170
|
|
|
@@ -214,13 +225,44 @@ function createLocalSigningIdentity({ run, dir, keychain, password, fsImpl }) {
|
|
|
214
225
|
runOrThrow(run, "/usr/bin/security", [
|
|
215
226
|
"set-key-partition-list", "-S", "apple-tool:,apple:,codesign:", "-s", "-k", password, keychain,
|
|
216
227
|
], "authorize codesign for the signing key");
|
|
228
|
+
trustLocalCertificate(run, keychain, certPath);
|
|
217
229
|
} finally {
|
|
218
230
|
fsImpl.rmSync(work, { recursive: true, force: true });
|
|
219
231
|
}
|
|
220
232
|
}
|
|
221
233
|
|
|
222
234
|
function unlockKeychain(run, keychain, password) {
|
|
223
|
-
|
|
235
|
+
runOrThrow(
|
|
236
|
+
run,
|
|
237
|
+
"/usr/bin/security",
|
|
238
|
+
["unlock-keychain", "-p", password, keychain],
|
|
239
|
+
"unlock signing keychain",
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function trustExistingLocalCertificate({ run, keychain, fsImpl }) {
|
|
244
|
+
const exported = run("/usr/bin/security", [
|
|
245
|
+
"find-certificate", "-p", "-c", SIGNING_IDENTITY_NAME, keychain,
|
|
246
|
+
]);
|
|
247
|
+
if (exported.status !== 0 || exported.error || !String(exported.stdout || "").includes("BEGIN CERTIFICATE")) {
|
|
248
|
+
throw new Error("existing signing certificate was not found in its keychain");
|
|
249
|
+
}
|
|
250
|
+
const certPath = path.join(os.tmpdir(), `impel-codesign-cert-${process.pid}.pem`);
|
|
251
|
+
fsImpl.writeFileSync(certPath, exported.stdout, { mode: 0o600 });
|
|
252
|
+
try {
|
|
253
|
+
trustLocalCertificate(run, keychain, certPath);
|
|
254
|
+
} finally {
|
|
255
|
+
fsImpl.rmSync(certPath, { force: true });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function trustLocalCertificate(run, keychain, certPath) {
|
|
260
|
+
// Limit trust to the code-signing policy. This is a local build identity,
|
|
261
|
+
// not a TLS/document-signing CA, and it remains isolated in Impel's own
|
|
262
|
+
// keychain rather than the user's login keychain.
|
|
263
|
+
runOrThrow(run, "/usr/bin/security", [
|
|
264
|
+
"add-trusted-cert", "-r", "trustRoot", "-p", "codeSign", "-k", keychain, certPath,
|
|
265
|
+
], "trust local code-signing certificate");
|
|
224
266
|
}
|
|
225
267
|
|
|
226
268
|
// codesign resolves an identity by searching the user's keychain search list,
|
|
@@ -228,12 +270,21 @@ function unlockKeychain(run, keychain, password) {
|
|
|
228
270
|
// user's existing keychains (a bare `-s <one>` would replace the whole list).
|
|
229
271
|
function ensureKeychainInSearchList(run, keychain) {
|
|
230
272
|
const listed = run("/usr/bin/security", ["list-keychains", "-d", "user"]);
|
|
273
|
+
if (listed.status !== 0 || listed.error) {
|
|
274
|
+
const detail = listed.error?.message || String(listed.stderr || "").trim() || `exit ${listed.status}`;
|
|
275
|
+
throw new Error(`read user keychain search list failed: ${detail}`);
|
|
276
|
+
}
|
|
231
277
|
const current = String(listed.stdout || "")
|
|
232
278
|
.split("\n")
|
|
233
279
|
.map((line) => line.trim().replace(/^"|"$/gu, ""))
|
|
234
280
|
.filter(Boolean);
|
|
235
281
|
if (current.includes(keychain)) return;
|
|
236
|
-
|
|
282
|
+
runOrThrow(
|
|
283
|
+
run,
|
|
284
|
+
"/usr/bin/security",
|
|
285
|
+
["list-keychains", "-d", "user", "-s", ...current, keychain],
|
|
286
|
+
"add Impel signing keychain to the user search list",
|
|
287
|
+
);
|
|
237
288
|
}
|
|
238
289
|
|
|
239
290
|
function runOrThrow(run, command, args, description) {
|
package/src/commands/update.js
CHANGED
|
@@ -165,6 +165,7 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
165
165
|
runAgentsSync: defaultRunAgentsSync,
|
|
166
166
|
appsInstalled: anyAppInstalled,
|
|
167
167
|
platform: process.platform,
|
|
168
|
+
progress: withProgress,
|
|
168
169
|
...overrides,
|
|
169
170
|
};
|
|
170
171
|
const { flags } = parseFlags(argv, {
|
|
@@ -185,7 +186,7 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
const current = io.installedVersion();
|
|
188
|
-
const remote = await
|
|
189
|
+
const remote = await io.progress("Checking npm for impel-cli updates", () => io.fetchRemoteVersion());
|
|
189
190
|
if (remote) io.writeCache({ remoteVersion: remote, checkedAt: Date.now() });
|
|
190
191
|
|
|
191
192
|
console.log(`impel-cli v${current ?? "?"}`);
|
|
@@ -204,7 +205,7 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
204
205
|
console.log("CLI: already up to date.");
|
|
205
206
|
} else {
|
|
206
207
|
console.log("CLI: installing the latest build…");
|
|
207
|
-
if (!await
|
|
208
|
+
if (!await io.progress("Installing the latest impel-cli build", () => io.selfUpdate(updateInstallSpec()))) {
|
|
208
209
|
console.error("impel update: `npm install -g` failed; the CLI was not updated.");
|
|
209
210
|
if (io.platform === "win32") {
|
|
210
211
|
console.error(" Verify `npm --version` in PowerShell, then retry `impel update`.");
|
|
@@ -228,20 +229,24 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
228
229
|
console.log(io.platform === "win32"
|
|
229
230
|
? "Apps: updating every installed tenant's signed Claude and ChatGPT profiles…"
|
|
230
231
|
: "Apps: refreshing every installed tenant and rebuilding only stale app bundles…");
|
|
231
|
-
|
|
232
|
+
// These child commands inherit the terminal and render their own progress
|
|
233
|
+
// and log lines. Wrapping them in another spinner makes both processes
|
|
234
|
+
// write the same terminal row, producing glued output such as
|
|
235
|
+
// "Updating managed desktop apps (...)Updating all managed...".
|
|
236
|
+
if (!await io.runAppsUpdate()) {
|
|
232
237
|
console.error("impel update: the app update failed; re-run `impel app update` after fixing the issue.");
|
|
233
238
|
cascadeFailed = true;
|
|
234
239
|
}
|
|
235
240
|
}
|
|
236
241
|
|
|
237
242
|
console.log("Skills: syncing native and isolated CLI profiles…");
|
|
238
|
-
if (!await
|
|
243
|
+
if (!await io.runSkillsSync()) {
|
|
239
244
|
console.error("impel update: skill sync failed; re-run `impel skills sync` after fixing the issue.");
|
|
240
245
|
cascadeFailed = true;
|
|
241
246
|
}
|
|
242
247
|
|
|
243
248
|
console.log("Agents: syncing the selected tenant into native and isolated CLI profiles…");
|
|
244
|
-
if (!await
|
|
249
|
+
if (!await io.runAgentsSync()) {
|
|
245
250
|
console.error("impel update: agent sync failed; re-run `impel agents sync` after fixing the issue.");
|
|
246
251
|
cascadeFailed = true;
|
|
247
252
|
}
|