@startanaicompany/crm 1.0.0 → 1.0.2

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 (3) hide show
  1. package/README.md +90 -13
  2. package/index.js +104 -34
  3. package/package.json +15 -2
package/README.md CHANGED
@@ -8,34 +8,70 @@ AI-first CRM CLI (`saac_crm`) — manage leads, API keys, and user accounts from
8
8
  npm install -g @startanaicompany/crm
9
9
  ```
10
10
 
11
- ## Configuration
11
+ ---
12
+
13
+ ## 🤖 AI Agent Quick Start (zero-auth, no human needed)
14
+
15
+ Agents bootstrap themselves with a single command — **no credentials required**:
12
16
 
13
17
  ```bash
14
- # Set API URL and default key
15
- saac_crm config set --url https://yourapp.example.com --api-key crm_xxxx
18
+ # Step 1: create your own API key (no auth needed)
19
+ saac_crm config set --url https://your-crm.example.com
20
+ saac_crm keys create --name my-agent
21
+ # → returns crm_xxxxxxxxxxxx — store this as your API key
22
+
23
+ # Step 2: set the key and start managing leads
24
+ export SAAC_CRM_API_KEY=crm_xxxxxxxxxxxx
25
+ saac_crm leads create --name "Jane Smith" --email "jane@example.com"
26
+ saac_crm leads list
27
+ ```
28
+
29
+ > **Auth priority**: `--api-key` flag > `SAAC_CRM_API_KEY` env var > config file (`~/.saac_crm/config.json`)
30
+
31
+ > ⚠️ **Agents: never call `saac_crm register`** — that command is for one-time operator setup only (requires deployment password). Your entry point is always `keys create --name`.
32
+
33
+ ---
34
+
35
+ ## 👤 Operator Setup (one-time, human)
36
+
37
+ This is done once per deployment — **not by agents**:
16
38
 
17
- # Auth priority: --api-key flag > SAAC_CRM_API_KEY env > config file
18
- export SAAC_CRM_API_KEY=crm_xxxx
39
+ ```bash
40
+ # 1. Point at your deployment
41
+ saac_crm config set --url https://your-crm.example.com
42
+
43
+ # 2. Register workspace + create admin key (prompts for workspace slug and password)
44
+ saac_crm register --name "main-admin-key"
45
+ # → prompts: Workspace slug (e.g. mycompany):
46
+ # → prompts: Deployment admin password:
47
+ # → returns crm_xxx admin key — store securely
48
+
49
+ # 3. Save admin key and create first human user account
50
+ saac_crm config set --api-key crm_xxx_admin_key
51
+ saac_crm users create --email admin@example.com --name "Admin" --role admin
19
52
  ```
20
53
 
54
+ ---
55
+
21
56
  ## Commands
22
57
 
23
58
  ### API Keys
24
59
 
25
60
  ```bash
26
- # Create an agent key (no auth required)
61
+ # Create agent key — ZERO AUTH, no prior setup needed
27
62
  saac_crm keys create --name "my-agent"
28
63
 
29
- # Create an admin scope key (requires deployment password)
30
- saac_crm keys create --name "admin-agent" --scope admin --admin-password <password>
64
+ # Create admin scope key (operator only — requires deployment password + workspace slug)
65
+ saac_crm keys create --name "admin-key" --scope admin \
66
+ --workspace mycompany --admin-password <deployment-password>
31
67
 
32
- # List all keys
68
+ # List all keys (metadata only — full key never shown again)
33
69
  saac_crm keys list
34
70
 
35
71
  # Show current key info
36
72
  saac_crm keys self
37
73
 
38
- # Revoke a key
74
+ # Revoke a key by ID
39
75
  saac_crm keys revoke <id>
40
76
  ```
41
77
 
@@ -54,17 +90,18 @@ saac_crm leads get <id>
54
90
  # Update lead
55
91
  saac_crm leads update <id> --status contacted --notes "Called, interested"
56
92
 
57
- # Delete lead (soft)
93
+ # Soft-delete lead
58
94
  saac_crm leads delete <id>
59
95
 
60
- # Status history
96
+ # Status change history
61
97
  saac_crm leads history <id>
62
98
  ```
63
99
 
64
100
  ### Users (requires admin scope key)
65
101
 
66
102
  ```bash
67
- # Create a human user account
103
+ # Create a human user account (both commands are equivalent)
104
+ saac_crm users create --email admin@example.com --name "Admin User" --role admin
68
105
  saac_crm users register --email admin@example.com --name "Admin User" --role admin
69
106
 
70
107
  # List users
@@ -76,3 +113,43 @@ saac_crm users update <id> --role viewer
76
113
  # Deactivate user
77
114
  saac_crm users deactivate <id>
78
115
  ```
116
+
117
+ ### Human Login
118
+
119
+ ```bash
120
+ # Log in as admin/viewer — saves JWT to config for dashboard access
121
+ saac_crm login --workspace mycompany --email admin@example.com
122
+ # → prompts for password, saves JWT to ~/.saac_crm/config.json
123
+ ```
124
+
125
+ ### Workspace Registration (operator only)
126
+
127
+ ```bash
128
+ # First-time setup — registers workspace slug + creates admin key
129
+ # Prompts for --workspace and --admin-password if not provided
130
+ saac_crm register --name "main-admin-key"
131
+ saac_crm register --name "main-admin-key" --workspace mycompany --admin-password <pw>
132
+ ```
133
+
134
+ ### Configuration
135
+
136
+ ```bash
137
+ saac_crm config set --url https://your-crm.example.com
138
+ saac_crm config set --api-key crm_xxxxxxxxxxxx
139
+ saac_crm config get
140
+ ```
141
+
142
+ ## Global Options
143
+
144
+ ```
145
+ --api-key <key> Override API key for this command
146
+ --url <url> Override API base URL for this command
147
+ ```
148
+
149
+ ## Architecture
150
+
151
+ This is a **single-tenant** system: one workspace slug per deployment, registered once by an operator.
152
+
153
+ - **Agents** → self-service `keys create` → manage leads
154
+ - **Operators** → `register` once → `users create` → human accounts
155
+ - **Humans** → `login` → web dashboard at your deployment URL
package/index.js CHANGED
@@ -99,7 +99,7 @@ const program = new Command();
99
99
  program
100
100
  .name('saac_crm')
101
101
  .description('AI-first CRM CLI — manage leads and API keys')
102
- .version('1.0.0')
102
+ .version('1.0.2')
103
103
  .option('--api-key <key>', 'API key (overrides SAAC_CRM_API_KEY env and config)')
104
104
  .option('--url <url>', 'API base URL (overrides config)');
105
105
 
@@ -115,9 +115,14 @@ configCmd
115
115
  .option('--url <url>', 'CRM API base URL (e.g. https://yourapp.example.com)')
116
116
  .option('--api-key <key>', 'Default API key to use')
117
117
  .action((opts) => {
118
+ // NOTE: commander.js may consume --url and --api-key at the parent program level
119
+ // if they share the same option names. Always fall back to program.opts().
120
+ const globalOpts = program.opts();
118
121
  const cfg = loadConfig();
119
- if (opts.url) cfg.apiUrl = opts.url;
120
- if (opts.apiKey) cfg.apiKey = opts.apiKey;
122
+ const urlValue = opts.url || globalOpts.url;
123
+ const apiKeyValue = opts.apiKey || globalOpts.apiKey;
124
+ if (urlValue) cfg.apiUrl = urlValue;
125
+ if (apiKeyValue) cfg.apiKey = apiKeyValue;
121
126
  saveConfig(cfg);
122
127
  console.log('Configuration saved to', CONFIG_FILE);
123
128
  printJSON(cfg);
@@ -185,15 +190,15 @@ keysCmd
185
190
  });
186
191
 
187
192
  // ============================================================
188
- // REGISTER — shortcut for first admin setup
193
+ // REGISTER — first-time workspace + admin key setup
194
+ // NOTE: For agent self-service (zero-auth), use: saac_crm keys create --name <name>
189
195
  // ============================================================
190
196
 
191
197
  program
192
198
  .command('register')
193
- .description('Register this workspace and create an admin scope API key')
194
- .requiredOption('--workspace <slug>', 'Workspace slug (3-30 chars, a-z0-9 only, e.g. goldenrecruit101)')
199
+ .description('First-time setup: register workspace slug and create admin scope API key (prompts for missing values)')
195
200
  .requiredOption('--name <name>', 'Name/label for this admin key')
196
- .option('--scope <scope>', 'Key scope (must be admin)', 'admin')
201
+ .option('--workspace <slug>', 'Workspace slug (3-30 chars, a-z0-9 only, e.g. mycompany)')
197
202
  .option('--admin-password <password>', 'Deployment admin password (will prompt if not provided)')
198
203
  .action(async (opts) => {
199
204
  const globalOpts = program.opts();
@@ -203,6 +208,13 @@ program
203
208
  process.exit(1);
204
209
  }
205
210
 
211
+ // Prompt for workspace slug if not provided
212
+ let workspace = opts.workspace;
213
+ if (!workspace) {
214
+ workspace = await promptSecret('Workspace slug (e.g. mycompany): ');
215
+ }
216
+
217
+ // Prompt for admin password if not provided
206
218
  let adminPw = opts.adminPassword;
207
219
  if (!adminPw) {
208
220
  adminPw = await promptSecret('Deployment admin password: ');
@@ -211,7 +223,7 @@ program
211
223
  const body = {
212
224
  name: opts.name,
213
225
  scope: 'admin',
214
- workspace: opts.workspace,
226
+ workspace: workspace.trim().toLowerCase(),
215
227
  admin_password: adminPw
216
228
  };
217
229
 
@@ -221,7 +233,7 @@ program
221
233
  body,
222
234
  { headers: { 'Content-Type': 'application/json' } }
223
235
  );
224
- console.log(`Workspace '${opts.workspace}' registered. Admin key created — store it securely.`);
236
+ console.log(`Workspace '${workspace}' registered. Admin key created — store it securely.`);
225
237
  printJSON(res.data.data);
226
238
  } catch (err) {
227
239
  handleError(err);
@@ -310,7 +322,7 @@ leadsCmd
310
322
  };
311
323
  try {
312
324
  const res = await client.post('/leads', body, { headers });
313
- printJSON(res.data.data);
325
+ printJSON(res.data);
314
326
  } catch (err) {
315
327
  handleError(err);
316
328
  }
@@ -323,15 +335,20 @@ leadsCmd
323
335
  .option('--email <email>', 'Filter by email (partial match)')
324
336
  .option('--company <company>', 'Filter by company (partial match)')
325
337
  .option('--tag <tag>', 'Filter by tag — repeatable, AND logic', (v, prev) => prev.concat([v]), [])
338
+ .option('--api-key-id <id>', 'Filter by the API key ID that created the lead')
339
+ .option('--self', 'Filter leads created by the current API key (shorthand for --api-key-id self)')
326
340
  .option('--page <n>', 'Page number', '1')
327
341
  .option('--per-page <n>', 'Results per page', '20')
328
342
  .action(async (opts) => {
329
343
  const globalOpts = program.opts();
330
344
  const client = getClient(globalOpts);
345
+ // --self is a shorthand for --api-key-id self
346
+ const apiKeyIdFilter = opts.self ? 'self' : opts.apiKeyId;
331
347
  const params = {
332
348
  ...(opts.status && { status: opts.status }),
333
349
  ...(opts.email && { email: opts.email }),
334
350
  ...(opts.company && { company: opts.company }),
351
+ ...(apiKeyIdFilter && { api_key_id: apiKeyIdFilter }),
335
352
  page: opts.page,
336
353
  per_page: opts.perPage
337
354
  };
@@ -354,7 +371,7 @@ leadsCmd
354
371
  const client = getClient(globalOpts);
355
372
  try {
356
373
  const res = await client.get(`/leads/${id}`);
357
- printJSON(res.data.data);
374
+ printJSON(res.data);
358
375
  } catch (err) {
359
376
  handleError(err);
360
377
  }
@@ -392,7 +409,7 @@ leadsCmd
392
409
  };
393
410
  try {
394
411
  const res = await client.put(`/leads/${id}`, body);
395
- printJSON(res.data.data);
412
+ printJSON(res.data);
396
413
  } catch (err) {
397
414
  handleError(err);
398
415
  }
@@ -433,35 +450,47 @@ leadsCmd
433
450
 
434
451
  const usersCmd = program.command('users').description('Manage human user accounts (requires admin scope key)');
435
452
 
453
+ // Shared action for users create / users register
454
+ async function createUserAction(opts) {
455
+ const globalOpts = program.opts();
456
+ const client = getClient(globalOpts);
457
+
458
+ let password = opts.password;
459
+ if (!password) {
460
+ password = await promptSecret('Password for new user: ');
461
+ }
462
+
463
+ try {
464
+ const res = await client.post('/users', {
465
+ email: opts.email,
466
+ name: opts.name,
467
+ password,
468
+ role: opts.role
469
+ });
470
+ console.log('User created successfully.');
471
+ printJSON(res.data.data);
472
+ } catch (err) {
473
+ handleError(err);
474
+ }
475
+ }
476
+
436
477
  usersCmd
437
- .command('register')
478
+ .command('create')
438
479
  .description('Create a new human user account')
439
480
  .requiredOption('--email <email>', 'User email address')
440
481
  .requiredOption('--name <name>', 'User full name')
441
482
  .option('--password <password>', 'User password (will prompt if not provided)')
442
483
  .option('--role <role>', 'Role: admin or viewer', 'viewer')
443
- .action(async (opts) => {
444
- const globalOpts = program.opts();
445
- const client = getClient(globalOpts);
446
-
447
- let password = opts.password;
448
- if (!password) {
449
- password = await promptSecret('Password for new user: ');
450
- }
484
+ .action(createUserAction);
451
485
 
452
- try {
453
- const res = await client.post('/users', {
454
- email: opts.email,
455
- name: opts.name,
456
- password,
457
- role: opts.role
458
- });
459
- console.log('User created successfully.');
460
- printJSON(res.data.data);
461
- } catch (err) {
462
- handleError(err);
463
- }
464
- });
486
+ usersCmd
487
+ .command('register')
488
+ .description('Create a new human user account (alias for users create)')
489
+ .requiredOption('--email <email>', 'User email address')
490
+ .requiredOption('--name <name>', 'User full name')
491
+ .option('--password <password>', 'User password (will prompt if not provided)')
492
+ .option('--role <role>', 'Role: admin or viewer', 'viewer')
493
+ .action(createUserAction);
465
494
 
466
495
  usersCmd
467
496
  .command('list')
@@ -518,4 +547,45 @@ usersCmd
518
547
  }
519
548
  });
520
549
 
550
+ // ============================================================
551
+ // LOGIN — human admin/viewer login, saves JWT to config
552
+ // ============================================================
553
+
554
+ program
555
+ .command('login')
556
+ .description('Log in as a human admin/viewer (workspace + email + password → JWT saved to config)')
557
+ .requiredOption('--workspace <slug>', 'Workspace slug')
558
+ .requiredOption('--email <email>', 'Your email address')
559
+ .option('--password <password>', 'Your password (will prompt if not provided)')
560
+ .action(async (opts) => {
561
+ const globalOpts = program.opts();
562
+ const apiUrl = resolveApiUrl(globalOpts.url);
563
+ if (!apiUrl) {
564
+ console.error('Error: API URL not configured. Run: saac_crm config set --url <api-url>');
565
+ process.exit(1);
566
+ }
567
+
568
+ let password = opts.password;
569
+ if (!password) {
570
+ password = await promptSecret('Password: ');
571
+ }
572
+
573
+ try {
574
+ const res = await axios.post(
575
+ `${apiUrl.replace(/\/$/, '')}/api/v1/auth/login`,
576
+ { workspace: opts.workspace, email: opts.email, password },
577
+ { headers: { 'Content-Type': 'application/json' } }
578
+ );
579
+ const { token, user } = res.data.data;
580
+ // Save token to config for subsequent admin operations
581
+ const cfg = loadConfig();
582
+ cfg.token = token;
583
+ saveConfig(cfg);
584
+ console.log(`Logged in as ${user.email} (${user.role}). JWT saved to config.`);
585
+ printJSON({ email: user.email, name: user.name, role: user.role });
586
+ } catch (err) {
587
+ handleError(err);
588
+ }
589
+ });
590
+
521
591
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startanaicompany/crm",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "AI-first CRM CLI — manage leads and API keys from the terminal",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,8 +9,21 @@
9
9
  "scripts": {
10
10
  "test": "echo \"No tests yet\""
11
11
  },
12
- "keywords": ["crm", "ai", "leads", "api"],
12
+ "keywords": ["crm", "ai", "leads", "api", "saac", "agent"],
13
13
  "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://git.startanaicompany.com/mikaelwestoo/url-friendly-slug-59b2ag.git",
17
+ "directory": "packages/crm-cli"
18
+ },
19
+ "files": [
20
+ "index.js",
21
+ "README.md",
22
+ "package.json"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
14
27
  "dependencies": {
15
28
  "axios": "^1.6.0",
16
29
  "commander": "^11.1.0"