@gajae-code/utils 0.16.4 → 0.16.6
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/types/dirs.d.ts +5 -1
- package/package.json +2 -2
- package/src/crash-redaction.ts +29 -2
- package/src/dirs.ts +45 -9
package/dist/types/dirs.d.ts
CHANGED
|
@@ -82,6 +82,8 @@ export declare function getTrustedConfigRootDir(): string;
|
|
|
82
82
|
export declare function setAgentDir(dir: string): void;
|
|
83
83
|
/** Get the agent config directory (~/.gjc/agent). */
|
|
84
84
|
export declare function getAgentDir(): string;
|
|
85
|
+
/** Resolver-owned profile classification, stable across HOME refreshes. */
|
|
86
|
+
export declare function getAgentProfileAuthority(): "default" | "custom";
|
|
85
87
|
export declare function getConfigDirName(): string;
|
|
86
88
|
/**
|
|
87
89
|
* Join a file under the provenance-checked agent directory, never the XDG
|
|
@@ -112,6 +114,8 @@ export declare function getLogPath(date?: Date): string;
|
|
|
112
114
|
* form — XDG semantics are preserved.
|
|
113
115
|
*/
|
|
114
116
|
export declare function getPluginsDir(home?: string): string;
|
|
117
|
+
/** Get the default-profile plugin directory without ambient profile authority. */
|
|
118
|
+
export declare function getDefaultPluginsDir(): string;
|
|
115
119
|
/** Where npm installs packages (~/.gjc/plugins/node_modules). */
|
|
116
120
|
export declare function getPluginsNodeModules(): string;
|
|
117
121
|
/** Plugin manifest (~/.gjc/plugins/package.json). */
|
|
@@ -216,4 +220,4 @@ export declare function getProjectPluginOverridesPath(cwd?: string): string;
|
|
|
216
220
|
*/
|
|
217
221
|
export declare function getMCPConfigPath(scope: "user" | "project", cwd?: string, agentDir?: string): string;
|
|
218
222
|
/** Get the SSH config file path. */
|
|
219
|
-
export declare function getSSHConfigPath(scope: "user" | "project", cwd?: string): string;
|
|
223
|
+
export declare function getSSHConfigPath(scope: "user" | "project", cwd?: string, agentDir?: string): string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/utils",
|
|
4
|
-
"version": "0.16.
|
|
4
|
+
"version": "0.16.6",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"fmt": "biome format --write ."
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@gajae-code/natives": "0.16.
|
|
34
|
+
"@gajae-code/natives": "0.16.6",
|
|
35
35
|
"beautiful-mermaid": "^1.1.3",
|
|
36
36
|
"handlebars": "^4.7.9",
|
|
37
37
|
"winston": "^3.19.0",
|
package/src/crash-redaction.ts
CHANGED
|
@@ -25,13 +25,37 @@ export function redactCrashSecrets(text: string): string {
|
|
|
25
25
|
redacted = redacted.replace(/\bgh[opsur]_[A-Za-z0-9]{16,}\b/g, "«redacted-github-token»");
|
|
26
26
|
redacted = redacted.replace(/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, "«redacted-github-token»");
|
|
27
27
|
redacted = redacted.replace(/\bxox[baprs]-[A-Za-z0-9-]{8,}\b/g, "«redacted-slack-token»");
|
|
28
|
+
// A PEM block carries the key material itself, so it is redacted whole rather
|
|
29
|
+
// than line by line. It runs before the narrower rules because they would
|
|
30
|
+
// otherwise chew on the base64 body and leave a truncated key behind.
|
|
31
|
+
redacted = redacted.replace(
|
|
32
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g,
|
|
33
|
+
"«redacted-private-key»",
|
|
34
|
+
);
|
|
35
|
+
// Google API keys are a fixed 39-character shape that carries no label of its
|
|
36
|
+
// own, so the labeled-value rule below never sees one.
|
|
37
|
+
redacted = redacted.replace(
|
|
38
|
+
/(?<![A-Za-z0-9_-])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9_-])/g,
|
|
39
|
+
"«redacted-google-api-key»",
|
|
40
|
+
);
|
|
41
|
+
// Basic-auth credentials embedded in a URL. Scheme and host stay readable
|
|
42
|
+
// because they are the diagnostic value; only the userinfo is dropped. The
|
|
43
|
+
// scheme repetition is bounded: an unbounded `[a-z0-9+.-]*` in front of the
|
|
44
|
+
// literal `://` re-tries every prefix of a long alphabetic run, which is
|
|
45
|
+
// quadratic in input length and costs ~10s on a 200 KB crash body.
|
|
46
|
+
redacted = redacted.replace(
|
|
47
|
+
/(?<![A-Za-z0-9+.-])([a-z][a-z0-9+.-]{0,15}:\/\/)[^/\s:@]{1,256}:[^/\s@]{1,256}@/gi,
|
|
48
|
+
"$1«redacted-url-credential»@",
|
|
49
|
+
);
|
|
28
50
|
// AKIA is the long-term access key id; ASIA is the temporary/STS one, which is
|
|
29
|
-
// the shape that actually shows up in a crashed request.
|
|
51
|
+
// the shape that actually shows up in a crashed request. ABIA (bearer) and
|
|
52
|
+
// ACCA (context) complete the set the session-import scrubber already lists.
|
|
53
|
+
// The id alone is not
|
|
30
54
|
// the credential: an STS payload carries `SecretAccessKey` and `SessionToken`
|
|
31
55
|
// alongside it, so the labeled-value rule below must name both. `secret_key`
|
|
32
56
|
// does not match `SecretAccessKey` (the canonical field has `Access` in the
|
|
33
57
|
// middle), and `access_token` does not match `SessionToken`.
|
|
34
|
-
redacted = redacted.replace(/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, "«redacted-aws-key»");
|
|
58
|
+
redacted = redacted.replace(/\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b/g, "«redacted-aws-key»");
|
|
35
59
|
redacted = redacted.replace(
|
|
36
60
|
/(?<![A-Za-z0-9_])(["']?(?:api[_-]?key|apikey|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|client[_-]?secret|secret[_-]?key|secret[_-]?access[_-]?key|password|passwd|authorization)["']?\s*[=:]\s*["']?)[^\s"',;}\]]{8,}/gi,
|
|
37
61
|
"$1«redacted»",
|
|
@@ -47,5 +71,8 @@ export const CRASH_REDACTION_MARKERS: readonly string[] = [
|
|
|
47
71
|
"«redacted-github-token»",
|
|
48
72
|
"«redacted-slack-token»",
|
|
49
73
|
"«redacted-aws-key»",
|
|
74
|
+
"«redacted-private-key»",
|
|
75
|
+
"«redacted-google-api-key»",
|
|
76
|
+
"«redacted-url-credential»",
|
|
50
77
|
"«redacted»",
|
|
51
78
|
];
|
package/src/dirs.ts
CHANGED
|
@@ -438,9 +438,11 @@ class DirResolver {
|
|
|
438
438
|
// Per-category base dirs. Without XDG, all three equal configRoot / agentDir.
|
|
439
439
|
// With XDG on Linux, they point to $XDG_*_HOME/gjc/.
|
|
440
440
|
#rootDirs: Record<XdgCategory, string>;
|
|
441
|
+
#defaultRootDirs: Record<XdgCategory, string>;
|
|
441
442
|
#agentDirs: Record<XdgCategory, string>;
|
|
442
443
|
|
|
443
444
|
readonly #rootCache = new Map<string, string>();
|
|
445
|
+
readonly #defaultRootCache = new Map<string, string>();
|
|
444
446
|
readonly #agentCache = new Map<string, string>();
|
|
445
447
|
|
|
446
448
|
constructor(agentDirOverride?: string, snapshot = projectEnvSnapshot()) {
|
|
@@ -463,7 +465,7 @@ class DirResolver {
|
|
|
463
465
|
// the parent reading `$XDG_STATE_HOME/gjc/python-gateway` while the child
|
|
464
466
|
// read `<agentDir>/python-gateway`. Splitting a live store in half is worse
|
|
465
467
|
// than the narrower complaint it was meant to answer.
|
|
466
|
-
const isDefault = this.agentDir === defaultAgent;
|
|
468
|
+
const isDefault = normalizePathForComparison(this.agentDir) === normalizePathForComparison(defaultAgent);
|
|
467
469
|
// That decision is then *sticky*. Recomputing it later from path shape is
|
|
468
470
|
// what let a pinned agent directory silently change storage lane when a home
|
|
469
471
|
// refresh made it coincide with the new default: `getAgentDir()` looked
|
|
@@ -471,10 +473,15 @@ class DirResolver {
|
|
|
471
473
|
this.#xdgEligible = isDefault;
|
|
472
474
|
|
|
473
475
|
this.#rootDirs = { data: this.configRoot, state: this.configRoot, cache: this.configRoot };
|
|
476
|
+
this.#defaultRootDirs = { data: this.configRoot, state: this.configRoot, cache: this.configRoot };
|
|
474
477
|
this.#agentDirs = { data: this.agentDir, state: this.agentDir, cache: this.agentDir };
|
|
475
478
|
this.refreshCategoryDirs(snapshot, isDefault);
|
|
476
479
|
}
|
|
477
480
|
|
|
481
|
+
get profileAuthority(): "default" | "custom" {
|
|
482
|
+
return this.#xdgEligible ? "default" : "custom";
|
|
483
|
+
}
|
|
484
|
+
|
|
478
485
|
/**
|
|
479
486
|
* `isDefault` decides whether the agent directory may follow `$XDG_*_HOME`.
|
|
480
487
|
*
|
|
@@ -491,7 +498,7 @@ class DirResolver {
|
|
|
491
498
|
let xdgData: string | undefined;
|
|
492
499
|
let xdgState: string | undefined;
|
|
493
500
|
let xdgCache: string | undefined;
|
|
494
|
-
if (
|
|
501
|
+
if (process.platform === "linux" || process.platform === "darwin") {
|
|
495
502
|
const resolveIf = (envVar: string) => {
|
|
496
503
|
const value = trustedValue(envVar, snapshot);
|
|
497
504
|
if (!value) return undefined;
|
|
@@ -506,15 +513,20 @@ class DirResolver {
|
|
|
506
513
|
xdgState = resolveIf("XDG_STATE_HOME");
|
|
507
514
|
xdgCache = resolveIf("XDG_CACHE_HOME");
|
|
508
515
|
}
|
|
509
|
-
this.#
|
|
516
|
+
this.#defaultRootDirs = {
|
|
510
517
|
data: xdgData ?? this.configRoot,
|
|
511
518
|
state: xdgState ?? this.configRoot,
|
|
512
519
|
cache: xdgCache ?? this.configRoot,
|
|
513
520
|
};
|
|
521
|
+
this.#rootDirs = {
|
|
522
|
+
data: isDefault ? (xdgData ?? this.configRoot) : this.configRoot,
|
|
523
|
+
state: isDefault ? (xdgState ?? this.configRoot) : this.configRoot,
|
|
524
|
+
cache: isDefault ? (xdgCache ?? this.configRoot) : this.configRoot,
|
|
525
|
+
};
|
|
514
526
|
this.#agentDirs = {
|
|
515
|
-
data: xdgData ?? this.agentDir,
|
|
516
|
-
state: xdgState ?? this.agentDir,
|
|
517
|
-
cache: xdgCache ?? this.agentDir,
|
|
527
|
+
data: isDefault ? (xdgData ?? this.agentDir) : this.agentDir,
|
|
528
|
+
state: isDefault ? (xdgState ?? this.agentDir) : this.agentDir,
|
|
529
|
+
cache: isDefault ? (xdgCache ?? this.agentDir) : this.agentDir,
|
|
518
530
|
};
|
|
519
531
|
}
|
|
520
532
|
|
|
@@ -543,6 +555,7 @@ class DirResolver {
|
|
|
543
555
|
// its path coincide with (or diverge from) the new default.
|
|
544
556
|
this.refreshCategoryDirs(this.#projectEnv, this.#xdgEligible);
|
|
545
557
|
this.#rootCache.clear();
|
|
558
|
+
this.#defaultRootCache.clear();
|
|
546
559
|
this.#agentCache.clear();
|
|
547
560
|
}
|
|
548
561
|
|
|
@@ -571,7 +584,7 @@ class DirResolver {
|
|
|
571
584
|
agentSubdir(userAgentDir: string | undefined, subdir: string, xdg?: XdgCategory): string {
|
|
572
585
|
this.refreshConfigDirOverride();
|
|
573
586
|
if (!this.#homeAvailable) throw new Error("User state is unavailable: no trustworthy home directory");
|
|
574
|
-
if (!userAgentDir || userAgentDir === this.agentDir) {
|
|
587
|
+
if (!userAgentDir || normalizePathForComparison(userAgentDir) === normalizePathForComparison(this.agentDir)) {
|
|
575
588
|
const cached = this.#agentCache.get(subdir);
|
|
576
589
|
if (cached) return cached;
|
|
577
590
|
const base = xdg ? this.#agentDirs[xdg] : this.agentDir;
|
|
@@ -582,6 +595,19 @@ class DirResolver {
|
|
|
582
595
|
return path.join(userAgentDir, subdir);
|
|
583
596
|
}
|
|
584
597
|
|
|
598
|
+
/** Config-root subdirectory for the resolver-owned default profile. */
|
|
599
|
+
rootSubdirForDefaultProfile(subdir: string, xdg?: XdgCategory): string {
|
|
600
|
+
this.refreshConfigDirOverride();
|
|
601
|
+
if (!this.#homeAvailable) throw new Error("User state is unavailable: no trustworthy home directory");
|
|
602
|
+
const cacheKey = `${xdg ?? "config"}:${subdir}`;
|
|
603
|
+
const cached = this.#defaultRootCache.get(cacheKey);
|
|
604
|
+
if (cached) return cached;
|
|
605
|
+
const base = xdg ? this.#defaultRootDirs[xdg] : this.configRoot;
|
|
606
|
+
const result = path.join(base, subdir);
|
|
607
|
+
this.#defaultRootCache.set(cacheKey, result);
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
|
|
585
611
|
get configDirName(): string {
|
|
586
612
|
return this.#configDirName;
|
|
587
613
|
}
|
|
@@ -654,6 +680,11 @@ export function getAgentDir(): string {
|
|
|
654
680
|
return dirs.agentDir;
|
|
655
681
|
}
|
|
656
682
|
|
|
683
|
+
/** Resolver-owned profile classification, stable across HOME refreshes. */
|
|
684
|
+
export function getAgentProfileAuthority(): "default" | "custom" {
|
|
685
|
+
return dirs.profileAuthority;
|
|
686
|
+
}
|
|
687
|
+
|
|
657
688
|
export function getConfigDirName(): string {
|
|
658
689
|
dirs.refreshConfigDirOverride();
|
|
659
690
|
return dirs.configDirName;
|
|
@@ -723,6 +754,11 @@ export function getPluginsDir(home?: string): string {
|
|
|
723
754
|
return dirs.rootSubdir("plugins", "data");
|
|
724
755
|
}
|
|
725
756
|
|
|
757
|
+
/** Get the default-profile plugin directory without ambient profile authority. */
|
|
758
|
+
export function getDefaultPluginsDir(): string {
|
|
759
|
+
return dirs.rootSubdirForDefaultProfile("plugins", "data");
|
|
760
|
+
}
|
|
761
|
+
|
|
726
762
|
/** Where npm installs packages (~/.gjc/plugins/node_modules). */
|
|
727
763
|
export function getPluginsNodeModules(): string {
|
|
728
764
|
return path.join(getPluginsDir(), "node_modules");
|
|
@@ -972,9 +1008,9 @@ export function getMCPConfigPath(scope: "user" | "project", cwd: string = getPro
|
|
|
972
1008
|
}
|
|
973
1009
|
|
|
974
1010
|
/** Get the SSH config file path. */
|
|
975
|
-
export function getSSHConfigPath(scope: "user" | "project", cwd: string = getProjectDir()): string {
|
|
1011
|
+
export function getSSHConfigPath(scope: "user" | "project", cwd: string = getProjectDir(), agentDir?: string): string {
|
|
976
1012
|
if (scope === "user") {
|
|
977
|
-
return path.join(getAgentDir(), "ssh.json");
|
|
1013
|
+
return path.join(agentDir ?? getAgentDir(), "ssh.json");
|
|
978
1014
|
}
|
|
979
1015
|
return path.join(getProjectAgentDir(cwd), "ssh.json");
|
|
980
1016
|
}
|