@hawkeye-xb.com/imprint-cli 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.
Files changed (2) hide show
  1. package/dist/cli.js +204 -53
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,27 +1,17 @@
1
1
  #!/usr/bin/env node
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __esm = (fn, res) => function __init() {
5
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
- };
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
2
+
3
+ // src/login.ts
4
+ import http from "http";
5
+ import os2 from "os";
6
+ import crypto from "crypto";
11
7
 
12
8
  // src/config.ts
13
- var config_exports = {};
14
- __export(config_exports, {
15
- DASHBOARD_URL: () => DASHBOARD_URL,
16
- credentialsPath: () => credentialsPath,
17
- readCredentials: () => readCredentials,
18
- removeCredentials: () => removeCredentials,
19
- resolveApiBase: () => resolveApiBase,
20
- writeCredentials: () => writeCredentials
21
- });
22
9
  import os from "os";
23
10
  import path from "path";
24
11
  import fs from "fs/promises";
12
+ var DASHBOARD_URL = process.env.IMPRINT_DASHBOARD_URL?.replace(/\/+$/, "") ?? "https://imprint.hawkeye-xb.com";
13
+ var HARDCODED_API_BASE_FALLBACK = "https://imprint-api.hawkeye-xb.com";
14
+ var _resolvedApiBase = null;
25
15
  async function resolveApiBase() {
26
16
  if (_resolvedApiBase) return _resolvedApiBase;
27
17
  const envOverride = process.env.IMPRINT_API_BASE?.replace(/\/+$/, "");
@@ -46,6 +36,8 @@ async function resolveApiBase() {
46
36
  _resolvedApiBase = HARDCODED_API_BASE_FALLBACK;
47
37
  return _resolvedApiBase;
48
38
  }
39
+ var CRED_DIR = path.join(os.homedir(), ".imprint");
40
+ var CRED_FILE = path.join(CRED_DIR, "credentials.json");
49
41
  function credentialsPath() {
50
42
  return CRED_FILE;
51
43
  }
@@ -73,23 +65,6 @@ async function writeCredentials(token, apiBase) {
73
65
  async function removeCredentials() {
74
66
  await fs.rm(CRED_FILE, { force: true });
75
67
  }
76
- var DASHBOARD_URL, HARDCODED_API_BASE_FALLBACK, _resolvedApiBase, CRED_DIR, CRED_FILE;
77
- var init_config = __esm({
78
- "src/config.ts"() {
79
- "use strict";
80
- DASHBOARD_URL = process.env.IMPRINT_DASHBOARD_URL?.replace(/\/+$/, "") ?? "https://imprint.hawkeye-xb.com";
81
- HARDCODED_API_BASE_FALLBACK = "https://imprint-api.hawkeye-xb.com";
82
- _resolvedApiBase = null;
83
- CRED_DIR = path.join(os.homedir(), ".imprint");
84
- CRED_FILE = path.join(CRED_DIR, "credentials.json");
85
- }
86
- });
87
-
88
- // src/login.ts
89
- init_config();
90
- import http from "http";
91
- import os2 from "os";
92
- import crypto from "crypto";
93
68
 
94
69
  // src/utils.ts
95
70
  import { exec } from "child_process";
@@ -160,7 +135,8 @@ var TOKEN_PREFIX = "imp_";
160
135
  async function login() {
161
136
  const port = await findFreePort();
162
137
  const state = crypto.randomBytes(16).toString("hex");
163
- const name = `${process.env.USER ?? "user"}@${os2.hostname()}`;
138
+ const user = process.env.USER || os2.userInfo().username || "user";
139
+ const name = `${user}@${friendlyHostname()}`;
164
140
  const tokenPromise = waitForCallback(port, state);
165
141
  const url = new URL("/cli-login", DASHBOARD_URL);
166
142
  url.searchParams.set("port", String(port));
@@ -177,6 +153,11 @@ async function login() {
177
153
  console.log(`\u2713 Logged in. Token saved.`);
178
154
  return token;
179
155
  }
156
+ function friendlyHostname() {
157
+ const raw = os2.hostname();
158
+ if (!raw || raw === "bogon" || raw.startsWith("bogon.")) return "local";
159
+ return raw.replace(/\.local$/, "");
160
+ }
180
161
  function waitForCallback(port, expectedState) {
181
162
  return new Promise((resolve, reject) => {
182
163
  const timeout = setTimeout(
@@ -237,13 +218,25 @@ function waitForCallback(port, expectedState) {
237
218
  }
238
219
 
239
220
  // src/install.ts
240
- init_config();
241
221
  import os3 from "os";
242
222
  import path2 from "path";
243
223
  import fs2 from "fs/promises";
244
- var SUPPORTED = ["claude-code", "cursor", "codex"];
224
+
225
+ // src/codex-markers.ts
226
+ var CODEX_MARK_BEGIN = "# === imprint:begin (managed \u2014 do not edit between markers) ===";
227
+ var CODEX_MARK_END = "# === imprint:end ===";
228
+
229
+ // src/install.ts
230
+ var SUPPORTED_TOOLS = ["claude-code", "cursor", "codex"];
245
231
  async function install(args) {
246
232
  const tool = parseTool(args);
233
+ const creds = await ensureLoggedIn();
234
+ const apiBase = await resolveApiBase();
235
+ await installToolByName(tool, creds.token, apiBase);
236
+ await bootstrapUsageDoc(creds.token, apiBase);
237
+ await printIdentity(creds.token, apiBase);
238
+ }
239
+ async function ensureLoggedIn() {
247
240
  let creds = await readCredentials();
248
241
  if (!creds) {
249
242
  console.log("No credentials found \u2014 running login first.\n");
@@ -251,21 +244,21 @@ async function install(args) {
251
244
  creds = await readCredentials();
252
245
  if (!creds) throw new Error("login completed but credentials still missing");
253
246
  }
254
- const apiBase = await resolveApiBase();
247
+ return creds;
248
+ }
249
+ async function installToolByName(tool, token, apiBase) {
255
250
  switch (tool) {
256
251
  case "claude-code":
257
- await installClaudeCode(creds.token, apiBase);
252
+ await installClaudeCode(token, apiBase);
258
253
  await migrateLegacyClaudeSettings();
259
254
  break;
260
255
  case "cursor":
261
- await installCursor(creds.token, apiBase);
256
+ await installCursor(token, apiBase);
262
257
  break;
263
258
  case "codex":
264
- await installCodex(creds.token, apiBase);
259
+ await installCodex(token, apiBase);
265
260
  break;
266
261
  }
267
- await bootstrapUsageDoc(creds.token, apiBase);
268
- await printIdentity(creds.token, apiBase);
269
262
  }
270
263
  async function printIdentity(token, apiBase) {
271
264
  try {
@@ -376,9 +369,9 @@ function parseTool(args) {
376
369
  if (idx >= 0) {
377
370
  const v = args[idx + 1];
378
371
  if (!v) throw new Error("--tool requires a value");
379
- if (!SUPPORTED.includes(v)) {
372
+ if (!SUPPORTED_TOOLS.includes(v)) {
380
373
  throw new Error(
381
- `unsupported tool: ${v}. Supported: ${SUPPORTED.join(", ")}`
374
+ `unsupported tool: ${v}. Supported: ${SUPPORTED_TOOLS.join(", ")}`
382
375
  );
383
376
  }
384
377
  return v;
@@ -422,8 +415,6 @@ async function installCursor(token, apiBase) {
422
415
  console.log(`\u2713 Cursor: wrote mcpServers.imprint to ${settingsPath}`);
423
416
  console.log(` Reload Cursor (or its MCP server list in Settings) to pick it up.`);
424
417
  }
425
- var CODEX_MARK_BEGIN = "# === imprint:begin (managed \u2014 do not edit between markers) ===";
426
- var CODEX_MARK_END = "# === imprint:end ===";
427
418
  async function installCodex(token, apiBase) {
428
419
  const configPath = path2.join(os3.homedir(), ".codex", "config.toml");
429
420
  await fs2.mkdir(path2.dirname(configPath), { recursive: true });
@@ -463,6 +454,158 @@ async function installCodex(token, apiBase) {
463
454
  );
464
455
  }
465
456
 
457
+ // src/logout.ts
458
+ import os4 from "os";
459
+ import path3 from "path";
460
+ import fs3 from "fs/promises";
461
+ async function logout() {
462
+ await removeCredentials();
463
+ console.log(`\u2713 Removed credentials file`);
464
+ await cleanupJson(
465
+ path3.join(os4.homedir(), ".claude.json"),
466
+ "Claude Code (~/.claude.json)"
467
+ );
468
+ await cleanupJson(
469
+ path3.join(os4.homedir(), ".claude", "settings.json"),
470
+ "Claude legacy (~/.claude/settings.json)"
471
+ );
472
+ await cleanupJson(
473
+ path3.join(os4.homedir(), ".cursor", "mcp.json"),
474
+ "Cursor (~/.cursor/mcp.json)"
475
+ );
476
+ await cleanupCodex(path3.join(os4.homedir(), ".codex", "config.toml"));
477
+ }
478
+ async function cleanupJson(filePath, label) {
479
+ let raw;
480
+ try {
481
+ raw = await fs3.readFile(filePath, "utf8");
482
+ } catch (err) {
483
+ if (err.code === "ENOENT") return;
484
+ return;
485
+ }
486
+ if (raw.trim().length === 0) return;
487
+ let parsed;
488
+ try {
489
+ parsed = JSON.parse(raw);
490
+ } catch {
491
+ return;
492
+ }
493
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
494
+ const obj = parsed;
495
+ if (!obj.mcpServers || typeof obj.mcpServers !== "object") return;
496
+ if (!("imprint" in obj.mcpServers)) return;
497
+ delete obj.mcpServers.imprint;
498
+ if (Object.keys(obj.mcpServers).length === 0) delete obj.mcpServers;
499
+ await fs3.writeFile(filePath, JSON.stringify(obj, null, 2) + "\n");
500
+ console.log(`\u2713 Removed imprint from ${label}`);
501
+ }
502
+ async function cleanupCodex(filePath) {
503
+ let raw;
504
+ try {
505
+ raw = await fs3.readFile(filePath, "utf8");
506
+ } catch {
507
+ return;
508
+ }
509
+ const startIdx = raw.indexOf(CODEX_MARK_BEGIN);
510
+ const endIdx = raw.indexOf(CODEX_MARK_END);
511
+ if (startIdx < 0 || endIdx <= startIdx) return;
512
+ const before = raw.slice(0, startIdx).replace(/\s+$/, "");
513
+ const after = raw.slice(endIdx + CODEX_MARK_END.length).replace(/^\s+/, "");
514
+ const next = [before, after].filter((s) => s.length > 0).join("\n\n");
515
+ const final = next.length > 0 ? next + "\n" : "";
516
+ await fs3.writeFile(filePath, final);
517
+ console.log(`\u2713 Removed imprint from Codex (~/.codex/config.toml)`);
518
+ }
519
+ async function detectInstalledTools() {
520
+ const tools = [];
521
+ if (await jsonHasImprint(path3.join(os4.homedir(), ".claude.json"))) {
522
+ tools.push("claude-code");
523
+ }
524
+ if (await jsonHasImprint(path3.join(os4.homedir(), ".cursor", "mcp.json"))) {
525
+ tools.push("cursor");
526
+ }
527
+ if (await fileContains(
528
+ path3.join(os4.homedir(), ".codex", "config.toml"),
529
+ CODEX_MARK_BEGIN
530
+ )) {
531
+ tools.push("codex");
532
+ }
533
+ return tools;
534
+ }
535
+ async function jsonHasImprint(filePath) {
536
+ try {
537
+ const raw = await fs3.readFile(filePath, "utf8");
538
+ const parsed = JSON.parse(raw);
539
+ return typeof parsed === "object" && parsed !== null && typeof parsed.mcpServers === "object" && parsed.mcpServers !== null && "imprint" in parsed.mcpServers;
540
+ } catch {
541
+ return false;
542
+ }
543
+ }
544
+ async function fileContains(filePath, needle) {
545
+ try {
546
+ const raw = await fs3.readFile(filePath, "utf8");
547
+ return raw.includes(needle);
548
+ } catch {
549
+ return false;
550
+ }
551
+ }
552
+
553
+ // src/switch.ts
554
+ async function switchAccount(_args) {
555
+ const detected = await detectInstalledTools();
556
+ if (detected.length === 0) {
557
+ console.log(
558
+ "No installed tools detected. Run 'imprint install --tool <claude-code|cursor|codex>' instead."
559
+ );
560
+ return;
561
+ }
562
+ console.log(`Will re-install for: ${detected.join(", ")}
563
+ `);
564
+ await logout();
565
+ console.log("\nLog in with the account you want to switch to.\n");
566
+ const creds = await ensureLoggedIn();
567
+ const apiBase = await resolveApiBase();
568
+ for (const tool of detected) {
569
+ await installToolByName(tool, creds.token, apiBase);
570
+ }
571
+ await bootstrapUsageDoc(creds.token, apiBase);
572
+ await printIdentity(creds.token, apiBase);
573
+ }
574
+
575
+ // src/whoami.ts
576
+ async function whoami(args) {
577
+ if (args.includes("--path")) {
578
+ console.log(credentialsPath());
579
+ return;
580
+ }
581
+ const creds = await readCredentials();
582
+ if (!creds) {
583
+ console.log("(not logged in \u2014 run 'imprint login')");
584
+ console.log(` Would store at: ${credentialsPath()}`);
585
+ return;
586
+ }
587
+ const apiBase = await resolveApiBase();
588
+ try {
589
+ const res = await fetch(`${apiBase}/api/v1/whoami`, {
590
+ headers: { Authorization: `Bearer ${creds.token}` }
591
+ });
592
+ if (!res.ok) {
593
+ console.log(`(token rejected by ${apiBase}: HTTP ${res.status})`);
594
+ console.log(` Stored at: ${credentialsPath()}`);
595
+ console.log(` Try 'imprint login' to re-authenticate.`);
596
+ return;
597
+ }
598
+ const body = await res.json();
599
+ const label = body.email || body.userId || "(unknown)";
600
+ console.log(`Logged in as: ${label}`);
601
+ console.log(` Token stored at: ${credentialsPath()}`);
602
+ console.log(` API: ${apiBase}`);
603
+ } catch (err) {
604
+ console.log(`(could not reach ${apiBase}: ${err.message})`);
605
+ console.log(` Stored at: ${credentialsPath()}`);
606
+ }
607
+ }
608
+
466
609
  // src/cli.ts
467
610
  var HELP = `imprint \u2014 memory layer for AI coding tools
468
611
 
@@ -471,8 +614,14 @@ Usage:
471
614
  imprint login Authenticate via browser; saves token to ~/.imprint/credentials.json.
472
615
  imprint install [--tool ID] Log in (if needed) and write MCP config for the target tool.
473
616
  Tools: claude-code (default), cursor, codex.
474
- imprint logout Forget the saved token.
475
- imprint whoami Print where the credentials are stored.
617
+ imprint switch Switch the locally-bound account. Detects which tools you
618
+ installed, clears all imprint configs and credentials, then
619
+ walks you through fresh OAuth and re-installs the same tools
620
+ under the new account.
621
+ imprint logout Remove credentials AND clean imprint entries out of
622
+ Claude Code / Cursor / Codex config files.
623
+ imprint whoami Show which account the saved token belongs to.
624
+ Use --path to print the credentials file location instead.
476
625
 
477
626
  Environment overrides (for development):
478
627
  IMPRINT_DASHBOARD_URL Dashboard origin (default: https://imprint.hawkeye-xb.com).
@@ -488,12 +637,14 @@ async function main() {
488
637
  case "install":
489
638
  await install(rest);
490
639
  break;
640
+ case "switch":
641
+ await switchAccount(rest);
642
+ break;
491
643
  case "logout":
492
- (await Promise.resolve().then(() => (init_config(), config_exports))).removeCredentials();
493
- console.log("\u2713 logged out");
644
+ await logout();
494
645
  break;
495
646
  case "whoami":
496
- console.log((await Promise.resolve().then(() => (init_config(), config_exports))).credentialsPath());
647
+ await whoami(rest);
497
648
  break;
498
649
  case "-h":
499
650
  case "--help":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hawkeye-xb.com/imprint-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "description": "Imprint CLI — long-term memory for AI coding tools (Claude Code, Cursor, Codex). Installs the imprint MCP server into your tool's settings file via a browser OAuth flow.",
6
6
  "license": "Elastic-2.0",