ai-cmd 1.0.2 → 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/README.md +89 -10
- 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 +2 -1
- package/dist/cli/commands.js +134 -90
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/repl.d.ts +4 -1
- package/dist/cli/repl.js +19 -1
- package/dist/cli/repl.js.map +1 -1
- 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/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 +2 -0
- package/dist/config/userConfig.js +68 -18
- package/dist/config/userConfig.js.map +1 -1
- package/dist/core/generateCommand.js +7 -2
- package/dist/core/generateCommand.js.map +1 -1
- package/dist/core/prompts.d.ts +1 -0
- package/dist/core/prompts.js +4 -0
- package/dist/core/prompts.js.map +1 -1
- 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.js +16 -1
- package/dist/providers/factory.js.map +1 -1
- 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 +2 -2
- package/dist/providers/openai.js +10 -6
- package/dist/providers/openai.js.map +1 -1
- package/dist/types/index.d.ts +28 -2
- package/dist/utils/branding.d.ts +1 -1
- package/dist/utils/branding.js +7 -2
- package/dist/utils/branding.js.map +1 -1
- 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 +68 -68
package/README.md
CHANGED
|
@@ -44,7 +44,10 @@ This project is part of the **Ottili ONE ecosystem** — a modular AI system for
|
|
|
44
44
|
- Clipboard copy support
|
|
45
45
|
- Optional execution with confirmation
|
|
46
46
|
- Heuristic risk classification for destructive commands
|
|
47
|
-
-
|
|
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
|
|
48
51
|
- JSON mode for scripting
|
|
49
52
|
|
|
50
53
|
## Installation
|
|
@@ -69,23 +72,68 @@ npm link
|
|
|
69
72
|
|
|
70
73
|
Environment variables take precedence over the config file.
|
|
71
74
|
|
|
72
|
-
If
|
|
75
|
+
If required configuration is missing and you launch `ai-cmd` interactively, a first-run configurator opens and asks for:
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
- AI provider
|
|
78
|
+
- API key
|
|
79
|
+
- analytics consent
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
export AI_API_KEY="your-api-key"
|
|
78
|
-
```
|
|
81
|
+
If you opt in, `config.json` stores `"analytics": true` and a random anonymous install id. If you opt out, it stores `"analytics": false`.
|
|
79
82
|
|
|
80
|
-
###
|
|
83
|
+
### OpenAI
|
|
84
|
+
|
|
85
|
+
OpenAI remains the default provider:
|
|
81
86
|
|
|
82
87
|
```bash
|
|
88
|
+
export AI_API_KEY="your-api-key"
|
|
83
89
|
export AI_PROVIDER="openai"
|
|
84
90
|
export AI_MODEL="gpt-5.4-mini"
|
|
85
91
|
export AI_BASE_URL="https://api.openai.com/v1"
|
|
86
92
|
export AI_TIMEOUT_MS="30000"
|
|
87
93
|
```
|
|
88
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
|
+
|
|
89
137
|
### Example config file
|
|
90
138
|
|
|
91
139
|
`~/.ai-cmd/config.json`
|
|
@@ -96,11 +144,41 @@ export AI_TIMEOUT_MS="30000"
|
|
|
96
144
|
"model": "gpt-5.4-mini",
|
|
97
145
|
"apiKey": "your-api-key",
|
|
98
146
|
"baseUrl": "https://api.openai.com/v1",
|
|
99
|
-
"timeoutMs": 30000
|
|
147
|
+
"timeoutMs": 30000,
|
|
148
|
+
"analytics": false
|
|
100
149
|
}
|
|
101
150
|
```
|
|
102
151
|
|
|
103
|
-
|
|
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:
|
|
104
182
|
|
|
105
183
|
```text
|
|
106
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.
|
|
@@ -118,6 +196,8 @@ ai "remove node_modules and reinstall packages" --exec
|
|
|
118
196
|
ai "find all jpg files" --copy
|
|
119
197
|
```
|
|
120
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
|
+
|
|
121
201
|
### Interactive mode
|
|
122
202
|
|
|
123
203
|
```bash
|
|
@@ -263,7 +343,6 @@ Example shape:
|
|
|
263
343
|
|
|
264
344
|
## Roadmap
|
|
265
345
|
|
|
266
|
-
- additional provider adapters
|
|
267
346
|
- richer shell support
|
|
268
347
|
- more precise filesystem-aware risk detection
|
|
269
348
|
- Homebrew distribution
|
|
@@ -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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"}
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { runCommand } from "../exec/runCommand.js";
|
|
2
|
-
import type { AIProvider, AppConfig, PlatformContext, PromptAdapter } from "../types/index.js";
|
|
2
|
+
import type { AIProvider, AnalyticsClient, AppConfig, PlatformContext, PromptAdapter } from "../types/index.js";
|
|
3
3
|
export interface CliDependencies {
|
|
4
4
|
loadConfig: () => Promise<AppConfig>;
|
|
5
5
|
detectPlatformContext: () => Promise<PlatformContext>;
|
|
6
6
|
createProvider: (config: AppConfig) => AIProvider;
|
|
7
|
+
createAnalyticsClient: (config: AppConfig) => AnalyticsClient;
|
|
7
8
|
createPromptAdapter: () => PromptAdapter;
|
|
8
9
|
copyToClipboard: (command: string) => Promise<void>;
|
|
9
10
|
commandRunner: typeof runCommand;
|
package/dist/cli/commands.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import clipboardy from "clipboardy";
|
|
2
2
|
import { Command, Option } from "commander";
|
|
3
|
+
import { createAnalyticsClient as createDefaultAnalyticsClient } from "../analytics/client.js";
|
|
3
4
|
import { startRepl } from "./repl.js";
|
|
4
5
|
import { createPromptAdapter } from "./prompts.js";
|
|
5
|
-
import {
|
|
6
|
+
import { loadOrConfigureConfig } from "../config/configurator.js";
|
|
6
7
|
import { generateCommand } from "../core/generateCommand.js";
|
|
7
8
|
import { formatSuggestion } from "../core/output.js";
|
|
8
9
|
import { runCommand } from "../exec/runCommand.js";
|
|
@@ -10,14 +11,16 @@ import { detectPlatformContext } from "../platform/detectPlatform.js";
|
|
|
10
11
|
import { createProvider } from "../providers/factory.js";
|
|
11
12
|
import { assessCommandRisk } from "../safety/classifyRisk.js";
|
|
12
13
|
import { enforceExecutionPolicy } from "../safety/executionPolicy.js";
|
|
13
|
-
import { ClipboardError, ConfigurationError, getErrorMessage } from "../utils/errors.js";
|
|
14
|
+
import { ClipboardError, ConfigurationError, ExecutionPolicyError, UserCancelledError, getErrorMessage } from "../utils/errors.js";
|
|
14
15
|
import { formatVersionBanner } from "../utils/branding.js";
|
|
15
16
|
import { Logger } from "../utils/logger.js";
|
|
17
|
+
import { inspectWorkspace } from "../workspace/inspectWorkspace.js";
|
|
16
18
|
export function createDefaultDependencies() {
|
|
17
19
|
return {
|
|
18
|
-
loadConfig: () =>
|
|
20
|
+
loadConfig: () => loadOrConfigureConfig(),
|
|
19
21
|
detectPlatformContext,
|
|
20
22
|
createProvider,
|
|
23
|
+
createAnalyticsClient: createDefaultAnalyticsClient,
|
|
21
24
|
createPromptAdapter,
|
|
22
25
|
copyToClipboard: async (command) => {
|
|
23
26
|
try {
|
|
@@ -53,6 +56,32 @@ function ensureInteractiveFlagsAreValid(options) {
|
|
|
53
56
|
throw new ConfigurationError("--json, --exec, --yes, and --copy require a question in one-shot mode.");
|
|
54
57
|
}
|
|
55
58
|
}
|
|
59
|
+
function shouldReportError(error) {
|
|
60
|
+
return !(error instanceof UserCancelledError || error instanceof ExecutionPolicyError);
|
|
61
|
+
}
|
|
62
|
+
async function prepareRuntime(options, deps, mode) {
|
|
63
|
+
const config = await deps.loadConfig();
|
|
64
|
+
const platform = await deps.detectPlatformContext();
|
|
65
|
+
const effectivePlatform = options.shell
|
|
66
|
+
? { ...platform, shell: options.shell }
|
|
67
|
+
: platform;
|
|
68
|
+
const provider = deps.createProvider(config);
|
|
69
|
+
const analytics = deps.createAnalyticsClient(config);
|
|
70
|
+
const workspaceContext = await inspectWorkspace(effectivePlatform.cwd);
|
|
71
|
+
await analytics.trackCliStart({
|
|
72
|
+
os: effectivePlatform.os,
|
|
73
|
+
shell: effectivePlatform.shell,
|
|
74
|
+
provider: config.provider,
|
|
75
|
+
mode
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
config,
|
|
79
|
+
effectivePlatform,
|
|
80
|
+
provider,
|
|
81
|
+
analytics,
|
|
82
|
+
...(workspaceContext ? { workspaceContext } : {})
|
|
83
|
+
};
|
|
84
|
+
}
|
|
56
85
|
function writeJsonWithExecution(suggestion, execution) {
|
|
57
86
|
process.stdout.write(`${JSON.stringify({
|
|
58
87
|
question: suggestion.question,
|
|
@@ -65,31 +94,71 @@ function writeJsonWithExecution(suggestion, execution) {
|
|
|
65
94
|
execution
|
|
66
95
|
}, null, 2)}\n`);
|
|
67
96
|
}
|
|
68
|
-
async function handleOneShot(question, options, deps) {
|
|
97
|
+
async function handleOneShot(question, options, deps, runtime) {
|
|
69
98
|
const logger = new Logger(options.debug);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
try {
|
|
100
|
+
if (options.exec && runtime.effectivePlatform.os === "unsupported") {
|
|
101
|
+
throw new ConfigurationError("Execution is disabled on unsupported host OSes. Use a Unix-like shell or WSL.");
|
|
102
|
+
}
|
|
103
|
+
logger.debug("Using platform context", runtime.effectivePlatform);
|
|
104
|
+
logger.debug("Using provider", {
|
|
105
|
+
provider: runtime.config.provider,
|
|
106
|
+
model: runtime.config.model,
|
|
107
|
+
baseUrl: runtime.config.baseUrl
|
|
108
|
+
});
|
|
109
|
+
await runtime.analytics.trackPromptSent({
|
|
110
|
+
os: runtime.effectivePlatform.os,
|
|
111
|
+
shell: runtime.effectivePlatform.shell,
|
|
112
|
+
provider: runtime.config.provider,
|
|
113
|
+
mode: "one-shot"
|
|
114
|
+
});
|
|
115
|
+
const suggestion = await generateCommand({
|
|
116
|
+
question,
|
|
117
|
+
platform: runtime.effectivePlatform,
|
|
118
|
+
provider: runtime.provider,
|
|
119
|
+
explainRequested: true,
|
|
120
|
+
...(runtime.workspaceContext
|
|
121
|
+
? { workspaceContext: runtime.workspaceContext }
|
|
122
|
+
: {})
|
|
123
|
+
});
|
|
124
|
+
let execution;
|
|
125
|
+
if (options.json) {
|
|
126
|
+
if (options.copy) {
|
|
127
|
+
try {
|
|
128
|
+
await deps.copyToClipboard(suggestion.command);
|
|
129
|
+
process.stderr.write("Command copied to clipboard.\n");
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error instanceof ClipboardError) {
|
|
133
|
+
process.stderr.write(`${error.message}\n${suggestion.command}\n`);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (options.exec) {
|
|
141
|
+
const assessment = assessCommandRisk(suggestion.command);
|
|
142
|
+
await enforceExecutionPolicy({
|
|
143
|
+
command: suggestion.command,
|
|
144
|
+
risk: assessment.level,
|
|
145
|
+
yes: options.yes,
|
|
146
|
+
prompt: deps.createPromptAdapter(),
|
|
147
|
+
...(assessment.reasons[0] ? { reason: assessment.reasons[0] } : {})
|
|
148
|
+
});
|
|
149
|
+
execution = await deps.commandRunner(suggestion.command, {
|
|
150
|
+
cwd: runtime.effectivePlatform.cwd,
|
|
151
|
+
stdio: "pipe"
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
writeJsonWithExecution(suggestion, execution);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
process.stdout.write(`${formatSuggestion(suggestion, {
|
|
158
|
+
color: !options.noColor,
|
|
159
|
+
explain: true,
|
|
160
|
+
json: false
|
|
161
|
+
})}\n`);
|
|
93
162
|
if (options.copy) {
|
|
94
163
|
try {
|
|
95
164
|
await deps.copyToClipboard(suggestion.command);
|
|
@@ -104,57 +173,36 @@ async function handleOneShot(question, options, deps) {
|
|
|
104
173
|
}
|
|
105
174
|
}
|
|
106
175
|
}
|
|
107
|
-
if (options.exec) {
|
|
108
|
-
|
|
109
|
-
await enforceExecutionPolicy({
|
|
110
|
-
command: suggestion.command,
|
|
111
|
-
risk: assessment.level,
|
|
112
|
-
yes: options.yes,
|
|
113
|
-
prompt: deps.createPromptAdapter(),
|
|
114
|
-
...(assessment.reasons[0] ? { reason: assessment.reasons[0] } : {})
|
|
115
|
-
});
|
|
116
|
-
execution = await deps.commandRunner(suggestion.command, {
|
|
117
|
-
cwd: effectivePlatform.cwd,
|
|
118
|
-
stdio: "pipe"
|
|
119
|
-
});
|
|
176
|
+
if (!options.exec) {
|
|
177
|
+
return;
|
|
120
178
|
}
|
|
121
|
-
|
|
122
|
-
|
|
179
|
+
const assessment = assessCommandRisk(suggestion.command);
|
|
180
|
+
await enforceExecutionPolicy({
|
|
181
|
+
command: suggestion.command,
|
|
182
|
+
risk: assessment.level,
|
|
183
|
+
yes: options.yes,
|
|
184
|
+
prompt: deps.createPromptAdapter(),
|
|
185
|
+
...(assessment.reasons[0] ? { reason: assessment.reasons[0] } : {})
|
|
186
|
+
});
|
|
187
|
+
await deps.commandRunner(suggestion.command, {
|
|
188
|
+
cwd: runtime.effectivePlatform.cwd,
|
|
189
|
+
stdio: "inherit"
|
|
190
|
+
});
|
|
123
191
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (error instanceof ClipboardError) {
|
|
136
|
-
process.stderr.write(`${error.message}\n${suggestion.command}\n`);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
throw error;
|
|
140
|
-
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (shouldReportError(error)) {
|
|
194
|
+
await runtime.analytics.trackError({
|
|
195
|
+
prompt: question,
|
|
196
|
+
os: runtime.effectivePlatform.os,
|
|
197
|
+
shell: runtime.effectivePlatform.shell,
|
|
198
|
+
provider: runtime.config.provider,
|
|
199
|
+
message: getErrorMessage(error),
|
|
200
|
+
time: new Date().toISOString(),
|
|
201
|
+
...(error instanceof Error ? { code: error.name } : {})
|
|
202
|
+
});
|
|
141
203
|
}
|
|
204
|
+
throw error;
|
|
142
205
|
}
|
|
143
|
-
if (!options.exec) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const assessment = assessCommandRisk(suggestion.command);
|
|
147
|
-
await enforceExecutionPolicy({
|
|
148
|
-
command: suggestion.command,
|
|
149
|
-
risk: assessment.level,
|
|
150
|
-
yes: options.yes,
|
|
151
|
-
prompt: deps.createPromptAdapter(),
|
|
152
|
-
...(assessment.reasons[0] ? { reason: assessment.reasons[0] } : {})
|
|
153
|
-
});
|
|
154
|
-
await deps.commandRunner(suggestion.command, {
|
|
155
|
-
cwd: effectivePlatform.cwd,
|
|
156
|
-
stdio: "inherit"
|
|
157
|
-
});
|
|
158
206
|
}
|
|
159
207
|
export async function runCli(argv = process.argv, dependencies = createDefaultDependencies()) {
|
|
160
208
|
const program = new Command();
|
|
@@ -166,11 +214,7 @@ export async function runCli(argv = process.argv, dependencies = createDefaultDe
|
|
|
166
214
|
.option("--yes", "Skip the standard confirmation prompt for low/medium-risk commands")
|
|
167
215
|
.option("--explain", "Show the explanation alongside the generated command")
|
|
168
216
|
.option("--json", "Emit machine-readable JSON")
|
|
169
|
-
.addOption(new Option("--shell <shell>", "Shell hint for command generation").choices([
|
|
170
|
-
"bash",
|
|
171
|
-
"zsh",
|
|
172
|
-
"sh"
|
|
173
|
-
]))
|
|
217
|
+
.addOption(new Option("--shell <shell>", "Shell hint for command generation").choices(["bash", "zsh", "sh"]))
|
|
174
218
|
.option("-v, --version", "Show branded version information")
|
|
175
219
|
.option("--copy", "Copy the generated command to the clipboard")
|
|
176
220
|
.option("--no-color", "Disable colored output")
|
|
@@ -188,22 +232,23 @@ export async function runCli(argv = process.argv, dependencies = createDefaultDe
|
|
|
188
232
|
if (!question) {
|
|
189
233
|
ensureInteractiveFlagsAreValid(options);
|
|
190
234
|
const logger = new Logger(options.debug);
|
|
191
|
-
const
|
|
192
|
-
const platform = await dependencies.detectPlatformContext();
|
|
193
|
-
const provider = dependencies.createProvider(config);
|
|
194
|
-
const effectivePlatform = options.shell
|
|
195
|
-
? { ...platform, shell: options.shell }
|
|
196
|
-
: platform;
|
|
235
|
+
const runtime = await prepareRuntime(options, dependencies, "interactive");
|
|
197
236
|
await startRepl({
|
|
198
|
-
platform: effectivePlatform,
|
|
199
|
-
provider,
|
|
237
|
+
platform: runtime.effectivePlatform,
|
|
238
|
+
provider: runtime.provider,
|
|
239
|
+
providerName: runtime.config.provider,
|
|
200
240
|
prompt: dependencies.createPromptAdapter(),
|
|
241
|
+
analytics: runtime.analytics,
|
|
242
|
+
...(runtime.workspaceContext
|
|
243
|
+
? { workspaceContext: runtime.workspaceContext }
|
|
244
|
+
: {}),
|
|
201
245
|
color: !options.noColor,
|
|
202
246
|
logger
|
|
203
247
|
});
|
|
204
248
|
return;
|
|
205
249
|
}
|
|
206
|
-
await
|
|
250
|
+
const runtime = await prepareRuntime(options, dependencies, "one-shot");
|
|
251
|
+
await handleOneShot(question, options, dependencies, runtime);
|
|
207
252
|
});
|
|
208
253
|
await program.parseAsync(argv);
|
|
209
254
|
}
|
|
@@ -212,8 +257,7 @@ export async function runCliAndHandleErrors(argv = process.argv, dependencies =
|
|
|
212
257
|
await runCli(argv, dependencies);
|
|
213
258
|
}
|
|
214
259
|
catch (error) {
|
|
215
|
-
const debug = argv.includes("--debug") ||
|
|
216
|
-
argv.includes("-d");
|
|
260
|
+
const debug = argv.includes("--debug") || argv.includes("-d");
|
|
217
261
|
process.stderr.write(`${getErrorMessage(error, debug)}\n`);
|
|
218
262
|
process.exitCode = 1;
|
|
219
263
|
}
|