@f5-sales-demo/pi-utils 19.51.2
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/package.json +61 -0
- package/src/abortable.ts +86 -0
- package/src/async.ts +50 -0
- package/src/cli.ts +432 -0
- package/src/color.ts +204 -0
- package/src/dirs.ts +480 -0
- package/src/env.ts +108 -0
- package/src/format.ts +106 -0
- package/src/frontmatter.ts +118 -0
- package/src/fs-error.ts +56 -0
- package/src/glob.ts +189 -0
- package/src/hook-fetch.ts +30 -0
- package/src/i18n.test.ts +213 -0
- package/src/i18n.ts +90 -0
- package/src/index.ts +63 -0
- package/src/json.ts +10 -0
- package/src/logger.ts +204 -0
- package/src/mermaid-ascii.ts +31 -0
- package/src/mermaid-color.ts +231 -0
- package/src/mime.ts +159 -0
- package/src/models-yml.ts +162 -0
- package/src/peek-file.ts +114 -0
- package/src/postmortem.ts +197 -0
- package/src/procmgr.ts +326 -0
- package/src/prompt.ts +412 -0
- package/src/ptree.ts +426 -0
- package/src/ring.ts +169 -0
- package/src/snowflake.ts +136 -0
- package/src/stream.ts +394 -0
- package/src/tab-spacing.ts +312 -0
- package/src/temp.ts +77 -0
- package/src/type-guards.ts +11 -0
- package/src/which.ts +232 -0
- package/src/xcsh-context-paths.ts +23 -0
- package/src/xcsh-context-resolver.ts +333 -0
- package/src/xcsh-env-names.ts +69 -0
package/src/color.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color manipulation utilities for hex colors.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { hexToHsv, hsvToHex } from "@f5xc-salesdemos/pi-utils";
|
|
7
|
+
*
|
|
8
|
+
* // Work with HSV directly
|
|
9
|
+
*
|
|
10
|
+
* // Or work with HSV directly
|
|
11
|
+
* const hsv = hexToHsv("#4ade80");
|
|
12
|
+
* hsv.h = (hsv.h + 90) % 360;
|
|
13
|
+
* const newHex = hsvToHex(hsv);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface HSV {
|
|
18
|
+
/** Hue in degrees (0-360) */
|
|
19
|
+
h: number;
|
|
20
|
+
/** Saturation (0-1) */
|
|
21
|
+
s: number;
|
|
22
|
+
/** Value/brightness (0-1) */
|
|
23
|
+
v: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RGB {
|
|
27
|
+
/** Red (0-255) */
|
|
28
|
+
r: number;
|
|
29
|
+
/** Green (0-255) */
|
|
30
|
+
g: number;
|
|
31
|
+
/** Blue (0-255) */
|
|
32
|
+
b: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse a hex color string to RGB.
|
|
37
|
+
* Supports #RGB, #RRGGBB formats.
|
|
38
|
+
*/
|
|
39
|
+
export function hexToRgb(hex: string): RGB {
|
|
40
|
+
const h = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
41
|
+
if (h.length === 3) {
|
|
42
|
+
return {
|
|
43
|
+
r: parseInt(h[0] + h[0], 16),
|
|
44
|
+
g: parseInt(h[1] + h[1], 16),
|
|
45
|
+
b: parseInt(h[2] + h[2], 16),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
r: parseInt(h.slice(0, 2), 16),
|
|
50
|
+
g: parseInt(h.slice(2, 4), 16),
|
|
51
|
+
b: parseInt(h.slice(4, 6), 16),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Convert RGB to hex color string.
|
|
57
|
+
*/
|
|
58
|
+
export function rgbToHex(rgb: RGB): string {
|
|
59
|
+
const toHex = (n: number) =>
|
|
60
|
+
Math.max(0, Math.min(255, Math.round(n)))
|
|
61
|
+
.toString(16)
|
|
62
|
+
.padStart(2, "0");
|
|
63
|
+
return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Convert RGB to HSV.
|
|
68
|
+
*/
|
|
69
|
+
export function rgbToHsv(rgb: RGB): HSV {
|
|
70
|
+
const r = rgb.r / 255;
|
|
71
|
+
const g = rgb.g / 255;
|
|
72
|
+
const b = rgb.b / 255;
|
|
73
|
+
|
|
74
|
+
const max = Math.max(r, g, b);
|
|
75
|
+
const min = Math.min(r, g, b);
|
|
76
|
+
const d = max - min;
|
|
77
|
+
|
|
78
|
+
let h = 0;
|
|
79
|
+
if (d !== 0) {
|
|
80
|
+
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
81
|
+
else if (max === g) h = ((b - r) / d + 2) / 6;
|
|
82
|
+
else h = ((r - g) / d + 4) / 6;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
h: h * 360,
|
|
87
|
+
s: max === 0 ? 0 : d / max,
|
|
88
|
+
v: max,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Convert HSV to RGB.
|
|
94
|
+
*/
|
|
95
|
+
export function hsvToRgb(hsv: HSV): RGB {
|
|
96
|
+
const { s, v } = hsv;
|
|
97
|
+
const h = ((hsv.h % 360) + 360) % 360; // Normalize to 0-360
|
|
98
|
+
|
|
99
|
+
const i = Math.floor(h / 60);
|
|
100
|
+
const f = h / 60 - i;
|
|
101
|
+
const p = v * (1 - s);
|
|
102
|
+
const q = v * (1 - f * s);
|
|
103
|
+
const t = v * (1 - (1 - f) * s);
|
|
104
|
+
|
|
105
|
+
let r: number, g: number, b: number;
|
|
106
|
+
switch (i % 6) {
|
|
107
|
+
case 0:
|
|
108
|
+
r = v;
|
|
109
|
+
g = t;
|
|
110
|
+
b = p;
|
|
111
|
+
break;
|
|
112
|
+
case 1:
|
|
113
|
+
r = q;
|
|
114
|
+
g = v;
|
|
115
|
+
b = p;
|
|
116
|
+
break;
|
|
117
|
+
case 2:
|
|
118
|
+
r = p;
|
|
119
|
+
g = v;
|
|
120
|
+
b = t;
|
|
121
|
+
break;
|
|
122
|
+
case 3:
|
|
123
|
+
r = p;
|
|
124
|
+
g = q;
|
|
125
|
+
b = v;
|
|
126
|
+
break;
|
|
127
|
+
case 4:
|
|
128
|
+
r = t;
|
|
129
|
+
g = p;
|
|
130
|
+
b = v;
|
|
131
|
+
break;
|
|
132
|
+
default:
|
|
133
|
+
r = v;
|
|
134
|
+
g = p;
|
|
135
|
+
b = q;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
r: Math.round(r * 255),
|
|
141
|
+
g: Math.round(g * 255),
|
|
142
|
+
b: Math.round(b * 255),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Convert hex color to HSV.
|
|
148
|
+
*/
|
|
149
|
+
export function hexToHsv(hex: string): HSV {
|
|
150
|
+
return rgbToHsv(hexToRgb(hex));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Convert HSV to hex color.
|
|
155
|
+
*/
|
|
156
|
+
export function hsvToHex(hsv: HSV): string {
|
|
157
|
+
return rgbToHex(hsvToRgb(hsv));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Shift the hue of a hex color by a given number of degrees.
|
|
162
|
+
*/
|
|
163
|
+
export function shiftHue(hex: string, degrees: number): string {
|
|
164
|
+
const hsv = hexToHsv(hex);
|
|
165
|
+
hsv.h = (hsv.h + degrees) % 360;
|
|
166
|
+
if (hsv.h < 0) hsv.h += 360;
|
|
167
|
+
return hsvToHex(hsv);
|
|
168
|
+
}
|
|
169
|
+
export interface HSVAdjustment {
|
|
170
|
+
/** Hue shift in degrees (additive) */
|
|
171
|
+
h?: number;
|
|
172
|
+
/** Saturation multiplier */
|
|
173
|
+
s?: number;
|
|
174
|
+
/** Value/brightness multiplier */
|
|
175
|
+
v?: number;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Adjust HSV components of a hex color.
|
|
180
|
+
*
|
|
181
|
+
* @param hex - Hex color string (#RGB or #RRGGBB)
|
|
182
|
+
* @param adj - Adjustments: h is additive degrees, s and v are multipliers
|
|
183
|
+
* @returns New hex color string
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* // Shift hue +60°, reduce saturation to 71%
|
|
188
|
+
* adjustHsv("#00ff88", { h: 60, s: 0.71 }) // "#4a9eff"
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export function adjustHsv(hex: string, adj: HSVAdjustment): string {
|
|
192
|
+
const hsv = hexToHsv(hex);
|
|
193
|
+
if (adj.h !== undefined) {
|
|
194
|
+
hsv.h = (hsv.h + adj.h) % 360;
|
|
195
|
+
if (hsv.h < 0) hsv.h += 360;
|
|
196
|
+
}
|
|
197
|
+
if (adj.s !== undefined) {
|
|
198
|
+
hsv.s = Math.max(0, Math.min(1, hsv.s * adj.s));
|
|
199
|
+
}
|
|
200
|
+
if (adj.v !== undefined) {
|
|
201
|
+
hsv.v = Math.max(0, Math.min(1, hsv.v * adj.v));
|
|
202
|
+
}
|
|
203
|
+
return hsvToHex(hsv);
|
|
204
|
+
}
|
package/src/dirs.ts
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized path helpers for xcsh config directories.
|
|
3
|
+
*
|
|
4
|
+
* Uses PI_CONFIG_DIR (default ".xcsh") for the config root and
|
|
5
|
+
* PI_CODING_AGENT_DIR to override the agent directory.
|
|
6
|
+
*
|
|
7
|
+
* On Linux, if XDG_DATA_HOME / XDG_STATE_HOME / XDG_CACHE_HOME environment
|
|
8
|
+
* variables are set, paths are redirected to XDG-compliant locations under
|
|
9
|
+
* $XDG_*_HOME/xcsh/. This requires running `xcsh config migrate` first to
|
|
10
|
+
* move data to the new locations. No filesystem existence checks are performed
|
|
11
|
+
* — if the env var is set, xcsh trusts that the migration has been done.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import { engines, version } from "../package.json" with { type: "json" };
|
|
18
|
+
|
|
19
|
+
/** App name (e.g. "xcsh") */
|
|
20
|
+
export const APP_NAME: string = "xcsh";
|
|
21
|
+
|
|
22
|
+
/** Config directory name (e.g. ".xcsh") */
|
|
23
|
+
export const CONFIG_DIR_NAME: string = ".xcsh";
|
|
24
|
+
|
|
25
|
+
/** Version (e.g. "1.0.0") */
|
|
26
|
+
export const VERSION: string = version;
|
|
27
|
+
|
|
28
|
+
/** Minimum Bun version */
|
|
29
|
+
export const MIN_BUN_VERSION: string = engines.bun.replace(/[^0-9.]/g, "");
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// Project directory
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* On macOS, strip /private prefix only when both paths resolve to the same location.
|
|
37
|
+
* This preserves aliases like /private/tmp -> /tmp without rewriting unrelated paths.
|
|
38
|
+
*/
|
|
39
|
+
function standardizeMacOSPath(p: string): string {
|
|
40
|
+
if (process.platform !== "darwin" || !p.startsWith("/private/")) return p;
|
|
41
|
+
const stripped = p.slice("/private".length);
|
|
42
|
+
try {
|
|
43
|
+
if (fs.realpathSync(p) === fs.realpathSync(stripped)) {
|
|
44
|
+
return stripped;
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
return p;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function resolveEquivalentPath(inputPath: string): string {
|
|
51
|
+
const resolvedPath = path.resolve(inputPath);
|
|
52
|
+
try {
|
|
53
|
+
return fs.realpathSync(resolvedPath);
|
|
54
|
+
} catch {
|
|
55
|
+
return resolvedPath;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function normalizePathForComparison(inputPath: string): string {
|
|
60
|
+
const resolvedPath = resolveEquivalentPath(inputPath);
|
|
61
|
+
return process.platform === "win32" ? resolvedPath.toLowerCase() : resolvedPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function pathIsWithin(root: string, candidate: string): boolean {
|
|
65
|
+
const normalizedRoot = normalizePathForComparison(root);
|
|
66
|
+
const normalizedCandidate = normalizePathForComparison(candidate);
|
|
67
|
+
const relative = path.relative(normalizedRoot, normalizedCandidate);
|
|
68
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function relativePathWithinRoot(root: string, candidate: string): string | null {
|
|
72
|
+
if (!pathIsWithin(root, candidate)) return null;
|
|
73
|
+
const normalizedRoot = normalizePathForComparison(root);
|
|
74
|
+
const normalizedCandidate = normalizePathForComparison(candidate);
|
|
75
|
+
const relative = path.relative(normalizedRoot, normalizedCandidate);
|
|
76
|
+
return relative || null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let projectDir = standardizeMacOSPath(process.cwd());
|
|
80
|
+
|
|
81
|
+
/** Get the project directory. */
|
|
82
|
+
export function getProjectDir(): string {
|
|
83
|
+
return projectDir;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Set the project directory. */
|
|
87
|
+
export function setProjectDir(dir: string): void {
|
|
88
|
+
projectDir = standardizeMacOSPath(path.resolve(dir));
|
|
89
|
+
process.chdir(projectDir);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Shell-tracked working directory (follows persistent-shell cd). */
|
|
93
|
+
let shellPwd = projectDir;
|
|
94
|
+
|
|
95
|
+
/** Get the shell's current working directory (may differ from Agent CWD). */
|
|
96
|
+
export function getShellPwd(): string {
|
|
97
|
+
return shellPwd;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Update the shell's tracked working directory. Does NOT change Agent CWD. */
|
|
101
|
+
export function setShellPwd(dir: string): void {
|
|
102
|
+
shellPwd = standardizeMacOSPath(path.resolve(dir));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Get the config directory name relative to home (e.g. ".xcsh" or PI_CONFIG_DIR override). */
|
|
106
|
+
export function getConfigDirName(): string {
|
|
107
|
+
return process.env.PI_CONFIG_DIR || CONFIG_DIR_NAME;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Get the config agent directory name relative to home (e.g. ".xcsh/agent" or PI_CONFIG_DIR + "/agent"). */
|
|
111
|
+
export function getConfigAgentDirName(): string {
|
|
112
|
+
return `${getConfigDirName()}/agent`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// =============================================================================
|
|
116
|
+
// DirResolver — cached, XDG-aware path resolution
|
|
117
|
+
// =============================================================================
|
|
118
|
+
|
|
119
|
+
type XdgCategory = "data" | "state" | "cache";
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Resolves and caches all xcsh directory paths. On Linux, when XDG environment
|
|
123
|
+
* variables are set, paths are redirected under $XDG_*_HOME/xcsh/. A new
|
|
124
|
+
* instance is created whenever the agent directory changes, which naturally
|
|
125
|
+
* invalidates all cached paths.
|
|
126
|
+
*/
|
|
127
|
+
class DirResolver {
|
|
128
|
+
readonly configRoot: string;
|
|
129
|
+
readonly agentDir: string;
|
|
130
|
+
|
|
131
|
+
// Per-category base dirs. Without XDG, all three equal configRoot / agentDir.
|
|
132
|
+
// With XDG on Linux, they point to $XDG_*_HOME/xcsh/.
|
|
133
|
+
readonly #rootDirs: Record<XdgCategory, string>;
|
|
134
|
+
readonly #agentDirs: Record<XdgCategory, string>;
|
|
135
|
+
|
|
136
|
+
readonly #rootCache = new Map<string, string>();
|
|
137
|
+
readonly #agentCache = new Map<string, string>();
|
|
138
|
+
|
|
139
|
+
constructor(agentDirOverride?: string) {
|
|
140
|
+
this.configRoot = path.join(os.homedir(), getConfigDirName());
|
|
141
|
+
|
|
142
|
+
const defaultAgent = path.join(this.configRoot, "agent");
|
|
143
|
+
this.agentDir = agentDirOverride ? path.resolve(agentDirOverride) : defaultAgent;
|
|
144
|
+
const isDefault = this.agentDir === defaultAgent;
|
|
145
|
+
|
|
146
|
+
// XDG is a Linux convention. On other platforms, or for non-default
|
|
147
|
+
// profiles, all categories resolve to the legacy paths.
|
|
148
|
+
let xdgData: string | undefined;
|
|
149
|
+
let xdgState: string | undefined;
|
|
150
|
+
let xdgCache: string | undefined;
|
|
151
|
+
if ((process.platform === "linux" || process.platform === "darwin") && isDefault) {
|
|
152
|
+
const resolveIf = (envVar: string) => {
|
|
153
|
+
const value = process.env[envVar];
|
|
154
|
+
if (value) {
|
|
155
|
+
try {
|
|
156
|
+
const joined = path.join(value, APP_NAME);
|
|
157
|
+
if (fs.existsSync(joined)) {
|
|
158
|
+
return joined;
|
|
159
|
+
}
|
|
160
|
+
} catch {}
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
};
|
|
164
|
+
xdgData = resolveIf("XDG_DATA_HOME");
|
|
165
|
+
xdgState = resolveIf("XDG_STATE_HOME");
|
|
166
|
+
xdgCache = resolveIf("XDG_CACHE_HOME");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.#rootDirs = {
|
|
170
|
+
data: xdgData ?? this.configRoot,
|
|
171
|
+
state: xdgState ?? this.configRoot,
|
|
172
|
+
cache: xdgCache ?? this.configRoot,
|
|
173
|
+
};
|
|
174
|
+
// XDG flattens the agent/ prefix: ~/.xcsh/agent/sessions → $XDG_DATA_HOME/xcsh/sessions
|
|
175
|
+
this.#agentDirs = {
|
|
176
|
+
data: xdgData ?? this.agentDir,
|
|
177
|
+
state: xdgState ?? this.agentDir,
|
|
178
|
+
cache: xdgCache ?? this.agentDir,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Config-root subdirectory, with optional XDG override. */
|
|
183
|
+
rootSubdir(subdir: string, xdg?: XdgCategory): string {
|
|
184
|
+
const cached = this.#rootCache.get(subdir);
|
|
185
|
+
if (cached) return cached;
|
|
186
|
+
const base = xdg ? this.#rootDirs[xdg] : this.configRoot;
|
|
187
|
+
const result = path.join(base, subdir);
|
|
188
|
+
this.#rootCache.set(subdir, result);
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Agent subdirectory, with optional XDG override. */
|
|
193
|
+
agentSubdir(userAgentDir: string | undefined, subdir: string, xdg?: XdgCategory): string {
|
|
194
|
+
if (!userAgentDir || userAgentDir === this.agentDir) {
|
|
195
|
+
const cached = this.#agentCache.get(subdir);
|
|
196
|
+
if (cached) return cached;
|
|
197
|
+
const base = xdg ? this.#agentDirs[xdg] : this.agentDir;
|
|
198
|
+
const result = path.join(base, subdir);
|
|
199
|
+
this.#agentCache.set(subdir, result);
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
return path.join(userAgentDir, subdir);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let dirs = new DirResolver(process.env.PI_CODING_AGENT_DIR);
|
|
207
|
+
|
|
208
|
+
// =============================================================================
|
|
209
|
+
// Root directories
|
|
210
|
+
// =============================================================================
|
|
211
|
+
|
|
212
|
+
/** Get the config root directory (~/.xcsh). */
|
|
213
|
+
export function getConfigRootDir(): string {
|
|
214
|
+
return dirs.configRoot;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Set the coding agent directory. Creates a fresh resolver, invalidating all cached paths. */
|
|
218
|
+
export function setAgentDir(dir: string): void {
|
|
219
|
+
dirs = new DirResolver(dir);
|
|
220
|
+
process.env.PI_CODING_AGENT_DIR = dir;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Get the agent config directory (~/.xcsh/agent). */
|
|
224
|
+
export function getAgentDir(): string {
|
|
225
|
+
return dirs.agentDir;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Get the project-local config directory (.xcsh). */
|
|
229
|
+
export function getProjectAgentDir(cwd: string = getProjectDir()): string {
|
|
230
|
+
return path.join(cwd, CONFIG_DIR_NAME);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// =============================================================================
|
|
234
|
+
// Config-root subdirectories (~/.xcsh/*)
|
|
235
|
+
// =============================================================================
|
|
236
|
+
|
|
237
|
+
/** Get the reports directory (~/.xcsh/reports). */
|
|
238
|
+
export function getReportsDir(): string {
|
|
239
|
+
return dirs.rootSubdir("reports", "state");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Get the logs directory (~/.xcsh/logs). */
|
|
243
|
+
export function getLogsDir(): string {
|
|
244
|
+
return dirs.rootSubdir("logs", "state");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Get the path to a dated log file (~/.xcsh/logs/xcsh.YYYY-MM-DD.log). */
|
|
248
|
+
export function getLogPath(date = new Date()): string {
|
|
249
|
+
return path.join(getLogsDir(), `${APP_NAME}.${date.toISOString().slice(0, 10)}.log`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Get the plugins directory (~/.xcsh/plugins). */
|
|
253
|
+
export function getPluginsDir(): string {
|
|
254
|
+
return dirs.rootSubdir("plugins", "data");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Where npm installs packages (~/.xcsh/plugins/node_modules). */
|
|
258
|
+
export function getPluginsNodeModules(): string {
|
|
259
|
+
return path.join(getPluginsDir(), "node_modules");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Plugin manifest (~/.xcsh/plugins/package.json). */
|
|
263
|
+
export function getPluginsPackageJson(): string {
|
|
264
|
+
return path.join(getPluginsDir(), "package.json");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Plugin lock file (~/.xcsh/plugins/xcsh-plugins.lock.json). */
|
|
268
|
+
export function getPluginsLockfile(): string {
|
|
269
|
+
return path.join(getPluginsDir(), "xcsh-plugins.lock.json");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Get the remote mount directory (~/.xcsh/remote). */
|
|
273
|
+
export function getRemoteDir(): string {
|
|
274
|
+
return dirs.rootSubdir("remote", "data");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Get the SSH control socket directory (~/.xcsh/ssh-control). */
|
|
278
|
+
export function getSshControlDir(): string {
|
|
279
|
+
return dirs.rootSubdir("ssh-control", "state");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Get the remote host info directory (~/.xcsh/remote-host). */
|
|
283
|
+
export function getRemoteHostDir(): string {
|
|
284
|
+
return dirs.rootSubdir("remote-host", "data");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Get the managed Python venv directory (~/.xcsh/python-env). */
|
|
288
|
+
export function getPythonEnvDir(): string {
|
|
289
|
+
return dirs.rootSubdir("python-env", "data");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Get the puppeteer sandbox directory (~/.xcsh/puppeteer). */
|
|
293
|
+
export function getPuppeteerDir(): string {
|
|
294
|
+
return dirs.rootSubdir("puppeteer", "cache");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Get the worktree base directory (~/.xcsh/wt). */
|
|
298
|
+
export function getWorktreeBaseDir(): string {
|
|
299
|
+
return dirs.rootSubdir("wt", "data");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Get the path to a worktree directory (~/.xcsh/wt/<project>/<id>). */
|
|
303
|
+
export function getWorktreeDir(encodedProject: string, id: string): string {
|
|
304
|
+
return path.join(getWorktreeBaseDir(), encodedProject, id);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Get the GPU cache path (~/.xcsh/gpu_cache.json). */
|
|
308
|
+
export function getGpuCachePath(): string {
|
|
309
|
+
return dirs.rootSubdir("gpu_cache.json", "cache");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Get the natives directory (~/.xcsh/natives). */
|
|
313
|
+
export function getNativesDir(): string {
|
|
314
|
+
return dirs.rootSubdir("natives", "cache");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Get the stats database path (~/.xcsh/stats.db). */
|
|
318
|
+
export function getStatsDbPath(): string {
|
|
319
|
+
return dirs.rootSubdir("stats.db", "data");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// =============================================================================
|
|
323
|
+
// Agent subdirectories (~/.xcsh/agent/*)
|
|
324
|
+
// =============================================================================
|
|
325
|
+
|
|
326
|
+
/** Get the path to agent.db (SQLite database for settings and auth storage). */
|
|
327
|
+
export function getAgentDbPath(agentDir?: string): string {
|
|
328
|
+
return dirs.agentSubdir(agentDir, "agent.db", "data");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Get the path to history.db (SQLite database for session history). */
|
|
332
|
+
export function getHistoryDbPath(agentDir?: string): string {
|
|
333
|
+
return dirs.agentSubdir(agentDir, "history.db", "data");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Get the path to models.db (model cache database). */
|
|
337
|
+
export function getModelDbPath(agentDir?: string): string {
|
|
338
|
+
return dirs.agentSubdir(agentDir, "models.db", "data");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Get the directory path for the shared search DB state (~/.xcsh/agent/search-db). */
|
|
342
|
+
export function getSearchDbDir(agentDir?: string): string {
|
|
343
|
+
return dirs.agentSubdir(agentDir, "search-db", "data");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Get the sessions directory (~/.xcsh/agent/sessions). */
|
|
347
|
+
export function getSessionsDir(agentDir?: string): string {
|
|
348
|
+
return dirs.agentSubdir(agentDir, "sessions", "data");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Get the content-addressed blob store directory (~/.xcsh/agent/blobs). */
|
|
352
|
+
export function getBlobsDir(agentDir?: string): string {
|
|
353
|
+
return dirs.agentSubdir(agentDir, "blobs", "data");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Get the custom themes directory (~/.xcsh/agent/themes). */
|
|
357
|
+
export function getCustomThemesDir(agentDir?: string): string {
|
|
358
|
+
return dirs.agentSubdir(agentDir, "themes");
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Get the tools directory (~/.xcsh/agent/tools). */
|
|
362
|
+
export function getToolsDir(agentDir?: string): string {
|
|
363
|
+
return dirs.agentSubdir(agentDir, "tools");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Get the slash commands directory (~/.xcsh/agent/commands). */
|
|
367
|
+
export function getCommandsDir(agentDir?: string): string {
|
|
368
|
+
return dirs.agentSubdir(agentDir, "commands");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Get the prompts directory (~/.xcsh/agent/prompts). */
|
|
372
|
+
export function getPromptsDir(agentDir?: string): string {
|
|
373
|
+
return dirs.agentSubdir(agentDir, "prompts");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** Get the user-level Python modules directory (~/.xcsh/agent/modules). */
|
|
377
|
+
export function getAgentModulesDir(agentDir?: string): string {
|
|
378
|
+
return dirs.agentSubdir(agentDir, "modules");
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Get the memories directory (~/.xcsh/agent/memories). */
|
|
382
|
+
export function getMemoriesDir(agentDir?: string): string {
|
|
383
|
+
return dirs.agentSubdir(agentDir, "memories", "state");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Get the terminal sessions directory (~/.xcsh/agent/terminal-sessions). */
|
|
387
|
+
export function getTerminalSessionsDir(agentDir?: string): string {
|
|
388
|
+
return dirs.agentSubdir(agentDir, "terminal-sessions", "state");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Get the crash log path (~/.xcsh/agent/xcsh-crash.log). */
|
|
392
|
+
export function getCrashLogPath(agentDir?: string): string {
|
|
393
|
+
return dirs.agentSubdir(agentDir, "xcsh-crash.log", "state");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Get the debug log path (~/.xcsh/agent/xcsh-debug.log). */
|
|
397
|
+
export function getDebugLogPath(agentDir?: string): string {
|
|
398
|
+
return dirs.agentSubdir(agentDir, `${APP_NAME}-debug.log`, "state");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// =============================================================================
|
|
402
|
+
// Project subdirectories (.xcsh/*)
|
|
403
|
+
// =============================================================================
|
|
404
|
+
|
|
405
|
+
/** Get the project-level Python modules directory (.xcsh/modules). */
|
|
406
|
+
export function getProjectModulesDir(cwd: string = getProjectDir()): string {
|
|
407
|
+
return path.join(getProjectAgentDir(cwd), "modules");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Get the project-level prompts directory (.xcsh/prompts). */
|
|
411
|
+
export function getProjectPromptsDir(cwd: string = getProjectDir()): string {
|
|
412
|
+
return path.join(getProjectAgentDir(cwd), "prompts");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Get the project-level plugin overrides path (.xcsh/plugin-overrides.json). */
|
|
416
|
+
export function getProjectPluginOverridesPath(cwd: string = getProjectDir()): string {
|
|
417
|
+
return path.join(getProjectAgentDir(cwd), "plugin-overrides.json");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// =============================================================================
|
|
421
|
+
// MCP config paths
|
|
422
|
+
// =============================================================================
|
|
423
|
+
|
|
424
|
+
/** Get the primary MCP config file path (first candidate). */
|
|
425
|
+
export function getMCPConfigPath(scope: "user" | "project", cwd: string = getProjectDir()): string {
|
|
426
|
+
if (scope === "user") {
|
|
427
|
+
return path.join(getAgentDir(), "mcp.json");
|
|
428
|
+
}
|
|
429
|
+
return path.join(getProjectAgentDir(cwd), "mcp.json");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Get the SSH config file path. */
|
|
433
|
+
export function getSSHConfigPath(scope: "user" | "project", cwd: string = getProjectDir()): string {
|
|
434
|
+
if (scope === "user") {
|
|
435
|
+
return path.join(getAgentDir(), "ssh.json");
|
|
436
|
+
}
|
|
437
|
+
return path.join(getProjectAgentDir(cwd), "ssh.json");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// =============================================================================
|
|
441
|
+
// F5 Distributed Cloud (F5 XC) context paths
|
|
442
|
+
// =============================================================================
|
|
443
|
+
|
|
444
|
+
const XCSH_DIR_NAME = "xcsh";
|
|
445
|
+
|
|
446
|
+
/** Get the F5 XC config directory ($XDG_CONFIG_HOME/xcsh or ~/.config/xcsh). */
|
|
447
|
+
export function getXCSHConfigDir(): string {
|
|
448
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
449
|
+
return path.join(xdgConfig, XCSH_DIR_NAME);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Get the F5 XC contexts directory (~/.config/xcsh/contexts). */
|
|
453
|
+
export function getXCSHContextsDir(): string {
|
|
454
|
+
return path.join(getXCSHConfigDir(), "contexts");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** Get the path to the active context indicator file (~/.config/xcsh/active_context). */
|
|
458
|
+
export function getXCSHActiveContextPath(): string {
|
|
459
|
+
return path.join(getXCSHConfigDir(), "active_context");
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Get the path to a specific context JSON file (~/.config/xcsh/contexts/<name>.json). */
|
|
463
|
+
export function getXCSHContextPath(name: string): string {
|
|
464
|
+
return path.join(getXCSHContextsDir(), `${name}.json`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** Get the project-local F5 XC contexts directory (.xcsh/contexts under cwd). */
|
|
468
|
+
export function getLocalXCSHContextsDir(cwd: string = getProjectDir()): string {
|
|
469
|
+
return path.join(cwd, CONFIG_DIR_NAME, "contexts");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** Get the project-local active context pointer (.xcsh/contexts/active_context). */
|
|
473
|
+
export function getLocalXCSHActiveContextPath(cwd: string = getProjectDir()): string {
|
|
474
|
+
return path.join(getLocalXCSHContextsDir(cwd), "active_context");
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Get the path to a project-local context JSON (.xcsh/contexts/<name>.json). */
|
|
478
|
+
export function getLocalXCSHContextPath(name: string, cwd: string = getProjectDir()): string {
|
|
479
|
+
return path.join(getLocalXCSHContextsDir(cwd), `${name}.json`);
|
|
480
|
+
}
|