@tonbo/cli 0.1.2 → 0.2.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 +21 -21
- package/dist/bin/tonbo.js +216 -226
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,11 +12,11 @@ The CLI deploys an Agent directly from a local directory without requiring Git o
|
|
|
12
12
|
```console
|
|
13
13
|
tonbo login
|
|
14
14
|
tonbo init
|
|
15
|
-
No Harness-specific configuration was found in this
|
|
15
|
+
No Harness-specific configuration was found in this agent.
|
|
16
16
|
? Which supported Harness should Tonbo use?
|
|
17
|
-
❯ PI (`pi`) Run this
|
|
17
|
+
❯ PI (`pi`) Run this agent with the pi command.
|
|
18
18
|
? How should this Agent connect to Tonbo?
|
|
19
|
-
❯ Create a new
|
|
19
|
+
❯ Create a new agent Create it only after you confirm the complete configuration.
|
|
20
20
|
Use an existing agent
|
|
21
21
|
Set up later
|
|
22
22
|
? Agent name [my-agent]:
|
|
@@ -26,11 +26,11 @@ Tonbo Agent configuration:
|
|
|
26
26
|
Harness: PI
|
|
27
27
|
Execution: PI CLI (`pi`)
|
|
28
28
|
Model: claude-sonnet-4-5
|
|
29
|
-
? Create
|
|
30
|
-
❯ Create
|
|
31
|
-
Edit configuration Change the
|
|
29
|
+
? Create agent and .tonbo with this configuration?
|
|
30
|
+
❯ Create agent and .tonbo
|
|
31
|
+
Edit configuration Change the agent, Harness, execution mode, model, or SDK entrypoint.
|
|
32
32
|
Cancel
|
|
33
|
-
Created
|
|
33
|
+
Created agent my-agent and .tonbo.
|
|
34
34
|
Agent: my-agent (my-agent-my-account.tonbo.sh)
|
|
35
35
|
Organization: my-account
|
|
36
36
|
Application: https://my-agent-my-account.tonbo.sh
|
|
@@ -39,7 +39,7 @@ Mode: PI CLI
|
|
|
39
39
|
Next: tonbo deploy
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Use the arrow keys and Enter to select a supported Harness and either create or select the one Agent owned by this source tree. Agent creation and file writes wait until the final review; cancelling leaves both local and cloud state unchanged. `AGENTS.md` is Harness-neutral and never identifies PI by itself. When `.pi/settings.json` exists, the shared source inspector identifies the PI Harness and reports its workspace-local packages. A regular PI
|
|
42
|
+
Use the arrow keys and Enter to select a supported Harness and either create or select the one Agent owned by this source tree. Agent creation and file writes wait until the final review; cancelling leaves both local and cloud state unchanged. `AGENTS.md` is Harness-neutral and never identifies PI by itself. When `.pi/settings.json` exists, the shared source inspector identifies the PI Harness and reports its workspace-local packages. A regular PI agent defaults to the native PI CLI; choose `Edit configuration` only when changing the Agent, execution mode, model, or another setting. Unsupported and planned Harnesses are not shown.
|
|
43
43
|
|
|
44
44
|
The generated `.tonbo` is TOML:
|
|
45
45
|
|
|
@@ -59,14 +59,14 @@ kind = "native"
|
|
|
59
59
|
model = "claude-sonnet-4-5"
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
The immutable full Agent hostname is the only durable Agent authority in the source tree; it is not a credential. This keeps Agents with the same name in different Organizations unambiguous. `deploy`, `run`, `ssh` and `secret` refuse an unbound declaration. `tonbo agent use <name-hostname-or-id>` accepts any convenient selector, resolves it through the signed-in account and writes the selected `
|
|
62
|
+
The immutable full Agent hostname is the only durable Agent authority in the source tree; it is not a credential. This keeps Agents with the same name in different Organizations unambiguous. `deploy`, `run`, `ssh` and `secret` refuse an unbound declaration. `tonbo agent use <name-hostname-or-id>` accepts any convenient selector, resolves it through the signed-in account and writes the selected `agent-organization.tonbo.sh` hostname into `.tonbo`; rebinding an existing declaration requires interactive confirmation or `--force`. UUIDs remain internal API identities and are never committed by the CLI.
|
|
63
63
|
|
|
64
64
|
The default inference model is written without prompting. Override it explicitly with `--model`. Headless initialization makes Harness and driver choices explicit and may bind the resulting declaration with `agent use`:
|
|
65
65
|
|
|
66
66
|
```console
|
|
67
67
|
tonbo init --harness pi --driver native
|
|
68
68
|
tonbo init --harness pi --driver command --agent-entry dist/agent.mjs
|
|
69
|
-
tonbo agent use my-
|
|
69
|
+
tonbo agent use my-agent-my-account.tonbo.sh
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
After interactive initialization, deploy the current contents:
|
|
@@ -88,12 +88,12 @@ Every `tonbo deploy` creates one immutable Deployment: the uploaded source, the
|
|
|
88
88
|
|
|
89
89
|
```console
|
|
90
90
|
$ tonbo deploy
|
|
91
|
-
Deployed
|
|
91
|
+
Deployed agent my-agent.
|
|
92
92
|
Organization: my-account
|
|
93
93
|
Deployment: Fix greeting (0f3a7c21)
|
|
94
94
|
Source: 0123456 · main
|
|
95
95
|
Production: promoting (generation 4)
|
|
96
|
-
Application: https://my-
|
|
96
|
+
Application: https://my-agent-my-account.tonbo.sh
|
|
97
97
|
|
|
98
98
|
Agent process: starts with the first turn and stays warm while the runtime is active.
|
|
99
99
|
|
|
@@ -101,7 +101,7 @@ Start the Agent:
|
|
|
101
101
|
tonbo run "<prompt>"
|
|
102
102
|
|
|
103
103
|
Connect with SSH:
|
|
104
|
-
ssh my-
|
|
104
|
+
ssh my-agent.my-account@tonbo.sh
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
When the new Deployment has the same contents as the Production Deployment it replaces, `deploy` says so; the record is still created. `tonbo deploy --no-promote` creates the Deployment without moving Production, for example to stage a build and promote it later:
|
|
@@ -120,7 +120,7 @@ tonbo deployments rollback 9b8c1d22 # or to a named one
|
|
|
120
120
|
Use `tonbo agent show` to rediscover the username, application address and SSH command later. `tonbo login` registers public keys found at the conventional `~/.ssh/id_ed25519.pub`, `id_ecdsa.pub` and `id_rsa.pub` paths. Register a key at another path with `tonbo ssh-key add <path.pub>` and revoke a lost or retired key with `tonbo ssh-key remove SHA256:...`. The CLI is not part of the SSH connection; ordinary OpenSSH, `scp` and `rsync` connect directly:
|
|
121
121
|
|
|
122
122
|
```console
|
|
123
|
-
ssh my-
|
|
123
|
+
ssh my-agent.my-account@tonbo.sh
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
The SSH user name is the Agent qualified by its username; the signed public key identifies the Tonbo user and is checked against membership and IAM at connection time. The CLI resolves the Agent name in `.tonbo` and prints the server-authoritative destination, so users never need to remember or assemble a username. The shell and HTTP turns may coexist on the singleton runtime, allowing the shell to observe the Agent while it works. Session writer fencing protects durable conversation history; ordinary workspace files retain Linux process concurrency semantics.
|
|
@@ -131,11 +131,11 @@ The platform injects scoped inference access and owns PI Session capture. Do not
|
|
|
131
131
|
|
|
132
132
|
Deploy snapshots regular files under `.tonbo`, excluding `.git`, `node_modules`, PI package caches, local environment files and patterns in `.tonboignore`. Coding tools start in the persistent Artifacts workspace, while `AGENTS.md` and trusted `.pi` extensions, skills, prompts and settings load from the Deployment's uploaded source.
|
|
133
133
|
|
|
134
|
-
## Supported PI
|
|
134
|
+
## Supported PI agent shapes
|
|
135
135
|
|
|
136
136
|
### 1. Agent-local Agent
|
|
137
137
|
|
|
138
|
-
Keep the normal PI
|
|
138
|
+
Keep the normal PI agent layout. Tonbo runs the same `AGENTS.md`, `.pi/APPEND_SYSTEM.md`, `.pi/settings.json`, `.pi/extensions`, `.pi/skills` and `.pi/prompts` that local `pi` loads:
|
|
139
139
|
|
|
140
140
|
```console
|
|
141
141
|
pi
|
|
@@ -144,9 +144,9 @@ tonbo deploy
|
|
|
144
144
|
tonbo run "Start the task."
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
### 2. PI Package
|
|
147
|
+
### 2. PI Package agent
|
|
148
148
|
|
|
149
|
-
Use PI's
|
|
149
|
+
Use PI's agent-local package declaration as the single source of truth. A package is a PI resource, not another Tonbo execution mode: `tonbo init` recognizes `.pi/settings.json`, then the native PI driver loads the packages. Install an immutable package reference locally, verify it with PI, and deploy the agent; Tonbo excludes `.pi/npm` and `.pi/git` caches and restores packages inside the isolated runtime:
|
|
150
150
|
|
|
151
151
|
```console
|
|
152
152
|
pi install -l npm:@acme/my-pi-agent@1.2.3
|
|
@@ -196,8 +196,8 @@ Node.js 22 or newer is required. Human OAuth tokens are stored at `${XDG_CONFIG_
|
|
|
196
196
|
- [Design documentation map](../../docs/design/README.md)
|
|
197
197
|
- [Agent source inspection and onboarding](../../docs/design/agent-source-onboarding.md)
|
|
198
198
|
- [Shared source inspector](../../packages/agent-source-inspector/README.md)
|
|
199
|
-
- [Agent resource model](../../docs/design/
|
|
200
|
-
- [Agent Deployments, Production and Rollouts](../../docs/design/
|
|
199
|
+
- [Agent resource model](../../docs/design/agent-resource-model.md)
|
|
200
|
+
- [Agent Deployments, Production and Rollouts](../../docs/design/agent-deployments.md)
|
|
201
201
|
- [ADR-0008: Tonbo CLI onboarding and credential boundaries](../../apps/user-center/docs/decisions/adr-0008-tonbo-cli-onboarding.md)
|
|
202
202
|
- [Management API v1](../../contracts/management/openapi-v1.yaml)
|
|
203
203
|
|
|
@@ -217,4 +217,4 @@ tonbo --json machine release <machine-uuid>
|
|
|
217
217
|
|
|
218
218
|
Only one active Agent binding per Machine and one Machine per Agent are currently supported. Unbinding drains execution and completes physical deletion, final usage settlement and storage credential revocation before the Machine becomes available. Wait for `available: true` in `machine show` before rebinding. Cross-Agent reassignment resets rootfs; the destination mounts its own Agent Workspace and Sessions. Hidden pause/resume within the same Agent retains rootfs and process state. Release also drains an existing binding automatically; it does not delete Agent files or Sessions.
|
|
219
219
|
|
|
220
|
-
`tonbo agent list` lists accessible Agents without requiring a local `.tonbo` file. JSON command output uses `agent` for one Agent and `agents` for the list; there is no duplicate `
|
|
220
|
+
`tonbo agent list` lists accessible Agents without requiring a local `.tonbo` file. JSON command output uses `agent` for one Agent and `agents` for the list; there is no duplicate `agent` output alias.
|
package/dist/bin/tonbo.js
CHANGED
|
@@ -49,14 +49,14 @@ var TonboApi = class {
|
|
|
49
49
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
listAgents(oauthToken) {
|
|
53
53
|
return requestJson(
|
|
54
54
|
this.fetcher,
|
|
55
55
|
`${this.accountOrigin}/api/cli/agents`,
|
|
56
56
|
{
|
|
57
57
|
headers: { authorization: `Bearer ${oauthToken}` }
|
|
58
58
|
}
|
|
59
|
-
).then((body) => body.
|
|
59
|
+
).then((body) => body.agents);
|
|
60
60
|
}
|
|
61
61
|
listOrganizations(oauthToken) {
|
|
62
62
|
return requestJson(
|
|
@@ -67,7 +67,7 @@ var TonboApi = class {
|
|
|
67
67
|
}
|
|
68
68
|
).then((body) => body.organizations);
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
createAgent(oauthToken, name, organizationId) {
|
|
71
71
|
return requestJson(
|
|
72
72
|
this.fetcher,
|
|
73
73
|
`${this.accountOrigin}/api/cli/agents`,
|
|
@@ -79,7 +79,7 @@ var TonboApi = class {
|
|
|
79
79
|
},
|
|
80
80
|
body: JSON.stringify(organizationId ? { name, orgId: organizationId } : { name })
|
|
81
81
|
}
|
|
82
|
-
).then((body) => body.
|
|
82
|
+
).then((body) => body.agent);
|
|
83
83
|
}
|
|
84
84
|
registerSshKey(oauthToken, key) {
|
|
85
85
|
return requestJson(
|
|
@@ -113,10 +113,10 @@ var TonboApi = class {
|
|
|
113
113
|
}
|
|
114
114
|
).then((body) => body.key);
|
|
115
115
|
}
|
|
116
|
-
exchangeManagementToken(oauthToken,
|
|
116
|
+
exchangeManagementToken(oauthToken, agentId) {
|
|
117
117
|
return requestJson(
|
|
118
118
|
this.fetcher,
|
|
119
|
-
`${this.accountOrigin}/api/cli/agents/${
|
|
119
|
+
`${this.accountOrigin}/api/cli/agents/${agentId}/token`,
|
|
120
120
|
{
|
|
121
121
|
method: "POST",
|
|
122
122
|
headers: { authorization: `Bearer ${oauthToken}` }
|
|
@@ -126,7 +126,7 @@ var TonboApi = class {
|
|
|
126
126
|
async deploy({
|
|
127
127
|
bundle,
|
|
128
128
|
origin,
|
|
129
|
-
|
|
129
|
+
agentId,
|
|
130
130
|
promote,
|
|
131
131
|
spec,
|
|
132
132
|
token
|
|
@@ -136,7 +136,7 @@ var TonboApi = class {
|
|
|
136
136
|
sha256: bundle.sha256,
|
|
137
137
|
size_bytes: bundle.size_bytes
|
|
138
138
|
};
|
|
139
|
-
const bundlesPath = `/v1/agents/${
|
|
139
|
+
const bundlesPath = `/v1/agents/${agentId}/source-bundles`;
|
|
140
140
|
const prepared = await this.management("PUT", `${bundlesPath}/${bundle.sha256}`, token, {
|
|
141
141
|
format: descriptor.format,
|
|
142
142
|
size_bytes: descriptor.size_bytes
|
|
@@ -166,14 +166,14 @@ var TonboApi = class {
|
|
|
166
166
|
throw error;
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
-
let deployment = await this.createDeployment(
|
|
170
|
-
const previous = await this.getProduction(
|
|
171
|
-
const previousDeployment = previous ? await this.getDeployment(
|
|
169
|
+
let deployment = await this.createDeployment(agentId, { spec, origin }, token);
|
|
170
|
+
const previous = await this.getProduction(agentId, token);
|
|
171
|
+
const previousDeployment = previous ? await this.getDeployment(agentId, previous.deployment_id, token) : null;
|
|
172
172
|
const unchanged = previousDeployment?.spec_sha256 === deployment.spec_sha256;
|
|
173
173
|
let production = previous;
|
|
174
174
|
if (promote) {
|
|
175
175
|
production = await this.putProduction(
|
|
176
|
-
|
|
176
|
+
agentId,
|
|
177
177
|
{
|
|
178
178
|
deployment_id: deployment.id,
|
|
179
179
|
desired_state: "running",
|
|
@@ -181,32 +181,32 @@ var TonboApi = class {
|
|
|
181
181
|
},
|
|
182
182
|
token
|
|
183
183
|
);
|
|
184
|
-
deployment = await this.getDeployment(
|
|
184
|
+
deployment = await this.getDeployment(agentId, deployment.id, token);
|
|
185
185
|
}
|
|
186
186
|
return { deployment, production, unchanged };
|
|
187
187
|
}
|
|
188
|
-
createDeployment(
|
|
188
|
+
createDeployment(agentId, body, token) {
|
|
189
189
|
return this.management(
|
|
190
190
|
"POST",
|
|
191
|
-
`/v1/agents/${
|
|
191
|
+
`/v1/agents/${agentId}/deployments`,
|
|
192
192
|
token,
|
|
193
193
|
body
|
|
194
194
|
).then((response) => response.data);
|
|
195
195
|
}
|
|
196
|
-
listDeployments(
|
|
197
|
-
return this.managementList(`/v1/agents/${
|
|
196
|
+
listDeployments(agentId, token) {
|
|
197
|
+
return this.managementList(`/v1/agents/${agentId}/deployments`, token);
|
|
198
198
|
}
|
|
199
|
-
getDeployment(
|
|
199
|
+
getDeployment(agentId, deploymentId, token) {
|
|
200
200
|
return this.management(
|
|
201
201
|
"GET",
|
|
202
|
-
`/v1/agents/${
|
|
202
|
+
`/v1/agents/${agentId}/deployments/${deploymentId}`,
|
|
203
203
|
token
|
|
204
204
|
).then((response) => response.data);
|
|
205
205
|
}
|
|
206
|
-
getProduction(
|
|
206
|
+
getProduction(agentId, token) {
|
|
207
207
|
return this.management(
|
|
208
208
|
"GET",
|
|
209
|
-
`/v1/agents/${
|
|
209
|
+
`/v1/agents/${agentId}/production`,
|
|
210
210
|
token
|
|
211
211
|
).then(
|
|
212
212
|
(response) => response.data,
|
|
@@ -216,44 +216,38 @@ var TonboApi = class {
|
|
|
216
216
|
}
|
|
217
217
|
);
|
|
218
218
|
}
|
|
219
|
-
putProduction(
|
|
219
|
+
putProduction(agentId, body, token) {
|
|
220
220
|
return this.management(
|
|
221
221
|
"PUT",
|
|
222
|
-
`/v1/agents/${
|
|
222
|
+
`/v1/agents/${agentId}/production`,
|
|
223
223
|
token,
|
|
224
224
|
body
|
|
225
225
|
).then((response) => response.data);
|
|
226
226
|
}
|
|
227
|
-
listRollouts(
|
|
228
|
-
return this.managementList(
|
|
229
|
-
`/v1/agents/${projectId}/production/rollouts`,
|
|
230
|
-
token
|
|
231
|
-
);
|
|
227
|
+
listRollouts(agentId, token) {
|
|
228
|
+
return this.managementList(`/v1/agents/${agentId}/production/rollouts`, token);
|
|
232
229
|
}
|
|
233
230
|
async run({
|
|
234
|
-
|
|
231
|
+
agentId,
|
|
235
232
|
prompt,
|
|
236
233
|
sessionId,
|
|
237
234
|
token,
|
|
238
235
|
turnId = randomUUID()
|
|
239
236
|
}) {
|
|
240
|
-
const
|
|
241
|
-
const production = await this.getProduction(
|
|
237
|
+
const agentPath = `/v1/agents/${agentId}`;
|
|
238
|
+
const production = await this.getProduction(agentId, token);
|
|
242
239
|
if (!production)
|
|
243
240
|
throw new Error("Agent has no Production Deployment; run `tonbo deploy` first.");
|
|
244
241
|
if (production.observed_state !== "running")
|
|
245
242
|
throw new Error(
|
|
246
243
|
`Production is ${production.observed_state}${production.last_error ? ` (${production.last_error})` : ""}; wait for it to be running.`
|
|
247
244
|
);
|
|
248
|
-
const session = sessionId ? { id: sessionId } : (await this.management(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
token,
|
|
252
|
-
{ deployment_id: production.deployment_id }
|
|
253
|
-
)).data;
|
|
245
|
+
const session = sessionId ? { id: sessionId } : (await this.management("POST", `${agentPath}/sessions`, token, {
|
|
246
|
+
deployment_id: production.deployment_id
|
|
247
|
+
})).data;
|
|
254
248
|
const turn = (await this.management(
|
|
255
249
|
"POST",
|
|
256
|
-
`${
|
|
250
|
+
`${agentPath}/sessions/${session.id}/turns`,
|
|
257
251
|
token,
|
|
258
252
|
{ prompt },
|
|
259
253
|
turnId
|
|
@@ -262,36 +256,36 @@ var TonboApi = class {
|
|
|
262
256
|
}
|
|
263
257
|
turnEvents({
|
|
264
258
|
after = 0,
|
|
265
|
-
|
|
259
|
+
agentId,
|
|
266
260
|
sessionId,
|
|
267
261
|
token,
|
|
268
262
|
turnId
|
|
269
263
|
}) {
|
|
270
264
|
return this.management(
|
|
271
265
|
"GET",
|
|
272
|
-
`/v1/agents/${
|
|
266
|
+
`/v1/agents/${agentId}/sessions/${sessionId}/turns/${turnId}/events?after=${after}`,
|
|
273
267
|
token
|
|
274
268
|
);
|
|
275
269
|
}
|
|
276
|
-
|
|
270
|
+
listAgentSecrets(agentId, token) {
|
|
277
271
|
return this.management(
|
|
278
272
|
"GET",
|
|
279
|
-
`/v1/agents/${
|
|
273
|
+
`/v1/agents/${agentId}/secrets`,
|
|
280
274
|
token
|
|
281
275
|
).then((body) => body.data);
|
|
282
276
|
}
|
|
283
|
-
|
|
277
|
+
setAgentSecret(agentId, name, value, token) {
|
|
284
278
|
return this.management(
|
|
285
279
|
"PUT",
|
|
286
|
-
`/v1/agents/${
|
|
280
|
+
`/v1/agents/${agentId}/secrets/${encodeURIComponent(name)}`,
|
|
287
281
|
token,
|
|
288
282
|
{ value }
|
|
289
283
|
);
|
|
290
284
|
}
|
|
291
|
-
|
|
285
|
+
deleteAgentSecret(agentId, name, token) {
|
|
292
286
|
return this.management(
|
|
293
287
|
"DELETE",
|
|
294
|
-
`/v1/agents/${
|
|
288
|
+
`/v1/agents/${agentId}/secrets/${encodeURIComponent(name)}`,
|
|
295
289
|
token
|
|
296
290
|
);
|
|
297
291
|
}
|
|
@@ -879,10 +873,10 @@ var piAgentSchema = {
|
|
|
879
873
|
}
|
|
880
874
|
}
|
|
881
875
|
};
|
|
882
|
-
var
|
|
876
|
+
var agentServiceSchema = {
|
|
883
877
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
884
|
-
"$id": "https://contracts.tonbo.dev/agents/
|
|
885
|
-
"title": "Tonbo
|
|
878
|
+
"$id": "https://contracts.tonbo.dev/agents/agent-service-v1.schema.json",
|
|
879
|
+
"title": "Tonbo Agent application service v1",
|
|
886
880
|
"type": "object",
|
|
887
881
|
"additionalProperties": false,
|
|
888
882
|
"required": [
|
|
@@ -982,10 +976,10 @@ var declarationSchema = {
|
|
|
982
976
|
}
|
|
983
977
|
},
|
|
984
978
|
"service": {
|
|
985
|
-
"$ref": "https://contracts.tonbo.dev/agents/
|
|
979
|
+
"$ref": "https://contracts.tonbo.dev/agents/agent-service-v1.schema.json"
|
|
986
980
|
},
|
|
987
981
|
"agent": {
|
|
988
|
-
"$ref": "https://contracts.tonbo.dev/agents/
|
|
982
|
+
"$ref": "https://contracts.tonbo.dev/agents/agent-name-v1.json#/$defs/publicHostname"
|
|
989
983
|
},
|
|
990
984
|
"harness": {
|
|
991
985
|
"$ref": "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json"
|
|
@@ -995,7 +989,7 @@ var declarationSchema = {
|
|
|
995
989
|
var deploymentSchema = {
|
|
996
990
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
997
991
|
"$id": "https://contracts.tonbo.dev/agents/managed-deployment-v1.schema.json",
|
|
998
|
-
"title": "Managed
|
|
992
|
+
"title": "Managed Agent Deployment v1",
|
|
999
993
|
"type": "object",
|
|
1000
994
|
"additionalProperties": false,
|
|
1001
995
|
"required": [
|
|
@@ -1049,7 +1043,7 @@ var deploymentSchema = {
|
|
|
1049
1043
|
}
|
|
1050
1044
|
},
|
|
1051
1045
|
"service": {
|
|
1052
|
-
"$ref": "https://contracts.tonbo.dev/agents/
|
|
1046
|
+
"$ref": "https://contracts.tonbo.dev/agents/agent-service-v1.schema.json"
|
|
1053
1047
|
}
|
|
1054
1048
|
}
|
|
1055
1049
|
};
|
|
@@ -1073,12 +1067,12 @@ var piSessionContract = {
|
|
|
1073
1067
|
"session-preflight"
|
|
1074
1068
|
]
|
|
1075
1069
|
};
|
|
1076
|
-
var
|
|
1070
|
+
var agentNameContract = {
|
|
1077
1071
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
1078
|
-
"$id": "https://contracts.tonbo.dev/agents/
|
|
1072
|
+
"$id": "https://contracts.tonbo.dev/agents/agent-name-v1.json",
|
|
1079
1073
|
"x-tonbo-version": 1,
|
|
1080
1074
|
"x-tonbo-public-hostname-apex": "tonbo.sh",
|
|
1081
|
-
"x-tonbo-public-hostname-template": "<
|
|
1075
|
+
"x-tonbo-public-hostname-template": "<agent>-<organization>.tonbo.sh",
|
|
1082
1076
|
"$defs": {
|
|
1083
1077
|
"name": {
|
|
1084
1078
|
"type": "string",
|
|
@@ -1129,8 +1123,8 @@ ajv.addKeyword({ keyword: "x-tonbo-public-hostname-apex" });
|
|
|
1129
1123
|
ajv.addKeyword({ keyword: "x-tonbo-public-hostname-template" });
|
|
1130
1124
|
ajv.addSchema(kubernetesProfilesSchema);
|
|
1131
1125
|
ajv.addSchema(piAgentSchema);
|
|
1132
|
-
ajv.addSchema(
|
|
1133
|
-
ajv.addSchema(
|
|
1126
|
+
ajv.addSchema(agentNameContract);
|
|
1127
|
+
ajv.addSchema(agentServiceSchema);
|
|
1134
1128
|
var validateDeclaration = ajv.compile(declarationSchema);
|
|
1135
1129
|
var validateDeploymentSpec = ajv.compile(deploymentSchema);
|
|
1136
1130
|
function validationMessage(label, errors) {
|
|
@@ -1157,10 +1151,10 @@ import path2 from "node:path";
|
|
|
1157
1151
|
import { parse, stringify } from "smol-toml";
|
|
1158
1152
|
var DECLARATION_FILENAME = ".tonbo";
|
|
1159
1153
|
var DEFAULT_INFERENCE_MODEL = "claude-sonnet-4-5";
|
|
1160
|
-
function createDeclaration(model = DEFAULT_INFERENCE_MODEL, driver = { kind: "native" }, buildCommand,
|
|
1154
|
+
function createDeclaration(model = DEFAULT_INFERENCE_MODEL, driver = { kind: "native" }, buildCommand, agentHostname) {
|
|
1161
1155
|
return parseDeclaration({
|
|
1162
1156
|
version: 2,
|
|
1163
|
-
...
|
|
1157
|
+
...agentHostname ? { agent: agentHostname } : {},
|
|
1164
1158
|
harness: { runtime: "pi", driver },
|
|
1165
1159
|
inference: { model: model.trim() },
|
|
1166
1160
|
...buildCommand ? { build: { command: buildCommand } } : {}
|
|
@@ -1171,9 +1165,9 @@ function renderDeclaration(declaration) {
|
|
|
1171
1165
|
# Edit this file directly or run \`tonbo init\` to reconfigure.
|
|
1172
1166
|
${stringify(declaration)}`;
|
|
1173
1167
|
}
|
|
1174
|
-
async function
|
|
1168
|
+
async function bindDeclarationAgent(root, agentHostname) {
|
|
1175
1169
|
const declaration = await loadDeclaration(root);
|
|
1176
|
-
const bound = parseDeclaration({ ...declaration, agent:
|
|
1170
|
+
const bound = parseDeclaration({ ...declaration, agent: agentHostname });
|
|
1177
1171
|
await saveDeclaration(root, bound, true);
|
|
1178
1172
|
return bound;
|
|
1179
1173
|
}
|
|
@@ -1303,7 +1297,7 @@ async function validatePackageSource(root, source) {
|
|
|
1303
1297
|
const resolved = path3.resolve(settingsDirectory, source);
|
|
1304
1298
|
const relative = path3.relative(root, resolved);
|
|
1305
1299
|
if (relative === ".." || relative.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative)) {
|
|
1306
|
-
throw new Error(`Local PI package ${source} resolves outside the deployed
|
|
1300
|
+
throw new Error(`Local PI package ${source} resolves outside the deployed agent.`);
|
|
1307
1301
|
}
|
|
1308
1302
|
let metadata;
|
|
1309
1303
|
try {
|
|
@@ -1320,7 +1314,7 @@ async function validatePackageSource(root, source) {
|
|
|
1320
1314
|
return;
|
|
1321
1315
|
}
|
|
1322
1316
|
throw new Error(
|
|
1323
|
-
`PI package ${source} must use an exact npm version, a full Git commit, or a
|
|
1317
|
+
`PI package ${source} must use an exact npm version, a full Git commit, or a agent-local path.`
|
|
1324
1318
|
);
|
|
1325
1319
|
}
|
|
1326
1320
|
async function validatePiPackages(root) {
|
|
@@ -1498,31 +1492,31 @@ async function readDefaultSshPublicKeys(sshDirectory = join(homedir(), ".ssh"))
|
|
|
1498
1492
|
return keys;
|
|
1499
1493
|
}
|
|
1500
1494
|
|
|
1501
|
-
// src/
|
|
1502
|
-
var
|
|
1503
|
-
var
|
|
1504
|
-
var
|
|
1505
|
-
function
|
|
1506
|
-
const valid = name.length >=
|
|
1495
|
+
// src/agent-name.ts
|
|
1496
|
+
var AGENT_NAME_RULE = agentNameContract.$defs.name;
|
|
1497
|
+
var AGENT_NAME_PATTERN = new RegExp(AGENT_NAME_RULE.pattern);
|
|
1498
|
+
var RESERVED_AGENT_NAMES = new Set(AGENT_NAME_RULE.not.enum);
|
|
1499
|
+
function validateAgentName(name) {
|
|
1500
|
+
const valid = name.length >= AGENT_NAME_RULE.minLength && name.length <= AGENT_NAME_RULE.maxLength && AGENT_NAME_PATTERN.test(name) && !RESERVED_AGENT_NAMES.has(name);
|
|
1507
1501
|
if (!valid) {
|
|
1508
1502
|
throw new Error(
|
|
1509
|
-
`Agent name must be ${
|
|
1503
|
+
`Agent name must be ${AGENT_NAME_RULE.minLength}-${AGENT_NAME_RULE.maxLength} lowercase letters, digits, or single hyphens, start with a letter, and not be reserved.`
|
|
1510
1504
|
);
|
|
1511
1505
|
}
|
|
1512
1506
|
}
|
|
1513
|
-
function
|
|
1507
|
+
function agentNameSuggestion(value) {
|
|
1514
1508
|
let name = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
|
|
1515
1509
|
if (!/^[a-z]/.test(name)) name = `agent-${name}`;
|
|
1516
|
-
name = name.slice(0,
|
|
1517
|
-
if (name.length <
|
|
1518
|
-
name = `${name || "agent"}-agent`.slice(0,
|
|
1510
|
+
name = name.slice(0, AGENT_NAME_RULE.maxLength).replace(/-+$/g, "");
|
|
1511
|
+
if (name.length < AGENT_NAME_RULE.minLength) {
|
|
1512
|
+
name = `${name || "agent"}-agent`.slice(0, AGENT_NAME_RULE.maxLength);
|
|
1519
1513
|
}
|
|
1520
|
-
if (
|
|
1514
|
+
if (RESERVED_AGENT_NAMES.has(name)) name = `${name}-agent`;
|
|
1521
1515
|
return name;
|
|
1522
1516
|
}
|
|
1523
1517
|
|
|
1524
1518
|
// src/commands.ts
|
|
1525
|
-
async function
|
|
1519
|
+
async function resolveAgent(deps) {
|
|
1526
1520
|
const root = await findDeclarationRoot(deps.cwd());
|
|
1527
1521
|
const declaration = await loadDeclaration(root);
|
|
1528
1522
|
if (!declaration.agent) {
|
|
@@ -1532,14 +1526,14 @@ async function resolveProject(deps) {
|
|
|
1532
1526
|
return {
|
|
1533
1527
|
declaration,
|
|
1534
1528
|
oauthToken,
|
|
1535
|
-
|
|
1529
|
+
agent: selectAgent(await deps.api.listAgents(oauthToken), declaration.agent),
|
|
1536
1530
|
root
|
|
1537
1531
|
};
|
|
1538
1532
|
}
|
|
1539
|
-
function
|
|
1533
|
+
function selectAgent(agents, selector) {
|
|
1540
1534
|
const normalized = selector.toLowerCase();
|
|
1541
|
-
const matches =
|
|
1542
|
-
(
|
|
1535
|
+
const matches = agents.filter(
|
|
1536
|
+
(agent) => agent.id === selector || agent.name === normalized || agent.publicHostname === normalized
|
|
1543
1537
|
);
|
|
1544
1538
|
if (matches.length === 0) throw new Error(`Agent ${selector} was not found in your account.`);
|
|
1545
1539
|
if (matches.length > 1)
|
|
@@ -1573,7 +1567,7 @@ async function initCommand(deps, options) {
|
|
|
1573
1567
|
throw new Error("No Harness-specific configuration found. Pass --harness pi.");
|
|
1574
1568
|
} else {
|
|
1575
1569
|
deps.output({
|
|
1576
|
-
message: "No Harness-specific configuration was found in this
|
|
1570
|
+
message: "No Harness-specific configuration was found in this agent."
|
|
1577
1571
|
});
|
|
1578
1572
|
harness = await selectHarness(deps);
|
|
1579
1573
|
}
|
|
@@ -1599,19 +1593,19 @@ async function initCommand(deps, options) {
|
|
|
1599
1593
|
entry ||= "dist/agent.mjs";
|
|
1600
1594
|
let model = options.model?.trim() || existingDeclaration?.inference.model || DEFAULT_INFERENCE_MODEL;
|
|
1601
1595
|
let buildCommand = driver === "command" ? options.buildCommand ?? existingDeclaration?.build?.command ?? ["npm", "run", "build"] : void 0;
|
|
1602
|
-
let
|
|
1596
|
+
let agentPlan = existingDeclaration?.agent ? {
|
|
1603
1597
|
kind: "existing",
|
|
1604
1598
|
publicHostname: existingDeclaration.agent
|
|
1605
1599
|
} : { kind: "later" };
|
|
1606
1600
|
if (interactive && !existingDeclaration?.agent) {
|
|
1607
|
-
|
|
1601
|
+
agentPlan = await selectInitAgent(deps, root);
|
|
1608
1602
|
}
|
|
1609
1603
|
if (interactive) {
|
|
1610
1604
|
while (true) {
|
|
1611
1605
|
deps.output({
|
|
1612
|
-
message: renderInitSummary(
|
|
1606
|
+
message: renderInitSummary(agentPlan, harness, driver, model, entry, buildCommand)
|
|
1613
1607
|
});
|
|
1614
|
-
const saveLabel = initSaveLabel(exists,
|
|
1608
|
+
const saveLabel = initSaveLabel(exists, agentPlan);
|
|
1615
1609
|
const action = await deps.select(`${saveLabel} with this configuration?`, [
|
|
1616
1610
|
{
|
|
1617
1611
|
name: saveLabel,
|
|
@@ -1633,9 +1627,9 @@ async function initCommand(deps, options) {
|
|
|
1633
1627
|
if (action === "save") break;
|
|
1634
1628
|
const setting = await deps.select("What would you like to change?", [
|
|
1635
1629
|
{
|
|
1636
|
-
description:
|
|
1637
|
-
name: "
|
|
1638
|
-
value: "
|
|
1630
|
+
description: initAgentSummary(agentPlan),
|
|
1631
|
+
name: "Agent",
|
|
1632
|
+
value: "agent"
|
|
1639
1633
|
},
|
|
1640
1634
|
{ description: harnessName(harness), name: "Harness", value: "harness" },
|
|
1641
1635
|
{
|
|
@@ -1647,7 +1641,7 @@ async function initCommand(deps, options) {
|
|
|
1647
1641
|
...driver === "command" ? [{ description: entry, name: "PI SDK entry file", value: "entry" }] : [],
|
|
1648
1642
|
{ name: "Back to review", value: "back" }
|
|
1649
1643
|
]);
|
|
1650
|
-
if (setting === "
|
|
1644
|
+
if (setting === "agent") agentPlan = await selectInitAgent(deps, root);
|
|
1651
1645
|
if (setting === "harness") harness = await selectHarness(deps);
|
|
1652
1646
|
if (setting === "driver") {
|
|
1653
1647
|
driver = await deps.select(
|
|
@@ -1672,22 +1666,18 @@ async function initCommand(deps, options) {
|
|
|
1672
1666
|
}
|
|
1673
1667
|
}
|
|
1674
1668
|
}
|
|
1675
|
-
let
|
|
1676
|
-
let
|
|
1677
|
-
if (
|
|
1669
|
+
let agentHostname = agentPlan.kind === "existing" ? agentPlan.publicHostname : void 0;
|
|
1670
|
+
let createdAgent;
|
|
1671
|
+
if (agentPlan.kind === "create") {
|
|
1678
1672
|
const oauthToken = await deps.auth.accessToken();
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
projectPlan.name,
|
|
1682
|
-
projectPlan.organizationId
|
|
1683
|
-
);
|
|
1684
|
-
projectHostname = createdProject.publicHostname;
|
|
1673
|
+
createdAgent = await deps.api.createAgent(oauthToken, agentPlan.name, agentPlan.organizationId);
|
|
1674
|
+
agentHostname = createdAgent.publicHostname;
|
|
1685
1675
|
}
|
|
1686
1676
|
const configuredDeclaration = createDeclaration(
|
|
1687
1677
|
model,
|
|
1688
1678
|
driver === "native" ? { kind: "native" } : { kind: "command", protocol: "pi-rpc-v1", command: ["node", entry] },
|
|
1689
1679
|
buildCommand,
|
|
1690
|
-
|
|
1680
|
+
agentHostname
|
|
1691
1681
|
);
|
|
1692
1682
|
const declaration = parseDeclaration({
|
|
1693
1683
|
...configuredDeclaration,
|
|
@@ -1700,9 +1690,9 @@ async function initCommand(deps, options) {
|
|
|
1700
1690
|
try {
|
|
1701
1691
|
await saveDeclaration(root, declaration, overwrite);
|
|
1702
1692
|
} catch (error) {
|
|
1703
|
-
if (
|
|
1693
|
+
if (createdAgent) {
|
|
1704
1694
|
throw new Error(
|
|
1705
|
-
`Created agent ${
|
|
1695
|
+
`Created agent ${createdAgent.name}, but could not write ${DECLARATION_FILENAME}. Fix the file and run \`tonbo agent use ${createdAgent.publicHostname}\`.`,
|
|
1706
1696
|
{ cause: error }
|
|
1707
1697
|
);
|
|
1708
1698
|
}
|
|
@@ -1711,28 +1701,28 @@ async function initCommand(deps, options) {
|
|
|
1711
1701
|
const driverSummary = driver === "command" ? `PI SDK app
|
|
1712
1702
|
Build: ${buildCommand?.join(" ")}
|
|
1713
1703
|
Entrypoint: node ${entry}` : inspection.pi.packageCount > 0 ? `PI CLI with ${inspection.pi.packageCount} package${inspection.pi.packageCount === 1 ? "" : "s"}` : "PI CLI";
|
|
1714
|
-
const
|
|
1715
|
-
Organization: ${
|
|
1716
|
-
Application: https://${
|
|
1717
|
-
SSH after deploy: ssh ${
|
|
1718
|
-
Organization: ${
|
|
1719
|
-
SSH after deploy: ssh ${
|
|
1704
|
+
const agentSummary = createdAgent ? `Agent: ${createdAgent.name} (${createdAgent.publicHostname})
|
|
1705
|
+
Organization: ${createdAgent.organizationName}
|
|
1706
|
+
Application: https://${createdAgent.publicHostname}
|
|
1707
|
+
SSH after deploy: ssh ${createdAgent.sshDestination}` : agentPlan.kind === "existing" ? `Agent: ${agentPlan.name ? `${agentPlan.name} (${agentPlan.publicHostname})` : agentPlan.publicHostname}${agentPlan.organizationName ? `
|
|
1708
|
+
Organization: ${agentPlan.organizationName}` : ""}${agentPlan.sshDestination ? `
|
|
1709
|
+
SSH after deploy: ssh ${agentPlan.sshDestination}` : ""}` : "Next: tonbo agent create <name>";
|
|
1720
1710
|
deps.output({
|
|
1721
|
-
message: `${
|
|
1722
|
-
${
|
|
1711
|
+
message: `${createdAgent ? `Created agent ${createdAgent.name} and` : exists ? "Updated" : "Created"} ${DECLARATION_FILENAME}.
|
|
1712
|
+
${agentSummary}
|
|
1723
1713
|
Harness: PI
|
|
1724
|
-
Mode: ${driverSummary}${
|
|
1714
|
+
Mode: ${driverSummary}${agentPlan.kind === "later" ? "" : "\nNext: tonbo deploy"}`,
|
|
1725
1715
|
declaration,
|
|
1726
1716
|
path: path4.join(root, DECLARATION_FILENAME)
|
|
1727
1717
|
});
|
|
1728
1718
|
}
|
|
1729
|
-
function initSaveLabel(exists,
|
|
1730
|
-
if (
|
|
1719
|
+
function initSaveLabel(exists, agent) {
|
|
1720
|
+
if (agent.kind === "create") {
|
|
1731
1721
|
return exists ? `Create agent and replace ${DECLARATION_FILENAME}` : `Create agent and ${DECLARATION_FILENAME}`;
|
|
1732
1722
|
}
|
|
1733
1723
|
return exists ? `Replace ${DECLARATION_FILENAME}` : `Create ${DECLARATION_FILENAME}`;
|
|
1734
1724
|
}
|
|
1735
|
-
async function
|
|
1725
|
+
async function selectInitAgent(deps, root) {
|
|
1736
1726
|
for (; ; ) {
|
|
1737
1727
|
const action = await deps.select("How should this directory connect to Tonbo?", [
|
|
1738
1728
|
{
|
|
@@ -1754,12 +1744,12 @@ async function selectInitProject(deps, root) {
|
|
|
1754
1744
|
if (action === "later") return { kind: "later" };
|
|
1755
1745
|
if (action === "create") {
|
|
1756
1746
|
const organization = await selectInitOrganization(deps);
|
|
1757
|
-
const defaultName =
|
|
1747
|
+
const defaultName = agentNameSuggestion(path4.basename(root)) || "tonbo-agent";
|
|
1758
1748
|
for (; ; ) {
|
|
1759
1749
|
const answer = (await deps.prompt(`Agent name [${defaultName}]: `)).trim().toLowerCase();
|
|
1760
1750
|
const name = answer || defaultName;
|
|
1761
1751
|
try {
|
|
1762
|
-
|
|
1752
|
+
validateAgentName(name);
|
|
1763
1753
|
return {
|
|
1764
1754
|
kind: "create",
|
|
1765
1755
|
name,
|
|
@@ -1774,36 +1764,36 @@ async function selectInitProject(deps, root) {
|
|
|
1774
1764
|
}
|
|
1775
1765
|
}
|
|
1776
1766
|
const oauthToken = await deps.auth.accessToken();
|
|
1777
|
-
const
|
|
1778
|
-
(
|
|
1767
|
+
const agents = (await deps.api.listAgents(oauthToken)).filter(
|
|
1768
|
+
(agent2) => agent2.status === "active"
|
|
1779
1769
|
);
|
|
1780
|
-
if (
|
|
1770
|
+
if (agents.length === 0) {
|
|
1781
1771
|
deps.output({ message: "No active agents are available. Create a new agent instead." });
|
|
1782
1772
|
continue;
|
|
1783
1773
|
}
|
|
1784
1774
|
const id = await deps.select(
|
|
1785
1775
|
"Which agent should this directory use?",
|
|
1786
|
-
|
|
1787
|
-
name:
|
|
1788
|
-
description: `Organization: ${
|
|
1789
|
-
value:
|
|
1776
|
+
agents.map((agent2) => ({
|
|
1777
|
+
name: agent2.name,
|
|
1778
|
+
description: `Organization: ${agent2.organizationName} \xB7 ${agent2.publicHostname} \xB7 ${agent2.id}`,
|
|
1779
|
+
value: agent2.id
|
|
1790
1780
|
}))
|
|
1791
1781
|
);
|
|
1792
|
-
const
|
|
1782
|
+
const agent = selectAgent(agents, id);
|
|
1793
1783
|
return {
|
|
1794
1784
|
kind: "existing",
|
|
1795
|
-
name:
|
|
1796
|
-
organizationName:
|
|
1797
|
-
publicHostname:
|
|
1798
|
-
sshDestination:
|
|
1785
|
+
name: agent.name,
|
|
1786
|
+
organizationName: agent.organizationName,
|
|
1787
|
+
publicHostname: agent.publicHostname,
|
|
1788
|
+
sshDestination: agent.sshDestination
|
|
1799
1789
|
};
|
|
1800
1790
|
}
|
|
1801
1791
|
}
|
|
1802
|
-
function
|
|
1803
|
-
if (
|
|
1804
|
-
return `Create ${
|
|
1805
|
-
if (
|
|
1806
|
-
return
|
|
1792
|
+
function initAgentSummary(agent) {
|
|
1793
|
+
if (agent.kind === "create")
|
|
1794
|
+
return `Create ${agent.name}${agent.organizationName ? ` in ${agent.organizationName}` : ""} (permanent address assigned after creation)`;
|
|
1795
|
+
if (agent.kind === "existing") {
|
|
1796
|
+
return agent.name ? `${agent.name}${agent.organizationName ? ` in ${agent.organizationName}` : ""} (${agent.publicHostname})` : `Keep ${agent.publicHostname}`;
|
|
1807
1797
|
}
|
|
1808
1798
|
return "Set up later";
|
|
1809
1799
|
}
|
|
@@ -1813,7 +1803,7 @@ async function selectInitOrganization(deps) {
|
|
|
1813
1803
|
(organization) => organization.role !== "member"
|
|
1814
1804
|
);
|
|
1815
1805
|
if (organizations.length === 0)
|
|
1816
|
-
throw new Error("No organization lets you create a
|
|
1806
|
+
throw new Error("No organization lets you create a agent. Ask an admin or owner.");
|
|
1817
1807
|
if (organizations.length === 1) return organizations[0];
|
|
1818
1808
|
const id = await deps.select(
|
|
1819
1809
|
"Which organization should own the new agent?",
|
|
@@ -1839,11 +1829,11 @@ function harnessName(harness) {
|
|
|
1839
1829
|
const supported = supportedHarnesses.find((candidate) => candidate.id === harness);
|
|
1840
1830
|
return supported?.name ?? harness;
|
|
1841
1831
|
}
|
|
1842
|
-
function renderInitSummary(
|
|
1832
|
+
function renderInitSummary(agent, harness, driver, model, entry, buildCommand) {
|
|
1843
1833
|
const lines = [
|
|
1844
1834
|
"",
|
|
1845
1835
|
"Tonbo Agent configuration:",
|
|
1846
|
-
` Agent: ${
|
|
1836
|
+
` Agent: ${initAgentSummary(agent)}`,
|
|
1847
1837
|
` Harness: ${harnessName(harness)}`,
|
|
1848
1838
|
` Execution: ${driver === "native" ? "PI CLI (`pi`)" : "PI SDK app"}`,
|
|
1849
1839
|
` Model: ${model}`
|
|
@@ -1862,7 +1852,7 @@ function piTargetChoices(settingsFound) {
|
|
|
1862
1852
|
value: "command"
|
|
1863
1853
|
},
|
|
1864
1854
|
native: {
|
|
1865
|
-
description: settingsFound ? "Run pi directly and load the detected
|
|
1855
|
+
description: settingsFound ? "Run pi directly and load the detected agent-local PI configuration." : "Run pi directly with AGENTS.md and optional agent-local .pi resources.",
|
|
1866
1856
|
name: "PI CLI (`pi`)",
|
|
1867
1857
|
value: "native"
|
|
1868
1858
|
}
|
|
@@ -1928,31 +1918,31 @@ async function sshKeyRemoveCommand(deps, fingerprint) {
|
|
|
1928
1918
|
const key = await deps.api.revokeSshKey(oauthToken, fingerprint);
|
|
1929
1919
|
deps.output({ message: `Revoked SSH key ${key.fingerprint}.`, key });
|
|
1930
1920
|
}
|
|
1931
|
-
async function
|
|
1921
|
+
async function agentUseCommand(deps, selector, force = false) {
|
|
1932
1922
|
const root = await findDeclarationRoot(deps.cwd());
|
|
1933
1923
|
const declaration = await loadDeclaration(root);
|
|
1934
1924
|
const oauthToken = await deps.auth.accessToken();
|
|
1935
|
-
const
|
|
1936
|
-
const
|
|
1937
|
-
if (declaration.agent ===
|
|
1925
|
+
const agents = await deps.api.listAgents(oauthToken);
|
|
1926
|
+
const agent = selectAgent(agents, selector);
|
|
1927
|
+
if (declaration.agent === agent.publicHostname) {
|
|
1938
1928
|
deps.output({
|
|
1939
|
-
message: `.tonbo already uses agent ${
|
|
1940
|
-
agent
|
|
1929
|
+
message: `.tonbo already uses agent ${agent.publicHostname}.`,
|
|
1930
|
+
agent
|
|
1941
1931
|
});
|
|
1942
1932
|
return;
|
|
1943
1933
|
}
|
|
1944
1934
|
if (declaration.agent && !force) {
|
|
1945
|
-
const current =
|
|
1935
|
+
const current = agents.find((candidate) => candidate.publicHostname === declaration.agent);
|
|
1946
1936
|
const currentLabel = current?.publicHostname ?? declaration.agent;
|
|
1947
1937
|
if (!deps.interactive()) {
|
|
1948
1938
|
throw new Error(
|
|
1949
|
-
`.tonbo already uses agent ${currentLabel}. Pass --force to rebind it to ${
|
|
1939
|
+
`.tonbo already uses agent ${currentLabel}. Pass --force to rebind it to ${agent.publicHostname}.`
|
|
1950
1940
|
);
|
|
1951
1941
|
}
|
|
1952
1942
|
const action = await deps.select(
|
|
1953
|
-
`Rebind .tonbo from ${currentLabel} to ${
|
|
1943
|
+
`Rebind .tonbo from ${currentLabel} to ${agent.publicHostname}?`,
|
|
1954
1944
|
[
|
|
1955
|
-
{ name: `Rebind to ${
|
|
1945
|
+
{ name: `Rebind to ${agent.publicHostname}`, value: "rebind" },
|
|
1956
1946
|
{ name: "Cancel", value: "cancel" }
|
|
1957
1947
|
]
|
|
1958
1948
|
);
|
|
@@ -1961,10 +1951,10 @@ async function projectUseCommand(deps, selector, force = false) {
|
|
|
1961
1951
|
return;
|
|
1962
1952
|
}
|
|
1963
1953
|
}
|
|
1964
|
-
await
|
|
1965
|
-
deps.output({ message: `Bound .tonbo to agent ${
|
|
1954
|
+
await bindDeclarationAgent(root, agent.publicHostname);
|
|
1955
|
+
deps.output({ message: `Bound .tonbo to agent ${agent.publicHostname}.`, agent });
|
|
1966
1956
|
}
|
|
1967
|
-
async function
|
|
1957
|
+
async function agentCreateCommand(deps, name) {
|
|
1968
1958
|
const root = await findDeclarationRoot(deps.cwd());
|
|
1969
1959
|
const declaration = await loadDeclaration(root);
|
|
1970
1960
|
if (declaration.agent) {
|
|
@@ -1973,28 +1963,28 @@ async function projectCreateCommand(deps, name) {
|
|
|
1973
1963
|
);
|
|
1974
1964
|
}
|
|
1975
1965
|
const oauthToken = await deps.auth.accessToken();
|
|
1976
|
-
|
|
1977
|
-
const
|
|
1966
|
+
validateAgentName(name);
|
|
1967
|
+
const agent = await deps.api.createAgent(oauthToken, name);
|
|
1978
1968
|
try {
|
|
1979
|
-
await
|
|
1969
|
+
await bindDeclarationAgent(root, agent.publicHostname);
|
|
1980
1970
|
} catch (error) {
|
|
1981
1971
|
throw new Error(
|
|
1982
|
-
`Created agent ${
|
|
1972
|
+
`Created agent ${agent.name}, but could not bind ${DECLARATION_FILENAME}. Fix the file and run \`tonbo agent use ${agent.publicHostname}\`.`,
|
|
1983
1973
|
{ cause: error }
|
|
1984
1974
|
);
|
|
1985
1975
|
}
|
|
1986
|
-
deps.output({ message: `Created agent ${
|
|
1976
|
+
deps.output({ message: `Created agent ${agent.name} and bound it in .tonbo.`, agent });
|
|
1987
1977
|
}
|
|
1988
1978
|
async function deployCommand(deps, options = { promote: true }) {
|
|
1989
|
-
const { declaration, oauthToken,
|
|
1979
|
+
const { declaration, oauthToken, agent, root } = await resolveAgent(deps);
|
|
1990
1980
|
if (declaration.build) await runBuildCommand(root, declaration.build.command);
|
|
1991
1981
|
const source = await buildSourceBundle(root);
|
|
1992
1982
|
const origin = { actor: "cli", git: await deps.gitProvenance(root) };
|
|
1993
|
-
const managementToken = await deps.api.exchangeManagementToken(oauthToken,
|
|
1983
|
+
const managementToken = await deps.api.exchangeManagementToken(oauthToken, agent.id);
|
|
1994
1984
|
const result = await deps.api.deploy({
|
|
1995
1985
|
bundle: source,
|
|
1996
1986
|
origin,
|
|
1997
|
-
|
|
1987
|
+
agentId: agent.id,
|
|
1998
1988
|
promote: options.promote,
|
|
1999
1989
|
spec: buildDeploymentSpec(declaration, source),
|
|
2000
1990
|
token: managementToken
|
|
@@ -2002,12 +1992,12 @@ async function deployCommand(deps, options = { promote: true }) {
|
|
|
2002
1992
|
const { deployment } = result;
|
|
2003
1993
|
const shortId = shortDeploymentId(deployment.id);
|
|
2004
1994
|
const lines = [
|
|
2005
|
-
options.promote ? `Deployed agent ${
|
|
2006
|
-
`Organization: ${
|
|
1995
|
+
options.promote ? `Deployed agent ${agent.name}.` : `Created Deployment ${deploymentName(deployment)} (${shortId}) for agent ${agent.name} without promoting it.`,
|
|
1996
|
+
`Organization: ${agent.organizationName}`,
|
|
2007
1997
|
`Deployment: ${deploymentName(deployment)} (${shortId})`,
|
|
2008
1998
|
`Source: ${deploymentSourceLabel(deployment)}`,
|
|
2009
1999
|
`Production: ${options.promote ? productionStateLabel(deployment) : `not promoted; run \`tonbo deployments promote ${shortId}\` to move Production here`}`,
|
|
2010
|
-
`Application: https://${
|
|
2000
|
+
`Application: https://${agent.publicHostname}`
|
|
2011
2001
|
];
|
|
2012
2002
|
if (result.unchanged) {
|
|
2013
2003
|
lines.push(
|
|
@@ -2023,49 +2013,49 @@ async function deployCommand(deps, options = { promote: true }) {
|
|
|
2023
2013
|
' tonbo run "<prompt>"',
|
|
2024
2014
|
"",
|
|
2025
2015
|
"Connect with SSH:",
|
|
2026
|
-
` ssh ${
|
|
2016
|
+
` ssh ${agent.sshDestination}`
|
|
2027
2017
|
);
|
|
2028
2018
|
}
|
|
2029
|
-
deps.output({ message: lines.join("\n"), agent
|
|
2019
|
+
deps.output({ message: lines.join("\n"), agent, ...result });
|
|
2030
2020
|
}
|
|
2031
2021
|
async function runCommand(deps, prompt, options) {
|
|
2032
|
-
const { oauthToken,
|
|
2033
|
-
const managementToken = await deps.api.exchangeManagementToken(oauthToken,
|
|
2022
|
+
const { oauthToken, agent } = await resolveAgent(deps);
|
|
2023
|
+
const managementToken = await deps.api.exchangeManagementToken(oauthToken, agent.id);
|
|
2034
2024
|
const result = await deps.api.run({
|
|
2035
|
-
|
|
2025
|
+
agentId: agent.id,
|
|
2036
2026
|
prompt,
|
|
2037
2027
|
sessionId: options.session,
|
|
2038
2028
|
token: managementToken
|
|
2039
2029
|
});
|
|
2040
2030
|
deps.output({
|
|
2041
2031
|
message: result.turn.assistant_text,
|
|
2042
|
-
agent
|
|
2032
|
+
agent,
|
|
2043
2033
|
...result
|
|
2044
2034
|
});
|
|
2045
2035
|
}
|
|
2046
2036
|
async function agentListCommand(deps) {
|
|
2047
|
-
const agents = await deps.api.
|
|
2037
|
+
const agents = await deps.api.listAgents(await deps.auth.accessToken());
|
|
2048
2038
|
deps.output({
|
|
2049
2039
|
message: agents.length ? agents.map((agent) => `${agent.name} \xB7 ${agent.publicHostname}`).join("\n") : "No agents are available.",
|
|
2050
2040
|
agents
|
|
2051
2041
|
});
|
|
2052
2042
|
}
|
|
2053
|
-
async function
|
|
2054
|
-
const {
|
|
2055
|
-
const production = await deps.api.getProduction(
|
|
2056
|
-
const deployment = production ? await deps.api.getDeployment(
|
|
2043
|
+
async function agentShowCommand(deps) {
|
|
2044
|
+
const { agent, token } = await agentManagement(deps);
|
|
2045
|
+
const production = await deps.api.getProduction(agent.id, token);
|
|
2046
|
+
const deployment = production ? await deps.api.getDeployment(agent.id, production.deployment_id, token) : null;
|
|
2057
2047
|
const lines = [
|
|
2058
|
-
`agent: ${
|
|
2059
|
-
`Organization: ${
|
|
2060
|
-
`application: https://${
|
|
2061
|
-
`ssh: ssh ${
|
|
2048
|
+
`agent: ${agent.name}`,
|
|
2049
|
+
`Organization: ${agent.organizationName}`,
|
|
2050
|
+
`application: https://${agent.publicHostname}`,
|
|
2051
|
+
`ssh: ssh ${agent.sshDestination}`
|
|
2062
2052
|
];
|
|
2063
2053
|
if (deployment) {
|
|
2064
2054
|
lines.push(
|
|
2065
2055
|
`production: ${deploymentName(deployment)} (${shortDeploymentId(deployment.id)}) ${productionStateLabel(deployment)}`
|
|
2066
2056
|
);
|
|
2067
2057
|
}
|
|
2068
|
-
deps.output({ message: lines.join("\n"), agent
|
|
2058
|
+
deps.output({ message: lines.join("\n"), agent, production, deployment });
|
|
2069
2059
|
}
|
|
2070
2060
|
function shortDeploymentId(id) {
|
|
2071
2061
|
return id.replace(/-/g, "").slice(0, 8);
|
|
@@ -2092,7 +2082,7 @@ function selectDeployment(deployments, prefix) {
|
|
|
2092
2082
|
const matches = deployments.filter(
|
|
2093
2083
|
(deployment) => deployment.id.replace(/-/g, "").startsWith(normalized)
|
|
2094
2084
|
);
|
|
2095
|
-
if (matches.length === 0) throw new Error(`Deployment ${prefix} was not found in this
|
|
2085
|
+
if (matches.length === 0) throw new Error(`Deployment ${prefix} was not found in this agent.`);
|
|
2096
2086
|
if (matches.length > 1) {
|
|
2097
2087
|
throw new Error(
|
|
2098
2088
|
`Deployment id ${prefix} is ambiguous; it matches ${matches.map((deployment) => shortDeploymentId(deployment.id)).join(", ")}. Use more characters.`
|
|
@@ -2118,10 +2108,10 @@ function productionMarker(state) {
|
|
|
2118
2108
|
return "";
|
|
2119
2109
|
}
|
|
2120
2110
|
async function deploymentsListCommand(deps) {
|
|
2121
|
-
const {
|
|
2111
|
+
const { agent, token } = await agentManagement(deps);
|
|
2122
2112
|
const [deployments, production] = await Promise.all([
|
|
2123
|
-
deps.api.listDeployments(
|
|
2124
|
-
deps.api.getProduction(
|
|
2113
|
+
deps.api.listDeployments(agent.id, token),
|
|
2114
|
+
deps.api.getProduction(agent.id, token)
|
|
2125
2115
|
]);
|
|
2126
2116
|
const message = deployments.length ? renderTable(
|
|
2127
2117
|
["ID", "NAME", "STATUS", "PRODUCTION", "SOURCE", "CREATED", "BY"],
|
|
@@ -2134,18 +2124,18 @@ async function deploymentsListCommand(deps) {
|
|
|
2134
2124
|
formatTimestamp(deployment.created_at),
|
|
2135
2125
|
deployment.created_by_user_id ? shortDeploymentId(deployment.created_by_user_id) : ""
|
|
2136
2126
|
])
|
|
2137
|
-
) : `No Deployments exist for agent ${
|
|
2138
|
-
deps.output({ message, agent
|
|
2127
|
+
) : `No Deployments exist for agent ${agent.name}. Run \`tonbo deploy\` to create one.`;
|
|
2128
|
+
deps.output({ message, agent, production, deployments });
|
|
2139
2129
|
}
|
|
2140
2130
|
async function deploymentsShowCommand(deps, prefix) {
|
|
2141
|
-
const {
|
|
2142
|
-
const deployment = selectDeployment(await deps.api.listDeployments(
|
|
2143
|
-
const rollouts = (await deps.api.listRollouts(
|
|
2131
|
+
const { agent, token } = await agentManagement(deps);
|
|
2132
|
+
const deployment = selectDeployment(await deps.api.listDeployments(agent.id, token), prefix);
|
|
2133
|
+
const rollouts = (await deps.api.listRollouts(agent.id, token)).filter(
|
|
2144
2134
|
(rollout) => rollout.deployment_id === deployment.id || rollout.previous_deployment_id === deployment.id
|
|
2145
2135
|
);
|
|
2146
2136
|
deps.output({
|
|
2147
2137
|
message: renderDeploymentDetail(deployment, rollouts),
|
|
2148
|
-
agent
|
|
2138
|
+
agent,
|
|
2149
2139
|
deployment,
|
|
2150
2140
|
rollouts
|
|
2151
2141
|
});
|
|
@@ -2186,43 +2176,43 @@ function renderDeploymentDetail(deployment, rollouts) {
|
|
|
2186
2176
|
return lines.join("\n");
|
|
2187
2177
|
}
|
|
2188
2178
|
async function deploymentsPromoteCommand(deps, prefix) {
|
|
2189
|
-
const {
|
|
2190
|
-
const target = selectDeployment(await deps.api.listDeployments(
|
|
2191
|
-
const result = await moveProduction(deps,
|
|
2179
|
+
const { agent, token } = await agentManagement(deps);
|
|
2180
|
+
const target = selectDeployment(await deps.api.listDeployments(agent.id, token), prefix);
|
|
2181
|
+
const result = await moveProduction(deps, agent.id, target, token);
|
|
2192
2182
|
deps.output({
|
|
2193
|
-
message: describeProductionMove(
|
|
2194
|
-
agent
|
|
2183
|
+
message: describeProductionMove(agent, result, "Promoted"),
|
|
2184
|
+
agent,
|
|
2195
2185
|
...result
|
|
2196
2186
|
});
|
|
2197
2187
|
}
|
|
2198
2188
|
async function deploymentsRollbackCommand(deps, prefix) {
|
|
2199
|
-
const {
|
|
2189
|
+
const { agent, token } = await agentManagement(deps);
|
|
2200
2190
|
let target;
|
|
2201
2191
|
if (prefix !== void 0) {
|
|
2202
|
-
target = selectDeployment(await deps.api.listDeployments(
|
|
2192
|
+
target = selectDeployment(await deps.api.listDeployments(agent.id, token), prefix);
|
|
2203
2193
|
} else {
|
|
2204
|
-
const rollouts = await deps.api.listRollouts(
|
|
2194
|
+
const rollouts = await deps.api.listRollouts(agent.id, token);
|
|
2205
2195
|
const previousId = rollouts.find(
|
|
2206
2196
|
(rollout) => rollout.previous_deployment_id !== null
|
|
2207
2197
|
)?.previous_deployment_id;
|
|
2208
2198
|
if (!previousId) {
|
|
2209
2199
|
throw new Error(
|
|
2210
|
-
`Agent ${
|
|
2200
|
+
`Agent ${agent.name} has no previous Production Deployment to roll back to. Name one with \`tonbo deployments rollback <id>\`.`
|
|
2211
2201
|
);
|
|
2212
2202
|
}
|
|
2213
|
-
target = await deps.api.getDeployment(
|
|
2203
|
+
target = await deps.api.getDeployment(agent.id, previousId, token);
|
|
2214
2204
|
}
|
|
2215
|
-
const result = await moveProduction(deps,
|
|
2205
|
+
const result = await moveProduction(deps, agent.id, target, token);
|
|
2216
2206
|
deps.output({
|
|
2217
|
-
message: describeProductionMove(
|
|
2218
|
-
agent
|
|
2207
|
+
message: describeProductionMove(agent, result, "Rolled back"),
|
|
2208
|
+
agent,
|
|
2219
2209
|
...result
|
|
2220
2210
|
});
|
|
2221
2211
|
}
|
|
2222
|
-
async function moveProduction(deps,
|
|
2223
|
-
const previous = await deps.api.getProduction(
|
|
2212
|
+
async function moveProduction(deps, agentId, target, token) {
|
|
2213
|
+
const previous = await deps.api.getProduction(agentId, token);
|
|
2224
2214
|
const production = await deps.api.putProduction(
|
|
2225
|
-
|
|
2215
|
+
agentId,
|
|
2226
2216
|
{
|
|
2227
2217
|
deployment_id: target.id,
|
|
2228
2218
|
desired_state: "running",
|
|
@@ -2231,49 +2221,49 @@ async function moveProduction(deps, projectId, target, token) {
|
|
|
2231
2221
|
token
|
|
2232
2222
|
);
|
|
2233
2223
|
return {
|
|
2234
|
-
deployment: await deps.api.getDeployment(
|
|
2224
|
+
deployment: await deps.api.getDeployment(agentId, target.id, token),
|
|
2235
2225
|
previous,
|
|
2236
2226
|
production
|
|
2237
2227
|
};
|
|
2238
2228
|
}
|
|
2239
|
-
function describeProductionMove(
|
|
2229
|
+
function describeProductionMove(agent, move, verb) {
|
|
2240
2230
|
const { deployment, previous } = move;
|
|
2241
2231
|
const name = `${deploymentName(deployment)} (${shortDeploymentId(deployment.id)})`;
|
|
2242
|
-
const headline = previous?.deployment_id === deployment.id ? `Deployment ${name} is already Production for agent ${
|
|
2232
|
+
const headline = previous?.deployment_id === deployment.id ? `Deployment ${name} is already Production for agent ${agent.name}; requested it to be running.` : `${verb} ${name} to Production for agent ${agent.name}${previous ? ` (from ${shortDeploymentId(previous.deployment_id)})` : ""}.`;
|
|
2243
2233
|
return `${headline}
|
|
2244
2234
|
Production: ${productionStateLabel(deployment)}
|
|
2245
|
-
Application: https://${
|
|
2235
|
+
Application: https://${agent.publicHostname}`;
|
|
2246
2236
|
}
|
|
2247
|
-
async function
|
|
2248
|
-
const { oauthToken,
|
|
2237
|
+
async function agentManagement(deps) {
|
|
2238
|
+
const { oauthToken, agent } = await resolveAgent(deps);
|
|
2249
2239
|
return {
|
|
2250
|
-
|
|
2251
|
-
token: await deps.api.exchangeManagementToken(oauthToken,
|
|
2240
|
+
agent,
|
|
2241
|
+
token: await deps.api.exchangeManagementToken(oauthToken, agent.id)
|
|
2252
2242
|
};
|
|
2253
2243
|
}
|
|
2254
2244
|
async function secretListCommand(deps) {
|
|
2255
|
-
const {
|
|
2256
|
-
const secrets = await deps.api.
|
|
2245
|
+
const { agent, token } = await agentManagement(deps);
|
|
2246
|
+
const secrets = await deps.api.listAgentSecrets(agent.id, token);
|
|
2257
2247
|
deps.output({
|
|
2258
2248
|
message: secrets.length ? secrets.map((secret) => secret.name).join("\n") : "No agent secrets are configured.",
|
|
2259
|
-
agent
|
|
2249
|
+
agent,
|
|
2260
2250
|
secrets
|
|
2261
2251
|
});
|
|
2262
2252
|
}
|
|
2263
2253
|
async function secretSetCommand(deps, name, options) {
|
|
2264
2254
|
const value = await deps.secretValue(name, options.fromEnv);
|
|
2265
|
-
const {
|
|
2266
|
-
const secret = await deps.api.
|
|
2255
|
+
const { agent, token } = await agentManagement(deps);
|
|
2256
|
+
const secret = await deps.api.setAgentSecret(agent.id, name, value, token);
|
|
2267
2257
|
deps.output({
|
|
2268
2258
|
message: `Set agent secret ${secret.name}. Redeploy to replace the active runtime with this value.`,
|
|
2269
|
-
agent
|
|
2259
|
+
agent,
|
|
2270
2260
|
secret
|
|
2271
2261
|
});
|
|
2272
2262
|
}
|
|
2273
2263
|
async function secretRemoveCommand(deps, name) {
|
|
2274
|
-
const {
|
|
2275
|
-
await deps.api.
|
|
2276
|
-
deps.output({ message: `Removed agent secret ${name}.`, agent
|
|
2264
|
+
const { agent, token } = await agentManagement(deps);
|
|
2265
|
+
await deps.api.deleteAgentSecret(agent.id, name, token);
|
|
2266
|
+
deps.output({ message: `Removed agent secret ${name}.`, agent, name });
|
|
2277
2267
|
}
|
|
2278
2268
|
|
|
2279
2269
|
// src/credentials.ts
|
|
@@ -2593,18 +2583,18 @@ function createProgram(dependencies = createDependencies) {
|
|
|
2593
2583
|
})
|
|
2594
2584
|
);
|
|
2595
2585
|
});
|
|
2596
|
-
const
|
|
2597
|
-
|
|
2598
|
-
|
|
2586
|
+
const agent = program.command("agent").description("manage the Agent bound to this source directory");
|
|
2587
|
+
agent.command("create <name>").description("create an agent and bind this directory to it").action(async (name) => agentCreateCommand(dependencies(jsonOutput(program)), name));
|
|
2588
|
+
agent.command("list").description("list the agents available to your account").action(async () => agentListCommand(dependencies(jsonOutput(program))));
|
|
2599
2589
|
const sshKey = program.command("ssh-key").description("manage public keys used by native agent SSH");
|
|
2600
2590
|
sshKey.command("add <public-key>").description("register an OpenSSH public key with the current Tonbo account").action(async (path6) => sshKeyAddCommand(dependencies(jsonOutput(program)), path6));
|
|
2601
2591
|
sshKey.command("remove <fingerprint>").description("revoke an SSH public key from the current Tonbo account").action(
|
|
2602
2592
|
async (fingerprint) => sshKeyRemoveCommand(dependencies(jsonOutput(program)), fingerprint)
|
|
2603
2593
|
);
|
|
2604
|
-
|
|
2605
|
-
async (selector, options) =>
|
|
2594
|
+
agent.command("use <agent>").description("write the agent hostname into this directory's .tonbo declaration").option("--force", "replace an existing agent binding without confirmation").action(
|
|
2595
|
+
async (selector, options) => agentUseCommand(dependencies(jsonOutput(program)), selector, options.force === true)
|
|
2606
2596
|
);
|
|
2607
|
-
|
|
2597
|
+
agent.command("show").description("show the agent bound to this directory and its connection addresses").action(async () => agentShowCommand(dependencies(jsonOutput(program))));
|
|
2608
2598
|
program.command("deploy").description("upload this directory as a new Deployment and promote it to Production").option("--no-promote", "create the Deployment without moving Production").action(
|
|
2609
2599
|
async (options) => deployCommand(dependencies(jsonOutput(program)), { promote: options.promote })
|
|
2610
2600
|
);
|