neuvikon 0.2.2 → 0.2.4

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/README.md CHANGED
@@ -38,7 +38,7 @@ as being worked on, by whom, with which tool.
38
38
  | `neuvikon start --ticket EWWQ-1` | Begin tracking. `--tool claude-code\|codex\|cursor\|other` |
39
39
  | `neuvikon stop` | Stop tracking |
40
40
  | `neuvikon status` | Login, project setup, current ticket |
41
- | `neuvikon logout` | Forget the stored token |
41
+ | `neuvikon logout` | Revoke this machine's token and forget it |
42
42
 
43
43
  `start` also accepts the ticket on stdin, and pulls the reference out of it:
44
44
 
@@ -71,6 +71,7 @@ always exits 0 — a token count is never worth interrupting someone's work.
71
71
  |---|---|
72
72
  | `~/.neuvikon/config.json` | Token and deployment URL, `0600` |
73
73
  | `.neuvikon-session` | Which ticket this directory is tracking. Git-ignored by `init` |
74
+ | `--dev` | Target the development deployment instead of production |
74
75
  | `NEUVIKON_URL` | Point at a self-hosted deployment. `--url` does the same per command |
75
76
  | `NEUVIKON_TOKEN` | Use this token instead of logging in — for CI and anywhere a browser cannot open |
76
77
 
package/dist/index.mjs CHANGED
@@ -142,14 +142,35 @@ function isInstalled(root) {
142
142
  //#endregion
143
143
  //#region src/config.ts
144
144
  /**
145
- * The deployment the CLI talks to. Overridable so self-hosted installs and
146
- * local Convex dev deployments work without a separate build.
147
- *
148
- * MUST point at the production Convex deployment before publishing: every
149
- * installed copy of this CLI hardcodes whatever ships here, and a dev
150
- * deployment can be reset out from under them.
145
+ * The two deployments this CLI knows about. The browser origin is kept here
146
+ * rather than read back from the server, because the API answers on a Convex
147
+ * domain while approval happens on the web app's — one is not derivable from
148
+ * the other, and guessing wrong sends people to a page that does not exist.
151
149
  */
152
- const DEFAULT_SITE_URL = process.env.NEUVIKON_URL ?? "https://flexible-bird-801.eu-west-1.convex.sites";
150
+ const DEPLOYMENTS = {
151
+ production: {
152
+ api: "https://flexible-bird-801.eu-west-1.convex.site",
153
+ web: "https://www.neuvikon.space"
154
+ },
155
+ development: {
156
+ api: "https://calm-sturgeon-831.eu-west-1.convex.site",
157
+ web: "http://localhost:3000"
158
+ }
159
+ };
160
+ /**
161
+ * `--dev` targets the development deployment; NEUVIKON_URL overrides the API
162
+ * for a self-hosted install, in which case the web origin is unknowable and
163
+ * falls back to the matching default.
164
+ */
165
+ function resolveDeployment(flags) {
166
+ const base = flags.has("dev") ? DEPLOYMENTS.development : DEPLOYMENTS.production;
167
+ const override = flags.get("url") || process.env.NEUVIKON_URL;
168
+ return override ? {
169
+ api: override.trim().replace(/\/+$/, ""),
170
+ web: base.web
171
+ } : base;
172
+ }
173
+ const DEFAULT_SITE_URL = process.env.NEUVIKON_URL ?? DEPLOYMENTS.production.api;
153
174
  const CONFIG_DIR = join(homedir(), ".neuvikon");
154
175
  const CONFIG_PATH = join(CONFIG_DIR, "config.json");
155
176
  /**
@@ -317,7 +338,8 @@ function sleep(ms) {
317
338
  return new Promise((resolve) => setTimeout(resolve, ms));
318
339
  }
319
340
  async function login(flags) {
320
- const url = normalizeUrl(flags.get("url") ?? DEFAULT_SITE_URL);
341
+ const deployment = resolveDeployment(flags);
342
+ const url = normalizeUrl(deployment.api);
321
343
  let start;
322
344
  try {
323
345
  start = await post(url, "/api/cli/auth/start", {});
@@ -326,12 +348,13 @@ async function login(flags) {
326
348
  if (error instanceof ApiError) fail(error.message);
327
349
  throw error;
328
350
  }
351
+ const verifyUrl = `${deployment.web}/cli?code=${encodeURIComponent(start.userCode)}`;
329
352
  info("");
330
- info(` Open ${cyan(start.verifyUrl)}`);
353
+ info(` Open ${cyan(verifyUrl)}`);
331
354
  info(` and confirm this code: ${bold(start.userCode)}`);
332
355
  info("");
333
356
  info(dim(" Waiting for approval..."));
334
- openBrowser(start.verifyUrl);
357
+ openBrowser(verifyUrl);
335
358
  const interval = Math.max(1e3, start.intervalMs);
336
359
  for (;;) {
337
360
  if (Date.now() > start.expiresAt) fail("That login request expired.", "Run `npx neuvikon login` to get a fresh code.");
@@ -366,15 +389,6 @@ async function login(flags) {
366
389
  }
367
390
  }
368
391
  //#endregion
369
- //#region src/commands/logout.ts
370
- function logout() {
371
- if (clearCredentials()) {
372
- success("Logged out.");
373
- return;
374
- }
375
- info("You were not logged in.");
376
- }
377
- //#endregion
378
392
  //#region src/session.ts
379
393
  /**
380
394
  * Scoped to the working directory rather than the machine: two checkouts driven
@@ -409,6 +423,42 @@ function clearSession() {
409
423
  } catch {}
410
424
  }
411
425
  //#endregion
426
+ //#region src/commands/logout.ts
427
+ /**
428
+ * Deleting the local file is not enough: the token stays valid until the server
429
+ * is told otherwise, so a copy of it lifted from a backup or a shared machine
430
+ * would still be able to report. Revoking is the part that actually ends the
431
+ * session; forgetting it locally is bookkeeping.
432
+ */
433
+ async function logout() {
434
+ const credentials = readCredentials();
435
+ if (!credentials) {
436
+ info("You were not logged in.");
437
+ return;
438
+ }
439
+ const session = readSession();
440
+ if (session) {
441
+ await post(credentials.url, "/api/agent/events", {
442
+ token: credentials.token,
443
+ event: "stop",
444
+ sessionKey: session.sessionKey
445
+ }).catch(() => void 0);
446
+ clearSession();
447
+ }
448
+ let revoked = false;
449
+ try {
450
+ revoked = (await post(credentials.url, "/api/cli/auth/revoke", { token: credentials.token })).revoked;
451
+ } catch {}
452
+ if (process.env.NEUVIKON_TOKEN) warn("NEUVIKON_TOKEN is set in your environment — unset it to finish logging out.");
453
+ clearCredentials();
454
+ if (revoked) {
455
+ success("Logged out. That token has been revoked.");
456
+ return;
457
+ }
458
+ success("Logged out on this machine.");
459
+ info(dim(" Could not reach Neuvikon to revoke the token — revoke it in settings if it leaked."));
460
+ }
461
+ //#endregion
412
462
  //#region src/ticket.ts
413
463
  /**
414
464
  * Ticket refs look like `AWKN-14`. This pattern is deliberately looser than the
@@ -687,6 +737,7 @@ ${bold("Options")}
687
737
  --tool <name> claude-code | codex | cursor | other
688
738
  --transcript <path> Read token usage from an agent transcript
689
739
  --tokens-in <n> --tokens-out <n>
740
+ --dev Use the development deployment
690
741
  --url <site-url> Point at a self-hosted deployment
691
742
 
692
743
  ${dim("Paste a ticket into any coding agent after logging in — it takes it from there.")}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuvikon",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Tell Neuvikon which ticket your coding agent is working on.",
5
5
  "keywords": [
6
6
  "neuvikon",