truecourse 0.1.13 → 0.1.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/cli.mjs CHANGED
@@ -12271,6 +12271,9 @@ var init_macos = __esm({
12271
12271
  async install(serverPath, logPath) {
12272
12272
  const envFile = path.join(os.homedir(), ".truecourse", ".env");
12273
12273
  const envVars = parseEnvFile(envFile);
12274
+ if (process.env.PATH && !envVars.PATH) {
12275
+ envVars.PATH = process.env.PATH;
12276
+ }
12274
12277
  fs2.mkdirSync(PLIST_DIR, { recursive: true });
12275
12278
  fs2.mkdirSync(path.dirname(logPath), { recursive: true });
12276
12279
  const plist = buildPlist(serverPath, logPath, envVars);
@@ -12610,6 +12613,7 @@ var init_logs = __esm({
12610
12613
  // tools/cli/src/commands/start.ts
12611
12614
  var start_exports = {};
12612
12615
  __export(start_exports, {
12616
+ getServerProcess: () => getServerProcess,
12613
12617
  runStart: () => runStart
12614
12618
  });
12615
12619
  import { spawn as spawn2 } from "node:child_process";
@@ -12620,7 +12624,7 @@ function getServerPath() {
12620
12624
  }
12621
12625
  async function healthcheck() {
12622
12626
  const url2 = getServerUrl();
12623
- for (let i = 0; i < 30; i++) {
12627
+ for (let i = 0; i < 120; i++) {
12624
12628
  try {
12625
12629
  const res = await fetch(`${url2}/api/health`);
12626
12630
  if (res.ok) return true;
@@ -12662,11 +12666,14 @@ async function startServiceMode(openBrowser) {
12662
12666
  v2.info("Check logs with: truecourse service logs");
12663
12667
  }
12664
12668
  }
12669
+ function getServerProcess() {
12670
+ return _serverProcess;
12671
+ }
12665
12672
  function startConsoleMode(openBrowser) {
12666
12673
  const serverPath = getServerPath();
12667
12674
  const url2 = getServerUrl();
12668
12675
  v2.step("Starting server (embedded PostgreSQL starts automatically)...");
12669
- const serverProcess = spawn2(
12676
+ const serverProcess = _serverProcess = spawn2(
12670
12677
  process.execPath,
12671
12678
  [serverPath],
12672
12679
  {
@@ -12709,7 +12716,7 @@ async function runStart({ openBrowser = true } = {}) {
12709
12716
  startConsoleMode(openBrowser);
12710
12717
  }
12711
12718
  }
12712
- var __dirname;
12719
+ var __dirname, _serverProcess;
12713
12720
  var init_start = __esm({
12714
12721
  "tools/cli/src/commands/start.ts"() {
12715
12722
  "use strict";
@@ -12718,6 +12725,7 @@ var init_start = __esm({
12718
12725
  init_platform();
12719
12726
  init_logs();
12720
12727
  __dirname = path4.dirname(fileURLToPath(import.meta.url));
12728
+ _serverProcess = null;
12721
12729
  }
12722
12730
  });
12723
12731
 
@@ -12730,6 +12738,7 @@ __export(helpers_exports, {
12730
12738
  getConfigPath: () => getConfigPath,
12731
12739
  getServerUrl: () => getServerUrl,
12732
12740
  openInBrowser: () => openInBrowser,
12741
+ promptInstallSkills: () => promptInstallSkills,
12733
12742
  readConfig: () => readConfig,
12734
12743
  renderDiffResults: () => renderDiffResults,
12735
12744
  renderDiffResultsSummary: () => renderDiffResultsSummary,
@@ -12740,8 +12749,11 @@ __export(helpers_exports, {
12740
12749
  writeConfig: () => writeConfig
12741
12750
  });
12742
12751
  import { exec } from "node:child_process";
12752
+ import { cpSync, existsSync } from "node:fs";
12743
12753
  import fs5 from "node:fs";
12744
12754
  import path5 from "node:path";
12755
+ import { dirname, resolve } from "node:path";
12756
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
12745
12757
  import os4 from "node:os";
12746
12758
  function getConfigPath() {
12747
12759
  return path5.join(os4.homedir(), ".truecourse", "config.json");
@@ -12775,16 +12787,28 @@ async function ensureServer() {
12775
12787
  if (!res.ok) throw new Error(`Server returned ${res.status}`);
12776
12788
  return false;
12777
12789
  } catch {
12778
- const { runStart: runStart2 } = await Promise.resolve().then(() => (init_start(), start_exports));
12790
+ const envPath = path5.join(os4.homedir(), ".truecourse", ".env");
12791
+ if (!fs5.existsSync(envPath)) {
12792
+ const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
12793
+ await runSetup2();
12794
+ }
12795
+ const { runStart: runStart2, getServerProcess: getServerProcess2 } = await Promise.resolve().then(() => (init_start(), start_exports));
12779
12796
  await runStart2({ openBrowser: false });
12780
- try {
12781
- const res = await fetch(`${url2}/api/health`);
12782
- if (!res.ok) throw new Error();
12783
- } catch {
12784
- v2.error("Server failed to start. Check logs with: truecourse service logs");
12785
- process.exit(1);
12797
+ const killServer = () => {
12798
+ const proc = getServerProcess2();
12799
+ if (proc && !proc.killed) proc.kill("SIGTERM");
12800
+ };
12801
+ process.on("exit", killServer);
12802
+ for (let i = 0; i < 120; i++) {
12803
+ try {
12804
+ const res = await fetch(`${url2}/api/health`);
12805
+ if (res.ok) return true;
12806
+ } catch {
12807
+ }
12808
+ await new Promise((r2) => setTimeout(r2, 500));
12786
12809
  }
12787
- return true;
12810
+ v2.error("Server failed to start. Check logs with: truecourse service logs");
12811
+ process.exit(1);
12788
12812
  }
12789
12813
  }
12790
12814
  async function ensureRepo() {
@@ -12807,7 +12831,11 @@ async function ensureRepo() {
12807
12831
  v2.error(message);
12808
12832
  process.exit(1);
12809
12833
  }
12810
- return await res.json();
12834
+ const repo = await res.json();
12835
+ if (res.status === 201) {
12836
+ await promptInstallSkills(process.cwd());
12837
+ }
12838
+ return repo;
12811
12839
  }
12812
12840
  function connectSocket(repoId) {
12813
12841
  const url2 = getServerUrl();
@@ -13005,6 +13033,26 @@ function openInBrowser(url2) {
13005
13033
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
13006
13034
  exec(`${cmd} ${url2}`);
13007
13035
  }
13036
+ async function promptInstallSkills(repoPath) {
13037
+ const installSkills = await me({
13038
+ message: "Would you like to install Claude Code skills?"
13039
+ });
13040
+ if (BD(installSkills) || !installSkills) return;
13041
+ const __dirname3 = dirname(fileURLToPath2(import.meta.url));
13042
+ const srcPath = resolve(__dirname3, "..", "..", "skills", "truecourse");
13043
+ const distPath = resolve(__dirname3, "skills", "truecourse");
13044
+ const skillsSrc = existsSync(srcPath) ? srcPath : distPath;
13045
+ if (!existsSync(skillsSrc)) {
13046
+ v2.warn("Skills directory not found in package \u2014 skipping.");
13047
+ return;
13048
+ }
13049
+ const skillsDest = resolve(repoPath, ".claude", "skills");
13050
+ cpSync(skillsSrc, skillsDest, { recursive: true });
13051
+ v2.success("Installed Claude Code skills:");
13052
+ v2.message(" - truecourse-analyze (run analysis)");
13053
+ v2.message(" - truecourse-list (list violations)");
13054
+ v2.message(" - truecourse-fix (apply fixes)");
13055
+ }
13008
13056
  var DEFAULT_PORT, DEFAULT_CONFIG;
13009
13057
  var init_helpers = __esm({
13010
13058
  "tools/cli/src/commands/helpers.ts"() {
@@ -13016,39 +13064,15 @@ var init_helpers = __esm({
13016
13064
  }
13017
13065
  });
13018
13066
 
13019
- // node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
13020
- var import_index = __toESM(require_commander(), 1);
13021
- var {
13022
- program,
13023
- createCommand,
13024
- createArgument,
13025
- createOption,
13026
- CommanderError,
13027
- InvalidArgumentError,
13028
- InvalidOptionArgumentError,
13029
- // deprecated old name
13030
- Command,
13031
- Argument,
13032
- Option,
13033
- Help
13034
- } = import_index.default;
13035
-
13036
- // tools/cli/src/index.ts
13037
- init_dist2();
13038
- import fs7 from "node:fs";
13039
- import path8 from "node:path";
13040
- import os6 from "node:os";
13041
-
13042
13067
  // tools/cli/src/commands/setup.ts
13043
- init_dist2();
13068
+ var setup_exports = {};
13069
+ __export(setup_exports, {
13070
+ runSetup: () => runSetup
13071
+ });
13044
13072
  import { execSync as execSync4 } from "node:child_process";
13045
13073
  import fs6 from "node:fs";
13046
13074
  import path6 from "node:path";
13047
13075
  import os5 from "node:os";
13048
- var DEFAULT_MODELS = {
13049
- anthropic: "claude-sonnet-4-20250514",
13050
- openai: "gpt-5.3-codex"
13051
- };
13052
13076
  function buildEnvContents(config) {
13053
13077
  const lines = [
13054
13078
  "# TrueCourse Environment Configuration",
@@ -13194,8 +13218,8 @@ async function runSetup() {
13194
13218
  const runMode = await de({
13195
13219
  message: "How would you like to run TrueCourse?",
13196
13220
  options: [
13197
- { value: "console", label: "Console (keep terminal open)" },
13198
- { value: "service", label: "Background service (runs automatically, no terminal needed)" }
13221
+ { value: "service", label: "Background service (Recommended)" },
13222
+ { value: "console", label: "Console (keep terminal open)" }
13199
13223
  ]
13200
13224
  });
13201
13225
  if (BD(runMode)) {
@@ -13211,16 +13235,46 @@ async function runSetup() {
13211
13235
  v2.info("Database migrations are applied on server startup.");
13212
13236
  fe("Setup complete!");
13213
13237
  }
13238
+ var DEFAULT_MODELS;
13239
+ var init_setup = __esm({
13240
+ "tools/cli/src/commands/setup.ts"() {
13241
+ "use strict";
13242
+ init_dist2();
13243
+ DEFAULT_MODELS = {
13244
+ anthropic: "claude-sonnet-4-20250514",
13245
+ openai: "gpt-5.3-codex"
13246
+ };
13247
+ }
13248
+ });
13249
+
13250
+ // node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
13251
+ var import_index = __toESM(require_commander(), 1);
13252
+ var {
13253
+ program,
13254
+ createCommand,
13255
+ createArgument,
13256
+ createOption,
13257
+ CommanderError,
13258
+ InvalidArgumentError,
13259
+ InvalidOptionArgumentError,
13260
+ // deprecated old name
13261
+ Command,
13262
+ Argument,
13263
+ Option,
13264
+ Help
13265
+ } = import_index.default;
13214
13266
 
13215
13267
  // tools/cli/src/index.ts
13268
+ init_dist2();
13269
+ init_setup();
13216
13270
  init_start();
13271
+ import fs7 from "node:fs";
13272
+ import path8 from "node:path";
13273
+ import os6 from "node:os";
13217
13274
 
13218
13275
  // tools/cli/src/commands/add.ts
13219
13276
  init_dist2();
13220
13277
  init_helpers();
13221
- import { cpSync, existsSync } from "node:fs";
13222
- import { resolve, dirname } from "node:path";
13223
- import { fileURLToPath as fileURLToPath2 } from "node:url";
13224
13278
  async function runAdd() {
13225
13279
  const repoPath = process.cwd();
13226
13280
  const serverUrl = getServerUrl();
@@ -13247,27 +13301,7 @@ async function runAdd() {
13247
13301
  const repo = await res.json();
13248
13302
  const repoUrl = `${serverUrl}/repos/${repo.id}`;
13249
13303
  v2.success(`Repository "${repo.name}" added`);
13250
- const installSkills = await me({
13251
- message: "Would you like to install Claude Code skills?"
13252
- });
13253
- if (BD(installSkills)) {
13254
- fe(`Open ${repoUrl}`);
13255
- return;
13256
- }
13257
- if (installSkills) {
13258
- const cliDir = dirname(fileURLToPath2(import.meta.url));
13259
- const skillsSrc = resolve(cliDir, "..", "..", "skills", "truecourse");
13260
- if (!existsSync(skillsSrc)) {
13261
- v2.warn("Skills directory not found in package \u2014 skipping.");
13262
- } else {
13263
- const skillsDest = resolve(repoPath, ".claude", "skills", "truecourse");
13264
- cpSync(skillsSrc, skillsDest, { recursive: true });
13265
- v2.success("Installed Claude Code skills:");
13266
- v2.message(" - truecourse-analyze (run analysis)");
13267
- v2.message(" - truecourse-list (list violations)");
13268
- v2.message(" - truecourse-fix (apply fixes)");
13269
- }
13270
- }
13304
+ await promptInstallSkills(repoPath);
13271
13305
  fe(`Open ${repoUrl}`);
13272
13306
  } catch (err) {
13273
13307
  const message = err instanceof Error ? err.message : String(err);
@@ -13286,15 +13320,37 @@ async function runAdd() {
13286
13320
  init_dist2();
13287
13321
  init_helpers();
13288
13322
  var TIMEOUT_MS = 15 * 60 * 1e3;
13289
- async function runAnalyze() {
13323
+ async function runAnalyze({ noAutostart = false } = {}) {
13290
13324
  we("Analyzing repository");
13291
- const firstRun = await ensureServer();
13325
+ if (noAutostart) {
13326
+ const url2 = getServerUrl();
13327
+ try {
13328
+ const res = await fetch(`${url2}/api/health`);
13329
+ if (!res.ok) throw new Error();
13330
+ } catch {
13331
+ v2.error("TrueCourse server is not running. Start it with: npx truecourse start");
13332
+ process.exit(1);
13333
+ }
13334
+ }
13335
+ const firstRun = noAutostart ? false : await ensureServer();
13292
13336
  const repo = await ensureRepo();
13293
13337
  v2.step(`Repository: ${repo.name}`);
13294
13338
  const serverUrl = getServerUrl();
13295
13339
  const socket = connectSocket(repo.id);
13296
13340
  const spinner = L2();
13297
13341
  spinner.start("Starting analysis...");
13342
+ let canceled = false;
13343
+ const onSigint = () => {
13344
+ if (canceled) return;
13345
+ canceled = true;
13346
+ spinner.stop("Cancelling analysis...");
13347
+ fetch(`${serverUrl}/api/repos/${repo.id}/analyze/cancel`, { method: "POST" }).catch(() => {
13348
+ }).finally(() => {
13349
+ socket.disconnect();
13350
+ process.exit(130);
13351
+ });
13352
+ };
13353
+ process.on("SIGINT", onSigint);
13298
13354
  try {
13299
13355
  await new Promise((resolve2, reject) => {
13300
13356
  const timeout = setTimeout(() => {
@@ -13321,6 +13377,10 @@ async function runAnalyze() {
13321
13377
  violationsReady = true;
13322
13378
  checkDone();
13323
13379
  });
13380
+ socket.on("analysis:canceled", () => {
13381
+ clearTimeout(timeout);
13382
+ reject(new Error("CANCELED"));
13383
+ });
13324
13384
  fetch(`${serverUrl}/api/repos/${repo.id}/analyze`, {
13325
13385
  method: "POST",
13326
13386
  headers: { "Content-Type": "application/json" },
@@ -13360,17 +13420,34 @@ async function runAnalyze() {
13360
13420
  fe(`Analysis complete \u2014 open ${repoUrl}`);
13361
13421
  }
13362
13422
  } catch (err) {
13363
- spinner.stop("Analysis failed");
13364
13423
  const message = err instanceof Error ? err.message : String(err);
13365
- v2.error(message);
13366
- process.exit(1);
13424
+ if (message === "CANCELED") {
13425
+ spinner.stop("Analysis cancelled");
13426
+ fe("Analysis cancelled");
13427
+ } else {
13428
+ spinner.stop("Analysis failed");
13429
+ v2.error(message);
13430
+ process.exit(1);
13431
+ }
13367
13432
  } finally {
13433
+ process.removeListener("SIGINT", onSigint);
13368
13434
  socket.disconnect();
13369
13435
  }
13370
13436
  }
13371
- async function runAnalyzeDiff() {
13437
+ async function runAnalyzeDiff({ noAutostart = false } = {}) {
13372
13438
  we("Running diff check");
13373
- await ensureServer();
13439
+ if (noAutostart) {
13440
+ const url2 = getServerUrl();
13441
+ try {
13442
+ const res = await fetch(`${url2}/api/health`);
13443
+ if (!res.ok) throw new Error();
13444
+ } catch {
13445
+ v2.error("TrueCourse server is not running. Start it with: npx truecourse start");
13446
+ process.exit(1);
13447
+ }
13448
+ } else {
13449
+ await ensureServer();
13450
+ }
13374
13451
  const repo = await ensureRepo();
13375
13452
  v2.step(`Repository: ${repo.name}`);
13376
13453
  const serverUrl = getServerUrl();
@@ -13622,11 +13699,11 @@ program2.command("start").description("Start TrueCourse services").action(async
13622
13699
  program2.command("add").description("Add the current directory as a repository").action(async () => {
13623
13700
  await runAdd();
13624
13701
  });
13625
- program2.command("analyze").description("Analyze the current repository").option("--diff", "Run diff check against latest analysis").action(async (options) => {
13702
+ program2.command("analyze").description("Analyze the current repository").option("--diff", "Run diff check against latest analysis").option("--no-autostart", "Don't auto-start the server (for use from Claude Code skills)").action(async (options) => {
13626
13703
  if (options.diff) {
13627
- await runAnalyzeDiff();
13704
+ await runAnalyzeDiff({ noAutostart: !options.autostart });
13628
13705
  } else {
13629
- await runAnalyze();
13706
+ await runAnalyze({ noAutostart: !options.autostart });
13630
13707
  }
13631
13708
  });
13632
13709
  program2.command("list").description("List violations from the latest analysis").option("--diff", "Show diff check results (new and resolved)").action(async (options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "truecourse",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Visualize your codebase architecture as an interactive graph",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,9 +15,9 @@
15
15
  "dotenv": "^16.4.0",
16
16
  "embedded-postgres": "18.3.0-beta.16",
17
17
  "postgres": "^3.4.0",
18
- "tree-sitter": "^0.21.1",
19
- "tree-sitter-javascript": "^0.21.4",
20
- "tree-sitter-typescript": "^0.21.2"
18
+ "tree-sitter": "^0.25.0",
19
+ "tree-sitter-javascript": "^0.25.0",
20
+ "tree-sitter-typescript": "^0.23.2"
21
21
  },
22
22
  "optionalDependencies": {
23
23
  "node-windows": "^1.0.0-beta.8"