@plosson/agentio 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,7 +34,7 @@ jobs:
34
34
  CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
35
35
  steps:
36
36
  - uses: actions/checkout@v4
37
- - run: curl -LsSf https://agentio.work/install | sh
37
+ - run: curl -LsSf https://agentio.me/install | sh
38
38
  - run: npm install -g @anthropic-ai/claude-code
39
39
  - run: agentio config import && agentio claude install
40
40
  - run: claude -p "$(cat prompt.md)" --max-turns 30 --dangerously-skip-permissions
@@ -53,12 +53,12 @@ See [`examples/daily-briefing/`](./examples/daily-briefing) for the complete wor
53
53
 
54
54
  **macOS / Linux:**
55
55
  ```bash
56
- curl -LsSf https://agentio.work/install | sh
56
+ curl -LsSf https://agentio.me/install | sh
57
57
  ```
58
58
 
59
59
  **Windows (PowerShell):**
60
60
  ```powershell
61
- iwr -useb https://agentio.work/install.ps1 | iex
61
+ iwr -useb https://agentio.me/install.ps1 | iex
62
62
  ```
63
63
 
64
64
  <details>
@@ -279,7 +279,7 @@ jobs:
279
279
  CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
280
280
  steps:
281
281
  - uses: actions/checkout@v4
282
- - run: curl -LsSf https://agentio.work/install | sh
282
+ - run: curl -LsSf https://agentio.me/install | sh
283
283
  - run: npm install -g @anthropic-ai/claude-code
284
284
  - run: agentio config import && agentio claude install
285
285
  - run: claude -p "$(cat prompt.md)" --max-turns 30 --dangerously-skip-permissions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plosson/agentio",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "CLI for LLM agents to interact with communication and tracking services",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,9 +47,11 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@inquirer/prompts": "^8.2.0",
50
+ "@whiskeysockets/baileys": "^7.0.0-rc.9",
50
51
  "commander": "^14.0.2",
51
52
  "googleapis": "^169.0.0",
52
53
  "libsodium-wrappers": "^0.8.1",
54
+ "qrcode-terminal": "^0.12.0",
53
55
  "rss-parser": "^3.13.0"
54
56
  }
55
57
  }
package/src/auth/oauth.ts CHANGED
@@ -34,15 +34,27 @@ const GDRIVE_FULL_SCOPES = [
34
34
  'https://www.googleapis.com/auth/userinfo.email', // get email for profile naming
35
35
  ];
36
36
 
37
- const SCOPES: Record<'gmail' | 'gchat' | 'gdocs' | 'gdrive-readonly' | 'gdrive-full', string[]> = {
37
+ const GCAL_SCOPES = [
38
+ 'https://www.googleapis.com/auth/calendar', // full access to calendars
39
+ 'https://www.googleapis.com/auth/userinfo.email', // get email for profile naming
40
+ ];
41
+
42
+ const GTASKS_SCOPES = [
43
+ 'https://www.googleapis.com/auth/tasks', // full access to tasks
44
+ 'https://www.googleapis.com/auth/userinfo.email', // get email for profile naming
45
+ ];
46
+
47
+ const SCOPES: Record<'gmail' | 'gchat' | 'gdocs' | 'gdrive-readonly' | 'gdrive-full' | 'gcal' | 'gtasks', string[]> = {
38
48
  gmail: GMAIL_SCOPES,
39
49
  gchat: GCHAT_SCOPES,
40
50
  gdocs: GDOCS_SCOPES,
41
51
  'gdrive-readonly': GDRIVE_READONLY_SCOPES,
42
52
  'gdrive-full': GDRIVE_FULL_SCOPES,
53
+ gcal: GCAL_SCOPES,
54
+ gtasks: GTASKS_SCOPES,
43
55
  };
44
56
 
45
- export type OAuthService = 'gmail' | 'gchat' | 'gdocs' | 'gdrive-readonly' | 'gdrive-full';
57
+ export type OAuthService = 'gmail' | 'gchat' | 'gdocs' | 'gdrive-readonly' | 'gdrive-full' | 'gcal' | 'gtasks';
46
58
 
47
59
  export async function performOAuthFlow(
48
60
  service: OAuthService
@@ -0,0 +1,259 @@
1
+ import { Command } from 'commander';
2
+ import { existsSync } from 'fs';
3
+ import { randomBytes } from 'crypto';
4
+ import { handleError, CliError } from '../utils/errors';
5
+ import { loadConfig, saveConfig } from '../config/config-manager';
6
+ import { startDaemon, stopDaemon, getDaemonStatus, reloadDaemon, LOG_FILE } from '../gateway/daemon';
7
+ import { exportWhatsAppAuthState, importWhatsAppAuthState } from '../gateway/store';
8
+ import type { Config } from '../types/config';
9
+
10
+ export function registerGatewayCommands(program: Command): void {
11
+ const gateway = program
12
+ .command('gateway')
13
+ .description('Gateway daemon management');
14
+
15
+ gateway
16
+ .command('start')
17
+ .description('Start the gateway daemon')
18
+ .option('--foreground', 'Run in foreground (don\'t daemonize)')
19
+ .action(async (options) => {
20
+ try {
21
+ await startDaemon({ foreground: options.foreground });
22
+ } catch (error) {
23
+ handleError(error);
24
+ }
25
+ });
26
+
27
+ gateway
28
+ .command('stop')
29
+ .description('Stop the gateway daemon')
30
+ .action(async () => {
31
+ try {
32
+ await stopDaemon();
33
+ } catch (error) {
34
+ handleError(error);
35
+ }
36
+ });
37
+
38
+ gateway
39
+ .command('status')
40
+ .description('Show gateway daemon status')
41
+ .action(async () => {
42
+ try {
43
+ const status = await getDaemonStatus();
44
+
45
+ if (!status.running) {
46
+ console.log('Gateway: stopped');
47
+ return;
48
+ }
49
+
50
+ console.log(`Gateway: running (PID ${status.pid})`);
51
+
52
+ if (status.adapters && status.adapters.length > 0) {
53
+ console.log('\nConnected adapters:');
54
+ for (const adapter of status.adapters) {
55
+ const statusIcon = adapter.connected ? '✓' : '✗';
56
+ console.log(` ${statusIcon} ${adapter.service}:${adapter.profile}`);
57
+ }
58
+ } else {
59
+ console.log('\nNo adapters connected');
60
+ }
61
+ } catch (error) {
62
+ handleError(error);
63
+ }
64
+ });
65
+
66
+ gateway
67
+ .command('reload')
68
+ .description('Reload gateway configuration')
69
+ .action(async () => {
70
+ try {
71
+ await reloadDaemon();
72
+ } catch (error) {
73
+ handleError(error);
74
+ }
75
+ });
76
+
77
+ gateway
78
+ .command('logs')
79
+ .description('View gateway logs')
80
+ .option('--follow, -f', 'Follow log output')
81
+ .option('--lines <n>', 'Number of lines to show', '50')
82
+ .action(async (options) => {
83
+ try {
84
+ if (!existsSync(LOG_FILE)) {
85
+ console.log('No log file found');
86
+ return;
87
+ }
88
+
89
+ if (options.follow) {
90
+ // Tail -f equivalent
91
+ const tailProcess = Bun.spawn(['tail', '-f', LOG_FILE], {
92
+ stdout: 'inherit',
93
+ stderr: 'inherit',
94
+ });
95
+
96
+ process.on('SIGINT', () => {
97
+ tailProcess.kill();
98
+ process.exit(0);
99
+ });
100
+
101
+ await tailProcess.exited;
102
+ } else {
103
+ // Read last N lines
104
+ const lines = parseInt(options.lines, 10) || 50;
105
+ const tailProcess = Bun.spawn(['tail', `-${lines}`, LOG_FILE], {
106
+ stdout: 'inherit',
107
+ stderr: 'inherit',
108
+ });
109
+ await tailProcess.exited;
110
+ }
111
+ } catch (error) {
112
+ handleError(error);
113
+ }
114
+ });
115
+
116
+ // Profile subcommands
117
+ const profile = gateway.command('profile').description('Manage gateway identity');
118
+
119
+ profile
120
+ .command('add')
121
+ .description('Create gateway identity with secret key')
122
+ .option('--name <name>', 'Gateway name', 'default')
123
+ .option('--secret <secret>', 'Use specific secret (otherwise auto-generated)')
124
+ .action(async (options) => {
125
+ try {
126
+ const config = await loadConfig() as Config;
127
+
128
+ if (config.gateway?.name) {
129
+ throw new CliError('INVALID_PARAMS', `Gateway already configured: ${config.gateway.name}`, 'Use "gateway profile remove" first to reconfigure');
130
+ }
131
+
132
+ // Generate or use provided secret
133
+ const secret = options.secret || `gw_${randomBytes(24).toString('base64url')}`;
134
+
135
+ config.gateway = {
136
+ ...config.gateway,
137
+ name: options.name,
138
+ secret,
139
+ api: {
140
+ ...config.gateway?.api,
141
+ secret, // Also set as API secret
142
+ },
143
+ };
144
+
145
+ await saveConfig(config);
146
+
147
+ console.log(`Gateway profile created`);
148
+ console.log(` Name: ${options.name}`);
149
+ console.log(` Secret: ${secret}`);
150
+ console.log(`\nThis secret will be included in "agentio config export"`);
151
+ } catch (error) {
152
+ handleError(error);
153
+ }
154
+ });
155
+
156
+ profile
157
+ .command('list')
158
+ .description('Show gateway identity')
159
+ .action(async () => {
160
+ try {
161
+ const config = await loadConfig() as Config;
162
+
163
+ if (!config.gateway?.name) {
164
+ console.log('No gateway configured');
165
+ console.log('Run: agentio gateway profile add --name <name>');
166
+ return;
167
+ }
168
+
169
+ console.log('Gateway Profile');
170
+ console.log(` Name: ${config.gateway.name}`);
171
+ console.log(` Secret: ${config.gateway.secret ? config.gateway.secret.slice(0, 10) + '...' : '(not set)'}`);
172
+ console.log(` API Port: ${config.gateway.api?.port || 7890}`);
173
+ console.log(` API Host: ${config.gateway.api?.host || '127.0.0.1'}`);
174
+ } catch (error) {
175
+ handleError(error);
176
+ }
177
+ });
178
+
179
+ profile
180
+ .command('remove')
181
+ .description('Remove gateway identity')
182
+ .action(async () => {
183
+ try {
184
+ const config = await loadConfig() as Config;
185
+
186
+ if (!config.gateway?.name) {
187
+ console.log('No gateway configured');
188
+ return;
189
+ }
190
+
191
+ const name = config.gateway.name;
192
+ delete config.gateway.name;
193
+ delete config.gateway.secret;
194
+ if (config.gateway.api) {
195
+ delete config.gateway.api.secret;
196
+ }
197
+
198
+ await saveConfig(config);
199
+ console.log(`Gateway profile "${name}" removed`);
200
+ } catch (error) {
201
+ handleError(error);
202
+ }
203
+ });
204
+
205
+ // Teleport command
206
+ gateway
207
+ .command('teleport')
208
+ .description('Transfer auth state to remote gateway')
209
+ .argument('<url>', 'Remote gateway URL (e.g., https://my-gateway.com)')
210
+ .option('--service <service>', 'Service to teleport (default: all)', 'all')
211
+ .action(async (url: string, options) => {
212
+ try {
213
+ const config = await loadConfig() as Config;
214
+
215
+ if (!config.gateway?.secret) {
216
+ throw new CliError('CONFIG_ERROR', 'No gateway secret configured', 'Run: agentio gateway profile add');
217
+ }
218
+
219
+ console.log(`Teleporting to ${url}...`);
220
+
221
+ // Export WhatsApp auth state
222
+ if (options.service === 'all' || options.service === 'whatsapp') {
223
+ const profiles = config.profiles.whatsapp || [];
224
+
225
+ for (const profile of profiles) {
226
+ console.log(` Exporting whatsapp:${profile}...`);
227
+ const authState = await exportWhatsAppAuthState(profile);
228
+
229
+ if (!authState) {
230
+ console.log(` No auth state found, skipping`);
231
+ continue;
232
+ }
233
+
234
+ // Send to remote gateway
235
+ const response = await fetch(`${url}/import/whatsapp/${encodeURIComponent(profile)}`, {
236
+ method: 'POST',
237
+ headers: {
238
+ 'Content-Type': 'application/json',
239
+ 'Authorization': `Bearer ${config.gateway.secret}`,
240
+ },
241
+ body: JSON.stringify(authState),
242
+ });
243
+
244
+ if (!response.ok) {
245
+ const error = await response.text();
246
+ throw new CliError('API_ERROR', `Failed to import to remote: ${error}`);
247
+ }
248
+
249
+ console.log(` Transferred successfully`);
250
+ }
251
+ }
252
+
253
+ console.log('\nTeleport complete!');
254
+ console.log('You can now stop the local gateway and use the remote one.');
255
+ } catch (error) {
256
+ handleError(error);
257
+ }
258
+ });
259
+ }