truecourse 0.1.12 → 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 +179 -87
- package/db/migrations/0009_sticky_blizzard.sql +16 -0
- package/db/migrations/meta/0009_snapshot.json +2062 -0
- package/db/migrations/meta/_journal.json +7 -0
- package/package.json +4 -4
- package/public/assets/{index-DYIYjwei.js → index-DMFqQ7fW.js} +163 -153
- package/public/assets/index-JxEVjRt1.css +1 -0
- package/public/index.html +3 -3
- package/server.mjs +148662 -144587
- package/skills/truecourse/truecourse-analyze/SKILL.md +39 -0
- package/skills/truecourse/truecourse-fix/SKILL.md +34 -0
- package/skills/truecourse/truecourse-list/SKILL.md +26 -0
- package/public/assets/index-CFKYSTp1.css +0 -1
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,10 +12663,14 @@ async function startServiceMode(openBrowser) {
|
|
|
12662
12663
|
v2.info("Check logs with: truecourse service logs");
|
|
12663
12664
|
}
|
|
12664
12665
|
}
|
|
12665
|
-
function
|
|
12666
|
+
function getServerProcess() {
|
|
12667
|
+
return _serverProcess;
|
|
12668
|
+
}
|
|
12669
|
+
function startConsoleMode(openBrowser) {
|
|
12666
12670
|
const serverPath = getServerPath();
|
|
12671
|
+
const url2 = getServerUrl();
|
|
12667
12672
|
v2.step("Starting server (embedded PostgreSQL starts automatically)...");
|
|
12668
|
-
const serverProcess = spawn2(
|
|
12673
|
+
const serverProcess = _serverProcess = spawn2(
|
|
12669
12674
|
process.execPath,
|
|
12670
12675
|
[serverPath],
|
|
12671
12676
|
{
|
|
@@ -12687,6 +12692,11 @@ function startConsoleMode() {
|
|
|
12687
12692
|
};
|
|
12688
12693
|
process.on("SIGINT", cleanup);
|
|
12689
12694
|
process.on("SIGTERM", cleanup);
|
|
12695
|
+
if (openBrowser) {
|
|
12696
|
+
healthcheck().then((healthy) => {
|
|
12697
|
+
if (healthy) openInBrowser(url2);
|
|
12698
|
+
});
|
|
12699
|
+
}
|
|
12690
12700
|
}
|
|
12691
12701
|
async function runStart({ openBrowser = true } = {}) {
|
|
12692
12702
|
we("Starting TrueCourse");
|
|
@@ -12697,13 +12707,13 @@ async function runStart({ openBrowser = true } = {}) {
|
|
|
12697
12707
|
} catch (error) {
|
|
12698
12708
|
v2.error(`Service mode failed: ${error.message}`);
|
|
12699
12709
|
v2.info("Falling back to console mode. Reconfigure with: truecourse setup");
|
|
12700
|
-
startConsoleMode();
|
|
12710
|
+
startConsoleMode(openBrowser);
|
|
12701
12711
|
}
|
|
12702
12712
|
} else {
|
|
12703
|
-
startConsoleMode();
|
|
12713
|
+
startConsoleMode(openBrowser);
|
|
12704
12714
|
}
|
|
12705
12715
|
}
|
|
12706
|
-
var __dirname;
|
|
12716
|
+
var __dirname, _serverProcess;
|
|
12707
12717
|
var init_start = __esm({
|
|
12708
12718
|
"tools/cli/src/commands/start.ts"() {
|
|
12709
12719
|
"use strict";
|
|
@@ -12712,6 +12722,7 @@ var init_start = __esm({
|
|
|
12712
12722
|
init_platform();
|
|
12713
12723
|
init_logs();
|
|
12714
12724
|
__dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
12725
|
+
_serverProcess = null;
|
|
12715
12726
|
}
|
|
12716
12727
|
});
|
|
12717
12728
|
|
|
@@ -12724,6 +12735,7 @@ __export(helpers_exports, {
|
|
|
12724
12735
|
getConfigPath: () => getConfigPath,
|
|
12725
12736
|
getServerUrl: () => getServerUrl,
|
|
12726
12737
|
openInBrowser: () => openInBrowser,
|
|
12738
|
+
promptInstallSkills: () => promptInstallSkills,
|
|
12727
12739
|
readConfig: () => readConfig,
|
|
12728
12740
|
renderDiffResults: () => renderDiffResults,
|
|
12729
12741
|
renderDiffResultsSummary: () => renderDiffResultsSummary,
|
|
@@ -12734,8 +12746,11 @@ __export(helpers_exports, {
|
|
|
12734
12746
|
writeConfig: () => writeConfig
|
|
12735
12747
|
});
|
|
12736
12748
|
import { exec } from "node:child_process";
|
|
12749
|
+
import { cpSync, existsSync } from "node:fs";
|
|
12737
12750
|
import fs5 from "node:fs";
|
|
12738
12751
|
import path5 from "node:path";
|
|
12752
|
+
import { dirname, resolve } from "node:path";
|
|
12753
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12739
12754
|
import os4 from "node:os";
|
|
12740
12755
|
function getConfigPath() {
|
|
12741
12756
|
return path5.join(os4.homedir(), ".truecourse", "config.json");
|
|
@@ -12769,16 +12784,28 @@ async function ensureServer() {
|
|
|
12769
12784
|
if (!res.ok) throw new Error(`Server returned ${res.status}`);
|
|
12770
12785
|
return false;
|
|
12771
12786
|
} catch {
|
|
12772
|
-
const
|
|
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));
|
|
12773
12793
|
await runStart2({ openBrowser: false });
|
|
12774
|
-
|
|
12775
|
-
const
|
|
12776
|
-
if (!
|
|
12777
|
-
}
|
|
12778
|
-
|
|
12779
|
-
|
|
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));
|
|
12780
12806
|
}
|
|
12781
|
-
|
|
12807
|
+
v2.error("Server failed to start. Check logs with: truecourse service logs");
|
|
12808
|
+
process.exit(1);
|
|
12782
12809
|
}
|
|
12783
12810
|
}
|
|
12784
12811
|
async function ensureRepo() {
|
|
@@ -12801,7 +12828,11 @@ async function ensureRepo() {
|
|
|
12801
12828
|
v2.error(message);
|
|
12802
12829
|
process.exit(1);
|
|
12803
12830
|
}
|
|
12804
|
-
|
|
12831
|
+
const repo = await res.json();
|
|
12832
|
+
if (res.status === 201) {
|
|
12833
|
+
await promptInstallSkills(process.cwd());
|
|
12834
|
+
}
|
|
12835
|
+
return repo;
|
|
12805
12836
|
}
|
|
12806
12837
|
function connectSocket(repoId) {
|
|
12807
12838
|
const url2 = getServerUrl();
|
|
@@ -12995,9 +13026,30 @@ function renderDiffResultsSummary(result) {
|
|
|
12995
13026
|
v2.info("Run `truecourse list --diff` to see full details.");
|
|
12996
13027
|
}
|
|
12997
13028
|
function openInBrowser(url2) {
|
|
13029
|
+
console.log(` Opening ${url2}`);
|
|
12998
13030
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
12999
13031
|
exec(`${cmd} ${url2}`);
|
|
13000
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
|
+
}
|
|
13001
13053
|
var DEFAULT_PORT, DEFAULT_CONFIG;
|
|
13002
13054
|
var init_helpers = __esm({
|
|
13003
13055
|
"tools/cli/src/commands/helpers.ts"() {
|
|
@@ -13009,38 +13061,15 @@ var init_helpers = __esm({
|
|
|
13009
13061
|
}
|
|
13010
13062
|
});
|
|
13011
13063
|
|
|
13012
|
-
// node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
|
|
13013
|
-
var import_index = __toESM(require_commander(), 1);
|
|
13014
|
-
var {
|
|
13015
|
-
program,
|
|
13016
|
-
createCommand,
|
|
13017
|
-
createArgument,
|
|
13018
|
-
createOption,
|
|
13019
|
-
CommanderError,
|
|
13020
|
-
InvalidArgumentError,
|
|
13021
|
-
InvalidOptionArgumentError,
|
|
13022
|
-
// deprecated old name
|
|
13023
|
-
Command,
|
|
13024
|
-
Argument,
|
|
13025
|
-
Option,
|
|
13026
|
-
Help
|
|
13027
|
-
} = import_index.default;
|
|
13028
|
-
|
|
13029
|
-
// tools/cli/src/index.ts
|
|
13030
|
-
init_dist2();
|
|
13031
|
-
import fs7 from "node:fs";
|
|
13032
|
-
import path8 from "node:path";
|
|
13033
|
-
import os6 from "node:os";
|
|
13034
|
-
|
|
13035
13064
|
// tools/cli/src/commands/setup.ts
|
|
13036
|
-
|
|
13065
|
+
var setup_exports = {};
|
|
13066
|
+
__export(setup_exports, {
|
|
13067
|
+
runSetup: () => runSetup
|
|
13068
|
+
});
|
|
13069
|
+
import { execSync as execSync4 } from "node:child_process";
|
|
13037
13070
|
import fs6 from "node:fs";
|
|
13038
13071
|
import path6 from "node:path";
|
|
13039
13072
|
import os5 from "node:os";
|
|
13040
|
-
var DEFAULT_MODELS = {
|
|
13041
|
-
anthropic: "claude-sonnet-4-20250514",
|
|
13042
|
-
openai: "gpt-5.3-codex"
|
|
13043
|
-
};
|
|
13044
13073
|
function buildEnvContents(config) {
|
|
13045
13074
|
const lines = [
|
|
13046
13075
|
"# TrueCourse Environment Configuration",
|
|
@@ -13073,8 +13102,9 @@ async function runSetup() {
|
|
|
13073
13102
|
const provider = await de({
|
|
13074
13103
|
message: "Which LLM provider would you like to use?",
|
|
13075
13104
|
options: [
|
|
13076
|
-
{ value: "
|
|
13077
|
-
{ value: "
|
|
13105
|
+
{ value: "claude-code", label: "Claude Code CLI \u2014 no API key needed (Recommended)" },
|
|
13106
|
+
{ value: "anthropic", label: "Anthropic (Claude API)" },
|
|
13107
|
+
{ value: "openai", label: "OpenAI (GPT API)" },
|
|
13078
13108
|
{ value: "skip", label: "Skip for now" }
|
|
13079
13109
|
]
|
|
13080
13110
|
});
|
|
@@ -13083,9 +13113,18 @@ async function runSetup() {
|
|
|
13083
13113
|
process.exit(0);
|
|
13084
13114
|
}
|
|
13085
13115
|
const config = {};
|
|
13086
|
-
if (provider === "anthropic" || provider === "openai") {
|
|
13116
|
+
if (provider === "anthropic" || provider === "openai" || provider === "claude-code") {
|
|
13087
13117
|
config.provider = provider;
|
|
13088
13118
|
}
|
|
13119
|
+
if (provider === "claude-code") {
|
|
13120
|
+
try {
|
|
13121
|
+
execSync4("which claude", { stdio: "ignore" });
|
|
13122
|
+
} catch {
|
|
13123
|
+
v2.error("Claude Code CLI not found on PATH. Install it first: https://docs.anthropic.com/en/docs/claude-code");
|
|
13124
|
+
process.exit(1);
|
|
13125
|
+
}
|
|
13126
|
+
v2.success("Claude Code CLI detected.");
|
|
13127
|
+
}
|
|
13089
13128
|
if (provider === "anthropic") {
|
|
13090
13129
|
const anthropicKey = await ue({
|
|
13091
13130
|
message: "Enter your Anthropic API key:",
|
|
@@ -13130,7 +13169,7 @@ async function runSetup() {
|
|
|
13130
13169
|
}
|
|
13131
13170
|
config.model = model?.trim() || defaultModel;
|
|
13132
13171
|
}
|
|
13133
|
-
const useLangfuse = await me({
|
|
13172
|
+
const useLangfuse = provider !== "claude-code" && await me({
|
|
13134
13173
|
message: "Would you like to enable Langfuse tracing?",
|
|
13135
13174
|
initialValue: false
|
|
13136
13175
|
});
|
|
@@ -13176,8 +13215,8 @@ async function runSetup() {
|
|
|
13176
13215
|
const runMode = await de({
|
|
13177
13216
|
message: "How would you like to run TrueCourse?",
|
|
13178
13217
|
options: [
|
|
13179
|
-
{ value: "
|
|
13180
|
-
{ value: "
|
|
13218
|
+
{ value: "service", label: "Background service (Recommended)" },
|
|
13219
|
+
{ value: "console", label: "Console (keep terminal open)" }
|
|
13181
13220
|
]
|
|
13182
13221
|
});
|
|
13183
13222
|
if (BD(runMode)) {
|
|
@@ -13193,16 +13232,46 @@ async function runSetup() {
|
|
|
13193
13232
|
v2.info("Database migrations are applied on server startup.");
|
|
13194
13233
|
fe("Setup complete!");
|
|
13195
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;
|
|
13196
13263
|
|
|
13197
13264
|
// tools/cli/src/index.ts
|
|
13265
|
+
init_dist2();
|
|
13266
|
+
init_setup();
|
|
13198
13267
|
init_start();
|
|
13268
|
+
import fs7 from "node:fs";
|
|
13269
|
+
import path8 from "node:path";
|
|
13270
|
+
import os6 from "node:os";
|
|
13199
13271
|
|
|
13200
13272
|
// tools/cli/src/commands/add.ts
|
|
13201
13273
|
init_dist2();
|
|
13202
13274
|
init_helpers();
|
|
13203
|
-
import { cpSync, existsSync } from "node:fs";
|
|
13204
|
-
import { resolve, dirname } from "node:path";
|
|
13205
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
13206
13275
|
async function runAdd() {
|
|
13207
13276
|
const repoPath = process.cwd();
|
|
13208
13277
|
const serverUrl = getServerUrl();
|
|
@@ -13229,30 +13298,8 @@ async function runAdd() {
|
|
|
13229
13298
|
const repo = await res.json();
|
|
13230
13299
|
const repoUrl = `${serverUrl}/repos/${repo.id}`;
|
|
13231
13300
|
v2.success(`Repository "${repo.name}" added`);
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
});
|
|
13235
|
-
if (BD(installSkills)) {
|
|
13236
|
-
openInBrowser(repoUrl);
|
|
13237
|
-
fe("Opened in browser");
|
|
13238
|
-
return;
|
|
13239
|
-
}
|
|
13240
|
-
if (installSkills) {
|
|
13241
|
-
const cliDir = dirname(fileURLToPath2(import.meta.url));
|
|
13242
|
-
const skillsSrc = resolve(cliDir, "..", "..", "skills", "truecourse");
|
|
13243
|
-
if (!existsSync(skillsSrc)) {
|
|
13244
|
-
v2.warn("Skills directory not found in package \u2014 skipping.");
|
|
13245
|
-
} else {
|
|
13246
|
-
const skillsDest = resolve(repoPath, ".claude", "skills", "truecourse");
|
|
13247
|
-
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
13248
|
-
v2.success("Installed Claude Code skills:");
|
|
13249
|
-
v2.message(" - truecourse-analyze (run analysis)");
|
|
13250
|
-
v2.message(" - truecourse-list (list violations)");
|
|
13251
|
-
v2.message(" - truecourse-fix (apply fixes)");
|
|
13252
|
-
}
|
|
13253
|
-
}
|
|
13254
|
-
openInBrowser(repoUrl);
|
|
13255
|
-
fe("Opened in browser");
|
|
13301
|
+
await promptInstallSkills(repoPath);
|
|
13302
|
+
fe(`Open ${repoUrl}`);
|
|
13256
13303
|
} catch (err) {
|
|
13257
13304
|
const message = err instanceof Error ? err.message : String(err);
|
|
13258
13305
|
if (message.includes("ECONNREFUSED") || message.includes("fetch failed")) {
|
|
@@ -13270,15 +13317,37 @@ async function runAdd() {
|
|
|
13270
13317
|
init_dist2();
|
|
13271
13318
|
init_helpers();
|
|
13272
13319
|
var TIMEOUT_MS = 15 * 60 * 1e3;
|
|
13273
|
-
async function runAnalyze() {
|
|
13320
|
+
async function runAnalyze({ noAutostart = false } = {}) {
|
|
13274
13321
|
we("Analyzing repository");
|
|
13275
|
-
|
|
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();
|
|
13276
13333
|
const repo = await ensureRepo();
|
|
13277
13334
|
v2.step(`Repository: ${repo.name}`);
|
|
13278
13335
|
const serverUrl = getServerUrl();
|
|
13279
13336
|
const socket = connectSocket(repo.id);
|
|
13280
13337
|
const spinner = L2();
|
|
13281
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);
|
|
13282
13351
|
try {
|
|
13283
13352
|
await new Promise((resolve2, reject) => {
|
|
13284
13353
|
const timeout = setTimeout(() => {
|
|
@@ -13305,6 +13374,10 @@ async function runAnalyze() {
|
|
|
13305
13374
|
violationsReady = true;
|
|
13306
13375
|
checkDone();
|
|
13307
13376
|
});
|
|
13377
|
+
socket.on("analysis:canceled", () => {
|
|
13378
|
+
clearTimeout(timeout);
|
|
13379
|
+
reject(new Error("CANCELED"));
|
|
13380
|
+
});
|
|
13308
13381
|
fetch(`${serverUrl}/api/repos/${repo.id}/analyze`, {
|
|
13309
13382
|
method: "POST",
|
|
13310
13383
|
headers: { "Content-Type": "application/json" },
|
|
@@ -13336,23 +13409,42 @@ async function runAnalyze() {
|
|
|
13336
13409
|
}
|
|
13337
13410
|
const violations = await res.json();
|
|
13338
13411
|
renderViolationsSummary(violations);
|
|
13412
|
+
const repoUrl = `${serverUrl}/repos/${repo.id}`;
|
|
13339
13413
|
if (firstRun) {
|
|
13340
|
-
const repoUrl = `${serverUrl}/repos/${repo.id}`;
|
|
13341
13414
|
openInBrowser(repoUrl);
|
|
13415
|
+
fe("Analysis complete \u2014 opened in browser");
|
|
13416
|
+
} else {
|
|
13417
|
+
fe(`Analysis complete \u2014 open ${repoUrl}`);
|
|
13342
13418
|
}
|
|
13343
|
-
fe("Analysis complete");
|
|
13344
13419
|
} catch (err) {
|
|
13345
|
-
spinner.stop("Analysis failed");
|
|
13346
13420
|
const message = err instanceof Error ? err.message : String(err);
|
|
13347
|
-
|
|
13348
|
-
|
|
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
|
+
}
|
|
13349
13429
|
} finally {
|
|
13430
|
+
process.removeListener("SIGINT", onSigint);
|
|
13350
13431
|
socket.disconnect();
|
|
13351
13432
|
}
|
|
13352
13433
|
}
|
|
13353
|
-
async function runAnalyzeDiff() {
|
|
13434
|
+
async function runAnalyzeDiff({ noAutostart = false } = {}) {
|
|
13354
13435
|
we("Running diff check");
|
|
13355
|
-
|
|
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
|
+
}
|
|
13356
13448
|
const repo = await ensureRepo();
|
|
13357
13449
|
v2.step(`Repository: ${repo.name}`);
|
|
13358
13450
|
const serverUrl = getServerUrl();
|
|
@@ -13604,11 +13696,11 @@ program2.command("start").description("Start TrueCourse services").action(async
|
|
|
13604
13696
|
program2.command("add").description("Add the current directory as a repository").action(async () => {
|
|
13605
13697
|
await runAdd();
|
|
13606
13698
|
});
|
|
13607
|
-
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) => {
|
|
13608
13700
|
if (options.diff) {
|
|
13609
|
-
await runAnalyzeDiff();
|
|
13701
|
+
await runAnalyzeDiff({ noAutostart: !options.autostart });
|
|
13610
13702
|
} else {
|
|
13611
|
-
await runAnalyze();
|
|
13703
|
+
await runAnalyze({ noAutostart: !options.autostart });
|
|
13612
13704
|
}
|
|
13613
13705
|
});
|
|
13614
13706
|
program2.command("list").description("List violations from the latest analysis").option("--diff", "Show diff check results (new and resolved)").action(async (options) => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
CREATE TABLE "analysis_usage" (
|
|
2
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
3
|
+
"analysis_id" uuid NOT NULL,
|
|
4
|
+
"provider" text NOT NULL,
|
|
5
|
+
"call_type" text NOT NULL,
|
|
6
|
+
"input_tokens" integer DEFAULT 0 NOT NULL,
|
|
7
|
+
"output_tokens" integer DEFAULT 0 NOT NULL,
|
|
8
|
+
"cache_read_tokens" integer DEFAULT 0 NOT NULL,
|
|
9
|
+
"cache_write_tokens" integer DEFAULT 0 NOT NULL,
|
|
10
|
+
"total_tokens" integer DEFAULT 0 NOT NULL,
|
|
11
|
+
"cost_usd" text,
|
|
12
|
+
"duration_ms" integer DEFAULT 0 NOT NULL,
|
|
13
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
14
|
+
);
|
|
15
|
+
--> statement-breakpoint
|
|
16
|
+
ALTER TABLE "analysis_usage" ADD CONSTRAINT "analysis_usage_analysis_id_analyses_id_fk" FOREIGN KEY ("analysis_id") REFERENCES "public"."analyses"("id") ON DELETE cascade ON UPDATE no action;
|