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 +20 -9
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +164 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +261 -10
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +65 -9
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +17 -16
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +13 -0
- package/package.json +2 -2
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
|
-
**
|
|
135
|
+
**System & Diagnostics**
|
|
136
136
|
```bash
|
|
137
|
-
thingd
|
|
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
|
|
141
|
-
thingd
|
|
142
|
-
|
|
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
|
|
153
|
-
thingd objects put decisions
|
|
154
|
-
thingd objects
|
|
155
|
-
thingd objects
|
|
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
|
+
|
package/dist/doctor.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/install.d.ts.map
CHANGED
|
@@ -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,
|
|
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 = "
|
|
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 &
|
|
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]
|
|
53
|
-
|
|
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
|
|
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 (
|
|
131
|
-
context.stderr.write(`\n Restart Claude Desktop to activate. Cursor activates immediately
|
|
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":"
|
|
1
|
+
{"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../src/interactive.ts"],"names":[],"mappings":"AA4jDA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA4BvD"}
|
package/dist/interactive.js
CHANGED
|
@@ -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 =
|
|
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))
|
|
716
|
+
buf += `${pc.inverse(padToWidth(titleStr, W))}\n`;
|
|
716
717
|
// Separator
|
|
717
|
-
buf += pc.dim("─".repeat(sideW)
|
|
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
|
|
787
|
+
buf += `${left + pc.dim(" │ ") + right}\n`;
|
|
787
788
|
}
|
|
788
789
|
// Separator
|
|
789
|
-
buf += pc.dim("─".repeat(sideW)
|
|
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("←→")
|
|
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 (
|
|
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 (
|
|
989
|
-
id =
|
|
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
|
|
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
|
|
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)
|
package/dist/mcp/tools.d.ts.map
CHANGED
|
@@ -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,
|
|
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.
|
|
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.
|
|
36
|
+
"thingd": "0.13.0"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=20"
|