@tonbo/cli 0.0.5 → 0.0.6

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 CHANGED
@@ -11,6 +11,7 @@ The V1 CLI deploys a PI-based Agent directly from a local directory without requ
11
11
 
12
12
  ```console
13
13
  tonbo init
14
+ ? Agent shape: [1] PI project [2] PI Package project [3] PI SDK app [1]:
14
15
  ? Inference model [claude-sonnet-4-5]:
15
16
  Created .tonbo.
16
17
  Next: tonbo project create <slug>
@@ -21,15 +22,14 @@ The generated `.tonbo` is TOML:
21
22
  ```toml
22
23
  version = 1
23
24
 
24
- [execution]
25
- mode = "managed"
25
+ [agent]
26
26
  runtime = "pi"
27
27
 
28
+ [agent.driver]
29
+ kind = "native"
30
+
28
31
  [inference]
29
32
  model = "claude-sonnet-4-5"
30
-
31
- [session_capture]
32
- adapter = "pi-jsonl-v3"
33
33
  ```
34
34
 
35
35
  For automation, use `tonbo init --model <model>`; an existing declaration requires interactive confirmation or `--force`. Then authenticate, bind the directory and deploy its current contents:
@@ -63,9 +63,61 @@ ssh my-project@tonbo.sh
63
63
 
64
64
  The SSH username selects the globally unique Project; the signed public key identifies the Tonbo user and is checked against Project membership and IAM at connection time. 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.
65
65
 
66
- The platform injects scoped inference access and the managed PI runtime writes the declared session format through the session-aware filesystem. Do not put a model-provider API key or a Tonbo credential in `.tonbo`.
66
+ The platform injects scoped inference access and owns PI Session capture. Do not put a model-provider API key or a Tonbo credential in `.tonbo`.
67
+
68
+ Deploy snapshots regular files under `.tonbo`, excluding `.git`, `node_modules`, PI package caches, local environment files and patterns in `.tonboignore`. Equal contents produce the same revision. Coding tools start in the persistent Artifacts workspace, while `AGENTS.md` and trusted `.pi` extensions, skills, prompts and settings load from the uploaded revision.
69
+
70
+ ## Supported PI project shapes
71
+
72
+ ### 1. Project-local Agent
73
+
74
+ 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:
75
+
76
+ ```console
77
+ pi
78
+ tonbo init
79
+ tonbo project create my-agent
80
+ tonbo deploy
81
+ tonbo run "Start the task."
82
+ ```
83
+
84
+ ### 2. PI Package project
85
+
86
+ Use PI's project-local package declaration as the single source of truth. 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:
87
+
88
+ ```console
89
+ pi install -l npm:@acme/my-pi-agent@1.2.3
90
+ pi
91
+ tonbo init
92
+ tonbo deploy
93
+ ```
94
+
95
+ Remote npm packages must use an exact version. Git packages must use a full 40-character commit, for example `git:github.com/acme/my-pi-agent@0123456789abcdef0123456789abcdef01234567`. Movable tags, branches, ranges and `latest` fail deployment.
96
+
97
+ ### 3. PI SDK app
98
+
99
+ Choose `PI SDK app` in `tonbo init`, or declare a command driver directly:
100
+
101
+ ```toml
102
+ version = 1
103
+
104
+ [agent]
105
+ runtime = "pi"
106
+ secrets = ["GITHUB_TOKEN"]
107
+
108
+ [agent.driver]
109
+ kind = "command"
110
+ protocol = "pi-rpc-v1"
111
+ command = ["node", "dist/agent.mjs"]
112
+
113
+ [build]
114
+ command = ["npm", "run", "build"]
115
+
116
+ [inference]
117
+ model = "claude-sonnet-4-5"
118
+ ```
67
119
 
68
- Deploy snapshots regular files under `.tonbo`, excluding `.git`, `node_modules`, local environment files and patterns in `.tonboignore`. Equal contents produce the same revision. Coding tools start in the persistent Artifacts workspace, while `.pi` extensions, skills and prompts load from the uploaded revision.
120
+ `tonbo deploy` runs the build command before creating the source snapshot. The entrypoint must keep stdout exclusively for PI RPC and send logs to stderr. It receives `PI_CODING_AGENT_MODULE`, `PI_CODING_AGENT_DIR`, `PI_CODING_AGENT_SESSION_DIR`, `TONBO_PI_SESSION_FILE`, `TONBO_PI_SESSION_ID`, `TONBO_PI_PROVIDER`, `TONBO_PI_MODEL` and `TONBO_PI_RPC_PROTOCOL`. Load the platform PI SDK from `PI_CODING_AGENT_MODULE`, open `TONBO_PI_SESSION_FILE`, create an `AgentSessionRuntime`, and pass it to `runRpcMode()`. This lets the app customize tools and runtime services without forking PI while preserving Tonbo's durable Session and Turn protocol.
69
121
 
70
122
  For a non-interactive invocation, provide a current Tonbo CLI OAuth access token and an explicit Project:
71
123
 
package/dist/src/app.js CHANGED
@@ -47,6 +47,9 @@ export function createProgram(dependencies = createDependencies) {
47
47
  .command("init")
48
48
  .description("interactively create a Tonbo Agent declaration in this directory")
49
49
  .option("--model <model>", "inference model")
50
+ .option("--driver <driver>", "PI driver: native or command")
51
+ .option("--agent-entry <file>", "Node entry file for the command driver")
52
+ .option("--build-command <argv...>", "build command argv for the command driver")
50
53
  .option("--force", `replace an existing ${DECLARATION_FILENAME}`)
51
54
  .action(async (options) => initCommand(dependencies(program.opts().json), options));
52
55
  program
@@ -0,0 +1 @@
1
+ export declare function runBuildCommand(root: string, command: string[]): Promise<void>;
@@ -0,0 +1,21 @@
1
+ import { spawn } from "node:child_process";
2
+ export async function runBuildCommand(root, command) {
3
+ if (command.length === 0)
4
+ throw new Error("Build command must not be empty.");
5
+ await new Promise((resolve, reject) => {
6
+ const child = spawn(command[0], command.slice(1), {
7
+ cwd: root,
8
+ env: process.env,
9
+ // Command output is progress, not the CLI result. Keep stdout available
10
+ // for the single JSON document emitted by `tonbo --json deploy`.
11
+ stdio: ["inherit", process.stderr, process.stderr],
12
+ });
13
+ child.once("error", reject);
14
+ child.once("exit", (code, signal) => {
15
+ if (code === 0)
16
+ resolve();
17
+ else
18
+ reject(new Error(`Build command failed${signal ? ` with signal ${signal}` : ` with status ${code}`}.`));
19
+ });
20
+ });
21
+ }
@@ -23,6 +23,9 @@ export declare function resolveProject(deps: CommandDependencies, selector?: str
23
23
  }>;
24
24
  export declare function selectProject(projects: ProjectSummary[], selector: string): ProjectSummary;
25
25
  export declare function initCommand(deps: CommandDependencies, options: {
26
+ agentEntry?: string;
27
+ buildCommand?: string[];
28
+ driver?: "native" | "command";
26
29
  force?: boolean;
27
30
  model?: string;
28
31
  }): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { runBuildCommand } from "./build.js";
2
3
  import { buildRevision, createDeclaration, declarationExists, DECLARATION_FILENAME, DEFAULT_INFERENCE_MODEL, loadDeclaration, saveDeclaration, } from "./declaration.js";
3
4
  import { buildSourceBundle, findDeclarationRoot } from "./source.js";
4
5
  import { readSshPublicKey } from "./ssh-key.js";
@@ -53,15 +54,45 @@ export async function initCommand(deps, options) {
53
54
  }
54
55
  overwrite = true;
55
56
  }
57
+ let driver = options.driver;
58
+ if (driver !== undefined && driver !== "native" && driver !== "command") {
59
+ throw new Error("--driver must be native or command.");
60
+ }
61
+ let shape = driver === "command" ? "sdk" : "project";
62
+ if (!driver && deps.interactive()) {
63
+ const answer = (await deps.prompt("Agent shape: [1] PI project [2] PI Package project [3] PI SDK app [1]: ")).trim();
64
+ if (!["", "1", "2", "3"].includes(answer)) {
65
+ throw new Error("Agent shape must be 1, 2, or 3.");
66
+ }
67
+ shape = answer === "2" ? "package" : answer === "3" ? "sdk" : "project";
68
+ driver = shape === "sdk" ? "command" : "native";
69
+ }
70
+ driver ||= "native";
71
+ if (driver === "native" && (options.agentEntry || options.buildCommand)) {
72
+ throw new Error("--agent-entry and --build-command require --driver command.");
73
+ }
74
+ let entry = options.agentEntry?.trim();
75
+ if (driver === "command" && !entry && deps.interactive()) {
76
+ entry = (await deps.prompt("PI SDK entry file [dist/agent.mjs]: ")).trim();
77
+ }
78
+ entry ||= "dist/agent.mjs";
56
79
  let model = options.model?.trim();
57
80
  if (!model && deps.interactive()) {
58
81
  model = (await deps.prompt(`Inference model [${DEFAULT_INFERENCE_MODEL}]: `)).trim();
59
82
  }
60
83
  model ||= DEFAULT_INFERENCE_MODEL;
61
- const declaration = createDeclaration(model);
84
+ const buildCommand = driver === "command" ? (options.buildCommand ?? ["npm", "run", "build"]) : undefined;
85
+ const declaration = createDeclaration(model, driver === "native"
86
+ ? { kind: "native" }
87
+ : { kind: "command", protocol: "pi-rpc-v1", command: ["node", entry] }, buildCommand);
62
88
  await saveDeclaration(root, declaration, overwrite);
89
+ const shapeHint = shape === "package"
90
+ ? "\nPI packages stay in .pi/settings.json; use exact npm versions or Git commits."
91
+ : shape === "sdk"
92
+ ? `\nBuild: ${buildCommand?.join(" ")}\nEntrypoint: node ${entry}`
93
+ : "";
63
94
  deps.output({
64
- message: `${exists ? "Updated" : "Created"} ${DECLARATION_FILENAME}.\nNext: tonbo project create <slug>`,
95
+ message: `${exists ? "Updated" : "Created"} ${DECLARATION_FILENAME}.${shapeHint}\nNext: tonbo project create <slug>`,
65
96
  declaration,
66
97
  path: path.join(root, DECLARATION_FILENAME),
67
98
  });
@@ -151,6 +182,8 @@ export async function projectCreateCommand(deps, slug, name) {
151
182
  export async function deployCommand(deps, selector) {
152
183
  const root = await findDeclarationRoot(deps.cwd());
153
184
  const declaration = await loadDeclaration(root);
185
+ if (declaration.build)
186
+ await runBuildCommand(root, declaration.build.command);
154
187
  const source = await buildSourceBundle(root);
155
188
  const oauthToken = await deps.auth.accessToken();
156
189
  let binding = await deps.config.getBinding(root);
@@ -1,8 +1,9 @@
1
1
  import { Ajv2020 } from "ajv/dist/2020.js";
2
- import { declarationSchema, kubernetesProfilesSchema, projectServiceSchema, revisionSchema, } from "./generated/contracts.js";
2
+ import { declarationSchema, kubernetesProfilesSchema, piAgentSchema, projectServiceSchema, revisionSchema, } from "./generated/contracts.js";
3
3
  const ajv = new Ajv2020({ allErrors: true, useDefaults: true });
4
4
  ajv.addKeyword({ keyword: "x-tonbo-profiles" });
5
5
  ajv.addSchema(kubernetesProfilesSchema);
6
+ ajv.addSchema(piAgentSchema);
6
7
  ajv.addSchema(projectServiceSchema);
7
8
  const validateDeclaration = ajv.compile(declarationSchema);
8
9
  const validateRevision = ajv.compile(revisionSchema);
@@ -2,7 +2,7 @@ import { parseDeclaration } from "./contracts.js";
2
2
  import type { ManagedRevisionSpec, SourceBundle, TonboDeclaration } from "./types.js";
3
3
  export declare const DECLARATION_FILENAME = ".tonbo";
4
4
  export declare const DEFAULT_INFERENCE_MODEL = "claude-sonnet-4-5";
5
- export declare function createDeclaration(model?: string): TonboDeclaration;
5
+ export declare function createDeclaration(model?: string, driver?: TonboDeclaration["agent"]["driver"], buildCommand?: string[]): TonboDeclaration;
6
6
  export declare function renderDeclaration(declaration: TonboDeclaration): string;
7
7
  export declare function declarationExists(root: string): Promise<boolean>;
8
8
  export declare function saveDeclaration(root: string, declaration: TonboDeclaration, overwrite: boolean): Promise<void>;
@@ -5,12 +5,12 @@ import { parse, stringify } from "smol-toml";
5
5
  import { assertManagedRevision, parseDeclaration } from "./contracts.js";
6
6
  export const DECLARATION_FILENAME = ".tonbo";
7
7
  export const DEFAULT_INFERENCE_MODEL = "claude-sonnet-4-5";
8
- export function createDeclaration(model = DEFAULT_INFERENCE_MODEL) {
8
+ export function createDeclaration(model = DEFAULT_INFERENCE_MODEL, driver = { kind: "native" }, buildCommand) {
9
9
  return parseDeclaration({
10
10
  version: 1,
11
- execution: { mode: "managed", runtime: "pi" },
11
+ agent: { runtime: "pi", driver },
12
12
  inference: { model: model.trim() },
13
- session_capture: { adapter: "pi-jsonl-v3" },
13
+ ...(buildCommand ? { build: { command: buildCommand } } : {}),
14
14
  });
15
15
  }
16
16
  export function renderDeclaration(declaration) {
@@ -83,14 +83,13 @@ export async function loadDeclaration(declarationRoot) {
83
83
  export function buildRevision(declaration, source) {
84
84
  const spec = {
85
85
  version: 1,
86
- execution: declaration.execution,
86
+ agent: declaration.agent,
87
87
  source: {
88
88
  format: source.format,
89
89
  sha256: source.sha256,
90
90
  size_bytes: source.size_bytes,
91
91
  },
92
92
  inference: declaration.inference,
93
- session_capture: { adapter: declaration.session_capture.adapter },
94
93
  ...(declaration.service ? { service: declaration.service } : {}),
95
94
  };
96
95
  assertManagedRevision(spec);
@@ -1,3 +1,59 @@
1
+ export declare const piAgentSchema: {
2
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
3
+ readonly $id: "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json";
4
+ readonly title: "PI Agent v1";
5
+ readonly type: "object";
6
+ readonly additionalProperties: false;
7
+ readonly required: readonly ["runtime", "driver"];
8
+ readonly properties: {
9
+ readonly runtime: {
10
+ readonly const: "pi";
11
+ };
12
+ readonly secrets: {
13
+ readonly type: "array";
14
+ readonly maxItems: 32;
15
+ readonly uniqueItems: true;
16
+ readonly items: {
17
+ readonly type: "string";
18
+ readonly pattern: "^[A-Z_][A-Z0-9_]{0,127}$";
19
+ };
20
+ };
21
+ readonly driver: {
22
+ readonly oneOf: readonly [{
23
+ readonly type: "object";
24
+ readonly additionalProperties: false;
25
+ readonly required: readonly ["kind"];
26
+ readonly properties: {
27
+ readonly kind: {
28
+ readonly const: "native";
29
+ };
30
+ };
31
+ }, {
32
+ readonly type: "object";
33
+ readonly additionalProperties: false;
34
+ readonly required: readonly ["kind", "protocol", "command"];
35
+ readonly properties: {
36
+ readonly kind: {
37
+ readonly const: "command";
38
+ };
39
+ readonly protocol: {
40
+ readonly const: "pi-rpc-v1";
41
+ };
42
+ readonly command: {
43
+ readonly type: "array";
44
+ readonly minItems: 1;
45
+ readonly maxItems: 64;
46
+ readonly items: {
47
+ readonly type: "string";
48
+ readonly minLength: 1;
49
+ readonly maxLength: 1024;
50
+ };
51
+ };
52
+ };
53
+ }];
54
+ };
55
+ };
56
+ };
1
57
  export declare const projectServiceSchema: {
2
58
  readonly $schema: "https://json-schema.org/draft/2020-12/schema";
3
59
  readonly $id: "https://contracts.tonbo.dev/agents/project-service-v1.schema.json";
@@ -52,23 +108,13 @@ export declare const declarationSchema: {
52
108
  readonly title: "Tonbo Project Agent declaration v1";
53
109
  readonly type: "object";
54
110
  readonly additionalProperties: false;
55
- readonly required: readonly ["version", "execution", "session_capture"];
111
+ readonly required: readonly ["version", "agent"];
56
112
  readonly properties: {
57
113
  readonly version: {
58
114
  readonly const: 1;
59
115
  };
60
- readonly execution: {
61
- readonly type: "object";
62
- readonly additionalProperties: false;
63
- readonly required: readonly ["mode", "runtime"];
64
- readonly properties: {
65
- readonly mode: {
66
- readonly const: "managed";
67
- };
68
- readonly runtime: {
69
- readonly const: "pi";
70
- };
71
- };
116
+ readonly agent: {
117
+ readonly $ref: "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json";
72
118
  };
73
119
  readonly inference: {
74
120
  readonly type: "object";
@@ -85,13 +131,20 @@ export declare const declarationSchema: {
85
131
  };
86
132
  };
87
133
  };
88
- readonly session_capture: {
134
+ readonly build: {
89
135
  readonly type: "object";
90
136
  readonly additionalProperties: false;
91
- readonly required: readonly ["adapter"];
137
+ readonly required: readonly ["command"];
92
138
  readonly properties: {
93
- readonly adapter: {
94
- readonly const: "pi-jsonl-v3";
139
+ readonly command: {
140
+ readonly type: "array";
141
+ readonly minItems: 1;
142
+ readonly maxItems: 64;
143
+ readonly items: {
144
+ readonly type: "string";
145
+ readonly minLength: 1;
146
+ readonly maxLength: 1024;
147
+ };
95
148
  };
96
149
  };
97
150
  };
@@ -106,23 +159,13 @@ export declare const revisionSchema: {
106
159
  readonly title: "Managed Project revision v1";
107
160
  readonly type: "object";
108
161
  readonly additionalProperties: false;
109
- readonly required: readonly ["version", "execution", "source", "inference", "session_capture"];
162
+ readonly required: readonly ["version", "agent", "source", "inference"];
110
163
  readonly properties: {
111
164
  readonly version: {
112
165
  readonly const: 1;
113
166
  };
114
- readonly execution: {
115
- readonly type: "object";
116
- readonly additionalProperties: false;
117
- readonly required: readonly ["mode", "runtime"];
118
- readonly properties: {
119
- readonly mode: {
120
- readonly const: "managed";
121
- };
122
- readonly runtime: {
123
- readonly const: "pi";
124
- };
125
- };
167
+ readonly agent: {
168
+ readonly $ref: "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json";
126
169
  };
127
170
  readonly source: {
128
171
  readonly type: "object";
@@ -155,16 +198,6 @@ export declare const revisionSchema: {
155
198
  };
156
199
  };
157
200
  };
158
- readonly session_capture: {
159
- readonly type: "object";
160
- readonly additionalProperties: false;
161
- readonly required: readonly ["adapter"];
162
- readonly properties: {
163
- readonly adapter: {
164
- readonly const: "pi-jsonl-v3";
165
- };
166
- };
167
- };
168
201
  readonly service: {
169
202
  readonly $ref: "https://contracts.tonbo.dev/agents/project-service-v1.schema.json";
170
203
  };
@@ -1,5 +1,73 @@
1
- // Generated from contracts/agents/project-service-v1.schema.json and contracts/agents/kubernetes-profiles-v1.schema.json and contracts/agents/tonbo-declaration-v1.schema.json and contracts/agents/managed-revision-v1.schema.json and contracts/agents/source-bundle-v1.json and contracts/agents/pi-session-v1.json.
1
+ // Generated from contracts/agents/pi-agent-v1.schema.json and contracts/agents/project-service-v1.schema.json and contracts/agents/kubernetes-profiles-v1.schema.json and contracts/agents/tonbo-declaration-v1.schema.json and contracts/agents/managed-revision-v1.schema.json and contracts/agents/source-bundle-v1.json and contracts/agents/pi-session-v1.json.
2
2
  // Run pnpm generate:contracts after changing a canonical Agent contract.
3
+ export const piAgentSchema = {
4
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
5
+ "$id": "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json",
6
+ "title": "PI Agent v1",
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "required": [
10
+ "runtime",
11
+ "driver"
12
+ ],
13
+ "properties": {
14
+ "runtime": {
15
+ "const": "pi"
16
+ },
17
+ "secrets": {
18
+ "type": "array",
19
+ "maxItems": 32,
20
+ "uniqueItems": true,
21
+ "items": {
22
+ "type": "string",
23
+ "pattern": "^[A-Z_][A-Z0-9_]{0,127}$"
24
+ }
25
+ },
26
+ "driver": {
27
+ "oneOf": [
28
+ {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": [
32
+ "kind"
33
+ ],
34
+ "properties": {
35
+ "kind": {
36
+ "const": "native"
37
+ }
38
+ }
39
+ },
40
+ {
41
+ "type": "object",
42
+ "additionalProperties": false,
43
+ "required": [
44
+ "kind",
45
+ "protocol",
46
+ "command"
47
+ ],
48
+ "properties": {
49
+ "kind": {
50
+ "const": "command"
51
+ },
52
+ "protocol": {
53
+ "const": "pi-rpc-v1"
54
+ },
55
+ "command": {
56
+ "type": "array",
57
+ "minItems": 1,
58
+ "maxItems": 64,
59
+ "items": {
60
+ "type": "string",
61
+ "minLength": 1,
62
+ "maxLength": 1024
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ]
68
+ }
69
+ }
70
+ };
3
71
  export const projectServiceSchema = {
4
72
  "$schema": "https://json-schema.org/draft/2020-12/schema",
5
73
  "$id": "https://contracts.tonbo.dev/agents/project-service-v1.schema.json",
@@ -60,28 +128,14 @@ export const declarationSchema = {
60
128
  "additionalProperties": false,
61
129
  "required": [
62
130
  "version",
63
- "execution",
64
- "session_capture"
131
+ "agent"
65
132
  ],
66
133
  "properties": {
67
134
  "version": {
68
135
  "const": 1
69
136
  },
70
- "execution": {
71
- "type": "object",
72
- "additionalProperties": false,
73
- "required": [
74
- "mode",
75
- "runtime"
76
- ],
77
- "properties": {
78
- "mode": {
79
- "const": "managed"
80
- },
81
- "runtime": {
82
- "const": "pi"
83
- }
84
- }
137
+ "agent": {
138
+ "$ref": "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json"
85
139
  },
86
140
  "inference": {
87
141
  "type": "object",
@@ -100,15 +154,22 @@ export const declarationSchema = {
100
154
  }
101
155
  }
102
156
  },
103
- "session_capture": {
157
+ "build": {
104
158
  "type": "object",
105
159
  "additionalProperties": false,
106
160
  "required": [
107
- "adapter"
161
+ "command"
108
162
  ],
109
163
  "properties": {
110
- "adapter": {
111
- "const": "pi-jsonl-v3"
164
+ "command": {
165
+ "type": "array",
166
+ "minItems": 1,
167
+ "maxItems": 64,
168
+ "items": {
169
+ "type": "string",
170
+ "minLength": 1,
171
+ "maxLength": 1024
172
+ }
112
173
  }
113
174
  }
114
175
  },
@@ -125,30 +186,16 @@ export const revisionSchema = {
125
186
  "additionalProperties": false,
126
187
  "required": [
127
188
  "version",
128
- "execution",
189
+ "agent",
129
190
  "source",
130
- "inference",
131
- "session_capture"
191
+ "inference"
132
192
  ],
133
193
  "properties": {
134
194
  "version": {
135
195
  "const": 1
136
196
  },
137
- "execution": {
138
- "type": "object",
139
- "additionalProperties": false,
140
- "required": [
141
- "mode",
142
- "runtime"
143
- ],
144
- "properties": {
145
- "mode": {
146
- "const": "managed"
147
- },
148
- "runtime": {
149
- "const": "pi"
150
- }
151
- }
197
+ "agent": {
198
+ "$ref": "https://contracts.tonbo.dev/agents/pi-agent-v1.schema.json"
152
199
  },
153
200
  "source": {
154
201
  "type": "object",
@@ -187,18 +234,6 @@ export const revisionSchema = {
187
234
  }
188
235
  }
189
236
  },
190
- "session_capture": {
191
- "type": "object",
192
- "additionalProperties": false,
193
- "required": [
194
- "adapter"
195
- ],
196
- "properties": {
197
- "adapter": {
198
- "const": "pi-jsonl-v3"
199
- }
200
- }
201
- },
202
237
  "service": {
203
238
  "$ref": "https://contracts.tonbo.dev/agents/project-service-v1.schema.json"
204
239
  }
@@ -1,4 +1,5 @@
1
1
  import type { SourceBundle } from "./types.js";
2
+ export declare function validatePiPackages(root: string): Promise<void>;
2
3
  export declare function findDeclarationRoot(start: string): Promise<string>;
3
4
  /**
4
5
  * Build one deterministic local-source snapshot. Git metadata and timestamps
@@ -15,7 +15,76 @@ const DEFAULT_IGNORES = [
15
15
  "!.env.example",
16
16
  ".tonbo-cache/",
17
17
  ".tonbo-system/",
18
+ ".pi/npm/",
19
+ ".pi/git/",
18
20
  ];
21
+ const EXACT_NPM_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
22
+ const GIT_COMMIT = /^[0-9a-f]{40}$/i;
23
+ function packageSource(value, index) {
24
+ if (typeof value === "string")
25
+ return value;
26
+ if (value && typeof value === "object" && "source" in value && typeof value.source === "string")
27
+ return value.source;
28
+ throw new Error(`.pi/settings.json packages[${index}] must be a source string or object.`);
29
+ }
30
+ async function validatePackageSource(root, source) {
31
+ if (source.startsWith("npm:")) {
32
+ const specifier = source.slice(4);
33
+ const separator = specifier.lastIndexOf("@");
34
+ if (separator <= 0 || !EXACT_NPM_VERSION.test(specifier.slice(separator + 1))) {
35
+ throw new Error(`PI package ${source} must pin an exact npm version, for example npm:my-agent@1.2.3.`);
36
+ }
37
+ return;
38
+ }
39
+ if (source.startsWith("git:")) {
40
+ const separator = source.lastIndexOf("@");
41
+ if (separator <= "git:".length || !GIT_COMMIT.test(source.slice(separator + 1))) {
42
+ throw new Error(`PI package ${source} must pin a full 40-character Git commit.`);
43
+ }
44
+ return;
45
+ }
46
+ if (source.startsWith("./") || source.startsWith("../")) {
47
+ const settingsDirectory = path.join(root, ".pi");
48
+ const resolved = path.resolve(settingsDirectory, source);
49
+ const relative = path.relative(root, resolved);
50
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
51
+ throw new Error(`Local PI package ${source} resolves outside the deployed project.`);
52
+ }
53
+ let metadata;
54
+ try {
55
+ metadata = await lstat(resolved);
56
+ }
57
+ catch (error) {
58
+ if (error.code === "ENOENT") {
59
+ throw new Error(`Local PI package ${source} does not exist.`);
60
+ }
61
+ throw error;
62
+ }
63
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
64
+ throw new Error(`Local PI package ${source} must resolve to a real project directory.`);
65
+ }
66
+ return;
67
+ }
68
+ throw new Error(`PI package ${source} must use an exact npm version, a full Git commit, or a project-local path.`);
69
+ }
70
+ export async function validatePiPackages(root) {
71
+ const filename = path.join(root, ".pi", "settings.json");
72
+ let settings;
73
+ try {
74
+ settings = JSON.parse(await readFile(filename, "utf8"));
75
+ }
76
+ catch (error) {
77
+ if (error.code === "ENOENT")
78
+ return;
79
+ throw new Error(`Could not read ${filename} as JSON.`, { cause: error });
80
+ }
81
+ if (!settings || typeof settings !== "object" || !("packages" in settings))
82
+ return;
83
+ const packages = settings.packages;
84
+ if (!Array.isArray(packages))
85
+ throw new Error(`${filename} packages must be an array.`);
86
+ await Promise.all(packages.map((value, index) => validatePackageSource(root, packageSource(value, index))));
87
+ }
19
88
  export async function findDeclarationRoot(start) {
20
89
  let candidate = path.resolve(start);
21
90
  for (;;) {
@@ -103,6 +172,7 @@ function addEntry(pack, file, contents) {
103
172
  */
104
173
  export async function buildSourceBundle(root) {
105
174
  const resolvedRoot = path.resolve(root);
175
+ await validatePiPackages(resolvedRoot);
106
176
  const files = await collectFiles(resolvedRoot);
107
177
  const payloadBytes = files.reduce((total, file) => total + file.size, 0);
108
178
  if (payloadBytes > MAX_BUNDLE_BYTES) {
@@ -1,14 +1,11 @@
1
1
  export interface TonboDeclaration {
2
2
  version: 1;
3
- execution: {
4
- mode: "managed";
5
- runtime: "pi";
6
- };
3
+ agent: PiAgent;
7
4
  inference: {
8
5
  model: string;
9
6
  };
10
- session_capture: {
11
- adapter: "pi-jsonl-v3";
7
+ build?: {
8
+ command: string[];
12
9
  };
13
10
  service?: {
14
11
  command: string[];
@@ -20,10 +17,7 @@ export interface TonboDeclaration {
20
17
  }
21
18
  export interface ManagedRevisionSpec {
22
19
  version: 1;
23
- execution: {
24
- mode: "managed";
25
- runtime: "pi";
26
- };
20
+ agent: PiAgent;
27
21
  source: {
28
22
  format: "tar-v1";
29
23
  sha256: string;
@@ -32,9 +26,6 @@ export interface ManagedRevisionSpec {
32
26
  inference: {
33
27
  model: string;
34
28
  };
35
- session_capture: {
36
- adapter: "pi-jsonl-v3";
37
- };
38
29
  service?: {
39
30
  command: string[];
40
31
  secrets?: string[];
@@ -43,6 +34,17 @@ export interface ManagedRevisionSpec {
43
34
  };
44
35
  };
45
36
  }
37
+ export type PiAgent = {
38
+ runtime: "pi";
39
+ secrets?: string[];
40
+ driver: {
41
+ kind: "native";
42
+ } | {
43
+ kind: "command";
44
+ protocol: "pi-rpc-v1";
45
+ command: string[];
46
+ };
47
+ };
46
48
  export interface SourceBundle {
47
49
  bytes: Buffer;
48
50
  format: "tar-v1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonbo/cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Deploy one persistent Agent per Project from the command line.",
5
5
  "homepage": "https://tonbo.dev",
6
6
  "bugs": {