@badgie/crm-cli 0.2.1 → 0.3.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.
- package/AGENTS.md +3 -2
- package/dist/bin.js +3 -4
- package/dist/bin.js.map +1 -1
- package/dist/commands/clients.js +15 -8
- package/dist/commands/clients.js.map +1 -1
- package/dist/commands/directory.js +38 -12
- package/dist/commands/directory.js.map +1 -1
- package/dist/commands/finance.js +17 -12
- package/dist/commands/finance.js.map +1 -1
- package/dist/commands/leads.js +105 -61
- package/dist/commands/leads.js.map +1 -1
- package/dist/commands/outbound.js +82 -28
- package/dist/commands/outbound.js.map +1 -1
- package/dist/commands/tasks.js +234 -69
- package/dist/commands/tasks.js.map +1 -1
- package/dist/commands/top-clients.js +43 -15
- package/dist/commands/top-clients.js.map +1 -1
- package/dist/core/bulk.js +68 -0
- package/dist/core/bulk.js.map +1 -0
- package/dist/core/errors.js +105 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/resolve.js +80 -0
- package/dist/core/resolve.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { shortError } from './errors.js';
|
|
3
|
+
export async function collectIds(input) {
|
|
4
|
+
const ids = new Set();
|
|
5
|
+
if (input.positional)
|
|
6
|
+
ids.add(input.positional.trim());
|
|
7
|
+
if (typeof input.ids === 'string') {
|
|
8
|
+
input.ids.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean).forEach((i) => ids.add(i));
|
|
9
|
+
}
|
|
10
|
+
if (typeof input.idsFile === 'string' && input.idsFile) {
|
|
11
|
+
const contents = await readFile(input.idsFile, 'utf8');
|
|
12
|
+
contents.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).forEach((i) => ids.add(i));
|
|
13
|
+
}
|
|
14
|
+
if (input.fromStdin) {
|
|
15
|
+
const stdin = await readStdin();
|
|
16
|
+
stdin.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).forEach((i) => ids.add(i));
|
|
17
|
+
}
|
|
18
|
+
return [...ids].filter((id) => id.length > 0);
|
|
19
|
+
}
|
|
20
|
+
async function readStdin() {
|
|
21
|
+
if (process.stdin.isTTY)
|
|
22
|
+
return '';
|
|
23
|
+
const chunks = [];
|
|
24
|
+
for await (const chunk of process.stdin) {
|
|
25
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
26
|
+
}
|
|
27
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
28
|
+
}
|
|
29
|
+
export async function runBulk(ids, fn, opts = {}) {
|
|
30
|
+
const label = opts.label ?? 'process';
|
|
31
|
+
if (opts.dryRun) {
|
|
32
|
+
process.stderr.write(`[dry-run] would ${label} ${ids.length} id${ids.length === 1 ? '' : 's'}:\n`);
|
|
33
|
+
for (const id of ids)
|
|
34
|
+
process.stderr.write(` ${id}\n`);
|
|
35
|
+
return { ok: [], failed: [] };
|
|
36
|
+
}
|
|
37
|
+
const concurrency = Math.max(1, Math.min(20, Number(opts.concurrency ?? 5)));
|
|
38
|
+
const result = { ok: [], failed: [] };
|
|
39
|
+
let index = 0;
|
|
40
|
+
async function worker() {
|
|
41
|
+
while (index < ids.length) {
|
|
42
|
+
const i = index++;
|
|
43
|
+
const id = ids[i];
|
|
44
|
+
try {
|
|
45
|
+
const data = await fn(id);
|
|
46
|
+
result.ok.push({ id, data });
|
|
47
|
+
opts.onProgress?.(id, true);
|
|
48
|
+
process.stderr.write(` ✓ ${id}\n`);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const msg = shortError(err);
|
|
52
|
+
result.failed.push({ id, error: msg });
|
|
53
|
+
opts.onProgress?.(id, false);
|
|
54
|
+
process.stderr.write(` ✗ ${id} — ${msg}\n`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
await Promise.all(Array.from({ length: concurrency }, worker));
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
export const bulkOptions = [
|
|
62
|
+
{ flag: '--ids <list>', description: 'comma/space-separated list of IDs' },
|
|
63
|
+
{ flag: '--ids-file <path>', description: 'newline-separated IDs from file' },
|
|
64
|
+
{ flag: '--stdin', description: 'read newline-separated IDs from stdin' },
|
|
65
|
+
{ flag: '--dry-run', description: 'show what would happen without writing' },
|
|
66
|
+
{ flag: '--concurrency <n>', description: 'parallel ops (default 5, max 20)' },
|
|
67
|
+
];
|
|
68
|
+
//# sourceMappingURL=bulk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk.js","sourceRoot":"","sources":["../../src/core/bulk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Rich error formatter for Supabase / PostgrestError / HTTP errors.
|
|
2
|
+
// Lives here (not bin.ts) so handlers can reuse it for per-row bulk error rendering.
|
|
3
|
+
const PG_HINTS = {
|
|
4
|
+
'23502': (c) => `missing required field${c ? `: --${toKebab(c)}` : ''}`,
|
|
5
|
+
'23503': (c) => `${c ?? 'reference'} points to a row that doesn't exist; verify the ID`,
|
|
6
|
+
'23505': (c) => `duplicate value${c ? ` for ${c}` : ''}; that record already exists`,
|
|
7
|
+
'23514': (c) => `value not allowed${c ? ` for ${c}` : ''}; check the column's check constraint / enum values`,
|
|
8
|
+
'22P02': (c) => `wrong data type${c ? ` for ${c}` : ''} (e.g. sending text where the column expects integer)`,
|
|
9
|
+
'42501': () => `permission denied — RLS policy rejected this. Verify auth with \`badgie-crm whoami\`, or set BADGIE_SERVICE_KEY for admin scripts`,
|
|
10
|
+
'42P01': () => `table not found — likely a typo in the query`,
|
|
11
|
+
'42703': () => `column not found — likely a typo`,
|
|
12
|
+
PGRST301: () => `JWT expired. Run \`badgie-crm login\` again`,
|
|
13
|
+
PGRST116: () => `no matching row (or more than one). If you queried by name, the value may be ambiguous`,
|
|
14
|
+
};
|
|
15
|
+
export function formatError(err) {
|
|
16
|
+
if (err instanceof Error) {
|
|
17
|
+
const hint = messageHint(err.message);
|
|
18
|
+
return hint ? `${err.message} | hint: ${hint}` : err.message;
|
|
19
|
+
}
|
|
20
|
+
if (err && typeof err === 'object') {
|
|
21
|
+
const e = err;
|
|
22
|
+
const parts = [];
|
|
23
|
+
const message = typeof e.message === 'string' ? e.message : '';
|
|
24
|
+
const details = typeof e.details === 'string' ? e.details : '';
|
|
25
|
+
const hintField = typeof e.hint === 'string' ? e.hint : '';
|
|
26
|
+
const code = typeof e.code === 'string' ? e.code : '';
|
|
27
|
+
if (message)
|
|
28
|
+
parts.push(message);
|
|
29
|
+
if (details)
|
|
30
|
+
parts.push(`details: ${details}`);
|
|
31
|
+
const hint = deriveHint(code, message, details) ?? hintField;
|
|
32
|
+
if (hint)
|
|
33
|
+
parts.push(`hint: ${hint}`);
|
|
34
|
+
if (code)
|
|
35
|
+
parts.push(`code: ${code}`);
|
|
36
|
+
if (parts.length > 0)
|
|
37
|
+
return parts.join(' | ');
|
|
38
|
+
try {
|
|
39
|
+
return JSON.stringify(err);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return String(err);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return String(err);
|
|
46
|
+
}
|
|
47
|
+
// One-line variant for bulk result tables: drop hints and details.
|
|
48
|
+
export function shortError(err) {
|
|
49
|
+
if (err instanceof Error)
|
|
50
|
+
return err.message;
|
|
51
|
+
if (err && typeof err === 'object') {
|
|
52
|
+
const e = err;
|
|
53
|
+
if (typeof e.message === 'string' && e.message)
|
|
54
|
+
return e.message;
|
|
55
|
+
try {
|
|
56
|
+
return JSON.stringify(err);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return String(err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return String(err);
|
|
63
|
+
}
|
|
64
|
+
function deriveHint(code, message, details) {
|
|
65
|
+
const col = extractColumn(message) ?? extractColumn(details);
|
|
66
|
+
if (code && PG_HINTS[code])
|
|
67
|
+
return PG_HINTS[code](col);
|
|
68
|
+
return messageHint(`${message} ${details}`);
|
|
69
|
+
}
|
|
70
|
+
function messageHint(text) {
|
|
71
|
+
const lower = text.toLowerCase();
|
|
72
|
+
if (lower.includes('jwt') && (lower.includes('expired') || lower.includes('invalid'))) {
|
|
73
|
+
return PG_HINTS.PGRST301();
|
|
74
|
+
}
|
|
75
|
+
if (lower.includes('invalid api key') || lower.includes('401 unauthorized')) {
|
|
76
|
+
return 'not authenticated. Run `badgie-crm login` or set BADGIE_SERVICE_KEY';
|
|
77
|
+
}
|
|
78
|
+
if (lower.includes('fetch failed') || lower.includes('enotfound') || lower.includes('econnrefused')) {
|
|
79
|
+
return 'network / DNS error. Check internet and BADGIE_SUPABASE_URL';
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
// Parses column names from Postgres error text. Handles:
|
|
84
|
+
// null value in column "foo" of relation ...
|
|
85
|
+
// Key (foo)=(bar) is not present in table ...
|
|
86
|
+
// column "foo" of relation ...
|
|
87
|
+
// duplicate key value violates unique constraint "tbl_foo_key"
|
|
88
|
+
function extractColumn(text) {
|
|
89
|
+
if (!text)
|
|
90
|
+
return undefined;
|
|
91
|
+
const quoted = /column "([a-z0-9_]+)"/i.exec(text);
|
|
92
|
+
if (quoted)
|
|
93
|
+
return quoted[1];
|
|
94
|
+
const keyed = /Key \(([a-z0-9_]+)\)=/i.exec(text);
|
|
95
|
+
if (keyed)
|
|
96
|
+
return keyed[1];
|
|
97
|
+
const inserting = /inserting NULL into column "?([a-z0-9_]+)"?/i.exec(text);
|
|
98
|
+
if (inserting)
|
|
99
|
+
return inserting[1];
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
function toKebab(s) {
|
|
103
|
+
return s.replace(/_/g, '-').toLowerCase();
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2
|
+
const RESOLVERS = {
|
|
3
|
+
team_member: {
|
|
4
|
+
table: 'team_members',
|
|
5
|
+
searchColumns: ['full_name', 'email'],
|
|
6
|
+
displayColumn: 'full_name',
|
|
7
|
+
suggest: (v) => `badgie-crm team list --search "${v}"`,
|
|
8
|
+
},
|
|
9
|
+
client: {
|
|
10
|
+
table: 'clients',
|
|
11
|
+
searchColumns: ['name', 'email'],
|
|
12
|
+
displayColumn: 'name',
|
|
13
|
+
suggest: (v) => `badgie-crm clients list --search "${v}"`,
|
|
14
|
+
},
|
|
15
|
+
lead: {
|
|
16
|
+
table: 'leads',
|
|
17
|
+
searchColumns: ['title'],
|
|
18
|
+
displayColumn: 'title',
|
|
19
|
+
suggest: (v) => `badgie-crm leads list --search "${v}"`,
|
|
20
|
+
},
|
|
21
|
+
lead_status: {
|
|
22
|
+
table: 'lead_statuses',
|
|
23
|
+
searchColumns: ['name'],
|
|
24
|
+
displayColumn: 'name',
|
|
25
|
+
suggest: () => `badgie-crm admin lead-statuses list`,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
// Cache lookups for the duration of a single CLI invocation so bulk operations
|
|
29
|
+
// don't hit the DB N times for the same name.
|
|
30
|
+
const cache = new Map();
|
|
31
|
+
export async function resolveRef(client, kind, value) {
|
|
32
|
+
if (UUID_RE.test(value))
|
|
33
|
+
return value;
|
|
34
|
+
const key = `${kind}:${value.toLowerCase()}`;
|
|
35
|
+
const hit = cache.get(key);
|
|
36
|
+
if (hit)
|
|
37
|
+
return hit;
|
|
38
|
+
const cfg = RESOLVERS[kind];
|
|
39
|
+
const escaped = value.replace(/[%,]/g, ' ').trim();
|
|
40
|
+
if (!escaped)
|
|
41
|
+
throw new Error(`Empty ${kind} reference`);
|
|
42
|
+
const orFilter = cfg.searchColumns.map((c) => `${c}.ilike.%${escaped}%`).join(',');
|
|
43
|
+
const { data, error } = await client
|
|
44
|
+
.from(cfg.table)
|
|
45
|
+
.select(`id, ${cfg.displayColumn}, ${cfg.searchColumns.join(', ')}`)
|
|
46
|
+
.or(orFilter)
|
|
47
|
+
.limit(6);
|
|
48
|
+
if (error)
|
|
49
|
+
throw error;
|
|
50
|
+
const rows = (data ?? []);
|
|
51
|
+
if (rows.length === 0) {
|
|
52
|
+
throw new Error(`No ${kind} matches "${value}". Try \`${cfg.suggest(value)}\` to list candidates.`);
|
|
53
|
+
}
|
|
54
|
+
// If there's an exact (case-insensitive) match on any search column, prefer it
|
|
55
|
+
// over ambiguity. Lets "Rita" win when there's also "Rita Perez" and "Rita L".
|
|
56
|
+
const lower = value.toLowerCase();
|
|
57
|
+
const exact = rows.filter((r) => cfg.searchColumns.some((c) => {
|
|
58
|
+
const v = r[c];
|
|
59
|
+
return typeof v === 'string' && v.toLowerCase() === lower;
|
|
60
|
+
}));
|
|
61
|
+
const finalRows = exact.length === 1 ? exact : rows;
|
|
62
|
+
if (finalRows.length > 1) {
|
|
63
|
+
const candidates = finalRows
|
|
64
|
+
.slice(0, 4)
|
|
65
|
+
.map((r) => ` ${r[cfg.displayColumn] ?? '(no name)'} (${r.id})`)
|
|
66
|
+
.join('\n');
|
|
67
|
+
throw new Error(`Ambiguous ${kind} "${value}" — ${finalRows.length} matches. Candidates:\n${candidates}\nUse the full name/email or paste the UUID.`);
|
|
68
|
+
}
|
|
69
|
+
const id = String(finalRows[0].id);
|
|
70
|
+
cache.set(key, id);
|
|
71
|
+
return id;
|
|
72
|
+
}
|
|
73
|
+
// Convenience wrapper: resolves only if the opts value is a non-empty string.
|
|
74
|
+
// Returns null when absent (mirroring what handlers want for nullable columns).
|
|
75
|
+
export async function maybeResolve(client, kind, value) {
|
|
76
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
77
|
+
return null;
|
|
78
|
+
return resolveRef(client, kind, value);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/core/resolve.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED