@betterness/cli 1.3.0 → 1.3.2

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.
Files changed (2) hide show
  1. package/dist/index.js +122 -37
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,12 +3,25 @@
3
3
  // src/program.ts
4
4
  import { Command } from "commander";
5
5
 
6
+ // src/commands/auth.ts
7
+ import { join as join4 } from "path";
8
+
6
9
  // src/auth/credentialStore.ts
7
10
  import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "fs";
11
+ import { join as join2 } from "path";
12
+
13
+ // src/paths.ts
8
14
  import { join } from "path";
9
15
  import { homedir } from "os";
10
- var CONFIG_DIR = join(homedir(), ".betterness");
11
- var CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
16
+ function resolveConfigDir() {
17
+ const apiUrl = "https://api.betterness.ai";
18
+ const isDev = apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1");
19
+ return join(homedir(), isDev ? ".betterness-dev" : ".betterness");
20
+ }
21
+ var CONFIG_DIR = resolveConfigDir();
22
+
23
+ // src/auth/credentialStore.ts
24
+ var CREDENTIALS_FILE = join2(CONFIG_DIR, "credentials.json");
12
25
  function ensureConfigDir() {
13
26
  if (!existsSync(CONFIG_DIR)) {
14
27
  mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
@@ -43,13 +56,11 @@ function deleteCredentials() {
43
56
 
44
57
  // src/auth/tokenStore.ts
45
58
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, unlinkSync as unlinkSync2 } from "fs";
46
- import { join as join2 } from "path";
47
- import { homedir as homedir2 } from "os";
48
- var CONFIG_DIR2 = join2(homedir2(), ".betterness");
49
- var TOKENS_FILE = join2(CONFIG_DIR2, "tokens.json");
59
+ import { join as join3 } from "path";
60
+ var TOKENS_FILE = join3(CONFIG_DIR, "tokens.json");
50
61
  function ensureConfigDir2() {
51
- if (!existsSync2(CONFIG_DIR2)) {
52
- mkdirSync2(CONFIG_DIR2, { recursive: true, mode: 448 });
62
+ if (!existsSync2(CONFIG_DIR)) {
63
+ mkdirSync2(CONFIG_DIR, { recursive: true, mode: 448 });
53
64
  }
54
65
  }
55
66
  function loadTokens() {
@@ -408,7 +419,7 @@ var ApiClient = class {
408
419
  headers: {
409
420
  "Authorization": `Bearer ${this.apiKey}`,
410
421
  "Content-Type": "application/json",
411
- "User-Agent": `betterness-cli/${"1.3.0"}`,
422
+ "User-Agent": `betterness-cli/${"1.3.2"}`,
412
423
  "Accept": "application/json"
413
424
  },
414
425
  body: body ? JSON.stringify(body) : void 0,
@@ -456,9 +467,9 @@ var ApiClient = class {
456
467
  return this.unwrap(raw, schema);
457
468
  }
458
469
  async upload(path, filePath, fieldName = "file", schema) {
459
- const { readFileSync: readFileSync4 } = await import("fs");
470
+ const { readFileSync: readFileSync5 } = await import("fs");
460
471
  const { basename, extname } = await import("path");
461
- const buffer = readFileSync4(filePath);
472
+ const buffer = readFileSync5(filePath);
462
473
  const fileName = basename(filePath);
463
474
  const ext = extname(filePath).toLowerCase();
464
475
  const mimeType = ext === ".pdf" ? "application/pdf" : "application/octet-stream";
@@ -472,7 +483,7 @@ var ApiClient = class {
472
483
  method: "POST",
473
484
  headers: {
474
485
  "Authorization": `Bearer ${this.apiKey}`,
475
- "User-Agent": `betterness-cli/${"1.3.0"}`,
486
+ "User-Agent": `betterness-cli/${"1.3.2"}`,
476
487
  "Accept": "application/json"
477
488
  },
478
489
  body: formData,
@@ -892,7 +903,7 @@ async function loginWithApiKey(apiKey) {
892
903
  name: user.firstName ?? void 0
893
904
  });
894
905
  console.log(`Logged in as: ${user.firstName ?? "Unknown"} (${user.email ?? "no email"})`);
895
- console.log("Credentials saved to ~/.betterness/credentials.json");
906
+ console.log(`Credentials saved to ${join4(CONFIG_DIR, "credentials.json")}`);
896
907
  }
897
908
  async function loginWithOAuth() {
898
909
  const result = await startOAuthLogin();
@@ -922,7 +933,7 @@ async function loginWithOAuth() {
922
933
  name: user.firstName ?? result.name
923
934
  });
924
935
  console.log(`Logged in as: ${user.firstName ?? "Unknown"} (${user.email ?? "no email"})`);
925
- console.log("Credentials saved to ~/.betterness/tokens.json");
936
+ console.log(`Credentials saved to ${join4(CONFIG_DIR, "tokens.json")}`);
926
937
  }
927
938
 
928
939
  // src/commands/profile.ts
@@ -1661,10 +1672,10 @@ function registerLabResultsCommands(program2) {
1661
1672
  });
1662
1673
  labResults.command("upload").description("Upload a lab result PDF for processing").requiredOption("--file <path>", "Path to the PDF file").action(async (opts, cmd) => {
1663
1674
  try {
1664
- const { existsSync: existsSync5 } = await import("fs");
1675
+ const { existsSync: existsSync6 } = await import("fs");
1665
1676
  const { resolve } = await import("path");
1666
1677
  const filePath = resolve(opts.file);
1667
- if (!existsSync5(filePath)) {
1678
+ if (!existsSync6(filePath)) {
1668
1679
  console.error(`File not found: ${filePath}`);
1669
1680
  process.exit(1);
1670
1681
  }
@@ -2127,21 +2138,20 @@ function registerSchemaCommand(program2) {
2127
2138
  }
2128
2139
 
2129
2140
  // src/commands/debug.ts
2130
- import { homedir as homedir3 } from "os";
2131
- import { join as join3 } from "path";
2141
+ import { join as join5 } from "path";
2132
2142
  import { existsSync as existsSync3 } from "fs";
2133
2143
  function registerDebugCommands(program2) {
2134
2144
  const debug = program2.command("debug", { hidden: true }).description("Internal debug utilities");
2135
2145
  debug.command("config").description("Show resolved configuration").action((_, cmd) => {
2136
- const credentialsPath = join3(homedir3(), ".betterness", "credentials.json");
2137
- const tokensPath = join3(homedir3(), ".betterness", "tokens.json");
2146
+ const credentialsPath = join5(CONFIG_DIR, "credentials.json");
2147
+ const tokensPath = join5(CONFIG_DIR, "tokens.json");
2138
2148
  const stored = loadCredentials();
2139
2149
  const tokens = loadTokens();
2140
2150
  const envKey = process.env.BETTERNESS_API_KEY;
2141
2151
  const parentOpts = cmd.optsWithGlobals();
2142
- const authSource = parentOpts.apiKey ? "--api-key flag" : envKey ? "BETTERNESS_API_KEY env" : tokens && !isTokenExpired(tokens) ? "OAuth tokens (~/.betterness/tokens.json)" : stored ? "API key (~/.betterness/credentials.json)" : "none";
2152
+ const authSource = parentOpts.apiKey ? "--api-key flag" : envKey ? "BETTERNESS_API_KEY env" : tokens && !isTokenExpired(tokens) ? `OAuth tokens (${tokensPath})` : stored ? `API key (${credentialsPath})` : "none";
2143
2153
  const config = {
2144
- version: "1.3.0",
2154
+ version: "1.3.2",
2145
2155
  apiUrl: "https://api.betterness.ai",
2146
2156
  auth0Domain: "betterness.us.auth0.com",
2147
2157
  auth0ClientId: "g4lqYHRQb2QMgdRKIlKwoTJl6eu41pWn",
@@ -2193,7 +2203,7 @@ function registerHealthProfileCommands(program2) {
2193
2203
  hp.command("schema").description("List all sections and question IDs available in the health profile").option("--section <acronym>", "Show questions for a specific section only").action(async (opts, cmd) => {
2194
2204
  try {
2195
2205
  const globalOpts = cmd.optsWithGlobals();
2196
- const client = new ApiClient(globalOpts.apiKey);
2206
+ const client = await createApiClient({ apiKey: globalOpts.apiKey });
2197
2207
  const schema = await client.get(
2198
2208
  "/api/v1/copilot-config/schema",
2199
2209
  void 0,
@@ -2230,7 +2240,7 @@ function registerHealthProfileCommands(program2) {
2230
2240
  hp.command("get").description("Retrieve the full health profile (all answered questions as a flat map)").action(async (_, cmd) => {
2231
2241
  try {
2232
2242
  const globalOpts = cmd.optsWithGlobals();
2233
- const client = new ApiClient(globalOpts.apiKey);
2243
+ const client = await createApiClient({ apiKey: globalOpts.apiKey });
2234
2244
  const user = await client.get(
2235
2245
  "/api/betterness-user/detail"
2236
2246
  );
@@ -2247,7 +2257,7 @@ function registerHealthProfileCommands(program2) {
2247
2257
  hp.command("get-section").description("Retrieve health profile answers for a specific section").requiredOption("--section <acronym>", "Section acronym (e.g. HWP, DMH, DA)").action(async (opts, cmd) => {
2248
2258
  try {
2249
2259
  const globalOpts = cmd.optsWithGlobals();
2250
- const client = new ApiClient(globalOpts.apiKey);
2260
+ const client = await createApiClient({ apiKey: globalOpts.apiKey });
2251
2261
  const user = await client.get(
2252
2262
  "/api/betterness-user/detail"
2253
2263
  );
@@ -2294,7 +2304,7 @@ function registerHealthProfileCommands(program2) {
2294
2304
  return;
2295
2305
  }
2296
2306
  const globalOpts = cmd.optsWithGlobals();
2297
- const client = new ApiClient(globalOpts.apiKey);
2307
+ const client = await createApiClient({ apiKey: globalOpts.apiKey });
2298
2308
  const user = await client.get(
2299
2309
  "/api/betterness-user/detail"
2300
2310
  );
@@ -2334,7 +2344,7 @@ function registerHealthProfileCommands(program2) {
2334
2344
  return;
2335
2345
  }
2336
2346
  const globalOpts = cmd.optsWithGlobals();
2337
- const client = new ApiClient(globalOpts.apiKey);
2347
+ const client = await createApiClient({ apiKey: globalOpts.apiKey });
2338
2348
  const user = await client.get(
2339
2349
  "/api/betterness-user/detail"
2340
2350
  );
@@ -2349,7 +2359,7 @@ function registerHealthProfileCommands(program2) {
2349
2359
  hp.command("summary").description("Human-readable summary of answered health profile questions").option("--section <acronym>", "Summarize a specific section only").action(async (opts, cmd) => {
2350
2360
  try {
2351
2361
  const globalOpts = cmd.optsWithGlobals();
2352
- const client = new ApiClient(globalOpts.apiKey);
2362
+ const client = await createApiClient({ apiKey: globalOpts.apiKey });
2353
2363
  const [schema, user] = await Promise.all([
2354
2364
  client.get("/api/v1/copilot-config/schema", void 0, healthProfileSchemaSchema),
2355
2365
  client.get("/api/betterness-user/detail")
@@ -2449,17 +2459,17 @@ function formatAnswerValue(value) {
2449
2459
 
2450
2460
  // src/commands/mcp.ts
2451
2461
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3, copyFileSync } from "fs";
2452
- import { join as join4, dirname } from "path";
2453
- import { homedir as homedir4, platform } from "os";
2462
+ import { join as join6, dirname } from "path";
2463
+ import { homedir as homedir2, platform } from "os";
2454
2464
  var CLIENTS = {
2455
2465
  claude: {
2456
2466
  name: "Claude Desktop",
2457
2467
  supportsScope: false,
2458
2468
  configPath: () => {
2459
2469
  if (platform() === "darwin") {
2460
- return join4(homedir4(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
2470
+ return join6(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
2461
2471
  }
2462
- return join4(homedir4(), ".config", "Claude", "claude_desktop_config.json");
2472
+ return join6(homedir2(), ".config", "Claude", "claude_desktop_config.json");
2463
2473
  }
2464
2474
  },
2465
2475
  "claude-code": {
@@ -2467,20 +2477,20 @@ var CLIENTS = {
2467
2477
  supportsScope: true,
2468
2478
  configPath: (scope) => {
2469
2479
  if (scope === "project") {
2470
- return join4(process.cwd(), ".mcp.json");
2480
+ return join6(process.cwd(), ".mcp.json");
2471
2481
  }
2472
- return join4(homedir4(), ".claude", "settings.json");
2482
+ return join6(homedir2(), ".claude", "settings.json");
2473
2483
  }
2474
2484
  },
2475
2485
  cursor: {
2476
2486
  name: "Cursor",
2477
2487
  supportsScope: false,
2478
- configPath: () => join4(process.cwd(), ".cursor", "mcp.json")
2488
+ configPath: () => join6(process.cwd(), ".cursor", "mcp.json")
2479
2489
  },
2480
2490
  windsurf: {
2481
2491
  name: "Windsurf",
2482
2492
  supportsScope: false,
2483
- configPath: () => join4(homedir4(), ".codeium", "windsurf", "mcp_config.json")
2493
+ configPath: () => join6(homedir2(), ".codeium", "windsurf", "mcp_config.json")
2484
2494
  }
2485
2495
  };
2486
2496
  var VALID_CLIENTS = Object.keys(CLIENTS).join(", ");
@@ -2670,7 +2680,7 @@ Restart ${client.name} to activate. Then try asking:
2670
2680
  // src/program.ts
2671
2681
  function createProgram() {
2672
2682
  const program2 = new Command();
2673
- program2.name("betterness").description("Betterness CLI - Agent-first terminal interface for the Betterness platform").version("1.3.0").option("--api-key <key>", "API key (overrides env and stored credentials)").option("--json", "Output as JSON").option("--markdown", "Output as Markdown").option("--quiet", "Suppress output (exit code only)");
2683
+ program2.name("betterness").description("Betterness CLI - Agent-first terminal interface for the Betterness platform").version("1.3.2").option("--api-key <key>", "API key (overrides env and stored credentials)").option("--json", "Output as JSON").option("--markdown", "Output as Markdown").option("--quiet", "Suppress output (exit code only)");
2674
2684
  registerAuthCommands(program2);
2675
2685
  registerProfileCommands(program2);
2676
2686
  registerBiomarkersCommands(program2);
@@ -2694,6 +2704,81 @@ function createProgram() {
2694
2704
  return program2;
2695
2705
  }
2696
2706
 
2707
+ // src/updateCheck.ts
2708
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
2709
+ import { join as join7 } from "path";
2710
+ var CACHE_FILE = join7(CONFIG_DIR, "update-check.json");
2711
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
2712
+ var REGISTRY_TIMEOUT_MS = 3e3;
2713
+ function loadCache() {
2714
+ if (!existsSync5(CACHE_FILE)) return null;
2715
+ try {
2716
+ return JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
2717
+ } catch {
2718
+ return null;
2719
+ }
2720
+ }
2721
+ function saveCache(cache) {
2722
+ if (!existsSync5(CONFIG_DIR)) {
2723
+ mkdirSync4(CONFIG_DIR, { recursive: true, mode: 448 });
2724
+ }
2725
+ writeFileSync4(CACHE_FILE, JSON.stringify(cache), { mode: 384 });
2726
+ }
2727
+ function isNewer(latest, current) {
2728
+ const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
2729
+ const [lMaj, lMin, lPat] = parse(latest);
2730
+ const [cMaj, cMin, cPat] = parse(current);
2731
+ if (lMaj !== cMaj) return lMaj > cMaj;
2732
+ if (lMin !== cMin) return lMin > cMin;
2733
+ return lPat > cPat;
2734
+ }
2735
+ async function fetchLatestVersion() {
2736
+ const controller = new AbortController();
2737
+ const timeoutId = setTimeout(() => controller.abort(), REGISTRY_TIMEOUT_MS);
2738
+ try {
2739
+ const res = await fetch("https://registry.npmjs.org/@betterness/cli/latest", {
2740
+ headers: { Accept: "application/json" },
2741
+ signal: controller.signal
2742
+ });
2743
+ clearTimeout(timeoutId);
2744
+ if (!res.ok) return null;
2745
+ const data = await res.json();
2746
+ return data.version ?? null;
2747
+ } catch {
2748
+ clearTimeout(timeoutId);
2749
+ return null;
2750
+ }
2751
+ }
2752
+ function scheduleUpdateCheck() {
2753
+ const currentVersion = "1.3.2";
2754
+ if (currentVersion === "0.0.0") return;
2755
+ const cache = loadCache();
2756
+ if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
2757
+ if (isNewer(cache.latestVersion, currentVersion)) {
2758
+ printNotice(currentVersion, cache.latestVersion);
2759
+ }
2760
+ return;
2761
+ }
2762
+ fetchLatestVersion().then((latest) => {
2763
+ if (!latest) return;
2764
+ saveCache({ lastCheck: Date.now(), latestVersion: latest });
2765
+ if (isNewer(latest, currentVersion)) {
2766
+ printNotice(currentVersion, latest);
2767
+ }
2768
+ }).catch(() => {
2769
+ });
2770
+ }
2771
+ function printNotice(current, latest) {
2772
+ process.stderr.write(
2773
+ `
2774
+ Update available: ${current} \u2192 ${latest}
2775
+ Run \`npm i -g @betterness/cli\` to update.
2776
+
2777
+ `
2778
+ );
2779
+ }
2780
+
2697
2781
  // src/index.ts
2782
+ scheduleUpdateCheck();
2698
2783
  var program = createProgram();
2699
2784
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@betterness/cli",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Betterness CLI - Agent-first terminal interface for the Betterness platform",
5
5
  "type": "module",
6
6
  "bin": {