@codyswann/lisa 2.227.0 → 2.229.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/all/copy-contents/gitignore +2 -0
- package/dist/cli/ui-cmd.d.ts +30 -2
- package/dist/cli/ui-cmd.d.ts.map +1 -1
- package/dist/cli/ui-cmd.js +191 -14
- package/dist/cli/ui-cmd.js.map +1 -1
- package/dist/cli/ui-status-json.d.ts +29 -0
- package/dist/cli/ui-status-json.d.ts.map +1 -0
- package/dist/cli/ui-status-json.js +162 -0
- package/dist/cli/ui-status-json.js.map +1 -0
- package/dist/cli/ui-status.d.ts +42 -0
- package/dist/cli/ui-status.d.ts.map +1 -0
- package/dist/cli/ui-status.js +221 -0
- package/dist/cli/ui-status.js.map +1 -0
- package/dist/core/lisa.d.ts +74 -1
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +165 -25
- package/dist/core/lisa.js.map +1 -1
- package/package.json +2 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/scripts/install-claude-plugins.sh +89 -9
- package/ui/README.md +16 -0
- package/ui/index.html +299 -8
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live-status probe contract and bounded execution for `lisa ui`.
|
|
3
|
+
* @module cli/ui-status
|
|
4
|
+
*/
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { normalizeProbeResult } from "./ui-status-json.js";
|
|
7
|
+
/** Error used internally to distinguish a bounded probe timeout. */
|
|
8
|
+
class ProbeTimeoutError extends Error {
|
|
9
|
+
/**
|
|
10
|
+
* Create a probe timeout error.
|
|
11
|
+
* @param timeoutMs - Configured timeout in milliseconds
|
|
12
|
+
*/
|
|
13
|
+
constructor(timeoutMs) {
|
|
14
|
+
super(`Probe timed out after ${timeoutMs}ms`);
|
|
15
|
+
this.name = "ProbeTimeoutError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Convert arbitrary thrown data into a stable failure classification.
|
|
20
|
+
* @param error - Value thrown by a probe
|
|
21
|
+
* @returns Timeout classification and non-empty human-readable message
|
|
22
|
+
*/
|
|
23
|
+
function classifyProbeError(error) {
|
|
24
|
+
try {
|
|
25
|
+
const timedOut = error instanceof ProbeTimeoutError;
|
|
26
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
27
|
+
return {
|
|
28
|
+
timedOut,
|
|
29
|
+
message: rawMessage.trim().length > 0
|
|
30
|
+
? rawMessage
|
|
31
|
+
: "Probe failed without an error message",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return {
|
|
36
|
+
timedOut: false,
|
|
37
|
+
message: "Probe failed with an unreadable error",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run one probe without allowing it to block siblings or fabricate data.
|
|
43
|
+
* @param probe - Probe to execute
|
|
44
|
+
* @returns Explicit result, or unknown for invalid input, failure, or timeout
|
|
45
|
+
*/
|
|
46
|
+
export async function runProbe(probe) {
|
|
47
|
+
if (!Number.isFinite(probe.timeoutMs) || probe.timeoutMs < 0) {
|
|
48
|
+
return {
|
|
49
|
+
state: "unknown",
|
|
50
|
+
reason: "invalid-timeout",
|
|
51
|
+
message: `Probe timeout must be a finite non-negative number; received ${String(probe.timeoutMs)}`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
56
|
+
controller.signal.addEventListener("abort", () => reject(controller.signal.reason), { once: true });
|
|
57
|
+
});
|
|
58
|
+
const timer = setTimeout(() => controller.abort(new ProbeTimeoutError(probe.timeoutMs)), probe.timeoutMs);
|
|
59
|
+
try {
|
|
60
|
+
const result = await Promise.race([probe.run(controller.signal), timeout]);
|
|
61
|
+
return normalizeProbeResult(result);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const { timedOut, message } = classifyProbeError(error);
|
|
65
|
+
return {
|
|
66
|
+
state: "unknown",
|
|
67
|
+
reason: timedOut ? "timeout" : "probe-failed",
|
|
68
|
+
message,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Execute `gh auth status` without invoking a shell.
|
|
77
|
+
* @param cwd - Project root used as the command working directory
|
|
78
|
+
* @param timeoutMs - Child-process timeout in milliseconds
|
|
79
|
+
* @param signal - Cancellation signal controlled by the probe runner
|
|
80
|
+
* @param hostname - Project origin hostname whose active account is checked
|
|
81
|
+
* @returns Authentication state when the command runs
|
|
82
|
+
*/
|
|
83
|
+
const runGithubAuthStatus = async (cwd, timeoutMs, signal, hostname) => await new Promise((resolve, reject) => {
|
|
84
|
+
const ghExecutable = "gh";
|
|
85
|
+
execFile(
|
|
86
|
+
// eslint-disable-next-line sonarjs/no-os-command-from-path -- fixed user-installed gh executable
|
|
87
|
+
ghExecutable, ["auth", "status", "--active", "--hostname", hostname, "--json", "hosts"], { cwd, signal, timeout: timeoutMs, encoding: "utf8" }, (error, stdout) => {
|
|
88
|
+
if (error !== null) {
|
|
89
|
+
reject(error);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(stdout);
|
|
94
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
95
|
+
throw new TypeError("GitHub auth status did not return an object");
|
|
96
|
+
}
|
|
97
|
+
const hosts = Reflect.get(parsed, "hosts");
|
|
98
|
+
if (typeof hosts !== "object" || hosts === null) {
|
|
99
|
+
throw new TypeError("GitHub auth status omitted its hosts map");
|
|
100
|
+
}
|
|
101
|
+
const accounts = Reflect.get(hosts, hostname);
|
|
102
|
+
const authenticated = Array.isArray(accounts) &&
|
|
103
|
+
accounts.some(account => typeof account === "object" &&
|
|
104
|
+
account !== null &&
|
|
105
|
+
Reflect.get(account, "active") === true &&
|
|
106
|
+
Reflect.get(account, "state") === "success");
|
|
107
|
+
resolve(authenticated ? "authenticated" : "not-authenticated");
|
|
108
|
+
}
|
|
109
|
+
catch (parseError) {
|
|
110
|
+
reject(parseError);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
/**
|
|
115
|
+
* Read the effective origin URL through git without invoking a shell.
|
|
116
|
+
* @param cwd - Project root
|
|
117
|
+
* @param timeoutMs - Child-process timeout
|
|
118
|
+
* @param signal - Probe cancellation signal
|
|
119
|
+
* @returns Origin remote URL
|
|
120
|
+
*/
|
|
121
|
+
async function readOriginRemote(cwd, timeoutMs, signal) {
|
|
122
|
+
return await new Promise((resolve, reject) => {
|
|
123
|
+
const gitExecutable = "git";
|
|
124
|
+
execFile(
|
|
125
|
+
// eslint-disable-next-line sonarjs/no-os-command-from-path -- fixed user-installed git executable
|
|
126
|
+
gitExecutable, ["-C", cwd, "remote", "get-url", "origin"], { cwd, signal, timeout: timeoutMs, encoding: "utf8" }, (error, stdout) => {
|
|
127
|
+
if (error !== null) {
|
|
128
|
+
reject(error);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const remote = stdout.trim();
|
|
132
|
+
if (remote.length === 0) {
|
|
133
|
+
reject(new Error("Project origin remote is empty"));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
resolve(remote);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Extract a hostname from URL-form and scp-form git remotes.
|
|
142
|
+
* @param remote - Git remote URL
|
|
143
|
+
* @returns Hostname used by `gh auth status`
|
|
144
|
+
*/
|
|
145
|
+
function remoteHostname(remote) {
|
|
146
|
+
const normalize = (hostname) => {
|
|
147
|
+
const normalized = hostname.toLowerCase();
|
|
148
|
+
return normalized.endsWith(".") ? normalized.slice(0, -1) : normalized;
|
|
149
|
+
};
|
|
150
|
+
if (!remote.includes("://")) {
|
|
151
|
+
const separator = remote.indexOf(":");
|
|
152
|
+
const authority = separator > 0 ? remote.slice(0, separator) : "";
|
|
153
|
+
const host = authority.slice(authority.lastIndexOf("@") + 1);
|
|
154
|
+
const looksLikeWindowsPath = authority.length === 1 && /[A-Za-z]/u.test(authority);
|
|
155
|
+
if (host.length > 0 &&
|
|
156
|
+
!looksLikeWindowsPath &&
|
|
157
|
+
!Array.from(host).some(character => /\s/u.test(character))) {
|
|
158
|
+
return normalize(host);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const hostname = new URL(remote).hostname;
|
|
163
|
+
if (hostname.length > 0) {
|
|
164
|
+
return normalize(hostname);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Fall through to the explicit error below.
|
|
169
|
+
}
|
|
170
|
+
throw new Error(`Unable to determine GitHub hostname from origin: ${remote}`);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Create the reference GitHub authentication probe.
|
|
174
|
+
* @param cwd - Project root used as the command working directory
|
|
175
|
+
* @param check - Injectable command check used by focused tests
|
|
176
|
+
* @param readRemote - Injectable effective-origin reader used by focused tests
|
|
177
|
+
* @returns Reference probe for the live-status transport
|
|
178
|
+
*/
|
|
179
|
+
export function createGithubAuthProbe(cwd, check = runGithubAuthStatus, readRemote = readOriginRemote) {
|
|
180
|
+
const timeoutMs = 5_000;
|
|
181
|
+
return {
|
|
182
|
+
id: "github-auth",
|
|
183
|
+
timeoutMs,
|
|
184
|
+
run: async (signal) => {
|
|
185
|
+
const remote = await readRemote(cwd, timeoutMs + 250, signal);
|
|
186
|
+
const hostname = remoteHostname(remote);
|
|
187
|
+
const state = await check(cwd, timeoutMs + 250, signal, hostname);
|
|
188
|
+
return state === "authenticated"
|
|
189
|
+
? { state: "value", value: true }
|
|
190
|
+
: {
|
|
191
|
+
state: "unknown",
|
|
192
|
+
reason: "not-authenticated",
|
|
193
|
+
message: "GitHub CLI is not authenticated",
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Validate probe keys before binding a server so status snapshots cannot lose data.
|
|
200
|
+
* @param probes - Registered status probes
|
|
201
|
+
*/
|
|
202
|
+
export function validateStatusProbes(probes) {
|
|
203
|
+
for (const [index, probe] of probes.entries()) {
|
|
204
|
+
if (probe.id.trim().length === 0) {
|
|
205
|
+
throw new Error("Probe id must contain a non-whitespace character");
|
|
206
|
+
}
|
|
207
|
+
if (probes.slice(0, index).some(candidate => candidate.id === probe.id)) {
|
|
208
|
+
throw new Error(`Duplicate probe id: ${probe.id}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Execute all probes concurrently, with failures isolated per probe.
|
|
214
|
+
* @param probes - Probes to execute
|
|
215
|
+
* @returns Results keyed by stable probe identifier
|
|
216
|
+
*/
|
|
217
|
+
export async function readStatusSnapshot(probes) {
|
|
218
|
+
const entries = await Promise.all(probes.map(async (probe) => [probe.id, await runProbe(probe)]));
|
|
219
|
+
return Object.fromEntries(entries);
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=ui-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-status.js","sourceRoot":"","sources":["../../src/cli/ui-status.ts"],"names":[],"mappings":"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"}
|
package/dist/core/lisa.d.ts
CHANGED
|
@@ -226,11 +226,76 @@ export declare class Lisa {
|
|
|
226
226
|
*/
|
|
227
227
|
private registerPlugins;
|
|
228
228
|
/**
|
|
229
|
-
* Install each plugin and refresh the Lisa marketplace cache
|
|
229
|
+
* Install each plugin and refresh the Lisa marketplace cache.
|
|
230
|
+
*
|
|
231
|
+
* Version-gated (perf): `claude plugin marketplace update` is a network git
|
|
232
|
+
* pull of the Lisa repo and every `claude plugin install` spawns the Claude
|
|
233
|
+
* CLI (seconds each); re-running all of it on every apply made a single
|
|
234
|
+
* `bun add` cost minutes across the repeated apply passes. A full sync
|
|
235
|
+
* (marketplace refresh + install every plugin) still runs whenever the
|
|
236
|
+
* installed Lisa version differs from the `.claude/.lisa-plugins-synced`
|
|
237
|
+
* marker; between version changes only plugins missing for this project are
|
|
238
|
+
* installed, so the self-heal property (a removed plugin comes back on the
|
|
239
|
+
* next install) survives at the cost of one `claude plugin list` call.
|
|
230
240
|
* @param execAsync - Promisified exec function for running shell commands
|
|
231
241
|
* @param plugins - Plugin identifiers from enabledPlugins (e.g. "lisa@lisa")
|
|
232
242
|
*/
|
|
233
243
|
private installPluginsAndUpdateMarketplace;
|
|
244
|
+
/**
|
|
245
|
+
* Decide whether this apply needs a full plugin sync and which plugins to
|
|
246
|
+
* install.
|
|
247
|
+
* @param execAsync - Promisified exec function for running shell commands
|
|
248
|
+
* @param plugins - Plugin identifiers from enabledPlugins
|
|
249
|
+
* @returns Sync plan: fullSync (version changed), plugins to install, and
|
|
250
|
+
* the current Lisa version
|
|
251
|
+
*/
|
|
252
|
+
private computePluginSyncPlan;
|
|
253
|
+
/**
|
|
254
|
+
* Refresh the cached Lisa marketplace via the Claude CLI.
|
|
255
|
+
* @param execAsync - Promisified exec function for running shell commands
|
|
256
|
+
* @param announce - Whether to log the outcome (silent for the pre-install
|
|
257
|
+
* best-effort refresh)
|
|
258
|
+
* @returns Promise that resolves when the refresh attempt completes
|
|
259
|
+
*/
|
|
260
|
+
private updateLisaMarketplace;
|
|
261
|
+
/**
|
|
262
|
+
* Install a single plugin at project scope via the Claude CLI.
|
|
263
|
+
* @param execAsync - Promisified exec function for running shell commands
|
|
264
|
+
* @param plugin - Plugin identifier (already validated)
|
|
265
|
+
* @returns Promise that resolves when the install attempt completes
|
|
266
|
+
*/
|
|
267
|
+
private installPlugin;
|
|
268
|
+
/**
|
|
269
|
+
* Exec options shared by all Claude CLI plugin commands.
|
|
270
|
+
* @returns Options object with the project cwd and POSIX shell
|
|
271
|
+
*/
|
|
272
|
+
private pluginExecOptions;
|
|
273
|
+
/**
|
|
274
|
+
* Absolute path of the plugin sync marker recording the last fully-synced
|
|
275
|
+
* Lisa version for this project. Shared with install-claude-plugins.sh.
|
|
276
|
+
* @returns Marker path under the project's .claude directory
|
|
277
|
+
*/
|
|
278
|
+
private pluginSyncMarkerPath;
|
|
279
|
+
/**
|
|
280
|
+
* Absolute path of the host project's package.json.
|
|
281
|
+
* @returns package.json path under the destination directory
|
|
282
|
+
*/
|
|
283
|
+
private hostPackageJsonPath;
|
|
284
|
+
/**
|
|
285
|
+
* Read the plugin ids already installed for this project via
|
|
286
|
+
* `claude plugin list --json`.
|
|
287
|
+
* @param execAsync - Promisified exec function for running shell commands
|
|
288
|
+
* @returns Set of installed plugin ids, or null when the list cannot be
|
|
289
|
+
* obtained (older CLI, parse failure) so callers fall back to installing
|
|
290
|
+
* everything — the pre-existing behavior
|
|
291
|
+
*/
|
|
292
|
+
private readInstalledPluginIds;
|
|
293
|
+
/**
|
|
294
|
+
* Record the Lisa version whose plugin set was last fully synced.
|
|
295
|
+
* @param version - Lisa package version to record
|
|
296
|
+
* @returns Promise that resolves when the marker is written (best-effort)
|
|
297
|
+
*/
|
|
298
|
+
private writePluginSyncMarker;
|
|
234
299
|
/**
|
|
235
300
|
* Finalize operation
|
|
236
301
|
* @returns Promise that resolves when finalization is complete
|
|
@@ -387,6 +452,14 @@ export declare class Lisa {
|
|
|
387
452
|
* The trampoline is always fully detached so the package manager can exit
|
|
388
453
|
* normally; the child then detects the exit and re-runs Lisa. See
|
|
389
454
|
* utils/postinstall-trampoline.ts for details.
|
|
455
|
+
*
|
|
456
|
+
* Skipped when this apply did not mutate package.json: the trampoline exists
|
|
457
|
+
* solely to redo package.json changes that the parent package manager's
|
|
458
|
+
* end-of-command rewrite clobbers (#383). With no mutation there is nothing
|
|
459
|
+
* to clobber — the rewrite restores identical content — and scheduling a
|
|
460
|
+
* detached re-apply (plus its lockfile regen) is pure waste. File-based
|
|
461
|
+
* template writes are not clobbered and need no reconciliation.
|
|
462
|
+
* @param prePackageJsonHash - Hash of package.json captured before apply mutated anything (null if the file did not exist)
|
|
390
463
|
* @returns Promise that resolves immediately after the detached child is spawned
|
|
391
464
|
*/
|
|
392
465
|
private schedulePostinstallReconciliation;
|
package/dist/core/lisa.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lisa.d.ts","sourceRoot":"","sources":["../../src/core/lisa.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"lisa.d.ts","sourceRoot":"","sources":["../../src/core/lisa.ts"],"names":[],"mappings":"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"}
|
package/dist/core/lisa.js
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync } from "node:fs";
|
|
|
4
4
|
import { readFile, stat } from "node:fs/promises";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import pc from "picocolors";
|
|
7
|
+
import { getPackageVersion } from "../cli/version.js";
|
|
7
8
|
import { installAgentsMd } from "../codex/agents-md-installer.js";
|
|
8
9
|
import { installCodexProjectOverlay } from "../codex/project-overlay.js";
|
|
9
10
|
import { installAgyPlugin } from "../agy/plugin-installer.js";
|
|
@@ -491,49 +492,171 @@ export class Lisa {
|
|
|
491
492
|
await this.installPluginsAndUpdateMarketplace(execAsync, plugins);
|
|
492
493
|
}
|
|
493
494
|
/**
|
|
494
|
-
* Install each plugin and refresh the Lisa marketplace cache
|
|
495
|
+
* Install each plugin and refresh the Lisa marketplace cache.
|
|
496
|
+
*
|
|
497
|
+
* Version-gated (perf): `claude plugin marketplace update` is a network git
|
|
498
|
+
* pull of the Lisa repo and every `claude plugin install` spawns the Claude
|
|
499
|
+
* CLI (seconds each); re-running all of it on every apply made a single
|
|
500
|
+
* `bun add` cost minutes across the repeated apply passes. A full sync
|
|
501
|
+
* (marketplace refresh + install every plugin) still runs whenever the
|
|
502
|
+
* installed Lisa version differs from the `.claude/.lisa-plugins-synced`
|
|
503
|
+
* marker; between version changes only plugins missing for this project are
|
|
504
|
+
* installed, so the self-heal property (a removed plugin comes back on the
|
|
505
|
+
* next install) survives at the cost of one `claude plugin list` call.
|
|
495
506
|
* @param execAsync - Promisified exec function for running shell commands
|
|
496
507
|
* @param plugins - Plugin identifiers from enabledPlugins (e.g. "lisa@lisa")
|
|
497
508
|
*/
|
|
498
509
|
async installPluginsAndUpdateMarketplace(execAsync, plugins) {
|
|
499
510
|
const { logger } = this.deps;
|
|
500
511
|
const validPluginName = /^[\w@./-]+$/;
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
shell: "/bin/sh",
|
|
506
|
-
});
|
|
512
|
+
const { fullSync, toInstall, version } = await this.computePluginSyncPlan(execAsync, plugins);
|
|
513
|
+
if (!fullSync && toInstall.length === 0) {
|
|
514
|
+
logger.info(pc.gray(`Plugins already in sync for Lisa ${version}; skipping`));
|
|
515
|
+
return;
|
|
507
516
|
}
|
|
508
|
-
|
|
517
|
+
logger.info("Registering plugins with Claude Code (project scope)...");
|
|
518
|
+
if (fullSync) {
|
|
509
519
|
// Best-effort cache refresh. Individual plugin installs below will still
|
|
510
520
|
// report whether the marketplace entry is available.
|
|
521
|
+
await this.updateLisaMarketplace(execAsync, false);
|
|
511
522
|
}
|
|
512
|
-
for (const plugin of
|
|
523
|
+
for (const plugin of toInstall) {
|
|
513
524
|
if (!validPluginName.test(plugin)) {
|
|
514
525
|
logger.warn(`Skipping invalid plugin name: ${plugin}`);
|
|
515
526
|
continue;
|
|
516
527
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
528
|
+
await this.installPlugin(execAsync, plugin);
|
|
529
|
+
}
|
|
530
|
+
// Post-install refresh must run whenever installs happened: installing can
|
|
531
|
+
// register the marketplace for the first time, and without a refresh newly
|
|
532
|
+
// added skills are not discovered ("Unknown skill" in nightly CI — #320).
|
|
533
|
+
await this.updateLisaMarketplace(execAsync, true);
|
|
534
|
+
if (fullSync) {
|
|
535
|
+
await this.writePluginSyncMarker(version);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Decide whether this apply needs a full plugin sync and which plugins to
|
|
540
|
+
* install.
|
|
541
|
+
* @param execAsync - Promisified exec function for running shell commands
|
|
542
|
+
* @param plugins - Plugin identifiers from enabledPlugins
|
|
543
|
+
* @returns Sync plan: fullSync (version changed), plugins to install, and
|
|
544
|
+
* the current Lisa version
|
|
545
|
+
*/
|
|
546
|
+
async computePluginSyncPlan(execAsync, plugins) {
|
|
547
|
+
const version = getPackageVersion();
|
|
548
|
+
const syncedVersion = await readFile(this.pluginSyncMarkerPath(), "utf-8")
|
|
549
|
+
.then(content => content.trim())
|
|
550
|
+
.catch(() => null);
|
|
551
|
+
const fullSync = syncedVersion !== version;
|
|
552
|
+
const installed = fullSync
|
|
553
|
+
? null
|
|
554
|
+
: await this.readInstalledPluginIds(execAsync);
|
|
555
|
+
const toInstall = installed === null
|
|
556
|
+
? plugins
|
|
557
|
+
: plugins.filter(plugin => !installed.has(plugin));
|
|
558
|
+
return { fullSync, toInstall, version };
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Refresh the cached Lisa marketplace via the Claude CLI.
|
|
562
|
+
* @param execAsync - Promisified exec function for running shell commands
|
|
563
|
+
* @param announce - Whether to log the outcome (silent for the pre-install
|
|
564
|
+
* best-effort refresh)
|
|
565
|
+
* @returns Promise that resolves when the refresh attempt completes
|
|
566
|
+
*/
|
|
567
|
+
async updateLisaMarketplace(execAsync, announce) {
|
|
568
|
+
const { logger } = this.deps;
|
|
569
|
+
try {
|
|
570
|
+
await execAsync("claude plugin marketplace update lisa", this.pluginExecOptions());
|
|
571
|
+
if (announce) {
|
|
572
|
+
logger.success("Updated marketplace: lisa");
|
|
523
573
|
}
|
|
524
|
-
|
|
525
|
-
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
if (announce) {
|
|
577
|
+
logger.warn("Could not update marketplace: lisa");
|
|
526
578
|
}
|
|
527
579
|
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Install a single plugin at project scope via the Claude CLI.
|
|
583
|
+
* @param execAsync - Promisified exec function for running shell commands
|
|
584
|
+
* @param plugin - Plugin identifier (already validated)
|
|
585
|
+
* @returns Promise that resolves when the install attempt completes
|
|
586
|
+
*/
|
|
587
|
+
async installPlugin(execAsync, plugin) {
|
|
588
|
+
const { logger } = this.deps;
|
|
589
|
+
try {
|
|
590
|
+
await execAsync(`claude plugin install ${plugin} --scope project`, this.pluginExecOptions());
|
|
591
|
+
logger.success(`Registered plugin: ${plugin}`);
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
logger.warn(`Could not register plugin: ${plugin}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Exec options shared by all Claude CLI plugin commands.
|
|
599
|
+
* @returns Options object with the project cwd and POSIX shell
|
|
600
|
+
*/
|
|
601
|
+
pluginExecOptions() {
|
|
602
|
+
return { cwd: this.config.destDir, shell: "/bin/sh" };
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Absolute path of the plugin sync marker recording the last fully-synced
|
|
606
|
+
* Lisa version for this project. Shared with install-claude-plugins.sh.
|
|
607
|
+
* @returns Marker path under the project's .claude directory
|
|
608
|
+
*/
|
|
609
|
+
pluginSyncMarkerPath() {
|
|
610
|
+
return path.join(this.config.destDir, ".claude", ".lisa-plugins-synced");
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Absolute path of the host project's package.json.
|
|
614
|
+
* @returns package.json path under the destination directory
|
|
615
|
+
*/
|
|
616
|
+
hostPackageJsonPath() {
|
|
617
|
+
return path.join(this.config.destDir, "package.json");
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Read the plugin ids already installed for this project via
|
|
621
|
+
* `claude plugin list --json`.
|
|
622
|
+
* @param execAsync - Promisified exec function for running shell commands
|
|
623
|
+
* @returns Set of installed plugin ids, or null when the list cannot be
|
|
624
|
+
* obtained (older CLI, parse failure) so callers fall back to installing
|
|
625
|
+
* everything — the pre-existing behavior
|
|
626
|
+
*/
|
|
627
|
+
async readInstalledPluginIds(execAsync) {
|
|
528
628
|
try {
|
|
529
|
-
await execAsync("claude plugin
|
|
629
|
+
const result = (await execAsync("claude plugin list --json", {
|
|
530
630
|
cwd: this.config.destDir,
|
|
531
631
|
shell: "/bin/sh",
|
|
532
|
-
});
|
|
533
|
-
|
|
632
|
+
}));
|
|
633
|
+
const parsed = JSON.parse(String(result.stdout ?? ""));
|
|
634
|
+
if (!Array.isArray(parsed)) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
const destPath = path.resolve(this.config.destDir);
|
|
638
|
+
const ids = parsed
|
|
639
|
+
.filter((entry) => typeof entry.id === "string" &&
|
|
640
|
+
entry.projectPath === destPath)
|
|
641
|
+
.map(entry => entry.id);
|
|
642
|
+
return new Set(ids);
|
|
534
643
|
}
|
|
535
644
|
catch {
|
|
536
|
-
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Record the Lisa version whose plugin set was last fully synced.
|
|
650
|
+
* @param version - Lisa package version to record
|
|
651
|
+
* @returns Promise that resolves when the marker is written (best-effort)
|
|
652
|
+
*/
|
|
653
|
+
async writePluginSyncMarker(version) {
|
|
654
|
+
try {
|
|
655
|
+
await fse.ensureDir(path.join(this.config.destDir, ".claude"));
|
|
656
|
+
await fse.writeFile(this.pluginSyncMarkerPath(), `${version}\n`, "utf-8");
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
// Best-effort: a missing marker only costs a redundant full sync next run.
|
|
537
660
|
}
|
|
538
661
|
}
|
|
539
662
|
/* v8 ignore stop */
|
|
@@ -602,8 +725,12 @@ export class Lisa {
|
|
|
602
725
|
await this.loadPendingDeletions();
|
|
603
726
|
await this.loadCreateOnlyOwnership();
|
|
604
727
|
await this.loadCopyOverwriteOwnership();
|
|
728
|
+
// Captured BEFORE any mutation (migrations included): the hash gates both
|
|
729
|
+
// the detached trampoline and the in-process lockfile reconcile, and
|
|
730
|
+
// before-strategies migrations can wire scripts.postinstall — a change the
|
|
731
|
+
// package manager's end-of-command rewrite would clobber (#383).
|
|
732
|
+
const prePackageJsonHash = hashFile(this.hostPackageJsonPath());
|
|
605
733
|
await this.runMigrationsBeforeStrategies();
|
|
606
|
-
const prePackageJsonHash = hashFile(path.join(this.config.destDir, "package.json"));
|
|
607
734
|
await this.processConfigurations();
|
|
608
735
|
if (this.selfApply) {
|
|
609
736
|
this.deps.logger.info(pc.gray("Self-apply (Lisa source repo): skipped deletions.json processing"));
|
|
@@ -622,7 +749,7 @@ export class Lisa {
|
|
|
622
749
|
await this.finalize();
|
|
623
750
|
this.printSummary();
|
|
624
751
|
await this.printMigrationNotices(this.config.destDir);
|
|
625
|
-
await this.schedulePostinstallReconciliation();
|
|
752
|
+
await this.schedulePostinstallReconciliation(prePackageJsonHash);
|
|
626
753
|
await this.reconcileLockfilesInProcess(prePackageJsonHash);
|
|
627
754
|
return this.getSuccessResult();
|
|
628
755
|
}
|
|
@@ -657,7 +784,7 @@ export class Lisa {
|
|
|
657
784
|
return;
|
|
658
785
|
if (shouldSchedulePostinstallReconciliation(this.config.dryRun))
|
|
659
786
|
return;
|
|
660
|
-
const pkgPath =
|
|
787
|
+
const pkgPath = this.hostPackageJsonPath();
|
|
661
788
|
const postHash = hashFile(pkgPath);
|
|
662
789
|
if (prePackageJsonHash === null ||
|
|
663
790
|
postHash === null ||
|
|
@@ -987,9 +1114,22 @@ export class Lisa {
|
|
|
987
1114
|
* The trampoline is always fully detached so the package manager can exit
|
|
988
1115
|
* normally; the child then detects the exit and re-runs Lisa. See
|
|
989
1116
|
* utils/postinstall-trampoline.ts for details.
|
|
1117
|
+
*
|
|
1118
|
+
* Skipped when this apply did not mutate package.json: the trampoline exists
|
|
1119
|
+
* solely to redo package.json changes that the parent package manager's
|
|
1120
|
+
* end-of-command rewrite clobbers (#383). With no mutation there is nothing
|
|
1121
|
+
* to clobber — the rewrite restores identical content — and scheduling a
|
|
1122
|
+
* detached re-apply (plus its lockfile regen) is pure waste. File-based
|
|
1123
|
+
* template writes are not clobbered and need no reconciliation.
|
|
1124
|
+
* @param prePackageJsonHash - Hash of package.json captured before apply mutated anything (null if the file did not exist)
|
|
990
1125
|
* @returns Promise that resolves immediately after the detached child is spawned
|
|
991
1126
|
*/
|
|
992
|
-
async schedulePostinstallReconciliation() {
|
|
1127
|
+
async schedulePostinstallReconciliation(prePackageJsonHash) {
|
|
1128
|
+
const postHash = hashFile(this.hostPackageJsonPath());
|
|
1129
|
+
if (prePackageJsonHash === postHash) {
|
|
1130
|
+
this.deps.logger.info(pc.gray("Postinstall reconciliation skipped: package.json unchanged by this apply"));
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
993
1133
|
if (!shouldSchedulePostinstallReconciliation(this.config.dryRun)) {
|
|
994
1134
|
return;
|
|
995
1135
|
}
|