@moda-ai/cli 1.0.1 → 1.0.3

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.
@@ -0,0 +1,110 @@
1
+ // src/init/mcp.ts — MCP server auto-configuration for coding agents
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import { execSync } from 'node:child_process';
5
+ import { checkbox } from '@inquirer/prompts';
6
+ function isOnPath(cmd) {
7
+ try {
8
+ execSync(`which ${cmd}`, { stdio: 'pipe' });
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ export function detectAgents() {
16
+ const cwd = process.cwd();
17
+ const agents = [];
18
+ // Claude Code
19
+ if (existsSync(join(cwd, '.claude')) || isOnPath('claude')) {
20
+ agents.push({
21
+ name: 'Claude Code',
22
+ configPath: join(cwd, '.claude', 'settings.local.json'),
23
+ configKey: 'mcpServers',
24
+ });
25
+ }
26
+ // Cursor
27
+ if (existsSync(join(cwd, '.cursor'))) {
28
+ agents.push({
29
+ name: 'Cursor',
30
+ configPath: join(cwd, '.cursor', 'mcp.json'),
31
+ configKey: 'mcpServers',
32
+ });
33
+ }
34
+ // VS Code
35
+ if (existsSync(join(cwd, '.vscode'))) {
36
+ agents.push({
37
+ name: 'VS Code',
38
+ configPath: join(cwd, '.vscode', 'mcp.json'),
39
+ configKey: 'servers',
40
+ });
41
+ }
42
+ return agents;
43
+ }
44
+ export async function configureMCP(agents, apiKey, autoYes = false) {
45
+ if (agents.length === 0)
46
+ return [];
47
+ let selected;
48
+ if (autoYes) {
49
+ selected = agents;
50
+ }
51
+ else {
52
+ const choices = agents.map((a) => ({
53
+ name: a.name,
54
+ value: a,
55
+ checked: true,
56
+ }));
57
+ selected = await checkbox({
58
+ message: 'Configure MCP server for:',
59
+ choices,
60
+ });
61
+ }
62
+ const configured = [];
63
+ for (const agent of selected) {
64
+ try {
65
+ writeAgentConfig(agent, apiKey);
66
+ configured.push(agent.name);
67
+ }
68
+ catch (err) {
69
+ const message = err instanceof Error ? err.message : String(err);
70
+ console.error(` Warning: Failed to configure ${agent.name}: ${message}`);
71
+ }
72
+ }
73
+ // Show manual instructions for skipped agents
74
+ const skipped = agents.filter((a) => !selected.includes(a));
75
+ if (skipped.length > 0) {
76
+ console.log('\n Manual setup for skipped agents:');
77
+ for (const agent of skipped) {
78
+ console.log(` ${agent.name}: Add moda server to ${agent.configPath}`);
79
+ }
80
+ }
81
+ return configured;
82
+ }
83
+ function writeAgentConfig(agent, apiKey) {
84
+ // Ensure directory exists
85
+ const dir = dirname(agent.configPath);
86
+ mkdirSync(dir, { recursive: true });
87
+ // Read existing config
88
+ let config = {};
89
+ if (existsSync(agent.configPath)) {
90
+ try {
91
+ config = JSON.parse(readFileSync(agent.configPath, 'utf8'));
92
+ }
93
+ catch {
94
+ config = {};
95
+ }
96
+ }
97
+ // Merge moda server entry
98
+ const servers = config[agent.configKey] || {};
99
+ servers['moda'] = {
100
+ command: 'npx',
101
+ args: ['-y', '@moda-ai/cli'],
102
+ env: {
103
+ MODA_API_KEY: apiKey,
104
+ },
105
+ };
106
+ config[agent.configKey] = servers;
107
+ // Write back
108
+ writeFileSync(agent.configPath, JSON.stringify(config, null, 2) + '\n');
109
+ }
110
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1,2 @@
1
+ export declare function installRules(detectedAgents: string[], autoYes?: boolean): Promise<void>;
2
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1,72 @@
1
+ // src/init/rules.ts — Agent rules file installation
2
+ import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import { confirm } from '@inquirer/prompts';
5
+ const MODA_RULES_CONTENT = `
6
+
7
+ ## Moda SDK
8
+
9
+ Moda is integrated in this project for LLM observability. The \`moda-ai\` SDK
10
+ auto-instruments OpenAI and Anthropic calls via OpenTelemetry.
11
+
12
+ ### Initialization
13
+
14
+ - TypeScript: \`import { Moda } from 'moda-ai'; await Moda.init(process.env.MODA_API_KEY!);\`
15
+ - Python: \`import moda; moda.init()\` (reads MODA_API_KEY from env)
16
+
17
+ ### Conversation Tracking
18
+
19
+ Set conversation ID before LLM calls to group multi-turn conversations:
20
+ - TypeScript: \`Moda.conversationId = 'session-id';\`
21
+ - Python: \`moda.conversation_id = 'session-id'\`
22
+
23
+ ### Before Exit
24
+
25
+ Flush pending traces before the process exits:
26
+ - TypeScript: \`await Moda.flush();\`
27
+ - Python: \`moda.flush()\`
28
+ `;
29
+ const AGENT_RULES_FILES = {
30
+ 'Claude Code': 'CLAUDE.md',
31
+ 'Cursor': '.cursor/rules/moda.mdc',
32
+ };
33
+ export async function installRules(detectedAgents, autoYes = false) {
34
+ const applicable = detectedAgents.filter((a) => a in AGENT_RULES_FILES);
35
+ if (applicable.length === 0) {
36
+ console.log(' No supported agents detected for rules files');
37
+ return;
38
+ }
39
+ let shouldInstall = autoYes;
40
+ if (!shouldInstall) {
41
+ shouldInstall = await confirm({
42
+ message: 'Install agent rules for coding assistants?',
43
+ default: true,
44
+ });
45
+ }
46
+ if (!shouldInstall)
47
+ return;
48
+ for (const agent of applicable) {
49
+ const filePath = join(process.cwd(), AGENT_RULES_FILES[agent]);
50
+ try {
51
+ const dir = dirname(filePath);
52
+ mkdirSync(dir, { recursive: true });
53
+ if (existsSync(filePath)) {
54
+ const content = readFileSync(filePath, 'utf8');
55
+ if (content.includes('## Moda SDK')) {
56
+ console.log(` ${agent}: Moda SDK section already exists in ${AGENT_RULES_FILES[agent]}`);
57
+ continue;
58
+ }
59
+ appendFileSync(filePath, MODA_RULES_CONTENT);
60
+ }
61
+ else {
62
+ writeFileSync(filePath, MODA_RULES_CONTENT.trimStart());
63
+ }
64
+ console.log(` Added Moda SDK reference to ${AGENT_RULES_FILES[agent]}`);
65
+ }
66
+ catch (err) {
67
+ const message = err instanceof Error ? err.message : String(err);
68
+ console.error(` Warning: Failed to write ${AGENT_RULES_FILES[agent]}: ${message}`);
69
+ }
70
+ }
71
+ }
72
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1,9 @@
1
+ interface BootstrapResult {
2
+ apiKey: string;
3
+ tenantId: string;
4
+ tenantName: string;
5
+ tenantSlug: string;
6
+ }
7
+ export declare function selectTenantAndCreateKey(): Promise<BootstrapResult>;
8
+ export {};
9
+ //# sourceMappingURL=tenant.d.ts.map
@@ -0,0 +1,91 @@
1
+ // src/init/tenant.ts — Tenant selection and API key provisioning
2
+ import { select } from '@inquirer/prompts';
3
+ import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { authFetch } from '../auth.js';
6
+ export async function selectTenantAndCreateKey() {
7
+ // 1. Fetch CLI context
8
+ const contextRes = await authFetch('/api/cli/context');
9
+ if (!contextRes.ok) {
10
+ const body = await contextRes.text();
11
+ throw new Error(`Failed to fetch context (${contextRes.status}): ${body}`);
12
+ }
13
+ const ctx = (await contextRes.json());
14
+ console.log(` Authenticated as ${ctx.user.email}`);
15
+ // 2. Select tenant
16
+ if (ctx.tenants.length === 0) {
17
+ console.error(' No organizations found. Create one at https://app.modaflows.com');
18
+ process.exit(1);
19
+ }
20
+ let tenantId;
21
+ if (ctx.tenants.length === 1) {
22
+ tenantId = ctx.tenants[0].id;
23
+ console.log(` Organization: ${ctx.tenants[0].name}`);
24
+ }
25
+ else {
26
+ tenantId = await select({
27
+ message: 'Select organization',
28
+ choices: ctx.tenants.map((t) => ({
29
+ name: `${t.name} (${t.role})`,
30
+ value: t.id,
31
+ })),
32
+ });
33
+ }
34
+ // 3. Create API key via bootstrap endpoint
35
+ const bootstrapRes = await authFetch('/api/cli/bootstrap', {
36
+ method: 'POST',
37
+ headers: { 'Content-Type': 'application/json' },
38
+ body: JSON.stringify({ tenantId }),
39
+ });
40
+ if (bootstrapRes.status === 403) {
41
+ console.error(' Error: You need admin or owner access to create API keys.');
42
+ console.error(' Ask your organization owner to upgrade your role.');
43
+ process.exit(1);
44
+ }
45
+ if (!bootstrapRes.ok) {
46
+ const body = await bootstrapRes.text();
47
+ throw new Error(`Failed to create API key (${bootstrapRes.status}): ${body}`);
48
+ }
49
+ const result = (await bootstrapRes.json());
50
+ // 4. Write .env
51
+ writeEnvFile(result.apiKey);
52
+ // 5. Update .gitignore
53
+ ensureGitignore();
54
+ console.log(' API key created and saved to .env');
55
+ return result;
56
+ }
57
+ function writeEnvFile(apiKey) {
58
+ const envPath = join(process.cwd(), '.env');
59
+ const line = `MODA_API_KEY=${apiKey}`;
60
+ if (existsSync(envPath)) {
61
+ const content = readFileSync(envPath, 'utf8');
62
+ if (content.includes('MODA_API_KEY=')) {
63
+ // Replace existing line
64
+ const updated = content.replace(/^MODA_API_KEY=.*$/m, line);
65
+ writeFileSync(envPath, updated);
66
+ }
67
+ else {
68
+ // Append
69
+ const separator = content.endsWith('\n') ? '' : '\n';
70
+ appendFileSync(envPath, `${separator}${line}\n`);
71
+ }
72
+ }
73
+ else {
74
+ writeFileSync(envPath, `${line}\n`);
75
+ }
76
+ }
77
+ function ensureGitignore() {
78
+ const gitignorePath = join(process.cwd(), '.gitignore');
79
+ if (existsSync(gitignorePath)) {
80
+ const content = readFileSync(gitignorePath, 'utf8');
81
+ const lines = content.split('\n').map((l) => l.trim());
82
+ if (!lines.includes('.env')) {
83
+ const separator = content.endsWith('\n') ? '' : '\n';
84
+ appendFileSync(gitignorePath, `${separator}.env\n`);
85
+ }
86
+ }
87
+ else {
88
+ writeFileSync(gitignorePath, '.env\n');
89
+ }
90
+ }
91
+ //# sourceMappingURL=tenant.js.map
package/dist/schemas.d.ts CHANGED
@@ -38,18 +38,18 @@ export declare const ConversationsSchema: z.ZodObject<{
38
38
  limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
39
39
  offset: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
40
40
  }, "strip", z.ZodTypeAny, {
41
+ search?: string | undefined;
41
42
  time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
42
43
  limit?: number | undefined;
43
44
  offset?: number | undefined;
44
- search?: string | undefined;
45
45
  cluster_id?: string | undefined;
46
46
  user_id?: string | undefined;
47
47
  environment?: "all" | "development" | "staging" | "production" | undefined;
48
48
  }, {
49
+ search?: string | undefined;
49
50
  time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
50
51
  limit?: number | undefined;
51
52
  offset?: number | undefined;
52
- search?: string | undefined;
53
53
  cluster_id?: string | undefined;
54
54
  user_id?: string | undefined;
55
55
  environment?: "all" | "development" | "staging" | "production" | undefined;
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "CLI and MCP server for Moda - AI agent analytics and observability",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
- "cli": "./dist/index.js",
9
- "moda-mcp": "./dist/index.js",
10
- "moda": "./dist/cli.js"
8
+ "moda": "./dist/cli.js",
9
+ "moda-mcp": "./dist/index.js"
11
10
  },
12
11
  "files": [
13
12
  "dist/**/*.js",
@@ -22,7 +21,7 @@
22
21
  },
23
22
  "scripts": {
24
23
  "build": "tsc",
25
- "prepublishOnly": "npm run build",
24
+ "prepublishOnly": "tsc",
26
25
  "dev": "tsc --watch",
27
26
  "start": "node dist/index.js",
28
27
  "start:http": "node dist/http-server.js",
@@ -52,9 +51,11 @@
52
51
  "url": "https://github.com/ModaLabs/moda-cli/issues"
53
52
  },
54
53
  "dependencies": {
54
+ "@inquirer/prompts": "^8.4.1",
55
55
  "@modelcontextprotocol/sdk": "^1.0.4",
56
56
  "cors": "^2.8.5",
57
57
  "express": "^4.21.2",
58
+ "open": "^11.0.0",
58
59
  "zod": "^3.24.1"
59
60
  },
60
61
  "devDependencies": {