@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 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.14",
4
- "description": "Myte CLI core implementation (Project Assistant + deterministic diffs).",
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": {