@tonbo/cli 0.1.1 → 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.
Files changed (3) hide show
  1. package/README.md +40 -22
  2. package/dist/bin/tonbo.js +227 -226
  3. 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 project.
15
+ No Harness-specific configuration was found in this agent.
16
16
  ? Which supported Harness should Tonbo use?
17
- ❯ PI (`pi`) Run this project with the pi command.
17
+ ❯ PI (`pi`) Run this agent with the pi command.
18
18
  ? How should this Agent connect to Tonbo?
19
- ❯ Create a new project Create it only after you confirm the complete configuration.
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 project and .tonbo with this configuration?
30
- ❯ Create project and .tonbo
31
- Edit configuration Change the project, Harness, execution mode, model, or SDK entrypoint.
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 project my-agent and .tonbo.
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,12 +39,12 @@ 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 project 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.
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
 
46
46
  ```toml
47
- # Tonbo Agent Agent configuration.
47
+ # Tonbo Agent configuration.
48
48
  # Edit this file directly or run `tonbo init` to reconfigure.
49
49
  version = 2
50
50
  agent = "my-agent-my-account.tonbo.sh"
@@ -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 `project-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.
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
- 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 `project use`:
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-project-my-account.tonbo.sh
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 project my-project.
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-project-my-account.tonbo.sh
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-project.my-account@tonbo.sh
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-project.my-account@tonbo.sh
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 project shapes
134
+ ## Supported PI agent shapes
135
135
 
136
136
  ### 1. Agent-local Agent
137
137
 
138
- Keep the normal PI project 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:
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 project
147
+ ### 2. PI Package agent
148
148
 
149
- Use PI's project-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 project; Tonbo excludes `.pi/npm` and `.pi/git` caches and restores packages inside the isolated runtime:
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,7 +196,25 @@ 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/project-resource-model.md)
200
- - [Agent Deployments, Production and Rollouts](../../docs/design/project-deployments.md)
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
+
204
+ ## Machines
205
+
206
+ Machines belong to an Account independently of Agents. Allocate in `us-east-1`, bind an available Machine from Agent detail or the CLI, inspect measured metrics, and release it when no longer needed. Allocation starts no compute. The platform handles idle pause/resume automatically; there are no customer power controls or arbitrary Machine command/upload APIs.
207
+
208
+ ```console
209
+ tonbo --json machine allocate --account <account-uuid> --region us-east-1
210
+ tonbo --json machine list --account <account-uuid>
211
+ tonbo --json machine bind <machine-uuid> --agent <agent-uuid>
212
+ tonbo --json machine show <machine-uuid>
213
+ tonbo --json machine metrics <machine-uuid>
214
+ tonbo --json machine unbind <machine-uuid>
215
+ tonbo --json machine release <machine-uuid>
216
+ ```
217
+
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
+
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
- listProjects(oauthToken) {
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.projects);
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
- createProject(oauthToken, name, organizationId) {
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.project);
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, projectId) {
116
+ exchangeManagementToken(oauthToken, agentId) {
117
117
  return requestJson(
118
118
  this.fetcher,
119
- `${this.accountOrigin}/api/cli/agents/${projectId}/token`,
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
- projectId,
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/${projectId}/source-bundles`;
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(projectId, { spec, origin }, token);
170
- const previous = await this.getProduction(projectId, token);
171
- const previousDeployment = previous ? await this.getDeployment(projectId, previous.deployment_id, token) : null;
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
- projectId,
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(projectId, deployment.id, token);
184
+ deployment = await this.getDeployment(agentId, deployment.id, token);
185
185
  }
186
186
  return { deployment, production, unchanged };
187
187
  }
188
- createDeployment(projectId, body, token) {
188
+ createDeployment(agentId, body, token) {
189
189
  return this.management(
190
190
  "POST",
191
- `/v1/agents/${projectId}/deployments`,
191
+ `/v1/agents/${agentId}/deployments`,
192
192
  token,
193
193
  body
194
194
  ).then((response) => response.data);
195
195
  }
196
- listDeployments(projectId, token) {
197
- return this.managementList(`/v1/agents/${projectId}/deployments`, token);
196
+ listDeployments(agentId, token) {
197
+ return this.managementList(`/v1/agents/${agentId}/deployments`, token);
198
198
  }
199
- getDeployment(projectId, deploymentId, token) {
199
+ getDeployment(agentId, deploymentId, token) {
200
200
  return this.management(
201
201
  "GET",
202
- `/v1/agents/${projectId}/deployments/${deploymentId}`,
202
+ `/v1/agents/${agentId}/deployments/${deploymentId}`,
203
203
  token
204
204
  ).then((response) => response.data);
205
205
  }
206
- getProduction(projectId, token) {
206
+ getProduction(agentId, token) {
207
207
  return this.management(
208
208
  "GET",
209
- `/v1/agents/${projectId}/production`,
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(projectId, body, token) {
219
+ putProduction(agentId, body, token) {
220
220
  return this.management(
221
221
  "PUT",
222
- `/v1/agents/${projectId}/production`,
222
+ `/v1/agents/${agentId}/production`,
223
223
  token,
224
224
  body
225
225
  ).then((response) => response.data);
226
226
  }
227
- listRollouts(projectId, token) {
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
- projectId,
231
+ agentId,
235
232
  prompt,
236
233
  sessionId,
237
234
  token,
238
235
  turnId = randomUUID()
239
236
  }) {
240
- const projectPath = `/v1/agents/${projectId}`;
241
- const production = await this.getProduction(projectId, token);
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
- "POST",
250
- `${projectPath}/sessions`,
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
- `${projectPath}/sessions/${session.id}/turns`,
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
- projectId,
259
+ agentId,
266
260
  sessionId,
267
261
  token,
268
262
  turnId
269
263
  }) {
270
264
  return this.management(
271
265
  "GET",
272
- `/v1/agents/${projectId}/sessions/${sessionId}/turns/${turnId}/events?after=${after}`,
266
+ `/v1/agents/${agentId}/sessions/${sessionId}/turns/${turnId}/events?after=${after}`,
273
267
  token
274
268
  );
275
269
  }
276
- listProjectSecrets(projectId, token) {
270
+ listAgentSecrets(agentId, token) {
277
271
  return this.management(
278
272
  "GET",
279
- `/v1/agents/${projectId}/secrets`,
273
+ `/v1/agents/${agentId}/secrets`,
280
274
  token
281
275
  ).then((body) => body.data);
282
276
  }
283
- setProjectSecret(projectId, name, value, token) {
277
+ setAgentSecret(agentId, name, value, token) {
284
278
  return this.management(
285
279
  "PUT",
286
- `/v1/agents/${projectId}/secrets/${encodeURIComponent(name)}`,
280
+ `/v1/agents/${agentId}/secrets/${encodeURIComponent(name)}`,
287
281
  token,
288
282
  { value }
289
283
  );
290
284
  }
291
- deleteProjectSecret(projectId, name, token) {
285
+ deleteAgentSecret(agentId, name, token) {
292
286
  return this.management(
293
287
  "DELETE",
294
- `/v1/agents/${projectId}/secrets/${encodeURIComponent(name)}`,
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 projectServiceSchema = {
876
+ var agentServiceSchema = {
883
877
  "$schema": "https://json-schema.org/draft/2020-12/schema",
884
- "$id": "https://contracts.tonbo.dev/agents/project-service-v1.schema.json",
885
- "title": "Tonbo Project application service v1",
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/project-service-v1.schema.json"
979
+ "$ref": "https://contracts.tonbo.dev/agents/agent-service-v1.schema.json"
986
980
  },
987
981
  "agent": {
988
- "$ref": "https://contracts.tonbo.dev/agents/project-name-v1.json#/$defs/publicHostname"
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 Project Deployment v1",
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/project-service-v1.schema.json"
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 projectNameContract = {
1070
+ var agentNameContract = {
1077
1071
  "$schema": "https://json-schema.org/draft/2020-12/schema",
1078
- "$id": "https://contracts.tonbo.dev/agents/project-name-v1.json",
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": "<project>-<organization>.tonbo.sh",
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(projectNameContract);
1133
- ajv.addSchema(projectServiceSchema);
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, projectHostname) {
1154
+ function createDeclaration(model = DEFAULT_INFERENCE_MODEL, driver = { kind: "native" }, buildCommand, agentHostname) {
1161
1155
  return parseDeclaration({
1162
1156
  version: 2,
1163
- ...projectHostname ? { agent: projectHostname } : {},
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 bindDeclarationProject(root, projectHostname) {
1168
+ async function bindDeclarationAgent(root, agentHostname) {
1175
1169
  const declaration = await loadDeclaration(root);
1176
- const bound = parseDeclaration({ ...declaration, agent: projectHostname });
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 project.`);
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 project-local path.`
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/project-name.ts
1502
- var PROJECT_NAME_RULE = projectNameContract.$defs.name;
1503
- var PROJECT_NAME_PATTERN = new RegExp(PROJECT_NAME_RULE.pattern);
1504
- var RESERVED_PROJECT_NAMES = new Set(PROJECT_NAME_RULE.not.enum);
1505
- function validateProjectName(name) {
1506
- const valid = name.length >= PROJECT_NAME_RULE.minLength && name.length <= PROJECT_NAME_RULE.maxLength && PROJECT_NAME_PATTERN.test(name) && !RESERVED_PROJECT_NAMES.has(name);
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 ${PROJECT_NAME_RULE.minLength}-${PROJECT_NAME_RULE.maxLength} lowercase letters, digits, or single hyphens, start with a letter, and not be reserved.`
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 projectNameSuggestion(value) {
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, PROJECT_NAME_RULE.maxLength).replace(/-+$/g, "");
1517
- if (name.length < PROJECT_NAME_RULE.minLength) {
1518
- name = `${name || "agent"}-agent`.slice(0, PROJECT_NAME_RULE.maxLength);
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 (RESERVED_PROJECT_NAMES.has(name)) name = `${name}-agent`;
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 resolveProject(deps) {
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
- project: selectProject(await deps.api.listProjects(oauthToken), declaration.agent),
1529
+ agent: selectAgent(await deps.api.listAgents(oauthToken), declaration.agent),
1536
1530
  root
1537
1531
  };
1538
1532
  }
1539
- function selectProject(projects, selector) {
1533
+ function selectAgent(agents, selector) {
1540
1534
  const normalized = selector.toLowerCase();
1541
- const matches = projects.filter(
1542
- (project) => project.id === selector || project.name === normalized || project.publicHostname === normalized
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 project."
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 projectPlan = existingDeclaration?.agent ? {
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
- projectPlan = await selectInitProject(deps, root);
1601
+ agentPlan = await selectInitAgent(deps, root);
1608
1602
  }
1609
1603
  if (interactive) {
1610
1604
  while (true) {
1611
1605
  deps.output({
1612
- message: renderInitSummary(projectPlan, harness, driver, model, entry, buildCommand)
1606
+ message: renderInitSummary(agentPlan, harness, driver, model, entry, buildCommand)
1613
1607
  });
1614
- const saveLabel = initSaveLabel(exists, projectPlan);
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: initProjectSummary(projectPlan),
1637
- name: "Project",
1638
- value: "project"
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 === "project") projectPlan = await selectInitProject(deps, root);
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 projectHostname = projectPlan.kind === "existing" ? projectPlan.publicHostname : void 0;
1676
- let createdProject;
1677
- if (projectPlan.kind === "create") {
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
- createdProject = await deps.api.createProject(
1680
- oauthToken,
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
- projectHostname
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 (createdProject) {
1693
+ if (createdAgent) {
1704
1694
  throw new Error(
1705
- `Created agent ${createdProject.name}, but could not write ${DECLARATION_FILENAME}. Fix the file and run \`tonbo agent use ${createdProject.publicHostname}\`.`,
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,30 +1701,30 @@ 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 projectSummary = createdProject ? `Agent: ${createdProject.name} (${createdProject.publicHostname})
1715
- Organization: ${createdProject.organizationName}
1716
- Application: https://${createdProject.publicHostname}
1717
- SSH after deploy: ssh ${createdProject.sshDestination}` : projectPlan.kind === "existing" ? `Agent: ${projectPlan.name ? `${projectPlan.name} (${projectPlan.publicHostname})` : projectPlan.publicHostname}${projectPlan.organizationName ? `
1718
- Organization: ${projectPlan.organizationName}` : ""}${projectPlan.sshDestination ? `
1719
- SSH after deploy: ssh ${projectPlan.sshDestination}` : ""}` : "Next: tonbo agent create <name>";
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: `${createdProject ? `Created agent ${createdProject.name} and` : exists ? "Updated" : "Created"} ${DECLARATION_FILENAME}.
1722
- ${projectSummary}
1711
+ message: `${createdAgent ? `Created agent ${createdAgent.name} and` : exists ? "Updated" : "Created"} ${DECLARATION_FILENAME}.
1712
+ ${agentSummary}
1723
1713
  Harness: PI
1724
- Mode: ${driverSummary}${projectPlan.kind === "later" ? "" : "\nNext: tonbo deploy"}`,
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, project) {
1730
- if (project.kind === "create") {
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 selectInitProject(deps, root) {
1725
+ async function selectInitAgent(deps, root) {
1736
1726
  for (; ; ) {
1737
- const action = await deps.select("How should this Agent connect to Tonbo?", [
1727
+ const action = await deps.select("How should this directory connect to Tonbo?", [
1738
1728
  {
1739
1729
  description: "Create it only after you confirm the complete configuration.",
1740
1730
  name: "Create a new agent",
@@ -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 = projectNameSuggestion(path4.basename(root)) || "tonbo-agent";
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
- validateProjectName(name);
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 projects = (await deps.api.listProjects(oauthToken)).filter(
1778
- (project2) => project2.status === "active"
1767
+ const agents = (await deps.api.listAgents(oauthToken)).filter(
1768
+ (agent2) => agent2.status === "active"
1779
1769
  );
1780
- if (projects.length === 0) {
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
- "Which agent should this Agent use?",
1786
- projects.map((project2) => ({
1787
- name: project2.name,
1788
- description: `Organization: ${project2.organizationName} \xB7 ${project2.publicHostname} \xB7 ${project2.id}`,
1789
- value: project2.id
1775
+ "Which agent should this directory use?",
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 project = selectProject(projects, id);
1782
+ const agent = selectAgent(agents, id);
1793
1783
  return {
1794
1784
  kind: "existing",
1795
- name: project.name,
1796
- organizationName: project.organizationName,
1797
- publicHostname: project.publicHostname,
1798
- sshDestination: project.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 initProjectSummary(project) {
1803
- if (project.kind === "create")
1804
- return `Create ${project.name}${project.organizationName ? ` in ${project.organizationName}` : ""} (permanent address assigned after creation)`;
1805
- if (project.kind === "existing") {
1806
- return project.name ? `${project.name}${project.organizationName ? ` in ${project.organizationName}` : ""} (${project.publicHostname})` : `Keep ${project.publicHostname}`;
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 project. Ask an admin or owner.");
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(project, harness, driver, model, entry, buildCommand) {
1832
+ function renderInitSummary(agent, harness, driver, model, entry, buildCommand) {
1843
1833
  const lines = [
1844
1834
  "",
1845
1835
  "Tonbo Agent configuration:",
1846
- ` Agent: ${initProjectSummary(project)}`,
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 project-local PI configuration." : "Run pi directly with AGENTS.md and optional project-local .pi resources.",
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,28 +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 projectUseCommand(deps, selector, force = false) {
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 projects = await deps.api.listProjects(oauthToken);
1936
- const project = selectProject(projects, selector);
1937
- if (declaration.agent === project.publicHostname) {
1938
- deps.output({ message: `.tonbo already uses agent ${project.publicHostname}.`, project });
1925
+ const agents = await deps.api.listAgents(oauthToken);
1926
+ const agent = selectAgent(agents, selector);
1927
+ if (declaration.agent === agent.publicHostname) {
1928
+ deps.output({
1929
+ message: `.tonbo already uses agent ${agent.publicHostname}.`,
1930
+ agent
1931
+ });
1939
1932
  return;
1940
1933
  }
1941
1934
  if (declaration.agent && !force) {
1942
- const current = projects.find((candidate) => candidate.publicHostname === declaration.agent);
1935
+ const current = agents.find((candidate) => candidate.publicHostname === declaration.agent);
1943
1936
  const currentLabel = current?.publicHostname ?? declaration.agent;
1944
1937
  if (!deps.interactive()) {
1945
1938
  throw new Error(
1946
- `.tonbo already uses agent ${currentLabel}. Pass --force to rebind it to ${project.publicHostname}.`
1939
+ `.tonbo already uses agent ${currentLabel}. Pass --force to rebind it to ${agent.publicHostname}.`
1947
1940
  );
1948
1941
  }
1949
1942
  const action = await deps.select(
1950
- `Rebind .tonbo from ${currentLabel} to ${project.publicHostname}?`,
1943
+ `Rebind .tonbo from ${currentLabel} to ${agent.publicHostname}?`,
1951
1944
  [
1952
- { name: `Rebind to ${project.publicHostname}`, value: "rebind" },
1945
+ { name: `Rebind to ${agent.publicHostname}`, value: "rebind" },
1953
1946
  { name: "Cancel", value: "cancel" }
1954
1947
  ]
1955
1948
  );
@@ -1958,10 +1951,10 @@ async function projectUseCommand(deps, selector, force = false) {
1958
1951
  return;
1959
1952
  }
1960
1953
  }
1961
- await bindDeclarationProject(root, project.publicHostname);
1962
- deps.output({ message: `Bound .tonbo to agent ${project.publicHostname}.`, project });
1954
+ await bindDeclarationAgent(root, agent.publicHostname);
1955
+ deps.output({ message: `Bound .tonbo to agent ${agent.publicHostname}.`, agent });
1963
1956
  }
1964
- async function projectCreateCommand(deps, name) {
1957
+ async function agentCreateCommand(deps, name) {
1965
1958
  const root = await findDeclarationRoot(deps.cwd());
1966
1959
  const declaration = await loadDeclaration(root);
1967
1960
  if (declaration.agent) {
@@ -1970,28 +1963,28 @@ async function projectCreateCommand(deps, name) {
1970
1963
  );
1971
1964
  }
1972
1965
  const oauthToken = await deps.auth.accessToken();
1973
- validateProjectName(name);
1974
- const project = await deps.api.createProject(oauthToken, name);
1966
+ validateAgentName(name);
1967
+ const agent = await deps.api.createAgent(oauthToken, name);
1975
1968
  try {
1976
- await bindDeclarationProject(root, project.publicHostname);
1969
+ await bindDeclarationAgent(root, agent.publicHostname);
1977
1970
  } catch (error) {
1978
1971
  throw new Error(
1979
- `Created agent ${project.name}, but could not bind ${DECLARATION_FILENAME}. Fix the file and run \`tonbo agent use ${project.publicHostname}\`.`,
1972
+ `Created agent ${agent.name}, but could not bind ${DECLARATION_FILENAME}. Fix the file and run \`tonbo agent use ${agent.publicHostname}\`.`,
1980
1973
  { cause: error }
1981
1974
  );
1982
1975
  }
1983
- deps.output({ message: `Created agent ${project.name} and bound it in .tonbo.`, project });
1976
+ deps.output({ message: `Created agent ${agent.name} and bound it in .tonbo.`, agent });
1984
1977
  }
1985
1978
  async function deployCommand(deps, options = { promote: true }) {
1986
- const { declaration, oauthToken, project, root } = await resolveProject(deps);
1979
+ const { declaration, oauthToken, agent, root } = await resolveAgent(deps);
1987
1980
  if (declaration.build) await runBuildCommand(root, declaration.build.command);
1988
1981
  const source = await buildSourceBundle(root);
1989
1982
  const origin = { actor: "cli", git: await deps.gitProvenance(root) };
1990
- const managementToken = await deps.api.exchangeManagementToken(oauthToken, project.id);
1983
+ const managementToken = await deps.api.exchangeManagementToken(oauthToken, agent.id);
1991
1984
  const result = await deps.api.deploy({
1992
1985
  bundle: source,
1993
1986
  origin,
1994
- projectId: project.id,
1987
+ agentId: agent.id,
1995
1988
  promote: options.promote,
1996
1989
  spec: buildDeploymentSpec(declaration, source),
1997
1990
  token: managementToken
@@ -1999,12 +1992,12 @@ async function deployCommand(deps, options = { promote: true }) {
1999
1992
  const { deployment } = result;
2000
1993
  const shortId = shortDeploymentId(deployment.id);
2001
1994
  const lines = [
2002
- options.promote ? `Deployed agent ${project.name}.` : `Created Deployment ${deploymentName(deployment)} (${shortId}) for agent ${project.name} without promoting it.`,
2003
- `Organization: ${project.organizationName}`,
1995
+ options.promote ? `Deployed agent ${agent.name}.` : `Created Deployment ${deploymentName(deployment)} (${shortId}) for agent ${agent.name} without promoting it.`,
1996
+ `Organization: ${agent.organizationName}`,
2004
1997
  `Deployment: ${deploymentName(deployment)} (${shortId})`,
2005
1998
  `Source: ${deploymentSourceLabel(deployment)}`,
2006
1999
  `Production: ${options.promote ? productionStateLabel(deployment) : `not promoted; run \`tonbo deployments promote ${shortId}\` to move Production here`}`,
2007
- `Application: https://${project.publicHostname}`
2000
+ `Application: https://${agent.publicHostname}`
2008
2001
  ];
2009
2002
  if (result.unchanged) {
2010
2003
  lines.push(
@@ -2020,42 +2013,49 @@ async function deployCommand(deps, options = { promote: true }) {
2020
2013
  ' tonbo run "<prompt>"',
2021
2014
  "",
2022
2015
  "Connect with SSH:",
2023
- ` ssh ${project.sshDestination}`
2016
+ ` ssh ${agent.sshDestination}`
2024
2017
  );
2025
2018
  }
2026
- deps.output({ message: lines.join("\n"), project, ...result });
2019
+ deps.output({ message: lines.join("\n"), agent, ...result });
2027
2020
  }
2028
2021
  async function runCommand(deps, prompt, options) {
2029
- const { oauthToken, project } = await resolveProject(deps);
2030
- const managementToken = await deps.api.exchangeManagementToken(oauthToken, project.id);
2022
+ const { oauthToken, agent } = await resolveAgent(deps);
2023
+ const managementToken = await deps.api.exchangeManagementToken(oauthToken, agent.id);
2031
2024
  const result = await deps.api.run({
2032
- projectId: project.id,
2025
+ agentId: agent.id,
2033
2026
  prompt,
2034
2027
  sessionId: options.session,
2035
2028
  token: managementToken
2036
2029
  });
2037
2030
  deps.output({
2038
2031
  message: result.turn.assistant_text,
2039
- project,
2032
+ agent,
2040
2033
  ...result
2041
2034
  });
2042
2035
  }
2043
- async function projectShowCommand(deps) {
2044
- const { project, token } = await projectManagement(deps);
2045
- const production = await deps.api.getProduction(project.id, token);
2046
- const deployment = production ? await deps.api.getDeployment(project.id, production.deployment_id, token) : null;
2036
+ async function agentListCommand(deps) {
2037
+ const agents = await deps.api.listAgents(await deps.auth.accessToken());
2038
+ deps.output({
2039
+ message: agents.length ? agents.map((agent) => `${agent.name} \xB7 ${agent.publicHostname}`).join("\n") : "No agents are available.",
2040
+ agents
2041
+ });
2042
+ }
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;
2047
2047
  const lines = [
2048
- `agent: ${project.name}`,
2049
- `Organization: ${project.organizationName}`,
2050
- `application: https://${project.publicHostname}`,
2051
- `ssh: ssh ${project.sshDestination}`
2048
+ `agent: ${agent.name}`,
2049
+ `Organization: ${agent.organizationName}`,
2050
+ `application: https://${agent.publicHostname}`,
2051
+ `ssh: ssh ${agent.sshDestination}`
2052
2052
  ];
2053
2053
  if (deployment) {
2054
2054
  lines.push(
2055
2055
  `production: ${deploymentName(deployment)} (${shortDeploymentId(deployment.id)}) ${productionStateLabel(deployment)}`
2056
2056
  );
2057
2057
  }
2058
- deps.output({ message: lines.join("\n"), project, production, deployment });
2058
+ deps.output({ message: lines.join("\n"), agent, production, deployment });
2059
2059
  }
2060
2060
  function shortDeploymentId(id) {
2061
2061
  return id.replace(/-/g, "").slice(0, 8);
@@ -2082,7 +2082,7 @@ function selectDeployment(deployments, prefix) {
2082
2082
  const matches = deployments.filter(
2083
2083
  (deployment) => deployment.id.replace(/-/g, "").startsWith(normalized)
2084
2084
  );
2085
- if (matches.length === 0) throw new Error(`Deployment ${prefix} was not found in this project.`);
2085
+ if (matches.length === 0) throw new Error(`Deployment ${prefix} was not found in this agent.`);
2086
2086
  if (matches.length > 1) {
2087
2087
  throw new Error(
2088
2088
  `Deployment id ${prefix} is ambiguous; it matches ${matches.map((deployment) => shortDeploymentId(deployment.id)).join(", ")}. Use more characters.`
@@ -2108,10 +2108,10 @@ function productionMarker(state) {
2108
2108
  return "";
2109
2109
  }
2110
2110
  async function deploymentsListCommand(deps) {
2111
- const { project, token } = await projectManagement(deps);
2111
+ const { agent, token } = await agentManagement(deps);
2112
2112
  const [deployments, production] = await Promise.all([
2113
- deps.api.listDeployments(project.id, token),
2114
- deps.api.getProduction(project.id, token)
2113
+ deps.api.listDeployments(agent.id, token),
2114
+ deps.api.getProduction(agent.id, token)
2115
2115
  ]);
2116
2116
  const message = deployments.length ? renderTable(
2117
2117
  ["ID", "NAME", "STATUS", "PRODUCTION", "SOURCE", "CREATED", "BY"],
@@ -2124,18 +2124,18 @@ async function deploymentsListCommand(deps) {
2124
2124
  formatTimestamp(deployment.created_at),
2125
2125
  deployment.created_by_user_id ? shortDeploymentId(deployment.created_by_user_id) : ""
2126
2126
  ])
2127
- ) : `No Deployments exist for agent ${project.name}. Run \`tonbo deploy\` to create one.`;
2128
- deps.output({ message, project, production, deployments });
2127
+ ) : `No Deployments exist for agent ${agent.name}. Run \`tonbo deploy\` to create one.`;
2128
+ deps.output({ message, agent, production, deployments });
2129
2129
  }
2130
2130
  async function deploymentsShowCommand(deps, prefix) {
2131
- const { project, token } = await projectManagement(deps);
2132
- const deployment = selectDeployment(await deps.api.listDeployments(project.id, token), prefix);
2133
- const rollouts = (await deps.api.listRollouts(project.id, token)).filter(
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(
2134
2134
  (rollout) => rollout.deployment_id === deployment.id || rollout.previous_deployment_id === deployment.id
2135
2135
  );
2136
2136
  deps.output({
2137
2137
  message: renderDeploymentDetail(deployment, rollouts),
2138
- project,
2138
+ agent,
2139
2139
  deployment,
2140
2140
  rollouts
2141
2141
  });
@@ -2176,43 +2176,43 @@ function renderDeploymentDetail(deployment, rollouts) {
2176
2176
  return lines.join("\n");
2177
2177
  }
2178
2178
  async function deploymentsPromoteCommand(deps, prefix) {
2179
- const { project, token } = await projectManagement(deps);
2180
- const target = selectDeployment(await deps.api.listDeployments(project.id, token), prefix);
2181
- const result = await moveProduction(deps, project.id, target, token);
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);
2182
2182
  deps.output({
2183
- message: describeProductionMove(project, result, "Promoted"),
2184
- project,
2183
+ message: describeProductionMove(agent, result, "Promoted"),
2184
+ agent,
2185
2185
  ...result
2186
2186
  });
2187
2187
  }
2188
2188
  async function deploymentsRollbackCommand(deps, prefix) {
2189
- const { project, token } = await projectManagement(deps);
2189
+ const { agent, token } = await agentManagement(deps);
2190
2190
  let target;
2191
2191
  if (prefix !== void 0) {
2192
- target = selectDeployment(await deps.api.listDeployments(project.id, token), prefix);
2192
+ target = selectDeployment(await deps.api.listDeployments(agent.id, token), prefix);
2193
2193
  } else {
2194
- const rollouts = await deps.api.listRollouts(project.id, token);
2194
+ const rollouts = await deps.api.listRollouts(agent.id, token);
2195
2195
  const previousId = rollouts.find(
2196
2196
  (rollout) => rollout.previous_deployment_id !== null
2197
2197
  )?.previous_deployment_id;
2198
2198
  if (!previousId) {
2199
2199
  throw new Error(
2200
- `Agent ${project.name} has no previous Production Deployment to roll back to. Name one with \`tonbo deployments rollback <id>\`.`
2200
+ `Agent ${agent.name} has no previous Production Deployment to roll back to. Name one with \`tonbo deployments rollback <id>\`.`
2201
2201
  );
2202
2202
  }
2203
- target = await deps.api.getDeployment(project.id, previousId, token);
2203
+ target = await deps.api.getDeployment(agent.id, previousId, token);
2204
2204
  }
2205
- const result = await moveProduction(deps, project.id, target, token);
2205
+ const result = await moveProduction(deps, agent.id, target, token);
2206
2206
  deps.output({
2207
- message: describeProductionMove(project, result, "Rolled back"),
2208
- project,
2207
+ message: describeProductionMove(agent, result, "Rolled back"),
2208
+ agent,
2209
2209
  ...result
2210
2210
  });
2211
2211
  }
2212
- async function moveProduction(deps, projectId, target, token) {
2213
- const previous = await deps.api.getProduction(projectId, token);
2212
+ async function moveProduction(deps, agentId, target, token) {
2213
+ const previous = await deps.api.getProduction(agentId, token);
2214
2214
  const production = await deps.api.putProduction(
2215
- projectId,
2215
+ agentId,
2216
2216
  {
2217
2217
  deployment_id: target.id,
2218
2218
  desired_state: "running",
@@ -2221,49 +2221,49 @@ async function moveProduction(deps, projectId, target, token) {
2221
2221
  token
2222
2222
  );
2223
2223
  return {
2224
- deployment: await deps.api.getDeployment(projectId, target.id, token),
2224
+ deployment: await deps.api.getDeployment(agentId, target.id, token),
2225
2225
  previous,
2226
2226
  production
2227
2227
  };
2228
2228
  }
2229
- function describeProductionMove(project, move, verb) {
2229
+ function describeProductionMove(agent, move, verb) {
2230
2230
  const { deployment, previous } = move;
2231
2231
  const name = `${deploymentName(deployment)} (${shortDeploymentId(deployment.id)})`;
2232
- const headline = previous?.deployment_id === deployment.id ? `Deployment ${name} is already Production for agent ${project.name}; requested it to be running.` : `${verb} ${name} to Production for agent ${project.name}${previous ? ` (from ${shortDeploymentId(previous.deployment_id)})` : ""}.`;
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)})` : ""}.`;
2233
2233
  return `${headline}
2234
2234
  Production: ${productionStateLabel(deployment)}
2235
- Application: https://${project.publicHostname}`;
2235
+ Application: https://${agent.publicHostname}`;
2236
2236
  }
2237
- async function projectManagement(deps) {
2238
- const { oauthToken, project } = await resolveProject(deps);
2237
+ async function agentManagement(deps) {
2238
+ const { oauthToken, agent } = await resolveAgent(deps);
2239
2239
  return {
2240
- project,
2241
- token: await deps.api.exchangeManagementToken(oauthToken, project.id)
2240
+ agent,
2241
+ token: await deps.api.exchangeManagementToken(oauthToken, agent.id)
2242
2242
  };
2243
2243
  }
2244
2244
  async function secretListCommand(deps) {
2245
- const { project, token } = await projectManagement(deps);
2246
- const secrets = await deps.api.listProjectSecrets(project.id, token);
2245
+ const { agent, token } = await agentManagement(deps);
2246
+ const secrets = await deps.api.listAgentSecrets(agent.id, token);
2247
2247
  deps.output({
2248
2248
  message: secrets.length ? secrets.map((secret) => secret.name).join("\n") : "No agent secrets are configured.",
2249
- project,
2249
+ agent,
2250
2250
  secrets
2251
2251
  });
2252
2252
  }
2253
2253
  async function secretSetCommand(deps, name, options) {
2254
2254
  const value = await deps.secretValue(name, options.fromEnv);
2255
- const { project, token } = await projectManagement(deps);
2256
- const secret = await deps.api.setProjectSecret(project.id, name, value, token);
2255
+ const { agent, token } = await agentManagement(deps);
2256
+ const secret = await deps.api.setAgentSecret(agent.id, name, value, token);
2257
2257
  deps.output({
2258
2258
  message: `Set agent secret ${secret.name}. Redeploy to replace the active runtime with this value.`,
2259
- project,
2259
+ agent,
2260
2260
  secret
2261
2261
  });
2262
2262
  }
2263
2263
  async function secretRemoveCommand(deps, name) {
2264
- const { project, token } = await projectManagement(deps);
2265
- await deps.api.deleteProjectSecret(project.id, name, token);
2266
- deps.output({ message: `Removed agent secret ${name}.`, project, name });
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 });
2267
2267
  }
2268
2268
 
2269
2269
  // src/credentials.ts
@@ -2523,7 +2523,7 @@ function jsonOutput(program) {
2523
2523
  return program.opts().json === true;
2524
2524
  }
2525
2525
  function createProgram(dependencies = createDependencies) {
2526
- const program = new Command().name("tonbo").description("Deploy a persistent Agent Agent to Tonbo.").version(packageVersion.version).option("--json", "print machine-readable JSON");
2526
+ const program = new Command().name("tonbo").description("Deploy a persistent Agent to Tonbo.").version(packageVersion.version).option("--json", "print machine-readable JSON");
2527
2527
  program.command("init").description("interactively create a Tonbo Agent declaration in this directory").option("--harness <harness>", "Agent Harness (currently pi)").option("--model <model>", "inference model").option("--driver <driver>", "PI driver: native or command").option("--agent-entry <file>", "Node entry file for the command driver").option("--build-command <argv...>", "build command argv for the command driver").option("--force", `replace an existing ${DECLARATION_FILENAME}`).action(
2528
2528
  async (options) => initCommand(dependencies(jsonOutput(program)), options)
2529
2529
  );
@@ -2583,17 +2583,18 @@ function createProgram(dependencies = createDependencies) {
2583
2583
  })
2584
2584
  );
2585
2585
  });
2586
- const project = program.command("agent").description("manage the agent bound to this Agent directory");
2587
- project.command("create <name>").description("create and bind a agent to this Agent directory").action(async (name) => projectCreateCommand(dependencies(jsonOutput(program)), name));
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))));
2588
2589
  const sshKey = program.command("ssh-key").description("manage public keys used by native agent SSH");
2589
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));
2590
2591
  sshKey.command("remove <fingerprint>").description("revoke an SSH public key from the current Tonbo account").action(
2591
2592
  async (fingerprint) => sshKeyRemoveCommand(dependencies(jsonOutput(program)), fingerprint)
2592
2593
  );
2593
- project.command("use <agent>").description("write a agent ID into this Agent's .tonbo declaration").option("--force", "replace an existing agent binding without confirmation").action(
2594
- async (selector, options) => projectUseCommand(dependencies(jsonOutput(program)), selector, options.force === true)
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)
2595
2596
  );
2596
- project.command("show").description("show the agent bound to this Agent directory and its connection addresses").action(async () => projectShowCommand(dependencies(jsonOutput(program))));
2597
+ agent.command("show").description("show the agent bound to this directory and its connection addresses").action(async () => agentShowCommand(dependencies(jsonOutput(program))));
2597
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(
2598
2599
  async (options) => deployCommand(dependencies(jsonOutput(program)), { promote: options.promote })
2599
2600
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonbo/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Deploy persistent Agents and manage their Machines from the command line.",
5
5
  "homepage": "https://tonbo.dev",
6
6
  "bugs": {