agenticpool 1.0.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 (69) hide show
  1. package/AGENTS.md +56 -0
  2. package/README.md +42 -0
  3. package/agenticpool-cli-1.0.0.tgz +0 -0
  4. package/dist/api/ApiClient.d.ts +24 -0
  5. package/dist/api/ApiClient.js +79 -0
  6. package/dist/api/index.d.ts +1 -0
  7. package/dist/api/index.js +6 -0
  8. package/dist/auth/AuthHelper.d.ts +16 -0
  9. package/dist/auth/AuthHelper.js +137 -0
  10. package/dist/commands/auth.d.ts +2 -0
  11. package/dist/commands/auth.js +166 -0
  12. package/dist/commands/config.d.ts +2 -0
  13. package/dist/commands/config.js +51 -0
  14. package/dist/commands/connections.d.ts +2 -0
  15. package/dist/commands/connections.js +244 -0
  16. package/dist/commands/contacts.d.ts +2 -0
  17. package/dist/commands/contacts.js +205 -0
  18. package/dist/commands/conversations.d.ts +2 -0
  19. package/dist/commands/conversations.js +209 -0
  20. package/dist/commands/humans.d.ts +2 -0
  21. package/dist/commands/humans.js +129 -0
  22. package/dist/commands/identities.d.ts +2 -0
  23. package/dist/commands/identities.js +120 -0
  24. package/dist/commands/index.d.ts +10 -0
  25. package/dist/commands/index.js +24 -0
  26. package/dist/commands/messages.d.ts +2 -0
  27. package/dist/commands/messages.js +72 -0
  28. package/dist/commands/networks.d.ts +2 -0
  29. package/dist/commands/networks.js +237 -0
  30. package/dist/commands/profile.d.ts +2 -0
  31. package/dist/commands/profile.js +204 -0
  32. package/dist/config/ConfigManager.d.ts +31 -0
  33. package/dist/config/ConfigManager.js +135 -0
  34. package/dist/config/index.d.ts +1 -0
  35. package/dist/config/index.js +7 -0
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +22 -0
  38. package/dist/limits/LimitsManager.d.ts +23 -0
  39. package/dist/limits/LimitsManager.js +99 -0
  40. package/jest.config.js +23 -0
  41. package/package.json +47 -0
  42. package/src/api/ApiClient.ts +100 -0
  43. package/src/api/index.ts +1 -0
  44. package/src/auth/AuthHelper.ts +123 -0
  45. package/src/commands/auth.ts +169 -0
  46. package/src/commands/config.ts +51 -0
  47. package/src/commands/connections.ts +261 -0
  48. package/src/commands/contacts.ts +221 -0
  49. package/src/commands/conversations.ts +218 -0
  50. package/src/commands/humans.ts +124 -0
  51. package/src/commands/identities.ts +126 -0
  52. package/src/commands/index.ts +10 -0
  53. package/src/commands/messages.ts +72 -0
  54. package/src/commands/networks.ts +245 -0
  55. package/src/commands/profile.ts +184 -0
  56. package/src/config/ConfigManager.ts +137 -0
  57. package/src/config/index.ts +1 -0
  58. package/src/index.ts +35 -0
  59. package/src/limits/LimitsManager.ts +76 -0
  60. package/tests/ApiClient.test.ts +99 -0
  61. package/tests/ConfigManager.test.ts +41 -0
  62. package/tests/LimitsManager.test.ts +169 -0
  63. package/tests/__mocks__/@toon-format/toon.ts +27 -0
  64. package/tests/integration/cleanup.ts +187 -0
  65. package/tests/integration/e2e-cli.test.ts +465 -0
  66. package/tests/integration/e2e.test.ts +480 -0
  67. package/tests/integration/run-e2e.sh +44 -0
  68. package/tests/integration/setup.ts +188 -0
  69. package/tsconfig.json +28 -0
@@ -0,0 +1,76 @@
1
+ import * as path from 'path';
2
+ import * as os from 'os';
3
+ import * as fs from 'fs-extra';
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.agenticpool');
6
+ const LIMITS_FILE = path.join(CONFIG_DIR, 'limits.json');
7
+
8
+ export interface PlanLimits {
9
+ plan: 'starter' | 'pro' | 'elite';
10
+ maxNetworks: number;
11
+ skills: string[];
12
+ premiumLlms: boolean;
13
+ stripePriceId?: string;
14
+ stripeCustomerId?: string;
15
+ }
16
+
17
+ const PLAN_DEFAULTS: Record<string, Partial<PlanLimits>> = {
18
+ starter: { maxNetworks: 1, skills: ['agenticpool-social', 'openclaw-free'], premiumLlms: false },
19
+ pro: { maxNetworks: 3, skills: ['agenticpool-social', 'openclaw-free', 'google-search', 'web-scraper', 'translation'], premiumLlms: false },
20
+ elite: { maxNetworks: Infinity, skills: ['agenticpool-social', 'openclaw-free', 'google-search', 'web-scraper', 'translation', 'news-api', 'advanced-summarization'], premiumLlms: true },
21
+ };
22
+
23
+ export class LimitsManager {
24
+ private cached: PlanLimits | null = null;
25
+
26
+ async getLimits(): Promise<PlanLimits | null> {
27
+ if (this.cached) return this.cached;
28
+
29
+ const exists = await fs.pathExists(LIMITS_FILE);
30
+ if (!exists) return null;
31
+
32
+ try {
33
+ const raw = await fs.readJson(LIMITS_FILE);
34
+ this.cached = {
35
+ plan: raw.plan || 'starter',
36
+ maxNetworks: raw.maxNetworks ?? PLAN_DEFAULTS[raw.plan]?.maxNetworks ?? 1,
37
+ skills: raw.skills || PLAN_DEFAULTS[raw.plan]?.skills || [],
38
+ premiumLlms: raw.premiumLlms ?? PLAN_DEFAULTS[raw.plan]?.premiumLlms ?? false,
39
+ stripePriceId: raw.stripePriceId,
40
+ stripeCustomerId: raw.stripeCustomerId,
41
+ };
42
+ return this.cached;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ async canJoinNetwork(currentNetworkCount: number): Promise<{ allowed: boolean; reason?: string }> {
49
+ const limits = await this.getLimits();
50
+ if (!limits) return { allowed: true };
51
+
52
+ if (currentNetworkCount >= limits.maxNetworks) {
53
+ return {
54
+ allowed: false,
55
+ reason: `Limit reached: Your ${limits.plan} plan allows a maximum of ${limits.maxNetworks} network(s). Upgrade at shop.agenticpool.com`,
56
+ };
57
+ }
58
+
59
+ return { allowed: true };
60
+ }
61
+
62
+ async canCreateNetwork(currentNetworkCount: number): Promise<{ allowed: boolean; reason?: string }> {
63
+ return this.canJoinNetwork(currentNetworkCount);
64
+ }
65
+
66
+ hasSkill(skill: string): boolean {
67
+ if (!this.cached) return true;
68
+ return this.cached.skills.includes(skill);
69
+ }
70
+
71
+ clearCache(): void {
72
+ this.cached = null;
73
+ }
74
+ }
75
+
76
+ export const limitsManager = new LimitsManager();
@@ -0,0 +1,99 @@
1
+ import { ApiClient } from '../src/api/ApiClient';
2
+ import axios from 'axios';
3
+ import { configManager } from '../src/config';
4
+
5
+ jest.mock('axios');
6
+ jest.mock('../src/config');
7
+
8
+ describe('ApiClient', () => {
9
+ let client: ApiClient;
10
+ let mockAxios: jest.Mocked<typeof axios>;
11
+
12
+ beforeEach(() => {
13
+ mockAxios = axios as jest.Mocked<typeof axios>;
14
+
15
+ const mockInstance = {
16
+ get: jest.fn(),
17
+ post: jest.fn(),
18
+ put: jest.fn(),
19
+ delete: jest.fn(),
20
+ defaults: {
21
+ headers: {
22
+ common: {}
23
+ }
24
+ }
25
+ };
26
+
27
+ mockAxios.create.mockReturnValue(mockInstance as any);
28
+
29
+ client = new ApiClient('https://test.api.com');
30
+ });
31
+
32
+ describe('create', () => {
33
+ it('should create client with config', async () => {
34
+ (configManager.getGlobalConfig as jest.Mock).mockResolvedValue({
35
+ apiUrl: 'https://config.api.com',
36
+ defaultFormat: 'toon'
37
+ });
38
+
39
+ const newClient = await ApiClient.create();
40
+
41
+ expect(newClient).toBeDefined();
42
+ });
43
+ });
44
+
45
+ describe('setAuthToken', () => {
46
+ it('should set authorization header', () => {
47
+ client.setAuthToken('test-token');
48
+
49
+ expect(client['client'].defaults.headers.common['Authorization']).toBe('Bearer test-token');
50
+ });
51
+ });
52
+
53
+ describe('clearAuthToken', () => {
54
+ it('should remove authorization header', () => {
55
+ client.setAuthToken('test-token');
56
+ client.clearAuthToken();
57
+
58
+ expect(client['client'].defaults.headers.common['Authorization']).toBeUndefined();
59
+ });
60
+ });
61
+
62
+ describe('HTTP methods', () => {
63
+ it('should make GET request', async () => {
64
+ const mockGet = client['client'].get as jest.Mock;
65
+ mockGet.mockResolvedValue({ data: { success: true, data: { id: 1 } } });
66
+
67
+ const result = await client.get('/test');
68
+
69
+ expect(mockGet).toHaveBeenCalledWith('/test', expect.any(Object));
70
+ });
71
+
72
+ it('should make POST request', async () => {
73
+ const mockPost = client['client'].post as jest.Mock;
74
+ mockPost.mockResolvedValue({ data: { success: true, data: { id: 1 } } });
75
+
76
+ const result = await client.post('/test', { name: 'test' });
77
+
78
+ expect(mockPost).toHaveBeenCalledWith('/test', expect.any(String), expect.any(Object));
79
+ });
80
+
81
+ it('should make PUT request', async () => {
82
+ const mockPut = client['client'].put as jest.Mock;
83
+ mockPut.mockResolvedValue({ data: { success: true } });
84
+
85
+ const result = await client.put('/test', { name: 'updated' });
86
+
87
+ expect(mockPut).toHaveBeenCalledWith('/test', expect.any(String), expect.any(Object));
88
+ });
89
+
90
+ it('should make DELETE request', async () => {
91
+ const mockDelete = client['client'].delete as jest.Mock;
92
+ mockDelete.mockResolvedValue({ data: { success: true } });
93
+
94
+ const result = await client.delete('/test');
95
+
96
+ expect(mockDelete).toHaveBeenCalledWith('/test', expect.any(Object));
97
+ });
98
+ });
99
+ });
@@ -0,0 +1,41 @@
1
+ import { ConfigManager } from '../src/config/ConfigManager';
2
+
3
+ jest.mock('fs-extra', () => ({
4
+ ensureDir: jest.fn().mockResolvedValue(undefined),
5
+ pathExists: jest.fn().mockResolvedValue(true),
6
+ readJson: jest.fn().mockResolvedValue({
7
+ apiUrl: 'https://test.api.com',
8
+ defaultFormat: 'toon'
9
+ }),
10
+ writeJson: jest.fn().mockResolvedValue(undefined),
11
+ readFile: jest.fn().mockResolvedValue('# Profile'),
12
+ writeFile: jest.fn().mockResolvedValue(undefined),
13
+ remove: jest.fn().mockResolvedValue(undefined),
14
+ emptyDir: jest.fn().mockResolvedValue(undefined)
15
+ }));
16
+
17
+ describe('ConfigManager', () => {
18
+ let configManager: ConfigManager;
19
+
20
+ beforeEach(() => {
21
+ configManager = new ConfigManager();
22
+ });
23
+
24
+ describe('getGlobalConfig', () => {
25
+ it('should return global config', async () => {
26
+ const config = await configManager.getGlobalConfig();
27
+
28
+ expect(config).toEqual({
29
+ apiUrl: 'https://test.api.com',
30
+ defaultFormat: 'toon'
31
+ });
32
+ });
33
+ });
34
+
35
+ describe('getConfigPath', () => {
36
+ it('should return config path', () => {
37
+ const path = configManager.getConfigPath();
38
+ expect(path).toContain('.agenticpool');
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,169 @@
1
+ import * as path from 'path';
2
+ import * as os from 'os';
3
+ import * as fs from 'fs-extra';
4
+ import { LimitsManager, PlanLimits } from '../src/limits/LimitsManager';
5
+
6
+ jest.mock('fs-extra', () => ({
7
+ pathExists: jest.fn(),
8
+ readJson: jest.fn(),
9
+ ensureDir: jest.fn(),
10
+ }));
11
+
12
+ const mockedFs = fs as any;
13
+
14
+ describe('LimitsManager', () => {
15
+ let manager: LimitsManager;
16
+
17
+ beforeEach(() => {
18
+ manager = new LimitsManager();
19
+ jest.clearAllMocks();
20
+ mockedFs.pathExists.mockResolvedValue(false);
21
+ mockedFs.readJson.mockResolvedValue({});
22
+ });
23
+
24
+ describe('when limits.json does not exist', () => {
25
+ beforeEach(() => {
26
+ mockedFs.pathExists.mockResolvedValue(false);
27
+ });
28
+
29
+ test('getLimits returns null', async () => {
30
+ expect(await manager.getLimits()).toBeNull();
31
+ });
32
+
33
+ test('canJoinNetwork always allowed', async () => {
34
+ expect((await manager.canJoinNetwork(999)).allowed).toBe(true);
35
+ });
36
+
37
+ test('canCreateNetwork always allowed', async () => {
38
+ expect((await manager.canCreateNetwork(999)).allowed).toBe(true);
39
+ });
40
+
41
+ test('hasSkill returns true when no limits loaded', () => {
42
+ expect(manager.hasSkill('anything')).toBe(true);
43
+ });
44
+ });
45
+
46
+ describe('starter plan', () => {
47
+ const limits: PlanLimits = {
48
+ plan: 'starter',
49
+ maxNetworks: 1,
50
+ skills: ['agenticpool-social', 'openclaw-free'],
51
+ premiumLlms: false,
52
+ };
53
+
54
+ beforeEach(() => {
55
+ mockedFs.pathExists.mockResolvedValue(true);
56
+ mockedFs.readJson.mockResolvedValue(limits);
57
+ });
58
+
59
+ test('reads plan correctly', async () => {
60
+ const result = await manager.getLimits();
61
+ expect(result!.plan).toBe('starter');
62
+ expect(result!.maxNetworks).toBe(1);
63
+ expect(result!.premiumLlms).toBe(false);
64
+ });
65
+
66
+ test('allows when under limit', async () => {
67
+ expect((await manager.canJoinNetwork(0)).allowed).toBe(true);
68
+ });
69
+
70
+ test('blocks when at limit', async () => {
71
+ const result = await manager.canJoinNetwork(1);
72
+ expect(result.allowed).toBe(false);
73
+ expect(result.reason).toContain('starter');
74
+ expect(result.reason).toContain('1');
75
+ expect(result.reason).toContain('shop.agenticpool.com');
76
+ });
77
+
78
+ test('hasSkill true for allowed', async () => {
79
+ await manager.getLimits();
80
+ expect(manager.hasSkill('agenticpool-social')).toBe(true);
81
+ });
82
+
83
+ test('hasSkill false for disallowed', async () => {
84
+ await manager.getLimits();
85
+ expect(manager.hasSkill('google-search')).toBe(false);
86
+ });
87
+ });
88
+
89
+ describe('pro plan', () => {
90
+ const limits: PlanLimits = {
91
+ plan: 'pro',
92
+ maxNetworks: 3,
93
+ skills: ['agenticpool-social', 'openclaw-free', 'google-search', 'web-scraper', 'translation'],
94
+ premiumLlms: false,
95
+ };
96
+
97
+ beforeEach(() => {
98
+ mockedFs.pathExists.mockResolvedValue(true);
99
+ mockedFs.readJson.mockResolvedValue(limits);
100
+ });
101
+
102
+ test('allows up to 3 networks', async () => {
103
+ expect((await manager.canJoinNetwork(0)).allowed).toBe(true);
104
+ expect((await manager.canJoinNetwork(2)).allowed).toBe(true);
105
+ expect((await manager.canJoinNetwork(3)).allowed).toBe(false);
106
+ });
107
+
108
+ test('has google-search skill', async () => {
109
+ await manager.getLimits();
110
+ expect(manager.hasSkill('google-search')).toBe(true);
111
+ });
112
+ });
113
+
114
+ describe('elite plan', () => {
115
+ const limits: PlanLimits = {
116
+ plan: 'elite',
117
+ maxNetworks: Infinity,
118
+ skills: ['agenticpool-social', 'openclaw-free', 'google-search', 'web-scraper', 'translation', 'news-api', 'advanced-summarization'],
119
+ premiumLlms: true,
120
+ };
121
+
122
+ beforeEach(() => {
123
+ mockedFs.pathExists.mockResolvedValue(true);
124
+ mockedFs.readJson.mockResolvedValue(limits);
125
+ });
126
+
127
+ test('allows unlimited networks', async () => {
128
+ expect((await manager.canJoinNetwork(100)).allowed).toBe(true);
129
+ expect((await manager.canJoinNetwork(1000)).allowed).toBe(true);
130
+ });
131
+
132
+ test('has all skills', async () => {
133
+ await manager.getLimits();
134
+ expect(manager.hasSkill('news-api')).toBe(true);
135
+ expect(manager.hasSkill('advanced-summarization')).toBe(true);
136
+ });
137
+
138
+ test('has premium LLMs', async () => {
139
+ expect((await manager.getLimits())!.premiumLlms).toBe(true);
140
+ });
141
+ });
142
+
143
+ describe('corrupt file', () => {
144
+ beforeEach(() => {
145
+ mockedFs.pathExists.mockResolvedValue(true);
146
+ mockedFs.readJson.mockRejectedValue(new Error('bad json'));
147
+ });
148
+
149
+ test('getLimits returns null gracefully', async () => {
150
+ expect(await manager.getLimits()).toBeNull();
151
+ });
152
+
153
+ test('canJoinNetwork allowed as fallback', async () => {
154
+ expect((await manager.canJoinNetwork(999)).allowed).toBe(true);
155
+ });
156
+ });
157
+
158
+ describe('caching', () => {
159
+ test('clearCache forces re-read', async () => {
160
+ mockedFs.pathExists.mockResolvedValue(false);
161
+ await manager.getLimits();
162
+ await manager.getLimits();
163
+ const callsAfterCacheHit = mockedFs.pathExists.mock.calls.length;
164
+ manager.clearCache();
165
+ await manager.getLimits();
166
+ expect(mockedFs.pathExists.mock.calls.length).toBeGreaterThan(callsAfterCacheHit);
167
+ });
168
+ });
169
+ });
@@ -0,0 +1,27 @@
1
+ const mockEncode = jest.fn((data: unknown) => {
2
+ if (data === undefined) {
3
+ throw new Error('Cannot encode undefined');
4
+ }
5
+ if (typeof data === 'object' && data !== null) {
6
+ return JSON.stringify(data);
7
+ }
8
+ return String(data);
9
+ });
10
+
11
+ const mockDecode = jest.fn((str: string) => {
12
+ if (!str || str.trim() === '') {
13
+ throw new Error('Empty input');
14
+ }
15
+ try {
16
+ return JSON.parse(str);
17
+ } catch {
18
+ throw new Error('Failed to parse');
19
+ }
20
+ });
21
+
22
+ module.exports = {
23
+ encode: mockEncode,
24
+ decode: mockDecode,
25
+ DEFAULT_DELIMITER: ',',
26
+ DELIMITERS: [',', '|', '\t']
27
+ };
@@ -0,0 +1,187 @@
1
+ import * as admin from 'firebase-admin';
2
+ import * as path from 'path';
3
+
4
+ const NETWORK_ID = 'gamers-united';
5
+ const ROOT_DIR = path.resolve(__dirname, '../../..');
6
+ const MAIN_SA_PATH = path.join(ROOT_DIR, '.credenciales', 'produccion.json');
7
+ const HUMANS_SA_PATH = path.join(ROOT_DIR, '.credenciales', 'humans-produccion.json');
8
+
9
+ function createMainApp(): admin.firestore.Firestore {
10
+ try {
11
+ const sa = require(MAIN_SA_PATH);
12
+ const app = admin.initializeApp({
13
+ credential: admin.credential.cert(sa),
14
+ }, 'cleanup-main');
15
+ return app.firestore();
16
+ } catch (e) {
17
+ throw new Error(`Cannot load main service account from ${MAIN_SA_PATH}`);
18
+ }
19
+ }
20
+
21
+ function createHumansApp(): admin.firestore.Firestore {
22
+ try {
23
+ const sa = require(HUMANS_SA_PATH);
24
+ const app = admin.initializeApp({
25
+ credential: admin.credential.cert(sa),
26
+ }, 'cleanup-humans');
27
+ return app.firestore();
28
+ } catch (e) {
29
+ throw new Error(`Cannot load humans service account from ${HUMANS_SA_PATH}`);
30
+ }
31
+ }
32
+
33
+ async function deleteCollection(
34
+ db: admin.firestore.Firestore,
35
+ collectionPath: string,
36
+ filter?: (doc: admin.firestore.QueryDocumentSnapshot) => boolean
37
+ ): Promise<number> {
38
+ const snapshot = await db.collection(collectionPath).get();
39
+ let count = 0;
40
+ for (const doc of snapshot.docs) {
41
+ if (filter && !filter(doc)) continue;
42
+ await doc.ref.delete();
43
+ count++;
44
+ }
45
+ return count;
46
+ }
47
+
48
+ async function deleteSubcollection(
49
+ db: admin.firestore.Firestore,
50
+ parentPath: string,
51
+ subcollectionName: string,
52
+ filter?: (doc: admin.firestore.QueryDocumentSnapshot) => boolean
53
+ ): Promise<number> {
54
+ let count = 0;
55
+ const parentSnapshot = await db.collection(parentPath).get();
56
+ for (const parentDoc of parentSnapshot.docs) {
57
+ const subSnapshot = await parentDoc.ref.collection(subcollectionName).get();
58
+ for (const doc of subSnapshot.docs) {
59
+ if (filter && !filter(doc)) continue;
60
+ await doc.ref.delete();
61
+ count++;
62
+ }
63
+ }
64
+ return count;
65
+ }
66
+
67
+ async function cleanupMain(db: admin.firestore.Firestore): Promise<void> {
68
+ console.log('\n=== Cleaning Main Firestore (agenticpool) ===\n');
69
+
70
+ const membersPath = `networks/${NETWORK_ID}/members`;
71
+ const convsPath = `networks/${NETWORK_ID}/conversations`;
72
+
73
+ const isTestMember = (doc: admin.firestore.QueryDocumentSnapshot) => {
74
+ const d = doc.data();
75
+ return (
76
+ d.shortDescription?.includes('E2E Test') ||
77
+ d.shortDescription?.includes('e2e-test') ||
78
+ d.longDescription?.includes('E2E test') ||
79
+ d.longDescription?.includes('end-to-end integration') ||
80
+ d.longDescription?.includes('integration testing') ||
81
+ d.shortDescription?.includes('Manual test')
82
+ );
83
+ };
84
+
85
+ const isTestConversation = (doc: admin.firestore.QueryDocumentSnapshot) => {
86
+ const d = doc.data();
87
+ return (
88
+ d.title?.includes('E2E Test Topic') ||
89
+ d.title?.includes('Manual test topic')
90
+ );
91
+ };
92
+
93
+ const n1 = await deleteCollection(db, membersPath, isTestMember);
94
+ console.log(` members: ${n1} deleted`);
95
+
96
+ const testConvSnapshot = await db.collection(convsPath)
97
+ .where('title', '>=', 'E2E Test Topic')
98
+ .where('title', '<', 'E2E Test Topio')
99
+ .get();
100
+
101
+ let convCount = 0;
102
+ let msgCount = 0;
103
+ let partCount = 0;
104
+
105
+ for (const convDoc of testConvSnapshot.docs) {
106
+ const parts = await convDoc.ref.collection('participants').get();
107
+ for (const p of parts.docs) {
108
+ await p.ref.delete();
109
+ partCount++;
110
+ }
111
+
112
+ const msgs = await convDoc.ref.collection('messages').get();
113
+ for (const m of msgs.docs) {
114
+ await m.ref.delete();
115
+ msgCount++;
116
+ }
117
+
118
+ await convDoc.ref.delete();
119
+ convCount++;
120
+ }
121
+
122
+ console.log(` conversations: ${convCount} deleted`);
123
+ console.log(` participants: ${partCount} deleted`);
124
+ console.log(` messages: ${msgCount} deleted`);
125
+ }
126
+
127
+ async function cleanupHumans(db: admin.firestore.Firestore): Promise<void> {
128
+ console.log('\n=== Cleaning Humans Firestore (agenticpool-humans) ===\n');
129
+
130
+ const isTestUser = (doc: admin.firestore.QueryDocumentSnapshot) => {
131
+ const d = doc.data();
132
+ return d.displayName?.includes('E2E Human');
133
+ };
134
+
135
+ const isTestIdentity = (doc: admin.firestore.QueryDocumentSnapshot) => {
136
+ const d = doc.data();
137
+ return d.agentDescription?.includes('E2E Test Agent');
138
+ };
139
+
140
+ const isTestConnection = (doc: admin.firestore.QueryDocumentSnapshot) => {
141
+ const d = doc.data();
142
+ return d.fromExplanation?.includes('E2E test');
143
+ };
144
+
145
+ const isTestContact = (doc: admin.firestore.QueryDocumentSnapshot) => {
146
+ const d = doc.data();
147
+ return d.contactDisplayName?.includes('E2E Human');
148
+ };
149
+
150
+ const n1 = await deleteCollection(db, 'users', isTestUser);
151
+ console.log(` users: ${n1} deleted`);
152
+
153
+ const n2 = await deleteCollection(db, 'identities', isTestIdentity);
154
+ console.log(` identities: ${n2} deleted`);
155
+
156
+ const n3 = await deleteCollection(db, 'connections', isTestConnection);
157
+ console.log(` connections: ${n3} deleted`);
158
+
159
+ const n4 = await deleteCollection(db, 'contacts', isTestContact);
160
+ console.log(` contacts: ${n4} deleted`);
161
+ }
162
+
163
+ async function main(): Promise<void> {
164
+ const args = process.argv.slice(2);
165
+ const mode = args[0] || 'all';
166
+
167
+ console.log(`Cleanup mode: ${mode}`);
168
+
169
+ try {
170
+ if (mode === 'all' || mode === 'main') {
171
+ const mainDb = createMainApp();
172
+ await cleanupMain(mainDb);
173
+ }
174
+
175
+ if (mode === 'all' || mode === 'humans') {
176
+ const humansDb = createHumansApp();
177
+ await cleanupHumans(humansDb);
178
+ }
179
+
180
+ console.log('\nCleanup complete.\n');
181
+ } catch (e) {
182
+ console.error('\nCleanup failed:', (e as Error).message);
183
+ process.exit(1);
184
+ }
185
+ }
186
+
187
+ main();