dimies 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/README.md +35 -0
- package/bin/dimies.js +326 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# dimies
|
|
2
|
+
|
|
3
|
+
Integrate [Dimies](https://dimies.com) — conversation analytics for AI agents — into any codebase in one command.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx dimies init --key dm_live_… # scaffold a tracking helper (Node or Python)
|
|
7
|
+
npx dimies test --key dm_live_… # send a test conversation, verify end-to-end
|
|
8
|
+
npx dimies prompt # print an integration prompt for AI coding agents
|
|
9
|
+
npx dimies spec # print the machine-readable spec (llms.txt)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## The fastest path: let your coding agent do it
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npx dimies prompt | pbcopy
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Paste into Claude Code (or Cursor, etc.) inside the repo you want instrumented. The agent fetches `llms.txt` from your Dimies instance and wires everything: fire-and-forget conversation tracking, trace spans around LLM/tool calls, env vars in `.env.example`.
|
|
19
|
+
|
|
20
|
+
## What `init` does
|
|
21
|
+
|
|
22
|
+
- Detects Node (TS/JS) or Python
|
|
23
|
+
- Writes a zero-dependency `dimies.ts` / `dimies.js` / `dimies.py` helper exporting `trackConversation(...)` / `track_conversation(...)` — fire-and-forget, never throws, never blocks your response path
|
|
24
|
+
- Adds `DIMIES_API_KEY` / `DIMIES_URL` to `.env.example` (never hardcodes your key)
|
|
25
|
+
|
|
26
|
+
## Flags
|
|
27
|
+
|
|
28
|
+
| Flag | Meaning |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `--key dm_live_…` | Dimies API key (or env `DIMIES_API_KEY`) |
|
|
31
|
+
| `--url https://…` | Your Dimies instance URL (or env `DIMIES_URL`) |
|
|
32
|
+
| `--dir path` | Target directory for `init` (default `.`) |
|
|
33
|
+
| `--lang node\|python` | Override language detection |
|
|
34
|
+
|
|
35
|
+
MIT.
|
package/bin/dimies.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Dimies CLI — integrate conversation analytics into any codebase.
|
|
4
|
+
*
|
|
5
|
+
* npx dimies init --key dm_live_… [--url https://…] [--lang node|python] [--dir .]
|
|
6
|
+
* npx dimies test --key dm_live_… [--url https://…]
|
|
7
|
+
* npx dimies prompt [--url https://…] # integration prompt for AI coding agents
|
|
8
|
+
* npx dimies spec [--url https://…] # print the machine-readable spec (llms.txt)
|
|
9
|
+
*
|
|
10
|
+
* Zero dependencies. Node 18+.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const path = require("path");
|
|
15
|
+
|
|
16
|
+
const DEFAULT_URL = process.env.DIMIES_URL || "https://app.dimies.com";
|
|
17
|
+
|
|
18
|
+
/* ----------------------------- arg parsing ----------------------------- */
|
|
19
|
+
|
|
20
|
+
const [, , command, ...rest] = process.argv;
|
|
21
|
+
const flags = {};
|
|
22
|
+
for (let i = 0; i < rest.length; i++) {
|
|
23
|
+
if (rest[i].startsWith("--")) {
|
|
24
|
+
const key = rest[i].slice(2);
|
|
25
|
+
const next = rest[i + 1];
|
|
26
|
+
if (next && !next.startsWith("--")) {
|
|
27
|
+
flags[key] = next;
|
|
28
|
+
i++;
|
|
29
|
+
} else {
|
|
30
|
+
flags[key] = true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const url = String(flags.url || DEFAULT_URL).replace(/\/$/, "");
|
|
36
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
37
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
38
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
39
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
40
|
+
|
|
41
|
+
function die(message) {
|
|
42
|
+
console.error(red(`✖ ${message}`));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* ------------------------------ templates ------------------------------ */
|
|
47
|
+
|
|
48
|
+
const TS_HELPER = (baseUrl) => `/**
|
|
49
|
+
* Dimies — conversation analytics for AI agents.
|
|
50
|
+
* Fire-and-forget: never throws, never blocks your response path.
|
|
51
|
+
* Docs: ${baseUrl}/llms.txt
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
type DimiesMessage = {
|
|
55
|
+
role: "user" | "assistant" | "system" | "tool";
|
|
56
|
+
content: string;
|
|
57
|
+
timestamp?: number | string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type DimiesSpan = {
|
|
61
|
+
id?: string;
|
|
62
|
+
parent_id?: string;
|
|
63
|
+
name: string;
|
|
64
|
+
type?: "llm" | "tool" | "retrieval" | "function" | "other";
|
|
65
|
+
started_at?: number | string;
|
|
66
|
+
ended_at?: number | string;
|
|
67
|
+
input?: unknown;
|
|
68
|
+
output?: unknown;
|
|
69
|
+
model?: string;
|
|
70
|
+
tokens_in?: number;
|
|
71
|
+
tokens_out?: number;
|
|
72
|
+
cost?: number;
|
|
73
|
+
status?: "ok" | "error";
|
|
74
|
+
error?: string;
|
|
75
|
+
metadata?: Record<string, unknown>;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const DIMIES_URL = process.env.DIMIES_URL ?? "${baseUrl}";
|
|
79
|
+
const DIMIES_API_KEY = process.env.DIMIES_API_KEY ?? "";
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Send the FULL conversation so far (idempotent upsert on conversation_id).
|
|
83
|
+
* Call after each exchange, or once with closed: true when it ends.
|
|
84
|
+
*/
|
|
85
|
+
export function trackConversation(options: {
|
|
86
|
+
conversation_id: string;
|
|
87
|
+
messages: DimiesMessage[];
|
|
88
|
+
user_id?: string;
|
|
89
|
+
channel?: string;
|
|
90
|
+
closed?: boolean;
|
|
91
|
+
metadata?: Record<string, unknown>;
|
|
92
|
+
trace?: DimiesSpan[];
|
|
93
|
+
}): void {
|
|
94
|
+
if (!DIMIES_API_KEY) return;
|
|
95
|
+
fetch(\`\${DIMIES_URL}/api/v1/conversations\`, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
Authorization: \`Bearer \${DIMIES_API_KEY}\`,
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify(options),
|
|
102
|
+
}).catch(() => {
|
|
103
|
+
/* analytics must never break the product */
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
const PY_HELPER = (baseUrl) => `"""
|
|
109
|
+
Dimies — conversation analytics for AI agents.
|
|
110
|
+
Fire-and-forget: never raises, never blocks your response path.
|
|
111
|
+
Docs: ${baseUrl}/llms.txt
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
import json
|
|
115
|
+
import os
|
|
116
|
+
import threading
|
|
117
|
+
import urllib.request
|
|
118
|
+
|
|
119
|
+
DIMIES_URL = os.environ.get("DIMIES_URL", "${baseUrl}")
|
|
120
|
+
DIMIES_API_KEY = os.environ.get("DIMIES_API_KEY", "")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def track_conversation(
|
|
124
|
+
conversation_id: str,
|
|
125
|
+
messages: list, # [{"role": "user"|"assistant"|"system"|"tool", "content": str}]
|
|
126
|
+
user_id: str | None = None,
|
|
127
|
+
channel: str | None = None,
|
|
128
|
+
closed: bool = False,
|
|
129
|
+
metadata: dict | None = None,
|
|
130
|
+
trace: list | None = None, # spans: see ${baseUrl}/llms.txt
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Send the FULL conversation so far (idempotent upsert on conversation_id)."""
|
|
133
|
+
if not DIMIES_API_KEY:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
payload = {"conversation_id": conversation_id, "messages": messages, "closed": closed}
|
|
137
|
+
if user_id:
|
|
138
|
+
payload["user_id"] = user_id
|
|
139
|
+
if channel:
|
|
140
|
+
payload["channel"] = channel
|
|
141
|
+
if metadata:
|
|
142
|
+
payload["metadata"] = metadata
|
|
143
|
+
if trace:
|
|
144
|
+
payload["trace"] = trace
|
|
145
|
+
|
|
146
|
+
def _send() -> None:
|
|
147
|
+
try:
|
|
148
|
+
req = urllib.request.Request(
|
|
149
|
+
f"{DIMIES_URL}/api/v1/conversations",
|
|
150
|
+
data=json.dumps(payload).encode(),
|
|
151
|
+
headers={
|
|
152
|
+
"Content-Type": "application/json",
|
|
153
|
+
"Authorization": f"Bearer {DIMIES_API_KEY}",
|
|
154
|
+
},
|
|
155
|
+
method="POST",
|
|
156
|
+
)
|
|
157
|
+
urllib.request.urlopen(req, timeout=5)
|
|
158
|
+
except Exception:
|
|
159
|
+
pass # analytics must never break the product
|
|
160
|
+
|
|
161
|
+
threading.Thread(target=_send, daemon=True).start()
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
const AGENT_PROMPT = (baseUrl) => `Integrate Dimies conversation analytics into this codebase.
|
|
165
|
+
|
|
166
|
+
1. Fetch the integration spec: ${baseUrl}/llms.txt — follow it exactly.
|
|
167
|
+
2. Find where this app exchanges messages with an LLM or serves an AI agent/chatbot.
|
|
168
|
+
3. After each completed exchange (or when a conversation ends), send the FULL
|
|
169
|
+
conversation to POST ${baseUrl}/api/v1/conversations with the Authorization
|
|
170
|
+
header. Prefer a small fire-and-forget helper (never block or throw on the
|
|
171
|
+
response path). If the repo uses the OpenAI or Anthropic SDK directly, the
|
|
172
|
+
zero-code alternative is swapping the client baseURL to the Dimies proxy
|
|
173
|
+
(details in the spec).
|
|
174
|
+
4. Read DIMIES_API_KEY and DIMIES_URL from environment variables; add them
|
|
175
|
+
to .env.example. Never hardcode the key.
|
|
176
|
+
5. If the app has tool calls / retrievals around the LLM call, also send them as
|
|
177
|
+
a "trace" array (span schema in the spec) so executions are debuggable.
|
|
178
|
+
6. Verify: run the app, trigger one conversation, and confirm it appears at
|
|
179
|
+
${baseUrl}/app (or run \`npx dimies test --key $DIMIES_API_KEY\`).
|
|
180
|
+
|
|
181
|
+
My Dimies API key is in the DIMIES_API_KEY environment variable.`;
|
|
182
|
+
|
|
183
|
+
/* ------------------------------ commands ------------------------------- */
|
|
184
|
+
|
|
185
|
+
function detectLang(dir) {
|
|
186
|
+
if (flags.lang) return String(flags.lang);
|
|
187
|
+
if (fs.existsSync(path.join(dir, "package.json"))) return "node";
|
|
188
|
+
if (
|
|
189
|
+
fs.existsSync(path.join(dir, "pyproject.toml")) ||
|
|
190
|
+
fs.existsSync(path.join(dir, "requirements.txt")) ||
|
|
191
|
+
fs.existsSync(path.join(dir, "setup.py"))
|
|
192
|
+
) {
|
|
193
|
+
return "python";
|
|
194
|
+
}
|
|
195
|
+
return "node";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function cmdInit() {
|
|
199
|
+
const dir = path.resolve(String(flags.dir || "."));
|
|
200
|
+
if (!fs.existsSync(dir)) die(`Directory not found: ${dir}`);
|
|
201
|
+
const lang = detectLang(dir);
|
|
202
|
+
|
|
203
|
+
let helperPath;
|
|
204
|
+
if (lang === "python") {
|
|
205
|
+
helperPath = path.join(dir, "dimies.py");
|
|
206
|
+
fs.writeFileSync(helperPath, PY_HELPER(url));
|
|
207
|
+
} else {
|
|
208
|
+
const isTs = fs.existsSync(path.join(dir, "tsconfig.json"));
|
|
209
|
+
const srcDir = fs.existsSync(path.join(dir, "src")) ? path.join(dir, "src") : dir;
|
|
210
|
+
helperPath = path.join(srcDir, isTs ? "dimies.ts" : "dimies.js");
|
|
211
|
+
const content = isTs
|
|
212
|
+
? TS_HELPER(url)
|
|
213
|
+
: TS_HELPER(url)
|
|
214
|
+
.replace(/^type DimiesMessage[\s\S]*?};\n\n/m, "")
|
|
215
|
+
.replace(/^type DimiesSpan[\s\S]*?};\n\n/m, "")
|
|
216
|
+
.replace(/options: \{[\s\S]*?\}\): void \{/m, "options) {");
|
|
217
|
+
fs.writeFileSync(helperPath, content);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// .env.example entries (never write the real key into tracked files)
|
|
221
|
+
const envExample = path.join(dir, ".env.example");
|
|
222
|
+
const envLines = `\n# Dimies — conversation analytics (${url}/app/docs)\nDIMIES_API_KEY=\nDIMIES_URL=${url}\n`;
|
|
223
|
+
if (fs.existsSync(envExample)) {
|
|
224
|
+
if (!fs.readFileSync(envExample, "utf8").includes("DIMIES_API_KEY")) {
|
|
225
|
+
fs.appendFileSync(envExample, envLines);
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
fs.writeFileSync(envExample, envLines.trimStart());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// real key goes into .env only when provided explicitly
|
|
232
|
+
if (flags.key) {
|
|
233
|
+
const envFile = path.join(dir, ".env");
|
|
234
|
+
const line = `DIMIES_API_KEY=${flags.key}\n`;
|
|
235
|
+
if (!fs.existsSync(envFile)) fs.writeFileSync(envFile, line);
|
|
236
|
+
else if (!fs.readFileSync(envFile, "utf8").includes("DIMIES_API_KEY")) fs.appendFileSync(envFile, line);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
console.log(green("✔ Dimies integration scaffolded"));
|
|
240
|
+
console.log(` helper: ${bold(path.relative(process.cwd(), helperPath) || helperPath)}`);
|
|
241
|
+
console.log(` env: DIMIES_API_KEY ${flags.key ? "written to .env" : dim("(set it in your env)")}`);
|
|
242
|
+
console.log(`\nNext: call ${bold(lang === "python" ? "track_conversation(...)" : "trackConversation(...)")} after each exchange with your agent.`);
|
|
243
|
+
console.log(`Then verify: ${bold(`npx dimies test --key <your-key> --url ${url}`)}`);
|
|
244
|
+
console.log(dim(`Full spec (also for AI coding agents): ${url}/llms.txt`));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function cmdTest() {
|
|
248
|
+
const key = flags.key || process.env.DIMIES_API_KEY;
|
|
249
|
+
if (!key) die("Pass --key dm_live_… (or set DIMIES_API_KEY)");
|
|
250
|
+
const conversationId = `cli-test-${Date.now()}`;
|
|
251
|
+
const res = await fetch(`${url}/api/v1/conversations`, {
|
|
252
|
+
method: "POST",
|
|
253
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
254
|
+
body: JSON.stringify({
|
|
255
|
+
conversation_id: conversationId,
|
|
256
|
+
user_id: "dimies-cli",
|
|
257
|
+
channel: "cli-test",
|
|
258
|
+
closed: true,
|
|
259
|
+
messages: [
|
|
260
|
+
{ role: "user", content: "Hello! This is a test conversation sent by the Dimies CLI to verify the integration." },
|
|
261
|
+
{ role: "assistant", content: "Integration confirmed — this conversation was ingested successfully and will be analyzed shortly." },
|
|
262
|
+
],
|
|
263
|
+
}),
|
|
264
|
+
}).catch((err) => die(`Could not reach ${url} — ${err.message}`));
|
|
265
|
+
const body = await res.json().catch(() => ({}));
|
|
266
|
+
if (!res.ok) die(`API returned ${res.status}: ${body.error ?? "unknown error"}`);
|
|
267
|
+
console.log(green("✔ Test conversation ingested"));
|
|
268
|
+
console.log(` conversation_id: ${bold(body.conversation_id)}`);
|
|
269
|
+
console.log(` analysis: runs automatically within ~1 minute (conversation was sent as closed)`);
|
|
270
|
+
console.log(` view it: ${bold(`${url}/app/conversations`)}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function cmdPrompt() {
|
|
274
|
+
console.log(AGENT_PROMPT(url));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function cmdSpec() {
|
|
278
|
+
const res = await fetch(`${url}/llms.txt`).catch((err) => die(`Could not reach ${url} — ${err.message}`));
|
|
279
|
+
if (!res.ok) die(`GET ${url}/llms.txt returned ${res.status}`);
|
|
280
|
+
console.log(await res.text());
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function cmdHelp() {
|
|
284
|
+
console.log(`${bold("dimies")} — integrate Dimies conversation analytics
|
|
285
|
+
|
|
286
|
+
${bold("Usage")}
|
|
287
|
+
npx dimies <command> [flags]
|
|
288
|
+
|
|
289
|
+
${bold("Commands")}
|
|
290
|
+
init Scaffold a tracking helper into this codebase (detects Node/Python)
|
|
291
|
+
test Send a test conversation to verify your key + endpoint
|
|
292
|
+
prompt Print a ready-to-paste prompt for AI coding agents (Claude Code, Cursor…)
|
|
293
|
+
spec Print the machine-readable integration spec (llms.txt)
|
|
294
|
+
|
|
295
|
+
${bold("Flags")}
|
|
296
|
+
--key <dm_live_…> Dimies API key (or env DIMIES_API_KEY)
|
|
297
|
+
--url <https://…> Dimies instance ${dim(`(default: ${DEFAULT_URL})`)}
|
|
298
|
+
--dir <path> Target directory for init ${dim("(default: .)")}
|
|
299
|
+
--lang node|python Override language detection
|
|
300
|
+
|
|
301
|
+
${bold("Fastest path with Claude Code")}
|
|
302
|
+
npx dimies prompt | pbcopy ${dim("# then paste into Claude Code")}
|
|
303
|
+
`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* -------------------------------- main --------------------------------- */
|
|
307
|
+
|
|
308
|
+
(async () => {
|
|
309
|
+
switch (command) {
|
|
310
|
+
case "init":
|
|
311
|
+
cmdInit();
|
|
312
|
+
break;
|
|
313
|
+
case "test":
|
|
314
|
+
await cmdTest();
|
|
315
|
+
break;
|
|
316
|
+
case "prompt":
|
|
317
|
+
cmdPrompt();
|
|
318
|
+
break;
|
|
319
|
+
case "spec":
|
|
320
|
+
await cmdSpec();
|
|
321
|
+
break;
|
|
322
|
+
default:
|
|
323
|
+
cmdHelp();
|
|
324
|
+
process.exit(command ? 1 : 0);
|
|
325
|
+
}
|
|
326
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dimies",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Integrate Dimies (conversation analytics for AI agents) into any codebase in one command — human- and AI-coding-agent-friendly.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"dimies": "bin/dimies.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin"
|
|
10
|
+
],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"dimies",
|
|
16
|
+
"llm",
|
|
17
|
+
"analytics",
|
|
18
|
+
"observability",
|
|
19
|
+
"ai-agents",
|
|
20
|
+
"conversation-analytics"
|
|
21
|
+
],
|
|
22
|
+
"homepage": "https://dimies.com",
|
|
23
|
+
"license": "MIT"
|
|
24
|
+
}
|