@crmy/cli 0.5.2 → 0.5.4
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/dist/index.js +2039 -0
- package/package.json +5 -1
- package/src/client.ts +0 -287
- package/src/commands/accounts.ts +0 -78
- package/src/commands/activity-types.ts +0 -59
- package/src/commands/actors.ts +0 -80
- package/src/commands/assignments.ts +0 -164
- package/src/commands/auth.ts +0 -161
- package/src/commands/briefing.ts +0 -41
- package/src/commands/config.ts +0 -25
- package/src/commands/contacts.ts +0 -83
- package/src/commands/context-types.ts +0 -56
- package/src/commands/context.ts +0 -181
- package/src/commands/custom-fields.ts +0 -63
- package/src/commands/emails.ts +0 -71
- package/src/commands/events.ts +0 -54
- package/src/commands/help.ts +0 -81
- package/src/commands/hitl.ts +0 -50
- package/src/commands/init.ts +0 -129
- package/src/commands/mcp.ts +0 -70
- package/src/commands/migrate.ts +0 -49
- package/src/commands/notes.ts +0 -88
- package/src/commands/opps.ts +0 -106
- package/src/commands/pipeline.ts +0 -26
- package/src/commands/search.ts +0 -45
- package/src/commands/server.ts +0 -32
- package/src/commands/use-cases.ts +0 -97
- package/src/commands/webhooks.ts +0 -95
- package/src/commands/workflows.ts +0 -119
- package/src/config.ts +0 -74
- package/src/index.ts +0 -80
- package/tsconfig.json +0 -13
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crmy/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "CRMy CLI — Local CLI + stdio MCP server",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
6
9
|
"bin": {
|
|
7
10
|
"crmy": "dist/index.js"
|
|
8
11
|
},
|
|
9
12
|
"main": "dist/index.js",
|
|
10
13
|
"scripts": {
|
|
11
14
|
"build": "tsup src/index.ts --format esm --clean",
|
|
15
|
+
"prepare": "npm run build",
|
|
12
16
|
"dev": "tsx src/index.ts"
|
|
13
17
|
},
|
|
14
18
|
"dependencies": {
|
package/src/client.ts
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 CRMy Contributors
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import { loadConfigFile, loadAuthState } from './config.js';
|
|
5
|
-
|
|
6
|
-
export interface CliClient {
|
|
7
|
-
call(toolName: string, input: Record<string, unknown>): Promise<string>;
|
|
8
|
-
close(): Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Map MCP tool names to REST method + path
|
|
12
|
-
const TOOL_REST_MAP: Record<string, { method: string; path: (input: Record<string, unknown>) => string; bodyTransform?: (input: Record<string, unknown>) => Record<string, unknown> }> = {
|
|
13
|
-
// Contacts
|
|
14
|
-
contact_create: { method: 'POST', path: () => '/api/v1/contacts' },
|
|
15
|
-
contact_get: { method: 'GET', path: (i) => `/api/v1/contacts/${i.id}` },
|
|
16
|
-
contact_search: { method: 'GET', path: (i) => `/api/v1/contacts?q=${encodeURIComponent((i.query as string) ?? '')}&limit=${i.limit ?? 20}${i.lifecycle_stage ? `&stage=${i.lifecycle_stage}` : ''}${i.cursor ? `&cursor=${i.cursor}` : ''}` },
|
|
17
|
-
contact_update: { method: 'PATCH', path: (i) => `/api/v1/contacts/${i.id}` },
|
|
18
|
-
contact_set_lifecycle: { method: 'PATCH', path: (i) => `/api/v1/contacts/${i.id}` },
|
|
19
|
-
contact_log_activity: { method: 'POST', path: () => '/api/v1/activities' },
|
|
20
|
-
contact_get_timeline: { method: 'GET', path: (i) => `/api/v1/contacts/${i.id}/timeline` },
|
|
21
|
-
|
|
22
|
-
// Accounts
|
|
23
|
-
account_create: { method: 'POST', path: () => '/api/v1/accounts' },
|
|
24
|
-
account_get: { method: 'GET', path: (i) => `/api/v1/accounts/${i.id}` },
|
|
25
|
-
account_search: { method: 'GET', path: (i) => `/api/v1/accounts?q=${encodeURIComponent((i.query as string) ?? '')}&limit=${i.limit ?? 20}` },
|
|
26
|
-
account_update: { method: 'PATCH', path: (i) => `/api/v1/accounts/${i.id}` },
|
|
27
|
-
|
|
28
|
-
// Opportunities
|
|
29
|
-
opportunity_create: { method: 'POST', path: () => '/api/v1/opportunities' },
|
|
30
|
-
opportunity_get: { method: 'GET', path: (i) => `/api/v1/opportunities/${i.id}` },
|
|
31
|
-
opportunity_search: { method: 'GET', path: (i) => `/api/v1/opportunities?q=${encodeURIComponent((i.query as string) ?? '')}&limit=${i.limit ?? 20}${i.stage ? `&stage=${i.stage}` : ''}` },
|
|
32
|
-
opportunity_advance_stage: { method: 'PATCH', path: (i) => `/api/v1/opportunities/${i.id}` },
|
|
33
|
-
opportunity_update: { method: 'PATCH', path: (i) => `/api/v1/opportunities/${i.id}` },
|
|
34
|
-
|
|
35
|
-
// Activities
|
|
36
|
-
activity_create: { method: 'POST', path: () => '/api/v1/activities' },
|
|
37
|
-
activity_get: { method: 'GET', path: (i) => `/api/v1/activities?limit=${i.limit ?? 20}` },
|
|
38
|
-
activity_search: { method: 'GET', path: (i) => `/api/v1/activities?limit=${i.limit ?? 20}` },
|
|
39
|
-
|
|
40
|
-
// Use Cases
|
|
41
|
-
use_case_create: { method: 'POST', path: () => '/api/v1/use-cases' },
|
|
42
|
-
use_case_get: { method: 'GET', path: (i) => `/api/v1/use-cases/${i.id}` },
|
|
43
|
-
use_case_search: { method: 'GET', path: (i) => `/api/v1/use-cases?q=${encodeURIComponent((i.query as string) ?? '')}&limit=${i.limit ?? 20}${i.stage ? `&stage=${i.stage}` : ''}${i.account_id ? `&account_id=${i.account_id}` : ''}` },
|
|
44
|
-
use_case_update: { method: 'PATCH', path: (i) => `/api/v1/use-cases/${i.id}` },
|
|
45
|
-
use_case_delete: { method: 'DELETE', path: (i) => `/api/v1/use-cases/${i.id}` },
|
|
46
|
-
use_case_advance_stage: { method: 'POST', path: (i) => `/api/v1/use-cases/${i.id}/stage` },
|
|
47
|
-
use_case_update_consumption: { method: 'POST', path: (i) => `/api/v1/use-cases/${i.id}/consumption` },
|
|
48
|
-
use_case_set_health: { method: 'POST', path: (i) => `/api/v1/use-cases/${i.id}/health` },
|
|
49
|
-
use_case_link_contact: { method: 'POST', path: (i) => `/api/v1/use-cases/${i.use_case_id ?? i.id}/contacts` },
|
|
50
|
-
use_case_unlink_contact: { method: 'DELETE', path: (i) => `/api/v1/use-cases/${i.use_case_id ?? i.id}/contacts/${i.contact_id}` },
|
|
51
|
-
use_case_list_contacts: { method: 'GET', path: (i) => `/api/v1/use-cases/${i.use_case_id ?? i.id}/contacts` },
|
|
52
|
-
use_case_get_timeline: { method: 'GET', path: (i) => `/api/v1/use-cases/${i.id}/timeline` },
|
|
53
|
-
use_case_summary: { method: 'GET', path: (i) => `/api/v1/analytics/use-cases?group_by=${i.group_by ?? 'stage'}` },
|
|
54
|
-
|
|
55
|
-
// Analytics
|
|
56
|
-
pipeline_summary: { method: 'GET', path: (i) => `/api/v1/analytics/pipeline${i.owner_id ? `?owner_id=${i.owner_id}` : ''}` },
|
|
57
|
-
pipeline_forecast: { method: 'GET', path: (i) => `/api/v1/analytics/forecast${i.period ? `?period=${i.period}` : ''}` },
|
|
58
|
-
|
|
59
|
-
// HITL
|
|
60
|
-
hitl_list_pending: { method: 'GET', path: () => '/api/v1/hitl' },
|
|
61
|
-
hitl_check_status: { method: 'GET', path: (i) => `/api/v1/hitl/${i.id}` },
|
|
62
|
-
hitl_submit_request: { method: 'POST', path: () => '/api/v1/hitl' },
|
|
63
|
-
hitl_resolve: { method: 'POST', path: (i) => `/api/v1/hitl/${i.id}/resolve` },
|
|
64
|
-
|
|
65
|
-
// Webhooks
|
|
66
|
-
webhook_create: { method: 'POST', path: () => '/api/v1/webhooks' },
|
|
67
|
-
webhook_list: { method: 'GET', path: () => '/api/v1/webhooks' },
|
|
68
|
-
webhook_get: { method: 'GET', path: (i) => `/api/v1/webhooks/${i.id}` },
|
|
69
|
-
webhook_update: { method: 'PATCH', path: (i) => `/api/v1/webhooks/${i.id}` },
|
|
70
|
-
webhook_delete: { method: 'DELETE', path: (i) => `/api/v1/webhooks/${i.id}` },
|
|
71
|
-
webhook_list_deliveries: { method: 'GET', path: (i) => `/api/v1/webhooks/${i.endpoint_id ?? i.id}/deliveries` },
|
|
72
|
-
|
|
73
|
-
// Emails
|
|
74
|
-
email_create: { method: 'POST', path: () => '/api/v1/emails' },
|
|
75
|
-
email_get: { method: 'GET', path: (i) => `/api/v1/emails/${i.id}` },
|
|
76
|
-
email_search: { method: 'GET', path: (i) => `/api/v1/emails?limit=${i.limit ?? 20}` },
|
|
77
|
-
|
|
78
|
-
// Custom Fields
|
|
79
|
-
custom_field_create: { method: 'POST', path: () => '/api/v1/custom-fields' },
|
|
80
|
-
custom_field_list: { method: 'GET', path: (i) => `/api/v1/custom-fields?object_type=${i.object_type}` },
|
|
81
|
-
custom_field_delete: { method: 'DELETE', path: (i) => `/api/v1/custom-fields/${i.id}` },
|
|
82
|
-
|
|
83
|
-
// Notes
|
|
84
|
-
note_create: { method: 'POST', path: () => '/api/v1/notes' },
|
|
85
|
-
note_get: { method: 'GET', path: (i) => `/api/v1/notes/${i.id}` },
|
|
86
|
-
note_list: { method: 'GET', path: (i) => `/api/v1/notes?object_type=${i.object_type}&object_id=${i.object_id}` },
|
|
87
|
-
note_delete: { method: 'DELETE', path: (i) => `/api/v1/notes/${i.id}` },
|
|
88
|
-
|
|
89
|
-
// Workflows
|
|
90
|
-
workflow_create: { method: 'POST', path: () => '/api/v1/workflows' },
|
|
91
|
-
workflow_get: { method: 'GET', path: (i) => `/api/v1/workflows/${i.id}` },
|
|
92
|
-
workflow_list: { method: 'GET', path: () => '/api/v1/workflows' },
|
|
93
|
-
workflow_delete: { method: 'DELETE', path: (i) => `/api/v1/workflows/${i.id}` },
|
|
94
|
-
workflow_run_list: { method: 'GET', path: (i) => `/api/v1/workflows/${i.workflow_id ?? i.id}/runs` },
|
|
95
|
-
|
|
96
|
-
// Events
|
|
97
|
-
event_search: { method: 'GET', path: (i) => `/api/v1/events?${i.object_id ? `object_id=${i.object_id}&` : ''}limit=${i.limit ?? 20}` },
|
|
98
|
-
|
|
99
|
-
// Search
|
|
100
|
-
search: { method: 'GET', path: (i) => `/api/v1/search?q=${encodeURIComponent((i.query as string) ?? '')}` },
|
|
101
|
-
|
|
102
|
-
// Meta
|
|
103
|
-
schema_get: { method: 'GET', path: () => '/health' },
|
|
104
|
-
tenant_get_stats: { method: 'GET', path: () => '/health' },
|
|
105
|
-
|
|
106
|
-
// Actors
|
|
107
|
-
actor_register: { method: 'POST', path: () => '/api/v1/actors' },
|
|
108
|
-
actor_get: { method: 'GET', path: (i) => `/api/v1/actors/${i.id}` },
|
|
109
|
-
actor_list: { method: 'GET', path: (i) => `/api/v1/actors?limit=${i.limit ?? 20}${i.actor_type ? `&actor_type=${i.actor_type}` : ''}${i.query ? `&q=${encodeURIComponent(i.query as string)}` : ''}` },
|
|
110
|
-
actor_update: { method: 'PATCH', path: (i) => `/api/v1/actors/${i.id}` },
|
|
111
|
-
actor_whoami: { method: 'GET', path: () => '/api/v1/actors/whoami' },
|
|
112
|
-
|
|
113
|
-
// Assignments
|
|
114
|
-
assignment_create: { method: 'POST', path: () => '/api/v1/assignments' },
|
|
115
|
-
assignment_get: { method: 'GET', path: (i) => `/api/v1/assignments/${i.id}` },
|
|
116
|
-
assignment_list: { method: 'GET', path: (i) => `/api/v1/assignments?limit=${i.limit ?? 20}${i.assigned_to ? `&assigned_to=${i.assigned_to}` : ''}${i.assigned_by ? `&assigned_by=${i.assigned_by}` : ''}${i.status ? `&status=${i.status}` : ''}` },
|
|
117
|
-
assignment_update: { method: 'PATCH', path: (i) => `/api/v1/assignments/${i.id}` },
|
|
118
|
-
assignment_accept: { method: 'POST', path: (i) => `/api/v1/assignments/${i.id}/accept` },
|
|
119
|
-
assignment_complete: { method: 'POST', path: (i) => `/api/v1/assignments/${i.id}/complete` },
|
|
120
|
-
assignment_decline: { method: 'POST', path: (i) => `/api/v1/assignments/${i.id}/decline` },
|
|
121
|
-
|
|
122
|
-
// Context Entries
|
|
123
|
-
context_add: { method: 'POST', path: () => '/api/v1/context' },
|
|
124
|
-
context_get: { method: 'GET', path: (i) => `/api/v1/context/${i.id}` },
|
|
125
|
-
context_list: { method: 'GET', path: (i) => `/api/v1/context?limit=${i.limit ?? 20}${i.subject_type ? `&subject_type=${i.subject_type}` : ''}${i.subject_id ? `&subject_id=${i.subject_id}` : ''}${i.context_type ? `&context_type=${i.context_type}` : ''}` },
|
|
126
|
-
context_supersede: { method: 'POST', path: (i) => `/api/v1/context/${i.id}/supersede` },
|
|
127
|
-
context_search: { method: 'GET', path: (i) => `/api/v1/context/search?q=${encodeURIComponent(i.query as string)}&limit=${i.limit ?? 20}${i.subject_type ? `&subject_type=${i.subject_type}` : ''}${i.context_type ? `&context_type=${i.context_type}` : ''}${i.tag ? `&tag=${i.tag}` : ''}${i.current_only === false ? '¤t_only=false' : ''}` },
|
|
128
|
-
context_review: { method: 'POST', path: (i) => `/api/v1/context/${i.id}/review` },
|
|
129
|
-
context_stale: { method: 'GET', path: (i) => `/api/v1/context/stale?limit=${i.limit ?? 20}${i.subject_type ? `&subject_type=${i.subject_type}` : ''}${i.subject_id ? `&subject_id=${i.subject_id}` : ''}` },
|
|
130
|
-
|
|
131
|
-
// Activity Type Registry
|
|
132
|
-
activity_type_list: { method: 'GET', path: (i) => `/api/v1/activity-types${i.category ? `?category=${i.category}` : ''}` },
|
|
133
|
-
activity_type_add: { method: 'POST', path: () => '/api/v1/activity-types' },
|
|
134
|
-
activity_type_remove: { method: 'DELETE', path: (i) => `/api/v1/activity-types/${i.type_name}` },
|
|
135
|
-
|
|
136
|
-
// Context Type Registry
|
|
137
|
-
context_type_list: { method: 'GET', path: () => '/api/v1/context-types' },
|
|
138
|
-
context_type_add: { method: 'POST', path: () => '/api/v1/context-types' },
|
|
139
|
-
context_type_remove: { method: 'DELETE', path: (i) => `/api/v1/context-types/${i.type_name}` },
|
|
140
|
-
|
|
141
|
-
// Briefing
|
|
142
|
-
briefing_get: { method: 'GET', path: (i) => `/api/v1/briefing/${i.subject_type}/${i.subject_id}?format=${i.format ?? 'json'}${i.since ? `&since=${i.since}` : ''}${i.include_stale ? '&include_stale=true' : ''}${i.context_types ? `&context_types=${(i.context_types as string[]).join(',')}` : ''}` },
|
|
143
|
-
|
|
144
|
-
// Assignment: Start, Block, Cancel
|
|
145
|
-
assignment_start: { method: 'POST', path: (i) => `/api/v1/assignments/${i.id}/start` },
|
|
146
|
-
assignment_block: { method: 'POST', path: (i) => `/api/v1/assignments/${i.id}/block` },
|
|
147
|
-
assignment_cancel: { method: 'POST', path: (i) => `/api/v1/assignments/${i.id}/cancel` },
|
|
148
|
-
|
|
149
|
-
// Activity Timeline (enhanced)
|
|
150
|
-
activity_get_timeline: { method: 'GET', path: (i) => `/api/v1/activities?subject_type=${i.subject_type}&subject_id=${i.subject_id}&limit=${i.limit ?? 50}` },
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Create an HTTP-based client that calls the CRMy REST API.
|
|
155
|
-
*/
|
|
156
|
-
function createHttpClient(serverUrl: string, token: string): CliClient {
|
|
157
|
-
return {
|
|
158
|
-
async call(toolName: string, input: Record<string, unknown>): Promise<string> {
|
|
159
|
-
const mapping = TOOL_REST_MAP[toolName];
|
|
160
|
-
if (!mapping) {
|
|
161
|
-
throw new Error(`Unknown tool: ${toolName} (no REST mapping)`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const { method, path } = mapping;
|
|
165
|
-
const url = `${serverUrl.replace(/\/$/, '')}${path(input)}`;
|
|
166
|
-
|
|
167
|
-
const headers: Record<string, string> = {
|
|
168
|
-
'Authorization': `Bearer ${token}`,
|
|
169
|
-
'Content-Type': 'application/json',
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const fetchOpts: RequestInit = { method, headers };
|
|
173
|
-
if (method === 'POST' || method === 'PATCH' || method === 'PUT') {
|
|
174
|
-
// Strip path params from body
|
|
175
|
-
const body = { ...input };
|
|
176
|
-
delete body.id;
|
|
177
|
-
fetchOpts.body = JSON.stringify(body);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const res = await fetch(url, fetchOpts);
|
|
181
|
-
|
|
182
|
-
if (res.status === 401) {
|
|
183
|
-
throw new Error('Authentication expired. Run `crmy login` to re-authenticate.');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const responseBody = await res.text();
|
|
187
|
-
if (!res.ok) {
|
|
188
|
-
let detail = responseBody;
|
|
189
|
-
try {
|
|
190
|
-
detail = JSON.parse(responseBody).detail ?? responseBody;
|
|
191
|
-
} catch {}
|
|
192
|
-
throw new Error(`API error (${res.status}): ${detail}`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return responseBody;
|
|
196
|
-
},
|
|
197
|
-
async close() {
|
|
198
|
-
// No cleanup needed for HTTP client
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Create a direct database client using MCP tools.
|
|
205
|
-
*/
|
|
206
|
-
async function createDbClient(databaseUrl: string, apiKey?: string): Promise<CliClient> {
|
|
207
|
-
process.env.CRMY_IMPORTED = '1';
|
|
208
|
-
|
|
209
|
-
const { initPool, closePool, getAllTools } = await import('@crmy/server');
|
|
210
|
-
const db = await initPool(databaseUrl);
|
|
211
|
-
|
|
212
|
-
let actor = {
|
|
213
|
-
tenant_id: '',
|
|
214
|
-
actor_id: 'cli-user',
|
|
215
|
-
actor_type: 'user' as const,
|
|
216
|
-
role: 'owner' as const,
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
if (apiKey) {
|
|
220
|
-
const crypto = await import('node:crypto');
|
|
221
|
-
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
|
222
|
-
const result = await db.query(
|
|
223
|
-
`SELECT ak.tenant_id, ak.user_id, u.role
|
|
224
|
-
FROM api_keys ak LEFT JOIN users u ON ak.user_id = u.id
|
|
225
|
-
WHERE ak.key_hash = $1`,
|
|
226
|
-
[keyHash],
|
|
227
|
-
);
|
|
228
|
-
if (result.rows.length > 0) {
|
|
229
|
-
actor.tenant_id = result.rows[0].tenant_id;
|
|
230
|
-
actor.actor_id = result.rows[0].user_id ?? 'cli-user';
|
|
231
|
-
actor.role = result.rows[0].role ?? 'owner';
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (!actor.tenant_id) {
|
|
236
|
-
const tenantResult = await db.query("SELECT id FROM tenants WHERE slug = 'default' LIMIT 1");
|
|
237
|
-
if (tenantResult.rows.length > 0) {
|
|
238
|
-
actor.tenant_id = tenantResult.rows[0].id;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const tools = getAllTools(db);
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
async call(toolName: string, input: Record<string, unknown>): Promise<string> {
|
|
246
|
-
const tool = tools.find(t => t.name === toolName);
|
|
247
|
-
if (!tool) throw new Error(`Unknown tool: ${toolName}`);
|
|
248
|
-
const result = await tool.handler(input, actor);
|
|
249
|
-
return JSON.stringify(result, null, 2);
|
|
250
|
-
},
|
|
251
|
-
async close() {
|
|
252
|
-
await closePool();
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Get a CLI client. Priority:
|
|
259
|
-
* 1. Direct DB (if DATABASE_URL or .crmy.json database.url is set)
|
|
260
|
-
* 2. HTTP client (if authenticated via `crmy login`)
|
|
261
|
-
*/
|
|
262
|
-
export async function getClient(): Promise<CliClient> {
|
|
263
|
-
const config = loadConfigFile();
|
|
264
|
-
const databaseUrl = process.env.DATABASE_URL ?? config.database?.url;
|
|
265
|
-
|
|
266
|
-
// Prefer direct DB connection when available
|
|
267
|
-
if (databaseUrl) {
|
|
268
|
-
const apiKey = process.env.CRMY_API_KEY ?? config.apiKey;
|
|
269
|
-
return createDbClient(databaseUrl, apiKey);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Fall back to HTTP client if authenticated
|
|
273
|
-
const auth = loadAuthState();
|
|
274
|
-
if (auth) {
|
|
275
|
-
return createHttpClient(auth.serverUrl, auth.token);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Also check for server URL + API key (headless mode)
|
|
279
|
-
const serverUrl = process.env.CRMY_SERVER_URL ?? config.serverUrl;
|
|
280
|
-
const apiKey = process.env.CRMY_API_KEY ?? config.apiKey;
|
|
281
|
-
if (serverUrl && apiKey) {
|
|
282
|
-
return createHttpClient(serverUrl, apiKey);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
console.error('Not connected. Run `crmy auth setup` and `crmy login`, or `crmy init` for local mode.');
|
|
286
|
-
process.exit(1);
|
|
287
|
-
}
|
package/src/commands/accounts.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 CRMy Contributors
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import { Command } from 'commander';
|
|
5
|
-
import { getClient } from '../client.js';
|
|
6
|
-
|
|
7
|
-
export function accountsCommand(): Command {
|
|
8
|
-
const cmd = new Command('accounts').description('Manage accounts');
|
|
9
|
-
|
|
10
|
-
cmd.command('list')
|
|
11
|
-
.option('-q, --query <query>', 'Search query')
|
|
12
|
-
.action(async (opts) => {
|
|
13
|
-
const client = await getClient();
|
|
14
|
-
const result = await client.call('account_search', { query: opts.query, limit: 20 });
|
|
15
|
-
const data = JSON.parse(result);
|
|
16
|
-
if (data.accounts?.length === 0) {
|
|
17
|
-
console.log('No accounts found.');
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
console.table(data.accounts?.map((a: Record<string, unknown>) => ({
|
|
21
|
-
id: (a.id as string).slice(0, 8),
|
|
22
|
-
name: a.name,
|
|
23
|
-
industry: a.industry ?? '',
|
|
24
|
-
health: a.health_score ?? '',
|
|
25
|
-
})));
|
|
26
|
-
await client.close();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
cmd.command('get <id>')
|
|
30
|
-
.description('Get account details including contacts and open opportunities')
|
|
31
|
-
.action(async (id) => {
|
|
32
|
-
const client = await getClient();
|
|
33
|
-
const result = await client.call('account_get', { id });
|
|
34
|
-
console.log(JSON.parse(result));
|
|
35
|
-
await client.close();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
cmd.command('create')
|
|
39
|
-
.description('Create a new account')
|
|
40
|
-
.action(async () => {
|
|
41
|
-
const { default: inquirer } = await import('inquirer');
|
|
42
|
-
const answers = await inquirer.prompt([
|
|
43
|
-
{ type: 'input', name: 'name', message: 'Account name:' },
|
|
44
|
-
{ type: 'input', name: 'domain', message: 'Domain (optional):' },
|
|
45
|
-
{ type: 'input', name: 'industry', message: 'Industry (optional):' },
|
|
46
|
-
{ type: 'input', name: 'website', message: 'Website (optional):' },
|
|
47
|
-
]);
|
|
48
|
-
|
|
49
|
-
const client = await getClient();
|
|
50
|
-
const result = await client.call('account_create', {
|
|
51
|
-
name: answers.name,
|
|
52
|
-
domain: answers.domain || undefined,
|
|
53
|
-
industry: answers.industry || undefined,
|
|
54
|
-
website: answers.website || undefined,
|
|
55
|
-
});
|
|
56
|
-
const data = JSON.parse(result);
|
|
57
|
-
console.log(`\n Created account: ${data.account.id}\n`);
|
|
58
|
-
await client.close();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
cmd.command('delete <id>')
|
|
62
|
-
.description('Permanently delete an account (admin/owner only)')
|
|
63
|
-
.action(async (id) => {
|
|
64
|
-
const { default: inquirer } = await import('inquirer');
|
|
65
|
-
const { confirm } = await inquirer.prompt([
|
|
66
|
-
{ type: 'confirm', name: 'confirm', message: `Delete account ${id}? This cannot be undone.`, default: false },
|
|
67
|
-
]);
|
|
68
|
-
if (!confirm) { console.log(' Cancelled.'); return; }
|
|
69
|
-
|
|
70
|
-
const client = await getClient();
|
|
71
|
-
const result = await client.call('account_delete', { id });
|
|
72
|
-
const data = JSON.parse(result);
|
|
73
|
-
if (data.deleted) console.log(` Deleted.`);
|
|
74
|
-
await client.close();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return cmd;
|
|
78
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import { getClient } from '../client.js';
|
|
5
|
-
|
|
6
|
-
export function activityTypesCommand(): Command {
|
|
7
|
-
const cmd = new Command('activity-types').description('Manage activity type registry');
|
|
8
|
-
|
|
9
|
-
cmd.command('list')
|
|
10
|
-
.option('--category <cat>', 'Filter by category (outreach, meeting, proposal, contract, internal, lifecycle, handoff)')
|
|
11
|
-
.action(async (opts) => {
|
|
12
|
-
const client = await getClient();
|
|
13
|
-
const result = await client.call('activity_type_list', { category: opts.category });
|
|
14
|
-
const data = JSON.parse(result);
|
|
15
|
-
if (data.activity_types?.length === 0) {
|
|
16
|
-
console.log('No activity types found.');
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
console.table(data.activity_types?.map((t: Record<string, unknown>) => ({
|
|
20
|
-
type_name: t.type_name,
|
|
21
|
-
label: t.label,
|
|
22
|
-
category: t.category,
|
|
23
|
-
default: t.is_default ? '✓' : '',
|
|
24
|
-
})));
|
|
25
|
-
await client.close();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
cmd.command('add <type_name>')
|
|
29
|
-
.requiredOption('--label <label>', 'Display label')
|
|
30
|
-
.requiredOption('--category <category>', 'Category')
|
|
31
|
-
.option('--description <desc>', 'Description')
|
|
32
|
-
.action(async (typeName, opts) => {
|
|
33
|
-
const client = await getClient();
|
|
34
|
-
const result = await client.call('activity_type_add', {
|
|
35
|
-
type_name: typeName,
|
|
36
|
-
label: opts.label,
|
|
37
|
-
category: opts.category,
|
|
38
|
-
description: opts.description,
|
|
39
|
-
});
|
|
40
|
-
const data = JSON.parse(result);
|
|
41
|
-
console.log(`\n Added activity type: ${data.activity_type.type_name}\n`);
|
|
42
|
-
await client.close();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
cmd.command('remove <type_name>')
|
|
46
|
-
.description('Remove a custom activity type (cannot remove defaults)')
|
|
47
|
-
.action(async (typeName) => {
|
|
48
|
-
const client = await getClient();
|
|
49
|
-
try {
|
|
50
|
-
await client.call('activity_type_remove', { type_name: typeName });
|
|
51
|
-
console.log(`\n Removed activity type: ${typeName}\n`);
|
|
52
|
-
} catch (err) {
|
|
53
|
-
console.error(`\n Error: ${err instanceof Error ? err.message : err}\n`);
|
|
54
|
-
}
|
|
55
|
-
await client.close();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
return cmd;
|
|
59
|
-
}
|
package/src/commands/actors.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 CRMy Contributors
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import { Command } from 'commander';
|
|
5
|
-
import { getClient } from '../client.js';
|
|
6
|
-
|
|
7
|
-
export function actorsCommand(): Command {
|
|
8
|
-
const cmd = new Command('actors').description('Manage actors (humans & agents)');
|
|
9
|
-
|
|
10
|
-
cmd.command('list')
|
|
11
|
-
.option('--type <type>', 'Filter by actor_type (human or agent)')
|
|
12
|
-
.option('-q, --query <query>', 'Search query')
|
|
13
|
-
.action(async (opts) => {
|
|
14
|
-
const client = await getClient();
|
|
15
|
-
const result = await client.call('actor_list', {
|
|
16
|
-
actor_type: opts.type,
|
|
17
|
-
query: opts.query,
|
|
18
|
-
limit: 20,
|
|
19
|
-
});
|
|
20
|
-
const data = JSON.parse(result);
|
|
21
|
-
if (data.actors?.length === 0) {
|
|
22
|
-
console.log('No actors found.');
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
console.table(data.actors?.map((a: Record<string, unknown>) => ({
|
|
26
|
-
id: (a.id as string).slice(0, 8),
|
|
27
|
-
type: a.actor_type,
|
|
28
|
-
name: a.display_name,
|
|
29
|
-
email: a.email ?? '',
|
|
30
|
-
agent_id: a.agent_identifier ?? '',
|
|
31
|
-
active: a.is_active ? '✓' : '✗',
|
|
32
|
-
})));
|
|
33
|
-
if (data.total > 20) console.log(`\n Showing 20 of ${data.total} actors`);
|
|
34
|
-
await client.close();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
cmd.command('register')
|
|
38
|
-
.description('Register a new actor')
|
|
39
|
-
.action(async () => {
|
|
40
|
-
const { default: inquirer } = await import('inquirer');
|
|
41
|
-
const answers = await inquirer.prompt([
|
|
42
|
-
{ type: 'list', name: 'actor_type', message: 'Actor type:', choices: ['human', 'agent'] },
|
|
43
|
-
{ type: 'input', name: 'display_name', message: 'Display name:' },
|
|
44
|
-
{ type: 'input', name: 'email', message: 'Email (for humans):' },
|
|
45
|
-
{ type: 'input', name: 'agent_identifier', message: 'Agent identifier (for agents):' },
|
|
46
|
-
{ type: 'input', name: 'agent_model', message: 'Agent model (e.g. claude-sonnet-4-20250514):' },
|
|
47
|
-
]);
|
|
48
|
-
|
|
49
|
-
const client = await getClient();
|
|
50
|
-
const result = await client.call('actor_register', {
|
|
51
|
-
actor_type: answers.actor_type,
|
|
52
|
-
display_name: answers.display_name,
|
|
53
|
-
email: answers.email || undefined,
|
|
54
|
-
agent_identifier: answers.agent_identifier || undefined,
|
|
55
|
-
agent_model: answers.agent_model || undefined,
|
|
56
|
-
});
|
|
57
|
-
const data = JSON.parse(result);
|
|
58
|
-
console.log(`\n Registered actor: ${data.actor.id}\n`);
|
|
59
|
-
await client.close();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
cmd.command('get <id>')
|
|
63
|
-
.action(async (id) => {
|
|
64
|
-
const client = await getClient();
|
|
65
|
-
const result = await client.call('actor_get', { id });
|
|
66
|
-
console.log(JSON.parse(result));
|
|
67
|
-
await client.close();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
cmd.command('whoami')
|
|
71
|
-
.description('Show current actor identity')
|
|
72
|
-
.action(async () => {
|
|
73
|
-
const client = await getClient();
|
|
74
|
-
const result = await client.call('actor_whoami', {});
|
|
75
|
-
console.log(JSON.parse(result));
|
|
76
|
-
await client.close();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
return cmd;
|
|
80
|
-
}
|