@tonbo/cli 0.0.6 → 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/dist/src/http.js DELETED
@@ -1,19 +0,0 @@
1
- export class HttpError extends Error {
2
- status;
3
- body;
4
- constructor(message, status, body) {
5
- super(message);
6
- this.status = status;
7
- this.body = body;
8
- }
9
- }
10
- export async function requestJson(fetcher, url, init = {}) {
11
- const response = await fetcher(url, init);
12
- const body = (await response.json().catch(() => null));
13
- if (!response.ok) {
14
- const record = body && typeof body === "object" ? body : {};
15
- const message = [record.detail, record.message, record.error, record.title].find((value) => typeof value === "string");
16
- throw new HttpError(typeof message === "string" ? message : `Request failed with HTTP ${response.status}.`, response.status, body);
17
- }
18
- return body;
19
- }
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/src/main.js DELETED
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createProgram } from "./app.js";
3
- try {
4
- await createProgram().parseAsync(process.argv);
5
- }
6
- catch (error) {
7
- console.error(error instanceof Error ? error.message : error);
8
- process.exitCode = 1;
9
- }
@@ -1,25 +0,0 @@
1
- export interface ProgressReporter {
2
- start(message: string): void;
3
- succeed(message?: string): void;
4
- fail(message?: string): void;
5
- }
6
- interface ProgressStream {
7
- isTTY?: boolean;
8
- write(value: string): unknown;
9
- }
10
- export declare class TerminalProgress implements ProgressReporter {
11
- private readonly stream;
12
- private readonly intervalMs;
13
- private activeMessage;
14
- private frame;
15
- private lastWidth;
16
- private timer;
17
- constructor(stream: ProgressStream, intervalMs?: number);
18
- start(message: string): void;
19
- succeed(message?: string | undefined): void;
20
- fail(message?: string | undefined): void;
21
- private finish;
22
- private render;
23
- }
24
- export declare const silentProgress: ProgressReporter;
25
- export {};
@@ -1,60 +0,0 @@
1
- const FRAMES = ["|", "/", "-", "\\"];
2
- export class TerminalProgress {
3
- stream;
4
- intervalMs;
5
- activeMessage;
6
- frame = 0;
7
- lastWidth = 0;
8
- timer;
9
- constructor(stream, intervalMs = 80) {
10
- this.stream = stream;
11
- this.intervalMs = intervalMs;
12
- }
13
- start(message) {
14
- if (this.activeMessage)
15
- this.succeed();
16
- this.activeMessage = message;
17
- this.frame = 0;
18
- if (!this.stream.isTTY) {
19
- this.stream.write(`[..] ${message}\n`);
20
- return;
21
- }
22
- this.render(`[${FRAMES[this.frame]}] ${message}`);
23
- this.timer = setInterval(() => {
24
- this.frame = (this.frame + 1) % FRAMES.length;
25
- this.render(`[${FRAMES[this.frame]}] ${this.activeMessage}`);
26
- }, this.intervalMs);
27
- this.timer.unref();
28
- }
29
- succeed(message = this.activeMessage) {
30
- this.finish("ok", message);
31
- }
32
- fail(message = this.activeMessage) {
33
- this.finish("!!", message);
34
- }
35
- finish(marker, message) {
36
- if (this.timer)
37
- clearInterval(this.timer);
38
- this.timer = undefined;
39
- this.activeMessage = undefined;
40
- if (!message)
41
- return;
42
- const line = `[${marker}] ${message}`;
43
- if (this.stream.isTTY) {
44
- this.render(line);
45
- this.stream.write("\n");
46
- this.lastWidth = 0;
47
- return;
48
- }
49
- this.stream.write(`${line}\n`);
50
- }
51
- render(value) {
52
- this.stream.write(`\r${value.padEnd(this.lastWidth)}`);
53
- this.lastWidth = value.length;
54
- }
55
- }
56
- export const silentProgress = {
57
- start: () => { },
58
- succeed: () => { },
59
- fail: () => { },
60
- };
@@ -1 +0,0 @@
1
- export declare function terminalPrompt(question: string): Promise<string>;
@@ -1,10 +0,0 @@
1
- import { createInterface } from "node:readline/promises";
2
- export async function terminalPrompt(question) {
3
- const prompt = createInterface({ input: process.stdin, output: process.stderr });
4
- try {
5
- return await prompt.question(`? ${question}`);
6
- }
7
- finally {
8
- prompt.close();
9
- }
10
- }
@@ -1,9 +0,0 @@
1
- import type { SourceBundle } from "./types.js";
2
- export declare function validatePiPackages(root: string): Promise<void>;
3
- export declare function findDeclarationRoot(start: string): Promise<string>;
4
- /**
5
- * Build one deterministic local-source snapshot. Git metadata and timestamps
6
- * are deliberately absent: the bytes the user deploys are the revision input,
7
- * and equal directory contents must produce the same identity on every machine.
8
- */
9
- export declare function buildSourceBundle(root: string): Promise<SourceBundle>;
@@ -1,209 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { lstat, readFile, readdir } from "node:fs/promises";
3
- import path from "node:path";
4
- import ignore from "ignore";
5
- import tar from "tar-stream";
6
- import { piSessionContract, sourceBundleContract } from "./generated/contracts.js";
7
- const MAX_BUNDLE_BYTES = sourceBundleContract.max_bytes;
8
- const SESSION_SOURCE_DIRECTORY = piSessionContract.session_directory.replace(/^\/+|\/+$/g, "");
9
- const DEFAULT_IGNORES = [
10
- ".git/",
11
- "node_modules/",
12
- ".DS_Store",
13
- ".env",
14
- ".env.*",
15
- "!.env.example",
16
- ".tonbo-cache/",
17
- ".tonbo-system/",
18
- ".pi/npm/",
19
- ".pi/git/",
20
- ];
21
- const EXACT_NPM_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
22
- const GIT_COMMIT = /^[0-9a-f]{40}$/i;
23
- function packageSource(value, index) {
24
- if (typeof value === "string")
25
- return value;
26
- if (value && typeof value === "object" && "source" in value && typeof value.source === "string")
27
- return value.source;
28
- throw new Error(`.pi/settings.json packages[${index}] must be a source string or object.`);
29
- }
30
- async function validatePackageSource(root, source) {
31
- if (source.startsWith("npm:")) {
32
- const specifier = source.slice(4);
33
- const separator = specifier.lastIndexOf("@");
34
- if (separator <= 0 || !EXACT_NPM_VERSION.test(specifier.slice(separator + 1))) {
35
- throw new Error(`PI package ${source} must pin an exact npm version, for example npm:my-agent@1.2.3.`);
36
- }
37
- return;
38
- }
39
- if (source.startsWith("git:")) {
40
- const separator = source.lastIndexOf("@");
41
- if (separator <= "git:".length || !GIT_COMMIT.test(source.slice(separator + 1))) {
42
- throw new Error(`PI package ${source} must pin a full 40-character Git commit.`);
43
- }
44
- return;
45
- }
46
- if (source.startsWith("./") || source.startsWith("../")) {
47
- const settingsDirectory = path.join(root, ".pi");
48
- const resolved = path.resolve(settingsDirectory, source);
49
- const relative = path.relative(root, resolved);
50
- if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
51
- throw new Error(`Local PI package ${source} resolves outside the deployed project.`);
52
- }
53
- let metadata;
54
- try {
55
- metadata = await lstat(resolved);
56
- }
57
- catch (error) {
58
- if (error.code === "ENOENT") {
59
- throw new Error(`Local PI package ${source} does not exist.`);
60
- }
61
- throw error;
62
- }
63
- if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
64
- throw new Error(`Local PI package ${source} must resolve to a real project directory.`);
65
- }
66
- return;
67
- }
68
- throw new Error(`PI package ${source} must use an exact npm version, a full Git commit, or a project-local path.`);
69
- }
70
- export async function validatePiPackages(root) {
71
- const filename = path.join(root, ".pi", "settings.json");
72
- let settings;
73
- try {
74
- settings = JSON.parse(await readFile(filename, "utf8"));
75
- }
76
- catch (error) {
77
- if (error.code === "ENOENT")
78
- return;
79
- throw new Error(`Could not read ${filename} as JSON.`, { cause: error });
80
- }
81
- if (!settings || typeof settings !== "object" || !("packages" in settings))
82
- return;
83
- const packages = settings.packages;
84
- if (!Array.isArray(packages))
85
- throw new Error(`${filename} packages must be an array.`);
86
- await Promise.all(packages.map((value, index) => validatePackageSource(root, packageSource(value, index))));
87
- }
88
- export async function findDeclarationRoot(start) {
89
- let candidate = path.resolve(start);
90
- for (;;) {
91
- try {
92
- if ((await lstat(path.join(candidate, ".tonbo"))).isFile())
93
- return candidate;
94
- }
95
- catch (error) {
96
- if (error.code !== "ENOENT")
97
- throw error;
98
- }
99
- const parent = path.dirname(candidate);
100
- if (parent === candidate) {
101
- throw new Error(`No .tonbo declaration found above ${path.resolve(start)}.`);
102
- }
103
- candidate = parent;
104
- }
105
- }
106
- async function sourceIgnore(root) {
107
- const matcher = ignore().add(DEFAULT_IGNORES);
108
- try {
109
- matcher.add(await readFile(path.join(root, ".tonboignore"), "utf8"));
110
- }
111
- catch (error) {
112
- if (error.code !== "ENOENT")
113
- throw error;
114
- }
115
- return matcher;
116
- }
117
- async function collectFiles(root) {
118
- const matcher = await sourceIgnore(root);
119
- const files = [];
120
- async function visit(directory, relativeDirectory) {
121
- const entries = await readdir(directory, { withFileTypes: true });
122
- entries.sort((left, right) => left.name.localeCompare(right.name, "en"));
123
- for (const entry of entries) {
124
- const relative = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name;
125
- const ignored = matcher.ignores(relative + (entry.isDirectory() ? "/" : ""));
126
- if (ignored)
127
- continue;
128
- if (relative === SESSION_SOURCE_DIRECTORY ||
129
- relative.startsWith(`${SESSION_SOURCE_DIRECTORY}/`)) {
130
- throw new Error(`Source path ${SESSION_SOURCE_DIRECTORY}/ is reserved for durable PI history. Rename it or exclude it with .tonboignore.`);
131
- }
132
- const absolute = path.join(directory, entry.name);
133
- if (entry.isDirectory()) {
134
- await visit(absolute, relative);
135
- continue;
136
- }
137
- const metadata = await lstat(absolute);
138
- if (!metadata.isFile()) {
139
- throw new Error(`Source path ${relative} is not a regular file. V1 does not follow symlinks or special files.`);
140
- }
141
- files.push({
142
- absolute,
143
- mode: metadata.mode & 0o111 ? 0o755 : 0o644,
144
- relative,
145
- size: metadata.size,
146
- });
147
- }
148
- }
149
- await visit(root, "");
150
- if (!files.some((file) => file.relative === ".tonbo")) {
151
- throw new Error("The source bundle must contain .tonbo.");
152
- }
153
- return files;
154
- }
155
- function addEntry(pack, file, contents) {
156
- return new Promise((resolve, reject) => {
157
- pack.entry({
158
- gid: 0,
159
- mode: file.mode,
160
- mtime: new Date(0),
161
- name: file.relative,
162
- size: contents.length,
163
- type: "file",
164
- uid: 0,
165
- }, contents, (error) => (error ? reject(error) : resolve()));
166
- });
167
- }
168
- /**
169
- * Build one deterministic local-source snapshot. Git metadata and timestamps
170
- * are deliberately absent: the bytes the user deploys are the revision input,
171
- * and equal directory contents must produce the same identity on every machine.
172
- */
173
- export async function buildSourceBundle(root) {
174
- const resolvedRoot = path.resolve(root);
175
- await validatePiPackages(resolvedRoot);
176
- const files = await collectFiles(resolvedRoot);
177
- const payloadBytes = files.reduce((total, file) => total + file.size, 0);
178
- if (payloadBytes > MAX_BUNDLE_BYTES) {
179
- throw new Error(`Source files exceed ${MAX_BUNDLE_BYTES} bytes after ignore rules.`);
180
- }
181
- const pack = tar.pack();
182
- const chunks = [];
183
- let size = 0;
184
- pack.on("data", (chunk) => {
185
- size += chunk.length;
186
- if (size > MAX_BUNDLE_BYTES) {
187
- pack.destroy(new Error(`Source bundle exceeds ${MAX_BUNDLE_BYTES} bytes after ignore rules.`));
188
- return;
189
- }
190
- chunks.push(chunk);
191
- });
192
- const completed = new Promise((resolve, reject) => {
193
- pack.on("end", resolve);
194
- pack.on("error", reject);
195
- });
196
- for (const file of files) {
197
- await addEntry(pack, file, await readFile(file.absolute));
198
- }
199
- pack.finalize();
200
- await completed;
201
- const bytes = Buffer.concat(chunks);
202
- return {
203
- bytes,
204
- format: sourceBundleContract.format,
205
- root: resolvedRoot,
206
- sha256: createHash("sha256").update(bytes).digest("hex"),
207
- size_bytes: bytes.length,
208
- };
209
- }
@@ -1,9 +0,0 @@
1
- export interface SshPublicKey {
2
- algorithm: string;
3
- fingerprint: string;
4
- keyBase64: string;
5
- label: string;
6
- }
7
- export declare function parseOpenSshPublicKey(value: string, label: string): SshPublicKey;
8
- export declare function readSshPublicKey(path: string): Promise<SshPublicKey>;
9
- export declare function readDefaultSshPublicKeys(sshDirectory?: string): Promise<SshPublicKey[]>;
@@ -1,36 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { readFile } from "node:fs/promises";
3
- import { homedir } from "node:os";
4
- import { basename, join } from "node:path";
5
- const DEFAULT_PUBLIC_KEYS = ["id_ed25519.pub", "id_ecdsa.pub", "id_rsa.pub"];
6
- const ALGORITHMS = new Set(["ssh-ed25519", "ecdsa-sha2-nistp256", "ssh-rsa"]);
7
- export function parseOpenSshPublicKey(value, label) {
8
- const fields = value.trim().split(/\s+/);
9
- if (fields.length < 2 || !ALGORITHMS.has(fields[0]))
10
- throw new Error("SSH public key must be Ed25519, ECDSA P-256, or RSA.");
11
- const blob = Buffer.from(fields[1], "base64");
12
- if (blob.length < 32 || blob.length > 16_384 || blob.toString("base64") !== fields[1])
13
- throw new Error("SSH public key is not canonical base64.");
14
- return {
15
- algorithm: fields[0],
16
- fingerprint: `SHA256:${createHash("sha256").update(blob).digest("base64").replace(/=$/, "")}`,
17
- keyBase64: fields[1],
18
- label,
19
- };
20
- }
21
- export async function readSshPublicKey(path) {
22
- return parseOpenSshPublicKey(await readFile(path, "utf8"), basename(path, ".pub"));
23
- }
24
- export async function readDefaultSshPublicKeys(sshDirectory = join(homedir(), ".ssh")) {
25
- const keys = [];
26
- for (const name of DEFAULT_PUBLIC_KEYS) {
27
- const key = await readSshPublicKey(join(sshDirectory, name)).catch((error) => {
28
- if (error.code === "ENOENT")
29
- return null;
30
- throw error;
31
- });
32
- if (key)
33
- keys.push(key);
34
- }
35
- return keys;
36
- }
package/dist/src/ssh.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export declare const PROJECT_SSH_HOST = "tonbo.sh";
2
- export declare function projectSshDestination(projectSlug: string): string;
3
- export declare function launchProjectSsh(projectSlug: string): Promise<number>;
package/dist/src/ssh.js DELETED
@@ -1,22 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- export const PROJECT_SSH_HOST = "tonbo.sh";
3
- export function projectSshDestination(projectSlug) {
4
- return `${projectSlug}@${PROJECT_SSH_HOST}`;
5
- }
6
- export async function launchProjectSsh(projectSlug) {
7
- return new Promise((resolve, reject) => {
8
- // Native OpenSSH owns key selection, known_hosts and terminal behavior;
9
- // terminal behavior. The Project slug is routing metadata; the selected
10
- // public key is the user identity checked by Tonbo's SSH gateway.
11
- const child = spawn("ssh", [projectSshDestination(projectSlug)], {
12
- stdio: "inherit",
13
- });
14
- child.once("error", reject);
15
- child.once("exit", (code, signal) => {
16
- if (signal)
17
- reject(new Error(`ssh was terminated by ${signal}`));
18
- else
19
- resolve(code ?? 1);
20
- });
21
- });
22
- }
@@ -1,87 +0,0 @@
1
- export interface TonboDeclaration {
2
- version: 1;
3
- agent: PiAgent;
4
- inference: {
5
- model: string;
6
- };
7
- build?: {
8
- command: string[];
9
- };
10
- service?: {
11
- command: string[];
12
- secrets?: string[];
13
- kubernetes?: {
14
- profile: string;
15
- };
16
- };
17
- }
18
- export interface ManagedRevisionSpec {
19
- version: 1;
20
- agent: PiAgent;
21
- source: {
22
- format: "tar-v1";
23
- sha256: string;
24
- size_bytes: number;
25
- };
26
- inference: {
27
- model: string;
28
- };
29
- service?: {
30
- command: string[];
31
- secrets?: string[];
32
- kubernetes?: {
33
- profile: string;
34
- };
35
- };
36
- }
37
- export type PiAgent = {
38
- runtime: "pi";
39
- secrets?: string[];
40
- driver: {
41
- kind: "native";
42
- } | {
43
- kind: "command";
44
- protocol: "pi-rpc-v1";
45
- command: string[];
46
- };
47
- };
48
- export interface SourceBundle {
49
- bytes: Buffer;
50
- format: "tar-v1";
51
- root: string;
52
- sha256: string;
53
- size_bytes: number;
54
- }
55
- export interface ProjectSummary {
56
- id: string;
57
- name: string;
58
- slug: string;
59
- status: string;
60
- }
61
- export interface ProjectBinding {
62
- projectId: string;
63
- projectSlug: string;
64
- }
65
- export interface ProjectSession {
66
- id: string;
67
- created_revision_id: string;
68
- project_id: string;
69
- }
70
- export interface ProjectSessionTurn {
71
- assistant_text: string;
72
- session_id: string;
73
- turn_id: string;
74
- }
75
- export interface ProjectSessionTurnEvent {
76
- sequence: number;
77
- event_type: "run.started" | "tool.started" | "tool.completed" | "run.completed" | "run.failed";
78
- payload: Record<string, unknown>;
79
- created_at: string;
80
- }
81
- export interface OAuthTokenSet {
82
- access_token: string;
83
- expires_at?: number;
84
- expires_in?: number;
85
- refresh_token?: string;
86
- token_type: string;
87
- }
package/dist/src/types.js DELETED
@@ -1 +0,0 @@
1
- export {};