@nyte-ai/host 0.0.18 → 0.0.19
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/THIRD-PARTY-NOTICES.md +66 -0
- package/dist/chunks/{canvas-preview-5hxat7hh.js → canvas-preview-11nadadc.js} +8 -6
- package/dist/chunks/{canvas-preview-rp6fz0w5.js → canvas-preview-2jwbe14k.js} +5 -5
- package/dist/chunks/{canvas-preview-m02v4yy6.js → canvas-preview-55s0y68g.js} +3 -2
- package/dist/chunks/{canvas-preview-xn10d4f8.js → canvas-preview-5ekjx6sw.js} +9 -4
- package/dist/chunks/{canvas-preview-75db60xy.js → canvas-preview-8xc0vn7v.js} +5 -4
- package/dist/chunks/{canvas-preview-zn2d0k9d.js → canvas-preview-af86jz8t.js} +14 -6
- package/dist/chunks/{canvas-preview-d9wwhbtz.js → canvas-preview-aq5j4p25.js} +1 -1
- package/dist/chunks/{canvas-preview-j1tnf51w.js → canvas-preview-b2v8wfnz.js} +1 -1
- package/dist/chunks/canvas-preview-befkjea2.js +60 -0
- package/dist/chunks/canvas-preview-bxnqt8cd.js +103 -0
- package/dist/chunks/{canvas-preview-vax9ne4e.js → canvas-preview-cawy6vbz.js} +22 -11
- package/dist/chunks/{canvas-preview-senpjwep.js → canvas-preview-crmzxxt8.js} +3 -3
- package/dist/chunks/{canvas-preview-ymgz0937.js → canvas-preview-drctegz4.js} +8 -4
- package/dist/chunks/{canvas-preview-yrfz2ak6.js → canvas-preview-e3vnkkc5.js} +6 -6
- package/dist/chunks/canvas-preview-ec2mt7es.js +108 -0
- package/dist/chunks/{canvas-preview-1ta3avah.js → canvas-preview-fxa0rfb0.js} +10 -5
- package/dist/chunks/{canvas-preview-8xgt67ka.js → canvas-preview-g5fkjs34.js} +1 -1
- package/dist/chunks/{canvas-preview-g3qj3y3k.js → canvas-preview-jeddjex8.js} +13 -3
- package/dist/chunks/{canvas-preview-0nrnbt76.js → canvas-preview-k1yg4fgp.js} +199 -125
- package/dist/chunks/canvas-preview-kp75gb63.js +141 -0
- package/dist/chunks/{canvas-preview-ewgspmsd.js → canvas-preview-n2a35zv5.js} +1 -1
- package/dist/chunks/{canvas-preview-h8hshnff.js → canvas-preview-pkzqg96v.js} +1 -1
- package/dist/chunks/{canvas-preview-evh92ywd.js → canvas-preview-tnqe7k0b.js} +15 -10
- package/dist/chunks/{canvas-preview-ehf3vp31.js → canvas-preview-vm9zdp2t.js} +45 -58
- package/dist/chunks/{canvas-preview-76fwvd6e.js → canvas-preview-w54ymtb3.js} +1 -1
- package/dist/chunks/canvas-preview-y5y6ghaw.js +125 -0
- package/dist/chunks/{canvas-preview-98aj5p34.js → canvas-preview-ycepb05s.js} +1 -1
- package/dist/src/canvas-preview.js +7 -3
- package/dist/src/deploy/cloudflare/worker.js +1 -1
- package/dist/src/deploy/vercel/server/routes/[...].js +2 -2
- package/dist/src/deploy/vercel/server/utils/host.js +1 -1
- package/dist/src/deploy/vercel/server/workflows/drive.js +3 -3
- package/dist/src/images/exif.d.ts +1 -0
- package/dist/src/images/exif.js +6 -0
- package/dist/src/images/node.d.ts +37 -0
- package/dist/src/images/node.js +12 -0
- package/dist/src/images/photon.d.ts +3 -0
- package/dist/src/images/photon.js +7 -0
- package/dist/src/images/worker.d.ts +1 -0
- package/dist/src/images/worker.js +23 -0
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +23 -22
- package/dist/src/listen-fetch.js +3 -3
- package/dist/src/listen.js +4 -4
- package/dist/src/local/command.d.ts +5 -0
- package/dist/src/local/command.js +6 -2
- package/dist/src/local/files.js +2 -2
- package/dist/src/local/git-watch.d.ts +1 -1
- package/dist/src/local/git-watch.js +1 -1
- package/dist/src/local/git.js +3 -3
- package/dist/src/local/github.js +2 -2
- package/dist/src/local/jobs.d.ts +1 -1
- package/dist/src/local/jobs.js +2 -1
- package/dist/src/local/provider.js +9 -9
- package/dist/src/local/snapshots.js +2 -2
- package/dist/src/local/worktrees.js +2 -2
- package/dist/src/mcp/connect.d.ts +0 -13
- package/dist/src/mcp/connect.js +1 -1
- package/dist/src/mcp/hub.js +2 -2
- package/dist/src/open.d.ts +2 -30
- package/dist/src/open.js +20 -18
- package/dist/src/serve/binding.js +1 -1
- package/dist/src/serve/handler.js +2 -2
- package/dist/src/sqlite.d.ts +2 -1
- package/dist/src/sqlite.js +4 -3
- package/dist/src/store-usage.js +4 -1
- package/dist/src/usage-history.d.ts +54 -0
- package/dist/src/usage-history.js +14 -0
- package/dist/src/usage-service.d.ts +2 -1
- package/dist/src/usage-service.js +7 -3
- package/dist/src/usage-worker.js +8 -4
- package/dist/src/usage.d.ts +150 -27
- package/dist/src/usage.js +12 -2
- package/package.json +16 -8
- package/src/canvas-preview.ts +4 -3
- package/src/images/exif.ts +69 -0
- package/src/images/node.ts +163 -0
- package/src/images/photon.ts +141 -0
- package/src/images/worker.ts +29 -0
- package/src/index.ts +19 -1
- package/src/local/command.ts +64 -12
- package/src/local/files.ts +8 -3
- package/src/local/git-watch.ts +16 -6
- package/src/local/git.ts +7 -2
- package/src/local/github.ts +3 -4
- package/src/local/jobs.ts +55 -69
- package/src/local/snapshots.ts +4 -2
- package/src/local/worktrees.ts +4 -4
- package/src/mcp/connect.ts +1 -14
- package/src/open.ts +16 -36
- package/src/serve/binding.ts +4 -3
- package/src/sqlite.ts +33 -8
- package/src/store-usage.ts +5 -2
- package/src/usage-history.ts +219 -0
- package/src/usage-service.ts +12 -3
- package/src/usage.ts +251 -176
- package/dist/chunks/canvas-preview-a3yw0j70.js +0 -72
- package/dist/chunks/canvas-preview-azg56359.js +0 -68
- package/dist/src/store-owner.d.ts +0 -15
- package/dist/src/store-owner.js +0 -9
- package/src/store-owner.ts +0 -98
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PhotonImage,
|
|
3
|
+
SamplingFilter,
|
|
4
|
+
fliph,
|
|
5
|
+
flipv,
|
|
6
|
+
initSync,
|
|
7
|
+
resize,
|
|
8
|
+
} from "@silvia-odwyer/photon/photon_rs.js";
|
|
9
|
+
import type { SyncInitInput } from "@silvia-odwyer/photon/photon_rs.js";
|
|
10
|
+
import type { ImageLimits, ImageProcessor, PreparedImage } from "@nyte-ai/core";
|
|
11
|
+
import { exifOrientation } from "./exif.ts";
|
|
12
|
+
|
|
13
|
+
const INLINE_IMAGE_TYPES: ReadonlySet<string> = new Set([
|
|
14
|
+
"image/png",
|
|
15
|
+
"image/jpeg",
|
|
16
|
+
"image/gif",
|
|
17
|
+
"image/webp",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const JPEG_QUALITIES = [80, 70, 55, 40];
|
|
21
|
+
|
|
22
|
+
export function photonImages(wasm: SyncInitInput): ImageProcessor {
|
|
23
|
+
initSync({ module: wasm });
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
prepare: (bytes, mimeType, limits) => Promise.resolve(prepare(bytes, mimeType, limits)),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function prepare(
|
|
31
|
+
bytes: Uint8Array,
|
|
32
|
+
mimeType: string,
|
|
33
|
+
limits: ImageLimits,
|
|
34
|
+
): PreparedImage | undefined {
|
|
35
|
+
const orientation = exifOrientation(bytes);
|
|
36
|
+
let image: PhotonImage | undefined;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
image = PhotonImage.new_from_byteslice(bytes);
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
INLINE_IMAGE_TYPES.has(mimeType) &&
|
|
43
|
+
orientation === 1 &&
|
|
44
|
+
image.get_width() <= limits.maxWidth &&
|
|
45
|
+
image.get_height() <= limits.maxHeight &&
|
|
46
|
+
base64Length(bytes.byteLength) <= limits.maxBase64Bytes
|
|
47
|
+
)
|
|
48
|
+
return { data: bytes.toBase64(), mimeType };
|
|
49
|
+
|
|
50
|
+
if (orientation >= 5) {
|
|
51
|
+
const upright = transposed(image, orientation);
|
|
52
|
+
image.free();
|
|
53
|
+
image = undefined;
|
|
54
|
+
image = new PhotonImage(upright.pixels, upright.width, upright.height);
|
|
55
|
+
} else if (orientation !== 1) {
|
|
56
|
+
if (orientation !== 4) fliph(image);
|
|
57
|
+
|
|
58
|
+
if (orientation !== 2) flipv(image);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const width = image.get_width();
|
|
62
|
+
const height = image.get_height();
|
|
63
|
+
const scale = Math.min(1, limits.maxWidth / width, limits.maxHeight / height);
|
|
64
|
+
|
|
65
|
+
let target = {
|
|
66
|
+
width: Math.max(1, Math.round(width * scale)),
|
|
67
|
+
height: Math.max(1, Math.round(height * scale)),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
for (;;) {
|
|
71
|
+
const encoded = encodeWithin(image, target, limits.maxBase64Bytes, mimeType === "image/jpeg");
|
|
72
|
+
|
|
73
|
+
if (encoded !== undefined)
|
|
74
|
+
return target.width === width && target.height === height
|
|
75
|
+
? encoded
|
|
76
|
+
: { ...encoded, resized: { from: { width, height }, to: target } };
|
|
77
|
+
|
|
78
|
+
if (target.width === 1 && target.height === 1) return undefined;
|
|
79
|
+
target = {
|
|
80
|
+
width: Math.max(1, Math.floor(target.width * 0.75)),
|
|
81
|
+
height: Math.max(1, Math.floor(target.height * 0.75)),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
} finally {
|
|
87
|
+
image?.free();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function encodeWithin(
|
|
92
|
+
image: PhotonImage,
|
|
93
|
+
size: { readonly width: number; readonly height: number },
|
|
94
|
+
maxBase64Bytes: number,
|
|
95
|
+
jpegFirst: boolean,
|
|
96
|
+
): { readonly data: string; readonly mimeType: string } | undefined {
|
|
97
|
+
const same = size.width === image.get_width() && size.height === image.get_height();
|
|
98
|
+
const scaled = same ? image : resize(image, size.width, size.height, SamplingFilter.Lanczos3);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const png = { mimeType: "image/png", encode: () => scaled.get_bytes() };
|
|
102
|
+
|
|
103
|
+
const jpegs = JPEG_QUALITIES.map((quality) => ({
|
|
104
|
+
mimeType: "image/jpeg",
|
|
105
|
+
encode: () => scaled.get_bytes_jpeg(quality),
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
for (const candidate of jpegFirst ? [...jpegs, png] : [png, ...jpegs]) {
|
|
109
|
+
const encoded = candidate.encode();
|
|
110
|
+
|
|
111
|
+
if (base64Length(encoded.byteLength) <= maxBase64Bytes)
|
|
112
|
+
return { data: encoded.toBase64(), mimeType: candidate.mimeType };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return undefined;
|
|
116
|
+
} finally {
|
|
117
|
+
if (!same) scaled.free();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function transposed(image: PhotonImage, orientation: number) {
|
|
122
|
+
const width = image.get_width();
|
|
123
|
+
const height = image.get_height();
|
|
124
|
+
const source = image.get_raw_pixels();
|
|
125
|
+
const pixels = new Uint8Array(source.length);
|
|
126
|
+
|
|
127
|
+
for (let y = 0; y < height; y++) {
|
|
128
|
+
for (let x = 0; x < width; x++) {
|
|
129
|
+
const outX = orientation === 5 || orientation === 8 ? y : height - 1 - y;
|
|
130
|
+
const outY = orientation === 5 || orientation === 6 ? x : width - 1 - x;
|
|
131
|
+
const from = (y * width + x) * 4;
|
|
132
|
+
pixels.set(source.subarray(from, from + 4), (outY * height + outX) * 4);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { pixels, width: height, height: width };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function base64Length(byteLength: number): number {
|
|
140
|
+
return Math.ceil(byteLength / 3) * 4;
|
|
141
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
2
|
+
import type { Static } from "typebox";
|
|
3
|
+
import type { PrepareRequest, prepareReply } from "./node.ts";
|
|
4
|
+
import { photonImages } from "./photon.ts";
|
|
5
|
+
|
|
6
|
+
const port = parentPort;
|
|
7
|
+
|
|
8
|
+
if (port === null) throw new Error("The image worker needs a parent thread");
|
|
9
|
+
|
|
10
|
+
const wasm: unknown = workerData;
|
|
11
|
+
|
|
12
|
+
if (!(wasm instanceof Uint8Array)) throw new Error("The image worker needs Photon's WebAssembly");
|
|
13
|
+
|
|
14
|
+
const images = photonImages(wasm);
|
|
15
|
+
|
|
16
|
+
port.on("message", (request: PrepareRequest) => {
|
|
17
|
+
void images.prepare(request.bytes, request.mimeType, request.limits).then(
|
|
18
|
+
(result) =>
|
|
19
|
+
port.postMessage({
|
|
20
|
+
id: request.id,
|
|
21
|
+
result: result ?? null,
|
|
22
|
+
} satisfies Static<typeof prepareReply>),
|
|
23
|
+
(cause: unknown) =>
|
|
24
|
+
port.postMessage({
|
|
25
|
+
id: request.id,
|
|
26
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
27
|
+
} satisfies Static<typeof prepareReply>),
|
|
28
|
+
);
|
|
29
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,21 +1,39 @@
|
|
|
1
1
|
export * from "./credentials.ts";
|
|
2
|
+
|
|
2
3
|
export * from "./identity.ts";
|
|
4
|
+
|
|
3
5
|
export { identitySigner } from "./identity-key.ts";
|
|
6
|
+
|
|
4
7
|
export * from "./listen.ts";
|
|
8
|
+
|
|
5
9
|
export * from "./listen-fetch.ts";
|
|
10
|
+
|
|
6
11
|
export * from "./local/files.ts";
|
|
12
|
+
|
|
7
13
|
export * from "./local/git.ts";
|
|
14
|
+
|
|
8
15
|
export * from "./local/github.ts";
|
|
16
|
+
|
|
9
17
|
export * from "./local/identity.ts";
|
|
18
|
+
|
|
10
19
|
export * from "./local/jobs.ts";
|
|
20
|
+
|
|
11
21
|
export * from "./local/provider.ts";
|
|
22
|
+
|
|
12
23
|
export { createMcpHub } from "./mcp/hub.ts";
|
|
24
|
+
|
|
13
25
|
export type { McpConnector } from "./mcp/connect.ts";
|
|
26
|
+
|
|
14
27
|
export * from "./local/snapshots.ts";
|
|
28
|
+
|
|
15
29
|
export * from "./local/worktrees.ts";
|
|
30
|
+
|
|
16
31
|
export * from "./open.ts";
|
|
32
|
+
|
|
17
33
|
export * from "./plugins/loader.ts";
|
|
34
|
+
|
|
18
35
|
export * from "./postgres.ts";
|
|
36
|
+
|
|
19
37
|
export * from "./secrets.ts";
|
|
38
|
+
|
|
20
39
|
export * from "./sqlite.ts";
|
|
21
|
-
export { StoreOwned } from "./store-owner.ts";
|
package/src/local/command.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import type { ChildProcess, SpawnOptions } from "node:child_process";
|
|
2
3
|
import { constants } from "node:fs";
|
|
3
4
|
import { access, stat } from "node:fs/promises";
|
|
4
|
-
import { delimiter, isAbsolute, join } from "node:path";
|
|
5
|
+
import { delimiter, extname, isAbsolute, join } from "node:path";
|
|
5
6
|
import { SdkError } from "@nyte-ai/schema";
|
|
6
7
|
import { processEnvironment } from "@nyte-ai/core";
|
|
7
8
|
|
|
@@ -25,9 +26,10 @@ export async function run(
|
|
|
25
26
|
},
|
|
26
27
|
): Promise<Ran> {
|
|
27
28
|
await processEnvironment();
|
|
29
|
+
const resolved = process.platform === "win32" ? ((await findExecutable(file)) ?? file) : file;
|
|
28
30
|
|
|
29
31
|
return new Promise((resolve, reject) => {
|
|
30
|
-
const child =
|
|
32
|
+
const child = spawnCommand(resolved, args, {
|
|
31
33
|
cwd: options.cwd,
|
|
32
34
|
env: { ...process.env, ...options.env },
|
|
33
35
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -44,7 +46,7 @@ export async function run(
|
|
|
44
46
|
child.on("error", (error) =>
|
|
45
47
|
reject(new SdkError("unavailable", `Cannot run ${file}: ${error.message}`)),
|
|
46
48
|
);
|
|
47
|
-
child.stdout
|
|
49
|
+
child.stdout?.on("data", (chunk: Buffer) => {
|
|
48
50
|
if (truncated) return;
|
|
49
51
|
size += chunk.byteLength;
|
|
50
52
|
|
|
@@ -58,8 +60,8 @@ export async function run(
|
|
|
58
60
|
|
|
59
61
|
stdout.push(chunk);
|
|
60
62
|
});
|
|
61
|
-
child.stderr
|
|
62
|
-
child.stdin
|
|
63
|
+
child.stderr?.on("data", (chunk: Buffer) => stderr.push(chunk));
|
|
64
|
+
child.stdin?.on("error", () => undefined);
|
|
63
65
|
child.on("close", (code) =>
|
|
64
66
|
resolve({
|
|
65
67
|
code,
|
|
@@ -68,22 +70,72 @@ export async function run(
|
|
|
68
70
|
truncated,
|
|
69
71
|
}),
|
|
70
72
|
);
|
|
71
|
-
child.stdin
|
|
73
|
+
child.stdin?.end(options.input);
|
|
72
74
|
});
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
/** Spawns `file` as `spawn` does, except that on Windows a `.cmd` or `.bat` file runs through `cmd.exe` with its arguments escaped, as Node no longer starts batch files without a shell. */
|
|
78
|
+
export function spawnCommand(
|
|
79
|
+
file: string,
|
|
80
|
+
args: readonly string[],
|
|
81
|
+
options: SpawnOptions,
|
|
82
|
+
): ChildProcess {
|
|
83
|
+
if (process.platform !== "win32" || !/\.(cmd|bat)$/i.test(file))
|
|
84
|
+
return spawn(file, args, options);
|
|
85
|
+
const shim = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i.test(file);
|
|
86
|
+
const line = [escapeCommand(file), ...args.map((arg) => escapeArgument(arg, shim))].join(" ");
|
|
87
|
+
|
|
88
|
+
return spawn(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", `"${line}"`], {
|
|
89
|
+
...options,
|
|
90
|
+
windowsVerbatimArguments: true,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Ends `child` with `signal`; on Windows, which has no signals, it ends the child's whole process tree with `taskkill /T /F`, as `kill` alone would leave what a batch file or a shell started running. */
|
|
95
|
+
export function killTree(child: ChildProcess, signal: NodeJS.Signals): void {
|
|
96
|
+
if (process.platform !== "win32" || child.pid === undefined) {
|
|
97
|
+
child.kill(signal);
|
|
98
|
+
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
103
|
+
stdio: "ignore",
|
|
104
|
+
windowsHide: true,
|
|
105
|
+
}).on("error", () => child.kill(signal));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
|
|
109
|
+
|
|
110
|
+
function escapeCommand(command: string): string {
|
|
111
|
+
return command.replace(CMD_META, "^$1");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function escapeArgument(argument: string, twice: boolean): string {
|
|
115
|
+
const quoted = `"${argument.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1")}"`;
|
|
116
|
+
const escaped = quoted.replace(CMD_META, "^$1");
|
|
117
|
+
|
|
118
|
+
return twice ? escaped.replace(CMD_META, "^$1") : escaped;
|
|
119
|
+
}
|
|
120
|
+
|
|
75
121
|
export async function findExecutable(
|
|
76
122
|
name: string,
|
|
77
123
|
directories: readonly string[] = [],
|
|
78
124
|
): Promise<string | null> {
|
|
79
|
-
|
|
80
|
-
await processEnvironment();
|
|
81
|
-
const path = (process.env.PATH ?? "").split(delimiter).filter((entry) => entry !== "");
|
|
125
|
+
const pathext = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter((ext) => ext !== "");
|
|
82
126
|
|
|
83
127
|
const extensions =
|
|
84
|
-
process.platform
|
|
85
|
-
|
|
86
|
-
|
|
128
|
+
process.platform !== "win32" ? [""] : extname(name) === "" ? pathext : ["", ...pathext];
|
|
129
|
+
|
|
130
|
+
if (isAbsolute(name)) {
|
|
131
|
+
for (const extension of extensions)
|
|
132
|
+
if (await executable(`${name}${extension}`)) return `${name}${extension}`;
|
|
133
|
+
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await processEnvironment();
|
|
138
|
+
const path = (process.env.PATH ?? "").split(delimiter).filter((entry) => entry !== "");
|
|
87
139
|
|
|
88
140
|
for (const directory of [...directories, ...path])
|
|
89
141
|
for (const extension of extensions) {
|
package/src/local/files.ts
CHANGED
|
@@ -167,7 +167,7 @@ export function localFiles(
|
|
|
167
167
|
if (stats === null) return null;
|
|
168
168
|
|
|
169
169
|
if (stats.isSymbolicLink()) {
|
|
170
|
-
const link = await
|
|
170
|
+
const link = await readLink(target);
|
|
171
171
|
|
|
172
172
|
return { kind: "symlink", size: stats.size, version: hashVersion(link), target: link };
|
|
173
173
|
}
|
|
@@ -218,7 +218,7 @@ export function localFiles(
|
|
|
218
218
|
|
|
219
219
|
const current =
|
|
220
220
|
(await currentVersion(target)) ??
|
|
221
|
-
(link.isSymbolicLink() ? hashVersion(await
|
|
221
|
+
(link.isSymbolicLink() ? hashVersion(await readLink(target)) : null);
|
|
222
222
|
|
|
223
223
|
if (removeOptions?.expect !== undefined && removeOptions.expect !== current)
|
|
224
224
|
return { kind: "stale", current };
|
|
@@ -326,6 +326,10 @@ async function currentVersion(path: string): Promise<FileVersion | null> {
|
|
|
326
326
|
return stats.isDirectory() ? directoryVersion(path) : hashFile(path).catch(missing);
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
+
async function readLink(path: string): Promise<string> {
|
|
330
|
+
return (await readlink(path)).split(sep).join("/");
|
|
331
|
+
}
|
|
332
|
+
|
|
329
333
|
async function resolveLink(path: string): Promise<string> {
|
|
330
334
|
const link = await lstat(path).catch(missing);
|
|
331
335
|
|
|
@@ -529,7 +533,7 @@ function rejectFailure(code: number | null, stderr: string): void {
|
|
|
529
533
|
}
|
|
530
534
|
|
|
531
535
|
function ripgrepPath(path: string): string {
|
|
532
|
-
return path.
|
|
536
|
+
return path.split(sep).join("/").replace(/^\.\//, "");
|
|
533
537
|
}
|
|
534
538
|
|
|
535
539
|
async function* grep(
|
|
@@ -586,6 +590,7 @@ async function* grep(
|
|
|
586
590
|
} finally {
|
|
587
591
|
lines.close();
|
|
588
592
|
child.kill();
|
|
593
|
+
await closed;
|
|
589
594
|
}
|
|
590
595
|
}
|
|
591
596
|
|
package/src/local/git-watch.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { lstat } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
2
3
|
import type { Disposer, GitStatus } from "@nyte-ai/schema";
|
|
3
4
|
import type { FeedSink } from "./feed.ts";
|
|
4
5
|
import { createIgnores, watchTree } from "./tree.ts";
|
|
@@ -33,7 +34,7 @@ const GIT_STATE = new Set([
|
|
|
33
34
|
"rebase-apply",
|
|
34
35
|
]);
|
|
35
36
|
|
|
36
|
-
/** Returns the status watch `localGit` shares among its watchers: the first subscriber starts it and the last one stops it, and each gets the status at once, then again whenever a change could move it. A recursive `fs.watch` on the workspace hears worktree paths that `.gitignore` and `.git/info/exclude` do not ignore, and, in the git directory and the common directory, only HEAD, the index, branch and remote refs, `packed-refs`, `config` and the merge, cherry-pick, revert and rebase state, so object writes, logs and Nyte's snapshot refs read nothing; the `.git` entry itself appearing or going drops the remembered repository and starts over. Only where `fs.watch` is unavailable does it poll, every 2 s. */
|
|
37
|
+
/** Returns the status watch `localGit` shares among its watchers: the first subscriber starts it and the last one stops it, and each gets the status at once, then again whenever a change could move it. A recursive `fs.watch` on the workspace hears worktree paths that `.gitignore` and `.git/info/exclude` do not ignore, and, in the git directory and the common directory, only HEAD, the index, branch and remote refs, `packed-refs`, `config` and the merge, cherry-pick, revert and rebase state, so object writes, logs and Nyte's snapshot refs read nothing; the `.git` entry itself appearing or going (not a change inside it, which Windows also reports on the folder) drops the remembered repository and starts over. Only where `fs.watch` is unavailable does it poll, every 2 s. */
|
|
37
38
|
export function createWatcher(
|
|
38
39
|
cwd: string,
|
|
39
40
|
read: () => Promise<GitStatus>,
|
|
@@ -88,7 +89,10 @@ export function createWatcher(
|
|
|
88
89
|
);
|
|
89
90
|
};
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
const gitEntry = async (): Promise<boolean> =>
|
|
93
|
+
(await lstat(join(cwd, ".git")).catch(() => null)) !== null;
|
|
94
|
+
|
|
95
|
+
void Promise.all([repository(), gitEntry()]).then(([repo, hadGit]) => {
|
|
92
96
|
if (stopped) return;
|
|
93
97
|
const insideGitDir = repo === null ? null : pathInside(cwd, repo.gitDir);
|
|
94
98
|
const ignores = repo === null ? null : createIgnores(repo.root);
|
|
@@ -99,7 +103,11 @@ export function createWatcher(
|
|
|
99
103
|
let found: "none" | "status" = "none";
|
|
100
104
|
|
|
101
105
|
for (const path of paths) {
|
|
102
|
-
if (path === ".git")
|
|
106
|
+
if (path === ".git") {
|
|
107
|
+
if ((await gitEntry()) !== hadGit) return "repository";
|
|
108
|
+
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
103
111
|
|
|
104
112
|
if (repo === null || ignores === null) continue;
|
|
105
113
|
|
|
@@ -115,7 +123,10 @@ export function createWatcher(
|
|
|
115
123
|
if (path.endsWith(".gitignore"))
|
|
116
124
|
ignores.forget(`${repo.prefix}${path}`.split("/").slice(0, -1).join("/"));
|
|
117
125
|
|
|
118
|
-
if (found
|
|
126
|
+
if (found !== "none") continue;
|
|
127
|
+
const directory = (await lstat(join(cwd, path)).catch(() => null))?.isDirectory();
|
|
128
|
+
|
|
129
|
+
if (!(await ignores.ignored(`${repo.prefix}${path}`, directory === true)))
|
|
119
130
|
found = "status";
|
|
120
131
|
}
|
|
121
132
|
|
|
@@ -175,7 +186,6 @@ function movesStatus(path: string): boolean {
|
|
|
175
186
|
return (
|
|
176
187
|
path === "" ||
|
|
177
188
|
GIT_STATE.has(top) ||
|
|
178
|
-
path === "refs" ||
|
|
179
189
|
path.startsWith("refs/heads/") ||
|
|
180
190
|
path.startsWith("refs/remotes/")
|
|
181
191
|
);
|
package/src/local/git.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { lstat, readFile, stat } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
3
|
import { asId, SdkError } from "@nyte-ai/schema";
|
|
4
4
|
import type {
|
|
5
5
|
BlameView,
|
|
@@ -101,7 +101,12 @@ export function localGit(env: ExecutionEnv, options?: { readonly git?: string })
|
|
|
101
101
|
|
|
102
102
|
return root === undefined || gitDir === undefined
|
|
103
103
|
? null
|
|
104
|
-
: {
|
|
104
|
+
: {
|
|
105
|
+
root: resolve(root),
|
|
106
|
+
gitDir: resolve(gitDir),
|
|
107
|
+
commonDir: resolve(commonDir || gitDir),
|
|
108
|
+
prefix,
|
|
109
|
+
};
|
|
105
110
|
};
|
|
106
111
|
|
|
107
112
|
const repository = async (): Promise<Repository | null> => {
|
package/src/local/github.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { homedir } from "node:os";
|
|
3
2
|
import type { ChildProcess } from "node:child_process";
|
|
4
3
|
import { Type } from "typebox";
|
|
@@ -17,7 +16,7 @@ import type {
|
|
|
17
16
|
} from "@nyte-ai/schema";
|
|
18
17
|
import { dev } from "@nyte-ai/core";
|
|
19
18
|
import type { ExecutionEnv, GitHubAccountApi, GitHubApi } from "@nyte-ai/core";
|
|
20
|
-
import { findExecutable, run } from "./command.ts";
|
|
19
|
+
import { findExecutable, killTree, run, spawnCommand } from "./command.ts";
|
|
21
20
|
|
|
22
21
|
const ENV = {
|
|
23
22
|
GH_PROMPT_DISABLED: "1",
|
|
@@ -255,7 +254,7 @@ function startSignIn(file: string, cwd: string, ended: () => void): SignIn {
|
|
|
255
254
|
let verificationUri: string | undefined;
|
|
256
255
|
let done = false;
|
|
257
256
|
|
|
258
|
-
const child: ChildProcess =
|
|
257
|
+
const child: ChildProcess = spawnCommand(
|
|
259
258
|
file,
|
|
260
259
|
[
|
|
261
260
|
"auth",
|
|
@@ -278,7 +277,7 @@ function startSignIn(file: string, cwd: string, ended: () => void): SignIn {
|
|
|
278
277
|
child.stdout?.destroy();
|
|
279
278
|
child.stderr?.destroy();
|
|
280
279
|
|
|
281
|
-
if (child.exitCode === null && child.signalCode === null) child
|
|
280
|
+
if (child.exitCode === null && child.signalCode === null) killTree(child, "SIGKILL");
|
|
282
281
|
started.resolve(start);
|
|
283
282
|
ended();
|
|
284
283
|
};
|