@aexol/spectral 0.9.125 → 0.9.127
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/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +5 -0
- package/dist/cli.js +6 -0
- package/dist/commands/update.d.ts +17 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +292 -0
- package/dist/extensions/seo/analyzers/content.d.ts +29 -6
- package/dist/extensions/seo/analyzers/content.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/content.js +243 -8
- package/dist/extensions/seo/analyzers/crawler.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/crawler.js +133 -7
- package/dist/extensions/seo/analyzers/drift.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/drift.js +86 -29
- package/dist/extensions/seo/analyzers/images.d.ts +10 -4
- package/dist/extensions/seo/analyzers/images.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/images.js +153 -5
- package/dist/extensions/seo/analyzers/performance.d.ts +3 -3
- package/dist/extensions/seo/analyzers/performance.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/performance.js +77 -6
- package/dist/extensions/seo/analyzers/readability.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/readability.js +17 -9
- package/dist/extensions/seo/analyzers/schema.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/schema.js +99 -10
- package/dist/extensions/seo/analyzers/sitemap.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/sitemap.js +57 -16
- package/dist/extensions/seo/analyzers/technical.d.ts +0 -7
- package/dist/extensions/seo/analyzers/technical.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/technical.js +408 -37
- package/dist/extensions/seo/config.d.ts +2 -0
- package/dist/extensions/seo/config.d.ts.map +1 -1
- package/dist/extensions/seo/config.js +2 -0
- package/dist/extensions/seo/tools/batch.js +1 -1
- package/dist/extensions/seo/tools/content.d.ts.map +1 -1
- package/dist/extensions/seo/tools/content.js +5 -3
- package/dist/extensions/seo/tools/full-audit.d.ts +1 -1
- package/dist/extensions/seo/tools/full-audit.d.ts.map +1 -1
- package/dist/extensions/seo/tools/full-audit.js +391 -66
- package/dist/extensions/seo/tools/helpers.d.ts +15 -4
- package/dist/extensions/seo/tools/helpers.d.ts.map +1 -1
- package/dist/extensions/seo/tools/helpers.js +57 -6
- package/dist/extensions/seo/tools/performance.d.ts.map +1 -1
- package/dist/extensions/seo/tools/performance.js +17 -11
- package/dist/extensions/seo/tools/report.d.ts.map +1 -1
- package/dist/extensions/seo/tools/report.js +513 -47
- package/dist/extensions/seo/types.d.ts +295 -3
- package/dist/extensions/seo/types.d.ts.map +1 -1
- package/dist/extensions/seo/utils/fetcher.d.ts +4 -0
- package/dist/extensions/seo/utils/fetcher.d.ts.map +1 -1
- package/dist/extensions/seo/utils/fetcher.js +77 -11
- package/dist/extensions/seo/utils/parser.d.ts +3 -1
- package/dist/extensions/seo/utils/parser.d.ts.map +1 -1
- package/dist/extensions/seo/utils/parser.js +105 -25
- package/dist/extensions/seo/utils/renderer.d.ts +6 -1
- package/dist/extensions/seo/utils/renderer.d.ts.map +1 -1
- package/dist/extensions/seo/utils/renderer.js +207 -1
- package/dist/extensions/seo/utils/url-safety.d.ts.map +1 -1
- package/dist/extensions/seo/utils/url-safety.js +19 -3
- package/dist/extensions/web/tools/fetch.js +2 -2
- package/dist/memory/tools/compact-context.d.ts.map +1 -1
- package/dist/memory/tools/compact-context.js +10 -3
- package/dist/sdk/agent-core/agent-loop.d.ts.map +1 -1
- package/dist/sdk/agent-core/agent-loop.js +87 -4
- package/dist/sdk/agent-core/types.d.ts +1 -0
- package/dist/sdk/agent-core/types.d.ts.map +1 -1
- package/dist/sdk/ai/utils/validation.d.ts +0 -7
- package/dist/sdk/ai/utils/validation.d.ts.map +1 -1
- package/dist/sdk/ai/utils/validation.js +21 -1
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +41 -0
- package/dist/server/storage.d.ts +9 -0
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +35 -9
- package/dist/server/wire.d.ts +2 -0
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAIX,SAAS,EAET,MAAM,4BAA4B,CAAC;AAUpC,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,KAAK,WAAW,EAAmC,MAAM,aAAa,CAAC;AAEhF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAOrD,OAAO,KAAK,EAAE,gBAAgB,IAAI,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AA4F7G,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CASpG;AAED,wBAAgB,yBAAyB,CACxC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAAC,GAC/C,MAAM,CAKR;AAiHD;;;GAGG;AACH,wBAAgB,oCAAoC,CACnD,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,GAChD,IAAI,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC,CAK3D;AAED,wBAAgB,oBAAoB,CACnC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,gBAAgB,GACnB,SAAS,EAAE,CAqCb;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAIX,SAAS,EAET,MAAM,4BAA4B,CAAC;AAUpC,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,KAAK,WAAW,EAAmC,MAAM,aAAa,CAAC;AAEhF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAOrD,OAAO,KAAK,EAAE,gBAAgB,IAAI,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AA4F7G,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CASpG;AAED,wBAAgB,yBAAyB,CACxC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAAC,GAC/C,MAAM,CAKR;AAiHD;;;GAGG;AACH,wBAAgB,oCAAoC,CACnD,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,GAChD,IAAI,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC,CAK3D;AAED,wBAAgB,oBAAoB,CACnC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,gBAAgB,GACnB,SAAS,EAAE,CAqCb;AA4aD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;CAAE,GACpF,IAAI,CAgZN"}
|
package/dist/agent/index.js
CHANGED
|
@@ -494,6 +494,11 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
494
494
|
if (event.type === "tool_execution_end" && event.isError) {
|
|
495
495
|
emitUpdate();
|
|
496
496
|
}
|
|
497
|
+
if (event.type === "agent_end" && event.error) {
|
|
498
|
+
hadError = true;
|
|
499
|
+
currentResult.errorMessage = event.error;
|
|
500
|
+
currentResult.stderr = event.error;
|
|
501
|
+
}
|
|
497
502
|
}
|
|
498
503
|
// Drain the stream result (already consumed by iteration, but ensure cleanup)
|
|
499
504
|
try {
|
package/dist/cli.js
CHANGED
|
@@ -43,6 +43,7 @@ function printHeader() {
|
|
|
43
43
|
" spectral serve Connect this machine to the Aexol relay backend",
|
|
44
44
|
" spectral bind Link this directory to an Aexol Studio project",
|
|
45
45
|
" spectral unbind Remove the Aexol Studio project binding",
|
|
46
|
+
" spectral update Check for and install the latest Spectral version",
|
|
46
47
|
" spectral --version Print version and exit",
|
|
47
48
|
" spectral --help Print this help and exit",
|
|
48
49
|
"",
|
|
@@ -115,6 +116,11 @@ async function main() {
|
|
|
115
116
|
await runUnbind();
|
|
116
117
|
process.exit(0);
|
|
117
118
|
}
|
|
119
|
+
if (first === "update") {
|
|
120
|
+
const { runUpdate } = await import("./commands/update.js");
|
|
121
|
+
await runUpdate(args.slice(1));
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
118
124
|
// No TUI/interactive mode is supported. All other invocations must use
|
|
119
125
|
// explicit subcommands.
|
|
120
126
|
process.stderr.write(`spectral: unknown command "${first ?? ""}".\n` +
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `spectral update` — standalone binary self-update.
|
|
3
|
+
*
|
|
4
|
+
* Fetches the latest release manifest from api.aexol.ai, compares versions,
|
|
5
|
+
* downloads the new binary, verifies its SHA256 checksum, and atomically
|
|
6
|
+
* replaces the running binary on disk.
|
|
7
|
+
*
|
|
8
|
+
* Non-standalone install modes (Electron desktop app, npm global) are detected
|
|
9
|
+
* and the user is pointed at the correct update path instead.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Run the `spectral update` subcommand.
|
|
13
|
+
*
|
|
14
|
+
* @param args remaining CLI args after the `update` subcommand
|
|
15
|
+
*/
|
|
16
|
+
export declare function runUpdate(args: string[]): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=update.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAsOH;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8H7D"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `spectral update` — standalone binary self-update.
|
|
3
|
+
*
|
|
4
|
+
* Fetches the latest release manifest from api.aexol.ai, compares versions,
|
|
5
|
+
* downloads the new binary, verifies its SHA256 checksum, and atomically
|
|
6
|
+
* replaces the running binary on disk.
|
|
7
|
+
*
|
|
8
|
+
* Non-standalone install modes (Electron desktop app, npm global) are detected
|
|
9
|
+
* and the user is pointed at the correct update path instead.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { chmod, mkdtemp, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import pc from "picocolors";
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
/** Base URL for the Aexol release manifest / binary downloads. */
|
|
20
|
+
const RELEASE_BASE = "https://api.aexol.ai/api/releases/download";
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the current CLI version. Reads package.json adjacent to this module
|
|
23
|
+
* when available (dev / npm install), falling back to the npm-injected env var
|
|
24
|
+
* and finally "0.0.0" — mirroring the logic in cli.ts.
|
|
25
|
+
*/
|
|
26
|
+
function currentVersion() {
|
|
27
|
+
try {
|
|
28
|
+
const pkgPath = resolve(__dirname, "..", "package.json");
|
|
29
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
30
|
+
return pkg.version;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return process.env.npm_package_version ?? "0.0.0";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compare two semver-ish version strings (e.g. "1.2.3", "v1.2.3").
|
|
38
|
+
* Returns a negative number if `a` < `b`, zero if equal, positive if `a` > `b`.
|
|
39
|
+
* Only numeric major.minor.patch parts are compared; pre-release tags ignored.
|
|
40
|
+
*/
|
|
41
|
+
function compareSemver(a, b) {
|
|
42
|
+
const pa = a.replace(/^v/i, "").split(".").map((n) => Number(n) || 0);
|
|
43
|
+
const pb = b.replace(/^v/i, "").split(".").map((n) => Number(n) || 0);
|
|
44
|
+
const len = Math.max(pa.length, pb.length);
|
|
45
|
+
for (let i = 0; i < len; i++) {
|
|
46
|
+
const da = pa[i] ?? 0;
|
|
47
|
+
const db = pb[i] ?? 0;
|
|
48
|
+
if (da !== db)
|
|
49
|
+
return da - db;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Detect the release platform slug from the current process.
|
|
55
|
+
* - darwin → `darwin-${arch}` (arm64 | x64)
|
|
56
|
+
* - linux → `linux-x64`
|
|
57
|
+
* - win32 → `win-x64`
|
|
58
|
+
*/
|
|
59
|
+
function detectPlatform() {
|
|
60
|
+
const { platform, arch } = process;
|
|
61
|
+
if (platform === "darwin") {
|
|
62
|
+
const a = arch === "arm64" || arch === "x64" ? arch : "x64";
|
|
63
|
+
return `darwin-${a}`;
|
|
64
|
+
}
|
|
65
|
+
if (platform === "linux")
|
|
66
|
+
return "linux-x64";
|
|
67
|
+
if (platform === "win32")
|
|
68
|
+
return "win-x64";
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether the process is running as a Bun `--compile` standalone binary.
|
|
73
|
+
*
|
|
74
|
+
* In a compiled binary, `process.execPath` points at the binary itself (e.g.
|
|
75
|
+
* `spectral`) rather than a `node`/`bun` runtime, and Bun's embedded-files
|
|
76
|
+
* surface may be present.
|
|
77
|
+
*/
|
|
78
|
+
function isStandaloneBinary() {
|
|
79
|
+
// Bun-compiled binary with embedded files is a strong positive signal.
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
|
+
const Bun = globalThis.Bun;
|
|
82
|
+
if (Bun && typeof Bun === "object" && "embeddedFiles" in Bun) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
const execName = basename(process.execPath).replace(/\.exe$/i, "");
|
|
86
|
+
const isRuntime = execName === "node" || execName === "bun";
|
|
87
|
+
return !isRuntime;
|
|
88
|
+
}
|
|
89
|
+
/** True when invoked inside the Electron desktop app. */
|
|
90
|
+
function isElectronBundled() {
|
|
91
|
+
return Boolean(process.env.SPECTRAL_DESKTOP_VERSION);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Compute the SHA256 hex digest of a file's contents.
|
|
95
|
+
*/
|
|
96
|
+
async function sha256OfFile(filePath) {
|
|
97
|
+
const { createReadStream } = await import("node:fs");
|
|
98
|
+
return new Promise((resolveHash, reject) => {
|
|
99
|
+
const hash = createHash("sha256");
|
|
100
|
+
const stream = createReadStream(filePath);
|
|
101
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
102
|
+
stream.on("end", () => resolveHash(hash.digest("hex")));
|
|
103
|
+
stream.on("error", reject);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Download `url` to `destPath`, streaming the body to disk.
|
|
108
|
+
*/
|
|
109
|
+
async function downloadToFile(url, destPath) {
|
|
110
|
+
const res = await fetch(url);
|
|
111
|
+
if (!res.ok || !res.body) {
|
|
112
|
+
throw new Error(`Download failed: HTTP ${res.status} ${res.statusText} for ${url}`);
|
|
113
|
+
}
|
|
114
|
+
const reader = res.body.getReader();
|
|
115
|
+
const chunks = [];
|
|
116
|
+
// eslint-disable-next-line no-constant-condition
|
|
117
|
+
while (true) {
|
|
118
|
+
const { done, value } = await reader.read();
|
|
119
|
+
if (done)
|
|
120
|
+
break;
|
|
121
|
+
if (value)
|
|
122
|
+
chunks.push(value);
|
|
123
|
+
}
|
|
124
|
+
const total = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
125
|
+
const buf = new Uint8Array(total);
|
|
126
|
+
let offset = 0;
|
|
127
|
+
for (const c of chunks) {
|
|
128
|
+
buf.set(c, offset);
|
|
129
|
+
offset += c.length;
|
|
130
|
+
}
|
|
131
|
+
await writeFile(destPath, buf);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Fetch and parse the latest release manifest for `platform`.
|
|
135
|
+
*/
|
|
136
|
+
async function fetchManifest(platform) {
|
|
137
|
+
const url = `${RELEASE_BASE}/latest/latest-${platform}.json`;
|
|
138
|
+
const res = await fetch(url);
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
throw new Error(`Manifest fetch failed: HTTP ${res.status} ${res.statusText}`);
|
|
141
|
+
}
|
|
142
|
+
const data = (await res.json());
|
|
143
|
+
return data;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Atomically replace the running binary at `currentPath` with `newBinaryPath`.
|
|
147
|
+
*
|
|
148
|
+
* On Unix: moves the old binary aside to `<name>.old`, moves the new binary into
|
|
149
|
+
* place, and makes it executable. On Windows the running executable cannot be
|
|
150
|
+
* renamed while live, so we print an actionable message instead.
|
|
151
|
+
*/
|
|
152
|
+
async function atomicReplace(currentPath, newBinaryPath) {
|
|
153
|
+
const isWindows = process.platform === "win32";
|
|
154
|
+
const dir = dirname(currentPath);
|
|
155
|
+
if (isWindows) {
|
|
156
|
+
// Cannot atomically replace a running .exe on Windows from within itself.
|
|
157
|
+
// Leave the downloaded binary next to the current one and instruct the
|
|
158
|
+
// user to restart. A launcher / wrapper script would handle the swap.
|
|
159
|
+
const staged = join(dir, basename(currentPath) + ".new");
|
|
160
|
+
await rename(newBinaryPath, staged);
|
|
161
|
+
process.stdout.write(pc.yellow(`New binary staged at ${staged}\n` +
|
|
162
|
+
`Close all spectral processes, then replace the .exe manually,\n` +
|
|
163
|
+
`or run the provided update helper.\n`));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const oldPath = currentPath + ".old";
|
|
167
|
+
// Make the new binary executable before it takes over.
|
|
168
|
+
await chmod(newBinaryPath, 0o755);
|
|
169
|
+
// Remove any stale .old binary from a previous update.
|
|
170
|
+
try {
|
|
171
|
+
await rm(oldPath, { force: true });
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
/* ignore — best effort cleanup */
|
|
175
|
+
}
|
|
176
|
+
// Move the current binary aside, then move the new one into place.
|
|
177
|
+
try {
|
|
178
|
+
await rename(currentPath, oldPath);
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
// If the current path can't be moved (e.g. permissions), abort cleanly.
|
|
182
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
183
|
+
throw new Error(`Could not move current binary aside: ${msg}`);
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
await rename(newBinaryPath, currentPath);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
// Attempt to restore the old binary on failure.
|
|
190
|
+
try {
|
|
191
|
+
await rename(oldPath, currentPath);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
/* leave as-is */
|
|
195
|
+
}
|
|
196
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
197
|
+
throw new Error(`Could not install new binary: ${msg}`);
|
|
198
|
+
}
|
|
199
|
+
// Best-effort cleanup of the old binary.
|
|
200
|
+
try {
|
|
201
|
+
await rm(oldPath, { force: true });
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
/* non-fatal */
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Run the `spectral update` subcommand.
|
|
209
|
+
*
|
|
210
|
+
* @param args remaining CLI args after the `update` subcommand
|
|
211
|
+
*/
|
|
212
|
+
export async function runUpdate(args) {
|
|
213
|
+
const checkOnly = args.includes("--check") || args.includes("-c");
|
|
214
|
+
// --- Installation mode detection --------------------------------------
|
|
215
|
+
if (isElectronBundled()) {
|
|
216
|
+
process.stdout.write(pc.cyan("You're running Spectral inside the desktop app. Use the app menu → " +
|
|
217
|
+
"Check for Updates to update.\n"));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!isStandaloneBinary()) {
|
|
221
|
+
process.stdout.write(pc.cyan("Run `npm i -g @aexol/spectral@latest` to update.\n"));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// --- Standalone self-update flow --------------------------------------
|
|
225
|
+
const platform = detectPlatform();
|
|
226
|
+
if (!platform) {
|
|
227
|
+
process.stderr.write(pc.red(`Update is not supported on this platform (${process.platform}/${process.arch}).\n`));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const cur = currentVersion();
|
|
231
|
+
let manifest;
|
|
232
|
+
try {
|
|
233
|
+
manifest = await fetchManifest(platform);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
process.stdout.write(pc.yellow("Could not check for updates. Make sure you're connected to the internet.\n"));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const latest = manifest.version.replace(/^v/i, "");
|
|
240
|
+
const comparison = compareSemver(cur, latest);
|
|
241
|
+
if (comparison >= 0) {
|
|
242
|
+
process.stdout.write(pc.green(`Spectral is up to date (v${cur}).\n`));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (checkOnly) {
|
|
246
|
+
process.stdout.write(pc.cyan(`Update available: v${cur} → v${latest}. Run \`spectral update\` to install.\n`));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// --- Download + verify + replace -------------------------------------
|
|
250
|
+
if (!manifest.url) {
|
|
251
|
+
process.stderr.write(pc.red("Update manifest is missing a download URL. Aborting.\n"));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
let tempDir = null;
|
|
255
|
+
try {
|
|
256
|
+
tempDir = await mkdtemp(join(tmpdir(), "spectral-update-"));
|
|
257
|
+
const downloadedPath = join(tempDir, "spectral-new");
|
|
258
|
+
process.stdout.write(pc.dim(`Downloading v${latest}…\n`));
|
|
259
|
+
await downloadToFile(manifest.url, downloadedPath);
|
|
260
|
+
// Verify the download is complete and readable.
|
|
261
|
+
await stat(downloadedPath);
|
|
262
|
+
// SHA256 verification.
|
|
263
|
+
if (manifest.sha256) {
|
|
264
|
+
process.stdout.write(pc.dim("Verifying checksum…\n"));
|
|
265
|
+
const actual = await sha256OfFile(downloadedPath);
|
|
266
|
+
const expected = manifest.sha256.toLowerCase();
|
|
267
|
+
if (actual.toLowerCase() !== expected) {
|
|
268
|
+
process.stderr.write(pc.red(`Checksum mismatch. Expected ${expected}, got ${actual}.\n` +
|
|
269
|
+
"The downloaded file may be corrupted or tampered with. Aborting.\n"));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const currentPath = process.execPath;
|
|
274
|
+
process.stdout.write(pc.dim("Installing…\n"));
|
|
275
|
+
await atomicReplace(currentPath, downloadedPath);
|
|
276
|
+
process.stdout.write(pc.green(`Spectral updated to v${latest}. Restart spectral to use the new version.\n`));
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
280
|
+
process.stderr.write(pc.red(`Update failed: ${msg}\n`));
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
if (tempDir) {
|
|
284
|
+
try {
|
|
285
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
/* best-effort cleanup */
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -5,12 +5,35 @@
|
|
|
5
5
|
* analysis: filler phrase detection, AI-pattern detection, information density,
|
|
6
6
|
* and repetition scoring. No external API calls.
|
|
7
7
|
*/
|
|
8
|
-
import type { ContentQualityResult } from "../types.js";
|
|
8
|
+
import type { ContentQualityResult, HeadingInfo, PageData, SchemaBlock } from "../types.js";
|
|
9
|
+
/** Split text into sentences, respecting common abbreviations. */
|
|
10
|
+
export declare function splitSentences(text: string): string[];
|
|
11
|
+
/** Calculate Flesch-Kincaid Grade Level and related readability metrics. */
|
|
12
|
+
export declare function calculateReadability(text: string): {
|
|
13
|
+
fleschKincaid: number;
|
|
14
|
+
sentenceCount: number;
|
|
15
|
+
avgSentenceLength: number;
|
|
16
|
+
avgWordLength: number;
|
|
17
|
+
};
|
|
18
|
+
/** Extract named entities from text using proper noun heuristics. */
|
|
19
|
+
export declare function extractEntities(text: string, schemaBlocks?: SchemaBlock[]): string[];
|
|
20
|
+
/** Check whether heading keywords appear in body text. */
|
|
21
|
+
export declare function headingContentAlignment(headings: HeadingInfo[], bodyText: string, stopWords?: Set<string>): {
|
|
22
|
+
score: number;
|
|
23
|
+
missingKeywords: string[];
|
|
24
|
+
};
|
|
25
|
+
/** Evaluate heading structure depth and completeness. */
|
|
26
|
+
export declare function calculateSemanticDepth(headings: HeadingInfo[]): {
|
|
27
|
+
score: number;
|
|
28
|
+
hasH1: boolean;
|
|
29
|
+
hasH2: boolean;
|
|
30
|
+
hasH3: boolean;
|
|
31
|
+
headingCount: number;
|
|
32
|
+
};
|
|
9
33
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* All scores are 0-100 where higher is better (except filler and AI pattern
|
|
13
|
-
* scores, where higher means MORE problematic content).
|
|
34
|
+
* Detect language using word-frequency profiles of common function words.
|
|
35
|
+
* Returns the ISO 639-1 language code of the best match, or null if confidence is too low.
|
|
14
36
|
*/
|
|
15
|
-
export declare function
|
|
37
|
+
export declare function detectLanguage(text: string): string | null;
|
|
38
|
+
export declare function analyzeContentQuality(text: string, url?: string, pageData?: PageData, customStopWords?: string[]): ContentQualityResult;
|
|
16
39
|
//# sourceMappingURL=content.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/analyzers/content.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/analyzers/content.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,QAAQ,EAAqB,WAAW,EAAE,MAAM,aAAa,CAAC;AAqJ/G,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAcrD;AAaD,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG;IAClD,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;CACvB,CA2BA;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CA0BpF;AAED,0DAA0D;AAC1D,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,WAAW,EAAE,EACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAA;CAAE,CAsB9C;AAED,yDAAyD;AACzD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB,CAiBA;AAmBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgC1D;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,QAAQ,EACnB,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,oBAAoB,CAoFtB"}
|