@tpsdev-ai/cli 0.1.0 → 0.3.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 (198) hide show
  1. package/README.md +46 -60
  2. package/bin/tps.cjs +22 -0
  3. package/bin/tps.ts +435 -0
  4. package/nono-profiles/tps-backup.toml +18 -0
  5. package/nono-profiles/tps-bootstrap.toml +20 -0
  6. package/nono-profiles/tps-office-manager.toml +21 -0
  7. package/nono-profiles/tps-restore.toml +18 -0
  8. package/nono-profiles/tps-status.toml +19 -0
  9. package/package.json +19 -15
  10. package/LICENSE +0 -201
  11. package/dist/bin/tps.d.ts +0 -3
  12. package/dist/bin/tps.d.ts.map +0 -1
  13. package/dist/bin/tps.js +0 -267
  14. package/dist/bin/tps.js.map +0 -1
  15. package/dist/src/cli/hire.d.ts +0 -16
  16. package/dist/src/cli/hire.d.ts.map +0 -1
  17. package/dist/src/cli/hire.js +0 -176
  18. package/dist/src/cli/hire.js.map +0 -1
  19. package/dist/src/cli/office.d.ts +0 -7
  20. package/dist/src/cli/office.d.ts.map +0 -1
  21. package/dist/src/cli/office.js +0 -51
  22. package/dist/src/cli/office.js.map +0 -1
  23. package/dist/src/cli/review.d.ts +0 -9
  24. package/dist/src/cli/review.d.ts.map +0 -1
  25. package/dist/src/cli/review.js +0 -109
  26. package/dist/src/cli/review.js.map +0 -1
  27. package/dist/src/cli/roster.d.ts +0 -6
  28. package/dist/src/cli/roster.d.ts.map +0 -1
  29. package/dist/src/cli/roster.js +0 -60
  30. package/dist/src/cli/roster.js.map +0 -1
  31. package/dist/src/commands/branch.d.ts +0 -14
  32. package/dist/src/commands/branch.d.ts.map +0 -1
  33. package/dist/src/commands/branch.js +0 -395
  34. package/dist/src/commands/branch.js.map +0 -1
  35. package/dist/src/commands/context.d.ts +0 -9
  36. package/dist/src/commands/context.d.ts.map +0 -1
  37. package/dist/src/commands/context.js +0 -57
  38. package/dist/src/commands/context.js.map +0 -1
  39. package/dist/src/commands/identity.d.ts +0 -13
  40. package/dist/src/commands/identity.d.ts.map +0 -1
  41. package/dist/src/commands/identity.js +0 -231
  42. package/dist/src/commands/identity.js.map +0 -1
  43. package/dist/src/commands/mail.d.ts +0 -12
  44. package/dist/src/commands/mail.d.ts.map +0 -1
  45. package/dist/src/commands/mail.js +0 -225
  46. package/dist/src/commands/mail.js.map +0 -1
  47. package/dist/src/commands/office.d.ts +0 -19
  48. package/dist/src/commands/office.d.ts.map +0 -1
  49. package/dist/src/commands/office.js +0 -598
  50. package/dist/src/commands/office.js.map +0 -1
  51. package/dist/src/commands/roster.d.ts +0 -10
  52. package/dist/src/commands/roster.d.ts.map +0 -1
  53. package/dist/src/commands/roster.js +0 -143
  54. package/dist/src/commands/roster.js.map +0 -1
  55. package/dist/src/generators/claude-code.d.ts +0 -17
  56. package/dist/src/generators/claude-code.d.ts.map +0 -1
  57. package/dist/src/generators/claude-code.js +0 -80
  58. package/dist/src/generators/claude-code.js.map +0 -1
  59. package/dist/src/generators/codex.d.ts +0 -22
  60. package/dist/src/generators/codex.d.ts.map +0 -1
  61. package/dist/src/generators/codex.js +0 -78
  62. package/dist/src/generators/codex.js.map +0 -1
  63. package/dist/src/generators/ollama.d.ts +0 -18
  64. package/dist/src/generators/ollama.d.ts.map +0 -1
  65. package/dist/src/generators/ollama.js +0 -97
  66. package/dist/src/generators/ollama.js.map +0 -1
  67. package/dist/src/generators/openclaw.d.ts +0 -15
  68. package/dist/src/generators/openclaw.d.ts.map +0 -1
  69. package/dist/src/generators/openclaw.js +0 -103
  70. package/dist/src/generators/openclaw.js.map +0 -1
  71. package/dist/src/generators/registry.d.ts +0 -36
  72. package/dist/src/generators/registry.d.ts.map +0 -1
  73. package/dist/src/generators/registry.js +0 -99
  74. package/dist/src/generators/registry.js.map +0 -1
  75. package/dist/src/schema/manifest.d.ts +0 -140
  76. package/dist/src/schema/manifest.d.ts.map +0 -1
  77. package/dist/src/schema/manifest.js +0 -62
  78. package/dist/src/schema/manifest.js.map +0 -1
  79. package/dist/src/schema/report.d.ts +0 -166
  80. package/dist/src/schema/report.d.ts.map +0 -1
  81. package/dist/src/schema/report.js +0 -90
  82. package/dist/src/schema/report.js.map +0 -1
  83. package/dist/src/schema/sanitizer.d.ts +0 -30
  84. package/dist/src/schema/sanitizer.d.ts.map +0 -1
  85. package/dist/src/schema/sanitizer.js +0 -97
  86. package/dist/src/schema/sanitizer.js.map +0 -1
  87. package/dist/src/soundstage/mock-llm.d.ts +0 -3
  88. package/dist/src/soundstage/mock-llm.d.ts.map +0 -1
  89. package/dist/src/soundstage/mock-llm.js +0 -68
  90. package/dist/src/soundstage/mock-llm.js.map +0 -1
  91. package/dist/src/utils/agent-info.d.ts +0 -28
  92. package/dist/src/utils/agent-info.d.ts.map +0 -1
  93. package/dist/src/utils/agent-info.js +0 -102
  94. package/dist/src/utils/agent-info.js.map +0 -1
  95. package/dist/src/utils/archive.d.ts +0 -27
  96. package/dist/src/utils/archive.d.ts.map +0 -1
  97. package/dist/src/utils/archive.js +0 -80
  98. package/dist/src/utils/archive.js.map +0 -1
  99. package/dist/src/utils/config-inject.d.ts +0 -27
  100. package/dist/src/utils/config-inject.d.ts.map +0 -1
  101. package/dist/src/utils/config-inject.js +0 -83
  102. package/dist/src/utils/config-inject.js.map +0 -1
  103. package/dist/src/utils/config.d.ts +0 -30
  104. package/dist/src/utils/config.d.ts.map +0 -1
  105. package/dist/src/utils/config.js +0 -55
  106. package/dist/src/utils/config.js.map +0 -1
  107. package/dist/src/utils/connection-state.d.ts +0 -27
  108. package/dist/src/utils/connection-state.d.ts.map +0 -1
  109. package/dist/src/utils/connection-state.js +0 -81
  110. package/dist/src/utils/connection-state.js.map +0 -1
  111. package/dist/src/utils/context.d.ts +0 -14
  112. package/dist/src/utils/context.d.ts.map +0 -1
  113. package/dist/src/utils/context.js +0 -68
  114. package/dist/src/utils/context.js.map +0 -1
  115. package/dist/src/utils/github-webhook.d.ts +0 -3
  116. package/dist/src/utils/github-webhook.d.ts.map +0 -1
  117. package/dist/src/utils/github-webhook.js +0 -105
  118. package/dist/src/utils/github-webhook.js.map +0 -1
  119. package/dist/src/utils/identity.d.ts +0 -114
  120. package/dist/src/utils/identity.d.ts.map +0 -1
  121. package/dist/src/utils/identity.js +0 -341
  122. package/dist/src/utils/identity.js.map +0 -1
  123. package/dist/src/utils/internal-mail.d.ts +0 -18
  124. package/dist/src/utils/internal-mail.d.ts.map +0 -1
  125. package/dist/src/utils/internal-mail.js +0 -75
  126. package/dist/src/utils/internal-mail.js.map +0 -1
  127. package/dist/src/utils/loop-detector.d.ts +0 -27
  128. package/dist/src/utils/loop-detector.d.ts.map +0 -1
  129. package/dist/src/utils/loop-detector.js +0 -42
  130. package/dist/src/utils/loop-detector.js.map +0 -1
  131. package/dist/src/utils/mail-handler.d.ts +0 -19
  132. package/dist/src/utils/mail-handler.d.ts.map +0 -1
  133. package/dist/src/utils/mail-handler.js +0 -94
  134. package/dist/src/utils/mail-handler.js.map +0 -1
  135. package/dist/src/utils/mail.d.ts +0 -22
  136. package/dist/src/utils/mail.d.ts.map +0 -1
  137. package/dist/src/utils/mail.js +0 -111
  138. package/dist/src/utils/mail.js.map +0 -1
  139. package/dist/src/utils/manifest.d.ts +0 -36
  140. package/dist/src/utils/manifest.d.ts.map +0 -1
  141. package/dist/src/utils/manifest.js +0 -91
  142. package/dist/src/utils/manifest.js.map +0 -1
  143. package/dist/src/utils/noise-ik-transport.d.ts +0 -18
  144. package/dist/src/utils/noise-ik-transport.d.ts.map +0 -1
  145. package/dist/src/utils/noise-ik-transport.js +0 -357
  146. package/dist/src/utils/noise-ik-transport.js.map +0 -1
  147. package/dist/src/utils/nono.d.ts +0 -72
  148. package/dist/src/utils/nono.d.ts.map +0 -1
  149. package/dist/src/utils/nono.js +0 -166
  150. package/dist/src/utils/nono.js.map +0 -1
  151. package/dist/src/utils/outbox.d.ts +0 -10
  152. package/dist/src/utils/outbox.d.ts.map +0 -1
  153. package/dist/src/utils/outbox.js +0 -29
  154. package/dist/src/utils/outbox.js.map +0 -1
  155. package/dist/src/utils/output.d.ts +0 -17
  156. package/dist/src/utils/output.d.ts.map +0 -1
  157. package/dist/src/utils/output.js +0 -83
  158. package/dist/src/utils/output.js.map +0 -1
  159. package/dist/src/utils/plain-tcp-transport.d.ts +0 -10
  160. package/dist/src/utils/plain-tcp-transport.d.ts.map +0 -1
  161. package/dist/src/utils/plain-tcp-transport.js +0 -209
  162. package/dist/src/utils/plain-tcp-transport.js.map +0 -1
  163. package/dist/src/utils/provision.d.ts +0 -2
  164. package/dist/src/utils/provision.d.ts.map +0 -1
  165. package/dist/src/utils/provision.js +0 -186
  166. package/dist/src/utils/provision.js.map +0 -1
  167. package/dist/src/utils/relay.d.ts +0 -30
  168. package/dist/src/utils/relay.d.ts.map +0 -1
  169. package/dist/src/utils/relay.js +0 -539
  170. package/dist/src/utils/relay.js.map +0 -1
  171. package/dist/src/utils/sandbox.d.ts +0 -37
  172. package/dist/src/utils/sandbox.d.ts.map +0 -1
  173. package/dist/src/utils/sandbox.js +0 -126
  174. package/dist/src/utils/sandbox.js.map +0 -1
  175. package/dist/src/utils/transport.d.ts +0 -62
  176. package/dist/src/utils/transport.d.ts.map +0 -1
  177. package/dist/src/utils/transport.js +0 -75
  178. package/dist/src/utils/transport.js.map +0 -1
  179. package/dist/src/utils/wall.d.ts +0 -5
  180. package/dist/src/utils/wall.d.ts.map +0 -1
  181. package/dist/src/utils/wall.js +0 -51
  182. package/dist/src/utils/wall.js.map +0 -1
  183. package/dist/src/utils/wire-delivery.d.ts +0 -10
  184. package/dist/src/utils/wire-delivery.d.ts.map +0 -1
  185. package/dist/src/utils/wire-delivery.js +0 -57
  186. package/dist/src/utils/wire-delivery.js.map +0 -1
  187. package/dist/src/utils/wire-frame.d.ts +0 -10
  188. package/dist/src/utils/wire-frame.d.ts.map +0 -1
  189. package/dist/src/utils/wire-frame.js +0 -66
  190. package/dist/src/utils/wire-frame.js.map +0 -1
  191. package/dist/src/utils/wire-mail.d.ts +0 -54
  192. package/dist/src/utils/wire-mail.d.ts.map +0 -1
  193. package/dist/src/utils/wire-mail.js +0 -24
  194. package/dist/src/utils/wire-mail.js.map +0 -1
  195. package/dist/src/utils/ws-noise-transport.d.ts +0 -18
  196. package/dist/src/utils/ws-noise-transport.d.ts.map +0 -1
  197. package/dist/src/utils/ws-noise-transport.js +0 -356
  198. package/dist/src/utils/ws-noise-transport.js.map +0 -1
package/README.md CHANGED
@@ -1,79 +1,65 @@
1
- # TPS (Team Provisioning System)
1
+ # @tpsdev-ai/cli
2
2
 
3
- > "Yeah... I'm gonna need you to go ahead and come in on Saturday. We lost some people this week and we need to sort of play catch-up."
3
+ > TPS (Team Provisioning System) an Agent OS CLI for managing isolated AI agents.
4
4
 
5
- **TPS is an Agent OS CLI for managing isolated AI agents in remote branch offices.** It provides the secure primitives for agents to exist, discover each other, communicate asynchronously, and run in isolated environments (Docker sandboxes or remote VMs).
5
+ Hire agents, provision secure branch offices, manage identity and encrypted comms, track operational health all from the command line.
6
6
 
7
- If you want your AI agents to stop stepping on each other's toes and actually get some work done, you're going to need them to file their TPS reports.
8
-
9
- ![Lumbergh Agent](docs/media/lumbergh-agent.png)
10
-
11
- ## Why TPS?
12
-
13
- Most agent frameworks assume all agents run in the same memory space. TPS assumes agents are employees: they work in different offices, they have different security clearances, and they communicate via mail.
14
-
15
- - **The Branch Office**: Agents run in secure, remote sandboxes (VMs or Docker). Host keys never leave the host.
16
- - **The Mailroom**: Async, persistent, cross-boundary messaging.
17
- - **Wire Security**: All traffic over `wss://` is E2E encrypted and mutually authenticated using the **Noise_IK** protocol.
18
- - **The TPS Report**: One `tps.yaml` file defines an agent's identity, capabilities, and mail handlers.
19
-
20
- > "I have eight different bosses right now. So that means that when I make a mistake, I have eight different people coming by to tell me about it." — Make your agents communicate through a single, auditable mail interface instead.
21
-
22
- ## Quickstart
7
+ ## Install
23
8
 
24
9
  ```bash
25
- # 1. Install
26
- npm install -g @tpsdev-ai/cli
27
-
28
- # 2. Init your host identity
29
- tps identity init
30
-
31
- # 3. Create a branch office on a remote VM
32
- # (On the VM)
33
10
  npm install -g @tpsdev-ai/cli
34
- tps branch init --listen 6458 --host my-vm.example.com
35
-
36
- # 4. Join the branch office
37
- # (On your host)
38
- tps office join my-vm "tps://join?host=my-vm.example.com..."
39
-
40
- # 5. Connect the persistent relay
41
- tps office connect my-vm &
11
+ ```
42
12
 
43
- # 6. Send a memo
44
- tps mail send my-vm "Did you get the memo about the TPS reports?"
13
+ ## Commands
14
+
15
+ | Command | Description |
16
+ |---------|-------------|
17
+ | `tps hire <report>` | Onboard a new agent from a TPS report |
18
+ | `tps roster` | List all agents and their status |
19
+ | `tps bootstrap <agent>` | Run first-boot health checks and workspace scaffolding |
20
+ | `tps office setup` | Configure sandbox environment from workspace manifest |
21
+ | `tps backup <agent>` | Create encrypted, checksummed workspace backup |
22
+ | `tps restore <agent> <archive>` | Restore agent workspace with transactional rollback |
23
+ | `tps status` | Operational dashboard — health, uptime, cost tracking |
24
+ | `tps heartbeat <agent>` | Agent self-reports health (workspace, gateway, provider) |
25
+ | `tps identity init` | Generate Ed25519 host keypair |
26
+ | `tps branch init` | Initialize a remote branch office |
27
+ | `tps office join` | Join a branch to the head office via Noise_IK handshake |
28
+ | `tps office connect` | Establish persistent encrypted relay channel |
29
+ | `tps mail send` | Send async mail to a branch agent |
30
+ | `tps mail check` | Check for incoming mail |
31
+ | `tps secrets set/get` | Encrypted vault for API keys and credentials |
45
32
 
46
- # 7. Check the branch status
47
- tps mail send my-vm "status"
48
- tps mail check
49
- ```
33
+ ## Architecture
50
34
 
51
- ## The Three-Channel Model
35
+ TPS treats AI agents like employees in an organization:
52
36
 
53
- Agents shouldn't do everything over a single chat thread. TPS enforces:
54
- 1. **Mail** for messages (commands, status, notifications).
55
- 2. **Git** for artifacts (code, specs, docs).
56
- 3. **APIs** for external data.
37
+ - **Branch Offices** isolated sandboxes (Docker, VMs, or `nono` process isolation)
38
+ - **The Mailroom** async, persistent, cross-boundary messaging via Maildir
39
+ - **Wire Security** — Noise_IK protocol over WebSocket for E2E encrypted transport
40
+ - **Identity** Ed25519 keypairs with signed join tokens
57
41
 
58
- ![The Mailroom](docs/media/mailroom.png)
42
+ Agents communicate through three channels:
43
+ 1. **Mail** for messages (tasks, status, coordination)
44
+ 2. **Git** for artifacts (code, reports, generated files)
45
+ 3. **APIs** for external data
59
46
 
60
- ## Plugins & Handlers
47
+ ## Security
61
48
 
62
- Agents can declare `mailHandlers` in their `tps.yaml` manifest. The TPS branch daemon will automatically route incoming mail to the right handler based on regex patterns or sender allowlists.
49
+ - All inter-office traffic is E2E encrypted (Noise_IK + MessagePack)
50
+ - Sandbox profiles enforce filesystem, network, and exec boundaries
51
+ - Encrypted secrets vault (Argon2id key derivation)
52
+ - Audit trail with append-only logging and search
53
+ - Path traversal protection on all user-supplied identifiers
63
54
 
64
- ```yaml
65
- name: deploy-bot
66
- capabilities:
67
- mail_handler:
68
- exec: ./handler.sh
69
- match:
70
- bodyPattern: "^(deploy|status)"
71
- ```
55
+ See [SECURITY.md](https://github.com/tpsdev-ai/cli/blob/main/SECURITY.md) for responsible disclosure.
72
56
 
73
- ## Architecture
57
+ ## Links
74
58
 
75
- Read [ARCHITECTURE.md](ARCHITECTURE.md) for details on the Noise_IK implementation, hub-and-spoke topology, and security boundaries.
59
+ - [GitHub](https://github.com/tpsdev-ai/cli)
60
+ - [Runtime Library](https://www.npmjs.com/package/@tpsdev-ai/agent)
61
+ - [Contributing](https://github.com/tpsdev-ai/cli/blob/main/CONTRIBUTING.md)
76
62
 
77
63
  ## License
78
64
 
79
- Apache 2.0
65
+ Apache-2.0
package/bin/tps.cjs ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execFileSync } = require('child_process');
4
+
5
+ const platform = process.platform;
6
+ const arch = process.arch;
7
+ const pkg = `@tpsdev-ai/cli-${platform}-${arch}`;
8
+
9
+ function runBinary() {
10
+ try {
11
+ const binPath = require.resolve(`${pkg}/tps`);
12
+ execFileSync(binPath, process.argv.slice(2), { stdio: 'inherit' });
13
+ return;
14
+ } catch (err) {
15
+ console.error(`TPS: no binary package available for ${platform}-${arch}.`);
16
+ console.error(`Run npm install -g ${pkg} to install the platform binary package.`);
17
+ console.error('Or run from source inside the repository via `bun run tps` in packages/cli.');
18
+ process.exitCode = 1;
19
+ }
20
+ }
21
+
22
+ runBinary();
package/bin/tps.ts ADDED
@@ -0,0 +1,435 @@
1
+ #!/usr/bin/env node
2
+ import meow from "meow";
3
+
4
+ const cli = meow(
5
+ `
6
+ Usage
7
+ $ tps <command> [options]
8
+
9
+ Commands
10
+ hire <report> Onboard a new agent from a .tps report or persona
11
+ roster <action> Agent directory (list/show/find)
12
+ review <name> Performance review for a specific agent
13
+ office <action> Branch office sandbox lifecycle (start/stop/list/status/kill)
14
+ bootstrap <agent-id> Bring a hired agent to operational state
15
+ backup <agent-id> [--schedule daily|off] [--keep n] [--sanitize] Backup agent workspace
16
+ restore <agent-id> <archive> [--clone] [--overwrite] [--from <archive>] Restore agent workspace from a backup
17
+ status [agent-id] [--auto-prune] [--prune] [--json] [--cost] [--shared]
18
+ heartbeat <agent-id> [--nonono] Send a heartbeat/ping for an agent
19
+ context <action> Workstream context memory (read/update/list)
20
+ mail <action> Mailroom operations (send/check/list/search)
21
+ identity <action> Key management (init/show/register/list/revoke/verify)
22
+ secrets <action> Secret management (set/list/remove)
23
+ git <action> Git utilities (worktree)
24
+ branch <action> Branch office node (init/start/stop/status/log)
25
+
26
+ Options
27
+ --help Show this help text
28
+ --version Show version number
29
+ --config <path> Path to openclaw.json (default: auto-discover)
30
+
31
+ Examples
32
+ $ tps hire developer --name Fred
33
+ $ tps hire ./reports/strategy-lead.tps --dry-run
34
+ $ tps hire developer --name Scout --runtime claude-code
35
+ $ tps hire ops --name Monitor --runtime ollama --base-model llama3.1:8b
36
+ $ tps hire developer --name Coder --runtime codex
37
+ $ tps roster list
38
+ $ tps roster show flint --json
39
+ $ tps roster find --channel discord
40
+ $ tps roster list --config ~/custom/openclaw.json
41
+ $ tps mail send kern "hi"
42
+ $ tps mail check kern
43
+ $ tps mail log --limit 10
44
+ $ tps mail log flint --since 2026-02-20
45
+ $ tps office start branch-a
46
+ $ tps office status branch-a
47
+ $ tps bootstrap flint
48
+ $ tps backup flint --schedule daily
49
+ $ tps restore flint ~/.tps/backups/flint/old.tps-backup.tar.gz
50
+ $ tps status
51
+ $ tps status flint --cost
52
+ $ tps heartbeat flint
53
+ $ tps review flint
54
+
55
+ Built-in personas: developer, designer, support, ea, ops, strategy, security
56
+
57
+ If you could just go ahead and use the correct command, that'd be great.
58
+ `,
59
+ {
60
+ importMeta: import.meta,
61
+ flags: {
62
+ reason: { type: "string" },
63
+ expiresIn: { type: "string" },
64
+ trust: { type: "string" },
65
+ pubkey: { type: "string" },
66
+ encPubkey: { type: "string" },
67
+ name: { type: "string" },
68
+ workspace: { type: "string" },
69
+ dryRun: { type: "boolean", default: false },
70
+ json: { type: "boolean", default: false },
71
+ config: { type: "string" },
72
+ deep: { type: "boolean", default: false },
73
+ summary: { type: "string" },
74
+ channel: { type: "string" },
75
+ branch: { type: "boolean", default: false },
76
+ manifest: { type: "string" },
77
+ soundstage: { type: "boolean", default: false },
78
+ nonono: { type: "boolean", default: false },
79
+ inject: { type: "boolean", default: true },
80
+ runtime: { type: "string", default: "openclaw" },
81
+ baseModel: { type: "string" },
82
+ since: { type: "string" },
83
+ limit: { type: "number" },
84
+ from: { type: "string" },
85
+ clone: { type: "boolean", default: false },
86
+ overwrite: { type: "boolean", default: false },
87
+ schedule: { type: "string" },
88
+ keep: { type: "number" },
89
+ sanitize: { type: "boolean", default: true },
90
+ listen: { type: "number" },
91
+ host: { type: "string" },
92
+ force: { type: "boolean", default: false },
93
+ follow: { type: "boolean", default: false },
94
+ lines: { type: "number" },
95
+ transport: { type: "string" },
96
+ autoPrune: { type: "boolean", default: false },
97
+ prune: { type: "boolean", default: false },
98
+ staleMinutes: { type: "number" },
99
+ offlineHours: { type: "number" },
100
+ shared: { type: "boolean", default: false },
101
+ cost: { type: "boolean", default: false },
102
+ statusOverride: { type: "string" },
103
+ },
104
+ }
105
+ );
106
+
107
+ const [command, ...rest] = cli.input;
108
+
109
+ // nono availability check (skip for --no-nono or office/mail relay commands)
110
+ async function checkNono() {
111
+ if (cli.flags.nonono) return;
112
+ if (command === "office" && rest[0] === "relay") return; // relay runs in background
113
+ const { findNono } = await import("../src/utils/nono.js");
114
+ if (!findNono()) {
115
+ console.warn(
116
+ "⚠️ nono not found. Host agents will run without process isolation.\n" +
117
+ " Install nono for syscall filtering + filesystem boundaries.\n" +
118
+ " Use --nonono to run anyway (not recommended).\n"
119
+ );
120
+ }
121
+ }
122
+
123
+ async function main() {
124
+ await checkNono();
125
+ switch (command) {
126
+ case "hire": {
127
+ const reportPath = rest[0];
128
+ if (!reportPath) {
129
+ console.error(
130
+ "I'm gonna need you to specify a TPS report file or persona.\n\n tps hire <report.tps | persona> [--name Name]\n\nBuilt-in personas: developer, designer, support, ea, ops, strategy"
131
+ );
132
+ process.exit(1);
133
+ }
134
+ const { runHire } = await import("../src/cli/hire.js");
135
+ runHire({
136
+ reportPath,
137
+ name: cli.flags.name,
138
+ workspace: cli.flags.workspace,
139
+ dryRun: cli.flags.dryRun,
140
+ jsonOutput: cli.flags.json,
141
+ configPath: cli.flags.config,
142
+ branch: cli.flags.branch,
143
+ inject: cli.flags.inject,
144
+ runtime: cli.flags.runtime as any,
145
+ baseModel: cli.flags.baseModel,
146
+ });
147
+ break;
148
+ }
149
+ case "roster": {
150
+ // Backward-compatible path (keeps nono re-exec behavior used by existing tests):
151
+ // `tps roster` with no subcommand routes to the legacy CLI implementation.
152
+ if (!rest[0]) {
153
+ const { runRoster } = await import("../src/cli/roster.js");
154
+ runRoster({ configPath: cli.flags.config });
155
+ break;
156
+ }
157
+
158
+ const action = rest[0] as "list" | "show" | "find";
159
+ if (!["list", "show", "find"].includes(action)) {
160
+ console.error(
161
+ "Usage:\n tps roster\n tps roster list\n tps roster show <agent> [--json]\n tps roster find --channel <channel> [--json]"
162
+ );
163
+ process.exit(1);
164
+ }
165
+ const { runRoster } = await import("../src/commands/roster.js");
166
+ runRoster({
167
+ action,
168
+ agent: rest[1],
169
+ channel: cli.flags.channel || undefined,
170
+ json: cli.flags.json,
171
+ configPath: cli.flags.config,
172
+ });
173
+ break;
174
+ }
175
+ case "review": {
176
+ const agentName = rest[0];
177
+ if (!agentName) {
178
+ console.error(
179
+ "Review who? I'm gonna need a name.\n\n tps review <agent-name>"
180
+ );
181
+ process.exit(1);
182
+ }
183
+ const { runReview } = await import("../src/cli/review.js");
184
+ runReview({ agentName, configPath: cli.flags.config, deep: cli.flags.deep });
185
+ break;
186
+ }
187
+ case "bootstrap": {
188
+ const agentId = rest[0];
189
+ if (!agentId) {
190
+ console.error("Usage: tps bootstrap <agent-id>");
191
+ process.exit(1);
192
+ }
193
+
194
+ const { runBootstrap } = await import("../src/commands/bootstrap.js");
195
+ await runBootstrap({
196
+ agentId,
197
+ configPath: cli.flags.config,
198
+ channel: cli.flags.channel,
199
+ });
200
+ break;
201
+ }
202
+
203
+ case "office": {
204
+ const action = rest[0] as "start" | "stop" | "list" | "status" | "relay" | "exec" | "join" | "revoke" | "sync" | "connect" | "kill" | "setup" | undefined;
205
+ const validActions = ["start", "stop", "list", "status", "relay", "exec", "join", "revoke", "sync", "connect", "kill", "setup"];
206
+ // Backward compatibility: `tps office <agent>` maps to `start <agent>`.
207
+ const isLegacy = action && !validActions.includes(action);
208
+ if ((!action && !isLegacy) || (!isLegacy && !validActions.includes(action!))) {
209
+ console.error(
210
+ "Usage:\n tps office start <agent>\n tps office stop <agent>\n tps office list\n tps office status [agent]\n tps office exec <agent> -- <command...>\n tps office join <name> <join-token>\n tps office revoke <name>\n tps office sync <name>\n tps office connect <name>\n tps office setup <agent> [--dry-run]\n tps office kill"
211
+ );
212
+ process.exit(1);
213
+ }
214
+
215
+ const { runOffice } = await import("../src/commands/office.js");
216
+ if (isLegacy) {
217
+ await runOffice({ action: "start", agent: rest[0] });
218
+ } else if (action === "exec") {
219
+ // Everything after "--" is the command
220
+ const dashIdx = process.argv.indexOf("--");
221
+ const execCmd = dashIdx >= 0 ? process.argv.slice(dashIdx + 1) : rest.slice(2);
222
+ await runOffice({ action: "exec", agent: rest[1], command: execCmd });
223
+ } else if (action === "join") {
224
+ const joinToken = rest[2];
225
+ if (!rest[1] || !joinToken) {
226
+ console.error("Usage: tps office join <name> <join-token-url>");
227
+ process.exit(1);
228
+ }
229
+ await runOffice({ action: "join", agent: rest[1], joinToken });
230
+ } else if (action === "revoke") {
231
+ if (!rest[1]) {
232
+ console.error("Usage: tps office revoke <name>");
233
+ process.exit(1);
234
+ }
235
+ await runOffice({ action: "revoke", agent: rest[1] });
236
+ } else if (action === "setup") {
237
+ const dryRun = process.argv.includes("--dry-run") || process.argv.includes("--dry");
238
+ await runOffice({ action: "setup", agent: rest[1], dryRun });
239
+ } else {
240
+ const soundstageIdx = process.argv.indexOf("--soundstage");
241
+ const isSoundstage = soundstageIdx >= 0 || cli.flags.soundstage;
242
+ if (soundstageIdx >= 0) process.argv.splice(soundstageIdx, 1);
243
+
244
+ await runOffice({ action: action!, agent: rest[1], manifest: cli.flags.manifest, soundstage: isSoundstage });
245
+ }
246
+ break;
247
+ }
248
+ case "context": {
249
+ const action = rest[0] as "read" | "update" | "list" | undefined;
250
+ const workstream = rest[1];
251
+ if (!action || !["read", "update", "list"].includes(action)) {
252
+ console.error(
253
+ "Usage:\n tps context read <workstream>\n tps context update <workstream> --summary \"...\"\n tps context list"
254
+ );
255
+ process.exit(1);
256
+ }
257
+ const { runContext } = await import("../src/commands/context.js");
258
+ runContext({
259
+ action,
260
+ workstream,
261
+ summary: cli.flags.summary,
262
+ json: cli.flags.json,
263
+ });
264
+ break;
265
+ }
266
+ case "mail": {
267
+ const action = rest[0] as "send" | "check" | "list" | "log" | "read" | "watch" | "search" | undefined;
268
+ if (cli.flags.help || !action || !["send", "check", "list", "log", "read", "watch", "search"].includes(action)) {
269
+ console.log(
270
+ "Usage:\n tps mail send <agent> <message> Send mail to a local or remote agent\n tps mail check [agent] Read new messages (marks as read)\n tps mail watch [agent] Watch inbox for new messages\n tps mail list [agent] List all messages (read + unread)\n tps mail read <agent> <id> Show a specific message by ID (prefix ok)\n tps mail search <query> Search mail history using full-text search\n tps mail log [agent] Show audit log [--since YYYY-MM-DD] [--limit N]"
271
+ );
272
+ process.exit(cli.flags.help ? 0 : 1);
273
+ }
274
+ const { runMail } = await import("../src/commands/mail.js");
275
+ await runMail({
276
+ action,
277
+ agent: rest[1],
278
+ message: action === "send" ? rest.slice(2).join(" ") : undefined,
279
+ messageId: action === "read" ? rest[2] : undefined,
280
+ json: cli.flags.json,
281
+ since: cli.flags.since,
282
+ limit: cli.flags.limit ? Number(cli.flags.limit) : undefined,
283
+ });
284
+ break;
285
+ }
286
+ case "identity": {
287
+ const action = rest[0] as "init" | "show" | "register" | "list" | "revoke" | "verify" | undefined;
288
+ if (!action || !["init", "show", "register", "list", "revoke", "verify"].includes(action)) {
289
+ console.error(
290
+ "Usage:\n tps identity init [--expires-in 90d]\n tps identity show\n tps identity register <branch> [--expires-in 90d] [--trust standard]\n tps identity list\n tps identity revoke <branch> --reason \"...\"\n tps identity verify <branch>"
291
+ );
292
+ process.exit(1);
293
+ }
294
+ const { runIdentity } = await import("../src/commands/identity.js");
295
+ await runIdentity({
296
+ action,
297
+ branch: rest[1],
298
+ reason: cli.flags.reason,
299
+ json: cli.flags.json,
300
+ expiresIn: cli.flags.expiresIn,
301
+ trust: cli.flags.trust as any,
302
+ pubkey: cli.flags.pubkey,
303
+ encPubkey: cli.flags.encPubkey,
304
+ });
305
+ break;
306
+ }
307
+ case "secrets": {
308
+ const action = rest[0] as "set" | "list" | "remove" | undefined;
309
+ if (!action || !["set", "list", "remove"].includes(action)) {
310
+ console.error("Usage:\n tps secrets set <KEY>=<VALUE>\n tps secrets list\n tps secrets remove <KEY>");
311
+ process.exit(1);
312
+ }
313
+ const { runSecrets } = await import("../src/commands/secrets.js");
314
+ let key: string | undefined;
315
+ let value: string | undefined;
316
+ if (action === "set") {
317
+ const parts = rest[1]?.split("=");
318
+ key = parts?.[0];
319
+ value = parts?.slice(1).join("=");
320
+ } else {
321
+ key = rest[1];
322
+ }
323
+ await runSecrets({ action, key, value, json: cli.flags.json });
324
+ break;
325
+ }
326
+ case "backup": {
327
+ const agentId = rest[0];
328
+ if (!agentId) {
329
+ console.error("Usage: tps backup <agent-id> [--schedule daily|off] [--keep n]");
330
+ process.exit(1);
331
+ }
332
+ const { runBackup } = await import("../src/commands/backup.js");
333
+ await runBackup({
334
+ agentId,
335
+ keep: typeof cli.flags.keep === "number" ? Number(cli.flags.keep) : undefined,
336
+ schedule: cli.flags.schedule,
337
+ sanitize: cli.flags.sanitize,
338
+ configPath: cli.flags.config,
339
+ });
340
+ break;
341
+ }
342
+ case "restore": {
343
+ const agentId = rest[0];
344
+ const archivePath = cli.flags.from || rest[1];
345
+ if (!agentId || !archivePath) {
346
+ console.error("Usage: tps restore <agent-id> <archive> [--from <archive>] [--clone] [--overwrite] [--force]");
347
+ process.exit(1);
348
+ }
349
+ const { runRestore } = await import("../src/commands/backup.js");
350
+ await runRestore({
351
+ agentId,
352
+ archivePath,
353
+ force: !!cli.flags.force,
354
+ overwrite: !!cli.flags.overwrite,
355
+ clone: !!cli.flags.clone,
356
+ configPath: cli.flags.config,
357
+ });
358
+ break;
359
+ }
360
+ case "heartbeat": {
361
+ const agentId = rest[0];
362
+ if (!agentId) {
363
+ console.error("Usage: tps heartbeat <agent-id>");
364
+ process.exit(1);
365
+ }
366
+ const { runHeartbeat } = await import("../src/commands/status.js");
367
+ await runHeartbeat({
368
+ agentId,
369
+ status: (cli.flags.statusOverride as any) || undefined,
370
+ nonono: !!cli.flags.nonono,
371
+ profile: "tps-status",
372
+ });
373
+ break;
374
+ }
375
+ case "status": {
376
+ const agentId = rest[0];
377
+ const { runStatus } = await import("../src/commands/status.js");
378
+ await runStatus({
379
+ agentId,
380
+ autoPrune: !!cli.flags.autoPrune,
381
+ prune: !!cli.flags.prune,
382
+ json: !!cli.flags.json,
383
+ staleMinutes: cli.flags.staleMinutes ? Number(cli.flags.staleMinutes) : undefined,
384
+ offlineHours: cli.flags.offlineHours ? Number(cli.flags.offlineHours) : undefined,
385
+ cost: !!cli.flags.cost,
386
+ shared: !!cli.flags.shared,
387
+ });
388
+ break;
389
+ }
390
+ case "branch": {
391
+ const action = rest[0] as "init" | "start" | "stop" | "status" | "log" | undefined;
392
+ const valid = ["init", "start", "stop", "status", "log"];
393
+ if (!action || !valid.includes(action)) {
394
+ console.error("Usage:\n tps branch init [--listen <port>] [--host <hostname>] [--transport ws|tcp]\n tps branch start\n tps branch stop\n tps branch status\n tps branch log [--lines N] [--follow]");
395
+ process.exit(1);
396
+ }
397
+ const { runBranch } = await import("../src/commands/branch.js");
398
+ await runBranch({
399
+ action,
400
+ port: typeof cli.flags.listen === "number" ? Number(cli.flags.listen) : undefined,
401
+ host: cli.flags.host,
402
+ transport: cli.flags.transport === "tcp" ? "tcp" : cli.flags.transport === "ws" ? "ws" : undefined,
403
+ force: cli.flags.force,
404
+ lines: typeof cli.flags.lines === "number" ? Number(cli.flags.lines) : undefined,
405
+ follow: !!cli.flags.follow,
406
+ });
407
+ break;
408
+ }
409
+ case "git": {
410
+ const action = rest[0];
411
+ if (action === "worktree") {
412
+ const agent = rest[1];
413
+ const repoPath = rest[2];
414
+ const branchName = rest[3];
415
+ if (!agent || !repoPath) {
416
+ console.error("Usage: tps git worktree <agent> <repo-path> [branch-name]");
417
+ process.exit(1);
418
+ }
419
+ const { runGit } = await import("../src/commands/git.js");
420
+ await runGit({ action, agent, repoPath, branchName });
421
+ } else {
422
+ console.error("Unknown git action. Supported: worktree");
423
+ process.exit(1);
424
+ }
425
+ break;
426
+ }
427
+ default:
428
+ cli.showHelp();
429
+ }
430
+ }
431
+
432
+ main().catch((err) => {
433
+ console.error(err?.message || err);
434
+ process.exit(1);
435
+ });
@@ -0,0 +1,18 @@
1
+ [meta]
2
+ name = "tps-backup"
3
+ version = "1.0.0"
4
+ description = "Backup agent workspace for portability and recovery."
5
+
6
+ [workdir]
7
+ access = "readwrite"
8
+
9
+ [filesystem]
10
+ paths = [
11
+ { path = "/workspace", access = "readwrite" },
12
+ { path = "/tmp", access = "readwrite" },
13
+ { path = "/home", access = "readwrite" }
14
+ ]
15
+
16
+ [network]
17
+ # Backup creation stays local.
18
+ block = true
@@ -0,0 +1,20 @@
1
+ [meta]
2
+ name = "tps-bootstrap"
3
+ version = "1.0.0"
4
+ description = "Bootstrap protocol checks and health verification."
5
+
6
+ [workdir]
7
+ access = "readwrite"
8
+
9
+ [filesystem]
10
+ # Workspace write access for generated files and health checks
11
+ paths = [
12
+ { path = "/workspace", access = "readwrite" },
13
+ { path = "/tmp", access = "readwrite" },
14
+ ]
15
+
16
+ # Restrict network to local gateway verification only.
17
+ [network]
18
+ block = false
19
+ # NOTE: nono parser expects allowlist entries in policy tests; keep explicit local host scope.
20
+ allow = ["127.0.0.1"]
@@ -0,0 +1,21 @@
1
+ [meta]
2
+ name = "tps-office-manager"
3
+ version = "1.0.0"
4
+ description = "tps office manager — installs dependencies and tools."
5
+
6
+ [workdir]
7
+ access = "readwrite"
8
+
9
+ [filesystem]
10
+ # Allow read/write to system binary and package directories to install tools
11
+ paths = [
12
+ { path = "/usr/local/bin", access = "readwrite" },
13
+ { path = "/usr/bin", access = "readwrite" },
14
+ { path = "/usr/lib/node_modules", access = "readwrite" },
15
+ { path = "/var/cache/apk", access = "readwrite" },
16
+ { path = "/lib/apk", access = "readwrite" },
17
+ { path = "/etc/apk", access = "readwrite" }
18
+ ]
19
+
20
+ [network]
21
+ block = false # Must allow network to download packages
@@ -0,0 +1,18 @@
1
+ [meta]
2
+ name = "tps-restore"
3
+ version = "1.0.0"
4
+ description = "Restore agent workspace and run post-restore health check."
5
+
6
+ [workdir]
7
+ access = "readwrite"
8
+
9
+ [filesystem]
10
+ paths = [
11
+ { path = "/tmp", access = "readwrite" },
12
+ { path = "/home", access = "readwrite" }
13
+ ]
14
+
15
+ [network]
16
+ # Health checks may hit local gateway.
17
+ block = false
18
+ allow = ["127.0.0.1", "localhost"]