@tostudy-ai/cli 0.18.11 → 0.18.16

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/main.js CHANGED
@@ -1842,12 +1842,30 @@ var init_formatter = __esm({
1842
1842
  }
1843
1843
  });
1844
1844
 
1845
+ // src/io/has-binary.ts
1846
+ import { execFileSync } from "node:child_process";
1847
+ function locatorCommand() {
1848
+ return process.platform === "win32" ? "where" : "which";
1849
+ }
1850
+ function hasBinary(name) {
1851
+ try {
1852
+ execFileSync(locatorCommand(), [name], { encoding: "utf-8", stdio: "pipe", timeout: 3e3 });
1853
+ return true;
1854
+ } catch {
1855
+ return false;
1856
+ }
1857
+ }
1858
+ var init_has_binary = __esm({
1859
+ "src/io/has-binary.ts"() {
1860
+ "use strict";
1861
+ }
1862
+ });
1863
+
1845
1864
  // src/installer/ide-detector.ts
1846
1865
  var ide_detector_exports = {};
1847
1866
  __export(ide_detector_exports, {
1848
1867
  detectIDEs: () => detectIDEs
1849
1868
  });
1850
- import { execFileSync } from "node:child_process";
1851
1869
  import fs3 from "node:fs";
1852
1870
  import path4 from "node:path";
1853
1871
  import os3 from "node:os";
@@ -1937,11 +1955,7 @@ function detectIDEs(home = os3.homedir()) {
1937
1955
  ];
1938
1956
  const claudeIde = ides.find((ide) => ide.id === "claude-code");
1939
1957
  if (claudeIde) {
1940
- try {
1941
- execFileSync("which", ["claude"], { encoding: "utf-8" });
1942
- claudeIde.detected = true;
1943
- } catch {
1944
- }
1958
+ claudeIde.detected = hasBinary("claude");
1945
1959
  }
1946
1960
  for (const [id, bin] of [
1947
1961
  ["grok", "grok"],
@@ -1949,11 +1963,7 @@ function detectIDEs(home = os3.homedir()) {
1949
1963
  ]) {
1950
1964
  const ide = ides.find((i) => i.id === id);
1951
1965
  if (ide && !ide.detected) {
1952
- try {
1953
- execFileSync("which", [bin], { encoding: "utf-8" });
1954
- ide.detected = true;
1955
- } catch {
1956
- }
1966
+ ide.detected = hasBinary(bin);
1957
1967
  }
1958
1968
  }
1959
1969
  return ides;
@@ -1961,6 +1971,7 @@ function detectIDEs(home = os3.homedir()) {
1961
1971
  var init_ide_detector = __esm({
1962
1972
  "src/installer/ide-detector.ts"() {
1963
1973
  "use strict";
1974
+ init_has_binary();
1964
1975
  }
1965
1976
  });
1966
1977
 
@@ -2333,14 +2344,6 @@ __export(runtime_registry_exports, {
2333
2344
  import { execFileSync as execFileSync2 } from "node:child_process";
2334
2345
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
2335
2346
  import { dirname, join as join2 } from "node:path";
2336
- function hasBinary(name) {
2337
- try {
2338
- execFileSync2("which", [name], { encoding: "utf-8", stdio: "pipe" });
2339
- return true;
2340
- } catch {
2341
- return false;
2342
- }
2343
- }
2344
2347
  function writeFile2(absDir, fileName, body) {
2345
2348
  mkdirSync2(absDir, { recursive: true });
2346
2349
  writeFileSync2(join2(absDir, fileName), body);
@@ -2402,10 +2405,10 @@ ${content}`;
2402
2405
  function detectedSlashHints(slug, home, cwd) {
2403
2406
  return RUNTIMES.filter((r) => r.slashHint && r.detect(home, cwd)).map((r) => r.slashHint(slug));
2404
2407
  }
2405
- function slashHintLines(slug, home, cwd, fallbackCommand) {
2408
+ function slashHintLines(slug, home, cwd) {
2406
2409
  const hints = slug ? detectedSlashHints(slug, home, cwd) : [];
2407
2410
  if (hints.length > 0) return hints.map((h) => `\u2192 ${h}`);
2408
- return [`\u2192 Na sua plataforma, digite: ${fallbackCommand}`];
2411
+ return ["\u2192 Na sua plataforma, pe\xE7a: vamos estudar (no terminal: tostudy menu)"];
2409
2412
  }
2410
2413
  function needsRootAgentsConsent(cwd) {
2411
2414
  const filePath = join2(cwd, "AGENTS.md");
@@ -2425,7 +2428,7 @@ function upsertRootAgentsBlock(cwd, ctx) {
2425
2428
  "",
2426
2429
  `- **Course:** ${ctx.courseTitle} (${ctx.progress}% complete)`,
2427
2430
  `- Read \`.tostudy/courses/${slug}/AGENTS.md\` for the full tutor instructions.`,
2428
- "- Study commands: run `/tostudy` (or `tostudy menu` in the terminal).",
2431
+ '- To study, ask in plain language (e.g. "vamos estudar"). In Claude Code or OpenCode you can also run `/tostudy`; in the terminal, `tostudy menu`.',
2429
2432
  "",
2430
2433
  "<!-- tostudy:end -->"
2431
2434
  ].join("\n");
@@ -2467,6 +2470,7 @@ var init_runtime_registry = __esm({
2467
2470
  "use strict";
2468
2471
  init_dist();
2469
2472
  init_slug();
2473
+ init_has_binary();
2470
2474
  init_instruction_template_v3();
2471
2475
  logger2 = createLogger("cli:runtime-registry");
2472
2476
  detectOpencode = (home) => existsSync2(join2(home, ".opencode")) || existsSync2(join2(home, ".config", "opencode"));
@@ -2518,7 +2522,10 @@ var init_runtime_registry = __esm({
2518
2522
  const file2 = join2(home, ".codex", "prompts", "tostudy.md");
2519
2523
  writeFile2(join2(home, ".codex", "prompts"), "tostudy.md", content);
2520
2524
  return [file2];
2521
- }
2525
+ },
2526
+ // GH-2351: Codex never reads ~/.codex/prompts, so there is no /tostudy there;
2527
+ // asking in plain language works (it reads AGENTS.md and finds the course skill).
2528
+ slashHint: () => "No Codex, abra o Codex e pe\xE7a: vamos estudar"
2522
2529
  },
2523
2530
  {
2524
2531
  id: "opencode",
@@ -3735,27 +3742,57 @@ import { Command as Command3 } from "commander";
3735
3742
 
3736
3743
  // src/installer/mcp-setup.ts
3737
3744
  import { spawn } from "node:child_process";
3738
- async function exchangeCliSessionForMcpToken(session, fetchImpl = fetch) {
3739
- const res = await fetchImpl(`${session.apiUrl}/api/mcp/token`, {
3740
- method: "POST",
3741
- headers: {
3742
- Authorization: `Bearer ${session.token}`
3743
- }
3744
- });
3745
+ import { hostname } from "node:os";
3746
+
3747
+ // src/version.ts
3748
+ var CLI_VERSION = true ? "0.18.16" : "0.7.1";
3749
+
3750
+ // src/installer/mcp-setup.ts
3751
+ function safeServerError(text2) {
3752
+ if (typeof text2 !== "string" || text2.length === 0 || text2.length > 200) return null;
3753
+ return ["Bearer", "mcp_", "eyJ"].some((m) => text2.includes(m)) ? null : text2;
3754
+ }
3755
+ async function createMcpApiKey(session, fetchImpl = fetch) {
3756
+ let res;
3757
+ try {
3758
+ res = await fetchImpl(`${session.apiUrl}/api/mcp/sessions`, {
3759
+ method: "POST",
3760
+ headers: {
3761
+ Authorization: `Bearer ${session.token}`,
3762
+ "Content-Type": "application/json"
3763
+ },
3764
+ body: JSON.stringify({
3765
+ clientInfo: {
3766
+ source: "tostudy-cli-setup",
3767
+ hostname: hostname(),
3768
+ os: process.platform,
3769
+ version: CLI_VERSION
3770
+ }
3771
+ })
3772
+ });
3773
+ } catch {
3774
+ throw new Error("Falha ao criar a chave MCP: erro de rede");
3775
+ }
3745
3776
  if (!res.ok) {
3746
3777
  const body = await res.json().catch(() => ({}));
3747
- throw new Error(body.error ?? `Falha ao obter token MCP (${res.status})`);
3778
+ throw new Error(
3779
+ `Falha ao criar a chave MCP: ${safeServerError(body.error) ?? `HTTP ${res.status}`}`
3780
+ );
3781
+ }
3782
+ const { apiKey } = await res.json().catch(() => ({}));
3783
+ if (typeof apiKey !== "string" || !apiKey.startsWith("mcp_")) {
3784
+ throw new Error("Falha ao criar a chave MCP: resposta inv\xE1lida do servidor");
3748
3785
  }
3749
- return res.json();
3786
+ return apiKey;
3750
3787
  }
3751
- async function runMcpSetup(session, token, spawnImpl = spawn) {
3788
+ async function runMcpSetup(session, apiKey, spawnImpl = spawn) {
3752
3789
  const command = process.platform === "win32" ? "npx.cmd" : "npx";
3753
3790
  await new Promise((resolve, reject) => {
3754
3791
  const child = spawnImpl(command, ["-y", "@tostudy-ai/mcp-setup", "--url", session.apiUrl], {
3755
3792
  stdio: "inherit",
3756
3793
  env: {
3757
3794
  ...process.env,
3758
- TOSTUDY_API_KEY: token
3795
+ TOSTUDY_API_KEY: apiKey
3759
3796
  }
3760
3797
  });
3761
3798
  child.once("error", reject);
@@ -3790,8 +3827,15 @@ async function runSetupMcpSubcommand() {
3790
3827
  const session = await requireSession();
3791
3828
  console.log(` \u2713 Logado como ${session.userName}
3792
3829
  `);
3793
- const { token } = await exchangeCliSessionForMcpToken(session);
3794
- await runMcpSetup(session, token);
3830
+ const apiKey = await createMcpApiKey(session);
3831
+ try {
3832
+ await runMcpSetup(session, apiKey);
3833
+ } catch (error49) {
3834
+ console.error(
3835
+ " Instala\xE7\xE3o incompleta: a chave MCP criada aparece em Configura\xE7\xF5es \u203A MCP e pode ser revogada."
3836
+ );
3837
+ throw error49;
3838
+ }
3795
3839
  console.log("\n Setup MCP completo!");
3796
3840
  console.log(" \u2192 Reinicie o IDE para ativar o servidor MCP");
3797
3841
  console.log(" \u2192 tostudy doctor (verificar conex\xE3o)\n");
@@ -3845,9 +3889,6 @@ init_session_store();
3845
3889
  init_workspace_state();
3846
3890
  init_formatter();
3847
3891
 
3848
- // src/version.ts
3849
- var CLI_VERSION = true ? "0.18.11" : "0.7.1";
3850
-
3851
3892
  // src/update-checker.ts
3852
3893
  init_config_dir();
3853
3894
  import fs8 from "node:fs";
@@ -5206,7 +5247,6 @@ var selectCommand = new Command8("select").description("Activate a course by ID
5206
5247
  { json: true }
5207
5248
  );
5208
5249
  } else {
5209
- const slashCmd = courseSlug2 ? `/tostudy-${courseSlug2}` : "/tostudy";
5210
5250
  const home = os7.homedir();
5211
5251
  const cwd = process.cwd();
5212
5252
  const namespacedPath = `${cwd}/.tostudy`;
@@ -5238,7 +5278,7 @@ var selectCommand = new Command8("select").description("Activate a course by ID
5238
5278
  lines.push(rootAgentsSkippedHint());
5239
5279
  }
5240
5280
  lines.push("", "\u2192 Abra sua plataforma (claude, codex, cursor...)");
5241
- lines.push(...slashHintLines(courseSlug2, home, cwd, slashCmd));
5281
+ lines.push(...slashHintLines(courseSlug2, home, cwd));
5242
5282
  lines.push("\u2192 Em outras plataformas: tostudy init");
5243
5283
  output(lines.join("\n"), { json: false });
5244
5284
  }
@@ -5927,7 +5967,7 @@ __export(external_exports, {
5927
5967
  guid: () => guid2,
5928
5968
  hash: () => hash,
5929
5969
  hex: () => hex2,
5930
- hostname: () => hostname2,
5970
+ hostname: () => hostname3,
5931
5971
  httpUrl: () => httpUrl,
5932
5972
  includes: () => _includes,
5933
5973
  instanceof: () => _instanceof,
@@ -7314,7 +7354,7 @@ __export(regexes_exports, {
7314
7354
  extendedDuration: () => extendedDuration,
7315
7355
  guid: () => guid,
7316
7356
  hex: () => hex,
7317
- hostname: () => hostname,
7357
+ hostname: () => hostname2,
7318
7358
  html5Email: () => html5Email,
7319
7359
  idnEmail: () => idnEmail,
7320
7360
  integer: () => integer,
@@ -7391,7 +7431,7 @@ var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]
7391
7431
  var cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
7392
7432
  var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
7393
7433
  var base64url = /^[A-Za-z0-9_-]*$/;
7394
- var hostname = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
7434
+ var hostname2 = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
7395
7435
  var domain = /^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
7396
7436
  var e164 = /^\+[1-9]\d{6,14}$/;
7397
7437
  var dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
@@ -17761,7 +17801,7 @@ __export(schemas_exports2, {
17761
17801
  guid: () => guid2,
17762
17802
  hash: () => hash,
17763
17803
  hex: () => hex2,
17764
- hostname: () => hostname2,
17804
+ hostname: () => hostname3,
17765
17805
  httpUrl: () => httpUrl,
17766
17806
  instanceof: () => _instanceof,
17767
17807
  int: () => int,
@@ -18265,7 +18305,7 @@ var ZodCustomStringFormat = /* @__PURE__ */ $constructor("ZodCustomStringFormat"
18265
18305
  function stringFormat(format, fnOrRegex, _params = {}) {
18266
18306
  return _stringFormat(ZodCustomStringFormat, format, fnOrRegex, _params);
18267
18307
  }
18268
- function hostname2(_params) {
18308
+ function hostname3(_params) {
18269
18309
  return _stringFormat(ZodCustomStringFormat, "hostname", regexes_exports.hostname, _params);
18270
18310
  }
18271
18311
  function hex2(_params) {
@@ -20190,8 +20230,9 @@ var menuCommand = new Command18("menu").description("Show available commands and
20190
20230
  } else {
20191
20231
  lines.push(
20192
20232
  " Curso ativo: (nenhum)",
20193
- " \u2192 tostudy courses para ver seus cursos",
20233
+ " \u2192 tostudy courses para ver seus cursos",
20194
20234
  " \u2192 tostudy select <n\xFAmero> para ativar um curso",
20235
+ " \u2192 tostudy suggest se voc\xEA ainda n\xE3o tem nenhum curso",
20195
20236
  ""
20196
20237
  );
20197
20238
  }