openshell-ai 1.0.4
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/LICENSE +21 -0
- package/README.md +354 -0
- package/dist/analytics/client.d.ts +2 -0
- package/dist/analytics/client.js +95 -0
- package/dist/analytics/client.js.map +1 -0
- package/dist/analytics/session.d.ts +15 -0
- package/dist/analytics/session.js +70 -0
- package/dist/analytics/session.js.map +1 -0
- package/dist/cli/commands.d.ts +14 -0
- package/dist/cli/commands.js +265 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +2 -0
- package/dist/cli/prompts.js +32 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/cli/repl.d.ts +12 -0
- package/dist/cli/repl.js +184 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/config/configurator.d.ts +18 -0
- package/dist/config/configurator.js +133 -0
- package/dist/config/configurator.js.map +1 -0
- package/dist/config/env.d.ts +2 -0
- package/dist/config/env.js +8 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/providerCatalog.d.ts +8 -0
- package/dist/config/providerCatalog.js +33 -0
- package/dist/config/providerCatalog.js.map +1 -0
- package/dist/config/userConfig.d.ts +12 -0
- package/dist/config/userConfig.js +141 -0
- package/dist/config/userConfig.js.map +1 -0
- package/dist/core/explainCommand.d.ts +2 -0
- package/dist/core/explainCommand.js +4 -0
- package/dist/core/explainCommand.js.map +1 -0
- package/dist/core/generateCommand.d.ts +2 -0
- package/dist/core/generateCommand.js +34 -0
- package/dist/core/generateCommand.js.map +1 -0
- package/dist/core/output.d.ts +4 -0
- package/dist/core/output.js +67 -0
- package/dist/core/output.js.map +1 -0
- package/dist/core/prompts.d.ts +8 -0
- package/dist/core/prompts.js +75 -0
- package/dist/core/prompts.js.map +1 -0
- package/dist/core/response.d.ts +19 -0
- package/dist/core/response.js +44 -0
- package/dist/core/response.js.map +1 -0
- package/dist/core/session.d.ts +9 -0
- package/dist/core/session.js +23 -0
- package/dist/core/session.js.map +1 -0
- package/dist/exec/runCommand.d.ts +11 -0
- package/dist/exec/runCommand.js +57 -0
- package/dist/exec/runCommand.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/platform/detectPlatform.d.ts +9 -0
- package/dist/platform/detectPlatform.js +49 -0
- package/dist/platform/detectPlatform.js.map +1 -0
- package/dist/platform/detectServiceManager.d.ts +3 -0
- package/dist/platform/detectServiceManager.js +38 -0
- package/dist/platform/detectServiceManager.js.map +1 -0
- package/dist/platform/detectShell.d.ts +3 -0
- package/dist/platform/detectShell.js +21 -0
- package/dist/platform/detectShell.js.map +1 -0
- package/dist/providers/anthropic.d.ts +8 -0
- package/dist/providers/anthropic.js +67 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/factory.d.ts +2 -0
- package/dist/providers/factory.js +24 -0
- package/dist/providers/factory.js.map +1 -0
- package/dist/providers/google.d.ts +8 -0
- package/dist/providers/google.js +76 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/ollama.d.ts +8 -0
- package/dist/providers/ollama.js +67 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +8 -0
- package/dist/providers/openai.js +77 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/safety/classifyRisk.d.ts +7 -0
- package/dist/safety/classifyRisk.js +115 -0
- package/dist/safety/classifyRisk.js.map +1 -0
- package/dist/safety/dangerousPatterns.d.ts +8 -0
- package/dist/safety/dangerousPatterns.js +70 -0
- package/dist/safety/dangerousPatterns.js.map +1 -0
- package/dist/safety/executionPolicy.d.ts +9 -0
- package/dist/safety/executionPolicy.js +21 -0
- package/dist/safety/executionPolicy.js.map +1 -0
- package/dist/types/index.d.ts +105 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/branding.d.ts +6 -0
- package/dist/utils/branding.js +16 -0
- package/dist/utils/branding.js.map +1 -0
- package/dist/utils/errors.d.ts +26 -0
- package/dist/utils/errors.js +59 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +14 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/strings.d.ts +5 -0
- package/dist/utils/strings.js +30 -0
- package/dist/utils/strings.js.map +1 -0
- package/dist/workspace/inspectWorkspace.d.ts +1 -0
- package/dist/workspace/inspectWorkspace.js +174 -0
- package/dist/workspace/inspectWorkspace.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ottili ONE
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# `ai-cmd`
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
`ai-cmd` is a focused CLI for turning natural-language terminal questions into a single shell command you can inspect, copy, explain, and optionally run with confirmation.
|
|
9
|
+
|
|
10
|
+
It is designed for the fast path:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
ai "how do I restart nginx"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Instead of a chatty assistant or a full autonomous agent, `ai-cmd` aims to be the small utility you keep installed because it is quick, predictable, and useful.
|
|
17
|
+
|
|
18
|
+
## Why it exists
|
|
19
|
+
|
|
20
|
+
Developers regularly know what they want to do in the terminal, but not the exact command for the current machine, shell, or init system.
|
|
21
|
+
|
|
22
|
+
`ai-cmd` narrows that gap by:
|
|
23
|
+
|
|
24
|
+
- generating one best command instead of a long essay
|
|
25
|
+
- adapting to Linux, macOS, WSL, and generic Unix-like environments
|
|
26
|
+
- explaining what the command does
|
|
27
|
+
- classifying command risk before execution
|
|
28
|
+
- keeping interactive follow-ups lightweight
|
|
29
|
+
|
|
30
|
+
## Powered by Ottili ONE
|
|
31
|
+
|
|
32
|
+
This project is part of the **Ottili ONE ecosystem** — a modular AI system for automating development, business workflows, and operations.
|
|
33
|
+
|
|
34
|
+
### Website: https://ottili.one
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- One-shot command generation: `ai "<question>"`
|
|
41
|
+
- Interactive REPL mode: `ai`
|
|
42
|
+
- Session-aware follow-up questions in REPL mode
|
|
43
|
+
- Command explanations and platform notes
|
|
44
|
+
- Clipboard copy support
|
|
45
|
+
- Optional execution with confirmation
|
|
46
|
+
- Heuristic risk classification for destructive commands
|
|
47
|
+
- First-run configurator for provider, API key, and analytics consent
|
|
48
|
+
- Configurable provider abstraction for OpenAI, Anthropic, Google, Ollama, and vLLM
|
|
49
|
+
- Workspace-aware command generation based on the current folder
|
|
50
|
+
- Opt-in anonymous analytics with separate error reporting
|
|
51
|
+
- JSON mode for scripting
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
### Global install from npm
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install -g ai-cmd
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Local development install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install
|
|
65
|
+
npm run build
|
|
66
|
+
npm link
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
`ai-cmd` reads configuration from environment variables and optionally from `~/.ai-cmd/config.json`.
|
|
72
|
+
|
|
73
|
+
Environment variables take precedence over the config file.
|
|
74
|
+
|
|
75
|
+
If required configuration is missing and you launch `ai-cmd` interactively, a first-run configurator opens and asks for:
|
|
76
|
+
|
|
77
|
+
- AI provider
|
|
78
|
+
- API key
|
|
79
|
+
- analytics consent
|
|
80
|
+
|
|
81
|
+
If you opt in, `config.json` stores `"analytics": true` and a random anonymous install id. If you opt out, it stores `"analytics": false`.
|
|
82
|
+
|
|
83
|
+
### OpenAI
|
|
84
|
+
|
|
85
|
+
OpenAI remains the default provider:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
export AI_API_KEY="your-api-key"
|
|
89
|
+
export AI_PROVIDER="openai"
|
|
90
|
+
export AI_MODEL="gpt-5.4-mini"
|
|
91
|
+
export AI_BASE_URL="https://api.openai.com/v1"
|
|
92
|
+
export AI_TIMEOUT_MS="30000"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Anthropic
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
export AI_PROVIDER="anthropic"
|
|
99
|
+
export AI_API_KEY="your-anthropic-key"
|
|
100
|
+
export AI_MODEL="claude-sonnet-4-20250514"
|
|
101
|
+
export AI_BASE_URL="https://api.anthropic.com/v1"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Google
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
export AI_PROVIDER="google"
|
|
108
|
+
export AI_API_KEY="your-google-ai-key"
|
|
109
|
+
export AI_MODEL="gemini-2.5-flash"
|
|
110
|
+
export AI_BASE_URL="https://generativelanguage.googleapis.com/v1beta"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Ollama
|
|
114
|
+
|
|
115
|
+
Use a local Ollama model such as Gemma:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
export AI_PROVIDER="ollama"
|
|
119
|
+
export AI_MODEL="gemma3:4b"
|
|
120
|
+
export AI_BASE_URL="http://localhost:11434/api"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
No API key is required for the default local Ollama setup.
|
|
124
|
+
|
|
125
|
+
### vLLM
|
|
126
|
+
|
|
127
|
+
Use a local or self-hosted OpenAI-compatible vLLM server:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
export AI_PROVIDER="vllm"
|
|
131
|
+
export AI_MODEL="google/gemma-3-4b-it"
|
|
132
|
+
export AI_BASE_URL="http://localhost:8000/v1"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
No API key is required for a local vLLM server unless you configured auth yourself.
|
|
136
|
+
|
|
137
|
+
### Example config file
|
|
138
|
+
|
|
139
|
+
`~/.ai-cmd/config.json`
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"provider": "openai",
|
|
144
|
+
"model": "gpt-5.4-mini",
|
|
145
|
+
"apiKey": "your-api-key",
|
|
146
|
+
"baseUrl": "https://api.openai.com/v1",
|
|
147
|
+
"timeoutMs": 30000,
|
|
148
|
+
"analytics": false
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Analytics
|
|
153
|
+
|
|
154
|
+
Analytics are opt-in only.
|
|
155
|
+
|
|
156
|
+
When enabled, `ai-cmd` sends anonymous usage events to:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
https://tracking.ottili.one/api/aicmd
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Usage events include:
|
|
163
|
+
|
|
164
|
+
- anonymous install count
|
|
165
|
+
- CLI starts
|
|
166
|
+
- prompt count
|
|
167
|
+
|
|
168
|
+
Error reports can include:
|
|
169
|
+
|
|
170
|
+
- prompt
|
|
171
|
+
- OS
|
|
172
|
+
- version
|
|
173
|
+
- timestamp
|
|
174
|
+
|
|
175
|
+
Regular usage analytics do not store generated command content.
|
|
176
|
+
|
|
177
|
+
Analytics requests now use a short-lived server-issued session plus proof-of-work. This does not create perfect authentication for a public open-source client, but it makes automated spam substantially harder and gives the server something real to validate.
|
|
178
|
+
|
|
179
|
+
Server setup notes live in [docs/analytics-server.md](docs/analytics-server.md).
|
|
180
|
+
|
|
181
|
+
If OpenAI configuration is missing, `ai-cmd` fails clearly:
|
|
182
|
+
|
|
183
|
+
```text
|
|
184
|
+
Missing AI_API_KEY. Set it in your environment or edit ~/.ai-cmd/config.json. A starter config has been created if it did not already exist.
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Usage
|
|
188
|
+
|
|
189
|
+
### One-shot mode
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
ai "how do I restart nginx"
|
|
193
|
+
ai "find all files bigger than 100MB"
|
|
194
|
+
ai "show disk usage" --json
|
|
195
|
+
ai "remove node_modules and reinstall packages" --exec
|
|
196
|
+
ai "find all jpg files" --copy
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`ai-cmd` now inspects the current folder structure and common project files like `package.json`, `Makefile`, `Cargo.toml`, `go.mod`, and compose files so it can suggest commands that fit the active workspace better.
|
|
200
|
+
|
|
201
|
+
### Interactive mode
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
ai
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Commands available inside the REPL:
|
|
208
|
+
|
|
209
|
+
- `help`
|
|
210
|
+
- `last`
|
|
211
|
+
- `explain`
|
|
212
|
+
- `run`
|
|
213
|
+
- `copy`
|
|
214
|
+
- `clear`
|
|
215
|
+
- `exit`
|
|
216
|
+
|
|
217
|
+
Example session:
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
ai-cmd > how do I restart nginx
|
|
221
|
+
ai-cmd > explain
|
|
222
|
+
ai-cmd > run
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## CLI flags
|
|
226
|
+
|
|
227
|
+
- `--version` Show the branded version banner
|
|
228
|
+
- `--exec` Execute the generated command after confirmation
|
|
229
|
+
- `--yes` Skip the normal confirmation step for low and medium risk commands
|
|
230
|
+
- `--explain` Explicitly request explanation details from the provider
|
|
231
|
+
- `--json` Output machine-readable JSON
|
|
232
|
+
- `--shell <bash|zsh|sh>` Hint the active shell
|
|
233
|
+
- `--copy` Copy the generated command to the clipboard
|
|
234
|
+
- `--no-color` Disable colored output
|
|
235
|
+
- `--debug` Print diagnostic context
|
|
236
|
+
|
|
237
|
+
## Safety model
|
|
238
|
+
|
|
239
|
+
`ai-cmd` is allowed to execute commands, so the MVP includes a practical safety layer:
|
|
240
|
+
|
|
241
|
+
- `low` risk: read-only or status-style commands such as `ls -la`
|
|
242
|
+
- `medium` risk: service restarts, dependency installs, project-scoped cleanup
|
|
243
|
+
- `high` risk: destructive recursive deletes, disk tools, remote shell pipes, system package removal
|
|
244
|
+
|
|
245
|
+
Execution rules:
|
|
246
|
+
|
|
247
|
+
- low and medium risk commands require confirmation unless `--yes` is used
|
|
248
|
+
- high risk commands always require an explicit confirmation phrase
|
|
249
|
+
- `--yes` never bypasses the high-risk confirmation path
|
|
250
|
+
- unsupported host OSes can still generate best-effort Unix-style commands, but execution is disabled
|
|
251
|
+
|
|
252
|
+
Example high-risk confirmation:
|
|
253
|
+
|
|
254
|
+
```text
|
|
255
|
+
Risk: HIGH
|
|
256
|
+
This command can delete files recursively outside the current project.
|
|
257
|
+
Type EXECUTE HIGH RISK COMMAND to continue:
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Supported platforms
|
|
261
|
+
|
|
262
|
+
- Linux
|
|
263
|
+
- macOS
|
|
264
|
+
- WSL
|
|
265
|
+
- Generic Unix-like systems
|
|
266
|
+
|
|
267
|
+
Notes:
|
|
268
|
+
|
|
269
|
+
- Linux service-manager detection prefers `systemctl`, then `service`, then `rc-service`
|
|
270
|
+
- macOS service handling prefers launchd-oriented commands
|
|
271
|
+
- Native Windows command generation is intentionally limited in this MVP
|
|
272
|
+
|
|
273
|
+
## Architecture
|
|
274
|
+
|
|
275
|
+
```text
|
|
276
|
+
src/
|
|
277
|
+
cli/
|
|
278
|
+
config/
|
|
279
|
+
core/
|
|
280
|
+
exec/
|
|
281
|
+
platform/
|
|
282
|
+
providers/
|
|
283
|
+
safety/
|
|
284
|
+
types/
|
|
285
|
+
utils/
|
|
286
|
+
tests/
|
|
287
|
+
integration/
|
|
288
|
+
unit/
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Key design choices:
|
|
292
|
+
|
|
293
|
+
- thin Commander-based CLI layer
|
|
294
|
+
- business logic isolated under `core/`
|
|
295
|
+
- provider abstraction isolated under `providers/`
|
|
296
|
+
- platform and service-manager detection isolated under `platform/`
|
|
297
|
+
- execution safety and confirmation logic isolated under `safety/`
|
|
298
|
+
- strict JSON normalization and validation before rendering or execution
|
|
299
|
+
|
|
300
|
+
## Development
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
npm install
|
|
304
|
+
npm run typecheck
|
|
305
|
+
npm run lint
|
|
306
|
+
npm test
|
|
307
|
+
npm run build
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Run in development mode:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
npm run dev -- "how do I list listening ports"
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## JSON output
|
|
317
|
+
|
|
318
|
+
`--json` prints a structured payload suitable for scripts:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
ai "show disk usage" --json
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Example shape:
|
|
325
|
+
|
|
326
|
+
```json
|
|
327
|
+
{
|
|
328
|
+
"question": "show disk usage",
|
|
329
|
+
"command": "du -sh .",
|
|
330
|
+
"explanation": "Shows total disk usage for the current directory.",
|
|
331
|
+
"risk": "low",
|
|
332
|
+
"platformNotes": [],
|
|
333
|
+
"assumptions": [],
|
|
334
|
+
"platform": {
|
|
335
|
+
"os": "linux",
|
|
336
|
+
"shell": "bash",
|
|
337
|
+
"serviceManager": "systemctl",
|
|
338
|
+
"cwd": "/work/project",
|
|
339
|
+
"cwdName": "project"
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Roadmap
|
|
345
|
+
|
|
346
|
+
- richer shell support
|
|
347
|
+
- more precise filesystem-aware risk detection
|
|
348
|
+
- Homebrew distribution
|
|
349
|
+
- optional standalone binaries
|
|
350
|
+
- improved follow-up context handling
|
|
351
|
+
|
|
352
|
+
## License
|
|
353
|
+
|
|
354
|
+
MIT
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { APP_NAME, APP_VERSION } from "../utils/branding.js";
|
|
2
|
+
import { createAnalyticsProof, createAnalyticsSession, isAnalyticsSessionFresh } from "./session.js";
|
|
3
|
+
const TRACKING_BASE_URL = "https://tracking.ottili.one/api/aicmd";
|
|
4
|
+
function createSessionGetter(config) {
|
|
5
|
+
let cachedSession;
|
|
6
|
+
let sessionPromise;
|
|
7
|
+
return async () => {
|
|
8
|
+
if (isAnalyticsSessionFresh(cachedSession)) {
|
|
9
|
+
return cachedSession;
|
|
10
|
+
}
|
|
11
|
+
if (!sessionPromise) {
|
|
12
|
+
sessionPromise = createAnalyticsSession(config).then((session) => {
|
|
13
|
+
cachedSession = session;
|
|
14
|
+
sessionPromise = undefined;
|
|
15
|
+
return session;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return sessionPromise;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function postJson(path, payload, config, getSession) {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timeout = setTimeout(() => controller.abort(), 1_500);
|
|
24
|
+
try {
|
|
25
|
+
const session = await getSession();
|
|
26
|
+
if (!session) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await fetch(`${TRACKING_BASE_URL}${path}`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
"User-Agent": `${APP_NAME}/${APP_VERSION}`,
|
|
34
|
+
"X-AI-CMD-Install-Id": config.analyticsId ?? "",
|
|
35
|
+
"X-AI-CMD-Session-Id": session.sessionId,
|
|
36
|
+
"X-AI-CMD-Session-Expires": session.expiresAt,
|
|
37
|
+
"X-AI-CMD-Session-Signature": session.signature
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
payload,
|
|
41
|
+
auth: createAnalyticsProof(session, payload)
|
|
42
|
+
}),
|
|
43
|
+
signal: controller.signal
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Analytics should never block or break the CLI.
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function createNoopAnalyticsClient() {
|
|
54
|
+
return {
|
|
55
|
+
async trackCliStart() { },
|
|
56
|
+
async trackPromptSent() { },
|
|
57
|
+
async trackError() { }
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function createAnalyticsClient(config) {
|
|
61
|
+
if (!config.analytics || !config.analyticsId) {
|
|
62
|
+
return createNoopAnalyticsClient();
|
|
63
|
+
}
|
|
64
|
+
const getSession = createSessionGetter(config);
|
|
65
|
+
const basePayload = () => ({
|
|
66
|
+
installId: config.analyticsId,
|
|
67
|
+
app: APP_NAME.toLowerCase(),
|
|
68
|
+
version: APP_VERSION,
|
|
69
|
+
time: new Date().toISOString()
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
async trackCliStart(payload) {
|
|
73
|
+
await postJson("/events", {
|
|
74
|
+
...basePayload(),
|
|
75
|
+
event: "cli_started",
|
|
76
|
+
...payload
|
|
77
|
+
}, config, getSession);
|
|
78
|
+
},
|
|
79
|
+
async trackPromptSent(payload) {
|
|
80
|
+
await postJson("/events", {
|
|
81
|
+
...basePayload(),
|
|
82
|
+
event: "prompt_sent",
|
|
83
|
+
...payload
|
|
84
|
+
}, config, getSession);
|
|
85
|
+
},
|
|
86
|
+
async trackError(payload) {
|
|
87
|
+
await postJson("/errors", {
|
|
88
|
+
...basePayload(),
|
|
89
|
+
event: "error_reported",
|
|
90
|
+
...payload
|
|
91
|
+
}, config, getSession);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/analytics/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AppConfig } from "../types/index.js";
|
|
2
|
+
export interface AnalyticsSession {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
nonce: string;
|
|
5
|
+
difficulty: number;
|
|
6
|
+
expiresAt: string;
|
|
7
|
+
signature: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createAnalyticsSession(config: AppConfig): Promise<AnalyticsSession | undefined>;
|
|
10
|
+
export declare function isAnalyticsSessionFresh(session: AnalyticsSession | undefined): session is AnalyticsSession;
|
|
11
|
+
export declare function createAnalyticsProof(session: AnalyticsSession, payload: Record<string, unknown>): {
|
|
12
|
+
payloadHash: string;
|
|
13
|
+
counter: number;
|
|
14
|
+
proof: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { APP_NAME, APP_VERSION } from "../utils/branding.js";
|
|
3
|
+
const TRACKING_BASE_URL = "https://tracking.ottili.one/api/aicmd";
|
|
4
|
+
const SESSION_REFRESH_BUFFER_MS = 60_000;
|
|
5
|
+
function sha256(input) {
|
|
6
|
+
return createHash("sha256").update(input).digest("hex");
|
|
7
|
+
}
|
|
8
|
+
function hasLeadingZeroes(hex, zeroCount) {
|
|
9
|
+
return hex.startsWith("0".repeat(Math.max(0, zeroCount)));
|
|
10
|
+
}
|
|
11
|
+
export async function createAnalyticsSession(config) {
|
|
12
|
+
if (!config.analyticsId) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timeout = setTimeout(() => controller.abort(), 1_500);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(`${TRACKING_BASE_URL}/session`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: {
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
"User-Agent": `${APP_NAME}/${APP_VERSION}`
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify({
|
|
25
|
+
installId: config.analyticsId,
|
|
26
|
+
app: APP_NAME.toLowerCase(),
|
|
27
|
+
version: APP_VERSION
|
|
28
|
+
}),
|
|
29
|
+
signal: controller.signal
|
|
30
|
+
});
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return (await response.json());
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
clearTimeout(timeout);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function isAnalyticsSessionFresh(session) {
|
|
44
|
+
if (!session) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return (new Date(session.expiresAt).getTime() - Date.now() >
|
|
48
|
+
SESSION_REFRESH_BUFFER_MS);
|
|
49
|
+
}
|
|
50
|
+
export function createAnalyticsProof(session, payload) {
|
|
51
|
+
const payloadHash = sha256(JSON.stringify(payload));
|
|
52
|
+
let counter = 0;
|
|
53
|
+
while (counter < 250_000) {
|
|
54
|
+
const proof = sha256(`${session.sessionId}:${session.nonce}:${payloadHash}:${counter}`);
|
|
55
|
+
if (hasLeadingZeroes(proof, session.difficulty)) {
|
|
56
|
+
return {
|
|
57
|
+
payloadHash,
|
|
58
|
+
counter,
|
|
59
|
+
proof
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
counter += 1;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
payloadHash,
|
|
66
|
+
counter,
|
|
67
|
+
proof: sha256(`${session.sessionId}:${session.nonce}:${payloadHash}:${counter}`)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/analytics/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { runCommand } from "../exec/runCommand.js";
|
|
2
|
+
import type { AIProvider, AnalyticsClient, AppConfig, PlatformContext, PromptAdapter } from "../types/index.js";
|
|
3
|
+
export interface CliDependencies {
|
|
4
|
+
loadConfig: () => Promise<AppConfig>;
|
|
5
|
+
detectPlatformContext: () => Promise<PlatformContext>;
|
|
6
|
+
createProvider: (config: AppConfig) => AIProvider;
|
|
7
|
+
createAnalyticsClient: (config: AppConfig) => AnalyticsClient;
|
|
8
|
+
createPromptAdapter: () => PromptAdapter;
|
|
9
|
+
copyToClipboard: (command: string) => Promise<void>;
|
|
10
|
+
commandRunner: typeof runCommand;
|
|
11
|
+
}
|
|
12
|
+
export declare function createDefaultDependencies(): CliDependencies;
|
|
13
|
+
export declare function runCli(argv?: string[], dependencies?: CliDependencies): Promise<void>;
|
|
14
|
+
export declare function runCliAndHandleErrors(argv?: string[], dependencies?: CliDependencies): Promise<void>;
|