thingd-cli 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -132,14 +132,22 @@ thingd mcp-http --path ./thingd.db --driver native --port 8757 --auth-token chan
132
132
 
133
133
  ### Command Reference
134
134
 
135
- **Metrics & Discovery**
135
+ **System & Diagnostics**
136
136
  ```bash
137
- thingd metrics # Get total counts (objects, events, activeJobs, deadJobs)
137
+ thingd doctor # Run Node, native binding, and remote connectivity diagnostics
138
+ thingd metrics # Get total database counts (objects, events, activeJobs, deadJobs)
138
139
  thingd status # Check cluster health (requires --url)
139
140
  thingd tools # List available MCP tools (requires --url)
140
- thingd collections list # List all collection names
141
- thingd streams list # List all stream names
142
- thingd queues list-all # List all queue names
141
+ thingd bench rust --smoke # Run Rust SQLite engine smoke benchmarks
142
+ thingd bench rust --count 500 # Run Rust SQLite engine benchmarks with specific run count
143
+ ```
144
+
145
+ **Discovery & Collections**
146
+ ```bash
147
+ thingd collections list # List all active collections
148
+ thingd streams list # List all active stream names (alias for events streams)
149
+ thingd events streams # List all active event streams
150
+ thingd queues list-all # List all active queue names
143
151
  ```
144
152
 
145
153
  **Search**
@@ -149,10 +157,11 @@ thingd search "my query" [--collection <name>] [--limit <n>]
149
157
 
150
158
  **Objects**
151
159
  ```bash
152
- thingd objects put decisions rust-core --text "Use Rust for the core engine."
153
- thingd objects put decisions rust-core --data '{"status":"active"}'
154
- thingd objects get decisions rust-core
155
- thingd objects delete decisions rust-core
160
+ thingd objects list decisions # List all objects inside a collection
161
+ thingd objects put decisions core --text "msg" # Put object with plain text content
162
+ thingd objects put decisions core --data '{"a":1}' # Put object with arbitrary JSON data
163
+ thingd objects get decisions core # Fetch a specific object by ID
164
+ thingd objects delete decisions core # Delete an object by ID
156
165
  ```
157
166
 
158
167
  **Events**
@@ -163,6 +172,7 @@ thingd events list project:thingd
163
172
 
164
173
  **Queues**
165
174
  ```bash
175
+ thingd queues stats embed # View queue statistics (ready, leased, dead jobs)
166
176
  thingd queues push embed --payload '{"object":"docs/readme"}'
167
177
  thingd queues claim embed
168
178
  thingd queues ack embed <jobId>
@@ -172,3 +182,4 @@ thingd queues dead embed
172
182
  ```
173
183
 
174
184
 
185
+
@@ -0,0 +1,3 @@
1
+ import { type CliContext } from "./index.js";
2
+ export declare function runDoctor(context: CliContext): Promise<void>;
3
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,YAAY,CAAC;AAEhE,wBAAsB,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAsMlE"}
package/dist/doctor.js ADDED
@@ -0,0 +1,164 @@
1
+ import { existsSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { homedir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ import pc from "picocolors";
6
+ import { resolveConnection } from "./index.js";
7
+ export async function runDoctor(context) {
8
+ context.stderr.write(`\n${pc.bold("thingd doctor")}\n`);
9
+ context.stderr.write(`${pc.dim("Running system diagnostics and connectivity tests...")}\n\n`);
10
+ let healthy = true;
11
+ // 1. Check Node version
12
+ const nodeVersion = process.version;
13
+ const majorVersion = Number.parseInt(nodeVersion.slice(1).split(".")[0] ?? "0", 10);
14
+ if (majorVersion >= 20) {
15
+ context.stderr.write(` ${pc.green("✓")} Node version: ${pc.cyan(nodeVersion)} (OK)\n`);
16
+ }
17
+ else {
18
+ healthy = false;
19
+ context.stderr.write(` ${pc.red("×")} Node version: ${pc.yellow(nodeVersion)} (Requires >= v20.x)\n`);
20
+ }
21
+ // 2. Resolve connection options
22
+ const connection = resolveConnection(context);
23
+ // 3. Native Driver Checks
24
+ if (connection.driver === "native") {
25
+ const customPath = process.env.THINGD_NATIVE_PATH;
26
+ if (customPath) {
27
+ if (existsSync(customPath)) {
28
+ try {
29
+ const require = createRequire(import.meta.url);
30
+ const binding = require(customPath);
31
+ if (binding?.NativeThingStore) {
32
+ context.stderr.write(` ${pc.green("✓")} Native Binding: ${pc.cyan("Loaded via THINGD_NATIVE_PATH")} (${pc.dim(customPath)})\n`);
33
+ }
34
+ else {
35
+ healthy = false;
36
+ context.stderr.write(` ${pc.red("×")} Native Binding: ${pc.yellow("Loaded but missing NativeThingStore export")} (${pc.dim(customPath)})\n`);
37
+ }
38
+ }
39
+ catch (error) {
40
+ healthy = false;
41
+ context.stderr.write(` ${pc.red("×")} Native Binding: ${pc.yellow(`Failed to load: ${error instanceof Error ? error.message : String(error)}`)} (${pc.dim(customPath)})\n`);
42
+ }
43
+ }
44
+ else {
45
+ healthy = false;
46
+ context.stderr.write(` ${pc.red("×")} Native Binding: ${pc.yellow("File does not exist")} at THINGD_NATIVE_PATH="${pc.dim(customPath)}"\n`);
47
+ }
48
+ }
49
+ else {
50
+ // Auto-detect sibling binary
51
+ let detectedPath = null;
52
+ try {
53
+ const scriptPath = process.argv[1];
54
+ if (scriptPath) {
55
+ const cliDir = join(resolve(scriptPath), "..", "..");
56
+ const candidates = [
57
+ join(cliDir, "node_modules", "thingd-native", "dist", "thingd_native.node"),
58
+ join(cliDir, "..", "thingd-native", "dist", "thingd_native.node"),
59
+ join(homedir(), "Space/Programming/personal/thingd/packages/thingd-native/dist/thingd_native.node"),
60
+ join(homedir(), "Space/Programming/personal/thingd-cloud/packages/thingd-native/dist/thingd_native.node"),
61
+ ];
62
+ for (const candidate of candidates) {
63
+ if (existsSync(candidate)) {
64
+ detectedPath = candidate;
65
+ break;
66
+ }
67
+ }
68
+ }
69
+ }
70
+ catch {
71
+ // Ignore
72
+ }
73
+ if (detectedPath) {
74
+ try {
75
+ const require = createRequire(import.meta.url);
76
+ const binding = require(detectedPath);
77
+ if (binding?.NativeThingStore) {
78
+ context.stderr.write(` ${pc.green("✓")} Native Binding: ${pc.cyan("Auto-detected and loaded successfully")} (${pc.dim(detectedPath)})\n`);
79
+ }
80
+ else {
81
+ healthy = false;
82
+ context.stderr.write(` ${pc.red("×")} Native Binding: ${pc.yellow("Auto-detected but missing NativeThingStore export")} (${pc.dim(detectedPath)})\n`);
83
+ }
84
+ }
85
+ catch (error) {
86
+ healthy = false;
87
+ context.stderr.write(` ${pc.red("×")} Native Binding: ${pc.yellow(`Failed to load auto-detected binding: ${error instanceof Error ? error.message : String(error)}`)} (${pc.dim(detectedPath)})\n`);
88
+ }
89
+ }
90
+ else {
91
+ healthy = false;
92
+ context.stderr.write(` ${pc.red("×")} Native Binding: ${pc.yellow('Not found. Run "pnpm --filter thingd-native build" or configure THINGD_NATIVE_PATH.')}\n`);
93
+ }
94
+ }
95
+ }
96
+ else {
97
+ context.stderr.write(` ${pc.dim("○")} Native Binding: Skipped (Using driver: "${connection.driver ?? "memory"}")\n`);
98
+ }
99
+ // 4. Remote Sidecar Reachability & Auth Checks
100
+ if (connection.cloud) {
101
+ const rawUrl = connection.path;
102
+ const isLocal = rawUrl.includes("localhost") || rawUrl.includes("127.0.0.1");
103
+ if (!connection.authToken && !isLocal) {
104
+ context.stderr.write(` ${pc.yellow("⚠")} Auth Token: ${pc.yellow("Missing THINGD_AUTH_TOKEN for remote server (might fail)")}\n`);
105
+ }
106
+ else if (connection.authToken) {
107
+ context.stderr.write(` ${pc.green("✓")} Auth Token: ${pc.cyan("Configured")}\n`);
108
+ }
109
+ else {
110
+ context.stderr.write(` ${pc.green("✓")} Auth Token: ${pc.dim("Not required for local sidecar")}\n`);
111
+ }
112
+ try {
113
+ const normalizeUrl = (val) => val.startsWith("thingd://") ? `http://${val.slice("thingd://".length)}` : val;
114
+ const targetUrl = new URL(normalizeUrl(rawUrl));
115
+ if (targetUrl.pathname === "/mcp" || targetUrl.pathname === "") {
116
+ targetUrl.pathname = "/healthz";
117
+ }
118
+ else {
119
+ targetUrl.pathname = `${targetUrl.pathname.replace(/\/mcp$/, "")}/healthz`;
120
+ }
121
+ context.stderr.write(` ${pc.dim("○")} Connectivity: Checking reachability to ${pc.cyan(targetUrl.toString())}...\n`);
122
+ const controller = new AbortController();
123
+ const timeoutId = setTimeout(() => controller.abort(), 3000);
124
+ const headers = {};
125
+ if (connection.authToken) {
126
+ headers.Authorization = `Bearer ${connection.authToken}`;
127
+ }
128
+ try {
129
+ const response = await fetch(targetUrl, {
130
+ signal: controller.signal,
131
+ headers,
132
+ });
133
+ clearTimeout(timeoutId);
134
+ if (response.ok) {
135
+ context.stderr.write(` ${pc.green("✓")} Connectivity: ${pc.cyan("Connected successfully!")} (${pc.dim(`HTTP ${response.status}`)})\n`);
136
+ }
137
+ else {
138
+ healthy = false;
139
+ context.stderr.write(` ${pc.red("×")} Connectivity: ${pc.yellow(`Server responded with non-2xx status`)} (${pc.dim(`HTTP ${response.status}`)})\n`);
140
+ }
141
+ }
142
+ catch (fetchError) {
143
+ clearTimeout(timeoutId);
144
+ healthy = false;
145
+ context.stderr.write(` ${pc.red("×")} Connectivity: ${pc.yellow(`Failed to connect. Connection refused or timed out.`)} (${pc.dim(fetchError instanceof Error ? fetchError.message : String(fetchError))})\n`);
146
+ }
147
+ }
148
+ catch (urlError) {
149
+ healthy = false;
150
+ context.stderr.write(` ${pc.red("×")} Connectivity: ${pc.yellow(`Invalid URL structure: ${urlError instanceof Error ? urlError.message : String(urlError)}`)}\n`);
151
+ }
152
+ }
153
+ else {
154
+ context.stderr.write(` ${pc.green("✓")} Connectivity: ${pc.cyan("Local SQLite Store")} (${pc.dim(connection.path)})\n`);
155
+ }
156
+ // Final Summary Report
157
+ context.stderr.write("\n");
158
+ if (healthy) {
159
+ context.stderr.write(` ${pc.bold(pc.green("Diagnosis: Everything looks healthy!"))}\n\n`);
160
+ }
161
+ else {
162
+ context.stderr.write(` ${pc.bold(pc.yellow("Diagnosis: Some items require attention (see errors above)."))}\n\n`);
163
+ }
164
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAOA,OAAO,EAOL,MAAM,EACN,KAAK,YAAY,EAClB,MAAM,QAAQ,CAAC;AAKhB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEjD,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAmDF,wBAAsB,MAAM,CAC1B,IAAI,WAAwB,EAC5B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAiEjB;AA+UD,wBAAsB,MAAM,CAC1B,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAcf;AA4BD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,GAAG,iBAAiB,CA2BxE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AASA,OAAO,EAOL,MAAM,EACN,KAAK,YAAY,EAClB,MAAM,QAAQ,CAAC;AAKhB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEjD,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AA2DF,wBAAsB,MAAM,CAC1B,IAAI,WAAwB,EAC5B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAiEjB;AA8jBD,wBAAsB,MAAM,CAC1B,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAcf;AA4BD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,GAAG,iBAAiB,CA2BxE"}
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ import { resolve } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
6
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
7
+ import Table from "cli-table3";
8
+ import pc from "picocolors";
7
9
  import { ThingD, } from "thingd";
8
10
  import { runInteractiveCli } from "./interactive.js";
9
11
  import { runMcp } from "./mcp.js";
@@ -15,25 +17,31 @@ Admin and operator CLI for thingd.
15
17
  Usage:
16
18
  thingd status [--url <url>]
17
19
  thingd tools --url <url>
18
- thingd install [--raw] [--claude] [--cursor]
20
+ thingd install [--raw] [--claude] [--cursor] [--antigravity]
21
+ thingd doctor
19
22
  thingd mcp [--path <path>] [--driver <driver>]
20
23
  thingd mcp-http [--path <path>] [--driver <driver>] [--host <host>] [--port <port>] [--auth-token <tok>] [--allow-unauthenticated]
21
24
  thingd search <query> [--collection <name>] [--limit <n>]
25
+ thingd objects list <collection>
22
26
  thingd objects get <collection> <id>
23
27
  thingd objects put <collection> <id> --text <text>
24
28
  thingd objects put <collection> <id> --data '{"field":"value"}'
25
29
  thingd objects delete <collection> <id>
30
+ thingd events streams
26
31
  thingd events append <stream> <type> [--text <text>] [--data '{"field":"value"}']
27
32
  thingd events list [stream] [--limit <n>]
28
33
  thingd collections list
29
34
  thingd streams list
30
35
  thingd queues list-all
36
+ thingd queues stats <queue>
31
37
  thingd queues push <queue> --payload '{"key":"value"}'
32
38
  thingd queues claim <queue> [--lease-ms <ms>]
33
39
  thingd queues ack <queue> <jobId>
34
40
  thingd queues nack <queue> <jobId> [--error <message>] [--delay-ms <ms>]
35
41
  thingd queues list <queue> [--limit <n>]
36
42
  thingd queues dead <queue> [--limit <n>]
43
+ thingd bench rust --smoke
44
+ thingd bench rust --count <n>
37
45
  thingd metrics
38
46
 
39
47
  Options:
@@ -54,6 +62,8 @@ const BOOLEAN_FLAGS = new Set([
54
62
  "raw",
55
63
  "claude",
56
64
  "cursor",
65
+ "antigravity",
66
+ "smoke",
57
67
  ]);
58
68
  export async function runCli(args = process.argv.slice(2), options = {}) {
59
69
  // Auto-detect and set THINGD_NATIVE_PATH if not already set, to allow global execution
@@ -136,6 +146,15 @@ async function runCommand(context) {
136
146
  await runInstall(context);
137
147
  return;
138
148
  }
149
+ if (command === "doctor") {
150
+ const { runDoctor } = await import("./doctor.js");
151
+ await runDoctor(context);
152
+ return;
153
+ }
154
+ if (command === "bench") {
155
+ await runBench(context);
156
+ return;
157
+ }
139
158
  if (command === "objects") {
140
159
  await runObjects(context);
141
160
  return;
@@ -162,6 +181,63 @@ async function runCommand(context) {
162
181
  }
163
182
  throw new Error(`Unknown command: ${command}`);
164
183
  }
184
+ async function runBench(context) {
185
+ const target = requiredToken(context.parsed, 1, "benchmark target (rust)");
186
+ if (target !== "rust") {
187
+ throw new Error(`Unsupported benchmark target: ${target}`);
188
+ }
189
+ const isSmoke = hasFlag(context.parsed, "smoke");
190
+ const countStr = stringFlag(context.parsed, "count");
191
+ const count = countStr ? Number.parseInt(countStr, 10) : isSmoke ? 100 : undefined;
192
+ if (count === undefined) {
193
+ throw new Error("bench rust requires --smoke or --count <n>");
194
+ }
195
+ if (Number.isNaN(count) || count <= 0) {
196
+ throw new Error("--count must be a positive integer");
197
+ }
198
+ try {
199
+ const { execSync } = await import("node:child_process");
200
+ try {
201
+ execSync("cargo --version", { stdio: "ignore" });
202
+ }
203
+ catch {
204
+ throw new Error("Rust toolchain (cargo) is not installed or not in the PATH. Cannot run Rust benchmarks.");
205
+ }
206
+ context.stderr.write(`\n${pc.bold("Running Rust storage benchmark")} (Count: ${pc.cyan(count)})...\n\n`);
207
+ const { spawn } = await import("node:child_process");
208
+ const child = spawn("cargo", [
209
+ "run",
210
+ "--release",
211
+ "-p",
212
+ "thingd-core",
213
+ "--example",
214
+ "storage_bench",
215
+ "--features",
216
+ "sqlite",
217
+ "--",
218
+ String(count),
219
+ ], {
220
+ stdio: "inherit",
221
+ cwd: resolve(resolveCliPath(), "../../../.."),
222
+ });
223
+ return new Promise((resolvePromise, rejectPromise) => {
224
+ child.on("close", (code) => {
225
+ if (code === 0) {
226
+ resolvePromise();
227
+ }
228
+ else {
229
+ rejectPromise(new Error(`Benchmark failed with exit code: ${code}`));
230
+ }
231
+ });
232
+ child.on("error", (error) => {
233
+ rejectPromise(error);
234
+ });
235
+ });
236
+ }
237
+ catch (err) {
238
+ throw new Error(`Failed to run benchmark: ${err instanceof Error ? err.message : String(err)}`);
239
+ }
240
+ }
165
241
  async function runStatus(context) {
166
242
  const connection = resolveConnection(context);
167
243
  if (!connection.cloud) {
@@ -233,8 +309,32 @@ async function runSearch(context) {
233
309
  async function runObjects(context) {
234
310
  const action = requiredToken(context.parsed, 1, "objects action");
235
311
  const collection = requiredToken(context.parsed, 2, "collection");
236
- const id = requiredToken(context.parsed, 3, "object id");
237
312
  await withDb(context, async (db) => {
313
+ if (action === "list") {
314
+ const objects = await db.listObjects(collection);
315
+ if (context.pretty) {
316
+ const table = new Table({
317
+ head: ["ID", "Version", "Created At", "Updated At", "Data"],
318
+ style: { head: ["green"] },
319
+ });
320
+ for (const obj of objects) {
321
+ const { id, collection: _, createdAt, updatedAt, version, ...data } = obj;
322
+ table.push([
323
+ id,
324
+ String(version),
325
+ createdAt ? new Date(createdAt).toLocaleString() : "",
326
+ updatedAt ? new Date(updatedAt).toLocaleString() : "",
327
+ JSON.stringify(data),
328
+ ]);
329
+ }
330
+ context.stdout.write(`${table.toString()}\n`);
331
+ }
332
+ else {
333
+ writeJson(context.stdout, objects, false);
334
+ }
335
+ return;
336
+ }
337
+ const id = requiredToken(context.parsed, 3, "object id");
238
338
  if (action === "get") {
239
339
  writeJson(context.stdout, await db.get(collection, id), context.pretty);
240
340
  return;
@@ -254,10 +354,47 @@ async function runObjects(context) {
254
354
  async function runEvents(context) {
255
355
  const action = requiredToken(context.parsed, 1, "events action");
256
356
  await withDb(context, async (db) => {
357
+ if (action === "streams") {
358
+ const streams = await db.listStreams();
359
+ if (context.pretty) {
360
+ const table = new Table({
361
+ head: ["Stream Name"],
362
+ style: { head: ["green"] },
363
+ });
364
+ for (const str of streams) {
365
+ table.push([str]);
366
+ }
367
+ context.stdout.write(`${table.toString()}\n`);
368
+ }
369
+ else {
370
+ writeJson(context.stdout, streams, false);
371
+ }
372
+ return;
373
+ }
257
374
  if (action === "list") {
258
375
  const stream = optionalToken(context.parsed, 2);
259
- const events = await db.events.list(stream);
260
- writeJson(context.stdout, limitItems(events, optionalInt(context.parsed, "limit")), context.pretty);
376
+ const events = limitItems(await db.events.list(stream), optionalInt(context.parsed, "limit"));
377
+ if (context.pretty) {
378
+ const table = new Table({
379
+ head: ["Event ID", "Stream", "Event Type", "Created At", "Text", "Data"],
380
+ style: { head: ["green"] },
381
+ });
382
+ for (const ev of events) {
383
+ const { id, stream: evStream, type, createdAt, text, ...data } = ev;
384
+ table.push([
385
+ id,
386
+ evStream,
387
+ type,
388
+ createdAt ? new Date(createdAt).toLocaleString() : "",
389
+ text ?? "",
390
+ JSON.stringify(data),
391
+ ]);
392
+ }
393
+ context.stdout.write(`${table.toString()}\n`);
394
+ }
395
+ else {
396
+ writeJson(context.stdout, events, false);
397
+ }
261
398
  return;
262
399
  }
263
400
  if (action === "append") {
@@ -274,7 +411,20 @@ async function runCollections(context) {
274
411
  const action = requiredToken(context.parsed, 1, "collections action");
275
412
  await withDb(context, async (db) => {
276
413
  if (action === "list") {
277
- writeJson(context.stdout, await db.listCollections(), context.pretty);
414
+ const collections = await db.listCollections();
415
+ if (context.pretty) {
416
+ const table = new Table({
417
+ head: ["Collection Name"],
418
+ style: { head: ["green"] },
419
+ });
420
+ for (const col of collections) {
421
+ table.push([col]);
422
+ }
423
+ context.stdout.write(`${table.toString()}\n`);
424
+ }
425
+ else {
426
+ writeJson(context.stdout, collections, false);
427
+ }
278
428
  return;
279
429
  }
280
430
  throw new Error(`Unknown collections action: ${action}`);
@@ -284,7 +434,20 @@ async function runStreams(context) {
284
434
  const action = requiredToken(context.parsed, 1, "streams action");
285
435
  await withDb(context, async (db) => {
286
436
  if (action === "list") {
287
- writeJson(context.stdout, await db.listStreams(), context.pretty);
437
+ const streams = await db.listStreams();
438
+ if (context.pretty) {
439
+ const table = new Table({
440
+ head: ["Stream Name"],
441
+ style: { head: ["green"] },
442
+ });
443
+ for (const str of streams) {
444
+ table.push([str]);
445
+ }
446
+ context.stdout.write(`${table.toString()}\n`);
447
+ }
448
+ else {
449
+ writeJson(context.stdout, streams, false);
450
+ }
288
451
  return;
289
452
  }
290
453
  throw new Error(`Unknown streams action: ${action}`);
@@ -310,11 +473,50 @@ async function runQueues(context) {
310
473
  const action = requiredToken(context.parsed, 1, "queues action");
311
474
  await withDb(context, async (db) => {
312
475
  if (action === "list-all") {
313
- writeJson(context.stdout, await db.listQueues(), context.pretty);
476
+ const queues = await db.listQueues();
477
+ if (context.pretty) {
478
+ const table = new Table({
479
+ head: ["Queue Name"],
480
+ style: { head: ["green"] },
481
+ });
482
+ for (const q of queues) {
483
+ table.push([q]);
484
+ }
485
+ context.stdout.write(`${table.toString()}\n`);
486
+ }
487
+ else {
488
+ writeJson(context.stdout, queues, false);
489
+ }
314
490
  return;
315
491
  }
316
492
  const queueName = requiredToken(context.parsed, 2, "queue");
317
493
  const queue = db.queue(queueName);
494
+ if (action === "stats") {
495
+ const [activeJobs, deadJobs] = await Promise.all([queue.list(), queue.dead()]);
496
+ const totalActive = activeJobs.length;
497
+ const totalDead = deadJobs.length;
498
+ const leasedJobs = activeJobs.filter((job) => job.status === "leased");
499
+ const readyJobs = activeJobs.filter((job) => job.status === "ready");
500
+ const stats = {
501
+ queue: queueName,
502
+ totalActive,
503
+ ready: readyJobs.length,
504
+ leased: leasedJobs.length,
505
+ dead: totalDead,
506
+ };
507
+ if (context.pretty) {
508
+ const table = new Table({
509
+ head: ["Stat Metric", "Value"],
510
+ style: { head: ["green"] },
511
+ });
512
+ table.push(["Queue Name", queueName], ["Ready Jobs", String(readyJobs.length)], ["Leased Jobs", String(leasedJobs.length)], ["Dead Jobs", String(totalDead)], ["Total Active", String(totalActive)]);
513
+ context.stdout.write(`${table.toString()}\n`);
514
+ }
515
+ else {
516
+ writeJson(context.stdout, stats, false);
517
+ }
518
+ return;
519
+ }
318
520
  if (action === "push") {
319
521
  const payload = parseJsonRecord(requiredFlag(context.parsed, "payload"));
320
522
  const options = {
@@ -345,11 +547,51 @@ async function runQueues(context) {
345
547
  return;
346
548
  }
347
549
  if (action === "list") {
348
- writeJson(context.stdout, limitItems(await queue.list(), optionalInt(context.parsed, "limit")), context.pretty);
550
+ const jobs = limitItems(await queue.list(), optionalInt(context.parsed, "limit"));
551
+ if (context.pretty) {
552
+ const table = new Table({
553
+ head: ["Job ID", "Status", "Attempts", "Max Attempts", "Available At", "Payload"],
554
+ style: { head: ["green"] },
555
+ });
556
+ for (const job of jobs) {
557
+ table.push([
558
+ job.id,
559
+ job.status,
560
+ String(job.attempts),
561
+ String(job.maxAttempts),
562
+ job.availableAt ? new Date(job.availableAt).toLocaleString() : "",
563
+ JSON.stringify(job.payload),
564
+ ]);
565
+ }
566
+ context.stdout.write(`${table.toString()}\n`);
567
+ }
568
+ else {
569
+ writeJson(context.stdout, jobs, false);
570
+ }
349
571
  return;
350
572
  }
351
573
  if (action === "dead") {
352
- writeJson(context.stdout, limitItems(await queue.dead(), optionalInt(context.parsed, "limit")), context.pretty);
574
+ const jobs = limitItems(await queue.dead(), optionalInt(context.parsed, "limit"));
575
+ if (context.pretty) {
576
+ const table = new Table({
577
+ head: ["Job ID", "Attempts", "Max Attempts", "Dead At", "Last Error", "Payload"],
578
+ style: { head: ["green"] },
579
+ });
580
+ for (const job of jobs) {
581
+ table.push([
582
+ job.id,
583
+ String(job.attempts),
584
+ String(job.maxAttempts),
585
+ job.deadAt ? new Date(job.deadAt).toLocaleString() : "",
586
+ job.lastError ?? "",
587
+ JSON.stringify(job.payload),
588
+ ]);
589
+ }
590
+ context.stdout.write(`${table.toString()}\n`);
591
+ }
592
+ else {
593
+ writeJson(context.stdout, jobs, false);
594
+ }
353
595
  return;
354
596
  }
355
597
  throw new Error(`Unknown queues action: ${action}`);
@@ -575,5 +817,14 @@ if (process.argv[1]) {
575
817
  }
576
818
  }
577
819
  if (isMain) {
578
- process.exitCode = await runCli();
820
+ runCli()
821
+ .then((code) => {
822
+ if (code !== 0) {
823
+ process.exit(code);
824
+ }
825
+ })
826
+ .catch((error) => {
827
+ console.error(error);
828
+ process.exit(1);
829
+ });
579
830
  }
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAwB7C,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAqInE"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAwB7C,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA4JnE"}
package/dist/install.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
2
2
  import { homedir, platform } from "node:os";
3
- import { join, resolve } from "node:path";
3
+ import { dirname, join, resolve } from "node:path";
4
4
  import { createInterface } from "node:readline/promises";
5
5
  import pc from "picocolors";
6
6
  import { defaultThingdDbPath, ensureThingdDir } from "./paths.js";
@@ -25,6 +25,7 @@ export async function runInstall(context) {
25
25
  const isRaw = context.parsed.booleans.has("raw") || context.parsed.flags.has("raw");
26
26
  const isClaude = context.parsed.booleans.has("claude") || context.parsed.flags.has("claude");
27
27
  const isCursor = context.parsed.booleans.has("cursor") || context.parsed.flags.has("cursor");
28
+ const isAntigravity = context.parsed.booleans.has("antigravity") || context.parsed.flags.has("antigravity");
28
29
  if (!isRaw) {
29
30
  context.stderr.write(`\n${pc.bold("thingd install")}\n\n`);
30
31
  }
@@ -32,9 +33,9 @@ export async function runInstall(context) {
32
33
  let dbPath = dbPathDefault;
33
34
  let driver = driverDefault;
34
35
  if (isRaw) {
35
- choice = "4";
36
+ choice = "5";
36
37
  }
37
- else if (isClaude && isCursor) {
38
+ else if (isClaude && isCursor && isAntigravity) {
38
39
  choice = "1";
39
40
  }
40
41
  else if (isClaude) {
@@ -43,14 +44,18 @@ export async function runInstall(context) {
43
44
  else if (isCursor) {
44
45
  choice = "3";
45
46
  }
47
+ else if (isAntigravity) {
48
+ choice = "4";
49
+ }
46
50
  else if (process.stdin.isTTY) {
47
51
  // 1. Where to install
48
52
  context.stderr.write(`${pc.bold("Where would you like to install the MCP configuration?")}\n`);
49
- context.stderr.write(` [1] Claude Desktop & Cursor (Default)\n`);
53
+ context.stderr.write(` [1] Claude Desktop, Cursor & Antigravity (Default)\n`);
50
54
  context.stderr.write(` [2] Claude Desktop only\n`);
51
55
  context.stderr.write(` [3] Cursor only\n`);
52
- context.stderr.write(` [4] Print raw JSON configuration only\n\n`);
53
- const answerInstall = await askQuestion(`Select option [1-4] (default 1): `);
56
+ context.stderr.write(` [4] Antigravity only\n`);
57
+ context.stderr.write(` [5] Print raw JSON configuration only\n\n`);
58
+ const answerInstall = await askQuestion(`Select option [1-5] (default 1): `);
54
59
  choice = answerInstall.trim() || "1";
55
60
  context.stderr.write("\n");
56
61
  // 2. Database Path
@@ -97,7 +102,8 @@ export async function runInstall(context) {
97
102
  }
98
103
  const showClaude = choice === "1" || choice === "2";
99
104
  const showCursor = choice === "1" || choice === "3";
100
- const showRaw = choice === "4";
105
+ const showAntigravity = choice === "1" || choice === "4";
106
+ const showRaw = choice === "5";
101
107
  if (showClaude) {
102
108
  const claudeResult = updateClaudeDesktopConfig(config);
103
109
  if (claudeResult.updated) {
@@ -109,6 +115,17 @@ export async function runInstall(context) {
109
115
  context.stderr.write(` ${pc.yellow("⊘")} Skipped: ${claudeResult.reason}\n\n`);
110
116
  }
111
117
  }
118
+ if (showAntigravity) {
119
+ const antigravityResult = updateAntigravityConfig(config);
120
+ if (antigravityResult.updated) {
121
+ context.stderr.write(` ${pc.bold("Antigravity IDE:")}\n`);
122
+ context.stderr.write(` ${pc.green("✓")} Updated ${pc.cyan(antigravityResult.path)}\n\n`);
123
+ }
124
+ else if (antigravityResult.skipped) {
125
+ context.stderr.write(` ${pc.bold("Antigravity IDE:")}\n`);
126
+ context.stderr.write(` ${pc.yellow("⊘")} Skipped: ${antigravityResult.reason}\n\n`);
127
+ }
128
+ }
112
129
  if (showCursor) {
113
130
  context.stderr.write(` ${pc.bold("Cursor:")}\n`);
114
131
  context.stderr.write(` Paste this into Cursor Settings → Features → MCP → Add New MCP Server:\n\n`);
@@ -127,8 +144,17 @@ export async function runInstall(context) {
127
144
  };
128
145
  context.stdout.write(`${JSON.stringify(fullConfig, null, 2)}\n`);
129
146
  }
130
- if (showClaude || showCursor) {
131
- context.stderr.write(`\n Restart Claude Desktop to activate. Cursor activates immediately after pasting.\n\n`);
147
+ if (choice === "1") {
148
+ context.stderr.write(`\n Restart Claude Desktop or Antigravity to activate. Cursor activates immediately.\n\n`);
149
+ }
150
+ else if (choice === "2") {
151
+ context.stderr.write(`\n Restart Claude Desktop to activate.\n\n`);
152
+ }
153
+ else if (choice === "3") {
154
+ context.stderr.write(`\n Cursor activates immediately after pasting.\n\n`);
155
+ }
156
+ else if (choice === "4") {
157
+ context.stderr.write(`\n Restart Antigravity IDE to activate.\n\n`);
132
158
  }
133
159
  }
134
160
  function findGlobalBinPath() {
@@ -214,3 +240,33 @@ function updateClaudeDesktopConfig(config) {
214
240
  };
215
241
  }
216
242
  }
243
+ function updateAntigravityConfig(config) {
244
+ const configPath = join(homedir(), ".gemini", "antigravity-ide", "mcp_config.json");
245
+ const dir = dirname(configPath);
246
+ if (!existsSync(dir)) {
247
+ return {
248
+ skipped: true,
249
+ reason: `Antigravity directory not found at ${dir}.`,
250
+ };
251
+ }
252
+ try {
253
+ let existing = {};
254
+ if (existsSync(configPath)) {
255
+ const raw = readFileSync(configPath, "utf-8").trim();
256
+ if (raw) {
257
+ existing = JSON.parse(raw);
258
+ }
259
+ }
260
+ const mcpServers = (existing.mcpServers ?? {});
261
+ mcpServers.thingd = config;
262
+ existing.mcpServers = mcpServers;
263
+ writeFileSync(configPath, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
264
+ return { updated: true, path: configPath };
265
+ }
266
+ catch (error) {
267
+ return {
268
+ skipped: true,
269
+ reason: `Failed to update config: ${error instanceof Error ? error.message : String(error)}`,
270
+ };
271
+ }
272
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../src/interactive.ts"],"names":[],"mappings":"AA2jDA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA4BvD"}
1
+ {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../src/interactive.ts"],"names":[],"mappings":"AA4jDA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA4BvD"}
@@ -229,11 +229,12 @@ async function fetchResources() {
229
229
  db.listStreams(),
230
230
  db.listQueues?.() ?? Promise.resolve([]),
231
231
  ]);
232
- totalObjects = isNaN(objCount) || objCount === 0 ? totalObjects : objCount;
233
- totalEventsCount = isNaN(evtCount) || evtCount === 0 ? totalEventsCount : evtCount;
232
+ totalObjects = Number.isNaN(objCount) || objCount === 0 ? totalObjects : objCount;
233
+ totalEventsCount = Number.isNaN(evtCount) || evtCount === 0 ? totalEventsCount : evtCount;
234
234
  totalActiveJobsCount =
235
- isNaN(activeCount) || activeCount === 0 ? totalActiveJobsCount : activeCount;
236
- totalDeadJobsCount = isNaN(deadCount) || deadCount === 0 ? totalDeadJobsCount : deadCount;
235
+ Number.isNaN(activeCount) || activeCount === 0 ? totalActiveJobsCount : activeCount;
236
+ totalDeadJobsCount =
237
+ Number.isNaN(deadCount) || deadCount === 0 ? totalDeadJobsCount : deadCount;
237
238
  collections = nativeCollections.length > 0 ? nativeCollections : ["decisions", "load-test"];
238
239
  streams =
239
240
  nativeStreams.length > 0
@@ -712,9 +713,9 @@ function draw() {
712
713
  else {
713
714
  titleStr = ` thingd ${pc.dim("|")} ${driver.toUpperCase()} ${pc.dim("|")} ${dbPath} `;
714
715
  }
715
- buf += pc.inverse(padToWidth(titleStr, W)) + "\n";
716
+ buf += `${pc.inverse(padToWidth(titleStr, W))}\n`;
716
717
  // Separator
717
- buf += pc.dim("─".repeat(sideW) + "─┬─" + "─".repeat(viewW)) + "\n";
718
+ buf += `${pc.dim(`${"─".repeat(sideW)}─┬─${"─".repeat(viewW)}`)}\n`;
718
719
  // Build Form Lines if active
719
720
  if (formState?.active) {
720
721
  viewerLines = [`${pc.bgCyan(pc.black(` ${formState.title} `))}`, ""];
@@ -783,15 +784,15 @@ function draw() {
783
784
  // Viewer
784
785
  const vLine = viewerLines[r + viewerScroll] ?? "";
785
786
  const right = fitToWidth(vLine, viewW, false);
786
- buf += left + pc.dim(" │ ") + right + "\n";
787
+ buf += `${left + pc.dim(" │ ") + right}\n`;
787
788
  }
788
789
  // Separator
789
- buf += pc.dim("─".repeat(sideW) + "─┴─" + "─".repeat(viewW)) + "\n";
790
+ buf += `${pc.dim(`${"─".repeat(sideW)}─┴─${"─".repeat(viewW)}`)}\n`;
790
791
  // Footer
791
792
  let help;
792
793
  if (formState?.active) {
793
794
  const hasOptions = formState.fields[formState.activeIndex]?.options;
794
- help = ` ${pc.dim("↑↓")} focus ${hasOptions ? pc.dim("←→") + " select " : ""}${pc.dim("enter")} submit ${pc.dim("ctrl+e")} editor ${pc.dim("esc")} cancel `;
795
+ help = ` ${pc.dim("↑↓")} focus ${hasOptions ? `${pc.dim("←→")} select ` : ""}${pc.dim("enter")} submit ${pc.dim("ctrl+e")} editor ${pc.dim("esc")} cancel `;
795
796
  }
796
797
  else if (!connected) {
797
798
  help = ` ${pc.dim("↑↓")} nav ${pc.dim("enter")} connect ${pc.dim("q")} quit `;
@@ -897,7 +898,7 @@ async function launchEditor(f) {
897
898
  const newContent = fs.readFileSync(tmpFile, "utf-8");
898
899
  f.value = newContent.trim();
899
900
  }
900
- catch (e) { }
901
+ catch (_e) { }
901
902
  if (process.stdin.isTTY)
902
903
  process.stdin.setRawMode(true);
903
904
  process.stdin.on("keypress", keypressHandler);
@@ -928,7 +929,7 @@ function parsePayload(str) {
928
929
  v = true;
929
930
  else if (v === "false")
930
931
  v = false;
931
- else if (!isNaN(Number(v)))
932
+ else if (!Number.isNaN(Number(v)))
932
933
  v = Number(v);
933
934
  }
934
935
  obj[k] = v;
@@ -985,8 +986,8 @@ async function handleCreate(selected) {
985
986
  try {
986
987
  id = crypto.randomUUID();
987
988
  }
988
- catch (e) {
989
- id = "obj_" + Date.now().toString(36) + Math.random().toString(36).substring(2);
989
+ catch (_e) {
990
+ id = `obj_${Date.now().toString(36)}${Math.random().toString(36).substring(2)}`;
990
991
  }
991
992
  }
992
993
  const data = parsePayload(vals.payload || "");
@@ -1109,7 +1110,7 @@ async function handleSearch() {
1109
1110
  const options = {};
1110
1111
  if (limitStr) {
1111
1112
  const limit = parseInt(limitStr, 10);
1112
- if (!isNaN(limit))
1113
+ if (!Number.isNaN(limit))
1113
1114
  options.limit = limit;
1114
1115
  }
1115
1116
  const results = await db.search(query, options);
@@ -1147,7 +1148,7 @@ async function handleInfo() {
1147
1148
  const u = new URL(p, urlObj.toString());
1148
1149
  const headers = {};
1149
1150
  if (authToken)
1150
- headers["Authorization"] = `Bearer ${authToken}`;
1151
+ headers.Authorization = `Bearer ${authToken}`;
1151
1152
  const res = await fetch(u, { headers });
1152
1153
  if (!res.ok)
1153
1154
  throw new Error(`HTTP ${res.status}`);
@@ -1232,7 +1233,7 @@ function setupKeypress() {
1232
1233
  }
1233
1234
  else if (key.name === "left" || key.name === "right") {
1234
1235
  const f = formState.fields[formState.activeIndex];
1235
- if (f && f.options && f.options.length > 0) {
1236
+ if (f?.options && f.options.length > 0) {
1236
1237
  const currentIndex = f.options.indexOf(f.value);
1237
1238
  let nextIndex = key.name === "right" ? currentIndex + 1 : currentIndex - 1;
1238
1239
  if (nextIndex < 0)
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,YAAY,CAAC;AAWpB,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAC;CACvC,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,0BAA+B,GACvC,IAAI,CA6WN"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,YAAY,CAAC;AAWpB,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAC;CACvC,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,0BAA+B,GACvC,IAAI,CA+XN"}
package/dist/mcp/tools.js CHANGED
@@ -305,6 +305,19 @@ export function registerThingdTools(server, db, options = {}) {
305
305
  openWorldHint: false,
306
306
  },
307
307
  }, async ({ queue }) => jsonResult(await db.queue(queue).dead()));
308
+ server.registerTool("thing_objects_list", {
309
+ title: "List Objects",
310
+ description: "List all thingd objects in a collection.",
311
+ inputSchema: {
312
+ collection: z.string().min(1),
313
+ },
314
+ annotations: {
315
+ readOnlyHint: true,
316
+ destructiveHint: false,
317
+ idempotentHint: true,
318
+ openWorldHint: false,
319
+ },
320
+ }, async ({ collection }) => jsonResult(await db.listObjects(collection)));
308
321
  }
309
322
  function auditMetadata(actor, source) {
310
323
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thingd-cli",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Command-line interface, Interactive TUI Dashboard, and MCP server for thingd.",
5
5
  "type": "module",
6
6
  "author": "Sayan Mohsin",
@@ -33,7 +33,7 @@
33
33
  "cli-table3": "^0.6.5",
34
34
  "picocolors": "^1.1.1",
35
35
  "zod": "^4.4.3",
36
- "thingd": "0.11.0"
36
+ "thingd": "0.13.0"
37
37
  },
38
38
  "engines": {
39
39
  "node": ">=20"