@quantod/qq 0.1.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/bin/qq ADDED
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ exec node "$(dirname "$0")/../dist/index.js" "$@"
@@ -0,0 +1,43 @@
1
+ export declare class QQError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export interface Message {
5
+ id: string;
6
+ subqueue: string;
7
+ claimed: boolean;
8
+ claimed_by: string | null;
9
+ seq: number;
10
+ created: number;
11
+ last_modified: number;
12
+ payload?: string | null;
13
+ }
14
+ export interface FilterOptions {
15
+ claimed?: boolean;
16
+ claimed_by?: string;
17
+ ids?: string[];
18
+ created_after?: number;
19
+ created_before?: number;
20
+ modified_after?: number;
21
+ limit?: number;
22
+ offset?: number;
23
+ }
24
+ export interface ReleaseOptions {
25
+ target?: string;
26
+ payload?: string;
27
+ replace?: boolean;
28
+ }
29
+ export declare function makeQueue(nameSuffix?: string): string;
30
+ export declare function deleteQueue(queue: string): void;
31
+ export declare function push(path: string, id: string, payload?: string): void;
32
+ export declare function claim(path: string, options?: {
33
+ agentId?: string;
34
+ }): Message | null;
35
+ export declare function release(queue: string, id: string, options?: ReleaseOptions): void;
36
+ export declare function list(path: string, filters?: FilterOptions): Message[];
37
+ export declare function batchRead(path: string, filters?: FilterOptions): Message[];
38
+ export declare function stats(queue: string): Record<string, {
39
+ total: number;
40
+ claimed: number;
41
+ }>;
42
+ export declare function reap(queue: string, agentId?: string): number;
43
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAQA,qBAAa,OAAQ,SAAQ,KAAK;gBACpB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAmCD,wBAAgB,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CASrD;AAID,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE/C;AAID,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAkBrE;AAID,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,GAAG,IAAI,CAwBtF;AAID,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,IAAI,CA4BrF;AA8DD,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,EAAE,CAEzE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,EAAE,CAE9E;AAID,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAiBvF;AAID,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAiB5D"}
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QQError = void 0;
4
+ exports.makeQueue = makeQueue;
5
+ exports.deleteQueue = deleteQueue;
6
+ exports.push = push;
7
+ exports.claim = claim;
8
+ exports.release = release;
9
+ exports.list = list;
10
+ exports.batchRead = batchRead;
11
+ exports.stats = stats;
12
+ exports.reap = reap;
13
+ const node_fs_1 = require("node:fs");
14
+ const node_path_1 = require("node:path");
15
+ const node_crypto_1 = require("node:crypto");
16
+ const db_js_1 = require("./db.js");
17
+ // ── types ──────────────────────────────────────────────────────────────────────
18
+ class QQError extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = 'QQError';
22
+ }
23
+ }
24
+ exports.QQError = QQError;
25
+ // ── internal helpers ───────────────────────────────────────────────────────────
26
+ function parsePath(path) {
27
+ const slash = path.indexOf('/');
28
+ if (slash === -1)
29
+ return { queue: path, subqueue: undefined };
30
+ return { queue: path.slice(0, slash), subqueue: path.slice(slash + 1) };
31
+ }
32
+ function toGlob(pattern) {
33
+ return pattern.replace(/\*/g, '%').replace(/\?/g, '_');
34
+ }
35
+ function nextSeq(db) {
36
+ const row = db.prepare('SELECT MAX(seq) as m FROM messages').get();
37
+ return (row.m ?? 0) + 1;
38
+ }
39
+ function rowToMessage(row, includePayload) {
40
+ const msg = {
41
+ id: row.id,
42
+ subqueue: row.subqueue,
43
+ claimed: Boolean(row.claimed),
44
+ claimed_by: row.claimed_by ?? null,
45
+ seq: row.seq,
46
+ created: row.created,
47
+ last_modified: row.last_modified,
48
+ };
49
+ if (includePayload)
50
+ msg.payload = row.payload ?? null;
51
+ return msg;
52
+ }
53
+ // ── makeQueue ─────────────────────────────────────────────────────────────────
54
+ function makeQueue(nameSuffix) {
55
+ const now = new Date();
56
+ const mm = String(now.getMonth() + 1).padStart(2, '0');
57
+ const dd = String(now.getDate()).padStart(2, '0');
58
+ const suffix = nameSuffix ?? (0, node_crypto_1.randomBytes)(3).toString('hex');
59
+ const name = `${mm}${dd}_${suffix}`;
60
+ const db = (0, db_js_1.openDb)(name);
61
+ db.close();
62
+ return name;
63
+ }
64
+ // ── deleteQueue ───────────────────────────────────────────────────────────────
65
+ function deleteQueue(queue) {
66
+ (0, node_fs_1.rmSync)((0, node_path_1.join)(process.cwd(), '.claude', 'qq', `${queue}.db`), { force: true });
67
+ }
68
+ // ── push ──────────────────────────────────────────────────────────────────────
69
+ function push(path, id, payload) {
70
+ const { queue, subqueue } = parsePath(path);
71
+ if (!subqueue)
72
+ throw new QQError('path must include a subqueue: queue/subqueue');
73
+ const db = (0, db_js_1.openDb)(queue);
74
+ try {
75
+ (0, db_js_1.inTransaction)(db, () => {
76
+ const existing = db.prepare('SELECT id FROM messages WHERE id = ?').get(id);
77
+ if (existing)
78
+ throw new QQError(`duplicate id: ${id}`);
79
+ const seq = nextSeq(db);
80
+ const now = Date.now();
81
+ db.prepare('INSERT INTO messages (id, subqueue, claimed, seq, payload, created, last_modified) VALUES (?, ?, 0, ?, ?, ?, ?)').run(id, subqueue, seq, payload ?? null, now, now);
82
+ });
83
+ }
84
+ finally {
85
+ db.close();
86
+ }
87
+ }
88
+ // ── claim ─────────────────────────────────────────────────────────────────────
89
+ function claim(path, options = {}) {
90
+ const { queue, subqueue } = parsePath(path);
91
+ if (!subqueue)
92
+ throw new QQError('path must include a subqueue: queue/subqueue');
93
+ const db = (0, db_js_1.openDb)(queue);
94
+ try {
95
+ return (0, db_js_1.inTransaction)(db, () => {
96
+ const pattern = toGlob(subqueue);
97
+ const row = db.prepare('SELECT * FROM messages WHERE subqueue LIKE ? AND claimed = 0 ORDER BY seq ASC LIMIT 1').get(pattern);
98
+ if (!row)
99
+ return null;
100
+ const now = Date.now();
101
+ db.prepare('UPDATE messages SET claimed = 1, claimed_by = ?, last_modified = ? WHERE id = ?').run(options.agentId ?? null, now, row.id);
102
+ return rowToMessage({ ...row, claimed: 1, claimed_by: options.agentId ?? null }, true);
103
+ });
104
+ }
105
+ finally {
106
+ db.close();
107
+ }
108
+ }
109
+ // ── release ───────────────────────────────────────────────────────────────────
110
+ function release(queue, id, options = {}) {
111
+ const db = (0, db_js_1.openDb)(queue);
112
+ try {
113
+ (0, db_js_1.inTransaction)(db, () => {
114
+ const row = db.prepare('SELECT * FROM messages WHERE id = ?').get(id);
115
+ if (!row)
116
+ throw new QQError(`message not found: ${id}`);
117
+ if (!row.claimed)
118
+ throw new QQError(`message not claimed: ${id}`);
119
+ const seq = nextSeq(db);
120
+ const now = Date.now();
121
+ const newSubqueue = options.target ?? row.subqueue;
122
+ let newPayload;
123
+ if (options.payload !== undefined) {
124
+ newPayload = options.replace
125
+ ? options.payload
126
+ : [row.payload, options.payload].filter(Boolean).join('\n');
127
+ }
128
+ else {
129
+ newPayload = row.payload ?? null;
130
+ }
131
+ db.prepare('UPDATE messages SET claimed = 0, claimed_by = NULL, seq = ?, subqueue = ?, payload = ?, last_modified = ? WHERE id = ?').run(seq, newSubqueue, newPayload, now, id);
132
+ });
133
+ }
134
+ finally {
135
+ db.close();
136
+ }
137
+ }
138
+ // ── list / batchRead ──────────────────────────────────────────────────────────
139
+ function queryMessages(path, filters, includePayload) {
140
+ const { queue, subqueue } = parsePath(path);
141
+ const db = (0, db_js_1.openDb)(queue);
142
+ try {
143
+ const conditions = [];
144
+ const params = [];
145
+ if (subqueue) {
146
+ conditions.push('subqueue LIKE ?');
147
+ params.push(toGlob(subqueue));
148
+ }
149
+ if (filters.claimed !== undefined) {
150
+ conditions.push('claimed = ?');
151
+ params.push(filters.claimed ? 1 : 0);
152
+ }
153
+ if (filters.claimed_by !== undefined) {
154
+ conditions.push('claimed_by = ?');
155
+ params.push(filters.claimed_by);
156
+ }
157
+ if (filters.ids?.length) {
158
+ conditions.push(`id IN (${filters.ids.map(() => '?').join(', ')})`);
159
+ params.push(...filters.ids);
160
+ }
161
+ if (filters.created_after !== undefined) {
162
+ conditions.push('created > ?');
163
+ params.push(filters.created_after);
164
+ }
165
+ if (filters.created_before !== undefined) {
166
+ conditions.push('created < ?');
167
+ params.push(filters.created_before);
168
+ }
169
+ if (filters.modified_after !== undefined) {
170
+ conditions.push('last_modified > ?');
171
+ params.push(filters.modified_after);
172
+ }
173
+ let sql = 'SELECT * FROM messages';
174
+ if (conditions.length)
175
+ sql += ' WHERE ' + conditions.join(' AND ');
176
+ sql += ' ORDER BY seq ASC';
177
+ if (filters.limit !== undefined) {
178
+ sql += ' LIMIT ?';
179
+ params.push(filters.limit);
180
+ }
181
+ else if (filters.offset !== undefined) {
182
+ sql += ' LIMIT -1';
183
+ }
184
+ if (filters.offset !== undefined) {
185
+ sql += ' OFFSET ?';
186
+ params.push(filters.offset);
187
+ }
188
+ const rows = db.prepare(sql).all(...params);
189
+ return rows.map(r => rowToMessage(r, includePayload));
190
+ }
191
+ finally {
192
+ db.close();
193
+ }
194
+ }
195
+ function list(path, filters = {}) {
196
+ return queryMessages(path, filters, false);
197
+ }
198
+ function batchRead(path, filters = {}) {
199
+ return queryMessages(path, filters, true);
200
+ }
201
+ // ── stats ─────────────────────────────────────────────────────────────────────
202
+ function stats(queue) {
203
+ const db = (0, db_js_1.openDb)(queue);
204
+ try {
205
+ const rows = db.prepare('SELECT subqueue, claimed, COUNT(*) as count FROM messages GROUP BY subqueue, claimed').all();
206
+ const result = {};
207
+ for (const row of rows) {
208
+ if (!result[row.subqueue])
209
+ result[row.subqueue] = { total: 0, claimed: 0 };
210
+ result[row.subqueue].total += row.count;
211
+ if (row.claimed)
212
+ result[row.subqueue].claimed += row.count;
213
+ }
214
+ return result;
215
+ }
216
+ finally {
217
+ db.close();
218
+ }
219
+ }
220
+ // ── reap ──────────────────────────────────────────────────────────────────────
221
+ function reap(queue, agentId) {
222
+ const db = (0, db_js_1.openDb)(queue);
223
+ try {
224
+ return (0, db_js_1.inTransaction)(db, () => {
225
+ const now = Date.now();
226
+ const result = agentId
227
+ ? db.prepare('UPDATE messages SET claimed = 0, claimed_by = NULL, last_modified = ? WHERE claimed = 1 AND claimed_by = ?').run(now, agentId)
228
+ : db.prepare('UPDATE messages SET claimed = 0, claimed_by = NULL, last_modified = ? WHERE claimed = 1').run(now);
229
+ return result.changes;
230
+ });
231
+ }
232
+ finally {
233
+ db.close();
234
+ }
235
+ }
236
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":";;;AA4EA,8BASC;AAID,kCAEC;AAID,oBAkBC;AAID,sBAwBC;AAID,0BA4BC;AA8DD,oBAEC;AAED,8BAEC;AAID,sBAiBC;AAID,oBAiBC;AA3RD,qCAAiC;AACjC,yCAAiC;AACjC,6CAA0C;AAE1C,mCAAgD;AAEhD,kFAAkF;AAElF,MAAa,OAAQ,SAAQ,KAAK;IAChC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;IACxB,CAAC;CACF;AALD,0BAKC;AA8BD,kFAAkF;AAElF,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC9D,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;AAC1E,CAAC;AAED,SAAS,MAAM,CAAC,OAAe;IAC7B,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,OAAO,CAAC,EAAqB;IACpC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,EAA0B,CAAC;IAC3F,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,YAAY,CAAC,GAA4B,EAAE,cAAuB;IACzE,MAAM,GAAG,GAAY;QACnB,EAAE,EAAE,GAAG,CAAC,EAAY;QACpB,QAAQ,EAAE,GAAG,CAAC,QAAkB;QAChC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;QAC7B,UAAU,EAAG,GAAG,CAAC,UAA4B,IAAI,IAAI;QACrD,GAAG,EAAE,GAAG,CAAC,GAAa;QACtB,OAAO,EAAE,GAAG,CAAC,OAAiB;QAC9B,aAAa,EAAE,GAAG,CAAC,aAAuB;KAC3C,CAAC;IACF,IAAI,cAAc;QAAE,GAAG,CAAC,OAAO,GAAI,GAAG,CAAC,OAAyB,IAAI,IAAI,CAAC;IACzE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iFAAiF;AAEjF,SAAgB,SAAS,CAAC,UAAmB;IAC3C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,UAAU,IAAI,IAAA,yBAAW,EAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,GAAG,EAAE,GAAG,EAAE,IAAI,MAAM,EAAE,CAAC;IACpC,MAAM,EAAE,GAAG,IAAA,cAAM,EAAC,IAAI,CAAC,CAAC;IACxB,EAAE,CAAC,KAAK,EAAE,CAAC;IACX,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iFAAiF;AAEjF,SAAgB,WAAW,CAAC,KAAa;IACvC,IAAA,gBAAM,EAAC,IAAA,gBAAI,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,iFAAiF;AAEjF,SAAgB,IAAI,CAAC,IAAY,EAAE,EAAU,EAAE,OAAgB;IAC7D,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,OAAO,CAAC,8CAA8C,CAAC,CAAC;IAEjF,MAAM,EAAE,GAAG,IAAA,cAAM,EAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC;QACH,IAAA,qBAAa,EAAC,EAAE,EAAE,GAAG,EAAE;YACrB,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC5E,IAAI,QAAQ;gBAAE,MAAM,IAAI,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;YACvD,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;YACxB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,EAAE,CAAC,OAAO,CACR,iHAAiH,CAClH,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,IAAI,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,iFAAiF;AAEjF,SAAgB,KAAK,CAAC,IAAY,EAAE,UAAgC,EAAE;IACpE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,OAAO,CAAC,8CAA8C,CAAC,CAAC;IAEjF,MAAM,EAAE,GAAG,IAAA,cAAM,EAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,IAAA,qBAAa,EAAC,EAAE,EAAE,GAAG,EAAE;YAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CACpB,uFAAuF,CACxF,CAAC,GAAG,CAAC,OAAO,CAAwC,CAAC;YAEtD,IAAI,CAAC,GAAG;gBAAE,OAAO,IAAI,CAAC;YAEtB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,EAAE,CAAC,OAAO,CACR,iFAAiF,CAClF,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,EAAY,CAAC,CAAC;YAEtD,OAAO,YAAY,CAAC,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;QACzF,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,iFAAiF;AAEjF,SAAgB,OAAO,CAAC,KAAa,EAAE,EAAU,EAAE,UAA0B,EAAE;IAC7E,MAAM,EAAE,GAAG,IAAA,cAAM,EAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC;QACH,IAAA,qBAAa,EAAC,EAAE,EAAE,GAAG,EAAE;YACrB,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAwC,CAAC;YAC7G,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;YACxD,IAAI,CAAC,GAAG,CAAC,OAAO;gBAAE,MAAM,IAAI,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC;YAElE,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;YACxB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,IAAK,GAAG,CAAC,QAAmB,CAAC;YAE/D,IAAI,UAAyB,CAAC;YAC9B,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBAClC,UAAU,GAAG,OAAO,CAAC,OAAO;oBAC1B,CAAC,CAAC,OAAO,CAAC,OAAO;oBACjB,CAAC,CAAC,CAAC,GAAG,CAAC,OAAwB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjF,CAAC;iBAAM,CAAC;gBACN,UAAU,GAAI,GAAG,CAAC,OAAyB,IAAI,IAAI,CAAC;YACtD,CAAC;YAED,EAAE,CAAC,OAAO,CACR,wHAAwH,CACzH,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,iFAAiF;AAEjF,SAAS,aAAa,CAAC,IAAY,EAAE,OAAsB,EAAE,cAAuB;IAClF,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,EAAE,GAAG,IAAA,cAAM,EAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,MAAM,MAAM,GAA+B,EAAE,CAAC;QAE9C,IAAI,QAAQ,EAAE,CAAC;YACb,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAClC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,CAAC;QACD,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACrC,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC;YACxB,UAAU,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpE,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACxC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QACrC,CAAC;QACD,IAAI,OAAO,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YACzC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,OAAO,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YACzC,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACtC,CAAC;QAED,IAAI,GAAG,GAAG,wBAAwB,CAAC;QACnC,IAAI,UAAU,CAAC,MAAM;YAAE,GAAG,IAAI,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnE,GAAG,IAAI,mBAAmB,CAAC;QAE3B,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,GAAG,IAAI,UAAU,CAAC;YAClB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;aAAM,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACxC,GAAG,IAAI,WAAW,CAAC;QACrB,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACjC,GAAG,IAAI,WAAW,CAAC;YACnB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QAED,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAA8B,CAAC;QACzE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;IACxD,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,SAAgB,IAAI,CAAC,IAAY,EAAE,UAAyB,EAAE;IAC5D,OAAO,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED,SAAgB,SAAS,CAAC,IAAY,EAAE,UAAyB,EAAE;IACjE,OAAO,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,iFAAiF;AAEjF,SAAgB,KAAK,CAAC,KAAa;IACjC,MAAM,EAAE,GAAG,IAAA,cAAM,EAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CACrB,sFAAsF,CACvF,CAAC,GAAG,EAA4D,CAAC;QAElE,MAAM,MAAM,GAAuD,EAAE,CAAC;QACtE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;YAC3E,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC;YACxC,IAAI,GAAG,CAAC,OAAO;gBAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC;QAC7D,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,iFAAiF;AAEjF,SAAgB,IAAI,CAAC,KAAa,EAAE,OAAgB;IAClD,MAAM,EAAE,GAAG,IAAA,cAAM,EAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,IAAA,qBAAa,EAAC,EAAE,EAAE,GAAG,EAAE;YAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,OAAO;gBACpB,CAAC,CAAC,EAAE,CAAC,OAAO,CACR,4GAA4G,CAC7G,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC;gBACrB,CAAC,CAAC,EAAE,CAAC,OAAO,CACR,yFAAyF,CAC1F,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACf,OAAO,MAAM,CAAC,OAAO,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
package/dist/db.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import Database from 'better-sqlite3';
2
+ export declare function openDb(queueName: string): Database.Database;
3
+ export declare function inTransaction<T>(db: Database.Database, fn: () => T): T;
4
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAkBtC,wBAAgB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAQ3D;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEtE"}
package/dist/db.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.openDb = openDb;
7
+ exports.inTransaction = inTransaction;
8
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const DDL = `
12
+ CREATE TABLE IF NOT EXISTS messages (
13
+ id TEXT PRIMARY KEY,
14
+ subqueue TEXT NOT NULL,
15
+ claimed INTEGER NOT NULL DEFAULT 0,
16
+ seq INTEGER NOT NULL,
17
+ payload TEXT,
18
+ claimed_by TEXT,
19
+ created INTEGER NOT NULL,
20
+ last_modified INTEGER NOT NULL
21
+ );
22
+ CREATE INDEX IF NOT EXISTS idx_claim ON messages (subqueue, claimed, seq);
23
+ `;
24
+ function openDb(queueName) {
25
+ const dir = (0, node_path_1.join)(process.cwd(), '.claude', 'qq');
26
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
27
+ const db = new better_sqlite3_1.default((0, node_path_1.join)(dir, `${queueName}.db`));
28
+ db.pragma('journal_mode = WAL');
29
+ db.pragma('busy_timeout = 5000');
30
+ db.exec(DDL);
31
+ return db;
32
+ }
33
+ function inTransaction(db, fn) {
34
+ return db.transaction(fn).immediate();
35
+ }
36
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":";;;;;AAkBA,wBAQC;AAED,sCAEC;AA9BD,oEAAsC;AACtC,qCAAoC;AACpC,yCAAiC;AAEjC,MAAM,GAAG,GAAG;;;;;;;;;;;;CAYX,CAAC;AAEF,SAAgB,MAAM,CAAC,SAAiB;IACtC,MAAM,GAAG,GAAG,IAAA,gBAAI,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IACjD,IAAA,mBAAS,EAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,EAAE,GAAG,IAAI,wBAAQ,CAAC,IAAA,gBAAI,EAAC,GAAG,EAAE,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC;IACtD,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACjC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAgB,aAAa,CAAI,EAAqB,EAAE,EAAW;IACjE,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;AACxC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const node_fs_1 = require("node:fs");
5
+ const js_yaml_1 = require("js-yaml");
6
+ const commands_js_1 = require("./commands.js");
7
+ function out(data) {
8
+ process.stdout.write((0, js_yaml_1.dump)(data, { lineWidth: -1 }));
9
+ }
10
+ function fail(message) {
11
+ process.stderr.write(`error: ${message}\n`);
12
+ process.exit(1);
13
+ }
14
+ function readStdin() {
15
+ if (process.stdin.isTTY)
16
+ return null;
17
+ const raw = (0, node_fs_1.readFileSync)(0, 'utf8').trim();
18
+ return raw || null;
19
+ }
20
+ function parseIntArg(value) {
21
+ const n = parseInt(value, 10);
22
+ if (isNaN(n))
23
+ throw new commander_1.InvalidArgumentError('expected an integer');
24
+ return n;
25
+ }
26
+ function collectFilters(opts) {
27
+ const f = {};
28
+ if (opts['claimed'] !== undefined)
29
+ f.claimed = opts['claimed'] === '1';
30
+ if (opts['claimedBy'])
31
+ f.claimed_by = opts['claimedBy'];
32
+ if (opts['ids'])
33
+ f.ids = opts['ids'].split(',');
34
+ if (opts['createdAfter'])
35
+ f.created_after = opts['createdAfter'];
36
+ if (opts['createdBefore'])
37
+ f.created_before = opts['createdBefore'];
38
+ if (opts['modifiedAfter'])
39
+ f.modified_after = opts['modifiedAfter'];
40
+ if (opts['limit'] !== undefined)
41
+ f.limit = opts['limit'];
42
+ if (opts['offset'] !== undefined)
43
+ f.offset = opts['offset'];
44
+ return f;
45
+ }
46
+ function addFilterOptions(cmd) {
47
+ return cmd
48
+ .option('--claimed <0|1>')
49
+ .option('--claimed-by <agent>')
50
+ .option('--ids <id1,id2,...>')
51
+ .option('--created-after <epoch>', '', parseIntArg)
52
+ .option('--created-before <epoch>', '', parseIntArg)
53
+ .option('--modified-after <epoch>', '', parseIntArg)
54
+ .option('--limit <n>', '', parseIntArg)
55
+ .option('--offset <n>', '', parseIntArg);
56
+ }
57
+ const program = new commander_1.Command();
58
+ program.name('qq').description('Persistent queue for Claude agent pipelines');
59
+ program
60
+ .command('make-queue')
61
+ .option('--name <suffix>')
62
+ .action((opts) => {
63
+ try {
64
+ out({ queue: (0, commands_js_1.makeQueue)(opts.name) });
65
+ }
66
+ catch (e) {
67
+ fail(e instanceof Error ? e.message : String(e));
68
+ }
69
+ });
70
+ program
71
+ .command('delete-queue <queue>')
72
+ .action((queue) => {
73
+ try {
74
+ (0, commands_js_1.deleteQueue)(queue);
75
+ }
76
+ catch (e) {
77
+ fail(e instanceof Error ? e.message : String(e));
78
+ }
79
+ });
80
+ program
81
+ .command('push <path> <id>')
82
+ .action((path, id) => {
83
+ try {
84
+ (0, commands_js_1.push)(path, id, readStdin() ?? undefined);
85
+ out({ ok: true });
86
+ }
87
+ catch (e) {
88
+ e instanceof commands_js_1.QQError ? fail(e.message) : fail(String(e));
89
+ }
90
+ });
91
+ program
92
+ .command('claim <path>')
93
+ .option('--agent-id <id>')
94
+ .action((path, opts) => {
95
+ try {
96
+ out((0, commands_js_1.claim)(path, { agentId: opts.agentId }) ?? { empty: true });
97
+ }
98
+ catch (e) {
99
+ fail(e instanceof Error ? e.message : String(e));
100
+ }
101
+ });
102
+ program
103
+ .command('release <queue> <id>')
104
+ .option('--target <subqueue>')
105
+ .option('--payload', 'read payload from stdin')
106
+ .option('--replace')
107
+ .action((queue, id, opts) => {
108
+ try {
109
+ const releaseOpts = { target: opts.target, replace: opts.replace };
110
+ if (opts.payload)
111
+ releaseOpts.payload = readStdin() ?? undefined;
112
+ (0, commands_js_1.release)(queue, id, releaseOpts);
113
+ out({ ok: true });
114
+ }
115
+ catch (e) {
116
+ e instanceof commands_js_1.QQError ? fail(e.message) : fail(String(e));
117
+ }
118
+ });
119
+ addFilterOptions(program.command('list <path>'))
120
+ .action((path, opts) => {
121
+ try {
122
+ out((0, commands_js_1.list)(path, collectFilters(opts)));
123
+ }
124
+ catch (e) {
125
+ fail(e instanceof Error ? e.message : String(e));
126
+ }
127
+ });
128
+ addFilterOptions(program.command('batch-read <path>'))
129
+ .action((path, opts) => {
130
+ try {
131
+ out((0, commands_js_1.batchRead)(path, collectFilters(opts)));
132
+ }
133
+ catch (e) {
134
+ fail(e instanceof Error ? e.message : String(e));
135
+ }
136
+ });
137
+ program
138
+ .command('stats <queue>')
139
+ .action((queue) => {
140
+ try {
141
+ out((0, commands_js_1.stats)(queue));
142
+ }
143
+ catch (e) {
144
+ fail(e instanceof Error ? e.message : String(e));
145
+ }
146
+ });
147
+ program
148
+ .command('reap <queue> [agent-id]')
149
+ .action((queue, agentId) => {
150
+ try {
151
+ out({ reaped: (0, commands_js_1.reap)(queue, agentId) });
152
+ }
153
+ catch (e) {
154
+ fail(e instanceof Error ? e.message : String(e));
155
+ }
156
+ });
157
+ program.parse();
158
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@quantod/qq",
3
+ "version": "0.1.0",
4
+ "description": "Persistent queue for multi-stage Claude agent pipelines",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=22"
8
+ },
9
+ "main": "./dist/commands.js",
10
+ "exports": {
11
+ ".": "./dist/commands.js"
12
+ },
13
+ "bin": {
14
+ "qq": "./bin/qq"
15
+ },
16
+ "files": [
17
+ "bin",
18
+ "dist",
19
+ "src"
20
+ ],
21
+ "dependencies": {
22
+ "better-sqlite3": "^12.0.0",
23
+ "commander": "^13.1.0",
24
+ "js-yaml": "^4.1.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/better-sqlite3": "^7.6.0",
28
+ "@types/js-yaml": "^4.0.9",
29
+ "@types/node": "^22.0.0",
30
+ "typescript": "^5.8.3",
31
+ "vitest": "^3.2.0"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc --project tsconfig.build.json",
35
+ "test": "vitest run",
36
+ "lint": "tsc --noEmit"
37
+ }
38
+ }
@@ -0,0 +1,284 @@
1
+ import { rmSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { randomBytes } from 'node:crypto';
4
+ import type Database from 'better-sqlite3';
5
+ import { openDb, inTransaction } from './db.js';
6
+
7
+ // ── types ──────────────────────────────────────────────────────────────────────
8
+
9
+ export class QQError extends Error {
10
+ constructor(message: string) {
11
+ super(message);
12
+ this.name = 'QQError';
13
+ }
14
+ }
15
+
16
+ export interface Message {
17
+ id: string;
18
+ subqueue: string;
19
+ claimed: boolean;
20
+ claimed_by: string | null;
21
+ seq: number;
22
+ created: number;
23
+ last_modified: number;
24
+ payload?: string | null;
25
+ }
26
+
27
+ export interface FilterOptions {
28
+ claimed?: boolean;
29
+ claimed_by?: string;
30
+ ids?: string[];
31
+ created_after?: number;
32
+ created_before?: number;
33
+ modified_after?: number;
34
+ limit?: number;
35
+ offset?: number;
36
+ }
37
+
38
+ export interface ReleaseOptions {
39
+ target?: string;
40
+ payload?: string;
41
+ replace?: boolean;
42
+ }
43
+
44
+ // ── internal helpers ───────────────────────────────────────────────────────────
45
+
46
+ function parsePath(path: string): { queue: string; subqueue: string | undefined } {
47
+ const slash = path.indexOf('/');
48
+ if (slash === -1) return { queue: path, subqueue: undefined };
49
+ return { queue: path.slice(0, slash), subqueue: path.slice(slash + 1) };
50
+ }
51
+
52
+ function toGlob(pattern: string): string {
53
+ return pattern.replace(/\*/g, '%').replace(/\?/g, '_');
54
+ }
55
+
56
+ function nextSeq(db: Database.Database): number {
57
+ const row = db.prepare('SELECT MAX(seq) as m FROM messages').get() as { m: number | null };
58
+ return (row.m ?? 0) + 1;
59
+ }
60
+
61
+ function rowToMessage(row: Record<string, unknown>, includePayload: boolean): Message {
62
+ const msg: Message = {
63
+ id: row.id as string,
64
+ subqueue: row.subqueue as string,
65
+ claimed: Boolean(row.claimed),
66
+ claimed_by: (row.claimed_by as string | null) ?? null,
67
+ seq: row.seq as number,
68
+ created: row.created as number,
69
+ last_modified: row.last_modified as number,
70
+ };
71
+ if (includePayload) msg.payload = (row.payload as string | null) ?? null;
72
+ return msg;
73
+ }
74
+
75
+ // ── makeQueue ─────────────────────────────────────────────────────────────────
76
+
77
+ export function makeQueue(nameSuffix?: string): string {
78
+ const now = new Date();
79
+ const mm = String(now.getMonth() + 1).padStart(2, '0');
80
+ const dd = String(now.getDate()).padStart(2, '0');
81
+ const suffix = nameSuffix ?? randomBytes(3).toString('hex');
82
+ const name = `${mm}${dd}_${suffix}`;
83
+ const db = openDb(name);
84
+ db.close();
85
+ return name;
86
+ }
87
+
88
+ // ── deleteQueue ───────────────────────────────────────────────────────────────
89
+
90
+ export function deleteQueue(queue: string): void {
91
+ rmSync(join(process.cwd(), '.claude', 'qq', `${queue}.db`), { force: true });
92
+ }
93
+
94
+ // ── push ──────────────────────────────────────────────────────────────────────
95
+
96
+ export function push(path: string, id: string, payload?: string): void {
97
+ const { queue, subqueue } = parsePath(path);
98
+ if (!subqueue) throw new QQError('path must include a subqueue: queue/subqueue');
99
+
100
+ const db = openDb(queue);
101
+ try {
102
+ inTransaction(db, () => {
103
+ const existing = db.prepare('SELECT id FROM messages WHERE id = ?').get(id);
104
+ if (existing) throw new QQError(`duplicate id: ${id}`);
105
+ const seq = nextSeq(db);
106
+ const now = Date.now();
107
+ db.prepare(
108
+ 'INSERT INTO messages (id, subqueue, claimed, seq, payload, created, last_modified) VALUES (?, ?, 0, ?, ?, ?, ?)'
109
+ ).run(id, subqueue, seq, payload ?? null, now, now);
110
+ });
111
+ } finally {
112
+ db.close();
113
+ }
114
+ }
115
+
116
+ // ── claim ─────────────────────────────────────────────────────────────────────
117
+
118
+ export function claim(path: string, options: { agentId?: string } = {}): Message | null {
119
+ const { queue, subqueue } = parsePath(path);
120
+ if (!subqueue) throw new QQError('path must include a subqueue: queue/subqueue');
121
+
122
+ const db = openDb(queue);
123
+ try {
124
+ return inTransaction(db, () => {
125
+ const pattern = toGlob(subqueue);
126
+ const row = db.prepare(
127
+ 'SELECT * FROM messages WHERE subqueue LIKE ? AND claimed = 0 ORDER BY seq ASC LIMIT 1'
128
+ ).get(pattern) as Record<string, unknown> | undefined;
129
+
130
+ if (!row) return null;
131
+
132
+ const now = Date.now();
133
+ db.prepare(
134
+ 'UPDATE messages SET claimed = 1, claimed_by = ?, last_modified = ? WHERE id = ?'
135
+ ).run(options.agentId ?? null, now, row.id as string);
136
+
137
+ return rowToMessage({ ...row, claimed: 1, claimed_by: options.agentId ?? null }, true);
138
+ });
139
+ } finally {
140
+ db.close();
141
+ }
142
+ }
143
+
144
+ // ── release ───────────────────────────────────────────────────────────────────
145
+
146
+ export function release(queue: string, id: string, options: ReleaseOptions = {}): void {
147
+ const db = openDb(queue);
148
+ try {
149
+ inTransaction(db, () => {
150
+ const row = db.prepare('SELECT * FROM messages WHERE id = ?').get(id) as Record<string, unknown> | undefined;
151
+ if (!row) throw new QQError(`message not found: ${id}`);
152
+ if (!row.claimed) throw new QQError(`message not claimed: ${id}`);
153
+
154
+ const seq = nextSeq(db);
155
+ const now = Date.now();
156
+ const newSubqueue = options.target ?? (row.subqueue as string);
157
+
158
+ let newPayload: string | null;
159
+ if (options.payload !== undefined) {
160
+ newPayload = options.replace
161
+ ? options.payload
162
+ : [row.payload as string | null, options.payload].filter(Boolean).join('\n');
163
+ } else {
164
+ newPayload = (row.payload as string | null) ?? null;
165
+ }
166
+
167
+ db.prepare(
168
+ 'UPDATE messages SET claimed = 0, claimed_by = NULL, seq = ?, subqueue = ?, payload = ?, last_modified = ? WHERE id = ?'
169
+ ).run(seq, newSubqueue, newPayload, now, id);
170
+ });
171
+ } finally {
172
+ db.close();
173
+ }
174
+ }
175
+
176
+ // ── list / batchRead ──────────────────────────────────────────────────────────
177
+
178
+ function queryMessages(path: string, filters: FilterOptions, includePayload: boolean): Message[] {
179
+ const { queue, subqueue } = parsePath(path);
180
+ const db = openDb(queue);
181
+ try {
182
+ const conditions: string[] = [];
183
+ const params: (string | number | null)[] = [];
184
+
185
+ if (subqueue) {
186
+ conditions.push('subqueue LIKE ?');
187
+ params.push(toGlob(subqueue));
188
+ }
189
+ if (filters.claimed !== undefined) {
190
+ conditions.push('claimed = ?');
191
+ params.push(filters.claimed ? 1 : 0);
192
+ }
193
+ if (filters.claimed_by !== undefined) {
194
+ conditions.push('claimed_by = ?');
195
+ params.push(filters.claimed_by);
196
+ }
197
+ if (filters.ids?.length) {
198
+ conditions.push(`id IN (${filters.ids.map(() => '?').join(', ')})`);
199
+ params.push(...filters.ids);
200
+ }
201
+ if (filters.created_after !== undefined) {
202
+ conditions.push('created > ?');
203
+ params.push(filters.created_after);
204
+ }
205
+ if (filters.created_before !== undefined) {
206
+ conditions.push('created < ?');
207
+ params.push(filters.created_before);
208
+ }
209
+ if (filters.modified_after !== undefined) {
210
+ conditions.push('last_modified > ?');
211
+ params.push(filters.modified_after);
212
+ }
213
+
214
+ let sql = 'SELECT * FROM messages';
215
+ if (conditions.length) sql += ' WHERE ' + conditions.join(' AND ');
216
+ sql += ' ORDER BY seq ASC';
217
+
218
+ if (filters.limit !== undefined) {
219
+ sql += ' LIMIT ?';
220
+ params.push(filters.limit);
221
+ } else if (filters.offset !== undefined) {
222
+ sql += ' LIMIT -1';
223
+ }
224
+ if (filters.offset !== undefined) {
225
+ sql += ' OFFSET ?';
226
+ params.push(filters.offset);
227
+ }
228
+
229
+ const rows = db.prepare(sql).all(...params) as Record<string, unknown>[];
230
+ return rows.map(r => rowToMessage(r, includePayload));
231
+ } finally {
232
+ db.close();
233
+ }
234
+ }
235
+
236
+ export function list(path: string, filters: FilterOptions = {}): Message[] {
237
+ return queryMessages(path, filters, false);
238
+ }
239
+
240
+ export function batchRead(path: string, filters: FilterOptions = {}): Message[] {
241
+ return queryMessages(path, filters, true);
242
+ }
243
+
244
+ // ── stats ─────────────────────────────────────────────────────────────────────
245
+
246
+ export function stats(queue: string): Record<string, { total: number; claimed: number }> {
247
+ const db = openDb(queue);
248
+ try {
249
+ const rows = db.prepare(
250
+ 'SELECT subqueue, claimed, COUNT(*) as count FROM messages GROUP BY subqueue, claimed'
251
+ ).all() as { subqueue: string; claimed: number; count: number }[];
252
+
253
+ const result: Record<string, { total: number; claimed: number }> = {};
254
+ for (const row of rows) {
255
+ if (!result[row.subqueue]) result[row.subqueue] = { total: 0, claimed: 0 };
256
+ result[row.subqueue].total += row.count;
257
+ if (row.claimed) result[row.subqueue].claimed += row.count;
258
+ }
259
+ return result;
260
+ } finally {
261
+ db.close();
262
+ }
263
+ }
264
+
265
+ // ── reap ──────────────────────────────────────────────────────────────────────
266
+
267
+ export function reap(queue: string, agentId?: string): number {
268
+ const db = openDb(queue);
269
+ try {
270
+ return inTransaction(db, () => {
271
+ const now = Date.now();
272
+ const result = agentId
273
+ ? db.prepare(
274
+ 'UPDATE messages SET claimed = 0, claimed_by = NULL, last_modified = ? WHERE claimed = 1 AND claimed_by = ?'
275
+ ).run(now, agentId)
276
+ : db.prepare(
277
+ 'UPDATE messages SET claimed = 0, claimed_by = NULL, last_modified = ? WHERE claimed = 1'
278
+ ).run(now);
279
+ return result.changes;
280
+ });
281
+ } finally {
282
+ db.close();
283
+ }
284
+ }
package/src/db.ts ADDED
@@ -0,0 +1,31 @@
1
+ import Database from 'better-sqlite3';
2
+ import { mkdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ const DDL = `
6
+ CREATE TABLE IF NOT EXISTS messages (
7
+ id TEXT PRIMARY KEY,
8
+ subqueue TEXT NOT NULL,
9
+ claimed INTEGER NOT NULL DEFAULT 0,
10
+ seq INTEGER NOT NULL,
11
+ payload TEXT,
12
+ claimed_by TEXT,
13
+ created INTEGER NOT NULL,
14
+ last_modified INTEGER NOT NULL
15
+ );
16
+ CREATE INDEX IF NOT EXISTS idx_claim ON messages (subqueue, claimed, seq);
17
+ `;
18
+
19
+ export function openDb(queueName: string): Database.Database {
20
+ const dir = join(process.cwd(), '.claude', 'qq');
21
+ mkdirSync(dir, { recursive: true });
22
+ const db = new Database(join(dir, `${queueName}.db`));
23
+ db.pragma('journal_mode = WAL');
24
+ db.pragma('busy_timeout = 5000');
25
+ db.exec(DDL);
26
+ return db;
27
+ }
28
+
29
+ export function inTransaction<T>(db: Database.Database, fn: () => T): T {
30
+ return db.transaction(fn).immediate();
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,129 @@
1
+ import { Command, InvalidArgumentError } from 'commander';
2
+ import { readFileSync } from 'node:fs';
3
+ import { dump } from 'js-yaml';
4
+ import {
5
+ makeQueue, deleteQueue, push, claim, release,
6
+ list, batchRead, stats, reap, QQError,
7
+ type FilterOptions, type ReleaseOptions,
8
+ } from './commands.js';
9
+
10
+ function out(data: unknown): void {
11
+ process.stdout.write(dump(data, { lineWidth: -1 }));
12
+ }
13
+
14
+ function fail(message: string): never {
15
+ process.stderr.write(`error: ${message}\n`);
16
+ process.exit(1);
17
+ }
18
+
19
+ function readStdin(): string | null {
20
+ if (process.stdin.isTTY) return null;
21
+ const raw = readFileSync(0, 'utf8').trim();
22
+ return raw || null;
23
+ }
24
+
25
+ function parseIntArg(value: string): number {
26
+ const n = parseInt(value, 10);
27
+ if (isNaN(n)) throw new InvalidArgumentError('expected an integer');
28
+ return n;
29
+ }
30
+
31
+ function collectFilters(opts: Record<string, unknown>): FilterOptions {
32
+ const f: FilterOptions = {};
33
+ if (opts['claimed'] !== undefined) f.claimed = opts['claimed'] === '1';
34
+ if (opts['claimedBy']) f.claimed_by = opts['claimedBy'] as string;
35
+ if (opts['ids']) f.ids = (opts['ids'] as string).split(',');
36
+ if (opts['createdAfter']) f.created_after = opts['createdAfter'] as number;
37
+ if (opts['createdBefore']) f.created_before = opts['createdBefore'] as number;
38
+ if (opts['modifiedAfter']) f.modified_after = opts['modifiedAfter'] as number;
39
+ if (opts['limit'] !== undefined) f.limit = opts['limit'] as number;
40
+ if (opts['offset'] !== undefined) f.offset = opts['offset'] as number;
41
+ return f;
42
+ }
43
+
44
+ function addFilterOptions(cmd: Command): Command {
45
+ return cmd
46
+ .option('--claimed <0|1>')
47
+ .option('--claimed-by <agent>')
48
+ .option('--ids <id1,id2,...>')
49
+ .option('--created-after <epoch>', '', parseIntArg)
50
+ .option('--created-before <epoch>', '', parseIntArg)
51
+ .option('--modified-after <epoch>', '', parseIntArg)
52
+ .option('--limit <n>', '', parseIntArg)
53
+ .option('--offset <n>', '', parseIntArg);
54
+ }
55
+
56
+ const program = new Command();
57
+ program.name('qq').description('Persistent queue for Claude agent pipelines');
58
+
59
+ program
60
+ .command('make-queue')
61
+ .option('--name <suffix>')
62
+ .action((opts) => {
63
+ try { out({ queue: makeQueue(opts.name) }); }
64
+ catch (e) { fail(e instanceof Error ? e.message : String(e)); }
65
+ });
66
+
67
+ program
68
+ .command('delete-queue <queue>')
69
+ .action((queue) => {
70
+ try { deleteQueue(queue); }
71
+ catch (e) { fail(e instanceof Error ? e.message : String(e)); }
72
+ });
73
+
74
+ program
75
+ .command('push <path> <id>')
76
+ .action((path, id) => {
77
+ try { push(path, id, readStdin() ?? undefined); out({ ok: true }); }
78
+ catch (e) { e instanceof QQError ? fail(e.message) : fail(String(e)); }
79
+ });
80
+
81
+ program
82
+ .command('claim <path>')
83
+ .option('--agent-id <id>')
84
+ .action((path, opts) => {
85
+ try { out(claim(path, { agentId: opts.agentId }) ?? { empty: true }); }
86
+ catch (e) { fail(e instanceof Error ? e.message : String(e)); }
87
+ });
88
+
89
+ program
90
+ .command('release <queue> <id>')
91
+ .option('--target <subqueue>')
92
+ .option('--payload', 'read payload from stdin')
93
+ .option('--replace')
94
+ .action((queue, id, opts) => {
95
+ try {
96
+ const releaseOpts: ReleaseOptions = { target: opts.target, replace: opts.replace };
97
+ if (opts.payload) releaseOpts.payload = readStdin() ?? undefined;
98
+ release(queue, id, releaseOpts);
99
+ out({ ok: true });
100
+ } catch (e) { e instanceof QQError ? fail(e.message) : fail(String(e)); }
101
+ });
102
+
103
+ addFilterOptions(program.command('list <path>'))
104
+ .action((path, opts) => {
105
+ try { out(list(path, collectFilters(opts))); }
106
+ catch (e) { fail(e instanceof Error ? e.message : String(e)); }
107
+ });
108
+
109
+ addFilterOptions(program.command('batch-read <path>'))
110
+ .action((path, opts) => {
111
+ try { out(batchRead(path, collectFilters(opts))); }
112
+ catch (e) { fail(e instanceof Error ? e.message : String(e)); }
113
+ });
114
+
115
+ program
116
+ .command('stats <queue>')
117
+ .action((queue) => {
118
+ try { out(stats(queue)); }
119
+ catch (e) { fail(e instanceof Error ? e.message : String(e)); }
120
+ });
121
+
122
+ program
123
+ .command('reap <queue> [agent-id]')
124
+ .action((queue, agentId) => {
125
+ try { out({ reaped: reap(queue, agentId) }); }
126
+ catch (e) { fail(e instanceof Error ? e.message : String(e)); }
127
+ });
128
+
129
+ program.parse();