@quantod/qq 1.1.19 → 1.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/dist/commands.d.ts +10 -7
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +204 -60
- package/dist/commands.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +56 -4
- package/dist/db.js.map +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +43 -25
- package/dist/http.js.map +1 -1
- package/dist/index.js +25 -12
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +39 -40
- package/dist/mcp.js.map +1 -1
- package/dist/resources/chrome.md +3 -3
- package/dist/resources/guide.md +119 -116
- package/dist/sdk-templates/javascript.js +1 -1
- package/dist/sdk-templates/python.d.ts.map +1 -1
- package/dist/sdk-templates/python.js +5 -2
- package/dist/sdk-templates/python.js.map +1 -1
- package/package.json +1 -1
- package/src/commands.ts +196 -64
- package/src/db.ts +59 -4
- package/src/http.ts +46 -32
- package/src/index.ts +23 -14
- package/src/mcp.ts +40 -41
- package/src/resources/chrome.md +3 -3
- package/src/resources/guide.md +119 -116
- package/src/sdk-templates/javascript.ts +1 -1
- package/src/sdk-templates/python.ts +5 -2
package/src/index.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { dump } from 'js-yaml';
|
|
5
5
|
import {
|
|
6
6
|
makePipeline, listPipelines, deletePipeline, push, claim, release,
|
|
7
|
-
|
|
8
|
-
type FilterOptions, type PushOptions, type ClaimOptions, type ClaimStrategy, type PayloadFormat, type DuplicateMode, type BatchWriteFormat,
|
|
7
|
+
query, status, unstick, uploadFile, uploadData, backup, QQError,
|
|
8
|
+
type FilterOptions, type PushOptions, type ClaimOptions, type ClaimStrategy, type PayloadFormat, type DuplicateMode, type BatchWriteFormat, type PayloadSelect,
|
|
9
9
|
} from './commands.js';
|
|
10
10
|
|
|
11
11
|
function out(data: unknown): void {
|
|
@@ -112,7 +112,7 @@ program
|
|
|
112
112
|
program
|
|
113
113
|
.command('push <path> [id]')
|
|
114
114
|
.option('--priority <n>', 'item priority (higher = claimed first)', parseFloatArg, 0.0)
|
|
115
|
-
.option('--payload-format <yaml|json
|
|
115
|
+
.option('--payload-format <yaml|json>', 'payload parsing format: yaml (default) or json')
|
|
116
116
|
.action((path, id, opts) => {
|
|
117
117
|
try {
|
|
118
118
|
const { pipeline, stage = '' } = parsePath(path);
|
|
@@ -123,11 +123,11 @@ program
|
|
|
123
123
|
|
|
124
124
|
program
|
|
125
125
|
.command('claim <path> [id]')
|
|
126
|
-
.option('--
|
|
126
|
+
.option('--sort-order <priority|fifo|lifo|random>', 'claim order (default: priority)', 'priority')
|
|
127
127
|
.action((path, id, opts) => {
|
|
128
128
|
try {
|
|
129
129
|
const { pipeline, stage = '*' } = parsePath(path);
|
|
130
|
-
const claimOpts: ClaimOptions = { strategy: opts.
|
|
130
|
+
const claimOpts: ClaimOptions = { strategy: opts.sortOrder as ClaimStrategy };
|
|
131
131
|
const item = claim(pipeline, stage, id, claimOpts);
|
|
132
132
|
if (item === null) {
|
|
133
133
|
process.stdout.write('No items to claim\n');
|
|
@@ -143,7 +143,7 @@ program
|
|
|
143
143
|
.option('--target <stage>', 'move item to this stage (default: stay in current stage)')
|
|
144
144
|
.option('--replace')
|
|
145
145
|
.option('--priority <n>', 'update item priority', parseFloatArg)
|
|
146
|
-
.option('--payload-format <yaml|json
|
|
146
|
+
.option('--payload-format <yaml|json>', 'payload parsing format: yaml (default) or json')
|
|
147
147
|
.action((pipeline, seqStr, opts) => {
|
|
148
148
|
try {
|
|
149
149
|
const seq = parseInt(seqStr, 10);
|
|
@@ -154,15 +154,24 @@ program
|
|
|
154
154
|
} catch (e) { e instanceof QQError ? fail(e.message) : fail(String(e)); }
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
addFilterOptions(program.command('
|
|
158
|
-
.option('--
|
|
159
|
-
.option('--
|
|
157
|
+
addFilterOptions(program.command('query <path>'))
|
|
158
|
+
.option('--payload-filter <json>', 'MongoDB-style payload filter as JSON')
|
|
159
|
+
.option('--payload-fields <fields>', 'payload fields to return: * for all, or comma-separated paths (e.g. status,meta.author)')
|
|
160
|
+
.option('--sort-order <priority|fifo|lifo|random>', 'sort order (default: fifo)', 'fifo')
|
|
160
161
|
.action((path, opts) => {
|
|
161
162
|
try {
|
|
162
163
|
const { pipeline, stage = '*' } = parsePath(path);
|
|
163
164
|
const filters = collectFilters(opts);
|
|
164
165
|
if (filters.claimed === undefined) filters.claimed = false;
|
|
165
|
-
|
|
166
|
+
if (opts.payloadFilter) {
|
|
167
|
+
try { filters.filter = JSON.parse(opts.payloadFilter as string); }
|
|
168
|
+
catch { fail('--payload-filter must be valid JSON'); }
|
|
169
|
+
}
|
|
170
|
+
if (opts.payloadFields) {
|
|
171
|
+
const s = opts.payloadFields as string;
|
|
172
|
+
filters.select = s === '*' ? '*' : s.split(',').map((f: string) => f.trim()).filter(Boolean) as PayloadSelect;
|
|
173
|
+
}
|
|
174
|
+
out(query(pipeline, stage, filters, opts.sortOrder as ClaimStrategy));
|
|
166
175
|
}
|
|
167
176
|
catch (e) { fail(e instanceof Error ? e.message : String(e)); }
|
|
168
177
|
});
|
|
@@ -186,14 +195,14 @@ addReapFilterOptions(program.command('unstick <path>'))
|
|
|
186
195
|
});
|
|
187
196
|
|
|
188
197
|
program
|
|
189
|
-
.command('
|
|
198
|
+
.command('upload-file <path> <pipeline>')
|
|
190
199
|
.description('Load multiple items from a file into a pipeline (JSONL, JSON array, YAML array, CSV)')
|
|
191
200
|
.option('--stage <stage>', 'default stage for records that do not specify one')
|
|
192
201
|
.option('--delete-after', 'delete the file after loading')
|
|
193
202
|
.option('--duplicates <ignore|append|replace>', 'how to handle duplicate ids: ignore (default), append payload, or replace payload', 'ignore')
|
|
194
203
|
.action((filePath, pipeline, opts) => {
|
|
195
204
|
try {
|
|
196
|
-
const results =
|
|
205
|
+
const results = uploadFile(filePath, pipeline, { stage: opts.stage, deleteAfter: opts.deleteAfter ?? false, duplicates: opts.duplicates as DuplicateMode });
|
|
197
206
|
const counts = { new: 0, duplicate: 0, error: 0 };
|
|
198
207
|
for (const r of results) counts[r.status]++;
|
|
199
208
|
const items = results.map(r => ({ [r.id]: r.message ? `${r.status}: ${r.message}` : r.status }));
|
|
@@ -202,7 +211,7 @@ program
|
|
|
202
211
|
});
|
|
203
212
|
|
|
204
213
|
program
|
|
205
|
-
.command('
|
|
214
|
+
.command('upload-data <pipeline>')
|
|
206
215
|
.description('Write multiple items from stdin into a pipeline (JSONL, JSON array, YAML array, CSV)')
|
|
207
216
|
.requiredOption('--format <jsonl|json|yaml|csv>', 'data format')
|
|
208
217
|
.option('--stage <stage>', 'default stage for records that do not specify one')
|
|
@@ -211,7 +220,7 @@ program
|
|
|
211
220
|
try {
|
|
212
221
|
const data = readStdin();
|
|
213
222
|
if (!data) fail('no data on stdin');
|
|
214
|
-
const results =
|
|
223
|
+
const results = uploadData(data, opts.format as BatchWriteFormat, pipeline, { stage: opts.stage, duplicates: opts.duplicates as DuplicateMode });
|
|
215
224
|
const counts = { new: 0, duplicate: 0, error: 0 };
|
|
216
225
|
for (const r of results) counts[r.status]++;
|
|
217
226
|
const items = results.map(r => ({ [r.id]: r.message ? `${r.status}: ${r.message}` : r.status }));
|
package/src/mcp.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { z } from 'zod';
|
|
|
7
7
|
import { dump } from 'js-yaml';
|
|
8
8
|
import {
|
|
9
9
|
makePipeline, deletePipeline, push, claim, release,
|
|
10
|
-
|
|
10
|
+
query, status, unstick, uploadFile, uploadData, readFile,
|
|
11
11
|
} from './commands.js';
|
|
12
12
|
import { startHttp } from './http.js';
|
|
13
13
|
import { renderJavaScriptSdk } from './sdk-templates/javascript.js';
|
|
@@ -81,12 +81,11 @@ export async function startMcp(fixedPort?: number): Promise<void> {
|
|
|
81
81
|
pipeline: z.string(),
|
|
82
82
|
stage: z.string().optional(),
|
|
83
83
|
id: z.string().optional(),
|
|
84
|
-
payload: z.
|
|
84
|
+
payload: z.record(z.unknown()).optional(),
|
|
85
85
|
priority: z.number().optional(),
|
|
86
|
-
payloadFormat: z.enum(['yaml', 'json', 'text']).optional(),
|
|
87
86
|
},
|
|
88
|
-
}, ({ pipeline, stage, id, payload, priority
|
|
89
|
-
try { return ok(`id: ${push(pipeline, stage, id, { payload, priority
|
|
87
|
+
}, ({ pipeline, stage, id, payload, priority }) => {
|
|
88
|
+
try { return ok(`id: ${push(pipeline, stage, id, { payload, priority })}`); }
|
|
90
89
|
catch (e) { return err(e); }
|
|
91
90
|
});
|
|
92
91
|
|
|
@@ -96,10 +95,10 @@ export async function startMcp(fixedPort?: number): Promise<void> {
|
|
|
96
95
|
pipeline: z.string(),
|
|
97
96
|
stage: z.string().default('*'),
|
|
98
97
|
id: z.string().optional(),
|
|
99
|
-
|
|
98
|
+
sort_order: z.enum(['priority', 'fifo', 'lifo', 'random']).optional(),
|
|
100
99
|
},
|
|
101
|
-
}, ({ pipeline, stage, id,
|
|
102
|
-
try { return ok(dump(claim(pipeline, stage, id, { strategy }), { lineWidth: -1 })); }
|
|
100
|
+
}, ({ pipeline, stage, id, sort_order }) => {
|
|
101
|
+
try { return ok(dump(claim(pipeline, stage, id, { strategy: sort_order }), { lineWidth: -1 })); }
|
|
103
102
|
catch (e) { return err(e); }
|
|
104
103
|
});
|
|
105
104
|
|
|
@@ -109,13 +108,12 @@ export async function startMcp(fixedPort?: number): Promise<void> {
|
|
|
109
108
|
pipeline: z.string(),
|
|
110
109
|
seq: z.number(),
|
|
111
110
|
target: z.string().optional(),
|
|
112
|
-
payload: z.
|
|
111
|
+
payload: z.record(z.unknown()).optional(),
|
|
113
112
|
replace: z.boolean().optional(),
|
|
114
113
|
priority: z.number().optional(),
|
|
115
|
-
payloadFormat: z.enum(['yaml', 'json', 'text']).optional(),
|
|
116
114
|
},
|
|
117
|
-
}, ({ pipeline, seq, target, payload, replace, priority
|
|
118
|
-
try { release(pipeline, seq, { target, payload, replace, priority
|
|
115
|
+
}, ({ pipeline, seq, target, payload, replace, priority }) => {
|
|
116
|
+
try { release(pipeline, seq, { target, payload, replace, priority }); return ok('ok: true'); }
|
|
119
117
|
catch (e) { return err(e); }
|
|
120
118
|
});
|
|
121
119
|
|
|
@@ -127,27 +125,28 @@ export async function startMcp(fixedPort?: number): Promise<void> {
|
|
|
127
125
|
catch (e) { return err(e); }
|
|
128
126
|
});
|
|
129
127
|
|
|
130
|
-
server.registerTool('
|
|
131
|
-
description: '
|
|
128
|
+
server.registerTool('query', {
|
|
129
|
+
description: 'Query items from a pipeline stage without claiming. Read guide before using.',
|
|
132
130
|
inputSchema: {
|
|
133
131
|
pipeline: z.string(),
|
|
134
132
|
stage: z.string().optional(),
|
|
135
|
-
|
|
133
|
+
payload_filter: z.record(z.unknown()).optional(),
|
|
134
|
+
payload_fields: z.union([z.literal('*'), z.array(z.string())]).optional(),
|
|
136
135
|
claimed: z.boolean().optional(),
|
|
137
136
|
ids: z.array(z.string()).optional(),
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
created_after: z.union([z.number(), z.string()]).optional(),
|
|
138
|
+
created_before: z.union([z.number(), z.string()]).optional(),
|
|
139
|
+
modified_after: z.union([z.number(), z.string()]).optional(),
|
|
141
140
|
limit: z.number().optional(),
|
|
142
141
|
offset: z.number().optional(),
|
|
143
|
-
|
|
142
|
+
sort_order: z.enum(['priority', 'fifo', 'lifo', 'random']).optional(),
|
|
144
143
|
},
|
|
145
|
-
}, ({ pipeline, stage,
|
|
144
|
+
}, ({ pipeline, stage, payload_filter, payload_fields, claimed, ids, created_after, created_before, modified_after, limit, offset, sort_order }) => {
|
|
146
145
|
try {
|
|
147
|
-
const items =
|
|
148
|
-
claimed, ids, created_after
|
|
149
|
-
modified_after
|
|
150
|
-
},
|
|
146
|
+
const items = query(pipeline, stage ?? '*', {
|
|
147
|
+
claimed, ids, created_after, created_before,
|
|
148
|
+
modified_after, limit, offset, filter: payload_filter, select: payload_fields,
|
|
149
|
+
}, sort_order);
|
|
151
150
|
return ok(dump(items, { lineWidth: -1 }));
|
|
152
151
|
} catch (e) { return err(e); }
|
|
153
152
|
});
|
|
@@ -158,14 +157,14 @@ export async function startMcp(fixedPort?: number): Promise<void> {
|
|
|
158
157
|
pipeline: z.string(),
|
|
159
158
|
stage: z.string().optional(),
|
|
160
159
|
ids: z.array(z.string()).optional(),
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
created_after: z.union([z.number(), z.string()]).optional(),
|
|
161
|
+
created_before: z.union([z.number(), z.string()]).optional(),
|
|
162
|
+
modified_after: z.union([z.number(), z.string()]).optional(),
|
|
164
163
|
},
|
|
165
|
-
}, ({ pipeline, stage, ids,
|
|
164
|
+
}, ({ pipeline, stage, ids, created_after, created_before, modified_after }) => {
|
|
166
165
|
try {
|
|
167
166
|
const n = unstick(pipeline, stage ?? '*', {
|
|
168
|
-
ids, created_after
|
|
167
|
+
ids, created_after, created_before, modified_after,
|
|
169
168
|
});
|
|
170
169
|
return ok(`unstuck: ${n}`);
|
|
171
170
|
} catch (e) { return err(e); }
|
|
@@ -191,18 +190,18 @@ export async function startMcp(fixedPort?: number): Promise<void> {
|
|
|
191
190
|
return ok(snippet);
|
|
192
191
|
});
|
|
193
192
|
|
|
194
|
-
server.registerTool('
|
|
195
|
-
description: '
|
|
193
|
+
server.registerTool('upload_file', {
|
|
194
|
+
description: 'Upload items from a file into a pipeline. Supports JSONL, JSON array, YAML array, CSV. Read guide before using.',
|
|
196
195
|
inputSchema: {
|
|
197
196
|
path: z.string(),
|
|
198
197
|
pipeline: z.string(),
|
|
199
198
|
stage: z.string().optional(),
|
|
200
|
-
|
|
199
|
+
delete_after: z.boolean().default(true),
|
|
201
200
|
duplicates: z.enum(['ignore', 'append', 'replace']).optional(),
|
|
202
201
|
},
|
|
203
|
-
}, ({ path: filePath, pipeline, stage,
|
|
202
|
+
}, ({ path: filePath, pipeline, stage, delete_after, duplicates }) => {
|
|
204
203
|
try {
|
|
205
|
-
const results =
|
|
204
|
+
const results = uploadFile(filePath, pipeline, { stage, deleteAfter: delete_after, duplicates });
|
|
206
205
|
const counts = { new: 0, duplicate: 0, error: 0 };
|
|
207
206
|
for (const r of results) counts[r.status]++;
|
|
208
207
|
const items = results.map(r => ({ [r.id]: r.message ? `${r.status}: ${r.message}` : r.status }));
|
|
@@ -210,8 +209,8 @@ export async function startMcp(fixedPort?: number): Promise<void> {
|
|
|
210
209
|
} catch (e) { return err(e); }
|
|
211
210
|
});
|
|
212
211
|
|
|
213
|
-
server.registerTool('
|
|
214
|
-
description: '
|
|
212
|
+
server.registerTool('upload_data', {
|
|
213
|
+
description: 'Upload multiple items from inline data into a pipeline. Supports JSONL, JSON array, YAML array, CSV. Read guide before using.',
|
|
215
214
|
inputSchema: {
|
|
216
215
|
data: z.string(),
|
|
217
216
|
format: z.enum(['jsonl', 'json', 'yaml', 'csv']),
|
|
@@ -221,7 +220,7 @@ export async function startMcp(fixedPort?: number): Promise<void> {
|
|
|
221
220
|
},
|
|
222
221
|
}, ({ data, format, pipeline, stage, duplicates }) => {
|
|
223
222
|
try {
|
|
224
|
-
const results =
|
|
223
|
+
const results = uploadData(data, format, pipeline, { stage, duplicates });
|
|
225
224
|
const counts = { new: 0, duplicate: 0, error: 0 };
|
|
226
225
|
for (const r of results) counts[r.status]++;
|
|
227
226
|
const items = results.map(r => ({ [r.id]: r.message ? `${r.status}: ${r.message}` : r.status }));
|
|
@@ -230,13 +229,13 @@ export async function startMcp(fixedPort?: number): Promise<void> {
|
|
|
230
229
|
});
|
|
231
230
|
|
|
232
231
|
server.registerTool('read_file', {
|
|
233
|
-
description: 'Read and return the content of a file on the host filesystem. Supports $VAR env var expansion in path. Use
|
|
232
|
+
description: 'Read and return the content of a file on the host filesystem. Supports $VAR env var expansion in path. Use delete_after to consume the file after reading.',
|
|
234
233
|
inputSchema: {
|
|
235
234
|
path: z.string(),
|
|
236
|
-
|
|
235
|
+
delete_after: z.boolean().default(true),
|
|
237
236
|
},
|
|
238
|
-
}, ({ path: filePath,
|
|
239
|
-
try { return ok(readFile(filePath,
|
|
237
|
+
}, ({ path: filePath, delete_after }) => {
|
|
238
|
+
try { return ok(readFile(filePath, delete_after)); }
|
|
240
239
|
catch (e) { return err(e); }
|
|
241
240
|
});
|
|
242
241
|
|
package/src/resources/chrome.md
CHANGED
|
@@ -6,7 +6,7 @@ Read the `guide` resource first — this document covers Chrome-specific pattern
|
|
|
6
6
|
|
|
7
7
|
## How to get data out of Chrome
|
|
8
8
|
|
|
9
|
-
Chrome devtools and connectors usually struggle to get large data packages from web pages in Chrome. Solution: download +
|
|
9
|
+
Chrome devtools and connectors usually struggle to get large data packages from web pages in Chrome. Solution: download + upload_file. The browser's download API has no mixed content restrictions. Trigger a download from the HTTPS page, then call the `upload_file` MCP tool to ingest the file. Don't route large data through the context.
|
|
10
10
|
|
|
11
11
|
**In the injected script (HTTPS page):**
|
|
12
12
|
```javascript
|
|
@@ -19,9 +19,9 @@ a.download = 'qq-data.json';
|
|
|
19
19
|
a.click();
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
**Then call
|
|
22
|
+
**Then call upload_file:**
|
|
23
23
|
```
|
|
24
|
-
|
|
24
|
+
upload_file(
|
|
25
25
|
path: "$HOME/Downloads/qq-data.json",
|
|
26
26
|
pipeline: "my_pypeline",
|
|
27
27
|
stage: "inbox",
|