@linxiraos/pi-utils 1.1.27 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -6
- package/README.md +1 -1
- package/dist/types/ar/index.d.ts +1 -0
- package/dist/types/ar/zip-package.d.ts +21 -0
- package/dist/types/color.d.ts +2 -0
- package/dist/types/dirs.d.ts +31 -12
- package/dist/types/docx/converter.d.ts +2 -0
- package/dist/types/dom/core.d.ts +15 -4
- package/dist/types/dom/parser.d.ts +5 -3
- package/dist/types/dom/selector.d.ts +7 -1
- package/dist/types/logger/rotating-file.d.ts +4 -2
- package/dist/types/loop-phase.d.ts +12 -7
- package/dist/types/marked/core.d.ts +7 -6
- package/dist/types/marked-list.d.ts +38 -0
- package/dist/types/math-delimiters.d.ts +10 -0
- package/dist/types/readability/types.d.ts +2 -0
- package/dist/types/temp.d.ts +7 -0
- package/dist/types/turndown/types.d.ts +7 -0
- package/dist/types/vterm/buffer.d.ts +16 -5
- package/package.json +2 -2
- package/src/ar/index.ts +1 -0
- package/src/ar/open.ts +29 -8
- package/src/ar/zip-package.ts +57 -0
- package/src/chalk.ts +66 -23
- package/src/color.ts +6 -0
- package/src/dirs.ts +84 -24
- package/src/docx/converter.ts +26 -12
- package/src/dom/core.ts +354 -111
- package/src/dom/parser.ts +42 -19
- package/src/dom/selector.ts +328 -95
- package/src/fetch-retry.ts +2 -0
- package/src/format.ts +5 -1
- package/src/json-lexer.ts +54 -0
- package/src/json-parse.ts +21 -6
- package/src/logger/rotating-file.ts +5 -3
- package/src/logger.ts +39 -23
- package/src/loop-phase.ts +66 -21
- package/src/marked/core.ts +42 -18
- package/src/marked-list.ts +73 -0
- package/src/math-delimiters.ts +16 -0
- package/src/module-timer.ts +5 -5
- package/src/postmortem.ts +1 -1
- package/src/process-name.ts +2 -2
- package/src/prompt.ts +3 -1
- package/src/ptree.ts +28 -35
- package/src/readability/readability.ts +349 -39
- package/src/readability/types.ts +2 -0
- package/src/runtime-install.ts +2 -2
- package/src/stream.ts +34 -13
- package/src/temp.ts +7 -0
- package/src/template.ts +7 -4
- package/src/timing-buffer.ts +1 -1
- package/src/tls-fetch.ts +1 -1
- package/src/turndown/gfm.ts +16 -5
- package/src/turndown/html.ts +28 -8
- package/src/turndown/service.ts +43 -15
- package/src/turndown/types.ts +7 -0
- package/src/vterm/buffer.ts +31 -11
- package/src/vterm/terminal.ts +213 -100
package/src/chalk.ts
CHANGED
|
@@ -257,14 +257,74 @@ function applyStyles(input: string, styles: readonly Style[]): string {
|
|
|
257
257
|
for (let index = styles.length - 1; index >= 0; index--) {
|
|
258
258
|
const style = styles[index];
|
|
259
259
|
if (!style) continue;
|
|
260
|
-
output = output.split(style.close).join(style.close + style.open);
|
|
260
|
+
if (output.includes(style.close)) output = output.split(style.close).join(style.close + style.open);
|
|
261
261
|
opening = style.open + opening;
|
|
262
262
|
closing += style.close;
|
|
263
263
|
}
|
|
264
|
-
output = output.replace(/\r?\n/g, match => `${closing}${match}${opening}`);
|
|
264
|
+
if (output.includes("\n")) output = output.replace(/\r?\n/g, match => `${closing}${match}${opening}`);
|
|
265
265
|
return opening + output + closing;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
const BUILDER_CONTEXT = Symbol("chalk.context");
|
|
269
|
+
const BUILDER_STYLES = Symbol("chalk.styles");
|
|
270
|
+
const BUILDER_HEX_CACHE = Symbol("chalk.hexCache");
|
|
271
|
+
// Bounds per-builder hex children when callers derive colors dynamically.
|
|
272
|
+
const MAX_CACHED_HEX_BUILDERS = 256;
|
|
273
|
+
|
|
274
|
+
interface BuilderState {
|
|
275
|
+
[BUILDER_CONTEXT]: ChalkContext;
|
|
276
|
+
[BUILDER_STYLES]: readonly Style[];
|
|
277
|
+
[BUILDER_HEX_CACHE]?: Map<string, ChalkInstance>;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
type Builder = ChalkInstance & BuilderState;
|
|
281
|
+
|
|
282
|
+
/** Child builders are created on first access and cached on the parent builder. */
|
|
283
|
+
function defineStyleGetter(proto: object, name: string, style: Style): void {
|
|
284
|
+
Object.defineProperty(proto, name, {
|
|
285
|
+
get(this: Builder) {
|
|
286
|
+
const child = createBuilder(this[BUILDER_CONTEXT], [...this[BUILDER_STYLES], style]);
|
|
287
|
+
Object.defineProperty(this, name, { value: child });
|
|
288
|
+
return child;
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const builderPrototype: object = Object.create(Function.prototype);
|
|
294
|
+
Object.defineProperty(builderPrototype, "level", {
|
|
295
|
+
get(this: Builder) {
|
|
296
|
+
return this[BUILDER_CONTEXT].level;
|
|
297
|
+
},
|
|
298
|
+
set(this: Builder, level: number) {
|
|
299
|
+
this[BUILDER_CONTEXT].level = level;
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
for (const name in STYLES) {
|
|
303
|
+
const style = STYLES[name];
|
|
304
|
+
if (style) defineStyleGetter(builderPrototype, name, style);
|
|
305
|
+
}
|
|
306
|
+
defineStyleGetter(builderPrototype, "grey", STYLES.gray!);
|
|
307
|
+
defineStyleGetter(builderPrototype, "bgGray", STYLES.bgBlackBright!);
|
|
308
|
+
defineStyleGetter(builderPrototype, "bgGrey", STYLES.bgBlackBright!);
|
|
309
|
+
Object.defineProperty(builderPrototype, "hex", {
|
|
310
|
+
value(this: Builder, color: string): ChalkInstance {
|
|
311
|
+
const context = this[BUILDER_CONTEXT];
|
|
312
|
+
const key = `${context.level}:${color}`;
|
|
313
|
+
let cache = this[BUILDER_HEX_CACHE];
|
|
314
|
+
const cached = cache?.get(key);
|
|
315
|
+
if (cached) return cached;
|
|
316
|
+
const child = createBuilder(context, [...this[BUILDER_STYLES], hexStyle(color, context.level)]);
|
|
317
|
+
if (!cache) {
|
|
318
|
+
cache = new Map();
|
|
319
|
+
this[BUILDER_HEX_CACHE] = cache;
|
|
320
|
+
} else if (cache.size >= MAX_CACHED_HEX_BUILDERS) {
|
|
321
|
+
cache.clear();
|
|
322
|
+
}
|
|
323
|
+
cache.set(key, child);
|
|
324
|
+
return child;
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
|
|
268
328
|
function createBuilder(context: ChalkContext, styles: readonly Style[]): ChalkInstance {
|
|
269
329
|
const builder = ((...values: unknown[]) => {
|
|
270
330
|
let text = "";
|
|
@@ -274,27 +334,10 @@ function createBuilder(context: ChalkContext, styles: readonly Style[]): ChalkIn
|
|
|
274
334
|
}
|
|
275
335
|
if (text.length === 0 || context.level === 0) return text;
|
|
276
336
|
return applyStyles(text, styles);
|
|
277
|
-
}) as
|
|
278
|
-
Object.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
context.level = level;
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
|
-
for (const name in STYLES) {
|
|
285
|
-
const style = STYLES[name];
|
|
286
|
-
if (style) Object.defineProperty(builder, name, { get: () => createBuilder(context, [...styles, style]) });
|
|
287
|
-
}
|
|
288
|
-
Object.defineProperty(builder, "grey", { get: () => createBuilder(context, [...styles, STYLES.gray!]) });
|
|
289
|
-
Object.defineProperty(builder, "bgGray", {
|
|
290
|
-
get: () => createBuilder(context, [...styles, STYLES.bgBlackBright!]),
|
|
291
|
-
});
|
|
292
|
-
Object.defineProperty(builder, "bgGrey", {
|
|
293
|
-
get: () => createBuilder(context, [...styles, STYLES.bgBlackBright!]),
|
|
294
|
-
});
|
|
295
|
-
Object.defineProperty(builder, "hex", {
|
|
296
|
-
value: (color: string) => createBuilder(context, [...styles, hexStyle(color, context.level)]),
|
|
297
|
-
});
|
|
337
|
+
}) as Builder;
|
|
338
|
+
Object.setPrototypeOf(builder, builderPrototype);
|
|
339
|
+
builder[BUILDER_CONTEXT] = context;
|
|
340
|
+
builder[BUILDER_STYLES] = styles;
|
|
298
341
|
return builder;
|
|
299
342
|
}
|
|
300
343
|
|
package/src/color.ts
CHANGED
|
@@ -399,6 +399,12 @@ export function oklchCusp(h: number): { l: number; c: number } {
|
|
|
399
399
|
return { l: lCusp, c: lCusp * sCusp };
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
+
/** Angular distance between two hues in degrees (0–180), wrapping at 360. */
|
|
403
|
+
export function hueDistance(a: number, b: number): number {
|
|
404
|
+
const d = Math.abs(a - b) % 360;
|
|
405
|
+
return Math.min(d, 360 - d);
|
|
406
|
+
}
|
|
407
|
+
|
|
402
408
|
/** Slack allowed on linear channels before a color counts as out of sRGB gamut. */
|
|
403
409
|
const GAMUT_EPSILON = 1e-4;
|
|
404
410
|
|
package/src/dirs.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Centralized path helpers for
|
|
2
|
+
* Centralized path helpers for zetacode config directories.
|
|
3
3
|
*
|
|
4
4
|
* Uses PI_CONFIG_DIR (default ".zeta") for the config root and
|
|
5
|
+
* Uses ZETA_CONFIG_DIR (default ".zeta") for the config root and
|
|
5
6
|
* ZETA_CODING_AGENT_DIR to override the agent directory.
|
|
6
7
|
*
|
|
7
8
|
* On Linux, if XDG_DATA_HOME / XDG_STATE_HOME / XDG_CACHE_HOME environment
|
|
8
9
|
* variables are set, paths are redirected to XDG-compliant locations under
|
|
9
|
-
* $XDG_*_HOME/zeta/. This requires running `
|
|
10
|
+
* $XDG_*_HOME/zeta/. This requires running `zetacode config migrate` first to
|
|
10
11
|
* move data to the new locations. No filesystem existence checks are performed
|
|
11
12
|
* — if the env var is set, zeta trusts that the migration has been done.
|
|
12
13
|
*/
|
|
@@ -22,11 +23,11 @@ import { isEnoent, isEnotdir } from "./fs-error";
|
|
|
22
23
|
* names, provider attribution headers, splash wordmark. NOT the CLI command. */
|
|
23
24
|
export const APP_NAME: string = "zeta";
|
|
24
25
|
|
|
25
|
-
/** CLI binary name (`npm i -g @linxiraos/zeta` installs `
|
|
26
|
-
* `
|
|
27
|
-
* string and process title uses this; the bare `zeta` name
|
|
28
|
-
* @linxiraos/main workspace product, not this package. */
|
|
29
|
-
export const CLI_BIN_NAME: string = "
|
|
26
|
+
/** CLI binary name (`npm i -g @linxiraos/zeta` installs `zetacode`, with
|
|
27
|
+
* `zetacode` and `zeta-cli` as compatibility aliases). Every user-facing "run
|
|
28
|
+
* this command" string and process title uses this; the bare `zeta` name
|
|
29
|
+
* belongs to the @linxiraos/main workspace product, not this package. */
|
|
30
|
+
export const CLI_BIN_NAME: string = "zetacode";
|
|
30
31
|
|
|
31
32
|
/** Public homepage that inference gateways (OpenRouter, Vercel AI Gateway) credit our traffic to. */
|
|
32
33
|
export const APP_URL: string = "https://linxira-os.github.io/zeta/";
|
|
@@ -125,6 +126,14 @@ function getProfileConfigRoot(profile: string | undefined): string {
|
|
|
125
126
|
return profile ? path.join(root, "profiles", profile) : root;
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
function readPiProfileFromEnvSafe(): string | undefined {
|
|
130
|
+
try {
|
|
131
|
+
return normalizeProfileName(process.env.ZETA_PROFILE);
|
|
132
|
+
} catch {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
128
137
|
function getProfileAgentDir(profile: string): string {
|
|
129
138
|
return path.join(getProfileConfigRoot(profile), "agent");
|
|
130
139
|
}
|
|
@@ -299,9 +308,9 @@ export function getSafeProjectCwd(): string {
|
|
|
299
308
|
return os.homedir();
|
|
300
309
|
}
|
|
301
310
|
|
|
302
|
-
/** Get the config directory name relative to home (e.g. ".zeta" or
|
|
311
|
+
/** Get the config directory name relative to home (e.g. ".zeta" or ZETA_CONFIG_DIR override). */
|
|
303
312
|
export function getConfigDirName(): string {
|
|
304
|
-
return process.env.
|
|
313
|
+
return process.env.ZETA_CONFIG_DIR || CONFIG_DIR_NAME;
|
|
305
314
|
}
|
|
306
315
|
|
|
307
316
|
/** Get the config agent directory name relative to home (e.g. ".zeta/agent" or PI_CONFIG_DIR + "/agent"). */
|
|
@@ -345,7 +354,7 @@ class DirResolver {
|
|
|
345
354
|
const isDefault = this.agentDir === defaultAgent;
|
|
346
355
|
|
|
347
356
|
// XDG is a Linux convention. On supported platforms, default profile state
|
|
348
|
-
// resolves under $XDG_*_HOME/zeta once `
|
|
357
|
+
// resolves under $XDG_*_HOME/zeta once `zetacode config init-xdg` has migrated
|
|
349
358
|
// the user's data. Named profiles follow a stricter rule: the XDG choice
|
|
350
359
|
// is keyed on the profile-specific XDG path, never the base app root.
|
|
351
360
|
//
|
|
@@ -454,8 +463,12 @@ class DirResolver {
|
|
|
454
463
|
* without exporting it). Returns `undefined` in those cases so reset falls back
|
|
455
464
|
* to the standard `~/.zeta/agent`.
|
|
456
465
|
*/
|
|
457
|
-
function resolvePreProfileAgentDir(
|
|
458
|
-
|
|
466
|
+
function resolvePreProfileAgentDir(
|
|
467
|
+
profile: string | undefined,
|
|
468
|
+
agentDirEnv: string | undefined,
|
|
469
|
+
profileAgentDirSource: string | undefined = profile,
|
|
470
|
+
): string | undefined {
|
|
471
|
+
return isProfileDerivedAgentDir(profile ?? profileAgentDirSource, agentDirEnv) ? undefined : agentDirEnv;
|
|
459
472
|
}
|
|
460
473
|
|
|
461
474
|
let activeProfile = readProfileFromEnvSafe();
|
|
@@ -478,6 +491,9 @@ function resolveActiveAgentDirOverride(): string | undefined {
|
|
|
478
491
|
const env = process.env.ZETA_CODING_AGENT_DIR;
|
|
479
492
|
if (env && /[/\\]profiles[/\\][^/\\]+[/\\]agent$/.test(env.replace(/\\/g, "/"))) return undefined;
|
|
480
493
|
return resolvePreProfileAgentDir(undefined, env);
|
|
494
|
+
return activeProfile
|
|
495
|
+
? undefined
|
|
496
|
+
: resolvePreProfileAgentDir(undefined, process.env.ZETA_CODING_AGENT_DIR, readPiProfileFromEnvSafe());
|
|
481
497
|
}
|
|
482
498
|
|
|
483
499
|
let dirs = new DirResolver({
|
|
@@ -495,7 +511,11 @@ let dirs = new DirResolver({
|
|
|
495
511
|
* — and refreshed on `setAgentDir`, since that call is the user explicitly
|
|
496
512
|
* redefining the baseline.
|
|
497
513
|
*/
|
|
498
|
-
let preProfileAgentDirEnv: string | undefined =
|
|
514
|
+
let preProfileAgentDirEnv: string | undefined = resolvePreProfileAgentDir(
|
|
515
|
+
activeProfile,
|
|
516
|
+
process.env.ZETA_CODING_AGENT_DIR,
|
|
517
|
+
activeProfile ?? readPiProfileFromEnvSafe(),
|
|
518
|
+
);
|
|
499
519
|
// Anchor home for the resolver. Captured at module load to stay stable across
|
|
500
520
|
// test mocks of `os.homedir()`. `getPluginsDir(home)` compares against this so
|
|
501
521
|
// production callers (`home === RESOLVER_HOME`) hit the XDG-aware resolver while
|
|
@@ -548,7 +568,11 @@ export function setAgentDir(dir: string): void {
|
|
|
548
568
|
* no business clearing it.
|
|
549
569
|
*/
|
|
550
570
|
export function __resetProfileSnapshotForTests(): void {
|
|
551
|
-
preProfileAgentDirEnv =
|
|
571
|
+
preProfileAgentDirEnv = resolvePreProfileAgentDir(
|
|
572
|
+
activeProfile,
|
|
573
|
+
process.env.ZETA_CODING_AGENT_DIR,
|
|
574
|
+
activeProfile ?? readPiProfileFromEnvSafe(),
|
|
575
|
+
);
|
|
552
576
|
}
|
|
553
577
|
|
|
554
578
|
/**
|
|
@@ -573,11 +597,18 @@ export function setProfile(profile: string | undefined): void {
|
|
|
573
597
|
// snapshot — the "pre-profile" baseline is the state before profiles
|
|
574
598
|
// entered the picture, not the state between two activations.
|
|
575
599
|
preProfileAgentDirEnv = resolveActiveAgentDirOverride();
|
|
600
|
+
preProfileAgentDirEnv = resolvePreProfileAgentDir(
|
|
601
|
+
undefined,
|
|
602
|
+
process.env.ZETA_CODING_AGENT_DIR,
|
|
603
|
+
readPiProfileFromEnvSafe(),
|
|
604
|
+
);
|
|
576
605
|
}
|
|
577
606
|
activeProfile = next;
|
|
578
607
|
if (activeProfile) {
|
|
579
608
|
dirs = new DirResolver({ profile: activeProfile });
|
|
580
609
|
process.env.ZETA_PROFILE = activeProfile;
|
|
610
|
+
process.env.OMP_PROFILE = activeProfile;
|
|
611
|
+
process.env.PI_PROFILE = activeProfile;
|
|
581
612
|
process.env.ZETA_CODING_AGENT_DIR = dirs.agentDir;
|
|
582
613
|
} else {
|
|
583
614
|
for (const key of PROFILE_ENV_KEYS) {
|
|
@@ -669,13 +700,26 @@ export function getPluginsPackageJson(home?: string): string {
|
|
|
669
700
|
return path.join(getPluginsDir(home), "package.json");
|
|
670
701
|
}
|
|
671
702
|
|
|
672
|
-
/**
|
|
673
|
-
*
|
|
674
|
-
*
|
|
703
|
+
/**
|
|
704
|
+
* Plugin lock file (~/.zeta/plugins/omp-plugins.lock.json).
|
|
705
|
+
*
|
|
706
|
+
* The on-disk filename stays upstream-compatible: it carries per-plugin runtime
|
|
707
|
+
* state (enabled flag, version), so renaming it would reset every existing
|
|
708
|
+
* install's plugins on upgrade. That is the reason for the compat, not a
|
|
709
|
+
* general rule — see `document/brand-surface-rules.md`, which classifies this
|
|
710
|
+
* surface as DUAL (read both spellings, write the Zeta one) rather than
|
|
711
|
+
* permanently frozen.
|
|
712
|
+
*/
|
|
675
713
|
export function getPluginsLockfile(home?: string): string {
|
|
676
714
|
return path.join(getPluginsDir(home), "omp-plugins.lock.json");
|
|
677
715
|
}
|
|
678
716
|
|
|
717
|
+
/** Zeta-named lockfile, written going forward; `getPluginsLockfile` is read first. */
|
|
718
|
+
export const PLUGINS_LOCKFILE_CANONICAL = "zeta-plugins.lock.json";
|
|
719
|
+
|
|
720
|
+
/** Pre-rename lockfile name still produced by earlier installs. */
|
|
721
|
+
export const PLUGINS_LOCKFILE_LEGACY = "omp-plugins.lock.json";
|
|
722
|
+
|
|
679
723
|
/**
|
|
680
724
|
* Normalize a plugin id into a safe single path segment (spec §4.2): lowercased,
|
|
681
725
|
* every character outside `[a-z0-9._-]` (including `/` and `\`, so scoped npm
|
|
@@ -762,11 +806,17 @@ export function getRemoteDir(): string {
|
|
|
762
806
|
* empty/whitespace input or a path that is still relative after expansion.
|
|
763
807
|
*
|
|
764
808
|
* A worktree base is process-global and consumed by both creation
|
|
765
|
-
* (PR checkout, task isolation) and cleanup (`
|
|
809
|
+
* (PR checkout, task isolation) and cleanup (`zetacode worktree`). A relative value
|
|
766
810
|
* would resolve against whatever cwd happened to launch `zeta`, so checkout and
|
|
767
811
|
* cleanup could disagree — we refuse it rather than silently bind it to cwd.
|
|
812
|
+
* Worktree bases and the natives directory are process-global: a worktree base
|
|
813
|
+
* is consumed by both creation (PR checkout, task isolation) and cleanup
|
|
814
|
+
* (`zetacode worktree`), and every launch extracts or loads the native addon from
|
|
815
|
+
* the same natives directory. A relative value would resolve against whatever
|
|
816
|
+
* cwd happened to launch `zetacode`, so those readers could disagree — we refuse it
|
|
817
|
+
* rather than silently bind it to cwd.
|
|
768
818
|
*/
|
|
769
|
-
function
|
|
819
|
+
function resolveAbsoluteDir(value: string | undefined): string | undefined {
|
|
770
820
|
const trimmed = value?.trim();
|
|
771
821
|
if (!trimmed) return undefined;
|
|
772
822
|
let p = trimmed;
|
|
@@ -779,17 +829,17 @@ let worktreesDirOverride: string | undefined;
|
|
|
779
829
|
|
|
780
830
|
/**
|
|
781
831
|
* Relocate the base directory for agent-managed worktrees (PR checkouts, task
|
|
782
|
-
* isolation, and `
|
|
832
|
+
* isolation, and `zetacode worktree` cleanup all read the same base). Driven by the
|
|
783
833
|
* `worktree.base` setting in coding-agent; pass `undefined`/empty to clear and
|
|
784
834
|
* fall back to `OMP_WORKTREE_DIR` or the `~/.zeta/wt` default.
|
|
785
835
|
*
|
|
786
|
-
* `~` is expanded and a relative path is rejected (see {@link
|
|
836
|
+
* `~` is expanded and a relative path is rejected (see {@link resolveAbsoluteDir}).
|
|
787
837
|
* Returns the absolute path that took effect, or `undefined` if the input was
|
|
788
838
|
* cleared or rejected — callers can warn on a non-empty input that returns
|
|
789
839
|
* `undefined`.
|
|
790
840
|
*/
|
|
791
841
|
export function setWorktreesDir(dir: string | undefined): string | undefined {
|
|
792
|
-
worktreesDirOverride =
|
|
842
|
+
worktreesDirOverride = resolveAbsoluteDir(dir);
|
|
793
843
|
return worktreesDirOverride;
|
|
794
844
|
}
|
|
795
845
|
|
|
@@ -801,7 +851,7 @@ export function setWorktreesDir(dir: string | undefined): string | undefined {
|
|
|
801
851
|
* ignored and resolution falls through.
|
|
802
852
|
*/
|
|
803
853
|
export function getWorktreesDir(): string {
|
|
804
|
-
return
|
|
854
|
+
return resolveAbsoluteDir(process.env.OMP_WORKTREE_DIR) ?? worktreesDirOverride ?? dirs.rootSubdir("wt", "data");
|
|
805
855
|
}
|
|
806
856
|
|
|
807
857
|
/** Get the SSH control socket directory (~/.zeta/ssh-control). */
|
|
@@ -928,8 +978,18 @@ export function getFastembedRuntimeDir(): string {
|
|
|
928
978
|
}
|
|
929
979
|
|
|
930
980
|
/** Get the natives directory (~/.zeta/natives). */
|
|
981
|
+
/**
|
|
982
|
+
* Get the directory the native addon loads downloaded tree-sitter wasm
|
|
983
|
+
* grammars from (`<natives dir>/grammars`, `~/.zeta/natives/grammars`). The
|
|
984
|
+
* natives loader configures the addon with the same path.
|
|
985
|
+
*/
|
|
986
|
+
export function getNativeGrammarsDir(): string {
|
|
987
|
+
return path.join(getNativesDir(), "grammars");
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/** Get the natives directory. PI_NATIVES_DIR overrides the usual cache root; relative values are ignored. */
|
|
931
991
|
export function getNativesDir(): string {
|
|
932
|
-
return dirs.rootSubdir("natives", "cache");
|
|
992
|
+
return resolveAbsoluteDir(process.env.PI_NATIVES_DIR) ?? dirs.rootSubdir("natives", "cache");
|
|
933
993
|
}
|
|
934
994
|
|
|
935
995
|
/** Get the stats database path (~/.zeta/stats.db). */
|
package/src/docx/converter.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { ZipPackage } from "../ar";
|
|
4
4
|
import { attribute, childElements, descendants, firstChild, localName, parseXml, type XmlElement } from "./xml";
|
|
5
5
|
|
|
6
6
|
/** A mammoth-compatible diagnostic emitted while converting a document. */
|
|
@@ -26,6 +26,8 @@ export interface DocxImage {
|
|
|
26
26
|
readonly altText: string;
|
|
27
27
|
/** Read the image payload using mammoth's used encoding surface. */
|
|
28
28
|
read(encoding: "base64"): Promise<string>;
|
|
29
|
+
/** Read the raw image bytes without a base64 round trip. */
|
|
30
|
+
readBytes(): Promise<Uint8Array>;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/** HTML attributes returned by a custom image converter. */
|
|
@@ -119,7 +121,8 @@ interface NumberingLevel {
|
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
interface ConversionContext {
|
|
122
|
-
|
|
124
|
+
/** Lazily inflated DOCX members, capped on total inflated bytes. */
|
|
125
|
+
readonly package: ZipPackage;
|
|
123
126
|
readonly relationships: ReadonlyMap<string, Relationship>;
|
|
124
127
|
readonly contentTypes: ReadonlyMap<string, string>;
|
|
125
128
|
readonly styles: ReadonlyMap<string, Style>;
|
|
@@ -373,8 +376,8 @@ async function renderImage(element: XmlElement, context: ConversionContext): Pro
|
|
|
373
376
|
const relationship = relationshipId ? context.relationships.get(relationshipId) : undefined;
|
|
374
377
|
if (!relationship || relationship.external) return "";
|
|
375
378
|
const memberPath = relationshipPath(relationship.target);
|
|
376
|
-
const
|
|
377
|
-
if (!
|
|
379
|
+
const docx = context.package;
|
|
380
|
+
if (!docx.members.has(memberPath)) {
|
|
378
381
|
context.messages.push({ type: "warning", message: `Could not find image ${memberPath}` });
|
|
379
382
|
return "";
|
|
380
383
|
}
|
|
@@ -391,8 +394,12 @@ async function renderImage(element: XmlElement, context: ConversionContext): Pro
|
|
|
391
394
|
altText,
|
|
392
395
|
async read(encoding: "base64"): Promise<string> {
|
|
393
396
|
if (encoding !== "base64") throw new Error(`Unsupported image encoding: ${encoding}`);
|
|
397
|
+
const bytes = await readImageMember(docx, memberPath);
|
|
394
398
|
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
|
|
395
399
|
},
|
|
400
|
+
readBytes(): Promise<Uint8Array> {
|
|
401
|
+
return readImageMember(docx, memberPath);
|
|
402
|
+
},
|
|
396
403
|
};
|
|
397
404
|
const converted = await context.convertImage.convert(image);
|
|
398
405
|
const attributes = Object.entries(converted)
|
|
@@ -403,6 +410,13 @@ async function renderImage(element: XmlElement, context: ConversionContext): Pro
|
|
|
403
410
|
return attributes ? `<img ${attributes} />` : "<img />";
|
|
404
411
|
}
|
|
405
412
|
|
|
413
|
+
/** Bytes of an image member already known to exist. */
|
|
414
|
+
async function readImageMember(docx: ZipPackage, memberPath: string): Promise<Uint8Array> {
|
|
415
|
+
const bytes = await docx.readBytes(memberPath);
|
|
416
|
+
if (!bytes) throw new Error(`Could not find image ${memberPath}`);
|
|
417
|
+
return bytes;
|
|
418
|
+
}
|
|
419
|
+
|
|
406
420
|
function renderFootnoteReference(element: XmlElement, context: ConversionContext): string {
|
|
407
421
|
const id = attribute(element, "w:id");
|
|
408
422
|
if (!id || !context.footnotes.has(id)) return "";
|
|
@@ -655,21 +669,21 @@ function defaultImageConverter(): ImageConverter {
|
|
|
655
669
|
/** Convert a DOCX buffer or path to mammoth-compatible HTML. */
|
|
656
670
|
export async function convertToHtml(input: DocxInput, options: ConvertToHtmlOptions = {}): Promise<DocxResult> {
|
|
657
671
|
const bytes = "buffer" in input && input.buffer ? input.buffer : await fs.readFile(input.path);
|
|
658
|
-
const
|
|
659
|
-
const documentXml =
|
|
672
|
+
const docx = await ZipPackage.open(bytes);
|
|
673
|
+
const documentXml = await docx.readText("word/document.xml");
|
|
660
674
|
if (!documentXml) throw new Error("Invalid DOCX: missing word/document.xml");
|
|
661
675
|
const context: ConversionContext = {
|
|
662
|
-
|
|
663
|
-
relationships: parseRelationships(
|
|
664
|
-
contentTypes: parseContentTypes(
|
|
665
|
-
styles: parseStyles(
|
|
666
|
-
numbering: parseNumbering(
|
|
676
|
+
package: docx,
|
|
677
|
+
relationships: parseRelationships(await docx.readText("word/_rels/document.xml.rels")),
|
|
678
|
+
contentTypes: parseContentTypes(await docx.readText("[Content_Types].xml")),
|
|
679
|
+
styles: parseStyles(await docx.readText("word/styles.xml")),
|
|
680
|
+
numbering: parseNumbering(await docx.readText("word/numbering.xml")),
|
|
667
681
|
messages: [],
|
|
668
682
|
warnedStyles: new Set(),
|
|
669
683
|
customStyles: parseCustomStyles(options.styleMap),
|
|
670
684
|
includeDefaultStyleMap: options.includeDefaultStyleMap !== false,
|
|
671
685
|
convertImage: options.convertImage ?? defaultImageConverter(),
|
|
672
|
-
footnotes: parseFootnotes(
|
|
686
|
+
footnotes: parseFootnotes(await docx.readText("word/footnotes.xml")),
|
|
673
687
|
usedFootnotes: [],
|
|
674
688
|
footnoteOrdinals: new Map(),
|
|
675
689
|
};
|