@nolto/cli 0.4.0 → 0.5.0

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 +117 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import { createRequire as createRequire2 } from "module";
5
5
  import { fileURLToPath as fileURLToPath2 } from "url";
6
- import path7 from "path";
6
+ import path8 from "path";
7
7
  import { CommanderError } from "commander";
8
8
 
9
9
  // src/config.ts
@@ -503,11 +503,11 @@ function createHttpClient(opts) {
503
503
  const { baseUrl, version, token } = opts;
504
504
  const base = baseUrl.replace(/\/+$/, "");
505
505
  return {
506
- async post(path8, body) {
507
- if (!path8.startsWith("/api/")) {
508
- throw new CliError(`HTTP client path must start with /api/, got: ${path8}`, 2);
506
+ async post(path9, body) {
507
+ if (!path9.startsWith("/api/")) {
508
+ throw new CliError(`HTTP client path must start with /api/, got: ${path9}`, 2);
509
509
  }
510
- const url = `${base}${path8}`;
510
+ const url = `${base}${path9}`;
511
511
  const headers = {
512
512
  "Content-Type": "application/json",
513
513
  "User-Agent": `${CLI_USER_AGENT_NAME}/${version}`
@@ -709,10 +709,7 @@ async function injectClaude(opts) {
709
709
  nolto: {
710
710
  url: mcpUrl,
711
711
  headers: {
712
- Authorization: `Bearer ${opts.token}`
713
- },
714
- env: {
715
- NOLTO_MCP_TOKEN: opts.token
712
+ Authorization: "Bearer ${NOLTO_MCP_TOKEN}"
716
713
  }
717
714
  }
718
715
  };
@@ -721,7 +718,14 @@ async function injectClaude(opts) {
721
718
  mcpServers: updatedServers
722
719
  };
723
720
  await atomicWrite(configPath, JSON.stringify(newConfig, null, 2) + "\n", 384);
724
- process.stdout.write(`Updated ${configPath} (claude project .mcp.json)
721
+ const tokensUrl = `${opts.baseUrl.replace(/\/+$/, "")}/settings/tokens`;
722
+ process.stdout.write(
723
+ `Updated ${configPath} (claude project .mcp.json) \u2014 uses the NOLTO_MCP_TOKEN env var; no token is stored in the file, so it is safe to commit.
724
+ `
725
+ );
726
+ process.stdout.write("Set NOLTO_MCP_TOKEN in your environment before launching Claude Code, e.g.:\n");
727
+ process.stdout.write(" export NOLTO_MCP_TOKEN=<your token>\n");
728
+ process.stdout.write(`Your token is saved in your nolto config (from this login); or create one at ${tokensUrl}
725
729
  `);
726
730
  }
727
731
  async function injectCodex(opts) {
@@ -1998,10 +2002,10 @@ async function handleShow(deps, projectBindingPath, mode2) {
1998
2002
  }
1999
2003
  }
2000
2004
  async function handleUnlink(projectBindingPath, mode2) {
2001
- const { readFile: readFile5, writeFile: writeFile4, chmod } = await import("fs/promises");
2005
+ const { readFile: readFile6, writeFile: writeFile5, chmod } = await import("fs/promises");
2002
2006
  let existing = {};
2003
2007
  try {
2004
- const raw = await readFile5(projectBindingPath, "utf8");
2008
+ const raw = await readFile6(projectBindingPath, "utf8");
2005
2009
  const parsed = JSON.parse(raw);
2006
2010
  if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
2007
2011
  throw new CliError(
@@ -2016,7 +2020,7 @@ async function handleUnlink(projectBindingPath, mode2) {
2016
2020
  }
2017
2021
  const { projectId: _removed, ...rest } = existing;
2018
2022
  void _removed;
2019
- await writeFile4(projectBindingPath, JSON.stringify(rest, null, 2) + "\n", { mode: 420 });
2023
+ await writeFile5(projectBindingPath, JSON.stringify(rest, null, 2) + "\n", { mode: 420 });
2020
2024
  await chmod(projectBindingPath, 420);
2021
2025
  if (mode2 === "json") {
2022
2026
  printResult({ unlinked: true, projectBindingPath }, mode2);
@@ -2119,12 +2123,109 @@ function buildProgram(deps) {
2119
2123
  return program;
2120
2124
  }
2121
2125
 
2126
+ // src/update-notifier.ts
2127
+ import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
2128
+ import https from "https";
2129
+ import path7 from "path";
2130
+ var PACKAGE = "@nolto/cli";
2131
+ var CACHE_FILE = "update-check.json";
2132
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
2133
+ var REQUEST_TIMEOUT_MS = 2e3;
2134
+ function isNewerVersion(latest, current) {
2135
+ const parts = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
2136
+ const a = parts(latest);
2137
+ const b = parts(current);
2138
+ for (let i = 0; i < 3; i++) {
2139
+ const x = a[i] ?? 0;
2140
+ const y = b[i] ?? 0;
2141
+ if (x !== y) return x > y;
2142
+ }
2143
+ return false;
2144
+ }
2145
+ function formatUpdateNotice(latest, current) {
2146
+ return `
2147
+ Update available: ${current} \u2192 ${latest} \xB7 npm i -g @nolto/cli@latest
2148
+ `;
2149
+ }
2150
+ function isDisabled(env) {
2151
+ return env["NO_UPDATE_NOTIFIER"] === "1" || env["NODE_ENV"] === "test" || Boolean(env["CI"]);
2152
+ }
2153
+ function fetchLatestFromRegistry() {
2154
+ return new Promise((resolve) => {
2155
+ const req = https.get(
2156
+ `https://registry.npmjs.org/${PACKAGE}/latest`,
2157
+ { timeout: REQUEST_TIMEOUT_MS, headers: { accept: "application/json" } },
2158
+ (res) => {
2159
+ if (res.statusCode !== 200) {
2160
+ res.resume();
2161
+ resolve(null);
2162
+ return;
2163
+ }
2164
+ let data = "";
2165
+ res.on("data", (chunk) => data += chunk);
2166
+ res.on("end", () => {
2167
+ try {
2168
+ const v = JSON.parse(data).version;
2169
+ resolve(typeof v === "string" ? v : null);
2170
+ } catch {
2171
+ resolve(null);
2172
+ }
2173
+ });
2174
+ }
2175
+ );
2176
+ req.on("socket", (s) => s.unref());
2177
+ req.on("timeout", () => {
2178
+ req.destroy();
2179
+ resolve(null);
2180
+ });
2181
+ req.on("error", () => resolve(null));
2182
+ });
2183
+ }
2184
+ async function refreshCache(cachePath, now, fetchLatest) {
2185
+ const latest = await fetchLatest();
2186
+ if (!latest) return;
2187
+ await mkdir4(path7.dirname(cachePath), { recursive: true }).catch(() => void 0);
2188
+ const payload = { checkedAt: now, latest };
2189
+ await writeFile4(cachePath, JSON.stringify(payload), { mode: 384 }).catch(() => void 0);
2190
+ }
2191
+ async function checkForUpdate(opts) {
2192
+ if (isDisabled(opts.env)) return null;
2193
+ const cachePath = path7.join(opts.configDir, CACHE_FILE);
2194
+ let cache = {};
2195
+ try {
2196
+ cache = JSON.parse(await readFile5(cachePath, "utf8"));
2197
+ } catch {
2198
+ }
2199
+ if (typeof cache.checkedAt !== "number" || opts.now - cache.checkedAt > CACHE_TTL_MS) {
2200
+ void refreshCache(cachePath, opts.now, opts.fetchLatest ?? fetchLatestFromRegistry).catch(
2201
+ () => void 0
2202
+ );
2203
+ }
2204
+ if (typeof cache.latest === "string" && isNewerVersion(cache.latest, opts.current)) {
2205
+ return cache.latest;
2206
+ }
2207
+ return null;
2208
+ }
2209
+ async function notifyUpdate(opts) {
2210
+ try {
2211
+ if (opts.isJson || !process.stderr.isTTY) return;
2212
+ const latest = await checkForUpdate({
2213
+ current: opts.current,
2214
+ configDir: getConfigDir(opts.env),
2215
+ env: opts.env,
2216
+ now: opts.now
2217
+ });
2218
+ if (latest) process.stderr.write(formatUpdateNotice(latest, opts.current));
2219
+ } catch {
2220
+ }
2221
+ }
2222
+
2122
2223
  // src/index.ts
2123
- var __dirname2 = path7.dirname(fileURLToPath2(import.meta.url));
2224
+ var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
2124
2225
  var require2 = createRequire2(import.meta.url);
2125
2226
  function getVersion() {
2126
2227
  try {
2127
- const pkgPath = path7.resolve(__dirname2, "../package.json");
2228
+ const pkgPath = path8.resolve(__dirname2, "../package.json");
2128
2229
  const pkg = require2(pkgPath);
2129
2230
  return pkg.version ?? "0.0.0";
2130
2231
  } catch {
@@ -2183,6 +2284,7 @@ async function main() {
2183
2284
  };
2184
2285
  const program = buildProgram({ caller, settings, output: { mode }, version, configPath, projectBindingPath });
2185
2286
  await program.parseAsync(process.argv);
2287
+ await notifyUpdate({ current: version, env: process.env, isJson: mode === "json", now: Date.now() });
2186
2288
  }
2187
2289
  function extractFlag(argv, name) {
2188
2290
  const idx = argv.indexOf(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolto/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI for Nolto — register plans and update progress from your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",