lavish-axi 0.1.45 → 0.1.46
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 +27 -0
- package/dist/cli.mjs +415 -60
- package/package.json +5 -2
- package/plugin.json +21 -0
- package/skills/lavish/SKILL.md +5 -5
package/README.md
CHANGED
|
@@ -95,6 +95,32 @@ This installs a `SessionStart` hook for **Claude Code**, **Codex**, **OpenCode**
|
|
|
95
95
|
Unlike the skill, the hook also shows your live open sessions, so a fresh agent session can resume an in-flight review.
|
|
96
96
|
**Restart your agent session after running this** so the new hook takes effect.
|
|
97
97
|
|
|
98
|
+
### Agent Plugin
|
|
99
|
+
|
|
100
|
+
Lavish also ships as an [Agent Plugin](https://agent-plugins.org) - the vendor-neutral packaging standard for skills and MCP servers - so clients that speak that format can load it directly.
|
|
101
|
+
|
|
102
|
+
**No marketplace is involved.** The installed npm package _is_ the plugin: `plugin.json` sits at the package root next to the `skills/` directory, so whatever `npm install` already put on disk is a complete, conformant plugin. Install the CLI, then register it:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
npm install -g lavish-axi
|
|
106
|
+
lavish-axi setup plugin
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
That registers the installed package with every supported client it finds - **VS Code**, **Cursor**, and **GitHub Copilot CLI** - and reports which ones were absent. It is opt-in and idempotent, and it repairs the registered path after a reinstall or relocation. Reload each client afterward.
|
|
110
|
+
|
|
111
|
+
Each client is registered independently: one that cannot be registered is reported with what to do about it, and never blocks the others or fails the command.
|
|
112
|
+
|
|
113
|
+
To register by hand instead, point any client at the package directory (`npm root -g`/`lavish-axi`):
|
|
114
|
+
|
|
115
|
+
| Client | Register with |
|
|
116
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------ |
|
|
117
|
+
| VS Code | `"chat.pluginLocations": { "<package-dir>": true }` in user settings |
|
|
118
|
+
| Cursor | link the package dir at `~/.cursor/plugins/local/lavish-axi` (`setup plugin` handles Windows link compatibility) |
|
|
119
|
+
| GitHub Copilot CLI | `copilot plugin install <package-dir>` (or `copilot plugin install kunchenguid/lavish-axi` straight from the repo) |
|
|
120
|
+
|
|
121
|
+
Codex and ChatGPT install plugins only from marketplace sources, so Codex users should use the session hook above instead.
|
|
122
|
+
Lavish declares no MCP server - the CLI itself is the agent interface - so a plugin install brings the same `lavish` skill, and the skill and plugin are alternatives rather than a stack.
|
|
123
|
+
|
|
98
124
|
### From source
|
|
99
125
|
|
|
100
126
|
```sh
|
|
@@ -207,6 +233,7 @@ pnpm link
|
|
|
207
233
|
| `lavish-axi playbook [id]` | List focused artifact guidance or show one playbook; agents must open each matching playbook before writing HTML. |
|
|
208
234
|
| `lavish-axi design` | Show agent-facing design guidance, including optional CDN and Mermaid snippets. |
|
|
209
235
|
| `lavish-axi setup hooks` | Install or repair optional SessionStart hooks for Claude Code, Codex, OpenCode, and GitHub Copilot CLI; restart the agent session afterward. |
|
|
236
|
+
| `lavish-axi setup plugin` | Register the installed package as an [Agent Plugin](https://agent-plugins.org) in VS Code, Cursor, and GitHub Copilot CLI; opt-in, idempotent, no marketplace involved. Reload each client afterward. |
|
|
210
237
|
| `lavish-axi server` | Run the local Lavish Editor server. |
|
|
211
238
|
|
|
212
239
|
Known playbook IDs: `diagram`, `table`, `comparison`, `plan`, `code`, `input`, `slides`.
|
package/dist/cli.mjs
CHANGED
|
@@ -7,11 +7,11 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/cli.js
|
|
9
9
|
import { spawn, spawnSync } from "node:child_process";
|
|
10
|
-
import { closeSync, existsSync as
|
|
10
|
+
import { closeSync, existsSync as existsSync3, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
11
11
|
import { access, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import { fileURLToPath as
|
|
12
|
+
import os3 from "node:os";
|
|
13
|
+
import path7 from "node:path";
|
|
14
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
15
15
|
import { AxiError, installSessionStartHooks, RESERVED_COMMANDS, runAxiCli } from "axi-sdk-js";
|
|
16
16
|
|
|
17
17
|
// src/playbooks.js
|
|
@@ -4285,14 +4285,179 @@ function defaultPort() {
|
|
|
4285
4285
|
return Number(process.env.LAVISH_AXI_PORT || 4387);
|
|
4286
4286
|
}
|
|
4287
4287
|
|
|
4288
|
+
// src/plugin.js
|
|
4289
|
+
import { randomUUID } from "node:crypto";
|
|
4290
|
+
import {
|
|
4291
|
+
chmodSync,
|
|
4292
|
+
existsSync,
|
|
4293
|
+
lstatSync,
|
|
4294
|
+
mkdirSync,
|
|
4295
|
+
readFileSync,
|
|
4296
|
+
readlinkSync,
|
|
4297
|
+
renameSync,
|
|
4298
|
+
rmSync,
|
|
4299
|
+
statSync,
|
|
4300
|
+
symlinkSync,
|
|
4301
|
+
writeFileSync
|
|
4302
|
+
} from "node:fs";
|
|
4303
|
+
import { createRequire } from "node:module";
|
|
4304
|
+
import os2 from "node:os";
|
|
4305
|
+
import path3 from "node:path";
|
|
4306
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4307
|
+
var crossSpawn = createRequire(import.meta.url)("cross-spawn");
|
|
4308
|
+
var PLUGIN_AUTHOR = Object.freeze({ name: "Kun Chen", url: "https://github.com/kunchenguid" });
|
|
4309
|
+
function spawnPluginClientSync(command, args) {
|
|
4310
|
+
return crossSpawn.sync(command, args, { encoding: "utf8" });
|
|
4311
|
+
}
|
|
4312
|
+
function resolvePluginRoot() {
|
|
4313
|
+
return path3.resolve(fileURLToPath2(new URL("../", import.meta.url)));
|
|
4314
|
+
}
|
|
4315
|
+
function readPluginManifest(root) {
|
|
4316
|
+
try {
|
|
4317
|
+
return JSON.parse(readFileSync(path3.join(root, "plugin.json"), "utf8"));
|
|
4318
|
+
} catch {
|
|
4319
|
+
return null;
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
function isStalePluginLocation(candidate, pluginName) {
|
|
4323
|
+
const manifest = readPluginManifest(candidate);
|
|
4324
|
+
if (manifest) return manifest.name === pluginName;
|
|
4325
|
+
return !existsSync(candidate) && path3.basename(candidate) === pluginName;
|
|
4326
|
+
}
|
|
4327
|
+
function computeVsCodePluginLocationsUpdate(settings, pluginRoot, pluginName) {
|
|
4328
|
+
const updated = structuredClone(settings && typeof settings === "object" ? settings : {});
|
|
4329
|
+
const existing = updated["chat.pluginLocations"];
|
|
4330
|
+
const locations = existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : {};
|
|
4331
|
+
let changed = false;
|
|
4332
|
+
for (const key of Object.keys(locations)) {
|
|
4333
|
+
if (key !== pluginRoot && isStalePluginLocation(key, pluginName)) {
|
|
4334
|
+
delete locations[key];
|
|
4335
|
+
changed = true;
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
if (locations[pluginRoot] !== true) {
|
|
4339
|
+
locations[pluginRoot] = true;
|
|
4340
|
+
changed = true;
|
|
4341
|
+
}
|
|
4342
|
+
updated["chat.pluginLocations"] = locations;
|
|
4343
|
+
return [updated, changed];
|
|
4344
|
+
}
|
|
4345
|
+
function resolveVsCodeSettingsFile(env = process.env, homeDir = os2.homedir(), platform = process.platform) {
|
|
4346
|
+
const platformPath = platform === "win32" ? path3.win32 : path3.posix;
|
|
4347
|
+
if (platform === "win32") {
|
|
4348
|
+
return platformPath.join(
|
|
4349
|
+
env.APPDATA || platformPath.join(homeDir, "AppData", "Roaming"),
|
|
4350
|
+
"Code",
|
|
4351
|
+
"User",
|
|
4352
|
+
"settings.json"
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
4355
|
+
if (platform === "darwin") {
|
|
4356
|
+
return platformPath.join(homeDir, "Library", "Application Support", "Code", "User", "settings.json");
|
|
4357
|
+
}
|
|
4358
|
+
return platformPath.join(
|
|
4359
|
+
env.XDG_CONFIG_HOME || platformPath.join(homeDir, ".config"),
|
|
4360
|
+
"Code",
|
|
4361
|
+
"User",
|
|
4362
|
+
"settings.json"
|
|
4363
|
+
);
|
|
4364
|
+
}
|
|
4365
|
+
function resolveCursorLocalPluginsDir(homeDir = os2.homedir()) {
|
|
4366
|
+
return path3.join(homeDir, ".cursor", "plugins", "local");
|
|
4367
|
+
}
|
|
4368
|
+
function writeTextFileAtomically(file, content, operations = {}) {
|
|
4369
|
+
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
4370
|
+
const write = operations.writeFileSync || writeFileSync;
|
|
4371
|
+
const rename2 = operations.renameSync || renameSync;
|
|
4372
|
+
const remove = operations.rmSync || rmSync;
|
|
4373
|
+
const chmod = operations.chmodSync || chmodSync;
|
|
4374
|
+
const stat2 = operations.statSync || statSync;
|
|
4375
|
+
let mode;
|
|
4376
|
+
try {
|
|
4377
|
+
mode = stat2(file).mode & 511;
|
|
4378
|
+
} catch {
|
|
4379
|
+
}
|
|
4380
|
+
try {
|
|
4381
|
+
write(temporary, content, mode === void 0 ? "utf8" : { encoding: "utf8", mode });
|
|
4382
|
+
if (mode !== void 0) chmod(temporary, mode);
|
|
4383
|
+
rename2(temporary, file);
|
|
4384
|
+
} catch (error) {
|
|
4385
|
+
try {
|
|
4386
|
+
remove(temporary, { force: true });
|
|
4387
|
+
} catch {
|
|
4388
|
+
}
|
|
4389
|
+
throw error;
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
function createDirectoryLink(createSymlink, pluginRoot, linkPath, platform) {
|
|
4393
|
+
if (platform === "win32") {
|
|
4394
|
+
try {
|
|
4395
|
+
createSymlink(pluginRoot, linkPath, "junction");
|
|
4396
|
+
return;
|
|
4397
|
+
} catch {
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
createSymlink(pluginRoot, linkPath);
|
|
4401
|
+
}
|
|
4402
|
+
function linkCursorLocalPlugin(localPluginsDir, pluginRoot, pluginName, operations = {}) {
|
|
4403
|
+
const target = path3.join(localPluginsDir, pluginName);
|
|
4404
|
+
const platform = operations.platform || process.platform;
|
|
4405
|
+
const createSymlink = (linkTarget, linkPath) => createDirectoryLink(operations.symlinkSync || symlinkSync, linkTarget, linkPath, platform);
|
|
4406
|
+
const rename2 = operations.renameSync || renameSync;
|
|
4407
|
+
const remove = operations.rmSync || rmSync;
|
|
4408
|
+
let existing = null;
|
|
4409
|
+
try {
|
|
4410
|
+
existing = lstatSync(target);
|
|
4411
|
+
} catch {
|
|
4412
|
+
}
|
|
4413
|
+
if (existing && !existing.isSymbolicLink()) {
|
|
4414
|
+
return { status: "occupied", target };
|
|
4415
|
+
}
|
|
4416
|
+
if (existing) {
|
|
4417
|
+
if (path3.resolve(readlinkSync(target)) === pluginRoot) return { status: "current", target };
|
|
4418
|
+
mkdirSync(localPluginsDir, { recursive: true });
|
|
4419
|
+
const replacement = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
4420
|
+
const previous = `${target}.${process.pid}.${randomUUID()}.old`;
|
|
4421
|
+
let movedPrevious = false;
|
|
4422
|
+
try {
|
|
4423
|
+
createSymlink(pluginRoot, replacement);
|
|
4424
|
+
if (platform === "win32") {
|
|
4425
|
+
rename2(target, previous);
|
|
4426
|
+
movedPrevious = true;
|
|
4427
|
+
}
|
|
4428
|
+
rename2(replacement, target);
|
|
4429
|
+
if (movedPrevious) remove(previous, { force: true });
|
|
4430
|
+
} catch (error) {
|
|
4431
|
+
try {
|
|
4432
|
+
remove(replacement, { force: true });
|
|
4433
|
+
if (movedPrevious) rename2(previous, target);
|
|
4434
|
+
} catch {
|
|
4435
|
+
}
|
|
4436
|
+
return { status: "unsupported", target, reason: linkFailureReason(error) };
|
|
4437
|
+
}
|
|
4438
|
+
return { status: "repaired", target };
|
|
4439
|
+
}
|
|
4440
|
+
mkdirSync(localPluginsDir, { recursive: true });
|
|
4441
|
+
try {
|
|
4442
|
+
createSymlink(pluginRoot, target);
|
|
4443
|
+
} catch (error) {
|
|
4444
|
+
return { status: "unsupported", target, reason: linkFailureReason(error) };
|
|
4445
|
+
}
|
|
4446
|
+
return { status: "linked", target };
|
|
4447
|
+
}
|
|
4448
|
+
function linkFailureReason(error) {
|
|
4449
|
+
const message = String(error instanceof Error ? error.message : error).split("\n")[0];
|
|
4450
|
+
return message || "link creation failed";
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4288
4453
|
// src/server.js
|
|
4289
4454
|
import crypto4 from "node:crypto";
|
|
4290
4455
|
import { EventEmitter } from "node:events";
|
|
4291
|
-
import { existsSync } from "node:fs";
|
|
4456
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
4292
4457
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
4293
4458
|
import { homedir } from "node:os";
|
|
4294
|
-
import
|
|
4295
|
-
import { fileURLToPath as
|
|
4459
|
+
import path6 from "node:path";
|
|
4460
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
4296
4461
|
import chokidar from "chokidar";
|
|
4297
4462
|
import express from "express";
|
|
4298
4463
|
|
|
@@ -4792,15 +4957,15 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
4792
4957
|
return node.parentElement || document.body;
|
|
4793
4958
|
}
|
|
4794
4959
|
function nodePath(node, root) {
|
|
4795
|
-
const
|
|
4960
|
+
const path8 = [];
|
|
4796
4961
|
let current = node;
|
|
4797
4962
|
while (current && current !== root) {
|
|
4798
4963
|
const parentNode = current.parentNode;
|
|
4799
4964
|
if (!parentNode) break;
|
|
4800
|
-
|
|
4965
|
+
path8.unshift([...parentNode.childNodes].indexOf(current));
|
|
4801
4966
|
current = parentNode;
|
|
4802
4967
|
}
|
|
4803
|
-
return
|
|
4968
|
+
return path8;
|
|
4804
4969
|
}
|
|
4805
4970
|
function rangeBoundary(node, offset) {
|
|
4806
4971
|
const el = closestElement(node);
|
|
@@ -6358,7 +6523,7 @@ function mermaidSourceHash(source) {
|
|
|
6358
6523
|
|
|
6359
6524
|
// src/whiteboard-store.js
|
|
6360
6525
|
import { mkdir as mkdir2, readFile as readFile2, rename, rm, writeFile } from "node:fs/promises";
|
|
6361
|
-
import
|
|
6526
|
+
import path4 from "node:path";
|
|
6362
6527
|
|
|
6363
6528
|
// src/whiteboard-core.js
|
|
6364
6529
|
var EXCALIDRAW_SCENE_TARGET_TYPE = "excalidraw-scene";
|
|
@@ -6410,13 +6575,13 @@ function assertValidRef(key, index) {
|
|
|
6410
6575
|
if (!isValidDiagramIndex(index)) throw new Error(`invalid whiteboard diagram index: ${index}`);
|
|
6411
6576
|
}
|
|
6412
6577
|
function whiteboardDir(stateDir2, key) {
|
|
6413
|
-
return
|
|
6578
|
+
return path4.join(stateDir2, "whiteboards", String(key));
|
|
6414
6579
|
}
|
|
6415
6580
|
function workingFile(stateDir2, key, index) {
|
|
6416
|
-
return
|
|
6581
|
+
return path4.join(whiteboardDir(stateDir2, key), `${Number(index)}.json`);
|
|
6417
6582
|
}
|
|
6418
6583
|
function writeQueueKey(stateDir2, key, index) {
|
|
6419
|
-
return `${
|
|
6584
|
+
return `${path4.resolve(stateDir2)}\0${key}\0${Number(index)}`;
|
|
6420
6585
|
}
|
|
6421
6586
|
function queueWhiteboardWrite(stateDir2, key, index, operation) {
|
|
6422
6587
|
const queueKey = writeQueueKey(stateDir2, key, index);
|
|
@@ -6446,8 +6611,8 @@ function whiteboardFeedbackPaths(stateDir2, key, index) {
|
|
|
6446
6611
|
assertValidRef(key, index);
|
|
6447
6612
|
const dir = whiteboardDir(stateDir2, key);
|
|
6448
6613
|
return {
|
|
6449
|
-
scenePath:
|
|
6450
|
-
previewPath:
|
|
6614
|
+
scenePath: path4.join(dir, `${Number(index)}.excalidraw`),
|
|
6615
|
+
previewPath: path4.join(dir, `${Number(index)}.png`)
|
|
6451
6616
|
};
|
|
6452
6617
|
}
|
|
6453
6618
|
async function saveWhiteboard(stateDir2, key, index, { sourceHash, textMetricsVersion = 0, scene, baseline = null }) {
|
|
@@ -6536,7 +6701,7 @@ ${script}`;
|
|
|
6536
6701
|
// src/session-store.js
|
|
6537
6702
|
import crypto3 from "node:crypto";
|
|
6538
6703
|
import { readFile as readFile3, realpath as realpath2, writeFile as writeFile2 } from "node:fs/promises";
|
|
6539
|
-
import
|
|
6704
|
+
import path5 from "node:path";
|
|
6540
6705
|
var LAYOUT_WARNINGS_TARGET_TYPE = "layout-warnings";
|
|
6541
6706
|
var MAX_ARTIFACT_FAILURES = 20;
|
|
6542
6707
|
var SessionStore = class {
|
|
@@ -6995,7 +7160,7 @@ var SessionStore = class {
|
|
|
6995
7160
|
}
|
|
6996
7161
|
};
|
|
6997
7162
|
async function canonicalFile(file) {
|
|
6998
|
-
const absolute =
|
|
7163
|
+
const absolute = path5.resolve(file);
|
|
6999
7164
|
return realpath2(absolute);
|
|
7000
7165
|
}
|
|
7001
7166
|
function sessionKey(file) {
|
|
@@ -7101,9 +7266,9 @@ var WHITEBOARD_CHANNEL_TOKEN_TTL_MS = 5 * 6e4;
|
|
|
7101
7266
|
var RELOAD_DEBOUNCE_MS = 100;
|
|
7102
7267
|
var BATCH_RELOAD_DEBOUNCE_MS = 900;
|
|
7103
7268
|
function defaultWhiteboardAssetsDir() {
|
|
7104
|
-
const packaged =
|
|
7105
|
-
if (
|
|
7106
|
-
return
|
|
7269
|
+
const packaged = fileURLToPath3(new URL("./whiteboard", import.meta.url));
|
|
7270
|
+
if (existsSync2(packaged)) return packaged;
|
|
7271
|
+
return fileURLToPath3(new URL("../dist/whiteboard", import.meta.url));
|
|
7107
7272
|
}
|
|
7108
7273
|
function isWhiteboardWriteApiPath(pathname) {
|
|
7109
7274
|
return /^\/api\/[0-9a-f]{16}\/whiteboard\/\d{1,3}(\/feedback-files)?$/.test(String(pathname || ""));
|
|
@@ -7160,7 +7325,7 @@ async function serve({
|
|
|
7160
7325
|
`);
|
|
7161
7326
|
const logEvent = verbose ? (line) => writeLog(`[lavish] ${line}`) : null;
|
|
7162
7327
|
let publicPort = port;
|
|
7163
|
-
const whiteboardStateRoot =
|
|
7328
|
+
const whiteboardStateRoot = path6.dirname(stateFile2);
|
|
7164
7329
|
const allowedHostnames = buildAllowedHostnames({ host, linkHost: linkHostName, allowedHosts });
|
|
7165
7330
|
if (!allowsAllHosts(allowedHosts)) {
|
|
7166
7331
|
app.use((req, res, next) => {
|
|
@@ -7430,7 +7595,7 @@ async function serve({
|
|
|
7430
7595
|
return;
|
|
7431
7596
|
}
|
|
7432
7597
|
const source = await readFile4(session.file, "utf8");
|
|
7433
|
-
const root =
|
|
7598
|
+
const root = path6.dirname(session.file);
|
|
7434
7599
|
const { html, warnings } = await buildSelfContainedHtml(source, {
|
|
7435
7600
|
baseDir: root,
|
|
7436
7601
|
confineDir: root,
|
|
@@ -7458,7 +7623,7 @@ async function serve({
|
|
|
7458
7623
|
}
|
|
7459
7624
|
const body = req.body || {};
|
|
7460
7625
|
const source = await readFile4(session.file, "utf8");
|
|
7461
|
-
const root =
|
|
7626
|
+
const root = path6.dirname(session.file);
|
|
7462
7627
|
const { html, warnings } = await buildSelfContainedHtml(source, {
|
|
7463
7628
|
baseDir: root,
|
|
7464
7629
|
confineDir: root,
|
|
@@ -7603,7 +7768,7 @@ async function serve({
|
|
|
7603
7768
|
res.status(404).send("Session not found");
|
|
7604
7769
|
return;
|
|
7605
7770
|
}
|
|
7606
|
-
const root =
|
|
7771
|
+
const root = path6.dirname(session.file);
|
|
7607
7772
|
const file = resolveArtifactAsset(root, assetPath);
|
|
7608
7773
|
if (!file) {
|
|
7609
7774
|
res.status(403).send("Forbidden");
|
|
@@ -7736,8 +7901,8 @@ data: ${JSON.stringify({ state: computePresence(req.params.key, activePolls, del
|
|
|
7736
7901
|
res.status(403).send("Forbidden");
|
|
7737
7902
|
return;
|
|
7738
7903
|
}
|
|
7739
|
-
if (!
|
|
7740
|
-
res.status(404).send(
|
|
7904
|
+
if (!existsSync2(file)) {
|
|
7905
|
+
res.status(404).send(existsSync2(whiteboardAssetsDir) ? "Not found" : "Whiteboard bundle missing - run `pnpm run build`");
|
|
7741
7906
|
return;
|
|
7742
7907
|
}
|
|
7743
7908
|
res.setHeader("access-control-allow-origin", "*");
|
|
@@ -7945,10 +8110,10 @@ function resolveDesignAssetPath(refPath) {
|
|
|
7945
8110
|
if (!match) return null;
|
|
7946
8111
|
const asset = designAssetUrls[match[1]];
|
|
7947
8112
|
if (!asset) return null;
|
|
7948
|
-
const packaged =
|
|
7949
|
-
if (
|
|
7950
|
-
const source =
|
|
7951
|
-
return
|
|
8113
|
+
const packaged = fileURLToPath3(asset.packaged);
|
|
8114
|
+
if (existsSync2(packaged)) return packaged;
|
|
8115
|
+
const source = fileURLToPath3(asset.source);
|
|
8116
|
+
return existsSync2(source) ? source : null;
|
|
7952
8117
|
}
|
|
7953
8118
|
function exportContentDisposition(file) {
|
|
7954
8119
|
const filename = exportFileName(file);
|
|
@@ -8028,9 +8193,9 @@ function optionalBodyString(value) {
|
|
|
8028
8193
|
return trimmed || void 0;
|
|
8029
8194
|
}
|
|
8030
8195
|
function resolveArtifactAsset(root, assetPath) {
|
|
8031
|
-
const file =
|
|
8032
|
-
const relative =
|
|
8033
|
-
if (relative.startsWith("..") ||
|
|
8196
|
+
const file = path6.resolve(root, assetPath);
|
|
8197
|
+
const relative = path6.relative(root, file);
|
|
8198
|
+
if (relative.startsWith("..") || path6.isAbsolute(relative)) {
|
|
8034
8199
|
return null;
|
|
8035
8200
|
}
|
|
8036
8201
|
return file;
|
|
@@ -8066,7 +8231,7 @@ async function resolveWatchTarget(session) {
|
|
|
8066
8231
|
const html = await readFile4(session.file, "utf8");
|
|
8067
8232
|
if (hasLiveReloadRootOptIn(html)) {
|
|
8068
8233
|
return {
|
|
8069
|
-
path:
|
|
8234
|
+
path: path6.dirname(session.file),
|
|
8070
8235
|
scope: "directory",
|
|
8071
8236
|
options: {
|
|
8072
8237
|
...baseOptions,
|
|
@@ -8412,9 +8577,9 @@ var HttpTelemetryClient = class {
|
|
|
8412
8577
|
if (!trimmed) return;
|
|
8413
8578
|
this.send(trimmed, eventURL(this.app, trimmed), fields);
|
|
8414
8579
|
}
|
|
8415
|
-
pageview(
|
|
8580
|
+
pageview(path8, fields = {}) {
|
|
8416
8581
|
if (this.closed) return;
|
|
8417
|
-
this.send("", normalizePagePath(
|
|
8582
|
+
this.send("", normalizePagePath(path8), fields);
|
|
8418
8583
|
}
|
|
8419
8584
|
async close(timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
8420
8585
|
this.closed = true;
|
|
@@ -8489,8 +8654,8 @@ function eventURL(app, name) {
|
|
|
8489
8654
|
if (!name) return `app://${app}`;
|
|
8490
8655
|
return `app://${app}/${name.replace(/\./g, "/")}`;
|
|
8491
8656
|
}
|
|
8492
|
-
function normalizePagePath(
|
|
8493
|
-
const trimmed = String(
|
|
8657
|
+
function normalizePagePath(path8) {
|
|
8658
|
+
const trimmed = String(path8 || "").trim();
|
|
8494
8659
|
if (!trimmed) return "/";
|
|
8495
8660
|
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
8496
8661
|
}
|
|
@@ -8509,7 +8674,7 @@ var POLL_WAKE_PATH_RULES = Object.freeze([
|
|
|
8509
8674
|
]);
|
|
8510
8675
|
var POLL_SEND_AND_END_RULE = "`Send & End` ends the session. Its final feedback is still delivered once. After that response, polling stops, and the agent must not reopen the session uninvited.";
|
|
8511
8676
|
var CODEX_POLL_WAKE_PATH_GUIDANCE = "Codex detected: completed background tasks may not resume Codex automatically, so keep the poll attached to the active turn.";
|
|
8512
|
-
var VERSION = "0.1.
|
|
8677
|
+
var VERSION = "0.1.46";
|
|
8513
8678
|
function detectInvokingAgent(env = process.env) {
|
|
8514
8679
|
return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
|
|
8515
8680
|
}
|
|
@@ -8521,7 +8686,15 @@ function pollExecutionGuidance({ agent = "generic" } = {}) {
|
|
|
8521
8686
|
const agentGuidance = agent === "codex" ? ` ${CODEX_POLL_WAKE_PATH_GUIDANCE}` : "";
|
|
8522
8687
|
return `${sharedGuidance}${agentGuidance}`;
|
|
8523
8688
|
}
|
|
8689
|
+
function isVersionOnlyArgv(argv) {
|
|
8690
|
+
return argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v" || argv[0] === "-V");
|
|
8691
|
+
}
|
|
8524
8692
|
async function run(argv) {
|
|
8693
|
+
if (isVersionOnlyArgv(argv)) {
|
|
8694
|
+
process.stdout.write(`${VERSION}
|
|
8695
|
+
`);
|
|
8696
|
+
return;
|
|
8697
|
+
}
|
|
8525
8698
|
await ensureStateDir();
|
|
8526
8699
|
const normalizedArgv = normalizeArgv(argv);
|
|
8527
8700
|
const agent = detectInvokingAgent(process.env);
|
|
@@ -8595,7 +8768,7 @@ function telemetryCommandName(argv) {
|
|
|
8595
8768
|
}
|
|
8596
8769
|
function createHomeOutput({ bin, sessions, includeSessions = true, agent = "generic" }) {
|
|
8597
8770
|
return {
|
|
8598
|
-
bin: collapseHomeDirectory(bin,
|
|
8771
|
+
bin: collapseHomeDirectory(bin, os3.homedir()),
|
|
8599
8772
|
description: DESCRIPTION,
|
|
8600
8773
|
...includeSessions ? {
|
|
8601
8774
|
sessions: sessions.map((session) => ({
|
|
@@ -8838,8 +9011,8 @@ async function exportCommand(args) {
|
|
|
8838
9011
|
}
|
|
8839
9012
|
await assertHtmlFile(file);
|
|
8840
9013
|
const absolute = await canonicalFile(file);
|
|
8841
|
-
const root =
|
|
8842
|
-
const output =
|
|
9014
|
+
const root = path7.dirname(absolute);
|
|
9015
|
+
const output = path7.resolve(flagValue(args, "--out") || path7.join(root, exportFileName(absolute)));
|
|
8843
9016
|
const source = await readFile5(absolute, "utf8");
|
|
8844
9017
|
const { html, warnings } = await buildSelfContainedHtml(source, {
|
|
8845
9018
|
baseDir: root,
|
|
@@ -8885,7 +9058,7 @@ async function shareCommand(args) {
|
|
|
8885
9058
|
const absolute = await canonicalFile(file);
|
|
8886
9059
|
const password = optionalFlagString(flagValue(args, "--password"));
|
|
8887
9060
|
const token = optionalFlagString(flagValue(args, "--token"));
|
|
8888
|
-
const root =
|
|
9061
|
+
const root = path7.dirname(absolute);
|
|
8889
9062
|
const source = await readFile5(absolute, "utf8");
|
|
8890
9063
|
const { html, warnings } = await buildSelfContainedHtml(source, {
|
|
8891
9064
|
baseDir: root,
|
|
@@ -8964,9 +9137,13 @@ async function designCommand() {
|
|
|
8964
9137
|
return createDesignOutput();
|
|
8965
9138
|
}
|
|
8966
9139
|
async function setupCommand(args) {
|
|
8967
|
-
if (args.length !== 1 || args[0] !== "hooks") {
|
|
8968
|
-
throw new AxiError("Unknown setup action", "VALIDATION_ERROR", [
|
|
9140
|
+
if (args.length !== 1 || args[0] !== "hooks" && args[0] !== "plugin") {
|
|
9141
|
+
throw new AxiError("Unknown setup action", "VALIDATION_ERROR", [
|
|
9142
|
+
"Run `lavish-axi setup hooks`",
|
|
9143
|
+
"Run `lavish-axi setup plugin`"
|
|
9144
|
+
]);
|
|
8969
9145
|
}
|
|
9146
|
+
if (args[0] === "plugin") return setupPluginCommand();
|
|
8970
9147
|
const errors = [];
|
|
8971
9148
|
installSessionStartHooks({
|
|
8972
9149
|
marker: "lavish-axi",
|
|
@@ -8984,14 +9161,186 @@ async function setupCommand(args) {
|
|
|
8984
9161
|
}
|
|
8985
9162
|
return {
|
|
8986
9163
|
hooks: { status: "installed", integrations: "Claude Code, Codex, OpenCode, GitHub Copilot CLI" },
|
|
8987
|
-
help: [
|
|
9164
|
+
help: [
|
|
9165
|
+
"Restart your agent session to receive lavish-axi ambient context",
|
|
9166
|
+
"Run `lavish-axi setup plugin` to also register the Agent Plugin in VS Code, Cursor, and GitHub Copilot CLI"
|
|
9167
|
+
]
|
|
8988
9168
|
};
|
|
8989
9169
|
}
|
|
8990
|
-
function
|
|
9170
|
+
async function setupPluginCommand() {
|
|
9171
|
+
const pluginRoot = resolvePluginRoot();
|
|
9172
|
+
const manifest = readPluginManifest(pluginRoot);
|
|
9173
|
+
if (!manifest) {
|
|
9174
|
+
throw new AxiError("No plugin.json found in the lavish-axi package", "SERVER_ERROR", [
|
|
9175
|
+
`Expected a manifest at ${path7.join(pluginRoot, "plugin.json")}`,
|
|
9176
|
+
"Reinstall lavish-axi, or run `npm run build:plugin` when working from a source checkout"
|
|
9177
|
+
]);
|
|
9178
|
+
}
|
|
9179
|
+
const clients = [
|
|
9180
|
+
registerVsCodePlugin(pluginRoot, manifest.name),
|
|
9181
|
+
registerCursorPlugin(pluginRoot, manifest.name),
|
|
9182
|
+
registerCopilotPlugin(pluginRoot, manifest.name)
|
|
9183
|
+
];
|
|
9184
|
+
const help = ["Restart or reload each client so it discovers the plugin"];
|
|
9185
|
+
if (clients.some((client) => client.status === "absent")) {
|
|
9186
|
+
help.push("Absent clients are skipped; re-run `lavish-axi setup plugin` after installing one");
|
|
9187
|
+
}
|
|
9188
|
+
if (clients.some((client) => client.status === "manual")) {
|
|
9189
|
+
help.push(`Register the plugin root manually where noted: ${pluginRoot}`);
|
|
9190
|
+
}
|
|
9191
|
+
return { plugin: { name: manifest.name, root: collapseHome(pluginRoot) }, clients, help };
|
|
9192
|
+
}
|
|
9193
|
+
function collapseHome(target) {
|
|
9194
|
+
const home = resolveHookHomeDir();
|
|
9195
|
+
return home && target.startsWith(home) ? `~${target.slice(home.length)}` : target;
|
|
9196
|
+
}
|
|
9197
|
+
function registerVsCodePlugin(pluginRoot, pluginName) {
|
|
9198
|
+
const settingsFile = resolveVsCodeSettingsFile(process.env, resolveHookHomeDir());
|
|
9199
|
+
const settingsDir = path7.dirname(settingsFile);
|
|
9200
|
+
const hasSettingsFile = existsSync3(settingsFile);
|
|
9201
|
+
if (!hasSettingsFile && !existsSync3(settingsDir)) {
|
|
9202
|
+
return { client: "vscode", status: "absent", detail: "no VS Code user configuration found" };
|
|
9203
|
+
}
|
|
9204
|
+
let settings = {};
|
|
9205
|
+
if (hasSettingsFile) {
|
|
9206
|
+
try {
|
|
9207
|
+
settings = JSON.parse(readFileSync2(settingsFile, "utf8"));
|
|
9208
|
+
} catch {
|
|
9209
|
+
return {
|
|
9210
|
+
client: "vscode",
|
|
9211
|
+
status: "manual",
|
|
9212
|
+
detail: `add "chat.pluginLocations": {"${pluginRoot}": true} to ${collapseHome(settingsFile)}`
|
|
9213
|
+
};
|
|
9214
|
+
}
|
|
9215
|
+
}
|
|
9216
|
+
const [updated, changed] = computeVsCodePluginLocationsUpdate(settings, pluginRoot, pluginName);
|
|
9217
|
+
if (!changed) return { client: "vscode", status: "current", detail: collapseHome(settingsFile) };
|
|
9218
|
+
try {
|
|
9219
|
+
const writeTarget = hasSettingsFile ? realpathSync(settingsFile) : settingsFile;
|
|
9220
|
+
writeTextFileAtomically(writeTarget, `${JSON.stringify(updated, null, 2)}
|
|
9221
|
+
`);
|
|
9222
|
+
} catch (error) {
|
|
9223
|
+
return { client: "vscode", status: "failed", detail: String(error instanceof Error ? error.message : error) };
|
|
9224
|
+
}
|
|
9225
|
+
return { client: "vscode", status: "registered", detail: collapseHome(settingsFile) };
|
|
9226
|
+
}
|
|
9227
|
+
function registerCursorPlugin(pluginRoot, pluginName) {
|
|
9228
|
+
const cursorDir = path7.join(resolveHookHomeDir(), ".cursor");
|
|
9229
|
+
if (!existsSync3(cursorDir)) {
|
|
9230
|
+
return { client: "cursor", status: "absent", detail: "no ~/.cursor directory found" };
|
|
9231
|
+
}
|
|
9232
|
+
try {
|
|
9233
|
+
const { status, target, reason } = linkCursorLocalPlugin(
|
|
9234
|
+
resolveCursorLocalPluginsDir(resolveHookHomeDir()),
|
|
9235
|
+
pluginRoot,
|
|
9236
|
+
pluginName
|
|
9237
|
+
);
|
|
9238
|
+
if (status === "occupied") {
|
|
9239
|
+
return { client: "cursor", status: "manual", detail: `${collapseHome(target)} exists and is not a symlink` };
|
|
9240
|
+
}
|
|
9241
|
+
if (status === "unsupported") {
|
|
9242
|
+
return {
|
|
9243
|
+
client: "cursor",
|
|
9244
|
+
status: "manual",
|
|
9245
|
+
detail: `cannot link ${collapseHome(target)} (${reason}); link it to ${pluginRoot} manually, or enable Developer Mode on Windows`
|
|
9246
|
+
};
|
|
9247
|
+
}
|
|
9248
|
+
return {
|
|
9249
|
+
client: "cursor",
|
|
9250
|
+
status: status === "current" ? "current" : "registered",
|
|
9251
|
+
detail: collapseHome(target)
|
|
9252
|
+
};
|
|
9253
|
+
} catch (error) {
|
|
9254
|
+
return { client: "cursor", status: "failed", detail: String(error instanceof Error ? error.message : error) };
|
|
9255
|
+
}
|
|
9256
|
+
}
|
|
9257
|
+
function registerCopilotPlugin(pluginRoot, pluginName) {
|
|
9258
|
+
const listed = spawnPluginClientSync("copilot", ["plugins", "list", "--scope", "user", "--kind", "plugin", "--json"]);
|
|
9259
|
+
if (listed.error) {
|
|
9260
|
+
return { client: "copilot", status: "absent", detail: "copilot CLI not found on PATH" };
|
|
9261
|
+
}
|
|
9262
|
+
if (listed.status !== 0) {
|
|
9263
|
+
const detail = String(listed.stderr || listed.stdout || `exit ${listed.status}`).trim();
|
|
9264
|
+
return {
|
|
9265
|
+
client: "copilot",
|
|
9266
|
+
status: "manual",
|
|
9267
|
+
detail: `could not verify installed plugins: ${detail.split("\n")[0]}`
|
|
9268
|
+
};
|
|
9269
|
+
}
|
|
9270
|
+
const records = parseCopilotPluginRecords(listed.stdout);
|
|
9271
|
+
if (!records) {
|
|
9272
|
+
return { client: "copilot", status: "manual", detail: "could not parse installed plugin records" };
|
|
9273
|
+
}
|
|
9274
|
+
const existing = records.find((record) => record.name === pluginName && (!record.kind || record.kind === "plugin"));
|
|
9275
|
+
if (existing) {
|
|
9276
|
+
const source = copilotPluginSourcePath(existing) || installedCopilotPluginSourcePath(pluginName);
|
|
9277
|
+
if (!source) {
|
|
9278
|
+
return { client: "copilot", status: "manual", detail: "could not verify the installed plugin source" };
|
|
9279
|
+
}
|
|
9280
|
+
if (sameResolvedPath(source, pluginRoot)) {
|
|
9281
|
+
return { client: "copilot", status: "current", detail: collapseHome(pluginRoot) };
|
|
9282
|
+
}
|
|
9283
|
+
}
|
|
9284
|
+
const installed = spawnPluginClientSync("copilot", ["plugin", "install", pluginRoot]);
|
|
9285
|
+
if (installed.status !== 0) {
|
|
9286
|
+
const detail = String(installed.stderr || installed.stdout || `exit ${installed.status}`).trim();
|
|
9287
|
+
return { client: "copilot", status: "failed", detail: detail.split("\n")[0] };
|
|
9288
|
+
}
|
|
9289
|
+
return { client: "copilot", status: "registered", detail: "copilot plugin install" };
|
|
9290
|
+
}
|
|
9291
|
+
function parseCopilotPluginRecords(output) {
|
|
9292
|
+
try {
|
|
9293
|
+
const parsed = JSON.parse(String(output));
|
|
9294
|
+
const records = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.plugins) ? parsed.plugins : parsed?.items;
|
|
9295
|
+
return Array.isArray(records) && records.every((record) => record && typeof record === "object") ? records : null;
|
|
9296
|
+
} catch {
|
|
9297
|
+
return null;
|
|
9298
|
+
}
|
|
9299
|
+
}
|
|
9300
|
+
function installedCopilotPluginSourcePath(pluginName) {
|
|
9301
|
+
const configDir = process.env.COPILOT_HOME || path7.join(resolveHookHomeDir(), ".copilot");
|
|
9302
|
+
try {
|
|
9303
|
+
const config = JSON.parse(readFileSync2(path7.join(configDir, "config.json"), "utf8"));
|
|
9304
|
+
const record = Array.isArray(config.installedPlugins) ? config.installedPlugins.find((candidate) => candidate?.name === pluginName) : null;
|
|
9305
|
+
return record ? copilotPluginSourcePath(record) : null;
|
|
9306
|
+
} catch {
|
|
9307
|
+
return null;
|
|
9308
|
+
}
|
|
9309
|
+
}
|
|
9310
|
+
function copilotPluginSourcePath(record) {
|
|
9311
|
+
const candidates = [
|
|
9312
|
+
record.sourcePath,
|
|
9313
|
+
record.source_path,
|
|
9314
|
+
record.pluginRoot,
|
|
9315
|
+
record.plugin_root,
|
|
9316
|
+
record.path,
|
|
9317
|
+
record.source?.path
|
|
9318
|
+
];
|
|
9319
|
+
for (const candidate of candidates) {
|
|
9320
|
+
if (typeof candidate !== "string") continue;
|
|
9321
|
+
if (candidate.startsWith("file:")) {
|
|
9322
|
+
try {
|
|
9323
|
+
return fileURLToPath4(candidate);
|
|
9324
|
+
} catch {
|
|
9325
|
+
continue;
|
|
9326
|
+
}
|
|
9327
|
+
}
|
|
9328
|
+
if (path7.isAbsolute(candidate)) return candidate;
|
|
9329
|
+
}
|
|
9330
|
+
return null;
|
|
9331
|
+
}
|
|
9332
|
+
function sameResolvedPath(left, right) {
|
|
9333
|
+
try {
|
|
9334
|
+
return realpathSync(left) === realpathSync(right);
|
|
9335
|
+
} catch {
|
|
9336
|
+
return path7.resolve(left) === path7.resolve(right);
|
|
9337
|
+
}
|
|
9338
|
+
}
|
|
9339
|
+
function resolveHookHomeDir(env = process.env, fallback = os3.homedir()) {
|
|
8991
9340
|
return env.HOME || fallback;
|
|
8992
9341
|
}
|
|
8993
9342
|
function resolveCopilotHookDir(env = process.env, homeDir = resolveHookHomeDir(env)) {
|
|
8994
|
-
return
|
|
9343
|
+
return path7.join(env.COPILOT_HOME || path7.join(homeDir, ".copilot"), "hooks");
|
|
8995
9344
|
}
|
|
8996
9345
|
function createCopilotCliAmbientContextScript(command = "lavish-axi") {
|
|
8997
9346
|
return [
|
|
@@ -9040,16 +9389,16 @@ function installCopilotCliSessionStartHook({
|
|
|
9040
9389
|
timeoutSec = 10,
|
|
9041
9390
|
onError = void 0
|
|
9042
9391
|
} = {}) {
|
|
9043
|
-
const target =
|
|
9392
|
+
const target = path7.join(hookDir, "lavish-axi.json");
|
|
9044
9393
|
try {
|
|
9045
|
-
|
|
9046
|
-
const current =
|
|
9394
|
+
mkdirSync2(path7.dirname(target), { recursive: true });
|
|
9395
|
+
const current = existsSync3(target) ? JSON.parse(readFileSync2(target, "utf8")) : {};
|
|
9047
9396
|
const [updated, changed] = computeCopilotCliHookUpdate(
|
|
9048
9397
|
current,
|
|
9049
9398
|
createCopilotCliSessionStartHook(command, timeoutSec)
|
|
9050
9399
|
);
|
|
9051
9400
|
if (changed) {
|
|
9052
|
-
|
|
9401
|
+
writeFileSync2(target, `${JSON.stringify(updated, null, 2)}
|
|
9053
9402
|
`, "utf8");
|
|
9054
9403
|
}
|
|
9055
9404
|
} catch (error) {
|
|
@@ -9139,11 +9488,11 @@ function shouldRestartServer(currentVersion, healthBody, forceRestart = false) {
|
|
|
9139
9488
|
return healthBody.version !== currentVersion;
|
|
9140
9489
|
}
|
|
9141
9490
|
function shouldForceRestartForLocalBuild(executablePath, sourceServerExists = localSourceServerExists()) {
|
|
9142
|
-
const localBuildEntry =
|
|
9143
|
-
return sourceServerExists &&
|
|
9491
|
+
const localBuildEntry = fileURLToPath4(new URL("../dist/cli.mjs", import.meta.url));
|
|
9492
|
+
return sourceServerExists && path7.resolve(executablePath) === path7.resolve(localBuildEntry);
|
|
9144
9493
|
}
|
|
9145
9494
|
function localSourceServerExists() {
|
|
9146
|
-
return
|
|
9495
|
+
return existsSync3(fileURLToPath4(new URL("../src/server.js", import.meta.url)));
|
|
9147
9496
|
}
|
|
9148
9497
|
function shouldKillProcessOnPort(currentVersion, healthBody) {
|
|
9149
9498
|
if (!healthBody || typeof healthBody !== "object") return false;
|
|
@@ -9229,9 +9578,9 @@ async function startServer(port) {
|
|
|
9229
9578
|
}
|
|
9230
9579
|
}
|
|
9231
9580
|
function resolveServerEntry() {
|
|
9232
|
-
const binEntry =
|
|
9233
|
-
if (
|
|
9234
|
-
return
|
|
9581
|
+
const binEntry = fileURLToPath4(new URL("../bin/lavish-axi.js", import.meta.url));
|
|
9582
|
+
if (existsSync3(binEntry)) return binEntry;
|
|
9583
|
+
return fileURLToPath4(import.meta.url);
|
|
9235
9584
|
}
|
|
9236
9585
|
function createServerSpawnOptions(logFd = null) {
|
|
9237
9586
|
const stdio = (
|
|
@@ -9353,6 +9702,7 @@ Usage:
|
|
|
9353
9702
|
lavish-axi playbook [playbook_id]
|
|
9354
9703
|
lavish-axi design
|
|
9355
9704
|
lavish-axi setup hooks
|
|
9705
|
+
lavish-axi setup plugin
|
|
9356
9706
|
|
|
9357
9707
|
${DESIGN_SYSTEM_HINT}
|
|
9358
9708
|
|
|
@@ -9402,8 +9752,13 @@ Examples:
|
|
|
9402
9752
|
Show a copy-pasteable CDN snippet for Tailwind CSS browser runtime v4 + DaisyUI v5 + themes, Mermaid diagram tooling, a content-to-playbook router, an optional layout safety CSS snippet, plus technical reference for DaisyUI components. ${PLAYBOOK_ROUTER_HELP} Lavish artifacts stay portable HTML. This CDN snippet is the design fallback, not the default: inspect the subject project before falling back, and paste the layout safety CSS only when useful for dense nested grid/flex layouts, badges, wide fonts, or local media. ${DESIGN_PRIORITY_RULE}
|
|
9403
9753
|
`,
|
|
9404
9754
|
setup: `Usage: lavish-axi setup hooks
|
|
9755
|
+
lavish-axi setup plugin
|
|
9756
|
+
|
|
9757
|
+
hooks: install or repair agent SessionStart hooks for lavish-axi ambient context in Claude Code, Codex, OpenCode, and GitHub Copilot CLI. Restart your agent session afterward to receive the context. This is the primary integration - it carries live session state.
|
|
9758
|
+
|
|
9759
|
+
plugin: register the installed lavish-axi package as an Agent Plugin (agent-plugins.org) in VS Code, Cursor, and GitHub Copilot CLI. The installed package directory is itself the plugin root, so nothing is downloaded and no marketplace is involved. Reload each client afterward. Codex users should use \`setup hooks\` instead.
|
|
9405
9760
|
|
|
9406
|
-
|
|
9761
|
+
Both actions are explicit opt-in, idempotent, and repair a stale path after a reinstall.
|
|
9407
9762
|
`,
|
|
9408
9763
|
server: `Usage: lavish-axi server [--port 4387] [--verbose]
|
|
9409
9764
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lavish-axi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.46",
|
|
4
4
|
"packageManager": "pnpm@11.1.1",
|
|
5
5
|
"description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"plugin.json",
|
|
12
13
|
"skills/lavish",
|
|
13
14
|
"lavish-editor-marketing/renders/lavish-editor-marketing.gif",
|
|
14
15
|
"LICENSE",
|
|
@@ -18,7 +19,8 @@
|
|
|
18
19
|
"scripts": {
|
|
19
20
|
"build": "node scripts/build.js",
|
|
20
21
|
"build:skill": "node scripts/build-skill.js",
|
|
21
|
-
"
|
|
22
|
+
"build:plugin": "node scripts/build-plugin.js",
|
|
23
|
+
"check": "npm run build && npm run lint && npm run format:check && npm run typecheck && npm test && node scripts/build-skill.js --check && node scripts/build-plugin.js --check",
|
|
22
24
|
"start": "node dist/cli.mjs",
|
|
23
25
|
"lint": "eslint bin src test scripts",
|
|
24
26
|
"format": "prettier --write .",
|
|
@@ -32,6 +34,7 @@
|
|
|
32
34
|
"@tailwindcss/browser": "4.2.4",
|
|
33
35
|
"axi-sdk-js": "^0.1.8",
|
|
34
36
|
"chokidar": "^4.0.3",
|
|
37
|
+
"cross-spawn": "7.0.6",
|
|
35
38
|
"daisyui": "^5.5.19",
|
|
36
39
|
"express": "^5.2.1",
|
|
37
40
|
"open": "^10.2.0",
|
package/plugin.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "lavish-axi",
|
|
4
|
+
"version": "0.1.46",
|
|
5
|
+
"description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Kun Chen",
|
|
8
|
+
"url": "https://github.com/kunchenguid"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/kunchenguid/lavish-axi#readme",
|
|
11
|
+
"repository": "https://github.com/kunchenguid/lavish-axi",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"axi",
|
|
15
|
+
"agent",
|
|
16
|
+
"cli",
|
|
17
|
+
"html",
|
|
18
|
+
"review",
|
|
19
|
+
"collaboration"
|
|
20
|
+
]
|
|
21
|
+
}
|
package/skills/lavish/SKILL.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lavish
|
|
3
3
|
description: Turn complex or visual agent responses into rich, reviewable HTML artifacts the user can annotate and send feedback on, using the lavish-axi CLI. Use when about to give a plan, comparison, diagram, table, code diff, report, or anything easier to grasp visually than as prose.
|
|
4
|
-
|
|
5
|
-
author: Kun Chen (kunchenguid)
|
|
4
|
+
license: MIT
|
|
6
5
|
metadata:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
author: Kun Chen (kunchenguid)
|
|
7
|
+
argument-hint: <what the artifact should show>
|
|
8
|
+
hermes-tags: html, review, artifacts, visualization
|
|
9
|
+
hermes-category: productivity
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Lavish Editor
|