nyte-ai 0.0.1 → 0.0.7
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 +13 -11
- package/bin/nyte.js +5 -21
- package/docs/README.md +16 -0
- package/docs/examples/README.md +18 -0
- package/docs/examples/ask.sh +7 -0
- package/docs/examples/prompt-file.sh +7 -0
- package/docs/usage.md +47 -0
- package/package.json +17 -7
- package/src/launcher.js +121 -0
package/README.md
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
# nyte-ai
|
|
2
2
|
|
|
3
|
-
`nyte-ai`
|
|
3
|
+
`nyte-ai` installs the `nyte` command, the terminal client for Nyte.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
This preview establishes the Nyte CLI distribution. It does not start an agent or server yet.
|
|
8
|
-
|
|
9
|
-
The planned command shape is:
|
|
5
|
+
Run it without installing:
|
|
10
6
|
|
|
11
7
|
```sh
|
|
12
|
-
npx nyte-ai
|
|
13
|
-
npx nyte-ai serve # run the standalone headless server
|
|
8
|
+
npx nyte-ai
|
|
14
9
|
```
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+
Or install it globally:
|
|
17
12
|
|
|
18
13
|
```sh
|
|
19
14
|
npm install --global nyte-ai
|
|
20
15
|
nyte
|
|
21
|
-
nyte serve
|
|
22
16
|
```
|
|
23
17
|
|
|
24
|
-
The
|
|
18
|
+
The npm package downloads the matching release from
|
|
19
|
+
[`interfaces-lab/nyte`](https://github.com/interfaces-lab/nyte), verifies its SHA-256 checksum,
|
|
20
|
+
and caches the native executable under `~/.nyte/bin`. Set `NYTE_BIN_DIR` to choose another cache
|
|
21
|
+
directory.
|
|
22
|
+
|
|
23
|
+
Read the [included guide and runnable examples](docs/README.md). These files ship
|
|
24
|
+
with this npm version. For a global install, the guide is at
|
|
25
|
+
`$(npm root -g)/nyte-ai/docs/README.md`. A downloaded native release also retains
|
|
26
|
+
its guide at `<cache-directory>/<version>/docs/README.md`.
|
package/bin/nyte.js
CHANGED
|
@@ -1,26 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const HELP = `nyte
|
|
3
|
+
import { launch } from "../src/launcher.js";
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
nyte serve Run the standalone headless server
|
|
11
|
-
|
|
12
|
-
Status:
|
|
13
|
-
This preview establishes the Nyte CLI distribution. The runner is not released yet.`;
|
|
14
|
-
|
|
15
|
-
const args = process.argv.slice(2);
|
|
16
|
-
if (args.includes("--version") || args.includes("-v")) {
|
|
17
|
-
process.stdout.write(`${VERSION}\n`);
|
|
18
|
-
} else if (args.includes("--help") || args.includes("-h")) {
|
|
19
|
-
process.stdout.write(`${HELP}\n`);
|
|
20
|
-
} else {
|
|
21
|
-
process.stderr.write(
|
|
22
|
-
"Nyte is not available yet. This preview establishes the upcoming headless runner and terminal client distribution.\n",
|
|
23
|
-
);
|
|
24
|
-
process.stderr.write("Run `npx nyte-ai --help` to see the planned command shape.\n");
|
|
5
|
+
try {
|
|
6
|
+
process.exitCode = await launch();
|
|
7
|
+
} catch (error) {
|
|
8
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
25
9
|
process.exitCode = 1;
|
|
26
10
|
}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Nyte guide
|
|
2
|
+
|
|
3
|
+
This guide and its source examples ship with the Nyte release you installed.
|
|
4
|
+
The npm package records its version in `package.json`; a native archive records
|
|
5
|
+
it in `VERSION` beside the executable.
|
|
6
|
+
|
|
7
|
+
- [Run a conversation or one prompt](usage.md)
|
|
8
|
+
- [Source examples](examples/README.md)
|
|
9
|
+
|
|
10
|
+
For npm installs, this guide is inside `nyte-ai/docs`. For native installs it is
|
|
11
|
+
at `<install-directory>/../share/nyte/<version>/docs/README.md`. The default is
|
|
12
|
+
`~/.local/share/nyte/<version>/docs/README.md`. An unpacked archive has `docs`
|
|
13
|
+
beside `nyte`. Keep the docs for the executable version you use.
|
|
14
|
+
|
|
15
|
+
Nyte's workspace libraries are private. These examples use the installed `nyte`
|
|
16
|
+
command and do not require unpublished packages.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Source examples
|
|
2
|
+
|
|
3
|
+
Run these from the workspace you want Nyte to inspect, after login and workspace
|
|
4
|
+
trust setup. Put `nyte` on `PATH`. Each script starts one prompt run and
|
|
5
|
+
returns Nyte's exit status. Neither script changes your shell settings.
|
|
6
|
+
|
|
7
|
+
- [ask.sh](ask.sh) passes prompt arguments without shell evaluation.
|
|
8
|
+
- [prompt-file.sh](prompt-file.sh) reads a prompt from a UTF-8 text file through stdin.
|
|
9
|
+
|
|
10
|
+
Replace `/path/to/docs` with this guide's installed directory:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
sh /path/to/docs/examples/ask.sh "Summarize this project's README"
|
|
14
|
+
sh /path/to/docs/examples/prompt-file.sh ./prompt.txt
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
These are ordinary shell source files you can copy and adapt. See the
|
|
18
|
+
[usage guide](../usage.md) for setup and the [front door](../README.md) for locations.
|
package/docs/usage.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Run Nyte
|
|
2
|
+
|
|
3
|
+
Log in once, then start a conversation from your project directory:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
nyte login
|
|
7
|
+
nyte
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`nyte login` lists the providers you can sign in to. `nyte login github-copilot` signs in with a
|
|
11
|
+
GitHub device code: open the URL it prints, enter the code, and Nyte loads the models your Copilot
|
|
12
|
+
account can use. This uses the GitHub Copilot OAuth app used by Pi, not a Nyte or OpenCode app.
|
|
13
|
+
Nyte exchanges the GitHub token for a short-lived Copilot session token and refreshes it as needed.
|
|
14
|
+
Existing direct-token Copilot logins need a fresh sign-in after upgrading. Nyte is not an officially
|
|
15
|
+
supported Copilot client; the account needs Copilot access.
|
|
16
|
+
|
|
17
|
+
Nyte asks whether you trust the directory. A trusted workspace lets tools read,
|
|
18
|
+
edit, and run commands with your process's access. There are no per-tool prompts.
|
|
19
|
+
Use an isolated environment when the workspace needs stronger boundaries.
|
|
20
|
+
|
|
21
|
+
To send one prompt and exit:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
nyte --print -- "Summarize this project's README"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use `--provider <provider>` and `--model <model>` before `--` to select a model.
|
|
28
|
+
These requests use your configured provider and may incur charges. Run `nyte login`
|
|
29
|
+
interactively before using scripts. A script must also run in a trusted workspace.
|
|
30
|
+
|
|
31
|
+
Use `nyte --resume` in a terminal to continue the latest conversation.
|
|
32
|
+
Installed copies can update with `nyte update`.
|
|
33
|
+
That command currently updates only the executable; it does not install the new
|
|
34
|
+
release's docs. Run the installer for that version to install its matching docs.
|
|
35
|
+
|
|
36
|
+
## Delegated tasks
|
|
37
|
+
|
|
38
|
+
The agent waits for a delegated task by default. It can start independent work in the
|
|
39
|
+
background, then use `wait_task` with that job's ID when it needs the report. The parent
|
|
40
|
+
continues after the existing task finishes; it does not launch a replacement or poll.
|
|
41
|
+
`stop_task` cancels the task. These are agent tools, not terminal commands.
|
|
42
|
+
|
|
43
|
+
A background report cannot restart a finished or stopped conversation. If the parent
|
|
44
|
+
already finished, the report stays queued for your next message.
|
|
45
|
+
|
|
46
|
+
The [source examples](examples/README.md) demonstrate quoted arguments and stdin.
|
|
47
|
+
Return to the [guide](README.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nyte-ai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Headless agent runner and terminal client for Nyte",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -9,23 +9,33 @@
|
|
|
9
9
|
"coding-agent",
|
|
10
10
|
"headless"
|
|
11
11
|
],
|
|
12
|
-
"
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/interfaces-lab/nyte.git",
|
|
15
|
+
"directory": "packages/cli"
|
|
16
|
+
},
|
|
13
17
|
"bin": {
|
|
14
18
|
"nyte": "bin/nyte.js"
|
|
15
19
|
},
|
|
16
20
|
"files": [
|
|
17
21
|
"bin",
|
|
22
|
+
"docs",
|
|
23
|
+
"src",
|
|
18
24
|
"README.md"
|
|
19
25
|
],
|
|
20
26
|
"type": "module",
|
|
21
27
|
"publishConfig": {
|
|
22
28
|
"access": "public"
|
|
23
29
|
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"clean": "rm -rf node_modules .turbo dist",
|
|
32
|
+
"package": "pnpm pack --pack-destination dist",
|
|
33
|
+
"test": "vitest --run"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"vitest": "^5.0.0"
|
|
37
|
+
},
|
|
24
38
|
"engines": {
|
|
25
39
|
"node": ">=26"
|
|
26
|
-
},
|
|
27
|
-
"scripts": {
|
|
28
|
-
"clean": "rm -rf node_modules .turbo",
|
|
29
|
-
"test": "node --test"
|
|
30
40
|
}
|
|
31
|
-
}
|
|
41
|
+
}
|
package/src/launcher.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { chmod, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
|
|
8
|
+
export const REPOSITORY = "interfaces-lab/nyte";
|
|
9
|
+
|
|
10
|
+
export function platformTarget(platform = process.platform, arch = process.arch) {
|
|
11
|
+
if ((platform !== "darwin" && platform !== "linux") || (arch !== "arm64" && arch !== "x64")) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return `${platform}-${arch}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function releaseAssetName(version, target) {
|
|
18
|
+
return `nyte-v${version.replace(/^v/u, "")}-${target}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function packageVersion() {
|
|
22
|
+
const metadata = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
23
|
+
if (typeof metadata.version !== "string") throw new Error("nyte-ai has no package version.");
|
|
24
|
+
return metadata.version;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function cacheRoot(env = process.env) {
|
|
28
|
+
return env["NYTE_BIN_DIR"] ?? join(homedir(), ".nyte", "bin");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function sha256(path) {
|
|
32
|
+
const hash = createHash("sha256");
|
|
33
|
+
hash.update(await readFile(path));
|
|
34
|
+
return hash.digest("hex");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function fetchOk(fetchFn, url) {
|
|
38
|
+
const response = await fetchFn(url, { signal: AbortSignal.timeout(10 * 60_000) });
|
|
39
|
+
if (!response.ok) throw new Error(`Failed to download ${url} (HTTP ${response.status}).`);
|
|
40
|
+
return response;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function download(fetchFn, url, path) {
|
|
44
|
+
const response = await fetchOk(fetchFn, url);
|
|
45
|
+
await writeFile(path, new Uint8Array(await response.arrayBuffer()));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function ensureBinary(options = {}) {
|
|
49
|
+
const version = options.version ?? (await packageVersion());
|
|
50
|
+
const target = platformTarget(options.platform, options.arch);
|
|
51
|
+
if (target === undefined) {
|
|
52
|
+
throw new Error(`Nyte has no native release for ${process.platform}/${process.arch}.`);
|
|
53
|
+
}
|
|
54
|
+
const root = options.root ?? cacheRoot(options.env);
|
|
55
|
+
const binary = join(root, version, "nyte");
|
|
56
|
+
if ((await stat(binary).catch(() => undefined))?.isFile()) return binary;
|
|
57
|
+
|
|
58
|
+
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
59
|
+
const asset = releaseAssetName(version, target);
|
|
60
|
+
const base = `https://github.com/${REPOSITORY}/releases/download/v${version}`;
|
|
61
|
+
const versionDir = dirname(binary);
|
|
62
|
+
const archive = join(versionDir, `${asset}.tar.gz`);
|
|
63
|
+
await mkdir(versionDir, { recursive: true });
|
|
64
|
+
try {
|
|
65
|
+
const checksum = (await (await fetchOk(fetchFn, `${base}/${asset}.tar.gz.sha256`)).text())
|
|
66
|
+
.trim()
|
|
67
|
+
.split(/\s+/u)[0]
|
|
68
|
+
?.toLowerCase();
|
|
69
|
+
if (checksum === undefined || !/^[0-9a-f]{64}$/u.test(checksum)) {
|
|
70
|
+
throw new Error(`The checksum file for ${asset} is malformed.`);
|
|
71
|
+
}
|
|
72
|
+
await download(fetchFn, `${base}/${asset}.tar.gz`, archive);
|
|
73
|
+
const actual = await sha256(archive);
|
|
74
|
+
if (actual !== checksum) {
|
|
75
|
+
throw new Error(`Checksum mismatch for ${asset}: expected ${checksum}, got ${actual}.`);
|
|
76
|
+
}
|
|
77
|
+
const result = spawn("tar", ["-xzf", archive, "-C", versionDir], { stdio: "inherit" });
|
|
78
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
79
|
+
result.once("error", reject);
|
|
80
|
+
result.once("exit", (code, signal) => {
|
|
81
|
+
if (signal !== null) reject(new Error(`tar exited from signal ${signal}.`));
|
|
82
|
+
else resolve(code);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
if (exitCode !== 0 || !(await stat(binary).catch(() => undefined))?.isFile()) {
|
|
86
|
+
throw new Error(`${asset}.tar.gz does not contain a nyte binary.`);
|
|
87
|
+
}
|
|
88
|
+
await chmod(binary, 0o755);
|
|
89
|
+
return binary;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
await rm(binary, { force: true });
|
|
92
|
+
throw error;
|
|
93
|
+
} finally {
|
|
94
|
+
await rm(archive, { force: true });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function launch(args = process.argv.slice(2), options = {}) {
|
|
99
|
+
const binary = await ensureBinary(options);
|
|
100
|
+
const child = spawn(binary, args, { stdio: "inherit" });
|
|
101
|
+
const forwardInt = () => {
|
|
102
|
+
if (!child.killed) child.kill("SIGINT");
|
|
103
|
+
};
|
|
104
|
+
const forwardTerm = () => {
|
|
105
|
+
if (!child.killed) child.kill("SIGTERM");
|
|
106
|
+
};
|
|
107
|
+
process.once("SIGINT", forwardInt);
|
|
108
|
+
process.once("SIGTERM", forwardTerm);
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
child.once("error", reject);
|
|
111
|
+
child.once("exit", (code, signal) => {
|
|
112
|
+
process.removeListener("SIGINT", forwardInt);
|
|
113
|
+
process.removeListener("SIGTERM", forwardTerm);
|
|
114
|
+
if (signal !== null) {
|
|
115
|
+
process.kill(process.pid, signal);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
resolve(code ?? 1);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|