@dbx-tools/cli-service 0.9.79 → 0.9.95
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/README.md +33 -4
- package/index.ts +2 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -2
- package/lib/src/_compile.js +11 -1
- package/lib/src/_runtime.d.ts +1 -1
- package/lib/src/_runtime.js +30 -4
- package/lib/src/cli.d.ts +5 -1
- package/lib/src/cli.js +41 -5
- package/lib/src/icon.d.ts +4 -2
- package/lib/src/icon.js +56 -9
- package/lib/src/service.d.ts +9 -1
- package/lib/src/service.js +23 -4
- package/package.json +3 -3
- package/src/_compile.ts +11 -0
- package/src/_runtime.ts +30 -2
- package/src/cli.ts +52 -10
- package/src/icon.ts +72 -14
- package/src/service.ts +32 -2
package/src/icon.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { deflateSync } from "node:zlib";
|
|
2
2
|
|
|
3
3
|
const SIZE = 32;
|
|
4
|
-
const
|
|
4
|
+
const MODEL_PROXY_RECTANGLES = [
|
|
5
5
|
[4, 13, 6, 6],
|
|
6
6
|
[24, 4, 6, 6],
|
|
7
7
|
[24, 13, 6, 6],
|
|
@@ -12,35 +12,93 @@ const RECTANGLES = [
|
|
|
12
12
|
[18, 15, 6, 2],
|
|
13
13
|
[18, 24, 6, 2],
|
|
14
14
|
] as const;
|
|
15
|
+
const GRAPHITI_BOLT_RECTANGLES = [
|
|
16
|
+
[10, 5, 10, 2],
|
|
17
|
+
[17, 7, 2, 2],
|
|
18
|
+
[15, 9, 7, 2],
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
/** Plain glyphs available to identify dbx-tools system-tray services. */
|
|
22
|
+
export type ServiceTrayGlyph = "model-proxy" | "graphiti" | "lakebase";
|
|
15
23
|
|
|
16
|
-
/** Return
|
|
17
|
-
export function serviceTrayIcon(
|
|
24
|
+
/** Return a service glyph as a systray2 PNG or Windows ICO payload. */
|
|
25
|
+
export function serviceTrayIcon(
|
|
26
|
+
glyph: ServiceTrayGlyph = "model-proxy",
|
|
27
|
+
platform: NodeJS.Platform = process.platform,
|
|
28
|
+
): string {
|
|
18
29
|
const color =
|
|
19
30
|
platform === "darwin"
|
|
20
31
|
? ([0x00, 0x00, 0x00, 0xff] as const)
|
|
21
32
|
: ([0xff, 0x36, 0x21, 0xff] as const);
|
|
22
|
-
const png = encodePng(renderGlyph(color));
|
|
33
|
+
const png = encodePng(renderGlyph(glyph, color));
|
|
23
34
|
return (platform === "win32" ? encodeIco(png) : png).toString("base64");
|
|
24
35
|
}
|
|
25
36
|
|
|
26
|
-
function renderGlyph(
|
|
37
|
+
function renderGlyph(
|
|
38
|
+
glyph: ServiceTrayGlyph,
|
|
39
|
+
color: readonly [number, number, number, number],
|
|
40
|
+
): Buffer {
|
|
27
41
|
const rgba = Buffer.alloc(SIZE * SIZE * 4);
|
|
28
42
|
for (let y = 0; y < SIZE; y += 1) {
|
|
29
43
|
for (let x = 0; x < SIZE; x += 1) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
x >= left && x < left + width && y >= top && y < top + height,
|
|
34
|
-
)
|
|
35
|
-
) {
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
rgba.set(color, (y * SIZE + x) * 4);
|
|
44
|
+
const opacity = glyphOpacity(glyph, x, y);
|
|
45
|
+
if (opacity === 0) continue;
|
|
46
|
+
rgba.set([color[0], color[1], color[2], Math.round(color[3] * opacity)], (y * SIZE + x) * 4);
|
|
39
47
|
}
|
|
40
48
|
}
|
|
41
49
|
return rgba;
|
|
42
50
|
}
|
|
43
51
|
|
|
52
|
+
function glyphOpacity(glyph: ServiceTrayGlyph, x: number, y: number): number {
|
|
53
|
+
switch (glyph) {
|
|
54
|
+
case "model-proxy":
|
|
55
|
+
return modelProxyGlyphContains(x, y) ? 1 : 0;
|
|
56
|
+
case "graphiti":
|
|
57
|
+
return graphitiGlyphContains(x, y) ? 1 : 0;
|
|
58
|
+
case "lakebase":
|
|
59
|
+
return lakebaseGlyphOpacity(x, y);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function modelProxyGlyphContains(x: number, y: number): boolean {
|
|
64
|
+
return MODEL_PROXY_RECTANGLES.some(
|
|
65
|
+
([left, top, width, height]) => x >= left && x < left + width && y >= top && y < top + height,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function graphitiGlyphContains(x: number, y: number): boolean {
|
|
70
|
+
// Preserve the Graphiti bot mark through its bolt, antennae, outlined head, and ringed eyes.
|
|
71
|
+
const bolt = GRAPHITI_BOLT_RECTANGLES.some(
|
|
72
|
+
([left, top, width, height]) => x >= left && x < left + width && y >= top && y < top + height,
|
|
73
|
+
);
|
|
74
|
+
const head =
|
|
75
|
+
(y >= 10 && y < 13 && x >= 6 && x < 26) ||
|
|
76
|
+
(y >= 13 && y < 25 && x >= 4 && x < 7) ||
|
|
77
|
+
(y >= 13 && y < 25 && x >= 25 && x < 28) ||
|
|
78
|
+
(y >= 24 && y < 27 && x >= 6 && x < 26);
|
|
79
|
+
const antenna =
|
|
80
|
+
y >= 3 &&
|
|
81
|
+
y < 11 &&
|
|
82
|
+
((x >= 7 && x < 9) || (x >= 23 && x < 25));
|
|
83
|
+
return bolt || head || antenna || graphitiEyeContains(x, y, 11) || graphitiEyeContains(x, y, 21);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function graphitiEyeContains(x: number, y: number, centerX: number): boolean {
|
|
87
|
+
const distance = (x - centerX) ** 2 + (y - 18) ** 2;
|
|
88
|
+
return distance >= 10 && distance <= 25;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function lakebaseGlyphOpacity(x: number, y: number): number {
|
|
92
|
+
if (x < 5 || x >= 27) return 0;
|
|
93
|
+
if (y >= 5 && y < 11) return 0.45;
|
|
94
|
+
|
|
95
|
+
// Keep the official mark's light middle band, dark wave channel, and solid lower band.
|
|
96
|
+
const middleBoundary = 19 - Math.round(Math.sin(((x - 5) / 21) * Math.PI * 2));
|
|
97
|
+
if (y >= 13 && y < middleBoundary) return 0.45;
|
|
98
|
+
if (y >= middleBoundary + 3 && y < 27) return 1;
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
44
102
|
function encodePng(rgba: Buffer): Buffer {
|
|
45
103
|
const header = Buffer.alloc(13);
|
|
46
104
|
header.writeUInt32BE(SIZE, 0);
|
package/src/service.ts
CHANGED
|
@@ -66,6 +66,7 @@ export type CliServicePythonInstaller = (
|
|
|
66
66
|
packageSpecifiers: readonly string[],
|
|
67
67
|
python: string,
|
|
68
68
|
platform: NodeJS.Platform,
|
|
69
|
+
offline: boolean,
|
|
69
70
|
) => Promise<string>;
|
|
70
71
|
|
|
71
72
|
/** Host runtime overrides for tests, embedded distributions, and nonstandard homes. */
|
|
@@ -100,6 +101,10 @@ export interface CliServiceRuntimeOptions {
|
|
|
100
101
|
export interface CliServiceInstallOptions {
|
|
101
102
|
/** Start the service immediately after writing its configuration and login entry. */
|
|
102
103
|
readonly start?: boolean;
|
|
104
|
+
/** Install the primary Python package from this local project instead of the registry. */
|
|
105
|
+
readonly pythonProject?: string;
|
|
106
|
+
/** Resolve and install Python packages only from the uv cache. */
|
|
107
|
+
readonly offline?: boolean;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
/** Current installation and process state for a system-tray service. */
|
|
@@ -124,6 +129,8 @@ export interface CliServiceLifecycle {
|
|
|
124
129
|
stop(): Promise<void>;
|
|
125
130
|
/** Stop and start the installed service. */
|
|
126
131
|
restart(): Promise<void>;
|
|
132
|
+
/** Return the managed service process log path. */
|
|
133
|
+
logPath(): string;
|
|
127
134
|
/** Stop the service and remove its login entry and package-owned state. */
|
|
128
135
|
uninstall(): Promise<void>;
|
|
129
136
|
/** Read the current installation and process state. */
|
|
@@ -175,6 +182,9 @@ export class CliService implements CliServiceLifecycle {
|
|
|
175
182
|
|
|
176
183
|
/** Install the login entry and optionally start the service. */
|
|
177
184
|
async install(options: CliServiceInstallOptions = {}): Promise<void> {
|
|
185
|
+
if (options.pythonProject && !this.definition.pythonPackage) {
|
|
186
|
+
throw new Error(`${this.definition.name} does not define a Python package`);
|
|
187
|
+
}
|
|
178
188
|
const owner = resolveServicePackage(this.definition.packageName);
|
|
179
189
|
const dependencies = owner.dependencies();
|
|
180
190
|
const external = new Set(externalRuntimePackages(dependencies));
|
|
@@ -184,7 +194,7 @@ export class CliService implements CliServiceLifecycle {
|
|
|
184
194
|
Object.fromEntries(Object.entries(dependencies).filter(([name]) => external.has(name))),
|
|
185
195
|
);
|
|
186
196
|
const paths = resolveServicePaths(this.definition, this.runtime);
|
|
187
|
-
const installedDefinition = await this.installDefinition(owner, paths.directory);
|
|
197
|
+
const installedDefinition = await this.installDefinition(owner, paths.directory, options);
|
|
188
198
|
const host = await this.ensureCompiledBinary(
|
|
189
199
|
this.binaryName(installedDefinition, "service"),
|
|
190
200
|
this.hostEntrypoint,
|
|
@@ -260,6 +270,11 @@ export class CliService implements CliServiceLifecycle {
|
|
|
260
270
|
await this.start();
|
|
261
271
|
}
|
|
262
272
|
|
|
273
|
+
/** Return the managed service process log path. */
|
|
274
|
+
logPath(): string {
|
|
275
|
+
return resolveServicePaths(this.definition, this.runtime).processLog;
|
|
276
|
+
}
|
|
277
|
+
|
|
263
278
|
/** Stop the service and remove its login entry and package-owned state. */
|
|
264
279
|
async uninstall(): Promise<void> {
|
|
265
280
|
const paths = resolveServicePaths(this.definition, this.runtime);
|
|
@@ -295,6 +310,7 @@ export class CliService implements CliServiceLifecycle {
|
|
|
295
310
|
private async installDefinition(
|
|
296
311
|
owner: ServicePackage,
|
|
297
312
|
directory: string,
|
|
313
|
+
options: CliServiceInstallOptions,
|
|
298
314
|
): Promise<CliServiceDefinition> {
|
|
299
315
|
const definition = CliServiceDefinitionSchema.parse({
|
|
300
316
|
...this.definition,
|
|
@@ -309,12 +325,16 @@ export class CliService implements CliServiceLifecycle {
|
|
|
309
325
|
: undefined;
|
|
310
326
|
const pythonEnvironment: Record<string, string> = {};
|
|
311
327
|
if (pythonPackage) {
|
|
328
|
+
const primarySpecifier = options.pythonProject
|
|
329
|
+
? _localPythonSpecifier(options.pythonProject, pythonPackage.name)
|
|
330
|
+
: `${pythonPackage.name}==${pythonPackage.version}`;
|
|
312
331
|
pythonEnvironment.PYTHON = await this.pythonInstaller(
|
|
313
332
|
this.uvExecutable,
|
|
314
333
|
join(directory, "python"),
|
|
315
|
-
[
|
|
334
|
+
[primarySpecifier, ...pythonPackage.dependencies],
|
|
316
335
|
pythonPackage.python,
|
|
317
336
|
this.runtime.platform,
|
|
337
|
+
options.offline ?? false,
|
|
318
338
|
);
|
|
319
339
|
}
|
|
320
340
|
const command = definition.command
|
|
@@ -429,6 +449,16 @@ export class CliService implements CliServiceLifecycle {
|
|
|
429
449
|
}
|
|
430
450
|
}
|
|
431
451
|
|
|
452
|
+
/** Preserve a package's extras while replacing its registry name with a local project path. */
|
|
453
|
+
function _localPythonSpecifier(project: string, packageName: string): string {
|
|
454
|
+
const directory = resolve(project);
|
|
455
|
+
if (!existsSync(join(directory, "pyproject.toml"))) {
|
|
456
|
+
throw new Error(`Local Python project has no pyproject.toml: ${directory}`);
|
|
457
|
+
}
|
|
458
|
+
const extras = /(\[[^\]]+\])$/.exec(packageName)?.[1] ?? "";
|
|
459
|
+
return `${directory}${extras}`;
|
|
460
|
+
}
|
|
461
|
+
|
|
432
462
|
function resolveHostEntrypoint(): string {
|
|
433
463
|
const current = fileURLToPath(import.meta.url);
|
|
434
464
|
const source =
|