truecourse 0.1.13 → 0.1.14

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