@moda-ai/cli 1.11.0 → 1.12.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/dist/init/sdk-guide.d.ts +28 -1
- package/dist/init/sdk-guide.js +102 -98
- package/dist/init/sdk-integration.d.ts +23 -3
- package/dist/init/sdk-integration.js +106 -24
- package/dist/init/skill-resolver.d.ts +19 -0
- package/dist/init/skill-resolver.js +78 -0
- package/dist/skills-bundled.d.ts +35 -0
- package/dist/skills-bundled.js +157 -0
- package/dist/skills-manifest.d.ts +33 -0
- package/dist/skills-manifest.js +244 -0
- package/dist/skills.js +22 -142
- package/package.json +6 -4
- package/skills/integration/index.json +123 -0
- package/skills/integration/integration-node-anthropic/EXAMPLE.md +59 -0
- package/skills/integration/integration-node-anthropic/SKILL.md +150 -0
- package/skills/integration/integration-node-generic/EXAMPLE.md +77 -0
- package/skills/integration/integration-node-generic/SKILL.md +170 -0
- package/skills/integration/integration-node-langchain/EXAMPLE.md +59 -0
- package/skills/integration/integration-node-langchain/SKILL.md +144 -0
- package/skills/integration/integration-node-openai/EXAMPLE.md +55 -0
- package/skills/integration/integration-node-openai/SKILL.md +148 -0
- package/skills/integration/integration-node-vercel-ai-sdk/EXAMPLE.md +55 -0
- package/skills/integration/integration-node-vercel-ai-sdk/SKILL.md +179 -0
- package/skills/integration/integration-node-vercel-ai-sdk/references/prompt-attribution.md +31 -0
- package/skills/integration/integration-python-anthropic/EXAMPLE.md +66 -0
- package/skills/integration/integration-python-anthropic/SKILL.md +153 -0
- package/skills/integration/integration-python-claude-agent-sdk/EXAMPLE.md +61 -0
- package/skills/integration/integration-python-claude-agent-sdk/SKILL.md +129 -0
- package/skills/integration/integration-python-generic/EXAMPLE.md +46 -0
- package/skills/integration/integration-python-generic/SKILL.md +146 -0
- package/skills/integration/integration-python-langchain/EXAMPLE.md +63 -0
- package/skills/integration/integration-python-langchain/SKILL.md +141 -0
- package/skills/integration/integration-python-openai/EXAMPLE.md +65 -0
- package/skills/integration/integration-python-openai/SKILL.md +151 -0
- package/dist/init/detect.d.ts +0 -9
- package/dist/init/detect.js +0 -179
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-python-langchain
|
|
3
|
+
name: "Integrate Moda with LangChain / LangGraph (Python)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: python
|
|
8
|
+
frameworks:
|
|
9
|
+
- langchain
|
|
10
|
+
providers: []
|
|
11
|
+
sdk_package: moda-ai
|
|
12
|
+
summary: "Instrument a Python LangChain or LangGraph app with Moda: init before the framework, explicit conversation ids around every run."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Integrate Moda with LangChain / LangGraph (Python)
|
|
16
|
+
|
|
17
|
+
Follow the steps below in order. Each step has a stop condition; when you hit
|
|
18
|
+
one, stop and report instead of guessing. `EXAMPLE.md` in this directory shows
|
|
19
|
+
the complete finished integration; match its structure. For API details beyond
|
|
20
|
+
this skill, read https://docs.moda.dev/ingestion/moda-sdk.
|
|
21
|
+
|
|
22
|
+
## Safety rules (apply to every step)
|
|
23
|
+
|
|
24
|
+
- Only touch the repository you were launched in.
|
|
25
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
26
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
27
|
+
environment or a secret manager.
|
|
28
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
29
|
+
- Do not commit unless the user asks.
|
|
30
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
31
|
+
guessing.
|
|
32
|
+
|
|
33
|
+
## What is different about agent frameworks
|
|
34
|
+
|
|
35
|
+
Two constraints drive every step below:
|
|
36
|
+
|
|
37
|
+
1. Instrument at the Moda SDK level, not inside LangChain. Moda's Python SDK
|
|
38
|
+
ships a LangChain instrument (`Instruments.LANGCHAIN`), and `moda.init()`
|
|
39
|
+
enables all instruments by default (`instruments=None`). Do not add
|
|
40
|
+
LangChain callbacks or handler classes for Moda — no such Moda API
|
|
41
|
+
exists.
|
|
42
|
+
2. Explicit conversation ids are mandatory. Moda's automatic conversation
|
|
43
|
+
detection hashes the first user message, and agent frameworks construct
|
|
44
|
+
different messages on every internal iteration, so without an explicit id
|
|
45
|
+
every iteration lands in a different conversation.
|
|
46
|
+
|
|
47
|
+
## Step 1: Find the agent/chain entry points
|
|
48
|
+
|
|
49
|
+
Find where the application invokes LangChain or LangGraph: `.invoke(...)`,
|
|
50
|
+
`.stream(...)`, agent executor runs, or graph runs. These entry points — not
|
|
51
|
+
the internal LLM calls — are where conversation context gets set.
|
|
52
|
+
|
|
53
|
+
If you cannot find any LangChain/LangGraph entry point, stop and report.
|
|
54
|
+
|
|
55
|
+
## Step 2: Install the SDK
|
|
56
|
+
|
|
57
|
+
Install `moda-ai` with the package manager this repository already uses
|
|
58
|
+
(`uv.lock` means uv, `poetry.lock` means poetry, otherwise pip):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install moda-ai
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
With uv:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv add moda-ai
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
With poetry:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
poetry add moda-ai
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If `moda-ai` is already a dependency, skip the install.
|
|
77
|
+
|
|
78
|
+
## Step 3: Initialize Moda once at startup
|
|
79
|
+
|
|
80
|
+
`moda.init` is synchronous and must run before LangChain models, chains, or
|
|
81
|
+
graphs are constructed. Initialize at the top of the application entry
|
|
82
|
+
point. If a `moda.init` call already exists, verify it runs at startup and
|
|
83
|
+
skip this step; never add a second call.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import os
|
|
87
|
+
|
|
88
|
+
import moda
|
|
89
|
+
|
|
90
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Step 4: Wrap every agent/chain run in an explicit conversation id
|
|
94
|
+
|
|
95
|
+
Wrap each entry point found in Step 1 so all internal LLM calls made during
|
|
96
|
+
that run share one conversation id. Use the scoped context managers so
|
|
97
|
+
concurrent runs do not leak context into each other:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from moda import set_conversation_id, set_user_id
|
|
101
|
+
|
|
102
|
+
with set_conversation_id(f"agent_session_{session_id}"):
|
|
103
|
+
with set_user_id(user_id):
|
|
104
|
+
result = agent.invoke({"input": user_query})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
For strictly sequential code, the module-level property form is equivalent:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
moda.conversation_id = f"agent_session_{session_id}"
|
|
111
|
+
result = agent.invoke({"input": user_query})
|
|
112
|
+
moda.conversation_id = None
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Reuse the session/thread identifier the app already has (LangGraph
|
|
116
|
+
`thread_id`, a chat session id, a ticket id). If no session identifier
|
|
117
|
+
exists anywhere, stop and report; do not invent one.
|
|
118
|
+
|
|
119
|
+
## Step 5: Flush before exit
|
|
120
|
+
|
|
121
|
+
Flush pending telemetry before the process exits (short-lived scripts) or on
|
|
122
|
+
shutdown (servers):
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
moda.flush()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Step 6: Validate
|
|
129
|
+
|
|
130
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
131
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
132
|
+
verify telemetry reaches Moda:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
moda doctor --online --json
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The report should show recent traces, and multi-step agent runs should share
|
|
139
|
+
one conversation id. If each internal LLM call shows up as its own
|
|
140
|
+
conversation, the explicit conversation id from Step 4 is not wrapping the
|
|
141
|
+
run — fix that before finishing.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Golden reference: FastAPI + OpenAI SDK + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a minimal FastAPI chat service that
|
|
4
|
+
calls the OpenAI SDK directly. Match this structure; adapt names to the
|
|
5
|
+
target repository.
|
|
6
|
+
|
|
7
|
+
## app/main.py
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import os
|
|
11
|
+
from contextlib import asynccontextmanager
|
|
12
|
+
|
|
13
|
+
import moda
|
|
14
|
+
from fastapi import FastAPI
|
|
15
|
+
from moda import set_conversation_id, set_user_id
|
|
16
|
+
from openai import OpenAI
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
|
|
19
|
+
# Initialize Moda before creating any LLM client so instrumentation is
|
|
20
|
+
# active. After this, every chat.completions.create call is captured
|
|
21
|
+
# automatically.
|
|
22
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
23
|
+
|
|
24
|
+
client = OpenAI()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@asynccontextmanager
|
|
28
|
+
async def lifespan(app: FastAPI):
|
|
29
|
+
yield
|
|
30
|
+
# Flush pending telemetry on shutdown.
|
|
31
|
+
moda.flush()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
app = FastAPI(lifespan=lifespan)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ChatRequest(BaseModel):
|
|
38
|
+
session_id: str
|
|
39
|
+
user_id: str
|
|
40
|
+
message: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.post("/chat")
|
|
44
|
+
def chat(request: ChatRequest):
|
|
45
|
+
# Scoped context: safe under concurrent requests.
|
|
46
|
+
with set_conversation_id(f"session_{request.session_id}"):
|
|
47
|
+
with set_user_id(request.user_id):
|
|
48
|
+
response = client.chat.completions.create(
|
|
49
|
+
model="gpt-4o",
|
|
50
|
+
messages=[{"role": "user", "content": request.message}],
|
|
51
|
+
)
|
|
52
|
+
return {"reply": response.choices[0].message.content}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## What changed, and what did not
|
|
56
|
+
|
|
57
|
+
- `moda-ai` added to the project dependencies.
|
|
58
|
+
- One `moda.init` call added at module import time, before `OpenAI()` is
|
|
59
|
+
constructed.
|
|
60
|
+
- The handler wraps its existing OpenAI call in the scoped context managers;
|
|
61
|
+
the `chat.completions.create` call itself is unchanged — instrumentation
|
|
62
|
+
is automatic.
|
|
63
|
+
- Shutdown flushes telemetry via the FastAPI lifespan hook.
|
|
64
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
65
|
+
runtime environment.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-python-openai
|
|
3
|
+
name: "Integrate Moda with the OpenAI SDK (Python)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: python
|
|
8
|
+
frameworks: []
|
|
9
|
+
providers:
|
|
10
|
+
- openai
|
|
11
|
+
sdk_package: moda-ai
|
|
12
|
+
summary: "Auto-instrument openai client calls with Moda in a Python app; no per-call changes required."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Integrate Moda with the OpenAI SDK (Python)
|
|
16
|
+
|
|
17
|
+
Follow the steps below in order. Each step has a stop condition; when you hit
|
|
18
|
+
one, stop and report instead of guessing. `EXAMPLE.md` in this directory shows
|
|
19
|
+
the complete finished integration; match its structure. For API details beyond
|
|
20
|
+
this skill, read https://docs.moda.dev/ingestion/providers/openai and
|
|
21
|
+
https://docs.moda.dev/ingestion/moda-sdk.
|
|
22
|
+
|
|
23
|
+
## Safety rules (apply to every step)
|
|
24
|
+
|
|
25
|
+
- Only touch the repository you were launched in.
|
|
26
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
27
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
28
|
+
environment or a secret manager.
|
|
29
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
30
|
+
- Do not commit unless the user asks.
|
|
31
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
32
|
+
guessing.
|
|
33
|
+
|
|
34
|
+
## Step 1: Confirm the raw OpenAI client is the integration surface
|
|
35
|
+
|
|
36
|
+
Find the real LLM call sites: `client.chat.completions.create(...)` on a
|
|
37
|
+
client constructed with `OpenAI()` from the `openai` package.
|
|
38
|
+
|
|
39
|
+
- If the OpenAI calls go through a wrapper framework instead (LangChain,
|
|
40
|
+
CrewAI, the Claude Agent SDK, or similar), stop: instrument the wrapper
|
|
41
|
+
layer with its dedicated skill, never the underlying provider client.
|
|
42
|
+
- If the repo wraps its OpenAI client in a helper module (for example
|
|
43
|
+
`app/llm.py` exporting a shared client), plan to instrument at that one
|
|
44
|
+
place; do not edit every call site.
|
|
45
|
+
- If there are no OpenAI call sites at all, stop and report.
|
|
46
|
+
|
|
47
|
+
## Step 2: Install the SDK
|
|
48
|
+
|
|
49
|
+
Install `moda-ai` with the package manager this repository already uses
|
|
50
|
+
(`uv.lock` means uv, `poetry.lock` means poetry, otherwise pip):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install moda-ai
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
With uv:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uv add moda-ai
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
With poetry:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
poetry add moda-ai
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If `moda-ai` is already a dependency, skip the install. `moda-ai` includes
|
|
69
|
+
the OpenAI and Anthropic instrumentation packages; nothing else is needed
|
|
70
|
+
for this integration.
|
|
71
|
+
|
|
72
|
+
## Step 3: Initialize Moda once at startup, before the client is created
|
|
73
|
+
|
|
74
|
+
Instrumentation is automatic: once `moda.init` has run, every
|
|
75
|
+
`chat.completions.create` call (including streaming and tool use) is
|
|
76
|
+
captured with no per-call changes.
|
|
77
|
+
|
|
78
|
+
`moda.init` is synchronous and must run before the OpenAI client is
|
|
79
|
+
constructed. If a `moda.init` call already exists, verify it runs at startup
|
|
80
|
+
before client creation and skip this step; never add a second call.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import os
|
|
84
|
+
|
|
85
|
+
import moda
|
|
86
|
+
from openai import OpenAI
|
|
87
|
+
|
|
88
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
89
|
+
|
|
90
|
+
client = OpenAI()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For web frameworks, initialize at import time of the application module (as
|
|
94
|
+
above) so the client-creation ordering holds; requirement: `moda.init` runs
|
|
95
|
+
before any `OpenAI()` construction.
|
|
96
|
+
|
|
97
|
+
## Step 4: Set conversation and user context
|
|
98
|
+
|
|
99
|
+
Set a stable conversation id before each LLM call so multi-turn
|
|
100
|
+
conversations group together, and a user id when one user can be identified.
|
|
101
|
+
|
|
102
|
+
For sequential code, use the module-level properties:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
moda.conversation_id = f"session_{session_id}"
|
|
106
|
+
moda.user_id = user_id
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
For concurrent request handlers (any web server), use the scoped context
|
|
110
|
+
managers so overlapping requests do not leak context into each other:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from moda import set_conversation_id, set_user_id
|
|
114
|
+
|
|
115
|
+
with set_conversation_id(f"session_{session_id}"):
|
|
116
|
+
with set_user_id(user_id):
|
|
117
|
+
response = client.chat.completions.create(
|
|
118
|
+
model="gpt-4o",
|
|
119
|
+
messages=[{"role": "user", "content": user_message}],
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Reuse the session/thread identifier the app already has. If no session
|
|
124
|
+
identifier exists anywhere, the SDK falls back to hashing the first user
|
|
125
|
+
message plus the system prompt — acceptable only for simple chatbots that
|
|
126
|
+
pass full message history on every call. If this repo is an agent framework
|
|
127
|
+
or does not pass full history, an explicit conversation id is required; stop
|
|
128
|
+
and report if you cannot find a session identifier to use.
|
|
129
|
+
|
|
130
|
+
## Step 5: Flush before exit
|
|
131
|
+
|
|
132
|
+
Flush pending telemetry before the process exits (short-lived scripts) or on
|
|
133
|
+
shutdown (servers):
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
moda.flush()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Step 6: Validate
|
|
140
|
+
|
|
141
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
142
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
143
|
+
verify telemetry reaches Moda:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
moda doctor --online --json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The report should show recent traces. If it does not, confirm `MODA_API_KEY`
|
|
150
|
+
is available in the runtime environment and that `moda.init()` runs before
|
|
151
|
+
the OpenAI client is created.
|
package/dist/init/detect.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export interface ProjectInfo {
|
|
2
|
-
language: 'typescript' | 'python' | null;
|
|
3
|
-
packageManager: string | null;
|
|
4
|
-
llmProviders: string[];
|
|
5
|
-
}
|
|
6
|
-
export declare function detectProject(): Promise<ProjectInfo>;
|
|
7
|
-
export declare function installSDK(project: ProjectInfo): Promise<boolean>;
|
|
8
|
-
export declare function getInitSnippet(project: ProjectInfo): string;
|
|
9
|
-
//# sourceMappingURL=detect.d.ts.map
|
package/dist/init/detect.js
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
// src/init/detect.ts — Project detection and SDK installation
|
|
2
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { execSync } from 'node:child_process';
|
|
5
|
-
import { select, confirm } from '@inquirer/prompts';
|
|
6
|
-
const LOCKFILE_TO_PM = {
|
|
7
|
-
'package-lock.json': 'npm',
|
|
8
|
-
'yarn.lock': 'yarn',
|
|
9
|
-
'pnpm-lock.yaml': 'pnpm',
|
|
10
|
-
'bun.lockb': 'bun',
|
|
11
|
-
'bun.lock': 'bun',
|
|
12
|
-
'poetry.lock': 'poetry',
|
|
13
|
-
'uv.lock': 'uv',
|
|
14
|
-
};
|
|
15
|
-
const INSTALL_COMMANDS = {
|
|
16
|
-
npm: 'npm install moda-ai',
|
|
17
|
-
yarn: 'yarn add moda-ai',
|
|
18
|
-
pnpm: 'pnpm add moda-ai',
|
|
19
|
-
bun: 'bun add moda-ai',
|
|
20
|
-
pip: 'pip install moda-ai',
|
|
21
|
-
poetry: 'poetry add moda-ai',
|
|
22
|
-
uv: 'uv add moda-ai',
|
|
23
|
-
};
|
|
24
|
-
const LLM_PATTERNS = [
|
|
25
|
-
{ pattern: /from openai|import openai|require\(['"]openai['"]\)/i, name: 'OpenAI' },
|
|
26
|
-
{ pattern: /from anthropic|import.*@anthropic-ai\/sdk|require\(['"]@anthropic-ai\/sdk['"]\)/i, name: 'Anthropic' },
|
|
27
|
-
{ pattern: /from ai |import.*@ai-sdk|require\(['"](ai|@ai-sdk)/i, name: 'Vercel AI SDK' },
|
|
28
|
-
{ pattern: /from langchain|import.*langchain/i, name: 'LangChain' },
|
|
29
|
-
{ pattern: /from claude_agent_sdk|import.*claude.agent/i, name: 'Claude Agent SDK' },
|
|
30
|
-
];
|
|
31
|
-
export async function detectProject() {
|
|
32
|
-
const cwd = process.cwd();
|
|
33
|
-
const hasPackageJson = existsSync(join(cwd, 'package.json'));
|
|
34
|
-
const hasPython = existsSync(join(cwd, 'pyproject.toml')) ||
|
|
35
|
-
existsSync(join(cwd, 'requirements.txt')) ||
|
|
36
|
-
existsSync(join(cwd, 'Pipfile'));
|
|
37
|
-
let language = null;
|
|
38
|
-
if (hasPackageJson && hasPython) {
|
|
39
|
-
language = await select({
|
|
40
|
-
message: 'Both TypeScript and Python detected. Which to configure?',
|
|
41
|
-
choices: [
|
|
42
|
-
{ name: 'TypeScript / JavaScript', value: 'typescript' },
|
|
43
|
-
{ name: 'Python', value: 'python' },
|
|
44
|
-
],
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
else if (hasPackageJson) {
|
|
48
|
-
language = 'typescript';
|
|
49
|
-
}
|
|
50
|
-
else if (hasPython) {
|
|
51
|
-
language = 'python';
|
|
52
|
-
}
|
|
53
|
-
// Detect package manager
|
|
54
|
-
let packageManager = null;
|
|
55
|
-
if (language === 'typescript') {
|
|
56
|
-
for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
|
|
57
|
-
if (existsSync(join(cwd, lockfile))) {
|
|
58
|
-
packageManager = pm;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
// No lockfile — detect which package manager is actually installed
|
|
63
|
-
if (!packageManager) {
|
|
64
|
-
for (const pm of ['bun', 'pnpm', 'yarn', 'npm']) {
|
|
65
|
-
try {
|
|
66
|
-
execSync(`${pm} --version`, { stdio: 'pipe' });
|
|
67
|
-
packageManager = pm;
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
// not installed
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (!packageManager)
|
|
75
|
-
packageManager = 'npm';
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
else if (language === 'python') {
|
|
79
|
-
if (existsSync(join(cwd, 'poetry.lock'))) {
|
|
80
|
-
packageManager = 'poetry';
|
|
81
|
-
}
|
|
82
|
-
else if (existsSync(join(cwd, 'uv.lock'))) {
|
|
83
|
-
packageManager = 'uv';
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
packageManager = 'pip';
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Detect LLM providers (shallow scan)
|
|
90
|
-
const llmProviders = detectLLMProviders(cwd, language);
|
|
91
|
-
return { language, packageManager, llmProviders };
|
|
92
|
-
}
|
|
93
|
-
function detectLLMProviders(cwd, language) {
|
|
94
|
-
if (!language)
|
|
95
|
-
return [];
|
|
96
|
-
const providers = new Set();
|
|
97
|
-
const extensions = language === 'typescript' ? ['.ts', '.tsx', '.js', '.jsx', '.mjs'] : ['.py'];
|
|
98
|
-
const dirs = ['src', 'app', 'lib', '.'];
|
|
99
|
-
for (const dir of dirs) {
|
|
100
|
-
const dirPath = join(cwd, dir);
|
|
101
|
-
if (!existsSync(dirPath))
|
|
102
|
-
continue;
|
|
103
|
-
try {
|
|
104
|
-
const files = readdirSync(dirPath, { withFileTypes: true });
|
|
105
|
-
for (const file of files) {
|
|
106
|
-
if (!file.isFile())
|
|
107
|
-
continue;
|
|
108
|
-
if (!extensions.some((ext) => file.name.endsWith(ext)))
|
|
109
|
-
continue;
|
|
110
|
-
try {
|
|
111
|
-
const content = readFileSync(join(dirPath, file.name), 'utf8');
|
|
112
|
-
for (const { pattern, name } of LLM_PATTERNS) {
|
|
113
|
-
if (pattern.test(content)) {
|
|
114
|
-
providers.add(name);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
// Skip unreadable files
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
// Skip unreadable dirs
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return Array.from(providers);
|
|
128
|
-
}
|
|
129
|
-
export async function installSDK(project) {
|
|
130
|
-
if (!project.packageManager)
|
|
131
|
-
return false;
|
|
132
|
-
const command = INSTALL_COMMANDS[project.packageManager];
|
|
133
|
-
if (!command)
|
|
134
|
-
return false;
|
|
135
|
-
const shouldInstall = await confirm({
|
|
136
|
-
message: `Install moda-ai SDK? (${command})`,
|
|
137
|
-
default: true,
|
|
138
|
-
});
|
|
139
|
-
if (!shouldInstall) {
|
|
140
|
-
console.log(` Skipped. Run manually: ${command}`);
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
console.log(` Installing moda-ai (${project.packageManager})...`);
|
|
144
|
-
try {
|
|
145
|
-
execSync(command, { cwd: process.cwd(), stdio: 'pipe' });
|
|
146
|
-
console.log(' Installed moda-ai');
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
catch (err) {
|
|
150
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
151
|
-
console.error(` Warning: SDK install failed: ${message}`);
|
|
152
|
-
console.error(` Run manually: ${command}`);
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
export function getInitSnippet(project) {
|
|
157
|
-
if (project.language === 'typescript') {
|
|
158
|
-
return `
|
|
159
|
-
import { Moda } from 'moda-ai';
|
|
160
|
-
|
|
161
|
-
await Moda.init(process.env.MODA_API_KEY!);
|
|
162
|
-
// Moda.conversationId = 'your-session-id';
|
|
163
|
-
// ... your LLM calls are auto-instrumented ...
|
|
164
|
-
await Moda.flush();
|
|
165
|
-
`;
|
|
166
|
-
}
|
|
167
|
-
if (project.language === 'python') {
|
|
168
|
-
return `
|
|
169
|
-
import moda
|
|
170
|
-
|
|
171
|
-
moda.init() # reads MODA_API_KEY from env
|
|
172
|
-
# moda.conversation_id = 'your-session-id'
|
|
173
|
-
# ... your LLM calls are auto-instrumented ...
|
|
174
|
-
moda.flush()
|
|
175
|
-
`;
|
|
176
|
-
}
|
|
177
|
-
return '';
|
|
178
|
-
}
|
|
179
|
-
//# sourceMappingURL=detect.js.map
|