@moxxy/cli 0.0.12 → 0.1.1

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 (149) hide show
  1. package/README.md +278 -112
  2. package/bin/moxxy +10 -0
  3. package/package.json +36 -53
  4. package/src/api-client.js +286 -0
  5. package/src/cli.js +349 -0
  6. package/src/commands/agent.js +413 -0
  7. package/src/commands/auth.js +326 -0
  8. package/src/commands/channel.js +285 -0
  9. package/src/commands/doctor.js +261 -0
  10. package/src/commands/events.js +80 -0
  11. package/src/commands/gateway.js +428 -0
  12. package/src/commands/heartbeat.js +145 -0
  13. package/src/commands/init.js +954 -0
  14. package/src/commands/mcp.js +278 -0
  15. package/src/commands/plugin.js +583 -0
  16. package/src/commands/provider.js +1934 -0
  17. package/src/commands/settings.js +224 -0
  18. package/src/commands/skill.js +125 -0
  19. package/src/commands/template.js +237 -0
  20. package/src/commands/uninstall.js +196 -0
  21. package/src/commands/update.js +406 -0
  22. package/src/commands/vault.js +219 -0
  23. package/src/help.js +392 -0
  24. package/src/lib/plugin-registry.js +98 -0
  25. package/src/platform.js +40 -0
  26. package/src/sse-client.js +79 -0
  27. package/src/tui/action-wizards.js +130 -0
  28. package/src/tui/app.jsx +859 -0
  29. package/src/tui/components/action-picker.jsx +86 -0
  30. package/src/tui/components/chat-panel.jsx +120 -0
  31. package/src/tui/components/footer.jsx +13 -0
  32. package/src/tui/components/header.jsx +45 -0
  33. package/src/tui/components/input-area.jsx +384 -0
  34. package/src/tui/components/messages/ask-message.jsx +13 -0
  35. package/src/tui/components/messages/assistant-message.jsx +165 -0
  36. package/src/tui/components/messages/channel-message.jsx +18 -0
  37. package/src/tui/components/messages/event-message.jsx +22 -0
  38. package/src/tui/components/messages/hive-status.jsx +34 -0
  39. package/src/tui/components/messages/skill-message.jsx +31 -0
  40. package/src/tui/components/messages/system-message.jsx +12 -0
  41. package/src/tui/components/messages/thinking.jsx +25 -0
  42. package/src/tui/components/messages/tool-group.jsx +62 -0
  43. package/src/tui/components/messages/tool-message.jsx +66 -0
  44. package/src/tui/components/messages/user-message.jsx +12 -0
  45. package/src/tui/components/model-picker.jsx +138 -0
  46. package/src/tui/components/multiline-input.jsx +72 -0
  47. package/src/tui/events-handler.js +730 -0
  48. package/src/tui/helpers.js +59 -0
  49. package/src/tui/hooks/use-command-handler.js +451 -0
  50. package/src/tui/index.jsx +55 -0
  51. package/src/tui/input-utils.js +26 -0
  52. package/src/tui/markdown-renderer.js +66 -0
  53. package/src/tui/mcp-wizard.js +136 -0
  54. package/src/tui/model-picker.js +174 -0
  55. package/src/tui/slash-commands.js +26 -0
  56. package/src/tui/store.js +12 -0
  57. package/src/tui/theme.js +17 -0
  58. package/src/ui.js +109 -0
  59. package/bin/moxxy.js +0 -2
  60. package/dist/chunk-23LZYKQ6.mjs +0 -1131
  61. package/dist/chunk-2FZEA3NG.mjs +0 -457
  62. package/dist/chunk-3KDPLS22.mjs +0 -1131
  63. package/dist/chunk-3QRJTRBT.mjs +0 -1102
  64. package/dist/chunk-6DZX6EAA.mjs +0 -37
  65. package/dist/chunk-A4WRDUNY.mjs +0 -1242
  66. package/dist/chunk-C46NSEKG.mjs +0 -211
  67. package/dist/chunk-CAUXONEF.mjs +0 -1131
  68. package/dist/chunk-CPL5V56X.mjs +0 -1131
  69. package/dist/chunk-CTBVTTBG.mjs +0 -440
  70. package/dist/chunk-FHHLXTEZ.mjs +0 -1121
  71. package/dist/chunk-FXY3GPVA.mjs +0 -1126
  72. package/dist/chunk-GSNMMI3H.mjs +0 -530
  73. package/dist/chunk-HHOAOGUS.mjs +0 -1242
  74. package/dist/chunk-ITBO7BKI.mjs +0 -1243
  75. package/dist/chunk-J33O35WX.mjs +0 -532
  76. package/dist/chunk-N5JTPB6U.mjs +0 -820
  77. package/dist/chunk-NGVL4Q5C.mjs +0 -1102
  78. package/dist/chunk-Q2OCMNYI.mjs +0 -1131
  79. package/dist/chunk-QDVRLN6D.mjs +0 -1121
  80. package/dist/chunk-QO2JONHP.mjs +0 -1131
  81. package/dist/chunk-RVAPILHA.mjs +0 -1242
  82. package/dist/chunk-S7YBOV7E.mjs +0 -1131
  83. package/dist/chunk-SHIG6Y5L.mjs +0 -1074
  84. package/dist/chunk-SOFST2PV.mjs +0 -1242
  85. package/dist/chunk-SUNUYS6G.mjs +0 -1243
  86. package/dist/chunk-TMZWETMH.mjs +0 -1242
  87. package/dist/chunk-TYD7NMMI.mjs +0 -581
  88. package/dist/chunk-TYQ3YS42.mjs +0 -1068
  89. package/dist/chunk-UALWCJ7F.mjs +0 -1131
  90. package/dist/chunk-UQZKODNW.mjs +0 -1124
  91. package/dist/chunk-USC6R2ON.mjs +0 -1242
  92. package/dist/chunk-W32EQCVC.mjs +0 -823
  93. package/dist/chunk-WMB5ENMC.mjs +0 -1242
  94. package/dist/chunk-WNHA5JAP.mjs +0 -1242
  95. package/dist/cli-2AIWTL6F.mjs +0 -8
  96. package/dist/cli-2QKJ5UUL.mjs +0 -8
  97. package/dist/cli-4RIS6DQX.mjs +0 -8
  98. package/dist/cli-5RH4VBBL.mjs +0 -7
  99. package/dist/cli-7MK4YGOP.mjs +0 -7
  100. package/dist/cli-B4KH6MZI.mjs +0 -8
  101. package/dist/cli-CGO2LZ6Z.mjs +0 -8
  102. package/dist/cli-CVP26EL2.mjs +0 -8
  103. package/dist/cli-DDRVVNAV.mjs +0 -8
  104. package/dist/cli-E7U56QVQ.mjs +0 -8
  105. package/dist/cli-EQNRMLL3.mjs +0 -8
  106. package/dist/cli-F5RUHHH4.mjs +0 -8
  107. package/dist/cli-LX6FFSEF.mjs +0 -8
  108. package/dist/cli-LY74GWKR.mjs +0 -6
  109. package/dist/cli-MAT3ZJHI.mjs +0 -8
  110. package/dist/cli-NJXXTQYF.mjs +0 -8
  111. package/dist/cli-O4ZGFAZG.mjs +0 -8
  112. package/dist/cli-ORVLI3UQ.mjs +0 -8
  113. package/dist/cli-PV43ZVKA.mjs +0 -8
  114. package/dist/cli-REVD6ISM.mjs +0 -8
  115. package/dist/cli-TBX76KQX.mjs +0 -8
  116. package/dist/cli-THCGF7SQ.mjs +0 -8
  117. package/dist/cli-TLX5ENVM.mjs +0 -8
  118. package/dist/cli-TMNI5ZYE.mjs +0 -8
  119. package/dist/cli-TNJHCBQA.mjs +0 -6
  120. package/dist/cli-TUX22CZP.mjs +0 -8
  121. package/dist/cli-XJVH7EEP.mjs +0 -8
  122. package/dist/cli-XXOW4VXJ.mjs +0 -8
  123. package/dist/cli-XZ5RESNB.mjs +0 -6
  124. package/dist/cli-YCBYZ76Q.mjs +0 -8
  125. package/dist/cli-ZLMQCU7X.mjs +0 -8
  126. package/dist/dist-2VGKJRBH.mjs +0 -6820
  127. package/dist/dist-37BNX4QG.mjs +0 -7081
  128. package/dist/dist-7LTHRYKA.mjs +0 -11569
  129. package/dist/dist-7XJPQW5C.mjs +0 -6950
  130. package/dist/dist-AYMVOW7T.mjs +0 -7123
  131. package/dist/dist-BHUWCDRS.mjs +0 -7132
  132. package/dist/dist-FAXRJMEN.mjs +0 -6812
  133. package/dist/dist-HQGANM3P.mjs +0 -6976
  134. package/dist/dist-KATLOZQV.mjs +0 -7054
  135. package/dist/dist-KLSB6YHV.mjs +0 -6964
  136. package/dist/dist-LKIOZQ42.mjs +0 -17
  137. package/dist/dist-UYA4RJUH.mjs +0 -2792
  138. package/dist/dist-ZYHCBILM.mjs +0 -6993
  139. package/dist/index.d.mts +0 -23
  140. package/dist/index.d.ts +0 -23
  141. package/dist/index.js +0 -25531
  142. package/dist/index.mjs +0 -18
  143. package/dist/src-APP5P3UD.mjs +0 -1386
  144. package/dist/src-D5HMDDVE.mjs +0 -1324
  145. package/dist/src-EK3WD4AU.mjs +0 -1327
  146. package/dist/src-LSZFLMFN.mjs +0 -1400
  147. package/dist/src-T77DFTFP.mjs +0 -1407
  148. package/dist/src-WIOCZRAC.mjs +0 -1397
  149. package/dist/src-YK6CHCMW.mjs +0 -1400
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Vault commands: add/grant/revoke/list.
3
+ */
4
+ import { parseFlags } from './auth.js';
5
+ import { isInteractive, handleCancel, withSpinner, showResult, pickAgent, p } from '../ui.js';
6
+
7
+ export async function runVault(client, args) {
8
+ let [action, ...rest] = args;
9
+ const flags = parseFlags(rest);
10
+
11
+ // Interactive sub-menu when no valid action
12
+ if (!['add', 'remove', 'grant', 'revoke', 'list'].includes(action) && isInteractive()) {
13
+ action = await p.select({
14
+ message: 'Vault action',
15
+ options: [
16
+ { value: 'add', label: 'Add secret', hint: 'register a new secret' },
17
+ { value: 'remove', label: 'Remove secret', hint: 'delete a secret from the vault' },
18
+ { value: 'grant', label: 'Grant access', hint: 'grant agent access to a secret' },
19
+ { value: 'revoke', label: 'Revoke access', hint: 'revoke agent secret access' },
20
+ { value: 'list', label: 'List secrets', hint: 'show all secrets' },
21
+ ],
22
+ });
23
+ handleCancel(action);
24
+ }
25
+
26
+ switch (action) {
27
+ case 'add': {
28
+ // Interactive wizard when missing required fields
29
+ if ((!flags.key && !flags.name) && isInteractive()) {
30
+ const keyName = handleCancel(await p.text({
31
+ message: 'Secret key name',
32
+ placeholder: 'OPENAI_API_KEY',
33
+ validate: (val) => { if (!val) return 'Key name is required'; },
34
+ }));
35
+
36
+ const backendKey = handleCancel(await p.text({
37
+ message: 'Backend key reference',
38
+ placeholder: 'env:OPENAI_API_KEY',
39
+ validate: (val) => { if (!val) return 'Backend key is required'; },
40
+ }));
41
+
42
+ const policyLabel = handleCancel(await p.text({
43
+ message: 'Policy label',
44
+ placeholder: 'optional',
45
+ }));
46
+
47
+ const body = {
48
+ key_name: keyName,
49
+ backend_key: backendKey,
50
+ };
51
+ if (policyLabel) body.policy_label = policyLabel;
52
+
53
+ const result = await withSpinner('Adding secret...', () =>
54
+ client.request('/v1/vault/secrets', 'POST', body), 'Secret added.');
55
+
56
+ showResult('Secret Added', {
57
+ ID: result.id || result.secret_ref_id,
58
+ Key: keyName,
59
+ Backend: backendKey,
60
+ });
61
+
62
+ const grantNow = await p.confirm({
63
+ message: 'Grant to an agent?',
64
+ initialValue: false,
65
+ });
66
+ handleCancel(grantNow);
67
+
68
+ if (grantNow) {
69
+ const agentId = await pickAgent(client, 'Select agent to grant');
70
+ const secretId = result.id || result.secret_ref_id;
71
+ if (secretId) {
72
+ await withSpinner('Granting access...', () =>
73
+ client.request('/v1/vault/grants', 'POST', {
74
+ agent_id: agentId,
75
+ secret_ref_id: secretId,
76
+ }), 'Access granted.');
77
+ } else {
78
+ p.log.warn('Could not determine secret ID for grant.');
79
+ }
80
+ }
81
+
82
+ return result;
83
+ }
84
+
85
+ const body = {
86
+ key_name: flags.key || flags.name,
87
+ backend_key: flags.backend,
88
+ };
89
+ if (flags.label) body.policy_label = flags.label;
90
+ if (!body.key_name || !body.backend_key) {
91
+ throw new Error('Required: --key, --backend');
92
+ }
93
+ const result = await client.request('/v1/vault/secrets', 'POST', body);
94
+ console.log(JSON.stringify(result, null, 2));
95
+ return result;
96
+ }
97
+
98
+ case 'remove': {
99
+ let secretId = flags.id || flags.secret;
100
+
101
+ if (!secretId && isInteractive()) {
102
+ const secrets = await withSpinner('Fetching secrets...', () =>
103
+ client.listSecrets(), 'Secrets loaded.');
104
+
105
+ if (!Array.isArray(secrets) || secrets.length === 0) {
106
+ p.log.warn('No secrets to remove.');
107
+ return;
108
+ }
109
+
110
+ secretId = handleCancel(await p.select({
111
+ message: 'Select secret to remove',
112
+ options: secrets.map(s => ({
113
+ value: s.id,
114
+ label: s.key_name,
115
+ hint: `${s.id.slice(0, 12)} backend=${s.backend_key}`,
116
+ })),
117
+ }));
118
+ }
119
+
120
+ if (!secretId) throw new Error('Required: --id');
121
+
122
+ if (isInteractive()) {
123
+ await withSpinner('Removing secret...', () =>
124
+ client.deleteSecret(secretId), 'Secret removed.');
125
+ } else {
126
+ await client.deleteSecret(secretId);
127
+ console.log(`Secret ${secretId} removed.`);
128
+ }
129
+ break;
130
+ }
131
+
132
+ case 'grant': {
133
+ if (!flags.agent || !flags.secret) {
134
+ throw new Error('Required: --agent, --secret');
135
+ }
136
+ const body = {
137
+ agent_id: flags.agent,
138
+ secret_ref_id: flags.secret,
139
+ };
140
+ const result = await client.request('/v1/vault/grants', 'POST', body);
141
+ console.log(`Grant created for agent ${flags.agent}.`);
142
+ return result;
143
+ }
144
+
145
+ case 'list': {
146
+ if (isInteractive()) {
147
+ const secrets = await withSpinner('Fetching secrets...', () =>
148
+ client.listSecrets(), 'Secrets loaded.');
149
+ const grants = await withSpinner('Fetching grants...', () =>
150
+ client.listGrants(), 'Grants loaded.');
151
+
152
+ if (Array.isArray(secrets) && secrets.length > 0) {
153
+ p.log.info('\u2500\u2500 Secrets \u2500\u2500');
154
+ for (const s of secrets) {
155
+ p.log.info(` ${s.key_name} (${s.id.slice(0, 12)}) backend=${s.backend_key}`);
156
+ }
157
+ } else {
158
+ p.log.warn('No secrets found.');
159
+ }
160
+
161
+ if (Array.isArray(grants) && grants.length > 0) {
162
+ p.log.info('\u2500\u2500 Grants \u2500\u2500');
163
+ for (const g of grants) {
164
+ const status = g.revoked_at ? 'revoked' : 'active';
165
+ p.log.info(` agent=${g.agent_id.slice(0, 12)} secret=${g.secret_ref_id.slice(0, 12)} [${status}]`);
166
+ }
167
+ } else {
168
+ p.log.info('No grants found.');
169
+ }
170
+ } else {
171
+ const secrets = await client.listSecrets();
172
+ const grants = await client.listGrants();
173
+ console.log(JSON.stringify({ secrets, grants }, null, 2));
174
+ }
175
+ break;
176
+ }
177
+
178
+ case 'revoke': {
179
+ let grantId = flags.id || flags.grant;
180
+
181
+ if (!grantId && isInteractive()) {
182
+ const grants = await withSpinner('Fetching grants...', () =>
183
+ client.listGrants(), 'Grants loaded.');
184
+
185
+ const active = (grants || []).filter(g => !g.revoked_at);
186
+ if (active.length === 0) {
187
+ p.log.warn('No active grants to revoke.');
188
+ return;
189
+ }
190
+
191
+ grantId = handleCancel(await p.select({
192
+ message: 'Select grant to revoke',
193
+ options: active.map(g => ({
194
+ value: g.id,
195
+ label: `agent=${g.agent_id.slice(0, 12)} secret=${g.secret_ref_id.slice(0, 12)}`,
196
+ hint: g.id.slice(0, 12),
197
+ })),
198
+ }));
199
+ }
200
+
201
+ if (!grantId) throw new Error('Required: --id');
202
+
203
+ if (isInteractive()) {
204
+ await withSpinner('Revoking grant...', () =>
205
+ client.revokeGrant(grantId), 'Grant revoked.');
206
+ } else {
207
+ await client.revokeGrant(grantId);
208
+ console.log(`Grant ${grantId} revoked.`);
209
+ }
210
+ break;
211
+ }
212
+
213
+ default: {
214
+ const { showHelp } = await import('../help.js');
215
+ showHelp('vault', p);
216
+ break;
217
+ }
218
+ }
219
+ }
package/src/help.js ADDED
@@ -0,0 +1,392 @@
1
+ export const COMMAND_HELP = {
2
+ settings: `Usage: moxxy settings <action> [options]
3
+
4
+ Manage global Moxxy settings.
5
+
6
+ Actions:
7
+ network-mode [safe|unsafe] Get or set network mode
8
+ get [--key <k>] View all settings or a single key
9
+ set --key <k> --value <v> Set a setting value
10
+
11
+ Network Modes:
12
+ safe (default) Agent asks the user before accessing non-allowlisted domains
13
+ unsafe Domain allowlist is bypassed entirely — any domain is allowed
14
+
15
+ Options:
16
+ --json Output as JSON
17
+
18
+ Examples:
19
+ moxxy settings network-mode Show current mode
20
+ moxxy settings network-mode unsafe Switch to unsafe mode
21
+ moxxy settings network-mode safe Switch back to safe mode
22
+ moxxy settings get Show all settings
23
+ moxxy settings get --key network_mode Show a single setting
24
+ moxxy settings set --key network_mode --value unsafe`,
25
+
26
+ init: `Usage: moxxy init
27
+
28
+ First-time setup wizard. Configures the Moxxy home directory, auth mode,
29
+ API token, and optional channel setup.
30
+
31
+ Steps:
32
+ 1. Creates ~/.moxxy directory structure
33
+ 2. Configures gateway URL
34
+ 3. Selects auth mode (token or loopback)
35
+ 4. Bootstraps an API token
36
+ 5. Optionally sets up a Telegram/Discord channel`,
37
+
38
+ auth: `Usage: moxxy auth token <action> [options]
39
+
40
+ Manage API tokens for gateway authentication.
41
+
42
+ Actions:
43
+ create Create a new API token
44
+ list List all tokens
45
+ revoke Revoke an existing token
46
+
47
+ Options:
48
+ --scopes <s> Comma-separated scopes (e.g. "*", "agents:read,runs:write")
49
+ --ttl <seconds> Token time-to-live in seconds (omit for no expiry)
50
+ --description <d> Optional token description
51
+ --json Output as JSON
52
+
53
+ Valid scopes:
54
+ * agents:read agents:write runs:write vault:read vault:write
55
+ tokens:admin events:read channels:read channels:write
56
+
57
+ Examples:
58
+ moxxy auth token create --scopes "*"
59
+ moxxy auth token create --scopes "agents:read,runs:write" --ttl 86400
60
+ moxxy auth token list --json
61
+ moxxy auth token revoke <token-id>`,
62
+
63
+ agent: `Usage: moxxy agent <action> [options]
64
+
65
+ Create and manage agents.
66
+
67
+ Actions:
68
+ create Provision a new agent
69
+ run Start a task run on an agent
70
+ stop Stop a running agent
71
+ status Check agent status
72
+ update Change provider, model, or temperature
73
+ delete Permanently remove an agent
74
+
75
+ Options:
76
+ --provider <id> Provider ID (e.g. openai, anthropic)
77
+ --model <id> Model ID (e.g. gpt-4o, claude-sonnet-4-20250514)
78
+ --name <name> Agent name (create only)
79
+ --persona <text> Agent persona/system prompt (create only)
80
+ --temperature <n> Sampling temperature (default: 0.7)
81
+ --id <name> Agent name (run/stop/status/update/delete)
82
+ --task <text> Task description (run only)
83
+ --policy <profile> Policy profile name (create only)
84
+ --json Output as JSON
85
+
86
+ Examples:
87
+ moxxy agent create --name my-agent --provider openai --model gpt-4o
88
+ moxxy agent run --id my-agent --task "Summarize the README"
89
+ moxxy agent status --id my-agent --json
90
+ moxxy agent stop --id my-agent
91
+ moxxy agent update --id my-agent --model gpt-4o-mini
92
+ moxxy agent delete --id my-agent`,
93
+
94
+ provider: `Usage: moxxy provider <action> [options]
95
+
96
+ Manage LLM providers.
97
+
98
+ Actions:
99
+ install Add a built-in or custom provider
100
+ login OAuth/subscription login (currently openai-codex)
101
+ list Show installed providers
102
+
103
+ Options:
104
+ --id <id> Provider ID for install (e.g. openai, anthropic, xai)
105
+ --method <m> Login method for OAuth providers: browser | headless
106
+ --no-browser Do not auto-open browser (print URL only)
107
+ --originator <id> Advanced: override OAuth originator (default: Codex Desktop)
108
+ --allowed_workspace_id <id> Advanced: constrain browser OAuth to a workspace/org id
109
+ --organization_id <id> Advanced: pass explicit organization id in OAuth URL
110
+ --project_id <id> Advanced: pass explicit project id in OAuth URL
111
+ --api_key <key> Fallback: manually provide OpenAI API key if OAuth issuance fails
112
+ (automatic fallback) If API-key issuance fails, uses ChatGPT OAuth session mode
113
+ --model <id> Custom model ID to add
114
+ --name <name> Display name (custom providers)
115
+ --api_base <url> API base URL (custom providers)
116
+ --json Output as JSON
117
+
118
+ Built-in providers:
119
+ anthropic Anthropic (Claude models)
120
+ openai OpenAI (GPT models)
121
+ openai-codex OpenAI subscription OAuth login
122
+ xai xAI (Grok models)
123
+ google Google (Gemini models)
124
+ deepseek DeepSeek
125
+
126
+ Examples:
127
+ moxxy provider list
128
+ moxxy provider install --id openai
129
+ moxxy provider login --id openai-codex --method browser
130
+ moxxy provider login --id openai-codex --method headless --no-browser
131
+ moxxy provider login --id openai-codex --method browser --api_key sk-...
132
+ moxxy provider install --id anthropic --model claude-sonnet-4-20250514`,
133
+
134
+ skill: `Usage: moxxy skill <action> [options]
135
+
136
+ Create and manage agent skills.
137
+
138
+ Actions:
139
+ create Create a skill on an agent
140
+ remove Remove a skill from an agent
141
+ list List skills for an agent
142
+
143
+ Options:
144
+ --agent <id> Agent ID
145
+ --skill <id> Skill ID (remove)
146
+ --content <c> Skill content/markdown (create)
147
+
148
+ Examples:
149
+ moxxy skill create --agent <id> --content "..."
150
+ moxxy skill list --agent <id>
151
+ moxxy skill remove --agent <id> --skill <skill-id>`,
152
+
153
+ template: `Usage: moxxy template <action> [options]
154
+
155
+ Manage agent templates (archetypes).
156
+
157
+ Actions:
158
+ list List all templates
159
+ get View template details
160
+ create Create a new template
161
+ update Update an existing template
162
+ remove Delete a template
163
+ assign Assign a template to an agent
164
+
165
+ Options:
166
+ --slug <slug> Template slug (get/update/remove)
167
+ --content <c> Template content (create/update)
168
+ --agent <id> Agent ID (assign)
169
+ --template <slug> Template slug (assign)
170
+
171
+ Examples:
172
+ moxxy template list
173
+ moxxy template get builder
174
+ moxxy template create --content "---\\nname: Custom\\n..."
175
+ moxxy template assign --agent my-agent --template builder
176
+ moxxy template remove builder`,
177
+
178
+ heartbeat: `Usage: moxxy heartbeat <action> [options]
179
+
180
+ Schedule recurring heartbeat rules for agents.
181
+
182
+ Actions:
183
+ set Configure a heartbeat rule
184
+ list Show heartbeat rules for an agent
185
+ disable Disable a heartbeat rule
186
+
187
+ Options:
188
+ --agent <id> Agent ID
189
+ --interval <min> Interval in minutes (default: 5)
190
+ --action_type <t> Action type: notify_cli, webhook, restart
191
+ --payload <data> Webhook URL or payload (webhook action_type)
192
+ --id <id> Heartbeat ID (disable)
193
+
194
+ Examples:
195
+ moxxy heartbeat set --agent <id> --interval 10 --action_type notify_cli
196
+ moxxy heartbeat set --agent <id> --interval 30 --action_type webhook --payload https://...
197
+ moxxy heartbeat list --agent <id>
198
+ moxxy heartbeat disable --agent <id> --id <heartbeat-id>`,
199
+
200
+ vault: `Usage: moxxy vault <action> [options]
201
+
202
+ Manage secrets and access grants.
203
+
204
+ Actions:
205
+ add Register a new secret reference
206
+ grant Grant an agent access to a secret
207
+ revoke Revoke an agent's secret access
208
+ list Show all secrets and grants
209
+
210
+ Options:
211
+ --key <name> Secret key name (e.g. OPENAI_API_KEY)
212
+ --backend <key> Backend key reference (e.g. env:OPENAI_API_KEY)
213
+ --label <label> Policy label (optional)
214
+ --agent <id> Agent ID (grant)
215
+ --secret <id> Secret ref ID (grant)
216
+ --id <id> Grant ID (revoke)
217
+
218
+ Examples:
219
+ moxxy vault add --key OPENAI_API_KEY --backend env:OPENAI_API_KEY
220
+ moxxy vault grant --agent <agent-id> --secret <secret-id>
221
+ moxxy vault list
222
+ moxxy vault revoke --id <grant-id>`,
223
+
224
+ channel: `Usage: moxxy channel <action> [options]
225
+
226
+ Manage messaging channels (Telegram, Discord).
227
+
228
+ Actions:
229
+ create Create a new channel
230
+ list List all channels
231
+ pair --code <code> --agent <id> Pair a chat to an agent
232
+ delete <id> Delete a channel
233
+ bindings <id> List bindings for a channel
234
+ unbind <channel-id> <binding-id> Unbind a chat
235
+
236
+ Options:
237
+ --code <code> 6-digit pairing code from the bot
238
+ --agent <id> Agent ID to bind
239
+ --json Output as JSON
240
+
241
+ Examples:
242
+ moxxy channel create
243
+ moxxy channel list
244
+ moxxy channel pair --code 123456 --agent <agent-id>
245
+ moxxy channel delete <channel-id>
246
+ moxxy channel bindings <channel-id>
247
+ moxxy channel unbind <channel-id> <binding-id>`,
248
+
249
+ events: `Usage: moxxy events tail [options]
250
+
251
+ Stream live events from the gateway via SSE.
252
+
253
+ Options:
254
+ --agent <id> Filter events by agent ID
255
+ --run <id> Filter events by run ID
256
+ --json Output raw JSON per event
257
+
258
+ Examples:
259
+ moxxy events tail
260
+ moxxy events tail --agent <agent-id>
261
+ moxxy events tail --agent <agent-id> --json`,
262
+
263
+ gateway: `Usage: moxxy gateway <action>
264
+
265
+ Manage the Moxxy gateway process.
266
+
267
+ Actions:
268
+ start Start the gateway (launchd on macOS, systemd on Linux, fallback elsewhere)
269
+ stop Stop the gateway
270
+ restart Restart the gateway
271
+ status Show gateway status and health check
272
+ logs Tail gateway log output
273
+
274
+ Examples:
275
+ moxxy gateway start
276
+ moxxy gateway status
277
+ moxxy gateway logs
278
+ moxxy gateway restart
279
+ moxxy gateway stop`,
280
+
281
+ doctor: `Usage: moxxy doctor
282
+
283
+ Diagnose the Moxxy installation. Checks:
284
+ - Moxxy home directory (~/.moxxy)
285
+ - Environment variables (MOXXY_TOKEN, MOXXY_API_URL)
286
+ - Gateway connectivity and health
287
+ - Authentication
288
+ - Installed providers and agents
289
+ - Bun runtime (>= 1.2.0), Rust toolchain, Git, Chrome
290
+ - Provider API keys`,
291
+
292
+ update: `Usage: moxxy update [options]
293
+
294
+ Check for and install updates to the gateway binary and CLI.
295
+
296
+ Options:
297
+ --check Check for updates without installing
298
+ --force Force update even if already up to date
299
+ --rollback Restore previous gateway binary from backup
300
+ --json Output as JSON
301
+
302
+ Examples:
303
+ moxxy update --check
304
+ moxxy update
305
+ moxxy update --force
306
+ moxxy update --rollback`,
307
+
308
+ uninstall: `Usage: moxxy uninstall
309
+
310
+ Remove all Moxxy data from the system. This includes:
311
+ - ~/.moxxy directory (database, agents, config)
312
+ - Stops the gateway if running
313
+
314
+ Does NOT remove the CLI package itself. To fully remove:
315
+ npm remove -g @moxxy/cli`,
316
+
317
+ mcp: `Usage: moxxy mcp <action> [options]
318
+
319
+ Manage MCP (Model Context Protocol) servers for agents.
320
+
321
+ Actions:
322
+ list List MCP servers for an agent
323
+ add Register a new MCP server
324
+ remove Remove an MCP server
325
+ test Test connectivity to an MCP server
326
+
327
+ Options:
328
+ --agent <name> Agent name
329
+ --id <id> MCP server ID
330
+ --transport <type> Transport type: stdio or sse (add only)
331
+ --command <cmd> Command to run (stdio transport)
332
+ --args <arg> Command arguments (stdio, repeatable)
333
+ --url <url> Server URL (sse transport)
334
+
335
+ Examples:
336
+ moxxy mcp list --agent my-agent
337
+ moxxy mcp add --agent my-agent --id fs-server --transport stdio --command npx --args -y --args @modelcontextprotocol/server-filesystem --args /tmp
338
+ moxxy mcp add --agent my-agent --id remote --transport sse --url http://localhost:8080/sse
339
+ moxxy mcp remove --agent my-agent --id fs-server
340
+ moxxy mcp test --agent my-agent --id fs-server`,
341
+
342
+ plugin: `Usage: moxxy plugin <action> [options]
343
+
344
+ Manage CLI plugins that extend Moxxy functionality.
345
+
346
+ Actions:
347
+ list List installed plugins
348
+ install <package> Install a plugin from npm
349
+ start <name> Start a plugin
350
+ stop <name> Stop a plugin
351
+ restart <name> Restart a plugin
352
+ update <name> Update a plugin to the latest version
353
+ enable <name> Enable auto-start
354
+ disable <name> Disable auto-start
355
+ uninstall <name> Remove a plugin
356
+ logs <name> Tail plugin logs
357
+
358
+ Built-in plugins:
359
+ @moxxy/web-plugin Web Dashboard (browser UI)
360
+ @moxxy/virtual-office-plugin Virtual Office
361
+
362
+ Examples:
363
+ moxxy plugin install @moxxy/web-plugin
364
+ moxxy plugin start @moxxy/web-plugin
365
+ moxxy plugin list
366
+ moxxy plugin logs @moxxy/web-plugin
367
+ moxxy plugin uninstall my-custom-plugin`,
368
+
369
+ tui: `Usage: moxxy tui [options]
370
+ moxxy chat [options]
371
+
372
+ Full-screen terminal chat interface.
373
+
374
+ Options:
375
+ --agent <id> Start with a specific agent pre-selected
376
+
377
+ Keyboard shortcuts:
378
+ Enter Send message
379
+ Ctrl+X Stop running agent
380
+ /help Show available slash commands
381
+ /exit Exit the TUI`,
382
+ };
383
+
384
+ /**
385
+ * Show styled help for a command using @clack/prompts.
386
+ * Falls back to console.log if p is not available.
387
+ */
388
+ export function showHelp(commandName, p) {
389
+ const text = COMMAND_HELP[commandName];
390
+ if (!text) return;
391
+ p.note(text, `moxxy ${commandName}`);
392
+ }
@@ -0,0 +1,98 @@
1
+ import { getMoxxyHome } from '../commands/init.js';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ const DEFAULT_REGISTRY = { version: 1, plugins: {} };
6
+
7
+ export function pluginPaths() {
8
+ const home = getMoxxyHome();
9
+ const pluginsDir = join(home, 'plugins');
10
+ const registryFile = join(pluginsDir, 'registry.json');
11
+ const logsDir = join(pluginsDir, 'logs');
12
+ const packageJsonFile = join(pluginsDir, 'package.json');
13
+ return { pluginsDir, registryFile, logsDir, packageJsonFile };
14
+ }
15
+
16
+ export function readRegistry() {
17
+ const { registryFile } = pluginPaths();
18
+ if (!existsSync(registryFile)) return { ...DEFAULT_REGISTRY, plugins: {} };
19
+ try {
20
+ return JSON.parse(readFileSync(registryFile, 'utf-8'));
21
+ } catch {
22
+ return { ...DEFAULT_REGISTRY, plugins: {} };
23
+ }
24
+ }
25
+
26
+ export function writeRegistry(registry) {
27
+ const { registryFile } = pluginPaths();
28
+ writeFileSync(registryFile, JSON.stringify(registry, null, 2));
29
+ }
30
+
31
+ export function ensurePluginsDir() {
32
+ const { pluginsDir, logsDir, packageJsonFile } = pluginPaths();
33
+ mkdirSync(pluginsDir, { recursive: true });
34
+ mkdirSync(logsDir, { recursive: true });
35
+ if (!existsSync(packageJsonFile)) {
36
+ writeFileSync(packageJsonFile, JSON.stringify({ private: true, dependencies: {} }, null, 2));
37
+ }
38
+ }
39
+
40
+ export function readPluginMeta(pluginName) {
41
+ const { pluginsDir } = pluginPaths();
42
+ const pkgPath = join(pluginsDir, 'node_modules', pluginName, 'package.json');
43
+ if (!existsSync(pkgPath)) return null;
44
+ try {
45
+ return JSON.parse(readFileSync(pkgPath, 'utf-8'));
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ export function validatePluginMeta(meta) {
52
+ const errors = [];
53
+ if (!meta) {
54
+ return { valid: false, errors: ['Plugin package.json not found'] };
55
+ }
56
+ if (!meta.moxxy || meta.moxxy.type !== 'plugin') {
57
+ errors.push('Missing or invalid moxxy.type (must be "plugin")');
58
+ }
59
+ if (!meta.scripts || !meta.scripts['plugin:start']) {
60
+ errors.push('Missing required script "plugin:start"');
61
+ }
62
+ return { valid: errors.length === 0, errors };
63
+ }
64
+
65
+ export function isProcessAlive(pid) {
66
+ try {
67
+ process.kill(pid, 0);
68
+ return true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ export function sanitizeLogFileName(pluginName) {
75
+ return pluginName.replace(/\//g, '--') + '.log';
76
+ }
77
+
78
+ export function buildPluginEnv(pluginName, port) {
79
+ const apiUrl = process.env.MOXXY_API_URL || 'http://localhost:3000';
80
+ const token = process.env.MOXXY_TOKEN || '';
81
+ return {
82
+ ...process.env,
83
+ MOXXY_API_URL: apiUrl,
84
+ MOXXY_TOKEN: token,
85
+ MOXXY_PLUGIN_NAME: pluginName,
86
+ MOXXY_PLUGIN_PORT: port ? String(port) : '',
87
+ MOXXY_HOME: getMoxxyHome(),
88
+ PORT: port ? String(port) : '',
89
+ // Vite exposes only VITE_-prefixed vars to browser code
90
+ VITE_MOXXY_API_URL: apiUrl,
91
+ VITE_MOXXY_TOKEN: token,
92
+ };
93
+ }
94
+
95
+ export const BUILTIN_PLUGINS = [
96
+ { name: '@moxxy/web-plugin', label: 'Web Dashboard', hint: 'browser-based dashboard', defaultPort: 17900 },
97
+ { name: '@moxxy/virtual-office-plugin', label: 'Virtual Office', hint: 'virtual office environment', defaultPort: 17901 },
98
+ ];