@tonbo/cli 0.0.6 → 0.1.0
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 +93 -27
- package/dist/bin/tonbo.js +2629 -0
- package/package.json +12 -9
- package/dist/src/api.d.ts +0 -70
- package/dist/src/api.js +0 -179
- package/dist/src/app.d.ts +0 -4
- package/dist/src/app.js +0 -116
- package/dist/src/auth.d.ts +0 -20
- package/dist/src/auth.js +0 -196
- package/dist/src/build.d.ts +0 -1
- package/dist/src/build.js +0 -21
- package/dist/src/callback-page.d.ts +0 -23
- package/dist/src/callback-page.js +0 -145
- package/dist/src/commands.d.ts +0 -48
- package/dist/src/commands.js +0 -270
- package/dist/src/config.d.ts +0 -13
- package/dist/src/config.js +0 -46
- package/dist/src/contracts.d.ts +0 -3
- package/dist/src/contracts.js +0 -25
- package/dist/src/credentials.d.ts +0 -17
- package/dist/src/credentials.js +0 -90
- package/dist/src/declaration.d.ts +0 -10
- package/dist/src/declaration.js +0 -97
- package/dist/src/generated/contracts.d.ts +0 -221
- package/dist/src/generated/contracts.js +0 -261
- package/dist/src/http.d.ts +0 -6
- package/dist/src/http.js +0 -19
- package/dist/src/main.d.ts +0 -2
- package/dist/src/main.js +0 -9
- package/dist/src/progress.d.ts +0 -25
- package/dist/src/progress.js +0 -60
- package/dist/src/prompt.d.ts +0 -1
- package/dist/src/prompt.js +0 -10
- package/dist/src/source.d.ts +0 -9
- package/dist/src/source.js +0 -209
- package/dist/src/ssh-key.d.ts +0 -9
- package/dist/src/ssh-key.js +0 -36
- package/dist/src/ssh.d.ts +0 -3
- package/dist/src/ssh.js +0 -22
- package/dist/src/types.d.ts +0 -87
- package/dist/src/types.js +0 -1
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The page a browser lands on after `tonbo login`.
|
|
3
|
-
*
|
|
4
|
-
* This is served by a loopback HTTP server inside the CLI, so it cannot reach
|
|
5
|
-
* the app's stylesheet or webfonts. It is self-contained on purpose: no
|
|
6
|
-
* external request, which also means it renders offline and leaks nothing
|
|
7
|
-
* about the login to a third party.
|
|
8
|
-
*
|
|
9
|
-
* Nothing from the query string is interpolated here. The callback URL is
|
|
10
|
-
* reachable by anything that can make the user's browser open a localhost
|
|
11
|
-
* address, so reflecting `error_description` would put attacker-chosen text
|
|
12
|
-
* into a page served from the user's own machine.
|
|
13
|
-
*/
|
|
14
|
-
export type CallbackOutcome = "complete" | "denied" | "invalid";
|
|
15
|
-
export declare function renderCallbackPage(outcome: CallbackOutcome): string;
|
|
16
|
-
export declare function callbackResponse(outcome: CallbackOutcome): {
|
|
17
|
-
status: number;
|
|
18
|
-
headers: {
|
|
19
|
-
"content-type": string;
|
|
20
|
-
"cache-control": string;
|
|
21
|
-
};
|
|
22
|
-
body: string;
|
|
23
|
-
};
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The page a browser lands on after `tonbo login`.
|
|
3
|
-
*
|
|
4
|
-
* This is served by a loopback HTTP server inside the CLI, so it cannot reach
|
|
5
|
-
* the app's stylesheet or webfonts. It is self-contained on purpose: no
|
|
6
|
-
* external request, which also means it renders offline and leaks nothing
|
|
7
|
-
* about the login to a third party.
|
|
8
|
-
*
|
|
9
|
-
* Nothing from the query string is interpolated here. The callback URL is
|
|
10
|
-
* reachable by anything that can make the user's browser open a localhost
|
|
11
|
-
* address, so reflecting `error_description` would put attacker-chosen text
|
|
12
|
-
* into a page served from the user's own machine.
|
|
13
|
-
*/
|
|
14
|
-
// tonbo.io's wordmark. Kept as text so it needs no asset and stays sharp at
|
|
15
|
-
// any density; a monospace stack is the only thing it depends on.
|
|
16
|
-
const WORDMARK = [
|
|
17
|
-
"███▀█████████████▀████████",
|
|
18
|
-
"██▄ ▄██▀▄ ██ ▄▀██ ▄ ██ ▄▀█",
|
|
19
|
-
"███▄▄██▄▄███▄█▄██▄▄████▄▄█",
|
|
20
|
-
].join("\n");
|
|
21
|
-
const OUTCOMES = {
|
|
22
|
-
complete: {
|
|
23
|
-
status: 200,
|
|
24
|
-
title: "You are signed in.",
|
|
25
|
-
body: "Return to your terminal. This tab can be closed.",
|
|
26
|
-
},
|
|
27
|
-
denied: {
|
|
28
|
-
status: 400,
|
|
29
|
-
title: "Login was not completed.",
|
|
30
|
-
body: "Nothing was authorized. Return to your terminal to try again.",
|
|
31
|
-
},
|
|
32
|
-
invalid: {
|
|
33
|
-
// The state did not match, the code was missing, or the path was wrong.
|
|
34
|
-
// Say that it could not be verified rather than which check failed: the
|
|
35
|
-
// person who can read this page is not necessarily the one who started
|
|
36
|
-
// the login.
|
|
37
|
-
status: 400,
|
|
38
|
-
title: "This callback could not be verified.",
|
|
39
|
-
body: "Return to your terminal and start the login again.",
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
export function renderCallbackPage(outcome) {
|
|
43
|
-
const { title, body } = OUTCOMES[outcome];
|
|
44
|
-
return `<!doctype html>
|
|
45
|
-
<html lang="en">
|
|
46
|
-
<head>
|
|
47
|
-
<meta charset="utf-8">
|
|
48
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
49
|
-
<meta name="robots" content="noindex">
|
|
50
|
-
<title>Tonbo CLI</title>
|
|
51
|
-
<style>
|
|
52
|
-
:root {
|
|
53
|
-
--paper: #f3f2ee;
|
|
54
|
-
--ink: #1d1b17;
|
|
55
|
-
--rule: #c9c5ba;
|
|
56
|
-
--quiet: #5c564a;
|
|
57
|
-
--signal: #d64a03;
|
|
58
|
-
}
|
|
59
|
-
@media (prefers-color-scheme: dark) {
|
|
60
|
-
:root {
|
|
61
|
-
--paper: #1d1b17;
|
|
62
|
-
--ink: #f3f2ee;
|
|
63
|
-
--rule: #3a362e;
|
|
64
|
-
--quiet: #a49e8f;
|
|
65
|
-
--signal: #f08b4b;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
* { box-sizing: border-box; }
|
|
69
|
-
body {
|
|
70
|
-
margin: 0;
|
|
71
|
-
min-height: 100vh;
|
|
72
|
-
display: grid;
|
|
73
|
-
grid-template-rows: auto 1fr;
|
|
74
|
-
padding: 0 clamp(20px, 5vw, 72px);
|
|
75
|
-
background: var(--paper);
|
|
76
|
-
color: var(--ink);
|
|
77
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
78
|
-
-webkit-font-smoothing: antialiased;
|
|
79
|
-
}
|
|
80
|
-
header { padding: 28px 0; }
|
|
81
|
-
/* Same metrics as .auth-wordmark in the app: the glyphs are the ground
|
|
82
|
-
and the letters are the gaps, so the 8px/10px/scaleY(0.8) combination is
|
|
83
|
-
what makes the rows meet without seams. Iosevka is not available here,
|
|
84
|
-
so it falls back to the platform monospace. */
|
|
85
|
-
pre.wordmark {
|
|
86
|
-
margin: 0;
|
|
87
|
-
font-family: 'Iosevka', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
|
|
88
|
-
font-size: 8px;
|
|
89
|
-
font-weight: 400;
|
|
90
|
-
line-height: 10px;
|
|
91
|
-
letter-spacing: 0;
|
|
92
|
-
white-space: pre;
|
|
93
|
-
color: var(--signal);
|
|
94
|
-
transform: scaleY(0.8);
|
|
95
|
-
transform-origin: left top;
|
|
96
|
-
}
|
|
97
|
-
main {
|
|
98
|
-
display: flex;
|
|
99
|
-
flex-direction: column;
|
|
100
|
-
justify-content: center;
|
|
101
|
-
max-width: 46ch;
|
|
102
|
-
padding-bottom: 12vh;
|
|
103
|
-
}
|
|
104
|
-
h1 {
|
|
105
|
-
margin: 0;
|
|
106
|
-
font-size: 26px;
|
|
107
|
-
font-weight: 300;
|
|
108
|
-
line-height: 1.16;
|
|
109
|
-
letter-spacing: -0.018em;
|
|
110
|
-
}
|
|
111
|
-
p {
|
|
112
|
-
margin: 14px 0 0;
|
|
113
|
-
font-size: 16px;
|
|
114
|
-
line-height: 1.55;
|
|
115
|
-
color: var(--quiet);
|
|
116
|
-
}
|
|
117
|
-
hr {
|
|
118
|
-
width: 40px;
|
|
119
|
-
margin: 28px 0 0;
|
|
120
|
-
border: 0;
|
|
121
|
-
border-top: 1px solid var(--rule);
|
|
122
|
-
}
|
|
123
|
-
</style>
|
|
124
|
-
</head>
|
|
125
|
-
<body>
|
|
126
|
-
<header><pre class="wordmark" aria-hidden="true">${WORDMARK}</pre></header>
|
|
127
|
-
<main>
|
|
128
|
-
<h1>${title}</h1>
|
|
129
|
-
<p>${body}</p>
|
|
130
|
-
<hr>
|
|
131
|
-
</main>
|
|
132
|
-
</body>
|
|
133
|
-
</html>
|
|
134
|
-
`;
|
|
135
|
-
}
|
|
136
|
-
export function callbackResponse(outcome) {
|
|
137
|
-
return {
|
|
138
|
-
status: OUTCOMES[outcome].status,
|
|
139
|
-
headers: {
|
|
140
|
-
"content-type": "text/html; charset=utf-8",
|
|
141
|
-
"cache-control": "no-store",
|
|
142
|
-
},
|
|
143
|
-
body: renderCallbackPage(outcome),
|
|
144
|
-
};
|
|
145
|
-
}
|
package/dist/src/commands.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { AuthClient } from "./auth.js";
|
|
2
|
-
import type { TonboApi } from "./api.js";
|
|
3
|
-
import type { ConfigStore } from "./config.js";
|
|
4
|
-
import type { ProjectSummary } from "./types.js";
|
|
5
|
-
import { readDefaultSshPublicKeys } from "./ssh-key.js";
|
|
6
|
-
import type { ProgressReporter } from "./progress.js";
|
|
7
|
-
export interface CommandDependencies {
|
|
8
|
-
api: TonboApi;
|
|
9
|
-
auth: AuthClient;
|
|
10
|
-
config: ConfigStore;
|
|
11
|
-
cwd: () => string;
|
|
12
|
-
defaultSshPublicKeys: typeof readDefaultSshPublicKeys;
|
|
13
|
-
interactive: () => boolean;
|
|
14
|
-
output: (value: unknown) => void;
|
|
15
|
-
progress: ProgressReporter;
|
|
16
|
-
prompt: (question: string) => Promise<string>;
|
|
17
|
-
executable?: () => string;
|
|
18
|
-
secretValue: (name: string, fromEnvironment?: string) => Promise<string>;
|
|
19
|
-
}
|
|
20
|
-
export declare function resolveProject(deps: CommandDependencies, selector?: string): Promise<{
|
|
21
|
-
oauthToken: string;
|
|
22
|
-
project: ProjectSummary;
|
|
23
|
-
}>;
|
|
24
|
-
export declare function selectProject(projects: ProjectSummary[], selector: string): ProjectSummary;
|
|
25
|
-
export declare function initCommand(deps: CommandDependencies, options: {
|
|
26
|
-
agentEntry?: string;
|
|
27
|
-
buildCommand?: string[];
|
|
28
|
-
driver?: "native" | "command";
|
|
29
|
-
force?: boolean;
|
|
30
|
-
model?: string;
|
|
31
|
-
}): Promise<void>;
|
|
32
|
-
export declare function loginCommand(deps: CommandDependencies): Promise<void>;
|
|
33
|
-
export declare function sshKeyAddCommand(deps: CommandDependencies, path: string): Promise<void>;
|
|
34
|
-
export declare function sshKeyRemoveCommand(deps: CommandDependencies, fingerprint: string): Promise<void>;
|
|
35
|
-
export declare function projectUseCommand(deps: CommandDependencies, selector: string): Promise<void>;
|
|
36
|
-
export declare function projectCreateCommand(deps: CommandDependencies, slug: string, name?: string): Promise<void>;
|
|
37
|
-
export declare function deployCommand(deps: CommandDependencies, selector?: string): Promise<void>;
|
|
38
|
-
export declare function runCommand(deps: CommandDependencies, prompt: string, options: {
|
|
39
|
-
project?: string;
|
|
40
|
-
session?: string;
|
|
41
|
-
}): Promise<void>;
|
|
42
|
-
export declare function sshCommand(deps: CommandDependencies, selector?: string): Promise<void>;
|
|
43
|
-
export declare function secretListCommand(deps: CommandDependencies, selector?: string): Promise<void>;
|
|
44
|
-
export declare function secretSetCommand(deps: CommandDependencies, name: string, options: {
|
|
45
|
-
fromEnv?: string;
|
|
46
|
-
project?: string;
|
|
47
|
-
}): Promise<void>;
|
|
48
|
-
export declare function secretRemoveCommand(deps: CommandDependencies, name: string, selector?: string): Promise<void>;
|
package/dist/src/commands.js
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { runBuildCommand } from "./build.js";
|
|
3
|
-
import { buildRevision, createDeclaration, declarationExists, DECLARATION_FILENAME, DEFAULT_INFERENCE_MODEL, loadDeclaration, saveDeclaration, } from "./declaration.js";
|
|
4
|
-
import { buildSourceBundle, findDeclarationRoot } from "./source.js";
|
|
5
|
-
import { readSshPublicKey } from "./ssh-key.js";
|
|
6
|
-
import { launchProjectSsh } from "./ssh.js";
|
|
7
|
-
export async function resolveProject(deps, selector) {
|
|
8
|
-
const oauthToken = await deps.auth.accessToken();
|
|
9
|
-
if (selector) {
|
|
10
|
-
const normalized = selector.endsWith(".tonbo.sh")
|
|
11
|
-
? selector.slice(0, -".tonbo.sh".length)
|
|
12
|
-
: selector;
|
|
13
|
-
return {
|
|
14
|
-
oauthToken,
|
|
15
|
-
project: selectProject(await deps.api.listProjects(oauthToken), normalized),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const root = await findDeclarationRoot(deps.cwd());
|
|
19
|
-
const binding = await deps.config.getBinding(root);
|
|
20
|
-
if (!binding)
|
|
21
|
-
throw new Error("No Project selected. Run `tonbo project use <project>` first.");
|
|
22
|
-
return {
|
|
23
|
-
oauthToken,
|
|
24
|
-
project: {
|
|
25
|
-
id: binding.projectId,
|
|
26
|
-
slug: binding.projectSlug,
|
|
27
|
-
status: "active",
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
export function selectProject(projects, selector) {
|
|
32
|
-
const matches = projects.filter((project) => project.id === selector || project.slug === selector);
|
|
33
|
-
if (matches.length === 0)
|
|
34
|
-
throw new Error(`Project ${selector} was not found in your account.`);
|
|
35
|
-
if (matches.length > 1)
|
|
36
|
-
throw new Error(`Project slug ${selector} is ambiguous; use its ID.`);
|
|
37
|
-
if (matches[0].status !== "active")
|
|
38
|
-
throw new Error(`Project ${selector} is not active.`);
|
|
39
|
-
return matches[0];
|
|
40
|
-
}
|
|
41
|
-
export async function initCommand(deps, options) {
|
|
42
|
-
const root = deps.cwd();
|
|
43
|
-
const exists = await declarationExists(root);
|
|
44
|
-
let overwrite = options.force === true;
|
|
45
|
-
if (exists && !overwrite) {
|
|
46
|
-
if (!deps.interactive())
|
|
47
|
-
throw new Error(`${DECLARATION_FILENAME} already exists. Pass --force to replace it.`);
|
|
48
|
-
const answer = (await deps.prompt(`Replace existing ${DECLARATION_FILENAME}? [y/N] `))
|
|
49
|
-
.trim()
|
|
50
|
-
.toLowerCase();
|
|
51
|
-
if (answer !== "y" && answer !== "yes") {
|
|
52
|
-
deps.output({ message: `Kept existing ${DECLARATION_FILENAME}.` });
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
overwrite = true;
|
|
56
|
-
}
|
|
57
|
-
let driver = options.driver;
|
|
58
|
-
if (driver !== undefined && driver !== "native" && driver !== "command") {
|
|
59
|
-
throw new Error("--driver must be native or command.");
|
|
60
|
-
}
|
|
61
|
-
let shape = driver === "command" ? "sdk" : "project";
|
|
62
|
-
if (!driver && deps.interactive()) {
|
|
63
|
-
const answer = (await deps.prompt("Agent shape: [1] PI project [2] PI Package project [3] PI SDK app [1]: ")).trim();
|
|
64
|
-
if (!["", "1", "2", "3"].includes(answer)) {
|
|
65
|
-
throw new Error("Agent shape must be 1, 2, or 3.");
|
|
66
|
-
}
|
|
67
|
-
shape = answer === "2" ? "package" : answer === "3" ? "sdk" : "project";
|
|
68
|
-
driver = shape === "sdk" ? "command" : "native";
|
|
69
|
-
}
|
|
70
|
-
driver ||= "native";
|
|
71
|
-
if (driver === "native" && (options.agentEntry || options.buildCommand)) {
|
|
72
|
-
throw new Error("--agent-entry and --build-command require --driver command.");
|
|
73
|
-
}
|
|
74
|
-
let entry = options.agentEntry?.trim();
|
|
75
|
-
if (driver === "command" && !entry && deps.interactive()) {
|
|
76
|
-
entry = (await deps.prompt("PI SDK entry file [dist/agent.mjs]: ")).trim();
|
|
77
|
-
}
|
|
78
|
-
entry ||= "dist/agent.mjs";
|
|
79
|
-
let model = options.model?.trim();
|
|
80
|
-
if (!model && deps.interactive()) {
|
|
81
|
-
model = (await deps.prompt(`Inference model [${DEFAULT_INFERENCE_MODEL}]: `)).trim();
|
|
82
|
-
}
|
|
83
|
-
model ||= DEFAULT_INFERENCE_MODEL;
|
|
84
|
-
const buildCommand = driver === "command" ? (options.buildCommand ?? ["npm", "run", "build"]) : undefined;
|
|
85
|
-
const declaration = createDeclaration(model, driver === "native"
|
|
86
|
-
? { kind: "native" }
|
|
87
|
-
: { kind: "command", protocol: "pi-rpc-v1", command: ["node", entry] }, buildCommand);
|
|
88
|
-
await saveDeclaration(root, declaration, overwrite);
|
|
89
|
-
const shapeHint = shape === "package"
|
|
90
|
-
? "\nPI packages stay in .pi/settings.json; use exact npm versions or Git commits."
|
|
91
|
-
: shape === "sdk"
|
|
92
|
-
? `\nBuild: ${buildCommand?.join(" ")}\nEntrypoint: node ${entry}`
|
|
93
|
-
: "";
|
|
94
|
-
deps.output({
|
|
95
|
-
message: `${exists ? "Updated" : "Created"} ${DECLARATION_FILENAME}.${shapeHint}\nNext: tonbo project create <slug>`,
|
|
96
|
-
declaration,
|
|
97
|
-
path: path.join(root, DECLARATION_FILENAME),
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
export async function loginCommand(deps) {
|
|
101
|
-
try {
|
|
102
|
-
const tokens = await deps.auth.login((event) => reportLoginProgress(deps.progress, event));
|
|
103
|
-
deps.progress.start("Preparing SSH access");
|
|
104
|
-
const keys = await deps.defaultSshPublicKeys();
|
|
105
|
-
await Promise.all(keys.map((key) => deps.api.registerSshKey(tokens.access_token, key)));
|
|
106
|
-
deps.progress.succeed("SSH access ready");
|
|
107
|
-
deps.output({ message: "Logged in to Tonbo." });
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
deps.progress.fail();
|
|
111
|
-
throw error;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
const LOGIN_PROGRESS_MESSAGES = {
|
|
115
|
-
"account-config": {
|
|
116
|
-
started: "Connecting to Tonbo",
|
|
117
|
-
completed: "Connected to Tonbo",
|
|
118
|
-
},
|
|
119
|
-
"callback-server": {
|
|
120
|
-
started: "Starting local browser callback",
|
|
121
|
-
completed: "Local browser callback ready",
|
|
122
|
-
},
|
|
123
|
-
browser: {
|
|
124
|
-
started: "Opening browser",
|
|
125
|
-
completed: "Browser opened",
|
|
126
|
-
},
|
|
127
|
-
"browser-authorization": {
|
|
128
|
-
started: "Waiting for browser authorization",
|
|
129
|
-
completed: "Browser authorization received",
|
|
130
|
-
},
|
|
131
|
-
"callback-close": {
|
|
132
|
-
started: "Closing local browser callback",
|
|
133
|
-
completed: "Local browser callback closed",
|
|
134
|
-
},
|
|
135
|
-
"token-exchange": {
|
|
136
|
-
started: "Exchanging authorization code",
|
|
137
|
-
completed: "Authorization code exchanged",
|
|
138
|
-
},
|
|
139
|
-
"credential-store": {
|
|
140
|
-
started: "Saving login session",
|
|
141
|
-
completed: "Login session saved",
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
function reportLoginProgress(progress, event) {
|
|
145
|
-
const messages = LOGIN_PROGRESS_MESSAGES[event.step];
|
|
146
|
-
if (event.status === "started")
|
|
147
|
-
progress.start(messages.started);
|
|
148
|
-
else
|
|
149
|
-
progress.succeed(messages.completed);
|
|
150
|
-
}
|
|
151
|
-
export async function sshKeyAddCommand(deps, path) {
|
|
152
|
-
const key = await readSshPublicKey(path);
|
|
153
|
-
const oauthToken = await deps.auth.accessToken();
|
|
154
|
-
await deps.api.registerSshKey(oauthToken, key);
|
|
155
|
-
deps.output({ message: `Registered SSH key ${key.fingerprint}.`, key });
|
|
156
|
-
}
|
|
157
|
-
export async function sshKeyRemoveCommand(deps, fingerprint) {
|
|
158
|
-
const oauthToken = await deps.auth.accessToken();
|
|
159
|
-
const key = await deps.api.revokeSshKey(oauthToken, fingerprint);
|
|
160
|
-
deps.output({ message: `Revoked SSH key ${key.fingerprint}.`, key });
|
|
161
|
-
}
|
|
162
|
-
export async function projectUseCommand(deps, selector) {
|
|
163
|
-
const root = await findDeclarationRoot(deps.cwd());
|
|
164
|
-
const oauthToken = await deps.auth.accessToken();
|
|
165
|
-
const project = selectProject(await deps.api.listProjects(oauthToken), selector);
|
|
166
|
-
await deps.config.setBinding(root, {
|
|
167
|
-
projectId: project.id,
|
|
168
|
-
projectSlug: project.slug,
|
|
169
|
-
});
|
|
170
|
-
deps.output({ message: `Using Project ${project.slug}.`, project });
|
|
171
|
-
}
|
|
172
|
-
export async function projectCreateCommand(deps, slug, name) {
|
|
173
|
-
const root = await findDeclarationRoot(deps.cwd());
|
|
174
|
-
const oauthToken = await deps.auth.accessToken();
|
|
175
|
-
const project = await deps.api.createProject(oauthToken, slug, name);
|
|
176
|
-
await deps.config.setBinding(root, {
|
|
177
|
-
projectId: project.id,
|
|
178
|
-
projectSlug: project.slug,
|
|
179
|
-
});
|
|
180
|
-
deps.output({ message: `Created and selected Project ${project.slug}.`, project });
|
|
181
|
-
}
|
|
182
|
-
export async function deployCommand(deps, selector) {
|
|
183
|
-
const root = await findDeclarationRoot(deps.cwd());
|
|
184
|
-
const declaration = await loadDeclaration(root);
|
|
185
|
-
if (declaration.build)
|
|
186
|
-
await runBuildCommand(root, declaration.build.command);
|
|
187
|
-
const source = await buildSourceBundle(root);
|
|
188
|
-
const oauthToken = await deps.auth.accessToken();
|
|
189
|
-
let binding = await deps.config.getBinding(root);
|
|
190
|
-
if (selector) {
|
|
191
|
-
const project = selectProject(await deps.api.listProjects(oauthToken), selector);
|
|
192
|
-
binding = { projectId: project.id, projectSlug: project.slug };
|
|
193
|
-
}
|
|
194
|
-
if (!binding)
|
|
195
|
-
throw new Error("No Project selected. Run `tonbo project use <project>` first.");
|
|
196
|
-
const managementToken = await deps.api.exchangeManagementToken(oauthToken, binding.projectId);
|
|
197
|
-
const result = await deps.api.deploy({
|
|
198
|
-
bundle: source,
|
|
199
|
-
projectId: binding.projectId,
|
|
200
|
-
spec: buildRevision(declaration, source),
|
|
201
|
-
token: managementToken,
|
|
202
|
-
});
|
|
203
|
-
deps.output({
|
|
204
|
-
message: `Deployed Project ${binding.projectSlug}.`,
|
|
205
|
-
project: binding,
|
|
206
|
-
...result,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
export async function runCommand(deps, prompt, options) {
|
|
210
|
-
const root = await findDeclarationRoot(deps.cwd());
|
|
211
|
-
const oauthToken = await deps.auth.accessToken();
|
|
212
|
-
let binding = await deps.config.getBinding(root);
|
|
213
|
-
if (options.project) {
|
|
214
|
-
const project = selectProject(await deps.api.listProjects(oauthToken), options.project);
|
|
215
|
-
binding = { projectId: project.id, projectSlug: project.slug };
|
|
216
|
-
}
|
|
217
|
-
if (!binding)
|
|
218
|
-
throw new Error("No Project selected. Run `tonbo project use <project>` first.");
|
|
219
|
-
const managementToken = await deps.api.exchangeManagementToken(oauthToken, binding.projectId);
|
|
220
|
-
const result = await deps.api.run({
|
|
221
|
-
projectId: binding.projectId,
|
|
222
|
-
prompt,
|
|
223
|
-
sessionId: options.session,
|
|
224
|
-
token: managementToken,
|
|
225
|
-
});
|
|
226
|
-
deps.output({
|
|
227
|
-
message: result.turn.assistant_text,
|
|
228
|
-
project: binding,
|
|
229
|
-
...result,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
export async function sshCommand(deps, selector) {
|
|
233
|
-
const { project } = await resolveProject(deps, selector);
|
|
234
|
-
const code = await launchProjectSsh(project.slug);
|
|
235
|
-
if (code !== 0)
|
|
236
|
-
throw new Error(`ssh exited with status ${code}`);
|
|
237
|
-
}
|
|
238
|
-
async function projectManagement(deps, selector) {
|
|
239
|
-
const { oauthToken, project } = await resolveProject(deps, selector);
|
|
240
|
-
return {
|
|
241
|
-
project,
|
|
242
|
-
token: await deps.api.exchangeManagementToken(oauthToken, project.id),
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
export async function secretListCommand(deps, selector) {
|
|
246
|
-
const { project, token } = await projectManagement(deps, selector);
|
|
247
|
-
const secrets = await deps.api.listProjectSecrets(project.id, token);
|
|
248
|
-
deps.output({
|
|
249
|
-
message: secrets.length
|
|
250
|
-
? secrets.map((secret) => secret.name).join("\n")
|
|
251
|
-
: "No Project secrets are configured.",
|
|
252
|
-
project,
|
|
253
|
-
secrets,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
export async function secretSetCommand(deps, name, options) {
|
|
257
|
-
const value = await deps.secretValue(name, options.fromEnv);
|
|
258
|
-
const { project, token } = await projectManagement(deps, options.project);
|
|
259
|
-
const secret = await deps.api.setProjectSecret(project.id, name, value, token);
|
|
260
|
-
deps.output({
|
|
261
|
-
message: `Set Project secret ${secret.name}. Redeploy to replace the active runtime with this value.`,
|
|
262
|
-
project,
|
|
263
|
-
secret,
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
export async function secretRemoveCommand(deps, name, selector) {
|
|
267
|
-
const { project, token } = await projectManagement(deps, selector);
|
|
268
|
-
await deps.api.deleteProjectSecret(project.id, name, token);
|
|
269
|
-
deps.output({ message: `Removed Project secret ${name}.`, project, name });
|
|
270
|
-
}
|
package/dist/src/config.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ProjectBinding } from "./types.js";
|
|
2
|
-
export interface ConfigStore {
|
|
3
|
-
getBinding(declarationRoot: string): Promise<ProjectBinding | null>;
|
|
4
|
-
setBinding(declarationRoot: string, binding: ProjectBinding): Promise<void>;
|
|
5
|
-
}
|
|
6
|
-
export declare class FileConfigStore implements ConfigStore {
|
|
7
|
-
private readonly filename;
|
|
8
|
-
constructor(filename?: string);
|
|
9
|
-
getBinding(declarationRoot: string): Promise<ProjectBinding>;
|
|
10
|
-
setBinding(declarationRoot: string, binding: ProjectBinding): Promise<void>;
|
|
11
|
-
private read;
|
|
12
|
-
}
|
|
13
|
-
export declare function defaultConfigDirectory(): string;
|
package/dist/src/config.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
export class FileConfigStore {
|
|
5
|
-
filename;
|
|
6
|
-
constructor(filename = defaultConfigPath()) {
|
|
7
|
-
this.filename = filename;
|
|
8
|
-
}
|
|
9
|
-
async getBinding(declarationRoot) {
|
|
10
|
-
const config = await this.read();
|
|
11
|
-
return config.bindings[path.resolve(declarationRoot)] ?? null;
|
|
12
|
-
}
|
|
13
|
-
async setBinding(declarationRoot, binding) {
|
|
14
|
-
const config = await this.read();
|
|
15
|
-
config.bindings[path.resolve(declarationRoot)] = binding;
|
|
16
|
-
await mkdir(path.dirname(this.filename), { recursive: true, mode: 0o700 });
|
|
17
|
-
await writeFile(this.filename, `${JSON.stringify(config, null, 2)}\n`, {
|
|
18
|
-
mode: 0o600,
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
async read() {
|
|
22
|
-
try {
|
|
23
|
-
const value = JSON.parse(await readFile(this.filename, "utf8"));
|
|
24
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
25
|
-
throw new Error();
|
|
26
|
-
const bindings = value.bindings;
|
|
27
|
-
if (!bindings || typeof bindings !== "object" || Array.isArray(bindings))
|
|
28
|
-
throw new Error();
|
|
29
|
-
return { bindings: bindings };
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
if (error.code === "ENOENT")
|
|
33
|
-
return { bindings: {} };
|
|
34
|
-
throw new Error(`Could not read Tonbo config at ${this.filename}.`, {
|
|
35
|
-
cause: error,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function defaultConfigPath() {
|
|
41
|
-
return path.join(defaultConfigDirectory(), "config.json");
|
|
42
|
-
}
|
|
43
|
-
export function defaultConfigDirectory() {
|
|
44
|
-
const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
45
|
-
return path.join(base, "tonbo");
|
|
46
|
-
}
|
package/dist/src/contracts.d.ts
DELETED
package/dist/src/contracts.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
-
import { declarationSchema, kubernetesProfilesSchema, piAgentSchema, projectServiceSchema, revisionSchema, } from "./generated/contracts.js";
|
|
3
|
-
const ajv = new Ajv2020({ allErrors: true, useDefaults: true });
|
|
4
|
-
ajv.addKeyword({ keyword: "x-tonbo-profiles" });
|
|
5
|
-
ajv.addSchema(kubernetesProfilesSchema);
|
|
6
|
-
ajv.addSchema(piAgentSchema);
|
|
7
|
-
ajv.addSchema(projectServiceSchema);
|
|
8
|
-
const validateDeclaration = ajv.compile(declarationSchema);
|
|
9
|
-
const validateRevision = ajv.compile(revisionSchema);
|
|
10
|
-
function validationMessage(label, errors) {
|
|
11
|
-
const detail = errors?.map((error) => `${error.instancePath || "/"} ${error.message}`).join("; ");
|
|
12
|
-
return `${label} is invalid${detail ? `: ${detail}` : "."}`;
|
|
13
|
-
}
|
|
14
|
-
export function parseDeclaration(value) {
|
|
15
|
-
const candidate = structuredClone(value);
|
|
16
|
-
if (!validateDeclaration(candidate)) {
|
|
17
|
-
throw new Error(validationMessage(".tonbo TOML", validateDeclaration.errors));
|
|
18
|
-
}
|
|
19
|
-
return candidate;
|
|
20
|
-
}
|
|
21
|
-
export function assertManagedRevision(value) {
|
|
22
|
-
if (!validateRevision(value)) {
|
|
23
|
-
throw new Error(validationMessage("Managed revision", validateRevision.errors));
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { OAuthTokenSet } from "./types.js";
|
|
2
|
-
export interface CredentialStore {
|
|
3
|
-
load(): Promise<OAuthTokenSet | null>;
|
|
4
|
-
save(tokens: OAuthTokenSet): Promise<void>;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Human OAuth credentials live beside the CLI config, never in an Agent
|
|
8
|
-
* directory. The file is private and atomically replaced so a crash cannot
|
|
9
|
-
* leave a partially rotated refresh token. Symlinks are refused because this
|
|
10
|
-
* path contains bearer credentials and must not redirect writes elsewhere.
|
|
11
|
-
*/
|
|
12
|
-
export declare class FileCredentialStore implements CredentialStore {
|
|
13
|
-
private readonly filename;
|
|
14
|
-
constructor(filename?: string);
|
|
15
|
-
load(): Promise<OAuthTokenSet | null>;
|
|
16
|
-
save(tokens: OAuthTokenSet): Promise<void>;
|
|
17
|
-
}
|