@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/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
- batchRead, status, unstick, loadFile, batchWrite, backup, QQError,
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|text>', 'payload validation: yaml (default), json (minified), text (none)')
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('--strategy <priority|fifo|lifo|random>', 'claim strategy (default: priority)', 'priority')
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.strategy as ClaimStrategy };
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|text>', 'payload validation: yaml (default), json (minified), text (none)')
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('batch-read <path>'))
158
- .option('--include-payload', 'include payload in output')
159
- .option('--strategy <priority|fifo|lifo|random>', 'sort order (default: fifo)', 'fifo')
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
- out(batchRead(pipeline, stage, filters, opts.includePayload ?? false, opts.strategy as ClaimStrategy));
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('load-file <path> <pipeline>')
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 = loadFile(filePath, pipeline, { stage: opts.stage, deleteAfter: opts.deleteAfter ?? false, duplicates: opts.duplicates as DuplicateMode });
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('batch-write <pipeline>')
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 = batchWrite(data, opts.format as BatchWriteFormat, pipeline, { stage: opts.stage, duplicates: opts.duplicates as DuplicateMode });
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
- batchRead, status, unstick, loadFile, batchWrite, readFile,
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.string().optional(),
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, payloadFormat }) => {
89
- try { return ok(`id: ${push(pipeline, stage, id, { payload, priority, payloadFormat })}`); }
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
- strategy: z.enum(['priority', 'fifo', 'lifo', 'random']).optional(),
98
+ sort_order: z.enum(['priority', 'fifo', 'lifo', 'random']).optional(),
100
99
  },
101
- }, ({ pipeline, stage, id, strategy }) => {
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.string().optional(),
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, payloadFormat }) => {
118
- try { release(pipeline, seq, { target, payload, replace, priority, payloadFormat }); return ok('ok: true'); }
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('batch_read', {
131
- description: 'Read items from a pipeline stage without claiming. Read guide before using.',
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
- includePayload: z.boolean().optional(),
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
- createdAfter: z.union([z.number(), z.string()]).optional(),
139
- createdBefore: z.union([z.number(), z.string()]).optional(),
140
- modifiedAfter: z.union([z.number(), z.string()]).optional(),
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
- strategy: z.enum(['priority', 'fifo', 'lifo', 'random']).optional(),
142
+ sort_order: z.enum(['priority', 'fifo', 'lifo', 'random']).optional(),
144
143
  },
145
- }, ({ pipeline, stage, includePayload, claimed, ids, createdAfter, createdBefore, modifiedAfter, limit, offset, strategy }) => {
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 = batchRead(pipeline, stage ?? '*', {
148
- claimed, ids, created_after: createdAfter, created_before: createdBefore,
149
- modified_after: modifiedAfter, limit, offset,
150
- }, includePayload ?? false, strategy);
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
- createdAfter: z.union([z.number(), z.string()]).optional(),
162
- createdBefore: z.union([z.number(), z.string()]).optional(),
163
- modifiedAfter: z.union([z.number(), z.string()]).optional(),
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, createdAfter, createdBefore, modifiedAfter }) => {
164
+ }, ({ pipeline, stage, ids, created_after, created_before, modified_after }) => {
166
165
  try {
167
166
  const n = unstick(pipeline, stage ?? '*', {
168
- ids, created_after: createdAfter, created_before: createdBefore, modified_after: modifiedAfter,
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('load_file', {
195
- description: 'Load multiple items from a file into a pipeline. Supports JSONL, JSON array, YAML array, CSV. Read guide before using.',
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
- deleteAfter: z.boolean().default(true),
199
+ delete_after: z.boolean().default(true),
201
200
  duplicates: z.enum(['ignore', 'append', 'replace']).optional(),
202
201
  },
203
- }, ({ path: filePath, pipeline, stage, deleteAfter, duplicates }) => {
202
+ }, ({ path: filePath, pipeline, stage, delete_after, duplicates }) => {
204
203
  try {
205
- const results = loadFile(filePath, pipeline, { stage, deleteAfter, duplicates });
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('batch_write', {
214
- description: 'Write multiple items from inline data into a pipeline. Supports JSONL, JSON array, YAML array, CSV. Read guide before using.',
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 = batchWrite(data, format, pipeline, { stage, duplicates });
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 deleteAfter to consume the file after reading.',
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
- deleteAfter: z.boolean().default(true),
235
+ delete_after: z.boolean().default(true),
237
236
  },
238
- }, ({ path: filePath, deleteAfter }) => {
239
- try { return ok(readFile(filePath, deleteAfter)); }
237
+ }, ({ path: filePath, delete_after }) => {
238
+ try { return ok(readFile(filePath, delete_after)); }
240
239
  catch (e) { return err(e); }
241
240
  });
242
241
 
@@ -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 + load_file. The browser's download API has no mixed content restrictions. Trigger a download from the HTTPS page, then call the `load_file` MCP tool to ingest the file. Don't route large data through the context.
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 load_file:**
22
+ **Then call upload_file:**
23
23
  ```
24
- load_file(
24
+ upload_file(
25
25
  path: "$HOME/Downloads/qq-data.json",
26
26
  pipeline: "my_pypeline",
27
27
  stage: "inbox",