@mytegroupinc/myte-core 0.0.14 → 0.0.15
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 +5 -0
- package/cli.js +137 -1
- package/lib/ai-gateway.js +112 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
Internal implementation package for the `myte` CLI.
|
|
4
4
|
|
|
5
5
|
Most users should install the unscoped wrapper instead:
|
|
6
|
+
- `npm install myte` then `npx myte ai "Explain this repository"`
|
|
7
|
+
- `npm install myte` then `npx myte ai "Return a JSON object with risks and next_steps" --json-response`
|
|
6
8
|
- `npm install myte` then `npx myte bootstrap`
|
|
7
9
|
- `npm install myte` then `npx myte run-qaqc --mission-ids M001 --wait --sync`
|
|
8
10
|
- `npm install myte` then `npx myte sync-qaqc`
|
|
@@ -48,6 +50,7 @@ Requirements:
|
|
|
48
50
|
- Node `18+`
|
|
49
51
|
- macOS, Linux, or Windows
|
|
50
52
|
- `git` in `PATH` for `--with-diff`
|
|
53
|
+
- `MYTEAI_API_KEY=<inference_api_key>` in env or `.env` for `myte ai`
|
|
51
54
|
- `MYTE_API_KEY=<project_api_key>` in env or `.env`
|
|
52
55
|
- repo folder names must match the project repo names configured in Myte, including casing on case-sensitive filesystems
|
|
53
56
|
|
|
@@ -114,6 +117,8 @@ Deterministic `create-prd` contract:
|
|
|
114
117
|
- Optional structured fields: `priority`, `status`, `tags`, `assigned_user_email`, `assigned_user_id`, `due_date`, `repo_name`, `repo_id`, `preview_url`, `source`.
|
|
115
118
|
|
|
116
119
|
Examples:
|
|
120
|
+
- `npx myte ai "Explain what this project does"`
|
|
121
|
+
- `npx myte ai "Return a JSON object with risks and next_steps" --json-response`
|
|
117
122
|
- `npx myte bootstrap`
|
|
118
123
|
- `npx myte run-qaqc --mission-ids "M001,M002" --wait --sync`
|
|
119
124
|
- `npx myte sync-qaqc`
|
package/cli.js
CHANGED
|
@@ -11,6 +11,15 @@ const fs = require("fs");
|
|
|
11
11
|
const path = require("path");
|
|
12
12
|
const { createHash } = require("crypto");
|
|
13
13
|
const { spawnSync } = require("child_process");
|
|
14
|
+
const {
|
|
15
|
+
DEFAULT_MYTEAI_BASE,
|
|
16
|
+
buildSimpleAiPayload,
|
|
17
|
+
callMyteAiChat,
|
|
18
|
+
extractAssistantTextFromChatCompletion,
|
|
19
|
+
getMyteAiKey,
|
|
20
|
+
normalizeJsonAssistantText,
|
|
21
|
+
normalizeMyteAiBase,
|
|
22
|
+
} = require("./lib/ai-gateway");
|
|
14
23
|
|
|
15
24
|
const DEFAULT_API_BASE = "https://api.myte.dev";
|
|
16
25
|
const REMOVED_COMMAND_MESSAGES = {
|
|
@@ -59,6 +68,7 @@ function loadEnv() {
|
|
|
59
68
|
function splitCommand(argv) {
|
|
60
69
|
const known = new Set([
|
|
61
70
|
"query",
|
|
71
|
+
"ai",
|
|
62
72
|
"ask",
|
|
63
73
|
"chat",
|
|
64
74
|
"config",
|
|
@@ -91,10 +101,11 @@ function parseArgs(argv) {
|
|
|
91
101
|
try {
|
|
92
102
|
// eslint-disable-next-line global-require
|
|
93
103
|
const parsed = require("minimist")(argv, {
|
|
94
|
-
boolean: ["with-diff", "diff", "print-context", "dry-run", "fetch", "json", "stdin", "with-prd-text", "wait", "sync", "force"],
|
|
104
|
+
boolean: ["with-diff", "diff", "print-context", "dry-run", "fetch", "json", "stdin", "with-prd-text", "wait", "sync", "force", "json-response"],
|
|
95
105
|
string: [
|
|
96
106
|
"query",
|
|
97
107
|
"q",
|
|
108
|
+
"payload-file",
|
|
98
109
|
"context",
|
|
99
110
|
"ctx",
|
|
100
111
|
"file",
|
|
@@ -102,6 +113,9 @@ function parseArgs(argv) {
|
|
|
102
113
|
"base-url",
|
|
103
114
|
"timeout-ms",
|
|
104
115
|
"diff-limit",
|
|
116
|
+
"max-output-tokens",
|
|
117
|
+
"max-tokens",
|
|
118
|
+
"temperature",
|
|
105
119
|
"title",
|
|
106
120
|
"description",
|
|
107
121
|
"feedback-text",
|
|
@@ -171,6 +185,7 @@ function printHelp() {
|
|
|
171
185
|
"",
|
|
172
186
|
"Usage:",
|
|
173
187
|
" myte query \"<text>\" [--with-diff] [--context \"...\"]",
|
|
188
|
+
" myte ai \"<text>\" [--json-response] [--max-output-tokens 500]",
|
|
174
189
|
" myte config [--json]",
|
|
175
190
|
" myte bootstrap [--output-dir ./MyteCommandCenter] [--json]",
|
|
176
191
|
" myte run-qaqc --mission-ids \"M001[,M002...]\" [--wait] [--sync] [--force] [--json]",
|
|
@@ -190,12 +205,17 @@ function printHelp() {
|
|
|
190
205
|
"",
|
|
191
206
|
"Run forms:",
|
|
192
207
|
" npm install myte then npx myte query \"...\" --with-diff",
|
|
208
|
+
" npm install myte then npx myte ai \"Explain this code path\"",
|
|
193
209
|
" npm install myte then npm exec myte -- query \"...\" --with-diff",
|
|
210
|
+
" npm install myte then npm exec myte -- ai \"Return JSON only\" --json-response",
|
|
194
211
|
" npm i -g myte then myte query \"...\" --with-diff",
|
|
212
|
+
" npm i -g myte then myte ai \"Summarize this file\"",
|
|
195
213
|
" npx myte@latest query \"What changed in logging?\" --with-diff",
|
|
214
|
+
" npx myte@latest ai \"Return a JSON checklist\" --json-response",
|
|
196
215
|
"",
|
|
197
216
|
"Auth:",
|
|
198
217
|
" - Set MYTE_API_KEY in a workspace .env (or env var)",
|
|
218
|
+
" - Set MYTEAI_API_KEY in a workspace .env (or env var) for `myte ai`",
|
|
199
219
|
"",
|
|
200
220
|
"bootstrap contract:",
|
|
201
221
|
" - Run from the wrapper root that contains the project's configured repo folders",
|
|
@@ -253,6 +273,10 @@ function printHelp() {
|
|
|
253
273
|
" --diff-limit <chars> Truncate diff context to N chars (default: 200000)",
|
|
254
274
|
" --timeout-ms <ms> Request timeout (default: 300000)",
|
|
255
275
|
" --base-url <url> API base (default: https://api.myte.dev)",
|
|
276
|
+
" --payload-file <path> Raw OpenAI-style chat-completions payload for `myte ai`",
|
|
277
|
+
" --json-response Ask the Myte AI gateway to return clean JSON only",
|
|
278
|
+
" --max-output-tokens Output token cap for `myte ai` simple queries",
|
|
279
|
+
" --temperature <num> Temperature for `myte ai` simple queries",
|
|
256
280
|
" --output-dir <path> Command Center output directory (default: <wrapper-root>/MyteCommandCenter)",
|
|
257
281
|
" --file <path> YAML/JSON payload file for suggestions create/revise/review",
|
|
258
282
|
" --stdin Read supported command content from stdin instead of inline text or a file path",
|
|
@@ -278,6 +302,8 @@ function printHelp() {
|
|
|
278
302
|
"",
|
|
279
303
|
"Examples:",
|
|
280
304
|
" myte query \"What changed in logging?\" --with-diff",
|
|
305
|
+
" myte ai \"Explain what this repository does\"",
|
|
306
|
+
" myte ai \"Return a JSON object with risks and next_steps\" --json-response",
|
|
281
307
|
" myte bootstrap",
|
|
282
308
|
" myte suggestions sync",
|
|
283
309
|
" myte suggestions create",
|
|
@@ -3509,6 +3535,111 @@ async function runSuggestions(args) {
|
|
|
3509
3535
|
process.exit(1);
|
|
3510
3536
|
}
|
|
3511
3537
|
|
|
3538
|
+
async function runAi(args) {
|
|
3539
|
+
const key = getMyteAiKey(process.env);
|
|
3540
|
+
if (!key) {
|
|
3541
|
+
console.error("Missing MYTEAI_API_KEY in environment/.env");
|
|
3542
|
+
process.exit(1);
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
const printContext = Boolean(args["print-context"] || args.printContext || args["dry-run"] || args.dryRun);
|
|
3546
|
+
const timeoutMs = resolveTimeoutMs(args);
|
|
3547
|
+
const baseRaw = args["base-url"] || args.baseUrl || args.base_url || process.env.MYTEAI_API_BASE || process.env.MYTE_AI_API_BASE || DEFAULT_MYTEAI_BASE;
|
|
3548
|
+
const apiBase = normalizeMyteAiBase(baseRaw);
|
|
3549
|
+
const jsonResponse = Boolean(args["json-response"] || args.jsonResponse || args.json_response);
|
|
3550
|
+
const payloadFile = firstNonEmptyString(args["payload-file"], args.payloadFile, args.payload_file);
|
|
3551
|
+
|
|
3552
|
+
let payload;
|
|
3553
|
+
if (payloadFile) {
|
|
3554
|
+
const absPath = resolveInputFile(payloadFile, "AI payload");
|
|
3555
|
+
try {
|
|
3556
|
+
payload = JSON.parse(String(fs.readFileSync(absPath, "utf8") || ""));
|
|
3557
|
+
} catch (err) {
|
|
3558
|
+
console.error(`AI payload must be valid JSON: ${err?.message || err}`);
|
|
3559
|
+
process.exit(1);
|
|
3560
|
+
}
|
|
3561
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
3562
|
+
console.error("AI payload file must contain one JSON object.");
|
|
3563
|
+
process.exit(1);
|
|
3564
|
+
}
|
|
3565
|
+
if (jsonResponse && payload.myte_json_response === undefined) {
|
|
3566
|
+
payload.myte_json_response = true;
|
|
3567
|
+
}
|
|
3568
|
+
} else {
|
|
3569
|
+
const inlineQuery = firstNonEmptyString(args.query, args.q, Array.isArray(args._) ? args._.join(" ") : args._);
|
|
3570
|
+
const useStdin = Boolean(args.stdin || (!process.stdin.isTTY && !inlineQuery));
|
|
3571
|
+
let query = inlineQuery;
|
|
3572
|
+
if (useStdin) {
|
|
3573
|
+
query = String((await readStdinText()) || "").trim();
|
|
3574
|
+
}
|
|
3575
|
+
if (!query) {
|
|
3576
|
+
console.error("Missing AI query text.");
|
|
3577
|
+
printHelp();
|
|
3578
|
+
process.exit(1);
|
|
3579
|
+
}
|
|
3580
|
+
payload = buildSimpleAiPayload({
|
|
3581
|
+
query,
|
|
3582
|
+
jsonResponse,
|
|
3583
|
+
maxOutputTokens: firstNonEmptyString(args["max-output-tokens"], args.maxOutputTokens, args.max_output_tokens, args["max-tokens"], args.maxTokens, args.max_tokens),
|
|
3584
|
+
temperature: firstNonEmptyString(args.temperature),
|
|
3585
|
+
});
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
if (printContext) {
|
|
3589
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
const fetchFn = await getFetch();
|
|
3594
|
+
let responseBody;
|
|
3595
|
+
try {
|
|
3596
|
+
responseBody = await callMyteAiChat({
|
|
3597
|
+
fetchFn,
|
|
3598
|
+
apiBase,
|
|
3599
|
+
apiKey: key,
|
|
3600
|
+
payload,
|
|
3601
|
+
timeoutMs,
|
|
3602
|
+
});
|
|
3603
|
+
} catch (err) {
|
|
3604
|
+
if (err?.name === "AbortError") {
|
|
3605
|
+
console.error(`Request timed out after ${timeoutMs}ms`);
|
|
3606
|
+
} else {
|
|
3607
|
+
console.error("Myte AI request failed:", err?.message || err);
|
|
3608
|
+
}
|
|
3609
|
+
process.exit(1);
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
let content = extractAssistantTextFromChatCompletion(responseBody);
|
|
3613
|
+
let normalizedJson = null;
|
|
3614
|
+
if (jsonResponse) {
|
|
3615
|
+
try {
|
|
3616
|
+
normalizedJson = normalizeJsonAssistantText(content);
|
|
3617
|
+
content = normalizedJson.text;
|
|
3618
|
+
} catch {
|
|
3619
|
+
content = String(content || "").trim();
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
if (args.json) {
|
|
3624
|
+
console.log(
|
|
3625
|
+
JSON.stringify(
|
|
3626
|
+
{
|
|
3627
|
+
id: responseBody.id || null,
|
|
3628
|
+
model: responseBody.model || null,
|
|
3629
|
+
usage: responseBody.usage || null,
|
|
3630
|
+
content,
|
|
3631
|
+
parsed_json: normalizedJson ? normalizedJson.parsed : null,
|
|
3632
|
+
},
|
|
3633
|
+
null,
|
|
3634
|
+
2
|
|
3635
|
+
)
|
|
3636
|
+
);
|
|
3637
|
+
return;
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
console.log(content || "");
|
|
3641
|
+
}
|
|
3642
|
+
|
|
3512
3643
|
async function runQuery(args) {
|
|
3513
3644
|
const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
|
|
3514
3645
|
if (!key) {
|
|
@@ -3629,6 +3760,11 @@ async function main() {
|
|
|
3629
3760
|
return;
|
|
3630
3761
|
}
|
|
3631
3762
|
|
|
3763
|
+
if (command === "ai") {
|
|
3764
|
+
await runAi(args);
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3632
3768
|
if (command === "bootstrap") {
|
|
3633
3769
|
await runBootstrap(args);
|
|
3634
3770
|
return;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_MYTEAI_BASE = "https://api.myte.ai/v1";
|
|
4
|
+
|
|
5
|
+
function normalizeMyteAiBase(baseRaw) {
|
|
6
|
+
const baseTrim = String(baseRaw || "").trim().replace(/\/+$/, "");
|
|
7
|
+
const base = baseTrim || DEFAULT_MYTEAI_BASE;
|
|
8
|
+
return /\/v1$/i.test(base) ? base : `${base}/v1`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getMyteAiKey(env = process.env) {
|
|
12
|
+
return String(env.MYTEAI_API_KEY || env.MYTE_AI_API_KEY || "").trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildSimpleAiPayload({ query, jsonResponse = false, maxOutputTokens, temperature }) {
|
|
16
|
+
const payload = {
|
|
17
|
+
messages: [{ role: "user", content: String(query || "").trim() }],
|
|
18
|
+
};
|
|
19
|
+
if (jsonResponse) payload.myte_json_response = true;
|
|
20
|
+
if (Number.isFinite(Number(maxOutputTokens)) && Number(maxOutputTokens) > 0) {
|
|
21
|
+
payload.max_tokens = Number(maxOutputTokens);
|
|
22
|
+
}
|
|
23
|
+
if (temperature !== undefined && temperature !== null && temperature !== "") {
|
|
24
|
+
const parsed = Number(temperature);
|
|
25
|
+
if (Number.isFinite(parsed)) payload.temperature = parsed;
|
|
26
|
+
}
|
|
27
|
+
return payload;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function extractAssistantTextFromChatCompletion(payload) {
|
|
31
|
+
const choices = Array.isArray(payload?.choices) ? payload.choices : [];
|
|
32
|
+
const first = choices[0] && typeof choices[0] === "object" ? choices[0] : {};
|
|
33
|
+
const message = first.message && typeof first.message === "object" ? first.message : {};
|
|
34
|
+
const content = message.content;
|
|
35
|
+
if (typeof content === "string") return content.trim();
|
|
36
|
+
if (Array.isArray(content)) {
|
|
37
|
+
return content
|
|
38
|
+
.map((item) => {
|
|
39
|
+
if (typeof item === "string") return item;
|
|
40
|
+
if (!item || typeof item !== "object") return "";
|
|
41
|
+
if (typeof item.text === "string") return item.text;
|
|
42
|
+
if (typeof item.content === "string") return item.content;
|
|
43
|
+
return "";
|
|
44
|
+
})
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.join("\n")
|
|
47
|
+
.trim();
|
|
48
|
+
}
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function stripJsonFences(text) {
|
|
53
|
+
const raw = String(text || "").trim();
|
|
54
|
+
const match = raw.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
55
|
+
if (match) return String(match[1] || "").trim();
|
|
56
|
+
return raw;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeJsonAssistantText(text) {
|
|
60
|
+
const stripped = stripJsonFences(text);
|
|
61
|
+
const parsed = JSON.parse(stripped);
|
|
62
|
+
return {
|
|
63
|
+
parsed,
|
|
64
|
+
text: JSON.stringify(parsed, null, 2),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function callMyteAiChat({ fetchFn, apiBase, apiKey, payload, timeoutMs }) {
|
|
69
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : undefined;
|
|
70
|
+
const timeoutId =
|
|
71
|
+
controller && timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : undefined;
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetchFn(`${apiBase}/chat/completions`, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: {
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
Authorization: `Bearer ${apiKey}`,
|
|
78
|
+
},
|
|
79
|
+
signal: controller?.signal,
|
|
80
|
+
body: JSON.stringify(payload),
|
|
81
|
+
});
|
|
82
|
+
const text = await response.text();
|
|
83
|
+
let body;
|
|
84
|
+
try {
|
|
85
|
+
body = JSON.parse(text);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const err = new Error(`Non-JSON response (${response.status}): ${text.slice(0, 500)}`);
|
|
88
|
+
err.status = response.status;
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const err = new Error(body?.error?.message || body?.message || `Request failed (${response.status})`);
|
|
93
|
+
err.status = response.status;
|
|
94
|
+
err.body = body;
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
return body;
|
|
98
|
+
} finally {
|
|
99
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
DEFAULT_MYTEAI_BASE,
|
|
105
|
+
buildSimpleAiPayload,
|
|
106
|
+
callMyteAiChat,
|
|
107
|
+
extractAssistantTextFromChatCompletion,
|
|
108
|
+
getMyteAiKey,
|
|
109
|
+
normalizeJsonAssistantText,
|
|
110
|
+
normalizeMyteAiBase,
|
|
111
|
+
stripJsonFences,
|
|
112
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mytegroupinc/myte-core",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Myte CLI core implementation (Project Assistant +
|
|
3
|
+
"version": "0.0.15",
|
|
4
|
+
"description": "Myte CLI core implementation (Project Assistant + Myte AI gateway).",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "cli.js",
|
|
7
7
|
"files": [
|
|
8
8
|
"README.md",
|
|
9
9
|
"cli.js",
|
|
10
|
+
"lib",
|
|
10
11
|
"package.json"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|