viviscape-mcp 2.0.2 → 2.2.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/README.md +43 -1
- package/dist/api-client.d.ts +151 -1
- package/dist/api-client.js +515 -71
- package/dist/auth/permissions.js +2 -0
- package/dist/enums.d.ts +48 -0
- package/dist/enums.js +67 -0
- package/dist/index.js +463 -34
- package/dist/projection.d.ts +47 -0
- package/dist/projection.js +132 -0
- package/package.json +1 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response shaping for list tools -- phase 2 of docs/AGENT-CAPABILITY-SPEC.md.
|
|
3
|
+
*
|
|
4
|
+
* The core API returns every column it has: a task row carries ~90 fields
|
|
5
|
+
* (including fleet columns like vin and unit_class) and a project row carries 87
|
|
6
|
+
* (including video-conference config and welcome messages). Six open tasks came
|
|
7
|
+
* back as 102 KB; 211 active projects as 487 KB, which no caller can afford to
|
|
8
|
+
* read. These helpers trim to a PM-relevant default, page the result, and tell
|
|
9
|
+
* the caller what was left out so it can ask for more deliberately.
|
|
10
|
+
*/
|
|
11
|
+
/** Task fields worth returning by default, of roughly 90 available. */
|
|
12
|
+
export const TASK_FIELDS = [
|
|
13
|
+
'task_id', 'task', 'status', 'percentage', 'priority',
|
|
14
|
+
'company_id', 'company_name', 'group_id', 'group_name',
|
|
15
|
+
'milestone_id', 'milestone', 'estimated_time', 'total_duration_str',
|
|
16
|
+
'deadline', 'start', 'end', 'staff_assignees', 'total_comments', 'unread_for_me',
|
|
17
|
+
];
|
|
18
|
+
/** Project (group) fields worth returning by default, of 87 available. */
|
|
19
|
+
export const PROJECT_FIELDS = [
|
|
20
|
+
'intGroupId', 'strGroupName', 'intCompanyId', 'strCompany',
|
|
21
|
+
'status', 'bitActive', 'group_type', 'percentage',
|
|
22
|
+
'total_tasks', 'total_tasks_open', 'total_tasks_completed',
|
|
23
|
+
'target_date', 'complete_date', 'total_estimated_str', 'total_duration_str',
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Note fields worth returning by default. Excludes `note` (the full body) and
|
|
27
|
+
* `firefly_transcript_id`: the account list runs to hundreds of rows and the
|
|
28
|
+
* bodies dominate the payload. Ask for fields:["note"] to read one.
|
|
29
|
+
*/
|
|
30
|
+
export const NOTE_FIELDS = [
|
|
31
|
+
'note_id', 'title', 'book_id', 'creator', 'creator_id',
|
|
32
|
+
'summary', 'tags', 'assigned_users', 'knowledge', 'is_public',
|
|
33
|
+
'last_update', 'create_date',
|
|
34
|
+
];
|
|
35
|
+
/** Project/task file fields worth returning by default. */
|
|
36
|
+
export const FILE_FIELDS = [
|
|
37
|
+
'file_id', 'group_id', 'task_id', 'title', 'file_name', 'file_type',
|
|
38
|
+
'file_size_friendly', 'author', 'user_id', 'post_date', 'url_path',
|
|
39
|
+
'is_public', 'is_private',
|
|
40
|
+
];
|
|
41
|
+
export const DEFAULT_LIMIT = 50;
|
|
42
|
+
const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
43
|
+
/** Parse the staff_assignees blob, which arrives as JSON text or as an array. */
|
|
44
|
+
function assigneeIds(row) {
|
|
45
|
+
const raw = row.staff_assignees;
|
|
46
|
+
let arr = raw;
|
|
47
|
+
if (typeof raw === 'string') {
|
|
48
|
+
try {
|
|
49
|
+
arr = JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!Array.isArray(arr))
|
|
56
|
+
return [];
|
|
57
|
+
return arr
|
|
58
|
+
.map((u) => (isRecord(u) ? Number(u.user_id) : Number(u)))
|
|
59
|
+
.filter((n) => Number.isFinite(n));
|
|
60
|
+
}
|
|
61
|
+
const lower = (v) => String(v ?? '').toLowerCase();
|
|
62
|
+
/** Apply the filters the backend does not support, in-process. */
|
|
63
|
+
export function filterTasks(rows, f) {
|
|
64
|
+
return rows.filter((row) => {
|
|
65
|
+
if (!isRecord(row))
|
|
66
|
+
return true;
|
|
67
|
+
if (f.company_id && Number(row.company_id) !== f.company_id)
|
|
68
|
+
return false;
|
|
69
|
+
if (f.project_id && Number(row.group_id) !== f.project_id)
|
|
70
|
+
return false;
|
|
71
|
+
if (f.status && lower(row.status) !== f.status.toLowerCase())
|
|
72
|
+
return false;
|
|
73
|
+
if (f.priority && lower(row.priority) !== f.priority.toLowerCase())
|
|
74
|
+
return false;
|
|
75
|
+
if (f.assignee_id && !assigneeIds(row).includes(f.assignee_id))
|
|
76
|
+
return false;
|
|
77
|
+
if (f.search) {
|
|
78
|
+
const hay = `${lower(row.task)} ${lower(row.description)} ${lower(row.notes)}`;
|
|
79
|
+
if (!hay.includes(f.search.toLowerCase()))
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
// `end` is the task's own due date; `deadline` is a human string ("In 2 Days").
|
|
83
|
+
if (f.due_before || f.due_after) {
|
|
84
|
+
const due = Date.parse(String(row.end ?? ''));
|
|
85
|
+
if (!Number.isFinite(due))
|
|
86
|
+
return false;
|
|
87
|
+
if (f.due_before && due > Date.parse(f.due_before))
|
|
88
|
+
return false;
|
|
89
|
+
if (f.due_after && due < Date.parse(f.due_after))
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function pick(row, fields) {
|
|
96
|
+
if (!isRecord(row))
|
|
97
|
+
return row;
|
|
98
|
+
const out = {};
|
|
99
|
+
for (const f of fields)
|
|
100
|
+
if (f in row)
|
|
101
|
+
out[f] = row[f];
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Trim, page, and annotate a list response. Non-array payloads (a single record,
|
|
106
|
+
* or an error object) pass through untouched.
|
|
107
|
+
*
|
|
108
|
+
* Pass fields: ['all'] to opt out of projection and get every column.
|
|
109
|
+
*/
|
|
110
|
+
export function shape(payload, defaults, opts = {}) {
|
|
111
|
+
if (!Array.isArray(payload))
|
|
112
|
+
return payload;
|
|
113
|
+
const total = payload.length;
|
|
114
|
+
const offset = Math.max(0, opts.offset ?? 0);
|
|
115
|
+
const limit = Math.max(1, opts.limit ?? DEFAULT_LIMIT);
|
|
116
|
+
const page = payload.slice(offset, offset + limit);
|
|
117
|
+
const all = opts.fields?.length === 1 && opts.fields[0] === 'all';
|
|
118
|
+
const fields = all ? null : (opts.fields?.length ? opts.fields : defaults);
|
|
119
|
+
const items = fields ? page.map((r) => pick(r, fields)) : page;
|
|
120
|
+
const available = isRecord(payload[0]) ? Object.keys(payload[0]).length : 0;
|
|
121
|
+
const next = offset + page.length;
|
|
122
|
+
return {
|
|
123
|
+
total,
|
|
124
|
+
returned: page.length,
|
|
125
|
+
offset,
|
|
126
|
+
...(next < total ? { next_offset: next } : {}),
|
|
127
|
+
fields: fields
|
|
128
|
+
? `${fields.length} of ${available} columns; pass fields:["all"] or a specific list for more`
|
|
129
|
+
: `all ${available} columns`,
|
|
130
|
+
items,
|
|
131
|
+
};
|
|
132
|
+
}
|
package/package.json
CHANGED