litmus-cli 1.0.0 → 1.0.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiBA,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0J1D"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiBA,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+J1D"}
@@ -31,7 +31,7 @@ export async function runInit(token) {
31
31
  const targetDir = path.resolve(process.cwd(), metadata.folderName);
32
32
  const { existsSync } = await import("fs");
33
33
  if (existsSync(targetDir)) {
34
- fatal(`Folder "${metadata.folderName}" already exists in this directory.`, `Delete or rename it, then run: litmus init ${token}`);
34
+ fatal(`Folder "${metadata.folderName}" already exists in this directory.`, `Delete or rename it, then re-run litmus init with your token.`);
35
35
  }
36
36
  // Print what we're setting up
37
37
  console.log();
@@ -87,12 +87,18 @@ export async function runInit(token) {
87
87
  }
88
88
  // Step 4: Initialize git repo with initial commit
89
89
  try {
90
- execSync("git init", { cwd: targetDir, stdio: "pipe" });
91
- execSync("git add -A", { cwd: targetDir, stdio: "pipe" });
92
- execSync("git commit -m \"Assessment: initial state\"", { cwd: targetDir, stdio: "pipe" });
90
+ execSync("git --version", { stdio: "pipe" });
91
+ try {
92
+ execSync("git init", { cwd: targetDir, stdio: "pipe" });
93
+ execSync("git add -A", { cwd: targetDir, stdio: "pipe" });
94
+ execSync("git commit -m \"Assessment: initial state\"", { cwd: targetDir, stdio: "pipe" });
95
+ }
96
+ catch {
97
+ warn("Git is installed but failed to initialize. Check your git config (user.name / user.email).");
98
+ }
93
99
  }
94
100
  catch {
95
- // Non-critical — git may not be installed; analysis degrades gracefully
101
+ // git not installed — non-critical, analysis degrades gracefully
96
102
  }
97
103
  // Step 5: Write .litmus/config.json
98
104
  await writeConfig(targetDir, {
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runStatus(): Promise<void>;
2
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAOA,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAsG/C"}
@@ -0,0 +1,134 @@
1
+ import { existsSync, readFileSync, statSync, readdirSync } from "fs";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import { readConfig, findProjectRoot } from "../lib/config.js";
5
+ import { fatal, info } from "../utils/errors.js";
6
+ import { execSync } from "child_process";
7
+ export async function runStatus() {
8
+ const config = await readConfig();
9
+ if (!config) {
10
+ fatal("No Litmus assessment found in this directory.", "Run this command from inside your assessment folder, or run 'litmus init <token>' first.");
11
+ }
12
+ const projectRoot = findProjectRoot() ?? process.cwd();
13
+ const folderName = path.basename(projectRoot);
14
+ console.log();
15
+ console.log(chalk.bold(` ${config.assessmentName}`));
16
+ console.log();
17
+ // Candidate info
18
+ info(` Candidate: ${config.candidateName} (${config.candidateEmail})`);
19
+ info(` Folder: ${folderName}/`);
20
+ // Time elapsed since init
21
+ const startedAt = new Date(config.startedAt);
22
+ const elapsed = Date.now() - startedAt.getTime();
23
+ const elapsedHours = Math.floor(elapsed / (1000 * 60 * 60));
24
+ const elapsedMins = Math.floor((elapsed % (1000 * 60 * 60)) / (1000 * 60));
25
+ info(` Started: ${startedAt.toLocaleString()} (${elapsedHours}h ${elapsedMins}m ago)`);
26
+ // Deadline / time remaining
27
+ if (config.deadline) {
28
+ const deadline = new Date(config.deadline);
29
+ const remaining = deadline.getTime() - Date.now();
30
+ if (remaining <= 0) {
31
+ console.log(chalk.red(` Deadline: PASSED (${deadline.toLocaleString()})`));
32
+ }
33
+ else {
34
+ const remHours = Math.floor(remaining / (1000 * 60 * 60));
35
+ const remMins = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
36
+ info(` Deadline: ${deadline.toLocaleString()} (${remHours}h ${remMins}m remaining)`);
37
+ }
38
+ }
39
+ if (config.timeLimit) {
40
+ const timeLimitMs = config.timeLimit * 60 * 1000;
41
+ const used = Date.now() - startedAt.getTime();
42
+ const remaining = timeLimitMs - used;
43
+ if (remaining <= 0) {
44
+ console.log(chalk.red(` Time limit: EXCEEDED (${config.timeLimit} minutes)`));
45
+ }
46
+ else {
47
+ const remMins = Math.floor(remaining / (1000 * 60));
48
+ info(` Time limit: ${config.timeLimit} min (${remMins} min remaining)`);
49
+ }
50
+ }
51
+ console.log();
52
+ // Git stats
53
+ try {
54
+ execSync("git --version", { stdio: "pipe" });
55
+ try {
56
+ const commitCount = execSync("git rev-list --count HEAD", { cwd: projectRoot, stdio: "pipe" }).toString().trim();
57
+ const lastCommit = execSync("git log -1 --format=%s", { cwd: projectRoot, stdio: "pipe" }).toString().trim();
58
+ info(` Commits: ${commitCount}`);
59
+ info(` Last commit: "${lastCommit}"`);
60
+ }
61
+ catch {
62
+ info(` Commits: no commits yet`);
63
+ }
64
+ }
65
+ catch {
66
+ info(` Git: not installed`);
67
+ }
68
+ // Estimated submission size (count files, approximate size)
69
+ const sizeInfo = estimateSubmissionSize(projectRoot);
70
+ info(` Files: ~${sizeInfo.fileCount}`);
71
+ info(` Estimated submission size: ~${sizeInfo.sizeMb} MB`);
72
+ // Tracker status
73
+ const pidFile = path.join(projectRoot, ".litmus", "tracker.pid");
74
+ let trackerRunning = false;
75
+ try {
76
+ const pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
77
+ if (!isNaN(pid)) {
78
+ process.kill(pid, 0); // check if process exists (signal 0)
79
+ trackerRunning = true;
80
+ }
81
+ }
82
+ catch { /* not running */ }
83
+ info(` Tracker: ${trackerRunning ? "running" : "not running"}`);
84
+ // Activity log stats
85
+ const activityPath = path.join(projectRoot, ".litmus", "activity.jsonl");
86
+ if (existsSync(activityPath)) {
87
+ try {
88
+ const content = readFileSync(activityPath, "utf8");
89
+ const lines = content.split("\n").filter(l => l.trim());
90
+ info(` Activity events: ${lines.length}`);
91
+ }
92
+ catch { /* ignore */ }
93
+ }
94
+ console.log();
95
+ console.log(chalk.dim(" When you're ready to submit:"));
96
+ console.log(` ${chalk.cyan("litmus submit")}`);
97
+ console.log();
98
+ }
99
+ function estimateSubmissionSize(projectRoot) {
100
+ let fileCount = 0;
101
+ let totalBytes = 0;
102
+ const SKIP_DIRS = new Set([
103
+ "node_modules", "__pycache__", "venv", ".venv", "env", ".env",
104
+ "target", "build", "dist", ".gradle", ".idea",
105
+ ]);
106
+ function walk(dir) {
107
+ let entries;
108
+ try {
109
+ entries = readdirSync(dir);
110
+ }
111
+ catch {
112
+ return;
113
+ }
114
+ for (const entry of entries) {
115
+ if (SKIP_DIRS.has(entry))
116
+ continue;
117
+ const fullPath = path.join(dir, entry);
118
+ try {
119
+ const stat = statSync(fullPath);
120
+ if (stat.isDirectory()) {
121
+ walk(fullPath);
122
+ }
123
+ else {
124
+ fileCount++;
125
+ totalBytes += stat.size;
126
+ }
127
+ }
128
+ catch { /* permission errors, etc. */ }
129
+ }
130
+ }
131
+ walk(projectRoot);
132
+ return { fileCount, sizeMb: (totalBytes / (1024 * 1024)).toFixed(1) };
133
+ }
134
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AACpE,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAA;IAEjC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,KAAK,CACH,+CAA+C,EAC/C,0FAA0F,CAC3F,CAAA;IACH,CAAC;IAED,MAAM,WAAW,GAAG,eAAe,EAAE,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;IACtD,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;IAE7C,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAA;IACrD,OAAO,CAAC,GAAG,EAAE,CAAA;IAEb,iBAAiB;IACjB,IAAI,CAAC,gBAAgB,MAAM,CAAC,aAAa,KAAK,MAAM,CAAC,cAAc,GAAG,CAAC,CAAA;IACvE,IAAI,CAAC,aAAa,UAAU,GAAG,CAAC,CAAA;IAEhC,0BAA0B;IAC1B,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,CAAA;IAChD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAA;IAC3D,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAA;IAC1E,IAAI,CAAC,cAAc,SAAS,CAAC,cAAc,EAAE,KAAK,YAAY,KAAK,WAAW,QAAQ,CAAC,CAAA;IAEvF,4BAA4B;IAC5B,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC1C,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAEjD,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,uBAAuB,QAAQ,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC,CAAA;QAC7E,CAAC;aAAM,CAAC;YACN,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAA;YACzD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAA;YACxE,IAAI,CAAC,eAAe,QAAQ,CAAC,cAAc,EAAE,KAAK,QAAQ,KAAK,OAAO,cAAc,CAAC,CAAA;QACvF,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,GAAG,EAAE,GAAG,IAAI,CAAA;QAChD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,CAAA;QAC7C,MAAM,SAAS,GAAG,WAAW,GAAG,IAAI,CAAA;QACpC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,2BAA2B,MAAM,CAAC,SAAS,WAAW,CAAC,CAAC,CAAA;QAChF,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAA;YACnD,IAAI,CAAC,iBAAiB,MAAM,CAAC,SAAS,SAAS,OAAO,iBAAiB,CAAC,CAAA;QAC1E,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAA;IAEb,YAAY;IACZ,IAAI,CAAC;QACH,QAAQ,CAAC,eAAe,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC5C,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,QAAQ,CAAC,2BAA2B,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAA;YAChH,MAAM,UAAU,GAAG,QAAQ,CAAC,wBAAwB,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAA;YAC5G,IAAI,CAAC,cAAc,WAAW,EAAE,CAAC,CAAA;YACjC,IAAI,CAAC,mBAAmB,UAAU,GAAG,CAAC,CAAA;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,2BAA2B,CAAC,CAAA;QACnC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,CAAC,sBAAsB,CAAC,CAAA;IAC9B,CAAC;IAED,4DAA4D;IAC5D,MAAM,QAAQ,GAAG,sBAAsB,CAAC,WAAW,CAAC,CAAA;IACpD,IAAI,CAAC,aAAa,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAA;IACvC,IAAI,CAAC,iCAAiC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAA;IAE3D,iBAAiB;IACjB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,aAAa,CAAC,CAAA;IAChE,IAAI,cAAc,GAAG,KAAK,CAAA;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;QAC9D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA,CAAC,qCAAqC;YAC1D,cAAc,GAAG,IAAI,CAAA;QACvB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC7B,IAAI,CAAC,cAAc,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAA;IAEhE,qBAAqB;IACrB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAA;IACxE,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;YAClD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;YACvD,IAAI,CAAC,sBAAsB,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;QAC5C,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC,CAAA;IACxD,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,CAAA;IACjD,OAAO,CAAC,GAAG,EAAE,CAAA;AACf,CAAC;AAED,SAAS,sBAAsB,CAAC,WAAmB;IACjD,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,IAAI,UAAU,GAAG,CAAC,CAAA;IAElB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;QACxB,cAAc,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM;QAC7D,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO;KAC9C,CAAC,CAAA;IAEF,SAAS,IAAI,CAAC,GAAW;QACvB,IAAI,OAAiB,CAAA;QACrB,IAAI,CAAC;YACH,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAA;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,OAAM;QACR,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAQ;YAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YACtC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAA;gBAC/B,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;oBACvB,IAAI,CAAC,QAAQ,CAAC,CAAA;gBAChB,CAAC;qBAAM,CAAC;oBACN,SAAS,EAAE,CAAA;oBACX,UAAU,IAAI,IAAI,CAAC,IAAI,CAAA;gBACzB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC,CAAC,6BAA6B,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,IAAI,CAAC,WAAW,CAAC,CAAA;IACjB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,UAAU,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;AACvE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"AAWA,wBAAsB,SAAS,CAAC,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiItE"}
1
+ {"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"AAYA,wBAAsB,SAAS,CAAC,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwHtE"}
@@ -1,10 +1,11 @@
1
- import { statSync, unlinkSync } from "fs";
1
+ import { statSync, unlinkSync, readFileSync } from "fs";
2
+ import { rm } from "fs/promises";
2
3
  import path from "path";
3
4
  import chalk from "chalk";
4
5
  import ora from "ora";
5
6
  import * as readline from "readline/promises";
6
7
  import { stdin as input, stdout as output } from "process";
7
- import { readConfig } from "../lib/config.js";
8
+ import { readConfig, findProjectRoot } from "../lib/config.js";
8
9
  import { submitZip } from "../lib/api.js";
9
10
  import { createSubmissionZip } from "../lib/zip.js";
10
11
  import { fatal, success, warn, info, FALLBACK_URL } from "../utils/errors.js";
@@ -27,25 +28,7 @@ export async function runSubmit(opts) {
27
28
  }
28
29
  }
29
30
  // Step 3: Find the project root (where .litmus/config.json lives)
30
- let projectRoot = process.cwd();
31
- // readConfig walks up the tree, so we use the config's assessed location
32
- // The config file is at .litmus/config.json relative to the project root
33
- // We find it by searching from cwd up, same as readConfig does
34
- {
35
- let dir = process.cwd();
36
- while (true) {
37
- const configPath = path.join(dir, ".litmus", "config.json");
38
- const { existsSync } = await import("fs");
39
- if (existsSync(configPath)) {
40
- projectRoot = dir;
41
- break;
42
- }
43
- const parent = path.dirname(dir);
44
- if (parent === dir)
45
- break;
46
- dir = parent;
47
- }
48
- }
31
+ const projectRoot = findProjectRoot() ?? process.cwd();
49
32
  // Step 4: Create the submission ZIP
50
33
  const zipSpinner = ora("Preparing submission...").start();
51
34
  let zipPath;
@@ -92,11 +75,16 @@ export async function runSubmit(opts) {
92
75
  }
93
76
  }
94
77
  console.log();
95
- // Step 7: Upload
78
+ // Step 7: Upload with progress
96
79
  const uploadSpinner = ora("Uploading submission...").start();
97
80
  try {
98
81
  const fileName = `${folderName}_submission.zip`;
99
- const result = await submitZip(config.apiBase, config.token, zipPath, fileName);
82
+ const result = await submitZip(config.apiBase, config.token, zipPath, fileName, (uploaded, total) => {
83
+ const pct = Math.round((uploaded / total) * 100);
84
+ const uploadedMb = (uploaded / (1024 * 1024)).toFixed(1);
85
+ const totalMb = (total / (1024 * 1024)).toFixed(1);
86
+ uploadSpinner.text = `Uploading submission... ${uploadedMb}/${totalMb} MB (${pct}%)`;
87
+ });
100
88
  uploadSpinner.succeed("Submission received");
101
89
  console.log();
102
90
  success("You're done!");
@@ -106,6 +94,8 @@ export async function runSubmit(opts) {
106
94
  console.log(chalk.dim(` Submission ID: ${result.submissionId}`));
107
95
  }
108
96
  console.log();
97
+ // Step 8: Delete assessment folder after successful submission
98
+ await deleteAssessmentFolder(projectRoot, config.token, folderName);
109
99
  }
110
100
  catch (err) {
111
101
  uploadSpinner.fail("Upload failed");
@@ -121,4 +111,37 @@ export async function runSubmit(opts) {
121
111
  catch { /* cleanup */ }
122
112
  }
123
113
  }
114
+ async function deleteAssessmentFolder(projectRoot, token, folderName) {
115
+ // Safety check: verify this is actually a litmus assessment directory with matching token
116
+ try {
117
+ const configPath = path.join(projectRoot, ".litmus", "config.json");
118
+ const configData = JSON.parse(readFileSync(configPath, "utf8"));
119
+ if (configData.token !== token) {
120
+ warn(`Could not verify assessment folder. You can manually remove: ${projectRoot}`);
121
+ return;
122
+ }
123
+ }
124
+ catch {
125
+ warn(`Could not verify assessment folder. You can manually remove: ${projectRoot}`);
126
+ return;
127
+ }
128
+ // Verified — proceed with deletion
129
+ try {
130
+ // Stop the background tracker process
131
+ const pidFile = path.join(projectRoot, ".litmus", "tracker.pid");
132
+ try {
133
+ const pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
134
+ if (!isNaN(pid))
135
+ process.kill(pid);
136
+ }
137
+ catch { /* tracker may not be running */ }
138
+ await rm(projectRoot, { recursive: true, force: true });
139
+ console.log(chalk.dim(` Assessment folder deleted: ${folderName}`));
140
+ console.log(chalk.dim(` You may need to \`cd\` to another directory.`));
141
+ console.log();
142
+ }
143
+ catch {
144
+ warn(`Could not delete assessment folder. You can manually remove: ${projectRoot}`);
145
+ }
146
+ }
124
147
  //# sourceMappingURL=submit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"submit.js","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"submit.js","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { program } from "commander";
3
3
  import { runInit } from "./commands/init.js";
4
4
  import { runSubmit } from "./commands/submit.js";
5
+ import { runStatus } from "./commands/status.js";
5
6
  const VERSION = "0.1.0";
6
7
  program
7
8
  .name("litmus")
@@ -32,5 +33,17 @@ program
32
33
  process.exit(1);
33
34
  }
34
35
  });
36
+ program
37
+ .command("status")
38
+ .description("Show assessment status and stats")
39
+ .action(async () => {
40
+ try {
41
+ await runStatus();
42
+ }
43
+ catch (err) {
44
+ console.error(err instanceof Error ? err.message : String(err));
45
+ process.exit(1);
46
+ }
47
+ });
35
48
  program.parse();
36
49
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEhD,MAAM,OAAO,GAAG,OAAO,CAAA;AAEvB,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,mCAAmC,CAAC;KAChD,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,OAAO;KACJ,OAAO,CAAC,cAAc,CAAC;KACvB,WAAW,CAAC,yCAAyC,CAAC;KACtD,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,EAAE;IAC9B,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,KAAK,CAAC,CAAA;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,WAAW,EAAE,0BAA0B,CAAC;KAC/C,MAAM,CAAC,KAAK,EAAE,IAAuB,EAAE,EAAE;IACxC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO,CAAC,KAAK,EAAE,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEhD,MAAM,OAAO,GAAG,OAAO,CAAA;AAEvB,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,mCAAmC,CAAC;KAChD,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,OAAO;KACJ,OAAO,CAAC,cAAc,CAAC;KACvB,WAAW,CAAC,yCAAyC,CAAC;KACtD,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,EAAE;IAC9B,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,KAAK,CAAC,CAAA;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,WAAW,EAAE,0BAA0B,CAAC;KAC/C,MAAM,CAAC,KAAK,EAAE,IAAuB,EAAE,EAAE;IACxC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,SAAS,EAAE,CAAA;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO,CAAC,KAAK,EAAE,CAAA"}
package/dist/lib/api.d.ts CHANGED
@@ -25,6 +25,8 @@ export declare function fetchInitMetadata(apiBase: string, token: string): Promi
25
25
  export declare function downloadZip(apiBase: string, token: string): Promise<Response>;
26
26
  /**
27
27
  * Upload a ZIP file for submission.
28
+ * Streams the file to avoid loading it entirely into memory.
29
+ * Calls onProgress with (bytesUploaded, totalBytes) during upload.
28
30
  */
29
- export declare function submitZip(apiBase: string, token: string, zipPath: string, fileName: string): Promise<SubmitResult>;
31
+ export declare function submitZip(apiBase: string, token: string, zipPath: string, fileName: string, onProgress?: (uploaded: number, total: number) => void): Promise<SubmitResult>;
30
32
  //# sourceMappingURL=api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,YAAY,CAAC,CAWvB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,QAAQ,CAAC,CAWnB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAoBvB"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;CAChB;AAID;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,YAAY,CAAC,CAWvB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,QAAQ,CAAC,CAWnB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACrD,OAAO,CAAC,YAAY,CAAC,CAiEvB"}
package/dist/lib/api.js CHANGED
@@ -1,11 +1,14 @@
1
- import { readFileSync } from "fs";
1
+ import { createReadStream, statSync } from "fs";
2
+ import { request as httpsRequest } from "https";
3
+ import { request as httpRequest } from "http";
4
+ const TIMEOUT_MS = 30000; // 30s for metadata/download requests
2
5
  /**
3
6
  * Fetch assessment metadata by token.
4
7
  * Marks the candidate as in_progress if they were pending.
5
8
  */
6
9
  export async function fetchInitMetadata(apiBase, token) {
7
10
  const url = `${apiBase}/api/cli/init?token=${encodeURIComponent(token)}`;
8
- const res = await fetch(url);
11
+ const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
9
12
  const body = await res.json().catch(() => ({}));
10
13
  if (!res.ok) {
11
14
  const message = body.message || body.error || `Server returned ${res.status}`;
@@ -19,7 +22,7 @@ export async function fetchInitMetadata(apiBase, token) {
19
22
  */
20
23
  export async function downloadZip(apiBase, token) {
21
24
  const url = `${apiBase}/api/cli/download?token=${encodeURIComponent(token)}`;
22
- const res = await fetch(url);
25
+ const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
23
26
  if (!res.ok) {
24
27
  const body = await res.json().catch(() => ({}));
25
28
  const message = body.message || body.error || `Server returned ${res.status}`;
@@ -29,21 +32,68 @@ export async function downloadZip(apiBase, token) {
29
32
  }
30
33
  /**
31
34
  * Upload a ZIP file for submission.
35
+ * Streams the file to avoid loading it entirely into memory.
36
+ * Calls onProgress with (bytesUploaded, totalBytes) during upload.
32
37
  */
33
- export async function submitZip(apiBase, token, zipPath, fileName) {
34
- const url = `${apiBase}/api/cli/submit?token=${encodeURIComponent(token)}`;
35
- const form = new globalThis.FormData();
36
- const blob = new Blob([readFileSync(zipPath)], { type: "application/zip" });
37
- form.append("file", blob, fileName);
38
- const res = await fetch(url, {
39
- method: "POST",
40
- body: form,
38
+ export async function submitZip(apiBase, token, zipPath, fileName, onProgress) {
39
+ const parsedUrl = new URL(`/api/cli/submit?token=${encodeURIComponent(token)}`, apiBase);
40
+ const fileSize = statSync(zipPath).size;
41
+ const boundary = `----litmus${Date.now()}`;
42
+ const headerBuf = Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${fileName}"\r\nContent-Type: application/zip\r\n\r\n`);
43
+ const footerBuf = Buffer.from(`\r\n--${boundary}--\r\n`);
44
+ const totalSize = headerBuf.length + fileSize + footerBuf.length;
45
+ return new Promise((resolve, reject) => {
46
+ const reqFn = parsedUrl.protocol === "https:" ? httpsRequest : httpRequest;
47
+ const req = reqFn(parsedUrl, {
48
+ method: "POST",
49
+ headers: {
50
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
51
+ "Content-Length": totalSize.toString(),
52
+ },
53
+ timeout: 300000, // 5 min for upload
54
+ }, (res) => {
55
+ let data = "";
56
+ res.on("data", (chunk) => { data += chunk.toString(); });
57
+ res.on("end", () => {
58
+ try {
59
+ const body = JSON.parse(data);
60
+ if (res.statusCode && res.statusCode >= 400) {
61
+ const message = body.message || body.error || `Server returned ${res.statusCode}`;
62
+ reject(new Error(message));
63
+ }
64
+ else {
65
+ resolve(body);
66
+ }
67
+ }
68
+ catch {
69
+ reject(new Error(`Server returned invalid response (${res.statusCode})`));
70
+ }
71
+ });
72
+ });
73
+ req.on("error", reject);
74
+ req.on("timeout", () => {
75
+ req.destroy(new Error("Upload timed out"));
76
+ });
77
+ // Write multipart header
78
+ req.write(headerBuf);
79
+ let uploaded = headerBuf.length;
80
+ // Stream the file in chunks with backpressure handling
81
+ const fileStream = createReadStream(zipPath, { highWaterMark: 64 * 1024 });
82
+ fileStream.on("data", (chunk) => {
83
+ uploaded += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
84
+ onProgress?.(uploaded, totalSize);
85
+ if (!req.write(chunk)) {
86
+ fileStream.pause();
87
+ req.once("drain", () => fileStream.resume());
88
+ }
89
+ });
90
+ fileStream.on("end", () => {
91
+ req.end(footerBuf);
92
+ });
93
+ fileStream.on("error", (err) => {
94
+ req.destroy();
95
+ reject(err);
96
+ });
41
97
  });
42
- const body = await res.json().catch(() => ({}));
43
- if (!res.ok) {
44
- const message = body.message || body.error || `Server returned ${res.status}`;
45
- throw new Error(message);
46
- }
47
- return body;
48
98
  }
49
99
  //# sourceMappingURL=api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AAmBjC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAe,EACf,KAAa;IAEb,MAAM,GAAG,GAAG,GAAG,OAAO,uBAAuB,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAA;IACxE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAA4B,CAAA;IAE1E,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,OAAO,GAAI,IAAI,CAAC,OAAkB,IAAK,IAAI,CAAC,KAAgB,IAAI,mBAAmB,GAAG,CAAC,MAAM,EAAE,CAAA;QACrG,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;IAED,OAAO,IAA+B,CAAA;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAe,EACf,KAAa;IAEb,MAAM,GAAG,GAAG,GAAG,OAAO,2BAA2B,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAA;IAC5E,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;IAE5B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAA4B,CAAA;QAC1E,MAAM,OAAO,GAAI,IAAI,CAAC,OAAkB,IAAK,IAAI,CAAC,KAAgB,IAAI,mBAAmB,GAAG,CAAC,MAAM,EAAE,CAAA;QACrG,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAe,EACf,KAAa,EACb,OAAe,EACf,QAAgB;IAEhB,MAAM,GAAG,GAAG,GAAG,OAAO,yBAAyB,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAA;IAE1E,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAA;IACtC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAA;IAC3E,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAA;IAEnC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI;KACX,CAAC,CAAA;IAEF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAA4B,CAAA;IAE1E,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,OAAO,GAAI,IAAI,CAAC,OAAkB,IAAK,IAAI,CAAC,KAAgB,IAAI,mBAAmB,GAAG,CAAC,MAAM,EAAE,CAAA;QACrG,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;IAED,OAAO,IAA+B,CAAA;AACxC,CAAC"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,11 @@ export interface LitmusConfig {
11
11
  }
12
12
  export declare function getConfigPath(projectRoot: string): string;
13
13
  export declare function writeConfig(projectRoot: string, config: LitmusConfig): Promise<void>;
14
+ /**
15
+ * Walk up from cwd to find the directory containing .litmus/config.json.
16
+ * Returns null if not found.
17
+ */
18
+ export declare function findProjectRoot(): string | null;
14
19
  /**
15
20
  * Read .litmus/config.json from the current directory or a parent directory.
16
21
  * Returns null if no config is found.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;CAChB;AAKD,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAqB/D"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;CAChB;AAKD,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAY/C;AAED;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAuB/D"}
@@ -12,6 +12,24 @@ export async function writeConfig(projectRoot, config) {
12
12
  const configPath = path.join(configDir, CONFIG_FILE);
13
13
  await writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
14
14
  }
15
+ /**
16
+ * Walk up from cwd to find the directory containing .litmus/config.json.
17
+ * Returns null if not found.
18
+ */
19
+ export function findProjectRoot() {
20
+ let dir = process.cwd();
21
+ while (true) {
22
+ const configPath = path.join(dir, CONFIG_DIR, CONFIG_FILE);
23
+ if (existsSync(configPath)) {
24
+ return dir;
25
+ }
26
+ const parent = path.dirname(dir);
27
+ if (parent === dir)
28
+ break;
29
+ dir = parent;
30
+ }
31
+ return null;
32
+ }
15
33
  /**
16
34
  * Read .litmus/config.json from the current directory or a parent directory.
17
35
  * Returns null if no config is found.
@@ -22,12 +40,12 @@ export async function readConfig() {
22
40
  while (true) {
23
41
  const configPath = path.join(dir, CONFIG_DIR, CONFIG_FILE);
24
42
  if (existsSync(configPath)) {
43
+ const raw = await readFile(configPath, "utf8");
25
44
  try {
26
- const raw = await readFile(configPath, "utf8");
27
45
  return JSON.parse(raw);
28
46
  }
29
47
  catch {
30
- return null;
48
+ throw new Error(`Your .litmus/config.json is corrupted. Delete the .litmus folder and re-run litmus init with your token.`);
31
49
  }
32
50
  }
33
51
  const parent = path.dirname(dir);
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AAC/B,OAAO,IAAI,MAAM,MAAM,CAAA;AAcvB,MAAM,UAAU,GAAG,SAAS,CAAA;AAC5B,MAAM,WAAW,GAAG,aAAa,CAAA;AAEjC,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,WAAmB,EACnB,MAAoB;IAEpB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;IACpD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IACpD,MAAM,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEvB,6DAA6D;IAC7D,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;QAC1D,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;gBAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAA;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,MAAM,KAAK,GAAG;YAAE,MAAK,CAAC,0BAA0B;QACpD,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AAC/B,OAAO,IAAI,MAAM,MAAM,CAAA;AAcvB,MAAM,UAAU,GAAG,SAAS,CAAA;AAC5B,MAAM,WAAW,GAAG,aAAa,CAAA;AAEjC,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,WAAmB,EACnB,MAAoB;IAEpB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;IACpD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IACpD,MAAM,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IACvB,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;QAC1D,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,OAAO,GAAG,CAAA;QACZ,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,MAAM,KAAK,GAAG;YAAE,MAAK;QACzB,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEvB,6DAA6D;IAC7D,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;QAC1D,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;YAC9C,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAA;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CACb,0GAA0G,CAC3G,CAAA;YACH,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,MAAM,KAAK,GAAG;YAAE,MAAK,CAAC,0BAA0B;QACpD,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"detect-project.d.ts","sourceRoot":"","sources":["../../src/utils/detect-project.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,QAAQ,GACR,IAAI,GACJ,MAAM,GACN,QAAQ,GACR,MAAM,GACN,MAAM,GACN,KAAK,GACL,SAAS,CAAA;AAEb,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CA2DtD"}
1
+ {"version":3,"file":"detect-project.d.ts","sourceRoot":"","sources":["../../src/utils/detect-project.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,QAAQ,GACR,IAAI,GACJ,MAAM,GACN,QAAQ,GACR,MAAM,GACN,MAAM,GACN,KAAK,GACL,SAAS,CAAA;AAEb,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CA4DtD"}
@@ -1,4 +1,4 @@
1
- import { existsSync } from "fs";
1
+ import { existsSync, readdirSync } from "fs";
2
2
  import path from "path";
3
3
  /**
4
4
  * Detect project type and suggest an install command.
@@ -38,7 +38,8 @@ export function detectProject(dir) {
38
38
  if (existsSync(path.join(dir, "build.gradle")) || existsSync(path.join(dir, "build.gradle.kts"))) {
39
39
  return { type: "java", installCommand: "gradle build", runHint: "gradle test" };
40
40
  }
41
- if (existsSync(path.join(dir, "*.csproj")) || existsSync(path.join(dir, "*.sln"))) {
41
+ const files = readdirSync(dir);
42
+ if (files.some(f => f.endsWith(".csproj") || f.endsWith(".sln"))) {
42
43
  return { type: "dotnet", installCommand: "dotnet restore", runHint: "dotnet run / dotnet test" };
43
44
  }
44
45
  if (existsSync(path.join(dir, "Cargo.toml"))) {
@@ -1 +1 @@
1
- {"version":3,"file":"detect-project.js","sourceRoot":"","sources":["../../src/utils/detect-project.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"detect-project.js","sourceRoot":"","sources":["../../src/utils/detect-project.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litmus-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "CLI tool for Litmus engineering assessments",
5
5
  "license": "MIT",
6
6
  "author": "elenazhao",
@@ -37,7 +37,7 @@ export async function runInit(token: string): Promise<void> {
37
37
  if (existsSync(targetDir)) {
38
38
  fatal(
39
39
  `Folder "${metadata.folderName}" already exists in this directory.`,
40
- `Delete or rename it, then run: litmus init ${token}`
40
+ `Delete or rename it, then re-run litmus init with your token.`
41
41
  )
42
42
  }
43
43
 
@@ -98,14 +98,19 @@ export async function runInit(token: string): Promise<void> {
98
98
 
99
99
  // Step 4: Initialize git repo with initial commit
100
100
  try {
101
- execSync("git init", { cwd: targetDir, stdio: "pipe" })
102
- execSync("git add -A", { cwd: targetDir, stdio: "pipe" })
103
- execSync(
104
- "git commit -m \"Assessment: initial state\"",
105
- { cwd: targetDir, stdio: "pipe" }
106
- )
101
+ execSync("git --version", { stdio: "pipe" })
102
+ try {
103
+ execSync("git init", { cwd: targetDir, stdio: "pipe" })
104
+ execSync("git add -A", { cwd: targetDir, stdio: "pipe" })
105
+ execSync(
106
+ "git commit -m \"Assessment: initial state\"",
107
+ { cwd: targetDir, stdio: "pipe" }
108
+ )
109
+ } catch {
110
+ warn("Git is installed but failed to initialize. Check your git config (user.name / user.email).")
111
+ }
107
112
  } catch {
108
- // Non-critical — git may not be installed; analysis degrades gracefully
113
+ // git not installed — non-critical, analysis degrades gracefully
109
114
  }
110
115
 
111
116
  // Step 5: Write .litmus/config.json
@@ -0,0 +1,145 @@
1
+ import { existsSync, readFileSync, statSync, readdirSync } from "fs"
2
+ import path from "path"
3
+ import chalk from "chalk"
4
+ import { readConfig, findProjectRoot } from "../lib/config.js"
5
+ import { fatal, info } from "../utils/errors.js"
6
+ import { execSync } from "child_process"
7
+
8
+ export async function runStatus(): Promise<void> {
9
+ const config = await readConfig()
10
+
11
+ if (!config) {
12
+ fatal(
13
+ "No Litmus assessment found in this directory.",
14
+ "Run this command from inside your assessment folder, or run 'litmus init <token>' first."
15
+ )
16
+ }
17
+
18
+ const projectRoot = findProjectRoot() ?? process.cwd()
19
+ const folderName = path.basename(projectRoot)
20
+
21
+ console.log()
22
+ console.log(chalk.bold(` ${config.assessmentName}`))
23
+ console.log()
24
+
25
+ // Candidate info
26
+ info(` Candidate: ${config.candidateName} (${config.candidateEmail})`)
27
+ info(` Folder: ${folderName}/`)
28
+
29
+ // Time elapsed since init
30
+ const startedAt = new Date(config.startedAt)
31
+ const elapsed = Date.now() - startedAt.getTime()
32
+ const elapsedHours = Math.floor(elapsed / (1000 * 60 * 60))
33
+ const elapsedMins = Math.floor((elapsed % (1000 * 60 * 60)) / (1000 * 60))
34
+ info(` Started: ${startedAt.toLocaleString()} (${elapsedHours}h ${elapsedMins}m ago)`)
35
+
36
+ // Deadline / time remaining
37
+ if (config.deadline) {
38
+ const deadline = new Date(config.deadline)
39
+ const remaining = deadline.getTime() - Date.now()
40
+
41
+ if (remaining <= 0) {
42
+ console.log(chalk.red(` Deadline: PASSED (${deadline.toLocaleString()})`))
43
+ } else {
44
+ const remHours = Math.floor(remaining / (1000 * 60 * 60))
45
+ const remMins = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60))
46
+ info(` Deadline: ${deadline.toLocaleString()} (${remHours}h ${remMins}m remaining)`)
47
+ }
48
+ }
49
+
50
+ if (config.timeLimit) {
51
+ const timeLimitMs = config.timeLimit * 60 * 1000
52
+ const used = Date.now() - startedAt.getTime()
53
+ const remaining = timeLimitMs - used
54
+ if (remaining <= 0) {
55
+ console.log(chalk.red(` Time limit: EXCEEDED (${config.timeLimit} minutes)`))
56
+ } else {
57
+ const remMins = Math.floor(remaining / (1000 * 60))
58
+ info(` Time limit: ${config.timeLimit} min (${remMins} min remaining)`)
59
+ }
60
+ }
61
+
62
+ console.log()
63
+
64
+ // Git stats
65
+ try {
66
+ execSync("git --version", { stdio: "pipe" })
67
+ try {
68
+ const commitCount = execSync("git rev-list --count HEAD", { cwd: projectRoot, stdio: "pipe" }).toString().trim()
69
+ const lastCommit = execSync("git log -1 --format=%s", { cwd: projectRoot, stdio: "pipe" }).toString().trim()
70
+ info(` Commits: ${commitCount}`)
71
+ info(` Last commit: "${lastCommit}"`)
72
+ } catch {
73
+ info(` Commits: no commits yet`)
74
+ }
75
+ } catch {
76
+ info(` Git: not installed`)
77
+ }
78
+
79
+ // Estimated submission size (count files, approximate size)
80
+ const sizeInfo = estimateSubmissionSize(projectRoot)
81
+ info(` Files: ~${sizeInfo.fileCount}`)
82
+ info(` Estimated submission size: ~${sizeInfo.sizeMb} MB`)
83
+
84
+ // Tracker status
85
+ const pidFile = path.join(projectRoot, ".litmus", "tracker.pid")
86
+ let trackerRunning = false
87
+ try {
88
+ const pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10)
89
+ if (!isNaN(pid)) {
90
+ process.kill(pid, 0) // check if process exists (signal 0)
91
+ trackerRunning = true
92
+ }
93
+ } catch { /* not running */ }
94
+ info(` Tracker: ${trackerRunning ? "running" : "not running"}`)
95
+
96
+ // Activity log stats
97
+ const activityPath = path.join(projectRoot, ".litmus", "activity.jsonl")
98
+ if (existsSync(activityPath)) {
99
+ try {
100
+ const content = readFileSync(activityPath, "utf8")
101
+ const lines = content.split("\n").filter(l => l.trim())
102
+ info(` Activity events: ${lines.length}`)
103
+ } catch { /* ignore */ }
104
+ }
105
+
106
+ console.log()
107
+ console.log(chalk.dim(" When you're ready to submit:"))
108
+ console.log(` ${chalk.cyan("litmus submit")}`)
109
+ console.log()
110
+ }
111
+
112
+ function estimateSubmissionSize(projectRoot: string): { fileCount: number; sizeMb: string } {
113
+ let fileCount = 0
114
+ let totalBytes = 0
115
+
116
+ const SKIP_DIRS = new Set([
117
+ "node_modules", "__pycache__", "venv", ".venv", "env", ".env",
118
+ "target", "build", "dist", ".gradle", ".idea",
119
+ ])
120
+
121
+ function walk(dir: string): void {
122
+ let entries: string[]
123
+ try {
124
+ entries = readdirSync(dir)
125
+ } catch {
126
+ return
127
+ }
128
+ for (const entry of entries) {
129
+ if (SKIP_DIRS.has(entry)) continue
130
+ const fullPath = path.join(dir, entry)
131
+ try {
132
+ const stat = statSync(fullPath)
133
+ if (stat.isDirectory()) {
134
+ walk(fullPath)
135
+ } else {
136
+ fileCount++
137
+ totalBytes += stat.size
138
+ }
139
+ } catch { /* permission errors, etc. */ }
140
+ }
141
+ }
142
+
143
+ walk(projectRoot)
144
+ return { fileCount, sizeMb: (totalBytes / (1024 * 1024)).toFixed(1) }
145
+ }
@@ -1,10 +1,11 @@
1
- import { statSync, unlinkSync } from "fs"
1
+ import { statSync, unlinkSync, readFileSync } from "fs"
2
+ import { rm } from "fs/promises"
2
3
  import path from "path"
3
4
  import chalk from "chalk"
4
5
  import ora from "ora"
5
6
  import * as readline from "readline/promises"
6
7
  import { stdin as input, stdout as output } from "process"
7
- import { readConfig } from "../lib/config.js"
8
+ import { readConfig, findProjectRoot } from "../lib/config.js"
8
9
  import { submitZip } from "../lib/api.js"
9
10
  import { createSubmissionZip } from "../lib/zip.js"
10
11
  import { fatal, success, warn, info, FALLBACK_URL } from "../utils/errors.js"
@@ -39,24 +40,7 @@ export async function runSubmit(opts: { yes?: boolean }): Promise<void> {
39
40
  }
40
41
 
41
42
  // Step 3: Find the project root (where .litmus/config.json lives)
42
- let projectRoot = process.cwd()
43
- // readConfig walks up the tree, so we use the config's assessed location
44
- // The config file is at .litmus/config.json relative to the project root
45
- // We find it by searching from cwd up, same as readConfig does
46
- {
47
- let dir = process.cwd()
48
- while (true) {
49
- const configPath = path.join(dir, ".litmus", "config.json")
50
- const { existsSync } = await import("fs")
51
- if (existsSync(configPath)) {
52
- projectRoot = dir
53
- break
54
- }
55
- const parent = path.dirname(dir)
56
- if (parent === dir) break
57
- dir = parent
58
- }
59
- }
43
+ const projectRoot = findProjectRoot() ?? process.cwd()
60
44
 
61
45
  // Step 4: Create the submission ZIP
62
46
  const zipSpinner = ora("Preparing submission...").start()
@@ -108,12 +92,17 @@ export async function runSubmit(opts: { yes?: boolean }): Promise<void> {
108
92
 
109
93
  console.log()
110
94
 
111
- // Step 7: Upload
95
+ // Step 7: Upload with progress
112
96
  const uploadSpinner = ora("Uploading submission...").start()
113
97
 
114
98
  try {
115
99
  const fileName = `${folderName}_submission.zip`
116
- const result = await submitZip(config.apiBase, config.token, zipPath, fileName)
100
+ const result = await submitZip(config.apiBase, config.token, zipPath, fileName, (uploaded, total) => {
101
+ const pct = Math.round((uploaded / total) * 100)
102
+ const uploadedMb = (uploaded / (1024 * 1024)).toFixed(1)
103
+ const totalMb = (total / (1024 * 1024)).toFixed(1)
104
+ uploadSpinner.text = `Uploading submission... ${uploadedMb}/${totalMb} MB (${pct}%)`
105
+ })
117
106
  uploadSpinner.succeed("Submission received")
118
107
 
119
108
  console.log()
@@ -126,6 +115,9 @@ export async function runSubmit(opts: { yes?: boolean }): Promise<void> {
126
115
  console.log(chalk.dim(` Submission ID: ${result.submissionId}`))
127
116
  }
128
117
  console.log()
118
+
119
+ // Step 8: Delete assessment folder after successful submission
120
+ await deleteAssessmentFolder(projectRoot, config.token, folderName)
129
121
  } catch (err) {
130
122
  uploadSpinner.fail("Upload failed")
131
123
  const msg = err instanceof Error ? err.message : String(err)
@@ -139,3 +131,35 @@ export async function runSubmit(opts: { yes?: boolean }): Promise<void> {
139
131
  try { unlinkSync(zipPath) } catch { /* cleanup */ }
140
132
  }
141
133
  }
134
+
135
+ async function deleteAssessmentFolder(projectRoot: string, token: string, folderName: string): Promise<void> {
136
+ // Safety check: verify this is actually a litmus assessment directory with matching token
137
+ try {
138
+ const configPath = path.join(projectRoot, ".litmus", "config.json")
139
+ const configData = JSON.parse(readFileSync(configPath, "utf8"))
140
+ if (configData.token !== token) {
141
+ warn(`Could not verify assessment folder. You can manually remove: ${projectRoot}`)
142
+ return
143
+ }
144
+ } catch {
145
+ warn(`Could not verify assessment folder. You can manually remove: ${projectRoot}`)
146
+ return
147
+ }
148
+
149
+ // Verified — proceed with deletion
150
+ try {
151
+ // Stop the background tracker process
152
+ const pidFile = path.join(projectRoot, ".litmus", "tracker.pid")
153
+ try {
154
+ const pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10)
155
+ if (!isNaN(pid)) process.kill(pid)
156
+ } catch { /* tracker may not be running */ }
157
+
158
+ await rm(projectRoot, { recursive: true, force: true })
159
+ console.log(chalk.dim(` Assessment folder deleted: ${folderName}`))
160
+ console.log(chalk.dim(` You may need to \`cd\` to another directory.`))
161
+ console.log()
162
+ } catch {
163
+ warn(`Could not delete assessment folder. You can manually remove: ${projectRoot}`)
164
+ }
165
+ }
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import { program } from "commander"
3
3
  import { runInit } from "./commands/init.js"
4
4
  import { runSubmit } from "./commands/submit.js"
5
+ import { runStatus } from "./commands/status.js"
5
6
 
6
7
  const VERSION = "0.1.0"
7
8
 
@@ -35,4 +36,16 @@ program
35
36
  }
36
37
  })
37
38
 
39
+ program
40
+ .command("status")
41
+ .description("Show assessment status and stats")
42
+ .action(async () => {
43
+ try {
44
+ await runStatus()
45
+ } catch (err) {
46
+ console.error(err instanceof Error ? err.message : String(err))
47
+ process.exit(1)
48
+ }
49
+ })
50
+
38
51
  program.parse()
package/src/lib/api.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { readFileSync } from "fs"
1
+ import { createReadStream, statSync } from "fs"
2
+ import { request as httpsRequest } from "https"
3
+ import { request as httpRequest } from "http"
2
4
 
3
5
  export interface InitMetadata {
4
6
  assessmentId: string
@@ -17,6 +19,8 @@ export interface SubmitResult {
17
19
  message: string
18
20
  }
19
21
 
22
+ const TIMEOUT_MS = 30_000 // 30s for metadata/download requests
23
+
20
24
  /**
21
25
  * Fetch assessment metadata by token.
22
26
  * Marks the candidate as in_progress if they were pending.
@@ -26,7 +30,7 @@ export async function fetchInitMetadata(
26
30
  token: string
27
31
  ): Promise<InitMetadata> {
28
32
  const url = `${apiBase}/api/cli/init?token=${encodeURIComponent(token)}`
29
- const res = await fetch(url)
33
+ const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) })
30
34
  const body = await res.json().catch(() => ({})) as Record<string, unknown>
31
35
 
32
36
  if (!res.ok) {
@@ -46,7 +50,7 @@ export async function downloadZip(
46
50
  token: string
47
51
  ): Promise<Response> {
48
52
  const url = `${apiBase}/api/cli/download?token=${encodeURIComponent(token)}`
49
- const res = await fetch(url)
53
+ const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) })
50
54
 
51
55
  if (!res.ok) {
52
56
  const body = await res.json().catch(() => ({})) as Record<string, unknown>
@@ -59,30 +63,78 @@ export async function downloadZip(
59
63
 
60
64
  /**
61
65
  * Upload a ZIP file for submission.
66
+ * Streams the file to avoid loading it entirely into memory.
67
+ * Calls onProgress with (bytesUploaded, totalBytes) during upload.
62
68
  */
63
69
  export async function submitZip(
64
70
  apiBase: string,
65
71
  token: string,
66
72
  zipPath: string,
67
- fileName: string
73
+ fileName: string,
74
+ onProgress?: (uploaded: number, total: number) => void
68
75
  ): Promise<SubmitResult> {
69
- const url = `${apiBase}/api/cli/submit?token=${encodeURIComponent(token)}`
76
+ const parsedUrl = new URL(`/api/cli/submit?token=${encodeURIComponent(token)}`, apiBase)
77
+ const fileSize = statSync(zipPath).size
70
78
 
71
- const form = new globalThis.FormData()
72
- const blob = new Blob([readFileSync(zipPath)], { type: "application/zip" })
73
- form.append("file", blob, fileName)
79
+ const boundary = `----litmus${Date.now()}`
80
+ const headerBuf = Buffer.from(
81
+ `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${fileName}"\r\nContent-Type: application/zip\r\n\r\n`
82
+ )
83
+ const footerBuf = Buffer.from(`\r\n--${boundary}--\r\n`)
84
+ const totalSize = headerBuf.length + fileSize + footerBuf.length
74
85
 
75
- const res = await fetch(url, {
76
- method: "POST",
77
- body: form,
78
- })
86
+ return new Promise((resolve, reject) => {
87
+ const reqFn = parsedUrl.protocol === "https:" ? httpsRequest : httpRequest
88
+ const req = reqFn(parsedUrl, {
89
+ method: "POST",
90
+ headers: {
91
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
92
+ "Content-Length": totalSize.toString(),
93
+ },
94
+ timeout: 300_000, // 5 min for upload
95
+ }, (res) => {
96
+ let data = ""
97
+ res.on("data", (chunk: Buffer) => { data += chunk.toString() })
98
+ res.on("end", () => {
99
+ try {
100
+ const body = JSON.parse(data) as Record<string, unknown>
101
+ if (res.statusCode && res.statusCode >= 400) {
102
+ const message = (body.message as string) || (body.error as string) || `Server returned ${res.statusCode}`
103
+ reject(new Error(message))
104
+ } else {
105
+ resolve(body as unknown as SubmitResult)
106
+ }
107
+ } catch {
108
+ reject(new Error(`Server returned invalid response (${res.statusCode})`))
109
+ }
110
+ })
111
+ })
79
112
 
80
- const body = await res.json().catch(() => ({})) as Record<string, unknown>
113
+ req.on("error", reject)
114
+ req.on("timeout", () => {
115
+ req.destroy(new Error("Upload timed out"))
116
+ })
81
117
 
82
- if (!res.ok) {
83
- const message = (body.message as string) || (body.error as string) || `Server returned ${res.status}`
84
- throw new Error(message)
85
- }
118
+ // Write multipart header
119
+ req.write(headerBuf)
120
+ let uploaded = headerBuf.length
86
121
 
87
- return body as unknown as SubmitResult
122
+ // Stream the file in chunks with backpressure handling
123
+ const fileStream = createReadStream(zipPath, { highWaterMark: 64 * 1024 })
124
+ fileStream.on("data", (chunk: string | Buffer) => {
125
+ uploaded += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length
126
+ onProgress?.(uploaded, totalSize)
127
+ if (!req.write(chunk)) {
128
+ fileStream.pause()
129
+ req.once("drain", () => fileStream.resume())
130
+ }
131
+ })
132
+ fileStream.on("end", () => {
133
+ req.end(footerBuf)
134
+ })
135
+ fileStream.on("error", (err) => {
136
+ req.destroy()
137
+ reject(err)
138
+ })
139
+ })
88
140
  }
package/src/lib/config.ts CHANGED
@@ -31,6 +31,24 @@ export async function writeConfig(
31
31
  await writeFile(configPath, JSON.stringify(config, null, 2), "utf8")
32
32
  }
33
33
 
34
+ /**
35
+ * Walk up from cwd to find the directory containing .litmus/config.json.
36
+ * Returns null if not found.
37
+ */
38
+ export function findProjectRoot(): string | null {
39
+ let dir = process.cwd()
40
+ while (true) {
41
+ const configPath = path.join(dir, CONFIG_DIR, CONFIG_FILE)
42
+ if (existsSync(configPath)) {
43
+ return dir
44
+ }
45
+ const parent = path.dirname(dir)
46
+ if (parent === dir) break
47
+ dir = parent
48
+ }
49
+ return null
50
+ }
51
+
34
52
  /**
35
53
  * Read .litmus/config.json from the current directory or a parent directory.
36
54
  * Returns null if no config is found.
@@ -42,11 +60,13 @@ export async function readConfig(): Promise<LitmusConfig | null> {
42
60
  while (true) {
43
61
  const configPath = path.join(dir, CONFIG_DIR, CONFIG_FILE)
44
62
  if (existsSync(configPath)) {
63
+ const raw = await readFile(configPath, "utf8")
45
64
  try {
46
- const raw = await readFile(configPath, "utf8")
47
65
  return JSON.parse(raw) as LitmusConfig
48
66
  } catch {
49
- return null
67
+ throw new Error(
68
+ `Your .litmus/config.json is corrupted. Delete the .litmus folder and re-run litmus init with your token.`
69
+ )
50
70
  }
51
71
  }
52
72
 
@@ -1,4 +1,4 @@
1
- import { existsSync } from "fs"
1
+ import { existsSync, readdirSync } from "fs"
2
2
  import path from "path"
3
3
 
4
4
  export type ProjectType =
@@ -63,7 +63,8 @@ export function detectProject(dir: string): ProjectInfo {
63
63
  return { type: "java", installCommand: "gradle build", runHint: "gradle test" }
64
64
  }
65
65
 
66
- if (existsSync(path.join(dir, "*.csproj")) || existsSync(path.join(dir, "*.sln"))) {
66
+ const files = readdirSync(dir)
67
+ if (files.some(f => f.endsWith(".csproj") || f.endsWith(".sln"))) {
67
68
  return { type: "dotnet", installCommand: "dotnet restore", runHint: "dotnet run / dotnet test" }
68
69
  }
69
70