@docyrus/docyrus 0.0.25 → 0.0.27
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 +41 -1
- package/agent-loader.js +249 -45
- package/agent-loader.js.map +3 -3
- package/main.js +499 -259
- package/main.js.map +4 -4
- package/package.json +2 -1
- package/resources/pi-agent/extensions/architect.ts +18 -34
- package/resources/pi-agent/extensions/plan.ts +1197 -0
- package/resources/pi-agent/shared/askUserProtocol.ts +248 -0
- package/resources/pi-agent/skills/docyrus-platform/SKILL.md +1 -1
- package/resources/pi-agent/skills/docyrus-platform/references/developer-tools.md +1 -1
- package/resources/pi-agent/skills/docyrus-platform/references/docyrus-cli-usage.md +46 -0
- package/resources/pi-agent/skills/docyrus-platform/references/querying-and-data-operations.md +7 -0
- package/server-loader.js +1647 -61
- package/server-loader.js.map +4 -4
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
export const ASK_USER_TAG = "ask_user";
|
|
2
|
+
export const ASK_USER_OPEN = `<${ASK_USER_TAG}>`;
|
|
3
|
+
export const ASK_USER_CLOSE = `</${ASK_USER_TAG}>`;
|
|
4
|
+
export const ASK_USER_RESPONSE_TAG = "ask_user_response";
|
|
5
|
+
export const ASK_USER_RESPONSE_OPEN = `<${ASK_USER_RESPONSE_TAG}>`;
|
|
6
|
+
export const ASK_USER_RESPONSE_CLOSE = `</${ASK_USER_RESPONSE_TAG}>`;
|
|
7
|
+
|
|
8
|
+
export type IAskUserQuestionType = "text" | "textarea" | "boolean" | "singleSelect" | "multiSelect";
|
|
9
|
+
export type IAskUserAnswerValue = string | boolean | string[];
|
|
10
|
+
|
|
11
|
+
export interface IAskUserOption {
|
|
12
|
+
label: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IAskUserQuestion {
|
|
17
|
+
id: string;
|
|
18
|
+
label: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
type: IAskUserQuestionType;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
options?: IAskUserOption[];
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
defaultValue?: IAskUserAnswerValue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface IAskUserRequest {
|
|
28
|
+
title: string;
|
|
29
|
+
message: string;
|
|
30
|
+
questions: IAskUserQuestion[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface IAskUserResponse {
|
|
34
|
+
action: "submit" | "cancel" | "decline";
|
|
35
|
+
answers: Record<string, IAskUserAnswerValue>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hashString(value: string): string {
|
|
39
|
+
let hash = 0;
|
|
40
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
41
|
+
hash = ((hash << 5) - hash) + value.charCodeAt(index);
|
|
42
|
+
hash |= 0;
|
|
43
|
+
}
|
|
44
|
+
return Math.abs(hash).toString(36);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
48
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
52
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeTrimmedString(value: unknown): string | undefined {
|
|
56
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeQuestionType(value: unknown): IAskUserQuestionType | undefined {
|
|
60
|
+
return value === "text" ||
|
|
61
|
+
value === "textarea" ||
|
|
62
|
+
value === "boolean" ||
|
|
63
|
+
value === "singleSelect" ||
|
|
64
|
+
value === "multiSelect"
|
|
65
|
+
? value
|
|
66
|
+
: undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeDefaultValue(value: unknown): IAskUserAnswerValue | undefined {
|
|
70
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
|
|
74
|
+
return [...value];
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeOptions(value: unknown): IAskUserOption[] | undefined {
|
|
80
|
+
if (!Array.isArray(value)) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const options: IAskUserOption[] = [];
|
|
85
|
+
for (const item of value) {
|
|
86
|
+
if (!isRecord(item) || !isNonEmptyString(item.label) || !isNonEmptyString(item.value)) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
options.push({
|
|
90
|
+
label: item.label.trim(),
|
|
91
|
+
value: item.value.trim(),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return options;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function normalizeAskUserQuestion(value: unknown): IAskUserQuestion | undefined {
|
|
99
|
+
if (!isRecord(value)) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const id = normalizeTrimmedString(value.id);
|
|
104
|
+
const label = normalizeTrimmedString(value.label);
|
|
105
|
+
const type = normalizeQuestionType(value.type);
|
|
106
|
+
if (!id || !label || !type) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const question: IAskUserQuestion = {
|
|
111
|
+
id,
|
|
112
|
+
label,
|
|
113
|
+
type,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const description = normalizeTrimmedString(value.description);
|
|
117
|
+
if (description) {
|
|
118
|
+
question.description = description;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof value.required === "boolean") {
|
|
122
|
+
question.required = value.required;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const options = normalizeOptions(value.options);
|
|
126
|
+
if (type === "singleSelect" || type === "multiSelect") {
|
|
127
|
+
if (!options || options.length === 0) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
question.options = options;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const placeholder = normalizeTrimmedString(value.placeholder);
|
|
134
|
+
if (placeholder) {
|
|
135
|
+
question.placeholder = placeholder;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const defaultValue = normalizeDefaultValue(value.defaultValue);
|
|
139
|
+
if (defaultValue !== undefined) {
|
|
140
|
+
question.defaultValue = defaultValue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return question;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function normalizeAskUserRequest(value: unknown): IAskUserRequest | undefined {
|
|
147
|
+
if (!isRecord(value)) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const title = normalizeTrimmedString(value.title);
|
|
152
|
+
const message = normalizeTrimmedString(value.message);
|
|
153
|
+
if (!title || !message || !Array.isArray(value.questions) || value.questions.length === 0) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const questions: IAskUserQuestion[] = [];
|
|
158
|
+
for (const question of value.questions) {
|
|
159
|
+
const normalized = normalizeAskUserQuestion(question);
|
|
160
|
+
if (!normalized) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
questions.push(normalized);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
title,
|
|
168
|
+
message,
|
|
169
|
+
questions,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function normalizeAskUserResponse(value: unknown): IAskUserResponse | undefined {
|
|
174
|
+
if (!isRecord(value)) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const action = value.action;
|
|
179
|
+
if (action !== "submit" && action !== "cancel" && action !== "decline") {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const answersRecord = isRecord(value.answers) ? value.answers : {};
|
|
184
|
+
const answers: Record<string, IAskUserAnswerValue> = {};
|
|
185
|
+
for (const [key, answerValue] of Object.entries(answersRecord)) {
|
|
186
|
+
const normalized = normalizeDefaultValue(answerValue);
|
|
187
|
+
if (normalized !== undefined) {
|
|
188
|
+
answers[key] = normalized;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
action,
|
|
194
|
+
answers,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function buildAskUserProtocolInstructions(): string {
|
|
199
|
+
return [
|
|
200
|
+
"When clarification is required, do not ask free-form prose questions.",
|
|
201
|
+
`Instead, output only a single ${ASK_USER_OPEN}...${ASK_USER_CLOSE} block and nothing else in the assistant message.`,
|
|
202
|
+
"Inside that block, emit strict JSON with this shape:",
|
|
203
|
+
'{"title":"...","message":"...","questions":[{"id":"...","label":"...","description":"...","type":"text|textarea|boolean|singleSelect|multiSelect","required":true,"options":[{"label":"...","value":"..."}],"placeholder":"...","defaultValue":"..."}]}',
|
|
204
|
+
"Use batch questions when possible, keep ids stable, and limit the batch to the minimum needed to continue planning.",
|
|
205
|
+
].join("\n");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function serializeAskUserRequest(request: IAskUserRequest): string {
|
|
209
|
+
return `${ASK_USER_OPEN}\n${JSON.stringify(request, null, 2)}\n${ASK_USER_CLOSE}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function createAskUserToolCallId(request: IAskUserRequest): string {
|
|
213
|
+
return `ask_user_${hashString(JSON.stringify(request))}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function parseAskUserRequestFromText(text: string): IAskUserRequest | undefined {
|
|
217
|
+
const trimmed = text.trim();
|
|
218
|
+
if (!trimmed.startsWith(ASK_USER_OPEN) || !trimmed.endsWith(ASK_USER_CLOSE)) {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const body = trimmed.slice(ASK_USER_OPEN.length, trimmed.length - ASK_USER_CLOSE.length).trim();
|
|
223
|
+
if (!body) {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
return normalizeAskUserRequest(JSON.parse(body));
|
|
229
|
+
} catch {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function formatAskUserResponsePrompt(response: IAskUserResponse): string {
|
|
235
|
+
return [
|
|
236
|
+
"The user submitted structured clarification answers.",
|
|
237
|
+
"",
|
|
238
|
+
`${ASK_USER_RESPONSE_OPEN}`,
|
|
239
|
+
JSON.stringify(response, null, 2),
|
|
240
|
+
`${ASK_USER_RESPONSE_CLOSE}`,
|
|
241
|
+
"",
|
|
242
|
+
"Continue the planning workflow using these answers.",
|
|
243
|
+
].join("\n");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function parseAskUserResponseFromToolOutput(output: unknown): IAskUserResponse | undefined {
|
|
247
|
+
return normalizeAskUserResponse(output);
|
|
248
|
+
}
|
|
@@ -24,7 +24,7 @@ For detailed specifications of each building block, see [references/core-buildin
|
|
|
24
24
|
|
|
25
25
|
### Querying & Data Operations
|
|
26
26
|
|
|
27
|
-
Unified query engine with column selection, 50+ filter operators, aggregations, formulas, pivots, child queries, and full-text search. Full CRUD with bulk operations.
|
|
27
|
+
Unified query engine with column selection, 50+ filter operators, aggregations, formulas, pivots, child queries, and full-text search. Full CRUD with bulk operations, record comments, and file attachments.
|
|
28
28
|
|
|
29
29
|
See [references/querying-and-data-operations.md](references/querying-and-data-operations.md).
|
|
30
30
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
## CLI
|
|
15
15
|
|
|
16
16
|
- Full-featured CLI (`@docyrus/docyrus`) for terminal and AI agent use
|
|
17
|
-
- Commands: environment management, authentication, data operations, schema management (studio), app management, API discovery, AI chat, and direct API requests
|
|
17
|
+
- Commands: environment management, authentication, data operations, record comments, record file attachments, schema management (studio), app management, API discovery, AI chat, and direct API requests
|
|
18
18
|
- Multi-account, multi-tenant session management
|
|
19
19
|
- OpenAPI discovery with caching and fallback generation
|
|
20
20
|
- Interactive TUI mode
|
|
@@ -251,6 +251,52 @@ Delete a data source item.
|
|
|
251
251
|
| `dataSourceSlug` | string | yes | Data source slug |
|
|
252
252
|
| `recordId` | string | yes | Record ID |
|
|
253
253
|
|
|
254
|
+
### `docyrus ds comments create <appSlug> <dataSourceSlug> <recordId>`
|
|
255
|
+
|
|
256
|
+
Create a record-scoped comment.
|
|
257
|
+
|
|
258
|
+
| Argument | Type | Required | Description |
|
|
259
|
+
|---|---|---|---|
|
|
260
|
+
| `appSlug` | string | yes | App slug |
|
|
261
|
+
| `dataSourceSlug` | string | yes | Data source slug |
|
|
262
|
+
| `recordId` | string | yes | Record ID |
|
|
263
|
+
|
|
264
|
+
| Option | Type | Description |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| `--message` | string | Comment message |
|
|
267
|
+
| `--data` | string | Full JSON payload for the comment DTO |
|
|
268
|
+
| `--fromFile` | string | Path to a JSON payload file |
|
|
269
|
+
| `--parentId` | string | Parent comment ID |
|
|
270
|
+
| `--assignedTo` | string | Assigned user ID |
|
|
271
|
+
| `--attachments` | string | JSON attachments payload |
|
|
272
|
+
| `--level` | number | Comment level |
|
|
273
|
+
| `--status` | number | Comment status |
|
|
274
|
+
| `--done` | boolean | Mark comment as done |
|
|
275
|
+
|
|
276
|
+
**Notes:**
|
|
277
|
+
- Use either `--message` or `--data` / `--fromFile`
|
|
278
|
+
- `--data` and `--fromFile` cannot be mixed with field-specific flags
|
|
279
|
+
|
|
280
|
+
### `docyrus ds files upload <appSlug> <dataSourceSlug> <recordId>`
|
|
281
|
+
|
|
282
|
+
Upload a record-scoped file attachment.
|
|
283
|
+
|
|
284
|
+
| Argument | Type | Required | Description |
|
|
285
|
+
|---|---|---|---|
|
|
286
|
+
| `appSlug` | string | yes | App slug |
|
|
287
|
+
| `dataSourceSlug` | string | yes | Data source slug |
|
|
288
|
+
| `recordId` | string | yes | Record ID |
|
|
289
|
+
|
|
290
|
+
| Option | Type | Description |
|
|
291
|
+
|---|---|---|
|
|
292
|
+
| `--file` | string | Path to the local file to upload |
|
|
293
|
+
| `--contentType` | string | Override the inferred MIME type |
|
|
294
|
+
| `--publicFile` | boolean | Store the file in the public tenant bucket |
|
|
295
|
+
|
|
296
|
+
**Notes:**
|
|
297
|
+
- Uploads use `multipart/form-data`
|
|
298
|
+
- Content type is inferred from the file extension when omitted
|
|
299
|
+
|
|
254
300
|
---
|
|
255
301
|
|
|
256
302
|
## discover — OpenAPI Discovery
|
package/resources/pi-agent/skills/docyrus-platform/references/querying-and-data-operations.md
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
- Bulk create, bulk update, bulk delete (batched for performance)
|
|
7
7
|
- Insert/update/delete with custom return value selection
|
|
8
8
|
|
|
9
|
+
## Record Comments & Files
|
|
10
|
+
|
|
11
|
+
- Record-scoped comments with create, list, fetch by ID, update, and delete operations
|
|
12
|
+
- Comment payloads can include threading (`parentId`), assignee targeting (`assignedTo`), attachments metadata, level, status, and done state
|
|
13
|
+
- Record-scoped file attachments with upload, list, fetch by ID, insert-without-upload, copy/move, and delete operations
|
|
14
|
+
- File uploads support multipart form data, public/private storage selection, and record association
|
|
15
|
+
|
|
9
16
|
## Query Engine
|
|
10
17
|
|
|
11
18
|
Every list/get call accepts a structured query payload:
|