@plosson/agentio 0.1.23 → 0.1.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plosson/agentio",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "CLI for LLM agents to interact with communication and tracking services",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,6 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "commander": "^14.0.2",
50
- "googleapis": "^169.0.0"
50
+ "googleapis": "^169.0.0",
51
+ "rss-parser": "^3.13.0"
51
52
  }
52
53
  }
@@ -1,183 +1,267 @@
1
1
  import { Command } from 'commander';
2
2
  import * as path from 'path';
3
- import * as fs from 'fs';
3
+ import { spawn } from 'child_process';
4
4
  import { CliError, handleError } from '../utils/errors';
5
5
  import {
6
6
  loadAgentioJson,
7
- agentioJsonExists,
8
- listPlugins,
7
+ addMarketplace,
8
+ addPlugin,
9
+ removeMarketplace,
9
10
  removePlugin,
10
- getPlugin,
11
11
  } from '../services/claude-plugin/agentio-json';
12
- import {
13
- installPlugin,
14
- removePluginFiles,
15
- } from '../services/claude-plugin/installer';
16
- import type { InstalledComponent } from '../types/claude-plugin';
12
+
13
+ /**
14
+ * Execute a claude CLI command and return the result.
15
+ */
16
+ async function execClaude(
17
+ args: string[]
18
+ ): Promise<{ success: boolean; stdout: string; stderr: string }> {
19
+ return new Promise((resolve) => {
20
+ const proc = spawn('claude', args, {
21
+ stdio: ['inherit', 'pipe', 'pipe'],
22
+ });
23
+
24
+ let stdout = '';
25
+ let stderr = '';
26
+
27
+ proc.stdout?.on('data', (data) => {
28
+ stdout += data.toString();
29
+ });
30
+
31
+ proc.stderr?.on('data', (data) => {
32
+ stderr += data.toString();
33
+ });
34
+
35
+ proc.on('close', (code) => {
36
+ resolve({
37
+ success: code === 0,
38
+ stdout,
39
+ stderr,
40
+ });
41
+ });
42
+
43
+ proc.on('error', (err) => {
44
+ resolve({
45
+ success: false,
46
+ stdout: '',
47
+ stderr: err.message,
48
+ });
49
+ });
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Install a marketplace by calling claude plugin marketplace add.
55
+ * Silently skips if already installed.
56
+ */
57
+ async function installMarketplace(url: string): Promise<boolean> {
58
+ console.error(`Adding marketplace: ${url}`);
59
+ const result = await execClaude(['plugin', 'marketplace', 'add', url]);
60
+
61
+ if (result.success) {
62
+ console.log(` Added: ${url}`);
63
+ return true;
64
+ }
65
+
66
+ // Check if already installed (skip silently)
67
+ const errLower = result.stderr.toLowerCase();
68
+ if (errLower.includes('already') || errLower.includes('exists')) {
69
+ console.log(` Skipped (already added): ${url}`);
70
+ return true;
71
+ }
72
+
73
+ console.error(` Failed: ${result.stderr.trim()}`);
74
+ return false;
75
+ }
76
+
77
+ /**
78
+ * Install a plugin by calling claude plugin install --scope project.
79
+ */
80
+ async function installPluginCmd(name: string): Promise<boolean> {
81
+ console.error(`Installing plugin: ${name}`);
82
+ const result = await execClaude(['plugin', 'install', name, '--scope', 'project']);
83
+
84
+ if (result.success) {
85
+ console.log(` Installed: ${name}`);
86
+ return true;
87
+ }
88
+
89
+ console.error(` Failed: ${result.stderr.trim()}`);
90
+ return false;
91
+ }
92
+
93
+ /**
94
+ * Uninstall a plugin by calling claude plugin uninstall --scope project.
95
+ */
96
+ async function uninstallPluginCmd(name: string): Promise<boolean> {
97
+ console.error(`Uninstalling plugin: ${name}`);
98
+ const result = await execClaude(['plugin', 'uninstall', name, '--scope', 'project']);
99
+
100
+ if (result.success) {
101
+ console.log(` Uninstalled: ${name}`);
102
+ return true;
103
+ }
104
+
105
+ // Check if not installed (skip silently)
106
+ const errLower = result.stderr.toLowerCase();
107
+ if (errLower.includes('not installed') || errLower.includes('not found')) {
108
+ console.log(` Skipped (not installed): ${name}`);
109
+ return true;
110
+ }
111
+
112
+ console.error(` Failed: ${result.stderr.trim()}`);
113
+ return false;
114
+ }
17
115
 
18
116
  export function registerClaudeCommands(program: Command): void {
19
117
  const claude = program
20
118
  .command('claude')
21
119
  .description('Claude Code plugin operations');
22
120
 
23
- const plugin = claude.command('plugin').description('Manage Claude Code plugins');
24
-
25
- plugin
26
- .command('install')
27
- .description('Install plugin(s) from GitHub or agentio.json')
28
- .argument('[source]', 'GitHub URL or owner/repo (omit to install from agentio.json)')
29
- .option('--skills', 'Install only skills')
30
- .option('--commands', 'Install only commands')
31
- .option('--hooks', 'Install only hooks')
32
- .option('--agents', 'Install only agents')
33
- .option('-f, --force', 'Force reinstall if already exists')
34
- .option('-d, --dir <path>', 'Target directory (default: current directory)')
35
- .option('-v, --verbose', 'Show detailed installation logs')
36
- .action(async (source, options) => {
121
+ // install command group
122
+ const install = claude.command('install').description('Install marketplaces and plugins');
123
+
124
+ install
125
+ .command('marketplace')
126
+ .description('Add a plugin marketplace')
127
+ .argument('<url>', 'Marketplace GitHub URL')
128
+ .option('-d, --dir <path>', 'Directory with agentio.json (default: current directory)')
129
+ .action(async (url, options) => {
37
130
  try {
38
131
  const targetDir = options.dir ? path.resolve(options.dir) : process.cwd();
39
132
 
40
- if (!fs.existsSync(targetDir)) {
41
- throw new CliError('INVALID_PARAMS', `Directory does not exist: ${targetDir}`);
133
+ const success = await installMarketplace(url);
134
+ if (success) {
135
+ addMarketplace(targetDir, url);
42
136
  }
137
+ } catch (error) {
138
+ handleError(error);
139
+ }
140
+ });
43
141
 
44
- if (source) {
45
- // Install specific plugin from source
46
- console.error(`Installing plugin from: ${source}`);
47
- console.error(`Target: ${path.join(targetDir, '.claude')}`);
48
-
49
- const result = await installPlugin(source, {
50
- skills: options.skills,
51
- commands: options.commands,
52
- hooks: options.hooks,
53
- agents: options.agents,
54
- force: options.force,
55
- targetDir,
56
- verbose: options.verbose,
57
- });
58
-
59
- console.log(`\nInstalled: ${result.manifest.name} v${result.manifest.version}`);
60
- if (result.installed.length > 0) {
61
- console.log(`Components: ${result.installed.length}`);
62
- for (const comp of result.installed) {
63
- console.log(` ${comp.type}/${comp.name}`);
64
- }
65
- }
66
- } else {
67
- // Install all plugins from agentio.json
68
- if (!agentioJsonExists(targetDir)) {
69
- throw new CliError(
70
- 'NOT_FOUND',
71
- 'No agentio.json found',
72
- 'Run: agentio claude plugin install <source> to install a plugin'
73
- );
74
- }
75
-
76
- const agentioJson = loadAgentioJson(targetDir);
77
- const plugins = Object.entries(agentioJson.plugins);
78
-
79
- if (plugins.length === 0) {
80
- console.log('No plugins defined in agentio.json');
81
- return;
82
- }
83
-
84
- console.error(`Installing ${plugins.length} plugin(s) from agentio.json...`);
85
- console.error(`Target: ${path.join(targetDir, '.claude')}`);
86
-
87
- let installed = 0;
88
- for (const [name, entry] of plugins) {
89
- console.error(`\nInstalling ${name}...`);
90
-
91
- // Determine component flags based on entry.components
92
- const installOptions = {
93
- skills:
94
- !entry.components || entry.components.includes('skills'),
95
- commands:
96
- !entry.components || entry.components.includes('commands'),
97
- hooks: !entry.components || entry.components.includes('hooks'),
98
- agents: !entry.components || entry.components.includes('agents'),
99
- force: options.force,
100
- targetDir,
101
- verbose: options.verbose,
102
- };
103
-
104
- try {
105
- const result = await installPlugin(entry.source, installOptions);
106
- console.log(` Installed: ${result.manifest.name} v${result.manifest.version}`);
107
- installed++;
108
- } catch (error) {
109
- console.error(` Failed to install ${name}: ${error}`);
110
- }
111
- }
142
+ install
143
+ .command('plugin')
144
+ .description('Install a plugin')
145
+ .argument('<name>', 'Plugin name (e.g., plugin-name@marketplace)')
146
+ .option('-d, --dir <path>', 'Directory with agentio.json (default: current directory)')
147
+ .action(async (name, options) => {
148
+ try {
149
+ const targetDir = options.dir ? path.resolve(options.dir) : process.cwd();
112
150
 
113
- console.log(`\nInstalled ${installed} of ${plugins.length} plugin(s)`);
151
+ const success = await installPluginCmd(name);
152
+ if (success) {
153
+ addPlugin(targetDir, name);
114
154
  }
115
155
  } catch (error) {
116
156
  handleError(error);
117
157
  }
118
158
  });
119
159
 
120
- plugin
160
+ // install with no subcommand - install all from agentio.json
161
+ install.action(async (options) => {
162
+ try {
163
+ const targetDir = options.dir ? path.resolve(options.dir) : process.cwd();
164
+ const config = loadAgentioJson(targetDir);
165
+
166
+ if (config.marketplaces.length === 0 && config.plugins.length === 0) {
167
+ console.log('No marketplaces or plugins defined in agentio.json');
168
+ return;
169
+ }
170
+
171
+ console.error(`Installing from agentio.json...`);
172
+
173
+ // Install marketplaces first
174
+ if (config.marketplaces.length > 0) {
175
+ console.error(`\nMarketplaces (${config.marketplaces.length}):`);
176
+ for (const url of config.marketplaces) {
177
+ await installMarketplace(url);
178
+ }
179
+ }
180
+
181
+ // Then install plugins
182
+ if (config.plugins.length > 0) {
183
+ console.error(`\nPlugins (${config.plugins.length}):`);
184
+ for (const name of config.plugins) {
185
+ await installPluginCmd(name);
186
+ }
187
+ }
188
+
189
+ console.log('\nDone.');
190
+ } catch (error) {
191
+ handleError(error);
192
+ }
193
+ });
194
+
195
+ // list command
196
+ claude
121
197
  .command('list')
122
- .description('List plugins from agentio.json')
198
+ .description('List marketplaces and plugins from agentio.json')
123
199
  .option('-d, --dir <path>', 'Directory with agentio.json (default: current directory)')
124
200
  .action(async (options) => {
125
201
  try {
126
202
  const targetDir = options.dir ? path.resolve(options.dir) : process.cwd();
203
+ const config = loadAgentioJson(targetDir);
127
204
 
128
- const plugins = listPlugins(targetDir);
129
-
130
- if (plugins.length === 0) {
131
- console.log('No plugins in agentio.json');
205
+ if (config.marketplaces.length === 0 && config.plugins.length === 0) {
206
+ console.log('No marketplaces or plugins defined in agentio.json');
132
207
  return;
133
208
  }
134
209
 
135
- console.log(`Plugins (${plugins.length}):\n`);
136
- for (const { name, entry } of plugins) {
137
- console.log(`${name} v${entry.version}`);
138
- console.log(` Source: ${entry.source}`);
139
- if (entry.components) {
140
- console.log(` Components: ${entry.components.join(', ')}`);
210
+ if (config.marketplaces.length > 0) {
211
+ console.log(`Marketplaces (${config.marketplaces.length}):`);
212
+ for (const url of config.marketplaces) {
213
+ console.log(` ${url}`);
214
+ }
215
+ }
216
+
217
+ if (config.plugins.length > 0) {
218
+ if (config.marketplaces.length > 0) {
219
+ console.log('');
220
+ }
221
+ console.log(`Plugins (${config.plugins.length}):`);
222
+ for (const name of config.plugins) {
223
+ console.log(` ${name}`);
141
224
  }
142
- console.log('');
143
225
  }
144
226
  } catch (error) {
145
227
  handleError(error);
146
228
  }
147
229
  });
148
230
 
149
- plugin
150
- .command('remove')
151
- .description('Remove an installed plugin')
152
- .argument('<name>', 'Plugin name')
231
+ // remove command group
232
+ const remove = claude.command('remove').description('Remove marketplaces and plugins');
233
+
234
+ remove
235
+ .command('marketplace')
236
+ .description('Remove a marketplace from agentio.json')
237
+ .argument('<url>', 'Marketplace URL to remove')
153
238
  .option('-d, --dir <path>', 'Directory with agentio.json (default: current directory)')
154
- .action(async (name, options) => {
239
+ .action(async (url, options) => {
155
240
  try {
156
241
  const targetDir = options.dir ? path.resolve(options.dir) : process.cwd();
157
242
 
158
- const entry = getPlugin(targetDir, name);
159
- if (!entry) {
160
- throw new CliError('NOT_FOUND', `Plugin '${name}' not found in agentio.json`);
243
+ const removed = removeMarketplace(targetDir, url);
244
+ if (removed) {
245
+ console.log(`Removed marketplace: ${url}`);
246
+ } else {
247
+ throw new CliError('NOT_FOUND', `Marketplace not found: ${url}`);
161
248
  }
249
+ } catch (error) {
250
+ handleError(error);
251
+ }
252
+ });
162
253
 
163
- console.error(`Removing plugin: ${name}...`);
164
-
165
- // Use stored installed components (no network calls needed)
166
- const components: InstalledComponent[] = entry.installedComponents || [];
167
-
168
- // Remove files
169
- removePluginFiles(targetDir, components);
254
+ remove
255
+ .command('plugin')
256
+ .description('Uninstall a plugin and remove from agentio.json')
257
+ .argument('<name>', 'Plugin name to remove')
258
+ .option('-d, --dir <path>', 'Directory with agentio.json (default: current directory)')
259
+ .action(async (name, options) => {
260
+ try {
261
+ const targetDir = options.dir ? path.resolve(options.dir) : process.cwd();
170
262
 
171
- // Update agentio.json
263
+ await uninstallPluginCmd(name);
172
264
  removePlugin(targetDir, name);
173
-
174
- console.log(`Removed: ${name}`);
175
- if (components.length > 0) {
176
- console.log(`Removed components: ${components.length}`);
177
- for (const comp of components) {
178
- console.log(` ${comp.type}/${comp.name}`);
179
- }
180
- }
181
265
  } catch (error) {
182
266
  handleError(error);
183
267
  }
@@ -0,0 +1,68 @@
1
+ import { Command } from 'commander';
2
+ import { RssClient } from '../services/rss/client';
3
+ import { handleError } from '../utils/errors';
4
+ import {
5
+ printRssArticleList,
6
+ printRssArticle,
7
+ printRssFeedInfo,
8
+ } from '../utils/output';
9
+
10
+ export function registerRssCommands(program: Command): void {
11
+ const rss = program
12
+ .command('rss')
13
+ .description('RSS feed operations');
14
+
15
+ // Get articles from a feed
16
+ rss
17
+ .command('articles')
18
+ .description('List articles from a blog')
19
+ .argument('<url>', 'Blog URL (feed will be auto-discovered)')
20
+ .option('--limit <n>', 'Number of articles', '20')
21
+ .option('--since <date>', 'Only articles after this date (YYYY-MM-DD)')
22
+ .action(async (url, options) => {
23
+ try {
24
+ const client = new RssClient();
25
+ const listOptions = {
26
+ limit: parseInt(options.limit, 10),
27
+ since: options.since ? new Date(options.since) : undefined,
28
+ };
29
+
30
+ const info = await client.getInfo(url);
31
+ const articles = await client.list(url, listOptions);
32
+ printRssArticleList(articles, info.title);
33
+ } catch (error) {
34
+ handleError(error);
35
+ }
36
+ });
37
+
38
+ // Get a specific article
39
+ rss
40
+ .command('get')
41
+ .description('Get a specific article')
42
+ .argument('<url>', 'Blog URL (feed will be auto-discovered)')
43
+ .argument('<article-id>', 'Article ID or URL')
44
+ .action(async (url, articleId) => {
45
+ try {
46
+ const client = new RssClient();
47
+ const article = await client.get(url, articleId);
48
+ printRssArticle(article);
49
+ } catch (error) {
50
+ handleError(error);
51
+ }
52
+ });
53
+
54
+ // Get feed info
55
+ rss
56
+ .command('info')
57
+ .description('Get feed information')
58
+ .argument('<url>', 'Blog URL (feed will be auto-discovered)')
59
+ .action(async (url) => {
60
+ try {
61
+ const client = new RssClient();
62
+ const info = await client.getInfo(url);
63
+ printRssFeedInfo(info);
64
+ } catch (error) {
65
+ handleError(error);
66
+ }
67
+ });
68
+ }
@@ -115,7 +115,7 @@ export async function listProfiles(service?: ServiceName): Promise<{
115
115
  default?: string;
116
116
  }[]> {
117
117
  const config = await loadConfig();
118
- const services: ServiceName[] = service ? [service] : ['gmail', 'gchat', 'jira', 'telegram'];
118
+ const services: ServiceName[] = service ? [service] : ['gmail', 'gchat', 'jira', 'slack', 'telegram'];
119
119
 
120
120
  return services.map((svc) => ({
121
121
  service: svc,
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import { registerTelegramCommands } from './commands/telegram';
5
5
  import { registerGChatCommands } from './commands/gchat';
6
6
  import { registerJiraCommands } from './commands/jira';
7
7
  import { registerSlackCommands } from './commands/slack';
8
+ import { registerRssCommands } from './commands/rss';
8
9
  import { registerUpdateCommand } from './commands/update';
9
10
  import { registerConfigCommands } from './commands/config';
10
11
  import { registerClaudeCommands } from './commands/claude';
@@ -31,6 +32,7 @@ registerTelegramCommands(program);
31
32
  registerGChatCommands(program);
32
33
  registerJiraCommands(program);
33
34
  registerSlackCommands(program);
35
+ registerRssCommands(program);
34
36
  registerUpdateCommand(program);
35
37
  registerConfigCommands(program);
36
38
  registerClaudeCommands(program);
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import type { AgentioJson, AgentioPluginEntry } from '../../types/claude-plugin';
3
+ import type { AgentioJson } from '../../types/claude-plugin';
4
4
 
5
5
  const AGENTIO_JSON_FILE = 'agentio.json';
6
6
 
@@ -19,15 +19,18 @@ export function loadAgentioJson(dir: string): AgentioJson {
19
19
  const filePath = getAgentioJsonPath(dir);
20
20
 
21
21
  if (!fs.existsSync(filePath)) {
22
- return { plugins: {} };
22
+ return { marketplaces: [], plugins: [] };
23
23
  }
24
24
 
25
25
  const content = fs.readFileSync(filePath, 'utf-8');
26
26
  const data = JSON.parse(content) as AgentioJson;
27
27
 
28
- // Ensure plugins object exists
28
+ // Ensure arrays exist
29
+ if (!data.marketplaces) {
30
+ data.marketplaces = [];
31
+ }
29
32
  if (!data.plugins) {
30
- data.plugins = {};
33
+ data.plugins = [];
31
34
  }
32
35
 
33
36
  return data;
@@ -43,61 +46,53 @@ export function saveAgentioJson(dir: string, data: AgentioJson): void {
43
46
  }
44
47
 
45
48
  /**
46
- * Check if agentio.json exists in the given directory.
49
+ * Add a marketplace URL if not already present.
47
50
  */
48
- export function agentioJsonExists(dir: string): boolean {
49
- return fs.existsSync(getAgentioJsonPath(dir));
51
+ export function addMarketplace(dir: string, url: string): void {
52
+ const data = loadAgentioJson(dir);
53
+ if (!data.marketplaces.includes(url)) {
54
+ data.marketplaces.push(url);
55
+ saveAgentioJson(dir, data);
56
+ }
50
57
  }
51
58
 
52
59
  /**
53
- * Add or update a plugin entry in agentio.json.
60
+ * Add a plugin name if not already present.
54
61
  */
55
- export function addPlugin(
56
- dir: string,
57
- name: string,
58
- entry: AgentioPluginEntry
59
- ): void {
62
+ export function addPlugin(dir: string, name: string): void {
60
63
  const data = loadAgentioJson(dir);
61
- data.plugins[name] = entry;
62
- saveAgentioJson(dir, data);
64
+ if (!data.plugins.includes(name)) {
65
+ data.plugins.push(name);
66
+ saveAgentioJson(dir, data);
67
+ }
63
68
  }
64
69
 
65
70
  /**
66
- * Remove a plugin entry from agentio.json.
67
- * Returns true if plugin was found and removed.
71
+ * Remove a marketplace URL.
72
+ * Returns true if found and removed.
68
73
  */
69
- export function removePlugin(dir: string, name: string): boolean {
74
+ export function removeMarketplace(dir: string, url: string): boolean {
70
75
  const data = loadAgentioJson(dir);
71
-
72
- if (!data.plugins[name]) {
76
+ const index = data.marketplaces.indexOf(url);
77
+ if (index === -1) {
73
78
  return false;
74
79
  }
75
-
76
- delete data.plugins[name];
80
+ data.marketplaces.splice(index, 1);
77
81
  saveAgentioJson(dir, data);
78
82
  return true;
79
83
  }
80
84
 
81
85
  /**
82
- * Get a plugin entry from agentio.json.
83
- */
84
- export function getPlugin(
85
- dir: string,
86
- name: string
87
- ): AgentioPluginEntry | undefined {
88
- const data = loadAgentioJson(dir);
89
- return data.plugins[name];
90
- }
91
-
92
- /**
93
- * List all plugins in agentio.json.
86
+ * Remove a plugin name.
87
+ * Returns true if found and removed.
94
88
  */
95
- export function listPlugins(
96
- dir: string
97
- ): Array<{ name: string; entry: AgentioPluginEntry }> {
89
+ export function removePlugin(dir: string, name: string): boolean {
98
90
  const data = loadAgentioJson(dir);
99
- return Object.entries(data.plugins).map(([name, entry]) => ({
100
- name,
101
- entry,
102
- }));
91
+ const index = data.plugins.indexOf(name);
92
+ if (index === -1) {
93
+ return false;
94
+ }
95
+ data.plugins.splice(index, 1);
96
+ saveAgentioJson(dir, data);
97
+ return true;
103
98
  }