azerclaw 1.0.0 → 1.0.2-beta.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 (117) hide show
  1. package/README.md +8 -0
  2. package/bin/azerclaw.ts +575 -18
  3. package/dist/bin/azerclaw.d.ts +17 -8
  4. package/dist/bin/azerclaw.d.ts.map +1 -1
  5. package/dist/bin/azerclaw.js +506 -17
  6. package/dist/bin/azerclaw.js.map +1 -1
  7. package/dist/src/agents/builtin.js +1 -1
  8. package/dist/src/agents/builtin.js.map +1 -1
  9. package/dist/src/channels/adapter.d.ts +10 -1
  10. package/dist/src/channels/adapter.d.ts.map +1 -1
  11. package/dist/src/channels/adapter.js +89 -5
  12. package/dist/src/channels/adapter.js.map +1 -1
  13. package/dist/src/channels/pairing.d.ts +38 -0
  14. package/dist/src/channels/pairing.d.ts.map +1 -0
  15. package/dist/src/channels/pairing.js +171 -0
  16. package/dist/src/channels/pairing.js.map +1 -0
  17. package/dist/src/channels/routing.d.ts +14 -0
  18. package/dist/src/channels/routing.d.ts.map +1 -0
  19. package/dist/src/channels/routing.js +45 -0
  20. package/dist/src/channels/routing.js.map +1 -0
  21. package/dist/src/channels/security.d.ts +18 -0
  22. package/dist/src/channels/security.d.ts.map +1 -0
  23. package/dist/src/channels/security.js +80 -0
  24. package/dist/src/channels/security.js.map +1 -0
  25. package/dist/src/channels/slack.js +1 -1
  26. package/dist/src/channels/slack.js.map +1 -1
  27. package/dist/src/cli/animations/fish.d.ts +10 -7
  28. package/dist/src/cli/animations/fish.d.ts.map +1 -1
  29. package/dist/src/cli/animations/fish.js +116 -122
  30. package/dist/src/cli/animations/fish.js.map +1 -1
  31. package/dist/src/cli/commands/channels.d.ts +17 -0
  32. package/dist/src/cli/commands/channels.d.ts.map +1 -0
  33. package/dist/src/cli/commands/channels.js +173 -0
  34. package/dist/src/cli/commands/channels.js.map +1 -0
  35. package/dist/src/cli/commands/chat.d.ts +16 -0
  36. package/dist/src/cli/commands/chat.d.ts.map +1 -1
  37. package/dist/src/cli/commands/chat.js +157 -35
  38. package/dist/src/cli/commands/chat.js.map +1 -1
  39. package/dist/src/cli/commands/config.d.ts +1 -0
  40. package/dist/src/cli/commands/config.d.ts.map +1 -1
  41. package/dist/src/cli/commands/config.js +37 -11
  42. package/dist/src/cli/commands/config.js.map +1 -1
  43. package/dist/src/cli/commands/doctor.d.ts.map +1 -1
  44. package/dist/src/cli/commands/doctor.js +46 -2
  45. package/dist/src/cli/commands/doctor.js.map +1 -1
  46. package/dist/src/cli/commands/onboard.d.ts +12 -1
  47. package/dist/src/cli/commands/onboard.d.ts.map +1 -1
  48. package/dist/src/cli/commands/onboard.js +384 -64
  49. package/dist/src/cli/commands/onboard.js.map +1 -1
  50. package/dist/src/cli/commands/pairing.d.ts +7 -0
  51. package/dist/src/cli/commands/pairing.d.ts.map +1 -0
  52. package/dist/src/cli/commands/pairing.js +64 -0
  53. package/dist/src/cli/commands/pairing.js.map +1 -0
  54. package/dist/src/cli/commands/run.d.ts.map +1 -1
  55. package/dist/src/cli/commands/run.js +3 -2
  56. package/dist/src/cli/commands/run.js.map +1 -1
  57. package/dist/src/cli/commands/sandbox.d.ts +7 -0
  58. package/dist/src/cli/commands/sandbox.d.ts.map +1 -0
  59. package/dist/src/cli/commands/sandbox.js +98 -0
  60. package/dist/src/cli/commands/sandbox.js.map +1 -0
  61. package/dist/src/cli/commands/settings.d.ts +31 -0
  62. package/dist/src/cli/commands/settings.d.ts.map +1 -0
  63. package/dist/src/cli/commands/settings.js +566 -0
  64. package/dist/src/cli/commands/settings.js.map +1 -0
  65. package/dist/src/cli/commands/tui.d.ts +3 -0
  66. package/dist/src/cli/commands/tui.d.ts.map +1 -1
  67. package/dist/src/cli/commands/tui.js +212 -71
  68. package/dist/src/cli/commands/tui.js.map +1 -1
  69. package/dist/src/config/manager.d.ts +121 -9
  70. package/dist/src/config/manager.d.ts.map +1 -1
  71. package/dist/src/config/manager.js +363 -11
  72. package/dist/src/config/manager.js.map +1 -1
  73. package/dist/src/config/schema.d.ts +457 -9
  74. package/dist/src/config/schema.d.ts.map +1 -1
  75. package/dist/src/config/schema.js +117 -8
  76. package/dist/src/config/schema.js.map +1 -1
  77. package/dist/src/core/runtime.d.ts +9 -0
  78. package/dist/src/core/runtime.d.ts.map +1 -1
  79. package/dist/src/core/runtime.js +78 -24
  80. package/dist/src/core/runtime.js.map +1 -1
  81. package/dist/src/core/sandbox.d.ts +34 -0
  82. package/dist/src/core/sandbox.d.ts.map +1 -0
  83. package/dist/src/core/sandbox.js +127 -0
  84. package/dist/src/core/sandbox.js.map +1 -0
  85. package/dist/src/core/server.d.ts +15 -0
  86. package/dist/src/core/server.d.ts.map +1 -0
  87. package/dist/src/core/server.js +127 -0
  88. package/dist/src/core/server.js.map +1 -0
  89. package/dist/src/index.d.ts +4 -0
  90. package/dist/src/index.d.ts.map +1 -1
  91. package/dist/src/index.js +4 -0
  92. package/dist/src/index.js.map +1 -1
  93. package/dist/src/providers/router.d.ts.map +1 -1
  94. package/dist/src/providers/router.js +16 -0
  95. package/dist/src/providers/router.js.map +1 -1
  96. package/dist/src/tools/advanced.d.ts.map +1 -1
  97. package/dist/src/tools/advanced.js +3 -0
  98. package/dist/src/tools/advanced.js.map +1 -1
  99. package/dist/src/tools/filesystem.d.ts.map +1 -1
  100. package/dist/src/tools/filesystem.js +4 -0
  101. package/dist/src/tools/filesystem.js.map +1 -1
  102. package/dist/src/tools/index.d.ts +9 -0
  103. package/dist/src/tools/index.d.ts.map +1 -0
  104. package/dist/src/tools/index.js +73 -0
  105. package/dist/src/tools/index.js.map +1 -0
  106. package/dist/src/tools/loader.d.ts +14 -0
  107. package/dist/src/tools/loader.d.ts.map +1 -0
  108. package/dist/src/tools/loader.js +115 -0
  109. package/dist/src/tools/loader.js.map +1 -0
  110. package/dist/src/tools/registry.d.ts +29 -1
  111. package/dist/src/tools/registry.d.ts.map +1 -1
  112. package/dist/src/tools/registry.js +84 -6
  113. package/dist/src/tools/registry.js.map +1 -1
  114. package/dist/src/tools/shell.d.ts.map +1 -1
  115. package/dist/src/tools/shell.js +1 -0
  116. package/dist/src/tools/shell.js.map +1 -1
  117. package/package.json +9 -10
package/bin/azerclaw.ts CHANGED
@@ -7,39 +7,76 @@
7
7
  * Inspired by OpenClaw, themed with a fish 🐟 instead of a lobster.
8
8
  *
9
9
  * Usage:
10
- * azerclaw — Launch TUI (or onboard if first run)
11
- * azerclaw chat — Interactive chat
12
- * azerclaw run "task" — Execute a task
13
- * azerclaw tui — Premium terminal UI
14
- * azerclaw onboard — Setup wizard
15
- * azerclaw config — Manage configuration
16
- * azerclaw models Manage AI models
17
- * azerclaw doctor Health check
10
+ * azerclaw — Launch interactive session (or onboard if first run)
11
+ * azerclaw chat — Interactive chat
12
+ * azerclaw run "task" — Execute a task
13
+ * azerclaw tui — Premium terminal UI
14
+ * azerclaw onboard — Setup wizard
15
+ * azerclaw config — Manage configuration
16
+ * azerclaw config provider Switch provider
17
+ * azerclaw config model Switch model
18
+ * azerclaw config apikey — Set API key
19
+ * azerclaw config fallback — Configure fallback
20
+ * azerclaw config channels — DM policy + routing controls
21
+ * azerclaw config sandbox — Session sandbox controls
22
+ * azerclaw pairing — Manage DM pairing approvals
23
+ * azerclaw init — Initialize project (AZERCLAW.md)
24
+ * azerclaw models — Manage AI models
25
+ * azerclaw doctor — Health check
26
+ * azerclaw status — Show current status
18
27
  */
19
28
 
20
29
  const { Command } = require('commander');
21
- const { playSplashScreen, printQuickSplash, fishError, fishInfo } = require('../src/cli/animations/fish');
30
+ const chalk = require('chalk');
31
+ const { playSplashScreen, printQuickSplash, fishError, fishInfo, fishSuccess } = require('../src/cli/animations/fish');
22
32
  const { getConfigManager } = require('../src/config/manager');
23
33
 
24
- const VERSION = '1.0.0';
34
+ const VERSION = '1.1.4-beta';
25
35
  const program = new Command();
26
36
 
27
37
  // ─── Program Setup ──────────────────────────────────────────────
28
38
 
29
39
  program
30
40
  .name('azerclaw')
31
- .description('🐟 AZERCLAW — Your AI, Your Keys, Your Way')
41
+ .description('🐟 AZERCLAW — Diabolical AI · Scorched Earth · Your Way')
32
42
  .version(VERSION, '-v, --version', 'Display version')
33
43
  .option('--no-splash', 'Skip the splash screen')
34
- .option('--no-color', 'Disable colors');
44
+ .option('--no-color', 'Disable colors')
45
+ .hook('preAction', async () => {
46
+ // Global initialization
47
+ const { registerAllTools } = require('../src/tools');
48
+ await registerAllTools();
49
+ });
35
50
 
36
51
  // ─── Default Action (no command) ────────────────────────────────
37
52
 
38
53
  program
39
- .action(async (opts: any) => {
54
+ .argument('[task]', 'Optional task to execute immediately (one-off mode)')
55
+ .action(async (task: string | undefined, opts: any) => {
40
56
  const config = getConfigManager();
41
57
  config.resolveEnvOverrides();
42
58
 
59
+ // Check for positional task
60
+ if (task) {
61
+ const { runTask } = require('../src/cli/commands/run');
62
+ await runTask(task.trim(), opts);
63
+ return;
64
+ }
65
+
66
+ // Check for piped input (stdin)
67
+ if (!process.stdin.isTTY) {
68
+ let input = '';
69
+ process.stdin.setEncoding('utf-8');
70
+ for await (const chunk of process.stdin) {
71
+ input += chunk;
72
+ }
73
+ if (input.trim()) {
74
+ const { runTask } = require('../src/cli/commands/run');
75
+ await runTask(input.trim(), opts);
76
+ return;
77
+ }
78
+ }
79
+
43
80
  if (config.isFirstRun()) {
44
81
  // First run: show full splash + onboard
45
82
  await playSplashScreen(VERSION);
@@ -59,16 +96,30 @@ program
59
96
  .description('Start an interactive chat session')
60
97
  .option('-m, --model <model>', 'Override the default model')
61
98
  .option('-p, --provider <provider>', 'Override the default provider')
99
+ .option('-f, --file <path>', 'Include a file in the conversation context')
62
100
  .action(async (opts: any) => {
63
101
  const config = getConfigManager();
64
102
  config.resolveEnvOverrides();
65
103
 
104
+ // Apply CLI flag overrides
105
+ if (opts.model || opts.provider) {
106
+ config.applyRuntimeOverrides(opts);
107
+ }
108
+
109
+ if (opts.file) {
110
+ const fs = require('fs');
111
+ if (fs.existsSync(opts.file)) {
112
+ const content = fs.readFileSync(opts.file, 'utf-8');
113
+ opts.initialMessage = `I've attached the file: ${opts.file}\n\n\`\`\`\n${content}\n\`\`\``;
114
+ }
115
+ }
116
+
66
117
  if (!opts.parent?.splash === false) {
67
118
  printQuickSplash(VERSION);
68
119
  }
69
120
 
70
121
  if (config.isFirstRun()) {
71
- fishInfo('First time? Run `azerclaw onboard` to configure your AI providers.');
122
+ fishInfo('First time? Running setup wizard...');
72
123
  const { runOnboard } = require('../src/cli/commands/onboard');
73
124
  await runOnboard();
74
125
  return;
@@ -81,17 +132,61 @@ program
81
132
  // ─── Run Command ────────────────────────────────────────────────
82
133
 
83
134
  program
84
- .command('run <task>')
135
+ .command('run [task]')
85
136
  .description('Execute a single task')
86
137
  .option('-m, --model <model>', 'Override the default model')
138
+ .option('-p, --provider <provider>', 'Override the default provider')
139
+ .option('-f, --file <path>', 'Include a file in the task context')
87
140
  .option('-V, --verbose', 'Show tool calls in detail')
88
- .action(async (task: string, opts: any) => {
141
+ .action(async (task: string | undefined, opts: any) => {
89
142
  const config = getConfigManager();
90
143
  config.resolveEnvOverrides();
144
+
145
+ if (opts.model || opts.provider) {
146
+ config.applyRuntimeOverrides(opts);
147
+ }
148
+
149
+ let finalTask = task || '';
150
+
151
+ // Handle piped input if task is missing
152
+ if (!finalTask && !process.stdin.isTTY) {
153
+ process.stdin.setEncoding('utf-8');
154
+ for await (const chunk of process.stdin) {
155
+ finalTask += chunk;
156
+ }
157
+ }
158
+
159
+ if (!finalTask.trim()) {
160
+ fishError('No task provided. Usage: azerclaw run "your task" or echo "task" | azerclaw run');
161
+ return;
162
+ }
163
+
164
+ if (opts.file) {
165
+ const fs = require('fs');
166
+ if (fs.existsSync(opts.file)) {
167
+ const content = fs.readFileSync(opts.file, 'utf-8');
168
+ finalTask = `Context from file ${opts.file}:\n\`\`\`\n${content}\n\`\`\`\n\nTask: ${finalTask}`;
169
+ }
170
+ }
171
+
91
172
  printQuickSplash(VERSION);
92
173
 
93
174
  const { runTask } = require('../src/cli/commands/run');
94
- await runTask(task, opts);
175
+ await runTask(finalTask.trim(), opts);
176
+ });
177
+
178
+ // ─── Serve Command ──────────────────────────────────────────────
179
+
180
+ program
181
+ .command('serve')
182
+ .description('Start the AZERCLAW local WebSocket daemon for desktop apps')
183
+ .option('-p, --port <port>', 'Port to listen on', '8080')
184
+ .action((opts: any) => {
185
+ printQuickSplash(VERSION);
186
+ const { AzerclawServer } = require('../src/core/server');
187
+ const port = parseInt(opts.port, 10) || 8080;
188
+ const server = new AzerclawServer(port);
189
+ server.start();
95
190
  });
96
191
 
97
192
  // ─── TUI Command ────────────────────────────────────────────────
@@ -99,10 +194,16 @@ program
99
194
  program
100
195
  .command('tui')
101
196
  .description('Launch the premium terminal UI')
102
- .action(async () => {
197
+ .option('-m, --model <model>', 'Override the default model')
198
+ .option('-p, --provider <provider>', 'Override the default provider')
199
+ .action(async (opts: any) => {
103
200
  const config = getConfigManager();
104
201
  config.resolveEnvOverrides();
105
202
 
203
+ if (opts.model || opts.provider) {
204
+ config.applyRuntimeOverrides(opts);
205
+ }
206
+
106
207
  const { runTUI } = require('../src/cli/commands/tui');
107
208
  await runTUI();
108
209
  });
@@ -118,6 +219,28 @@ program
118
219
  await runOnboard();
119
220
  });
120
221
 
222
+ // ─── Init Command (Project) ────────────────────────────────────
223
+
224
+ program
225
+ .command('init')
226
+ .description('Initialize AZERCLAW for this project (creates AZERCLAW.md + .azerclaw/)')
227
+ .action(() => {
228
+ const { initProject } = require('../src/cli/commands/settings');
229
+ initProject();
230
+ });
231
+
232
+ // ─── Status Command ─────────────────────────────────────────────
233
+
234
+ program
235
+ .command('status')
236
+ .description('Show current model, provider, auth, and project status')
237
+ .action(() => {
238
+ const config = getConfigManager();
239
+ config.resolveEnvOverrides();
240
+ const { showStatus } = require('../src/cli/commands/settings');
241
+ showStatus();
242
+ });
243
+
121
244
  // ─── Config Command ─────────────────────────────────────────────
122
245
 
123
246
  const configCmd = program
@@ -156,6 +279,222 @@ configCmd
156
279
  configReset();
157
280
  });
158
281
 
282
+ configCmd
283
+ .command('provider [name]')
284
+ .description('Switch the active AI provider (interactive if no name given)')
285
+ .action(async (name?: string) => {
286
+ if (name) {
287
+ const { cliSwitchProvider } = require('../src/cli/commands/settings');
288
+ cliSwitchProvider(name);
289
+ } else {
290
+ const { interactiveProviderSwitch } = require('../src/cli/commands/settings');
291
+ await interactiveProviderSwitch();
292
+ }
293
+ });
294
+
295
+ configCmd
296
+ .command('model [id]')
297
+ .description('Switch the default model (interactive if no id given)')
298
+ .option('-p, --provider <provider>', 'Target provider (defaults to active)')
299
+ .action(async (id?: string, opts?: any) => {
300
+ if (id) {
301
+ const { cliSwitchModel } = require('../src/cli/commands/settings');
302
+ cliSwitchModel(id, opts?.provider);
303
+ } else {
304
+ const { interactiveModelSwitch } = require('../src/cli/commands/settings');
305
+ await interactiveModelSwitch();
306
+ }
307
+ });
308
+
309
+ configCmd
310
+ .command('apikey [provider] [key]')
311
+ .description('Set or change an API key (interactive if no args given)')
312
+ .action(async (provider?: string, key?: string) => {
313
+ if (provider && key) {
314
+ const { cliSetApiKey } = require('../src/cli/commands/settings');
315
+ cliSetApiKey(provider, key);
316
+ } else {
317
+ const { interactiveApiKeyChange } = require('../src/cli/commands/settings');
318
+ await interactiveApiKeyChange();
319
+ }
320
+ });
321
+
322
+ configCmd
323
+ .command('fallback [provider]')
324
+ .description('Set or change the fallback provider (interactive if no arg given)')
325
+ .action(async (provider?: string) => {
326
+ if (provider) {
327
+ const { cliSetFallback } = require('../src/cli/commands/settings');
328
+ cliSetFallback(provider);
329
+ } else {
330
+ const { interactiveFallbackConfig } = require('../src/cli/commands/settings');
331
+ await interactiveFallbackConfig();
332
+ }
333
+ });
334
+
335
+ configCmd
336
+ .command('settings')
337
+ .description('Open the full interactive settings menu')
338
+ .action(async () => {
339
+ const { interactiveSettingsMenu } = require('../src/cli/commands/settings');
340
+ await interactiveSettingsMenu();
341
+ });
342
+
343
+ const configChannelsCmd = configCmd
344
+ .command('channels')
345
+ .description('Manage channel DM policy, allowlists, and session routing');
346
+
347
+ configChannelsCmd
348
+ .command('list')
349
+ .description('Show channel DM policy and routing config')
350
+ .action(() => {
351
+ const { channelsConfigList } = require('../src/cli/commands/channels');
352
+ channelsConfigList();
353
+ });
354
+
355
+ configChannelsCmd
356
+ .command('dm-policy <platform> <policy>')
357
+ .description('Set dmPolicy for a channel platform (pairing|open|closed)')
358
+ .action((platform: string, policy: string) => {
359
+ const { setChannelDmPolicy } = require('../src/cli/commands/channels');
360
+ setChannelDmPolicy(platform, policy);
361
+ });
362
+
363
+ const configChannelsAllowCmd = configChannelsCmd
364
+ .command('allow')
365
+ .description('Manage channel allowFrom list');
366
+
367
+ configChannelsAllowCmd
368
+ .command('list <platform>')
369
+ .description('List allowFrom entries for a platform')
370
+ .action((platform: string) => {
371
+ const { listChannelAllowFrom } = require('../src/cli/commands/channels');
372
+ listChannelAllowFrom(platform);
373
+ });
374
+
375
+ configChannelsAllowCmd
376
+ .command('add <platform> <senderId>')
377
+ .description('Add senderId to allowFrom')
378
+ .action((platform: string, senderId: string) => {
379
+ const { addChannelAllowFrom } = require('../src/cli/commands/channels');
380
+ addChannelAllowFrom(platform, senderId);
381
+ });
382
+
383
+ configChannelsAllowCmd
384
+ .command('remove <platform> <senderId>')
385
+ .description('Remove senderId from allowFrom')
386
+ .action((platform: string, senderId: string) => {
387
+ const { removeChannelAllowFrom } = require('../src/cli/commands/channels');
388
+ removeChannelAllowFrom(platform, senderId);
389
+ });
390
+
391
+ const configChannelsRoutingCmd = configChannelsCmd
392
+ .command('routing')
393
+ .description('Manage channel session routing rules');
394
+
395
+ configChannelsRoutingCmd
396
+ .command('strategy <strategy>')
397
+ .description('Set routing strategy (channel|platform_channel|platform_sender)')
398
+ .action((strategy: string) => {
399
+ const { setRoutingStrategy } = require('../src/cli/commands/channels');
400
+ setRoutingStrategy(strategy);
401
+ });
402
+
403
+ configChannelsRoutingCmd
404
+ .command('add <sessionId>')
405
+ .description('Add routing rule')
406
+ .option('--platform <platform>', 'Platform matcher')
407
+ .option('--channel <channelId>', 'Channel matcher')
408
+ .option('--sender <senderId>', 'Sender matcher')
409
+ .action((sessionId: string, opts: any) => {
410
+ const { addRoutingRule } = require('../src/cli/commands/channels');
411
+ addRoutingRule(sessionId, { platform: opts.platform, channel: opts.channel, sender: opts.sender });
412
+ });
413
+
414
+ configChannelsRoutingCmd
415
+ .command('remove <sessionId>')
416
+ .description('Remove routing rule(s) for sessionId and optional matchers')
417
+ .option('--platform <platform>', 'Platform matcher')
418
+ .option('--channel <channelId>', 'Channel matcher')
419
+ .option('--sender <senderId>', 'Sender matcher')
420
+ .action((sessionId: string, opts: any) => {
421
+ const { removeRoutingRule } = require('../src/cli/commands/channels');
422
+ removeRoutingRule(sessionId, { platform: opts.platform, channel: opts.channel, sender: opts.sender });
423
+ });
424
+
425
+ configChannelsRoutingCmd
426
+ .command('list')
427
+ .description('List DM policy and routing settings')
428
+ .action(() => {
429
+ const { channelsConfigList } = require('../src/cli/commands/channels');
430
+ channelsConfigList();
431
+ });
432
+
433
+ const configSandboxCmd = configCmd
434
+ .command('sandbox')
435
+ .description('Manage session sandbox isolation');
436
+
437
+ configSandboxCmd
438
+ .command('status')
439
+ .description('Show sandbox mode and tool policy')
440
+ .action(() => {
441
+ const { sandboxStatus } = require('../src/cli/commands/sandbox');
442
+ sandboxStatus();
443
+ });
444
+
445
+ configSandboxCmd
446
+ .command('mode <mode>')
447
+ .description('Set sandbox mode (off|non-main|all)')
448
+ .action((mode: string) => {
449
+ const { setSandboxMode } = require('../src/cli/commands/sandbox');
450
+ setSandboxMode(mode);
451
+ });
452
+
453
+ const configSandboxAllowCmd = configSandboxCmd
454
+ .command('allow')
455
+ .description('Manage sandbox allowed tool list');
456
+
457
+ configSandboxAllowCmd
458
+ .command('add <toolName>')
459
+ .description('Add allowed tool')
460
+ .action((toolName: string) => {
461
+ const { addSandboxAllowedTool } = require('../src/cli/commands/sandbox');
462
+ addSandboxAllowedTool(toolName);
463
+ });
464
+
465
+ configSandboxAllowCmd
466
+ .command('remove <toolName>')
467
+ .description('Remove allowed tool')
468
+ .action((toolName: string) => {
469
+ const { removeSandboxAllowedTool } = require('../src/cli/commands/sandbox');
470
+ removeSandboxAllowedTool(toolName);
471
+ });
472
+
473
+ const configSandboxDenyCmd = configSandboxCmd
474
+ .command('deny')
475
+ .description('Manage sandbox denied tool list');
476
+
477
+ configSandboxDenyCmd
478
+ .command('add <toolName>')
479
+ .description('Add denied tool')
480
+ .action((toolName: string) => {
481
+ const { addSandboxDeniedTool } = require('../src/cli/commands/sandbox');
482
+ addSandboxDeniedTool(toolName);
483
+ });
484
+
485
+ configSandboxDenyCmd
486
+ .command('remove <toolName>')
487
+ .description('Remove denied tool')
488
+ .action((toolName: string) => {
489
+ const { removeSandboxDeniedTool } = require('../src/cli/commands/sandbox');
490
+ removeSandboxDeniedTool(toolName);
491
+ });
492
+
493
+ configSandboxCmd.action(() => {
494
+ const { sandboxStatus } = require('../src/cli/commands/sandbox');
495
+ sandboxStatus();
496
+ });
497
+
159
498
  // Default config action (no sub-command) shows list
160
499
  configCmd.action(() => {
161
500
  const { configList } = require('../src/cli/commands/config');
@@ -214,6 +553,8 @@ program
214
553
 
215
554
  const fs = require('fs');
216
555
  const config = getConfigManager();
556
+ const { auditDmPolicies, applySafeDmDefaults } = require('../src/channels/security');
557
+ const { auditSandboxPosture, applySafeSandboxDefaults } = require('../src/core/sandbox');
217
558
  const issues: string[] = [];
218
559
 
219
560
  // Check config file permissions
@@ -236,6 +577,36 @@ program
236
577
  fishInfo(`${key} found in environment (normal for CI/CD, prefer config file for local use)`);
237
578
  }
238
579
  }
580
+
581
+ const dmAudit = auditDmPolicies(config.getAll().channels);
582
+ for (const issue of dmAudit.failures) {
583
+ issues.push(issue.message);
584
+ }
585
+ for (const issue of dmAudit.warnings) {
586
+ issues.push(issue.message);
587
+ }
588
+
589
+ if (opts.fix && dmAudit.failures.length > 0) {
590
+ const changes = applySafeDmDefaults(config);
591
+ for (const change of changes) {
592
+ fishInfo(`Fixed: ${change}`);
593
+ }
594
+ }
595
+
596
+ const sandboxAudit = auditSandboxPosture(config.getAll());
597
+ for (const issue of sandboxAudit.failures) {
598
+ issues.push(issue.message);
599
+ }
600
+ for (const issue of sandboxAudit.warnings) {
601
+ issues.push(issue.message);
602
+ }
603
+
604
+ if (opts.fix && sandboxAudit.failures.length > 0) {
605
+ const changes = applySafeSandboxDefaults(config);
606
+ for (const change of changes) {
607
+ fishInfo(`Fixed: ${change}`);
608
+ }
609
+ }
239
610
 
240
611
  if (issues.length === 0) {
241
612
  const { fishSuccess } = require('../src/cli/animations/fish');
@@ -248,6 +619,43 @@ program
248
619
  }
249
620
  });
250
621
 
622
+ // ─── Pairing Command ──────────────────────────────────────────────
623
+
624
+ const pairingCmd = program
625
+ .command('pairing')
626
+ .description('Manage DM pairing approvals for channel adapters');
627
+
628
+ pairingCmd
629
+ .command('list')
630
+ .description('List approved pairings')
631
+ .option('--pending', 'Include pending pairing requests')
632
+ .option('-p, --platform <platform>', 'Filter by platform')
633
+ .action((opts: any) => {
634
+ const { pairingList } = require('../src/cli/commands/pairing');
635
+ pairingList({ pending: opts.pending, platform: opts.platform });
636
+ });
637
+
638
+ pairingCmd
639
+ .command('approve <platform> <code>')
640
+ .description('Approve a pending pairing code')
641
+ .action((platform: string, code: string) => {
642
+ const { pairingApprove } = require('../src/cli/commands/pairing');
643
+ pairingApprove(platform, code);
644
+ });
645
+
646
+ pairingCmd
647
+ .command('revoke <platform> <senderId>')
648
+ .description('Revoke an approved sender pairing')
649
+ .action((platform: string, senderId: string) => {
650
+ const { pairingRevoke } = require('../src/cli/commands/pairing');
651
+ pairingRevoke(platform, senderId);
652
+ });
653
+
654
+ pairingCmd.action(() => {
655
+ const { pairingList } = require('../src/cli/commands/pairing');
656
+ pairingList({ pending: true });
657
+ });
658
+
251
659
  // ─── Agents Command ─────────────────────────────────────────────
252
660
 
253
661
  const agentsCmd = program
@@ -285,6 +693,155 @@ agentsCmd.action(() => {
285
693
  agentsList();
286
694
  });
287
695
 
696
+ // ─── Workflow Command ───────────────────────────────────────────
697
+
698
+ const workflowCmd = program
699
+ .command('workflow')
700
+ .description('Manage and run Fishbone workflows');
701
+
702
+ workflowCmd
703
+ .command('run <file>')
704
+ .description('Run a .fishbone workflow file')
705
+ .action(async (file: string) => {
706
+ printQuickSplash(VERSION);
707
+ const { parseFishboneFile, FishboneEngine } = require('../src/workflow/engine');
708
+ const path = require('path');
709
+ const fs = require('fs');
710
+
711
+ const filePath = path.resolve(process.cwd(), file);
712
+ if (!fs.existsSync(filePath)) {
713
+ fishError(`Workflow file not found: ${filePath}`);
714
+ return;
715
+ }
716
+
717
+ const workflow = parseFishboneFile(filePath);
718
+ fishInfo(`Running workflow: ${workflow.name} (v${workflow.version})`);
719
+
720
+ const engine = new FishboneEngine();
721
+ await engine.execute(workflow, {}, async (event: any) => {
722
+ if (event.type === 'step_start') console.log(chalk.hex('#60a5fa')(`[Step] ${event.stepName}...`));
723
+ if (event.type === 'approval_needed') {
724
+ console.log(chalk.hex('#fbbf24')(`[Approval] Needed for: ${event.content}`));
725
+ console.log(chalk.hex('#34d399')(`Resume token: ${event.resumeToken}`));
726
+ }
727
+ if (event.type === 'step_error') console.log(chalk.hex('#ef4444')(`[Error] ${event.content}`));
728
+ if (event.type === 'workflow_complete') fishSuccess('Workflow completed successfully');
729
+ if (event.type === 'workflow_error') fishError(`Workflow failed: ${event.content}`);
730
+ });
731
+ });
732
+
733
+ workflowCmd
734
+ .command('resume <id> <token>')
735
+ .description('Resume a paused workflow')
736
+ .action(async (id: string, token: string) => {
737
+ printQuickSplash(VERSION);
738
+ const { FishboneEngine } = require('../src/workflow/engine');
739
+ const engine = new FishboneEngine();
740
+ const resumed = await engine.resume(id, token);
741
+ if (resumed) {
742
+ fishSuccess(`Workflow ${id} resumed successfully.`);
743
+ } else {
744
+ fishError(`Failed to resume workflow ${id}. Invalid token or session not found.`);
745
+ }
746
+ });
747
+
748
+ // ─── Tools Command ──────────────────────────────────────────────
749
+
750
+ const toolsCmd = program
751
+ .command('tools')
752
+ .description('Manage AZERCLAW tools and plugins');
753
+
754
+ toolsCmd
755
+ .command('list')
756
+ .description('List all registered tools')
757
+ .action(() => {
758
+ const { getToolRegistry } = require('../src/tools/registry');
759
+ const registry = getToolRegistry();
760
+ const tools = registry.getAll();
761
+
762
+ console.log('');
763
+ fishInfo(`Registered Tools (${tools.length})`);
764
+ console.log('');
765
+
766
+ const Table = require('cli-table3');
767
+ const table = new Table({
768
+ head: [chalk.hex('#60a5fa')('Name'), chalk.hex('#60a5fa')('Version'), chalk.hex('#60a5fa')('Description')],
769
+ colWidths: [20, 10, 50],
770
+ wordWrap: true,
771
+ });
772
+
773
+ tools.forEach((tool: any) => {
774
+ table.push([
775
+ chalk.hex('#34d399')(tool.name),
776
+ chalk.dim(tool.version),
777
+ tool.description.slice(0, 100) + (tool.description.length > 100 ? '...' : '')
778
+ ]);
779
+ });
780
+
781
+ console.log(table.toString());
782
+ });
783
+
784
+ toolsCmd
785
+ .command('info <name>')
786
+ .description('Show detailed information about a tool')
787
+ .action((name: string) => {
788
+ const { getToolRegistry } = require('../src/tools/registry');
789
+ const tool = getToolRegistry().get(name);
790
+ if (!tool) {
791
+ fishError(`Tool not found: ${name}`);
792
+ return;
793
+ }
794
+
795
+ console.log('');
796
+ console.log(chalk.hex('#60a5fa').bold(`Tool: ${tool.name}`));
797
+ console.log(chalk.dim(`Version: ${tool.version}`));
798
+ if (tool.author) console.log(chalk.dim(`Author: ${tool.author}`));
799
+ console.log('');
800
+ console.log(tool.description);
801
+ console.log('');
802
+ console.log(chalk.hex('#fbbf24')('Parameters:'));
803
+ console.log(JSON.stringify(tool.parameters, null, 2));
804
+ });
805
+
806
+ toolsCmd
807
+ .command('docs')
808
+ .description('Generate markdown documentation for all tools')
809
+ .option('-o, --output <file>', 'Output file path', 'TOOLS.md')
810
+ .action(async (opts: any) => {
811
+ const { getToolRegistry } = require('../src/tools/registry');
812
+ const fs = require('fs');
813
+ const path = require('path');
814
+
815
+ const tools = getToolRegistry().getAll();
816
+ let markdown = `# 🐟 AZERCLAW Tools Documentation\n\n`;
817
+ markdown += `Generated on ${new Date().toLocaleDateString()}\n\n`;
818
+
819
+ tools.forEach((tool: any) => {
820
+ markdown += `## ${tool.name} (v${tool.version})\n\n`;
821
+ markdown += `${tool.description}\n\n`;
822
+ markdown += `### Parameters\n\n\`\`\`json\n${JSON.stringify(tool.parameters, null, 2)}\n\`\`\`\n\n`;
823
+ markdown += `---\n\n`;
824
+ });
825
+
826
+
827
+ const outputPath = path.resolve(process.cwd(), opts.output);
828
+ fs.writeFileSync(outputPath, markdown);
829
+ fishSuccess(`Documentation generated at ${outputPath}`);
830
+ });
831
+
832
+ toolsCmd
833
+ .command('install <url_or_path>')
834
+ .description('Install a tool plugin from a URL or local file (coming soon)')
835
+ .action((src: string) => {
836
+ fishInfo(`Plugin installation for '${src}' will be available in the next release.`);
837
+ fishInfo('For now, manually place your .js/.ts files in the ./plugins directory.');
838
+ });
839
+
840
+ toolsCmd.action(() => {
841
+ program.helpInformation();
842
+ });
843
+
288
844
  // ─── Parse & Run ────────────────────────────────────────────────
289
845
 
846
+
290
847
  program.parse(process.argv);