@litfamily/litclaude 1.0.13 → 1.0.14
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/CHANGELOG.md +9 -0
- package/README.md +82 -986
- package/README_ko-KR.md +79 -978
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/hooks.md +10 -7
- package/docs/privacy.md +2 -2
- package/package.json +4 -1
- package/plugins/litclaude/.claude-plugin/plugin.json +1 -1
- package/plugins/litclaude/bin/litclaude-hud.js +22 -10
- package/plugins/litclaude/bin/litclaude-mcp.js +1 -1
- package/plugins/litclaude/lib/canonical-skill-resources.mjs +3 -3
- package/plugins/litclaude/lib/hud-accent-palette.mjs +16 -4
- package/plugins/litclaude/lib/jev-skill-hint.mjs +25 -7
- package/plugins/litclaude/lib/office-runtime.mjs +14 -0
- package/plugins/litclaude/lib/secret-shapes.mjs +5 -1
- package/plugins/litclaude/skills/lit-typographic-motion/engine/node/runtime.mjs +17 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.14"><title>release: 1.0.14</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.14</text></g></svg>
|
package/docs/hooks.md
CHANGED
|
@@ -244,19 +244,22 @@ would be without the feature. The first such failure in a session shows one `sys
|
|
|
244
244
|
Per-session counters live in `.litclaude/jev/session-<id>.json` under the project state
|
|
245
245
|
root, with the current turn's hinted skill ID and latency in milliseconds; the prompt hook
|
|
246
246
|
clears that pair at the start of every turn while the flag is on. The optional HUD status line
|
|
247
|
-
(`plugins/litclaude/bin/litclaude-hud.js`) reads it and
|
|
248
|
-
on,
|
|
249
|
-
without a key; it shows nothing while the flag is off
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
247
|
+
(`plugins/litclaude/bin/litclaude-hud.js`) reads it and puts a badge right after the model
|
|
248
|
+
label: `✦Jev` while the hint is on, `✦Jev → <skill>` on a hinted turn, or `✦Jev ⚠ key` when
|
|
249
|
+
the flag is on without a key; it shows nothing while the flag is off, never shows the latency,
|
|
250
|
+
and never shows the key or its length. While on, the HUD paints `✦` gold, the hinted skill green
|
|
251
|
+
and the missing-key badge amber, and paints `Jev` as a rainbow whose starting hue follows the
|
|
252
|
+
clock, so each refresh shifts it slightly (truecolor, 256- and 16-colour tiers; plain under
|
|
253
|
+
`NO_COLOR` or the HUD's no-colour switch; a light or unknown appearance keeps the words on the
|
|
254
|
+
default foreground and colours only the marks). The session file also records that the
|
|
253
255
|
once-per-session `✦ Jev skill hint ON ✦` `systemMessage` was shown; the prompt hook writes that
|
|
254
256
|
mark before it emits the line on the first turn with the flag and key set, puts the line ahead
|
|
255
257
|
of any other visible message that turn, and shows no line when the mark cannot be written. The opt-in trace, `.litclaude/jev/trace.jsonl`, records the timestamp, the SHA-256 of the
|
|
256
258
|
redacted `state` (never of the raw prompt), chosen ID, confidence, latency, HTTP status, and fallback reason; it holds no prompt text,
|
|
257
259
|
key, or response body. Session and trace files are opened with `O_NOFOLLOW` (an `lstat` check
|
|
258
260
|
where the platform lacks it), so a symlink planted at either path is refused rather than written
|
|
259
|
-
through.
|
|
261
|
+
through. The `.litclaude` and `.litclaude/jev` folders must be real folders too: when either one
|
|
262
|
+
is a symlink, the hint reads and writes nothing there, sends no request, and shows no hint or note. Because `TYPESAFE_API_KEY` is exported in the shell that starts Claude Code, the
|
|
260
263
|
agent's own tools can read it too; use a key dedicated to this feature, with a low spend limit.
|
|
261
264
|
`litclaude doctor` prints `Jev skill hint: off`, `Jev skill hint: on`,
|
|
262
265
|
or `Jev skill hint: flag on but TYPESAFE_API_KEY missing`. The offline regression tests,
|
package/docs/privacy.md
CHANGED
|
@@ -43,8 +43,8 @@ ledgers, saved knowledge, and other feature-specific records. SessionStart
|
|
|
43
43
|
context-pressure detection can inspect a supplied transcript path up to 256 KiB
|
|
44
44
|
for a fixed set of pressure markers; it returns only a bounded advisory. No skill
|
|
45
45
|
review reads transcript excerpts or sends them to a model. The optional Jev skill hint keeps
|
|
46
|
-
per-session request counts, the current turn's hinted skill ID
|
|
47
|
-
|
|
46
|
+
per-session request counts, the current turn's hinted skill ID (shown by the HUD) and
|
|
47
|
+
latency, and whether the once-per-session "on" line was shown in `.litclaude/jev/` and, only with `LITCLAUDE_JEV_TRACE=1`, a trace
|
|
48
48
|
of hashes of the redacted prompt, timings, and status codes without prompt text. Existing
|
|
49
49
|
`pending-review.json` and `skill-loop-state.json` files are inert after the
|
|
50
50
|
automatic review feature's removal and may be deleted. No other state is affected.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@litfamily/litclaude",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "Claude Code-native workflow distribution.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -53,6 +53,9 @@
|
|
|
53
53
|
],
|
|
54
54
|
"scripts": {
|
|
55
55
|
"prepublishOnly": "node scripts/prepublish-test-gate.mjs",
|
|
56
|
+
"prepack": "node tools/readme-for-npm.mjs apply",
|
|
57
|
+
"postpack": "node tools/readme-for-npm.mjs restore",
|
|
58
|
+
"check:npm-readme": "node tools/readme-for-npm.mjs check",
|
|
56
59
|
"postinstall": "node scripts/postinstall.mjs",
|
|
57
60
|
"test": "node --test --test-concurrency=1 --import ./test/helpers/motion-offline-prewarm.mjs test/*.test.mjs",
|
|
58
61
|
"validate:plugin": "node scripts/validate-plugin.mjs",
|
|
@@ -11,10 +11,11 @@ import {
|
|
|
11
11
|
litBrandPrefix,
|
|
12
12
|
normalizeHudAppearance,
|
|
13
13
|
rainbowText,
|
|
14
|
+
toneText,
|
|
14
15
|
} from "../lib/hud-accent-palette.mjs";
|
|
15
16
|
import { latestUsageTokens } from "../lib/cache-measurement.mjs";
|
|
16
17
|
import { readIgnitionState, renderIgnitionSegment } from "../lib/hud-ignition.mjs";
|
|
17
|
-
import {
|
|
18
|
+
import { jevHudState } from "../lib/jev-skill-hint.mjs";
|
|
18
19
|
import { resolveProjectStateRoot } from "../lib/project-state-root.mjs";
|
|
19
20
|
|
|
20
21
|
const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -246,8 +247,14 @@ const formatUsageSegment = (label, value, reset) => {
|
|
|
246
247
|
return `${labelText} ${accent("[")}${makeBlockBar(pct, 2)}${accent("]")} ${pctText}${resetText ? ` ${text(resetText)}` : ""}`;
|
|
247
248
|
};
|
|
248
249
|
|
|
249
|
-
// Optional Jev skill hint
|
|
250
|
-
|
|
250
|
+
// Optional Jev skill hint badge, attached to the model label: nothing while LITCLAUDE_JEV is
|
|
251
|
+
// off, and never the key or its length.
|
|
252
|
+
const JEV_GOLD = [255, 215, 90];
|
|
253
|
+
const JEV_GREEN = [110, 235, 120];
|
|
254
|
+
const JEV_AMBER = [255, 175, 60];
|
|
255
|
+
const tone = (value, rgb, ansi16) => toneText(value, rgb, { depth: colorDepth, ansi16 });
|
|
256
|
+
|
|
257
|
+
const jevBadge = (status) => {
|
|
251
258
|
let state;
|
|
252
259
|
try {
|
|
253
260
|
const cwd = typeof status.cwd === "string" && status.cwd ? status.cwd : null;
|
|
@@ -260,14 +267,19 @@ const jevSegment = (status) => {
|
|
|
260
267
|
return "";
|
|
261
268
|
}
|
|
262
269
|
if (!state) return "";
|
|
263
|
-
|
|
270
|
+
// Light and unknown appearances keep the words on the default foreground; only the marks
|
|
271
|
+
// keep their colour, as the rest of the HUD does.
|
|
272
|
+
if (state.keyMissing) {
|
|
273
|
+
return coloredText ? tone("✦Jev ⚠ key", JEV_AMBER, 33) : `${tone("✦", JEV_AMBER, 33)}Jev ${tone("⚠", JEV_AMBER, 33)} key`;
|
|
274
|
+
}
|
|
264
275
|
// While on, the word shimmers: each refresh starts the rainbow at a hue taken from the clock.
|
|
265
|
-
// Light and unknown appearances keep it on the default foreground like the rest of the text.
|
|
266
276
|
const now = Number(process.env.LITCLAUDE_HUD_TEST_NOW);
|
|
267
277
|
const phase = Math.floor((Number.isFinite(now) && now > 0 ? now : Date.now()) / 100) % 360;
|
|
268
|
-
const word = coloredText ? rainbowText("Jev", { depth: colorDepth, phase }) :
|
|
269
|
-
const
|
|
270
|
-
|
|
278
|
+
const word = coloredText ? rainbowText("Jev", { depth: colorDepth, phase }) : "Jev";
|
|
279
|
+
const badge = `${tone("✦", JEV_GOLD, 93)}${word}`;
|
|
280
|
+
if (!state.last) return badge;
|
|
281
|
+
const skill = coloredText ? tone(state.last.skill, JEV_GREEN, 92) : state.last.skill;
|
|
282
|
+
return `${badge} ${colors.dim}→${colors.reset} ${skill}`;
|
|
271
283
|
};
|
|
272
284
|
|
|
273
285
|
const main = async () => {
|
|
@@ -294,8 +306,8 @@ const main = async () => {
|
|
|
294
306
|
const ignition = renderIgnitionSegment(readIgnitionState(status.session_id), {
|
|
295
307
|
depth: plainOutput ? "plain" : colorDepth,
|
|
296
308
|
});
|
|
297
|
-
const jev =
|
|
298
|
-
const line = `${prefix}${ignition ? ` ${ignition}` : ""} ${accent("|")} ${text(model)} ${accent(sep)} ${contextText}${usageText}${git ? ` ${accent(sep)} ${text("git")} ${text(git)}` : ""}
|
|
309
|
+
const jev = jevBadge(status);
|
|
310
|
+
const line = `${prefix}${ignition ? ` ${ignition}` : ""} ${accent("|")} ${text(model)}${jev ? ` ${jev}` : ""} ${accent(sep)} ${contextText}${usageText}${git ? ` ${accent(sep)} ${text("git")} ${text(git)}` : ""}`;
|
|
299
311
|
process.stdout.write(`${line}\n`);
|
|
300
312
|
|
|
301
313
|
const lastMessage = latestUserMessage(status.transcript_path);
|
|
@@ -14,7 +14,7 @@ export const canonicalSkillResourceManifest = Object.freeze(
|
|
|
14
14
|
["lib/office-runtime-lock/package-lock.json", "4b9552dba69bf2803cecdf71996d26de0497004c00d1e1f5753a5b299f85d8ba"],
|
|
15
15
|
["lib/office-runtime-lock/package.json", "edf7fee2ba78d6ad42bf5761678b2a2210adb7282108864360f83c46adb59708"],
|
|
16
16
|
["lib/office-runtime-lock/requirements.lock", "7c63ce0de1ae9a912d1e546e9c01dd99a705d4a50435f36040d351cc3e6e6014"],
|
|
17
|
-
["lib/office-runtime.mjs", "
|
|
17
|
+
["lib/office-runtime.mjs", "268e8ca9bb6a08dcb43ea2b40c899e776db850adfcb284a89d315d3c1a535438"],
|
|
18
18
|
["lib/office_data.py", "0f4c2449622b6d59630e60508594d181279052c41918df4370e11a68df1ee239"],
|
|
19
19
|
["lib/office_runtime_bootstrap.py", "28812ef485f73f413c6d08cfff326290b301a0d7358260a2a0496edd8f71f4e9"],
|
|
20
20
|
["lib/ooxml_integrity.py", "85e5cba28aa3b9f4f4c28f59c6118439a985fe4f29e25274ae63e9ef1edf15e3"],
|
|
@@ -32,7 +32,7 @@ export const canonicalSkillResourceManifest = Object.freeze(
|
|
|
32
32
|
["lib/rules/ordering.mjs", "c6da1ef0b228b8f0932b54422d2bc9744eb6bfbb8f5bb70b30b8f1a798765d1c"],
|
|
33
33
|
["lib/rules/scanner.mjs", "c62d672a66af25f29fe1d05b09288583425a2bf55f20d788f81d1578035f79f9"],
|
|
34
34
|
["lib/rules/session-state.mjs", "ad660cadf147b102437da95a771bae19ee2140bf39c58915b8cb498e8d04f2af"],
|
|
35
|
-
["lib/secret-shapes.mjs", "
|
|
35
|
+
["lib/secret-shapes.mjs", "7dc58f1caa6d5fc9dc43797a703911e1ec26689a082bb6972f6ed71722a01d57"],
|
|
36
36
|
["lib/secure-path-read.mjs", "c2991d0279f7399c3022c78babe28e653260f2a2e9ff4a5df692883ccf8807fd"],
|
|
37
37
|
["lib/strict-json.mjs", "d941f327aabce5ffc212770852cfbc7e08dd03471e4e9bc9560eff54f06d489a"],
|
|
38
38
|
["lib/wikify-knowledge-cli.mjs", "6835f8d6d98a3dd26266409366c23b2da437c0cd41e63537f0f200667655132b"],
|
|
@@ -699,7 +699,7 @@ export const canonicalSkillResourceManifest = Object.freeze(
|
|
|
699
699
|
["skills/lit-typographic-motion/engine/node/pipeline.mjs", "fef77c5190c604fbdf360bd1d38d750587562ca0974a8b1aacccb6ad57411206"],
|
|
700
700
|
["skills/lit-typographic-motion/engine/node/png.mjs", "fa34ab36a17f614064d8f00e3540c3bd13b06fc61036981d44fe57ba8ec86d4a"],
|
|
701
701
|
["skills/lit-typographic-motion/engine/node/report.mjs", "6de1d1a1e81963a5d97da4c71f4d22a379d145ac3d3fb5aa7a7ba6b044811329"],
|
|
702
|
-
["skills/lit-typographic-motion/engine/node/runtime.mjs", "
|
|
702
|
+
["skills/lit-typographic-motion/engine/node/runtime.mjs", "e246f0b00b6859ee6793faf3927740d8d04e8c94f62b091c81e121ce6aa8b707"],
|
|
703
703
|
["skills/lit-typographic-motion/engine/node/session.mjs", "e013dfd801ba8503038233efd4625d78f4e48331564612eecb2034e9062c414f"],
|
|
704
704
|
["skills/lit-typographic-motion/engine/node/sound-cli.mjs", "e10cf22b8ec9d761e3e6e9279330520ed33b1b2b9e68fd46d7672ac8e98b5eae"],
|
|
705
705
|
["skills/lit-typographic-motion/engine/node/sound.mjs", "7b585b47f36c58cf4959310e59363fb321e04586193430b3ad09a9efd5bc9bd3"],
|
|
@@ -129,6 +129,21 @@ const hueToRgb = (hue) => {
|
|
|
129
129
|
return [r, g, b].map((channel) => Math.round(255 * (1 - s + s * channel)));
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
+
// Nearest 6x6x6 cube entry of the 256-colour palette.
|
|
133
|
+
const rgbTo256 = ([r, g, b]) => {
|
|
134
|
+
const cube = (value) => Math.round((value / 255) * 5);
|
|
135
|
+
return 16 + 36 * cube(r) + 6 * cube(g) + cube(b);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// One fixed colour for `text`: the exact RGB on truecolor, its cube entry on 256 colours, the
|
|
139
|
+
// given ANSI code on 16 colours, and the text untouched on plain.
|
|
140
|
+
export const toneText = (text, rgb, { depth = "plain", ansi16 = 37 } = {}) => {
|
|
141
|
+
if (depth === "truecolor") return `\x1b[38;2;${rgb.join(";")}m${text}\x1b[0m`;
|
|
142
|
+
if (depth === "256") return `\x1b[38;5;${rgbTo256(rgb)}m${text}\x1b[0m`;
|
|
143
|
+
if (depth === "16") return `\x1b[${ansi16}m${text}\x1b[0m`;
|
|
144
|
+
return text;
|
|
145
|
+
};
|
|
146
|
+
|
|
132
147
|
export const rainbowText = (text, { depth = "plain", phase = 0 } = {}) => {
|
|
133
148
|
if (!["truecolor", "256", "16"].includes(depth)) return text;
|
|
134
149
|
const chars = [...text];
|
|
@@ -138,10 +153,7 @@ export const rainbowText = (text, { depth = "plain", phase = 0 } = {}) => {
|
|
|
138
153
|
const hue = (((phase + index * step) % 360) + 360) % 360;
|
|
139
154
|
if (depth === "16") return `\x1b[${RAINBOW_ANSI16[Math.floor(((hue + 30) % 360) / 60)]}m${char}`;
|
|
140
155
|
const [r, g, b] = hueToRgb(hue);
|
|
141
|
-
if (depth === "256") {
|
|
142
|
-
const cube = (value) => Math.round((value / 255) * 5);
|
|
143
|
-
return `\x1b[38;5;${16 + 36 * cube(r) + 6 * cube(g) + cube(b)}m${char}`;
|
|
144
|
-
}
|
|
156
|
+
if (depth === "256") return `\x1b[38;5;${rgbTo256([r, g, b])}m${char}`;
|
|
145
157
|
return `\x1b[38;2;${r};${g};${b}m${char}`;
|
|
146
158
|
});
|
|
147
159
|
return `\x1b[1m${painted.join("")}\x1b[0m`;
|
|
@@ -232,7 +232,29 @@ const safeSessionId = (sessionId) => {
|
|
|
232
232
|
return cleaned || "unknown";
|
|
233
233
|
};
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
// The `.litclaude` folder and its `jev` folder must both be real folders inside the project. A
|
|
236
|
+
// symlink at either level would carry every read and write outside it, so the whole state path is
|
|
237
|
+
// refused and the caller falls back to its silent no-hint path. `create` makes missing levels.
|
|
238
|
+
const jevStateDirectory = (stateRoot, { create = false } = {}) => {
|
|
239
|
+
let directory = stateRoot;
|
|
240
|
+
for (const part of [".litclaude", "jev"]) {
|
|
241
|
+
directory = join(directory, part);
|
|
242
|
+
let stat;
|
|
243
|
+
try {
|
|
244
|
+
stat = lstatSync(directory);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (error?.code !== "ENOENT" || !create) throw error;
|
|
247
|
+
try {
|
|
248
|
+
mkdirSync(directory, { mode: 0o700 });
|
|
249
|
+
} catch (mkdirError) {
|
|
250
|
+
if (mkdirError?.code !== "EEXIST") throw mkdirError;
|
|
251
|
+
}
|
|
252
|
+
stat = lstatSync(directory);
|
|
253
|
+
}
|
|
254
|
+
if (!stat.isDirectory()) throw new Error("state folder is not a real folder");
|
|
255
|
+
}
|
|
256
|
+
return directory;
|
|
257
|
+
};
|
|
236
258
|
|
|
237
259
|
// Opening with O_NOFOLLOW refuses a symlink planted at a state or trace path, so a write never
|
|
238
260
|
// lands outside the state folder. Where the platform lacks the flag, lstat refuses it instead.
|
|
@@ -274,8 +296,7 @@ const readSession = (stateRoot, sessionId) => {
|
|
|
274
296
|
|
|
275
297
|
const writeSession = (stateRoot, sessionId, session) => {
|
|
276
298
|
try {
|
|
277
|
-
const directory = jevStateDirectory(stateRoot);
|
|
278
|
-
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
299
|
+
const directory = jevStateDirectory(stateRoot, { create: true });
|
|
279
300
|
const target = join(directory, `session-${safeSessionId(sessionId)}.json`);
|
|
280
301
|
const temporary = `${target}.${process.pid}.tmp`;
|
|
281
302
|
refuseSymlink(temporary);
|
|
@@ -293,8 +314,7 @@ const writeSession = (stateRoot, sessionId, session) => {
|
|
|
293
314
|
|
|
294
315
|
const writeTrace = (stateRoot, record) => {
|
|
295
316
|
try {
|
|
296
|
-
const directory = jevStateDirectory(stateRoot);
|
|
297
|
-
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
317
|
+
const directory = jevStateDirectory(stateRoot, { create: true });
|
|
298
318
|
const target = join(directory, "trace.jsonl");
|
|
299
319
|
refuseSymlink(target);
|
|
300
320
|
const fd = openSync(target, constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | NO_FOLLOW, 0o600);
|
|
@@ -341,8 +361,6 @@ export const jevHudState = ({ env = process.env, stateRoot, sessionId } = {}) =>
|
|
|
341
361
|
return { keyMissing: false, last: stateRoot ? readSession(stateRoot, sessionId).last : null };
|
|
342
362
|
};
|
|
343
363
|
|
|
344
|
-
export const formatJevLatency = (latencyMs) => `${(latencyMs / 1000).toFixed(2)}s`;
|
|
345
|
-
|
|
346
364
|
/** Host-side eligibility that needs no router: a leading command character or too little text. */
|
|
347
365
|
export const isJevEligibleText = (text) => {
|
|
348
366
|
if (typeof text !== "string") return false;
|
|
@@ -42,6 +42,19 @@ const HOST_TOOLS = [
|
|
|
42
42
|
|
|
43
43
|
const run = (command, args, options = {}) => spawnSync(command, args, { encoding: "utf8", ...options });
|
|
44
44
|
|
|
45
|
+
// npm exports its own settings as npm_config_* to every script it runs, so a caller started under
|
|
46
|
+
// `npm install -g` would hand `global=true` to this child, and npm refuses `npm ci` in global mode
|
|
47
|
+
// (ECIGLOBAL). Drop the inherited settings that change where or what `npm ci` installs; registry,
|
|
48
|
+
// proxy and cache settings pass through.
|
|
49
|
+
const NPM_INSTALL_SHAPE_SETTINGS = new Set([
|
|
50
|
+
"global", "location", "prefix", "dry_run", "package_lock_only", "include", "production",
|
|
51
|
+
"workspace", "workspaces", "include_workspace_root", "install_strategy", "global_style", "legacy_bundling",
|
|
52
|
+
]);
|
|
53
|
+
const npmChildEnv = (env) => Object.fromEntries(Object.entries(env).filter(([key]) => {
|
|
54
|
+
const setting = /^npm_config_(.+)$/iu.exec(key)?.[1].toLowerCase().replaceAll("-", "_");
|
|
55
|
+
return !NPM_INSTALL_SHAPE_SETTINGS.has(setting);
|
|
56
|
+
}));
|
|
57
|
+
|
|
45
58
|
export function pythonCommand() {
|
|
46
59
|
return process.env.LITCLAUDE_OFFICE_PYTHON || "python3";
|
|
47
60
|
}
|
|
@@ -136,6 +149,7 @@ export function ensureNode() {
|
|
|
136
149
|
const result = run(npm, ["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund", "--loglevel=error"], {
|
|
137
150
|
cwd: status.dir,
|
|
138
151
|
stdio: ["ignore", "pipe", "pipe"],
|
|
152
|
+
env: npmChildEnv(process.env),
|
|
139
153
|
});
|
|
140
154
|
if (result.error || result.status !== 0) {
|
|
141
155
|
throw new Error(`OFFICE_RUNTIME_NODE_INSTALL_FAILED: npm ci exited ${result.status}: ${(result.stderr || result.error?.message || "").trim().slice(-600)}`);
|
|
@@ -24,7 +24,11 @@ export const SECRET_SHAPE_PATTERNS = Object.freeze([
|
|
|
24
24
|
/(?<![A-Za-z0-9_])SG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_])/u,
|
|
25
25
|
/(?<![A-Za-z0-9_])(?:glpat|gldt|glrt)-[A-Za-z0-9_-]{8,}(?![A-Za-z0-9_])/u,
|
|
26
26
|
/(?<![A-Za-z0-9_])eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+(?![A-Za-z0-9_])/u,
|
|
27
|
-
/\
|
|
27
|
+
/\bbearer\s+[A-Za-z0-9._~+/=-]{12,}(?=$|[^A-Za-z0-9._~+/=-])/iu,
|
|
28
|
+
// A Basic credential is standard base64 of at least 8 characters in whole 4-character groups,
|
|
29
|
+
// carrying a digit, `+`, `/`, padding, or mixed case with an inner capital. The scheme matches
|
|
30
|
+
// in any case; the credential is case-sensitive, so "basic understanding" is not a credential.
|
|
31
|
+
/\b[Bb][Aa][Ss][Ii][Cc]\s+(?=[A-Za-z0-9+/=]{8})(?=[A-Za-z0-9+/=]*[0-9+/=]|(?=[A-Za-z0-9+/=]*[a-z])[A-Za-z0-9+/=]+[A-Z])(?:[A-Za-z0-9+/]{4})+(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?(?![A-Za-z0-9._~+/=-])/u,
|
|
28
32
|
/(?<![A-Za-z0-9-])(?:proxy-)?authorization\s*:\s*[!#$%&'*+\-.^_`|~0-9A-Za-z]+\s+(?:[!#$%&'*+\-.^_`|~0-9A-Za-z]+\s*=\s*\S+|\S{12,})/iu,
|
|
29
33
|
/\b(?:password|passwd|secret|token|api[_-]?key|access[_-]?token)\s*[:=]\s*[^\s,;]+/iu,
|
|
30
34
|
/\b[A-Za-z][A-Za-z0-9]*(?:[_-][A-Za-z0-9]+)*[_-](?:token|secret|key)(?:[_-][A-Za-z0-9]+)*\s*[:=]\s*[^\s,;]+/iu,
|
|
@@ -237,9 +237,22 @@ export async function installFonts({ env = process.env, log = () => {} } = {}) {
|
|
|
237
237
|
return done;
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
|
|
240
|
+
// npm exports its own settings to lifecycle scripts as npm_config_*, so an install started by the
|
|
241
|
+
// postinstall of `npm install -g` would hand `global=true` to this child, and npm refuses `npm ci`
|
|
242
|
+
// in global mode (ECIGLOBAL). Drop the inherited settings that change where or what `npm ci`
|
|
243
|
+
// installs; registry, proxy and cache settings pass through.
|
|
244
|
+
const NPM_INSTALL_SHAPE_SETTINGS = new Set([
|
|
245
|
+
"global", "location", "prefix", "dry_run", "package_lock_only", "include", "production",
|
|
246
|
+
"workspace", "workspaces", "include_workspace_root", "install_strategy", "global_style", "legacy_bundling",
|
|
247
|
+
]);
|
|
248
|
+
const npmChildEnv = (env) => Object.fromEntries(Object.entries(env).filter(([key]) => {
|
|
249
|
+
const setting = /^npm_config_(.+)$/iu.exec(key)?.[1].toLowerCase().replaceAll("-", "_");
|
|
250
|
+
return !NPM_INSTALL_SHAPE_SETTINGS.has(setting);
|
|
251
|
+
}));
|
|
252
|
+
|
|
253
|
+
function npm(args, { env, ...options }) {
|
|
241
254
|
const bin = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
242
|
-
return spawnSync(bin, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], ...options });
|
|
255
|
+
return spawnSync(bin, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], ...options, env: npmChildEnv(env) });
|
|
243
256
|
}
|
|
244
257
|
|
|
245
258
|
export async function installNode({ env = process.env, log = () => {} } = {}) {
|
|
@@ -264,12 +277,12 @@ export async function installNode({ env = process.env, log = () => {} } = {}) {
|
|
|
264
277
|
if (createHash("sha512").update(bytes).digest("hex") !== integrity) throw new Error(`integrity mismatch for ${name}`);
|
|
265
278
|
const tarball = path.join(cache, path.basename(new URL(pkg.resolved).pathname));
|
|
266
279
|
writeAtomic(tarball, bytes);
|
|
267
|
-
const add = npm(["cache", "add", tarball, "--cache", cache], { env
|
|
280
|
+
const add = npm(["cache", "add", tarball, "--cache", cache], { env });
|
|
268
281
|
if (add.status !== 0) throw new Error(`npm cache add failed: ${add.stderr.trim().slice(-400)}`);
|
|
269
282
|
}
|
|
270
283
|
args.push("--offline", "--cache", cache);
|
|
271
284
|
}
|
|
272
|
-
const result = npm(args, { cwd: dir, env
|
|
285
|
+
const result = npm(args, { cwd: dir, env });
|
|
273
286
|
if (cache) rmSync(cache, { recursive: true, force: true });
|
|
274
287
|
if (result.error || result.status !== 0) {
|
|
275
288
|
rmSync(dir, { recursive: true, force: true });
|