@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.
- package/dist/cli.js +204 -53
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
247
|
+
return creds;
|
|
248
|
+
}
|
|
249
|
+
async function installToolByName(tool, token, apiBase) {
|
|
255
250
|
switch (tool) {
|
|
256
251
|
case "claude-code":
|
|
257
|
-
await installClaudeCode(
|
|
252
|
+
await installClaudeCode(token, apiBase);
|
|
258
253
|
await migrateLegacyClaudeSettings();
|
|
259
254
|
break;
|
|
260
255
|
case "cursor":
|
|
261
|
-
await installCursor(
|
|
256
|
+
await installCursor(token, apiBase);
|
|
262
257
|
break;
|
|
263
258
|
case "codex":
|
|
264
|
-
await installCodex(
|
|
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 (!
|
|
372
|
+
if (!SUPPORTED_TOOLS.includes(v)) {
|
|
380
373
|
throw new Error(
|
|
381
|
-
`unsupported tool: ${v}. Supported: ${
|
|
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
|
|
475
|
-
|
|
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
|
-
|
|
493
|
-
console.log("\u2713 logged out");
|
|
644
|
+
await logout();
|
|
494
645
|
break;
|
|
495
646
|
case "whoami":
|
|
496
|
-
|
|
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.
|
|
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",
|