impel-cli 0.16.4 → 0.17.0
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 +224 -945
- package/package.json +2 -2
- package/src/apps.js +8 -2
- package/src/cli.js +38 -41
- package/src/cliProfiles.js +5 -1
- package/src/commands/apps.js +215 -0
- package/src/commands/converge.js +174 -0
- package/src/commands/sessions.js +114 -0
- package/src/commands/setup.js +312 -428
- package/src/commands/status.js +133 -83
- package/src/commands/tasks.js +1 -1
- package/src/commands/tenant.js +3 -5
- package/src/commands/update.js +39 -85
- package/src/commands/use.js +67 -238
- package/src/config.js +8 -3
- package/src/installRecovery/engine.js +9 -27
- package/src/installRecovery/redact.js +4 -0
- package/src/installRecovery/tools.js +1 -1
- package/src/provisioning.js +354 -0
- package/src/sessionCollector.js +996 -0
- package/src/sessionHooks.js +242 -0
- package/src/shellEntries.js +186 -0
- package/src/skills.js +1 -1
- package/src/tenants.js +1 -1
package/src/commands/status.js
CHANGED
|
@@ -1,95 +1,145 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { ensureTenantSelection, productAccessLabel, tenantCredential } from "../tenants.js";
|
|
5
|
-
import { maybePrintUpdateNotice } from "../updates.js";
|
|
6
|
-
import { findNativeBinary } from "../nativeProcess.js";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
5
|
+
import { appPaths, CLAUDE_CONFIG_ID, readTenantManifest } from "../apps.js";
|
|
6
|
+
import { loadConfig, maskSecret } from "../config.js";
|
|
7
|
+
import { environmentValue, findNativeBinary } from "../nativeProcess.js";
|
|
8
|
+
import { tenantCliClientReadiness } from "../provisioning.js";
|
|
9
|
+
import { windowsTenantShortcutName } from "../shellEntries.js";
|
|
10
|
+
import {
|
|
11
|
+
ensureTenantSelection,
|
|
12
|
+
PAT_SCOPE_CLAUDE,
|
|
13
|
+
PAT_SCOPE_CODEX,
|
|
14
|
+
productAccessLabel,
|
|
15
|
+
} from "../tenants.js";
|
|
16
|
+
import { installedVersion, maybePrintUpdateNotice } from "../updates.js";
|
|
17
|
+
import { windowsClaudeUserData } from "../windowsApps.js";
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
function readShellManifest(paths) {
|
|
20
|
+
try {
|
|
21
|
+
const value = JSON.parse(fs.readFileSync(path.join(paths.tenantRoot, "shell-entries.json"), "utf8"));
|
|
22
|
+
return Array.isArray(value?.entries) ? value.entries : [];
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
16
27
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
function desktopReadiness(tenant, {
|
|
29
|
+
platform = process.platform,
|
|
30
|
+
homeDir = os.homedir(),
|
|
31
|
+
environment = process.env,
|
|
32
|
+
existsSync = fs.existsSync,
|
|
33
|
+
} = {}) {
|
|
34
|
+
if (!["darwin", "win32"].includes(platform)) {
|
|
35
|
+
return {
|
|
36
|
+
claude: { app: "unavailable", shell: "unavailable" },
|
|
37
|
+
codex: { app: "unavailable", shell: "unavailable" },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const claudeUserData = platform === "win32" ? windowsClaudeUserData(environment, tenant.id) : null;
|
|
41
|
+
const paths = appPaths(homeDir, tenant.id, { tenantName: tenant.name, claudeUserData });
|
|
42
|
+
const manifest = readTenantManifest(homeDir, tenant.id);
|
|
43
|
+
const available = new Set(manifest?.availableTargets || manifest?.targets || []);
|
|
44
|
+
const registered = new Set(readShellManifest(paths).map((entry) => entry.product));
|
|
45
|
+
const appData = environmentValue(environment, "APPDATA");
|
|
46
|
+
const states = {};
|
|
47
|
+
for (const [client, product] of [["claude", "claude"], ["codex", "chatgpt"]]) {
|
|
48
|
+
if (manifest && !available.has(product)) {
|
|
49
|
+
states[client] = { app: "unsupported", shell: "unsupported" };
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const appReady = platform === "darwin"
|
|
53
|
+
? existsSync(paths[product].launcher)
|
|
54
|
+
: existsSync(product === "claude"
|
|
55
|
+
? path.join(paths.claude.userData, "configLibrary", `${CLAUDE_CONFIG_ID}.json`)
|
|
56
|
+
: path.join(paths.chatgpt.codexHome, "config.toml"));
|
|
57
|
+
let shellReady = appReady && registered.has(product);
|
|
58
|
+
if (platform === "win32" && appData) {
|
|
59
|
+
const shortcutPath = path.win32.join(
|
|
60
|
+
appData,
|
|
61
|
+
"Microsoft", "Windows", "Start Menu", "Programs", "Impel",
|
|
62
|
+
windowsTenantShortcutName(product, tenant.id, tenant.name),
|
|
63
|
+
);
|
|
64
|
+
shellReady = appReady && existsSync(shortcutPath);
|
|
33
65
|
}
|
|
66
|
+
states[client] = {
|
|
67
|
+
app: appReady ? "ready" : "missing",
|
|
68
|
+
shell: shellReady ? "ready" : "missing",
|
|
69
|
+
};
|
|
34
70
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
console.log(`Tenant: ${tenantId ? `${tenantId}${staleLabel}` : "not selected — run `impel tenant list`"}`);
|
|
38
|
-
console.log(`Access: ${selectionFresh ? productAccessLabel(productAccess) : `Unknown — ${unavailableReason}`}`);
|
|
39
|
-
console.log(`PAT scopes: ${selectionFresh && scopes?.length ? scopes.join(", ") : `unknown — ${unavailableReason}`}`);
|
|
40
|
-
const claudeBinary = findNativeBinary("claude");
|
|
41
|
-
const codexBinary = findNativeBinary("codex");
|
|
42
|
-
console.log("Isolated CLI launchers:");
|
|
43
|
-
console.log(
|
|
44
|
-
` Claude: ${config?.pat && claudeBinary ? "READY (`impel claude`)" : !config?.pat ? "NOT READY — run `impel setup`" : "NOT READY — Claude Code is not installed"}`
|
|
45
|
-
);
|
|
46
|
-
console.log(
|
|
47
|
-
` Codex: ${config?.pat && codexBinary ? "READY (`impel codex`)" : !config?.pat ? "NOT READY — run `impel setup`" : "NOT READY — Codex is not installed"}`
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
// Per-tool mode. Codex CLI and the Codex app/IDE share the same
|
|
51
|
-
// ~/.codex/config.toml, so they always report the same mode.
|
|
52
|
-
const claude = detectClaudeMode(gatewayUrl);
|
|
53
|
-
const codex = detectCodexMode();
|
|
54
|
-
|
|
55
|
-
console.log("");
|
|
56
|
-
console.log("Native profile mode (isolated launchers do not change this):");
|
|
57
|
-
console.log(` Claude Code: ${modeLabel(claude.mode)}`);
|
|
58
|
-
console.log(` Codex CLI: ${modeLabel(codex.mode)}`);
|
|
59
|
-
console.log(` Codex app: ${modeLabel(codex.mode)} (shares ~/.codex/config.toml with the CLI)`);
|
|
60
|
-
console.log(" Flip: `impel use gateway|account [claude|codex|all]` (aliases: `impel on` / `impel off`)");
|
|
71
|
+
return states;
|
|
72
|
+
}
|
|
61
73
|
|
|
62
|
-
|
|
74
|
+
function cliState({ supported, profileReady, binaryReady }) {
|
|
75
|
+
if (!supported) return "unsupported";
|
|
76
|
+
return profileReady && binaryReady ? "ready" : "missing";
|
|
77
|
+
}
|
|
63
78
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
export async function cmdStatus(overrides = {}) {
|
|
80
|
+
const io = {
|
|
81
|
+
loadConfig,
|
|
82
|
+
ensureTenantSelection,
|
|
83
|
+
cliReadiness: tenantCliClientReadiness,
|
|
84
|
+
desktopReadiness,
|
|
85
|
+
installedVersion,
|
|
86
|
+
findNativeBinary,
|
|
87
|
+
maybePrintUpdateNotice,
|
|
88
|
+
platform: process.platform,
|
|
89
|
+
environment: process.env,
|
|
90
|
+
...overrides,
|
|
91
|
+
};
|
|
92
|
+
const config = io.loadConfig();
|
|
93
|
+
console.log(`impel-cli: v${io.installedVersion() || "?"}`);
|
|
94
|
+
console.log(`Authentication: ${config?.pat ? `configured (${maskSecret(config.pat)})` : "not configured - run `impel setup`"}`);
|
|
95
|
+
if (!config?.pat) {
|
|
96
|
+
io.maybePrintUpdateNotice();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
67
99
|
|
|
68
|
-
|
|
69
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
100
|
+
let selected;
|
|
70
101
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
102
|
+
selected = await io.ensureTenantSelection(config, { refresh: true });
|
|
103
|
+
console.log(`Authentication check: accepted (${productAccessLabel(selected.productAccess)})`);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.log(`Authentication check: unavailable (${error?.message || error})`);
|
|
106
|
+
console.log(`Current CLI tenant: ${config.tenantId || "not selected"}`);
|
|
107
|
+
console.log("Run `impel setup` to refresh authentication, then `impel update` to repair local tenants.");
|
|
108
|
+
io.maybePrintUpdateNotice();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`Current CLI tenant: ${selected.tenantId}`);
|
|
113
|
+
console.log("Change it with: impel tenant use <tenant>");
|
|
114
|
+
const scopes = new Set(selected.scopes || []);
|
|
115
|
+
const scopeKnown = selected.scopes != null;
|
|
116
|
+
const binaries = {
|
|
117
|
+
claude: Boolean(io.findNativeBinary("claude", io.environment, io.platform)),
|
|
118
|
+
codex: Boolean(io.findNativeBinary("codex", io.environment, io.platform)),
|
|
119
|
+
};
|
|
120
|
+
console.log("Tenant readiness:");
|
|
121
|
+
let incomplete = false;
|
|
122
|
+
for (const tenant of [...selected.tenants].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
123
|
+
const profiles = io.cliReadiness(tenant.id);
|
|
124
|
+
const desktop = io.desktopReadiness(tenant, overrides);
|
|
125
|
+
const claudeCli = cliState({
|
|
126
|
+
supported: !scopeKnown || scopes.has(PAT_SCOPE_CLAUDE),
|
|
127
|
+
profileReady: profiles.claude,
|
|
128
|
+
binaryReady: binaries.claude,
|
|
81
129
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
130
|
+
const codexCli = cliState({
|
|
131
|
+
supported: !scopeKnown || scopes.has(PAT_SCOPE_CODEX),
|
|
132
|
+
profileReady: profiles.codex,
|
|
133
|
+
binaryReady: binaries.codex,
|
|
134
|
+
});
|
|
135
|
+
const states = [claudeCli, codexCli, desktop.claude.app, desktop.codex.app, desktop.claude.shell, desktop.codex.shell];
|
|
136
|
+
if (states.includes("missing")) incomplete = true;
|
|
137
|
+
console.log(
|
|
138
|
+
` ${tenant.id}${tenant.id === selected.tenantId ? " (current)" : ""}: `
|
|
139
|
+
+ `Claude CLI ${claudeCli}, Codex CLI ${codexCli}, `
|
|
140
|
+
+ `Claude app ${desktop.claude.app}, ChatGPT app ${desktop.codex.app}`,
|
|
141
|
+
);
|
|
94
142
|
}
|
|
143
|
+
if (incomplete) console.log("Repair or finish missing tenant surfaces with: impel update");
|
|
144
|
+
io.maybePrintUpdateNotice();
|
|
95
145
|
}
|
package/src/commands/tasks.js
CHANGED
|
@@ -92,7 +92,7 @@ async function requestJson({ flags, path, query, method = "GET", body }) {
|
|
|
92
92
|
fail("impel tasks: the control plane did not return live PAT scopes; retry after it is upgraded.");
|
|
93
93
|
}
|
|
94
94
|
if (!selected.scopes.includes(PAT_SCOPE_TASKS)) {
|
|
95
|
-
fail("impel tasks: this PAT is missing the \"tasks\" scope; create a fresh PAT in Impel Gateway setup, then run `impel
|
|
95
|
+
fail("impel tasks: this PAT is missing the \"tasks\" scope; create a fresh PAT in Impel Gateway setup, then run `impel setup` and provide it.");
|
|
96
96
|
}
|
|
97
97
|
const selectedOrgId = flags.org || selected.tenantId;
|
|
98
98
|
const resolvedQuery = { ...(query || {}) };
|
package/src/commands/tenant.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { parseFlags } from "../args.js";
|
|
2
2
|
import { loadConfig } from "../config.js";
|
|
3
3
|
import { ensureTenantSelection, productAccessLabel, selectTenant } from "../tenants.js";
|
|
4
|
-
import { cmdApps } from "./apps.js";
|
|
5
4
|
import { cmdLaunch } from "./launch.js";
|
|
6
5
|
|
|
7
6
|
const HELP = `impel tenant - select the organization used by Impel sessions
|
|
@@ -9,7 +8,7 @@ const HELP = `impel tenant - select the organization used by Impel sessions
|
|
|
9
8
|
Usage:
|
|
10
9
|
impel tenant list
|
|
11
10
|
impel tenant current
|
|
12
|
-
impel tenant use <org-slug> [--launch claude|codex
|
|
11
|
+
impel tenant use <org-slug> [--launch claude|codex]
|
|
13
12
|
|
|
14
13
|
Aliases: impel tenants ..., impel org ...
|
|
15
14
|
`;
|
|
@@ -69,10 +68,9 @@ export async function cmdTenant(argv) {
|
|
|
69
68
|
|
|
70
69
|
const launch = flags.launch;
|
|
71
70
|
if (!launch) {
|
|
72
|
-
console.log("Next: `impel claude
|
|
71
|
+
console.log("Next: `impel claude` or `impel codex`. Desktop apps are available from Finder or Windows Search.");
|
|
73
72
|
return;
|
|
74
73
|
}
|
|
75
74
|
if (launch === "claude" || launch === "codex") return cmdLaunch(launch, []);
|
|
76
|
-
|
|
77
|
-
throw new Error(`unknown launch target "${launch}"; use claude, codex, or apps`);
|
|
75
|
+
throw new Error(`unknown launch target "${launch}"; use claude or codex`);
|
|
78
76
|
}
|
package/src/commands/update.js
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
// `impel update` —
|
|
2
|
-
//
|
|
3
|
-
// skills and native tenant agents. The app step re-executes the freshly installed CLI so the new code
|
|
4
|
-
// performs it.
|
|
1
|
+
// `impel update` — update the CLI, then let the freshly installed build
|
|
2
|
+
// reconcile every tenant and managed surface from the live control-plane list.
|
|
5
3
|
|
|
6
|
-
import fs from "node:fs";
|
|
7
|
-
import os from "node:os";
|
|
8
|
-
import path from "node:path";
|
|
9
4
|
import { spawnSync } from "node:child_process";
|
|
10
5
|
import { fileURLToPath } from "node:url";
|
|
11
6
|
|
|
12
7
|
import { parseFlags } from "../args.js";
|
|
13
|
-
import { CLAUDE_CONFIG_ID, appPaths } from "../apps.js";
|
|
14
8
|
import { loadConfig, redactSecretText } from "../config.js";
|
|
15
9
|
import { nativeCommandInvocation } from "../nativeProcess.js";
|
|
16
|
-
import { windowsClaudeUserData } from "../windowsApps.js";
|
|
17
10
|
import { withProgress } from "../progress.js";
|
|
18
|
-
import { installedAppTenantIds } from "./apps.js";
|
|
19
11
|
import { runInstallRecovery } from "../installRecovery/engine.js";
|
|
20
12
|
import {
|
|
21
13
|
fetchRemoteVersion,
|
|
@@ -30,16 +22,14 @@ const CLI_BIN = fileURLToPath(new URL("../../bin/impel.js", import.meta.url));
|
|
|
30
22
|
|
|
31
23
|
const HELP = `impel update - update everything Impel in one command
|
|
32
24
|
|
|
33
|
-
Reinstalls impel-cli from npm, then
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
\`impel agents sync all\` across native and isolated CLI profiles.
|
|
25
|
+
Reinstalls impel-cli from npm, then uses the new build to discover every
|
|
26
|
+
accessible tenant. Missing tenants are installed; existing tenant profiles and
|
|
27
|
+
apps are repaired and upgraded in place while preserving local state.
|
|
37
28
|
|
|
38
29
|
Usage:
|
|
39
|
-
impel update Update the CLI
|
|
30
|
+
impel update Update the CLI and reconcile every accessible tenant
|
|
40
31
|
impel update --check Report whether an update is available; change nothing
|
|
41
|
-
impel update --skip-apps
|
|
42
|
-
impel update --repair Explicitly opt into sanitized assisted recovery on failure
|
|
32
|
+
impel update --skip-apps Reconcile only isolated CLI profiles
|
|
43
33
|
impel update --no-recovery Disable local and hosted recovery for this run
|
|
44
34
|
`;
|
|
45
35
|
|
|
@@ -93,17 +83,25 @@ function defaultSelfUpdate(spec) {
|
|
|
93
83
|
|
|
94
84
|
// The cascading steps re-execute the (freshly installed) CLI binary so the
|
|
95
85
|
// NEW code performs them, not the process that started the update.
|
|
96
|
-
export function
|
|
86
|
+
export function defaultRunConvergence({
|
|
87
|
+
skipApps = false,
|
|
88
|
+
noRecovery = false,
|
|
97
89
|
spawn = spawnSync,
|
|
98
90
|
execPath = process.execPath,
|
|
99
91
|
cliBin = CLI_BIN,
|
|
100
92
|
} = {}) {
|
|
101
|
-
const
|
|
93
|
+
const args = [cliBin, "_converge"];
|
|
94
|
+
if (skipApps) args.push("--skip-apps");
|
|
95
|
+
if (noRecovery) args.push("--no-recovery");
|
|
96
|
+
const result = spawn(execPath, args, {
|
|
102
97
|
stdio: "inherit",
|
|
103
98
|
});
|
|
104
99
|
return result.status === 0;
|
|
105
100
|
}
|
|
106
101
|
|
|
102
|
+
// Compatibility export for callers from the previous cascade implementation.
|
|
103
|
+
export const defaultRunAppsUpdate = defaultRunConvergence;
|
|
104
|
+
|
|
107
105
|
export function defaultRunSkillsSync({
|
|
108
106
|
spawn = spawnSync,
|
|
109
107
|
execPath = process.execPath,
|
|
@@ -126,36 +124,6 @@ export function defaultRunAgentsSync({
|
|
|
126
124
|
return result.status === 0;
|
|
127
125
|
}
|
|
128
126
|
|
|
129
|
-
function anyAppInstalled(homeDir = os.homedir()) {
|
|
130
|
-
if (installedAppTenantIds(["claude", "chatgpt"], {
|
|
131
|
-
homeDir,
|
|
132
|
-
platform: process.platform,
|
|
133
|
-
environment: process.env,
|
|
134
|
-
}).length > 0) return true;
|
|
135
|
-
const config = loadConfig();
|
|
136
|
-
const claudeUserData = process.platform === "win32"
|
|
137
|
-
? windowsClaudeUserData(process.env, config?.tenantId || null)
|
|
138
|
-
: null;
|
|
139
|
-
const paths = appPaths(homeDir, config?.tenantId || null, { claudeUserData });
|
|
140
|
-
if (process.platform === "win32") {
|
|
141
|
-
return (
|
|
142
|
-
fs.existsSync(path.join(
|
|
143
|
-
paths.claude.userData,
|
|
144
|
-
"configLibrary",
|
|
145
|
-
`${CLAUDE_CONFIG_ID}.json`,
|
|
146
|
-
))
|
|
147
|
-
|| fs.existsSync(path.join(paths.chatgpt.codexHome, "config.toml"))
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
const legacyPaths = appPaths(homeDir);
|
|
151
|
-
return (
|
|
152
|
-
fs.existsSync(paths.claude.launcher)
|
|
153
|
-
|| fs.existsSync(paths.chatgpt.launcher)
|
|
154
|
-
|| fs.existsSync(legacyPaths.claude.launcher)
|
|
155
|
-
|| fs.existsSync(legacyPaths.chatgpt.launcher)
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
127
|
export async function cmdUpdate(argv, overrides = {}) {
|
|
160
128
|
const io = {
|
|
161
129
|
fetchRemoteVersion,
|
|
@@ -163,16 +131,29 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
163
131
|
refreshUpdateCache,
|
|
164
132
|
writeCache: writeUpdateCache,
|
|
165
133
|
selfUpdate: defaultSelfUpdate,
|
|
134
|
+
runConvergence: defaultRunConvergence,
|
|
166
135
|
runAppsUpdate: defaultRunAppsUpdate,
|
|
167
136
|
runSkillsSync: defaultRunSkillsSync,
|
|
168
137
|
runAgentsSync: defaultRunAgentsSync,
|
|
169
|
-
appsInstalled: anyAppInstalled,
|
|
170
138
|
platform: process.platform,
|
|
171
139
|
progress: withProgress,
|
|
172
140
|
recoverInstall: runInstallRecovery,
|
|
173
141
|
loadConfig,
|
|
174
142
|
...overrides,
|
|
175
143
|
};
|
|
144
|
+
// Older embedders injected the three public cascade callbacks. Treat an
|
|
145
|
+
// injected app step as the convergence boundary so their tests/automation
|
|
146
|
+
// never fall through to a real fresh-build child process.
|
|
147
|
+
if (!overrides.runConvergence && (
|
|
148
|
+
overrides.runAppsUpdate || overrides.runSkillsSync || overrides.runAgentsSync
|
|
149
|
+
)) {
|
|
150
|
+
io.runConvergence = (options) => {
|
|
151
|
+
if (!options?.skipApps && overrides.runAppsUpdate && !overrides.runAppsUpdate()) return false;
|
|
152
|
+
if (overrides.runSkillsSync && !overrides.runSkillsSync()) return false;
|
|
153
|
+
if (overrides.runAgentsSync && !overrides.runAgentsSync()) return false;
|
|
154
|
+
return true;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
176
157
|
const { flags } = parseFlags(argv, {
|
|
177
158
|
check: { type: "boolean" },
|
|
178
159
|
"skip-apps": { type: "boolean" },
|
|
@@ -228,6 +209,7 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
228
209
|
const recovery = await io.recoverInstall(
|
|
229
210
|
{
|
|
230
211
|
failure: {
|
|
212
|
+
scope: "shared",
|
|
231
213
|
platform: io.platform,
|
|
232
214
|
architecture: process.arch,
|
|
233
215
|
step: "install.impel_cli",
|
|
@@ -278,41 +260,13 @@ export async function cmdUpdate(argv, overrides = {}) {
|
|
|
278
260
|
console.log(`CLI: updated${remote ? ` to v${remote}` : ""}.`);
|
|
279
261
|
}
|
|
280
262
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
console.log("Apps: none installed; skipping (run `impel app install` or `impel setup`).");
|
|
289
|
-
} else {
|
|
290
|
-
console.log(io.platform === "win32"
|
|
291
|
-
? "Apps: updating every installed tenant's signed Claude and ChatGPT profiles…"
|
|
292
|
-
: "Apps: refreshing every installed tenant and rebuilding only stale app bundles…");
|
|
293
|
-
// These child commands inherit the terminal and render their own progress
|
|
294
|
-
// and log lines. Wrapping them in another spinner makes both processes
|
|
295
|
-
// write the same terminal row, producing glued output such as
|
|
296
|
-
// "Updating managed desktop apps (...)Updating all managed...".
|
|
297
|
-
if (!await io.runAppsUpdate()) {
|
|
298
|
-
console.error("impel update: the app update failed; re-run `impel app update` after fixing the issue.");
|
|
299
|
-
cascadeFailed = true;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
console.log("Skills: syncing native and isolated CLI profiles…");
|
|
304
|
-
if (!await io.runSkillsSync()) {
|
|
305
|
-
console.error("impel update: skill sync failed; re-run `impel skills sync` after fixing the issue.");
|
|
306
|
-
cascadeFailed = true;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
console.log("Agents: syncing the selected tenant into native and isolated CLI profiles…");
|
|
310
|
-
if (!await io.runAgentsSync()) {
|
|
311
|
-
console.error("impel update: agent sync failed; re-run `impel agents sync` after fixing the issue.");
|
|
312
|
-
cascadeFailed = true;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (cascadeFailed) {
|
|
263
|
+
console.log("Tenants: discovering, installing, repairing, and upgrading every accessible tenant…");
|
|
264
|
+
const convergenceArgs = {
|
|
265
|
+
skipApps: Boolean(flags["skip-apps"]),
|
|
266
|
+
noRecovery: Boolean(flags["no-recovery"]),
|
|
267
|
+
};
|
|
268
|
+
if (!await io.runConvergence(convergenceArgs)) {
|
|
269
|
+
console.error("impel update: tenant convergence failed; rerun `impel update` after addressing the reported issue.");
|
|
316
270
|
process.exitCode = 1;
|
|
317
271
|
return;
|
|
318
272
|
}
|