@hawkeye-xb.com/imprint-cli 0.1.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.
- package/README.md +49 -0
- package/dist/cli.js +405 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @hawkeye-xb.com/imprint-cli
|
|
2
|
+
|
|
3
|
+
Installer for [Imprint](https://imprint.hawkeye-xb.com) — a long-term memory layer for AI coding tools (Claude Code, Cursor, Codex). Runs an OAuth flow in your browser, then writes the `imprint` MCP server entry into the target tool's settings file.
|
|
4
|
+
|
|
5
|
+
## Quick install
|
|
6
|
+
|
|
7
|
+
Tell your AI agent (or run yourself):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx --yes @hawkeye-xb.com/imprint-cli install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Defaults to Claude Code (`~/.claude/settings.json`). For Cursor or Codex:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx --yes @hawkeye-xb.com/imprint-cli install --tool cursor
|
|
17
|
+
npx --yes @hawkeye-xb.com/imprint-cli install --tool codex
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
A browser opens to authorize via Logto. The CLI captures the issued Personal API Token, writes the MCP server entry without disturbing your other settings, and exits. Restart the AI tool to pick up the new server.
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
| | |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `imprint` | Alias for `imprint login`. |
|
|
27
|
+
| `imprint login` | OAuth via browser; saves token to `~/.imprint/credentials.json` (mode `0600`). |
|
|
28
|
+
| `imprint install [--tool <id>]` | Log in (if needed) and write MCP config. Tools: `claude-code` (default), `cursor`, `codex`. |
|
|
29
|
+
| `imprint logout` | Forget the saved token. |
|
|
30
|
+
| `imprint whoami` | Print where the credentials are stored. |
|
|
31
|
+
|
|
32
|
+
## What it writes
|
|
33
|
+
|
|
34
|
+
| Tool | File | Format |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `claude-code` | `~/.claude/settings.json` | JSON; merges `mcpServers.imprint` without touching other entries |
|
|
37
|
+
| `cursor` | `~/.cursor/mcp.json` | Same JSON merge |
|
|
38
|
+
| `codex` | `~/.codex/config.toml` | TOML block bracketed by `# === imprint:begin / end ===` markers, idempotent on re-install |
|
|
39
|
+
|
|
40
|
+
## Environment overrides
|
|
41
|
+
|
|
42
|
+
| Variable | Purpose |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `IMPRINT_DASHBOARD_URL` | Override the dashboard origin (default: `https://imprint.hawkeye-xb.com`). |
|
|
45
|
+
| `IMPRINT_API_BASE` | Override the worker API base URL. Defaults to whatever the dashboard's `/.well-known/imprint.json` reports. |
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
Elastic License 2.0 (ELv2). See repo root.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
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
|
+
};
|
|
11
|
+
|
|
12
|
+
// 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
|
+
import os from "os";
|
|
23
|
+
import path from "path";
|
|
24
|
+
import fs from "fs/promises";
|
|
25
|
+
async function resolveApiBase() {
|
|
26
|
+
if (_resolvedApiBase) return _resolvedApiBase;
|
|
27
|
+
const envOverride = process.env.IMPRINT_API_BASE?.replace(/\/+$/, "");
|
|
28
|
+
if (envOverride) {
|
|
29
|
+
_resolvedApiBase = envOverride;
|
|
30
|
+
return envOverride;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const url = new URL("/.well-known/imprint.json", DASHBOARD_URL + "/");
|
|
34
|
+
const res = await fetch(url, {
|
|
35
|
+
headers: { Accept: "application/json" }
|
|
36
|
+
});
|
|
37
|
+
if (res.ok) {
|
|
38
|
+
const parsed = await res.json();
|
|
39
|
+
if (typeof parsed.api_base === "string" && parsed.api_base.length > 0) {
|
|
40
|
+
_resolvedApiBase = parsed.api_base.replace(/\/+$/, "");
|
|
41
|
+
return _resolvedApiBase;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
_resolvedApiBase = HARDCODED_API_BASE_FALLBACK;
|
|
47
|
+
return _resolvedApiBase;
|
|
48
|
+
}
|
|
49
|
+
function credentialsPath() {
|
|
50
|
+
return CRED_FILE;
|
|
51
|
+
}
|
|
52
|
+
async function readCredentials() {
|
|
53
|
+
try {
|
|
54
|
+
const raw = await fs.readFile(CRED_FILE, "utf8");
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
if (typeof parsed.token !== "string") return null;
|
|
57
|
+
return parsed;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function writeCredentials(token, apiBase) {
|
|
63
|
+
await fs.mkdir(CRED_DIR, { recursive: true, mode: 448 });
|
|
64
|
+
const payload = {
|
|
65
|
+
token,
|
|
66
|
+
savedAt: Math.floor(Date.now() / 1e3),
|
|
67
|
+
endpoint: apiBase
|
|
68
|
+
};
|
|
69
|
+
await fs.writeFile(CRED_FILE, JSON.stringify(payload, null, 2), {
|
|
70
|
+
mode: 384
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async function removeCredentials() {
|
|
74
|
+
await fs.rm(CRED_FILE, { force: true });
|
|
75
|
+
}
|
|
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
|
+
|
|
94
|
+
// src/utils.ts
|
|
95
|
+
import { exec } from "child_process";
|
|
96
|
+
import net from "net";
|
|
97
|
+
async function findFreePort() {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const server = net.createServer();
|
|
100
|
+
server.unref();
|
|
101
|
+
server.on("error", reject);
|
|
102
|
+
server.listen(0, "127.0.0.1", () => {
|
|
103
|
+
const addr = server.address();
|
|
104
|
+
if (addr && typeof addr === "object") {
|
|
105
|
+
const port = addr.port;
|
|
106
|
+
server.close(() => resolve(port));
|
|
107
|
+
} else {
|
|
108
|
+
reject(new Error("could not determine free port"));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function openBrowser(url) {
|
|
114
|
+
const platform = process.platform;
|
|
115
|
+
const cmd = platform === "darwin" ? `open "${url}"` : platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
116
|
+
exec(cmd, (err) => {
|
|
117
|
+
if (err) {
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
var SUCCESS_HTML = `<!doctype html>
|
|
122
|
+
<html><head><meta charset="utf-8"><title>Imprint \u2014 authorized</title>
|
|
123
|
+
<style>
|
|
124
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
125
|
+
display: flex; align-items: center; justify-content: center;
|
|
126
|
+
height: 100vh; margin: 0; background: #f7f9fa; color: #1a1a1a; }
|
|
127
|
+
.card { background: white; padding: 2rem 3rem; border-radius: 8px;
|
|
128
|
+
border: 1px solid #e5e7eb; text-align: center; }
|
|
129
|
+
h1 { color: #007c8b; margin: 0 0 0.5rem; }
|
|
130
|
+
p { color: #666; margin: 0; }
|
|
131
|
+
</style>
|
|
132
|
+
<script>history.replaceState({}, "", "/")</script></head>
|
|
133
|
+
<body><div class="card">
|
|
134
|
+
<h1>\u2713 Authorized</h1>
|
|
135
|
+
<p>You can close this tab. Return to your terminal.</p>
|
|
136
|
+
</div></body></html>`;
|
|
137
|
+
var ERROR_HTML = (msg) => `<!doctype html>
|
|
138
|
+
<html><head><meta charset="utf-8"><title>Imprint \u2014 error</title></head>
|
|
139
|
+
<body style="font-family: sans-serif; padding: 2rem">
|
|
140
|
+
<h1 style="color: #b91c1c">Authorization failed</h1>
|
|
141
|
+
<pre>${escapeHtml(msg)}</pre>
|
|
142
|
+
</body></html>`;
|
|
143
|
+
function escapeHtml(s) {
|
|
144
|
+
return s.replace(
|
|
145
|
+
/[&<>"']/g,
|
|
146
|
+
(c) => ({
|
|
147
|
+
"&": "&",
|
|
148
|
+
"<": "<",
|
|
149
|
+
">": ">",
|
|
150
|
+
'"': """,
|
|
151
|
+
"'": "'"
|
|
152
|
+
})[c]
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
var successPage = SUCCESS_HTML;
|
|
156
|
+
var errorPage = ERROR_HTML;
|
|
157
|
+
|
|
158
|
+
// src/login.ts
|
|
159
|
+
var TOKEN_PREFIX = "imp_";
|
|
160
|
+
async function login() {
|
|
161
|
+
const port = await findFreePort();
|
|
162
|
+
const state = crypto.randomBytes(16).toString("hex");
|
|
163
|
+
const name = `${process.env.USER ?? "user"}@${os2.hostname()}`;
|
|
164
|
+
const tokenPromise = waitForCallback(port, state);
|
|
165
|
+
const url = new URL("/cli-login", DASHBOARD_URL);
|
|
166
|
+
url.searchParams.set("port", String(port));
|
|
167
|
+
url.searchParams.set("state", state);
|
|
168
|
+
url.searchParams.set("name", name);
|
|
169
|
+
console.log("\u2192 Opening browser to authorize\u2026");
|
|
170
|
+
console.log(` If it doesn't open, visit:
|
|
171
|
+
${url.toString()}
|
|
172
|
+
`);
|
|
173
|
+
openBrowser(url.toString());
|
|
174
|
+
const token = await tokenPromise;
|
|
175
|
+
const apiBase = await resolveApiBase();
|
|
176
|
+
await writeCredentials(token, apiBase);
|
|
177
|
+
console.log(`\u2713 Logged in. Token saved.`);
|
|
178
|
+
return token;
|
|
179
|
+
}
|
|
180
|
+
function waitForCallback(port, expectedState) {
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const timeout = setTimeout(
|
|
183
|
+
() => {
|
|
184
|
+
server.close();
|
|
185
|
+
reject(new Error("timed out waiting for browser callback (5 min)"));
|
|
186
|
+
},
|
|
187
|
+
5 * 60 * 1e3
|
|
188
|
+
);
|
|
189
|
+
const server = http.createServer((req, res) => {
|
|
190
|
+
if (!req.url) return;
|
|
191
|
+
const host = req.headers.host ?? "";
|
|
192
|
+
if (!host.startsWith("127.0.0.1") && !host.startsWith("localhost")) {
|
|
193
|
+
res.writeHead(400);
|
|
194
|
+
res.end();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const requrl = new URL(req.url, `http://localhost:${port}`);
|
|
198
|
+
if (requrl.pathname !== "/callback") {
|
|
199
|
+
res.writeHead(404);
|
|
200
|
+
res.end();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const token = requrl.searchParams.get("token");
|
|
204
|
+
const state = requrl.searchParams.get("state");
|
|
205
|
+
const error = requrl.searchParams.get("error");
|
|
206
|
+
if (error) {
|
|
207
|
+
respond(res, 400, errorPage(error));
|
|
208
|
+
finish(new Error(`dashboard reported: ${error}`));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (state !== expectedState) {
|
|
212
|
+
respond(res, 400, errorPage("state mismatch \u2014 possible CSRF"));
|
|
213
|
+
finish(new Error("state mismatch in callback"));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!token || !token.startsWith(TOKEN_PREFIX)) {
|
|
217
|
+
respond(res, 400, errorPage("missing or malformed token"));
|
|
218
|
+
finish(new Error("missing token in callback"));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
respond(res, 200, successPage);
|
|
222
|
+
finish(null, token);
|
|
223
|
+
});
|
|
224
|
+
server.on("error", (err) => finish(err));
|
|
225
|
+
server.listen(port, "127.0.0.1");
|
|
226
|
+
function finish(err, token) {
|
|
227
|
+
clearTimeout(timeout);
|
|
228
|
+
server.close();
|
|
229
|
+
if (err) reject(err);
|
|
230
|
+
else if (token) resolve(token);
|
|
231
|
+
}
|
|
232
|
+
function respond(res, status, body) {
|
|
233
|
+
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
234
|
+
res.end(body);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/install.ts
|
|
240
|
+
init_config();
|
|
241
|
+
import os3 from "os";
|
|
242
|
+
import path2 from "path";
|
|
243
|
+
import fs2 from "fs/promises";
|
|
244
|
+
var SUPPORTED = ["claude-code", "cursor", "codex"];
|
|
245
|
+
async function install(args) {
|
|
246
|
+
const tool = parseTool(args);
|
|
247
|
+
let creds = await readCredentials();
|
|
248
|
+
if (!creds) {
|
|
249
|
+
console.log("No credentials found \u2014 running login first.\n");
|
|
250
|
+
await login();
|
|
251
|
+
creds = await readCredentials();
|
|
252
|
+
if (!creds) throw new Error("login completed but credentials still missing");
|
|
253
|
+
}
|
|
254
|
+
const apiBase = await resolveApiBase();
|
|
255
|
+
switch (tool) {
|
|
256
|
+
case "claude-code":
|
|
257
|
+
await installClaudeCode(creds.token, apiBase);
|
|
258
|
+
break;
|
|
259
|
+
case "cursor":
|
|
260
|
+
await installCursor(creds.token, apiBase);
|
|
261
|
+
break;
|
|
262
|
+
case "codex":
|
|
263
|
+
await installCodex(creds.token, apiBase);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function parseTool(args) {
|
|
268
|
+
const idx = args.indexOf("--tool");
|
|
269
|
+
if (idx >= 0) {
|
|
270
|
+
const v = args[idx + 1];
|
|
271
|
+
if (!v) throw new Error("--tool requires a value");
|
|
272
|
+
if (!SUPPORTED.includes(v)) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`unsupported tool: ${v}. Supported: ${SUPPORTED.join(", ")}`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return v;
|
|
278
|
+
}
|
|
279
|
+
return "claude-code";
|
|
280
|
+
}
|
|
281
|
+
async function mergeJsonMcpServer(filePath, serverName, serverConfig) {
|
|
282
|
+
await fs2.mkdir(path2.dirname(filePath), { recursive: true });
|
|
283
|
+
let config = {};
|
|
284
|
+
try {
|
|
285
|
+
const raw = await fs2.readFile(filePath, "utf8");
|
|
286
|
+
if (raw.trim().length > 0) config = JSON.parse(raw);
|
|
287
|
+
} catch (err) {
|
|
288
|
+
if (err.code !== "ENOENT") throw err;
|
|
289
|
+
}
|
|
290
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
291
|
+
throw new Error(`${filePath} is not a JSON object`);
|
|
292
|
+
}
|
|
293
|
+
const mcpServers = config.mcpServers ?? {};
|
|
294
|
+
mcpServers[serverName] = serverConfig;
|
|
295
|
+
config.mcpServers = mcpServers;
|
|
296
|
+
await fs2.writeFile(filePath, JSON.stringify(config, null, 2) + "\n");
|
|
297
|
+
}
|
|
298
|
+
async function installClaudeCode(token, apiBase) {
|
|
299
|
+
const settingsPath = path2.join(os3.homedir(), ".claude", "settings.json");
|
|
300
|
+
await mergeJsonMcpServer(settingsPath, "imprint", {
|
|
301
|
+
type: "http",
|
|
302
|
+
url: `${apiBase}/mcp`,
|
|
303
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
304
|
+
});
|
|
305
|
+
console.log(`\u2713 Claude Code: wrote mcpServers.imprint to ${settingsPath}`);
|
|
306
|
+
console.log(` Restart Claude Code to pick up the new server.`);
|
|
307
|
+
}
|
|
308
|
+
async function installCursor(token, apiBase) {
|
|
309
|
+
const settingsPath = path2.join(os3.homedir(), ".cursor", "mcp.json");
|
|
310
|
+
await mergeJsonMcpServer(settingsPath, "imprint", {
|
|
311
|
+
url: `${apiBase}/mcp`,
|
|
312
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
313
|
+
});
|
|
314
|
+
console.log(`\u2713 Cursor: wrote mcpServers.imprint to ${settingsPath}`);
|
|
315
|
+
console.log(` Reload Cursor (or its MCP server list in Settings) to pick it up.`);
|
|
316
|
+
}
|
|
317
|
+
var CODEX_MARK_BEGIN = "# === imprint:begin (managed \u2014 do not edit between markers) ===";
|
|
318
|
+
var CODEX_MARK_END = "# === imprint:end ===";
|
|
319
|
+
async function installCodex(token, apiBase) {
|
|
320
|
+
const configPath = path2.join(os3.homedir(), ".codex", "config.toml");
|
|
321
|
+
await fs2.mkdir(path2.dirname(configPath), { recursive: true });
|
|
322
|
+
let existing = "";
|
|
323
|
+
try {
|
|
324
|
+
existing = await fs2.readFile(configPath, "utf8");
|
|
325
|
+
} catch (err) {
|
|
326
|
+
if (err.code !== "ENOENT") throw err;
|
|
327
|
+
}
|
|
328
|
+
const block = [
|
|
329
|
+
CODEX_MARK_BEGIN,
|
|
330
|
+
"[mcp_servers.imprint]",
|
|
331
|
+
`url = "${apiBase}/mcp"`,
|
|
332
|
+
"",
|
|
333
|
+
"[mcp_servers.imprint.headers]",
|
|
334
|
+
`Authorization = "Bearer ${token}"`,
|
|
335
|
+
CODEX_MARK_END
|
|
336
|
+
].join("\n");
|
|
337
|
+
const startIdx = existing.indexOf(CODEX_MARK_BEGIN);
|
|
338
|
+
const endIdx = existing.indexOf(CODEX_MARK_END);
|
|
339
|
+
let next;
|
|
340
|
+
if (startIdx >= 0 && endIdx > startIdx) {
|
|
341
|
+
const before = existing.slice(0, startIdx).replace(/\s+$/, "");
|
|
342
|
+
const after = existing.slice(endIdx + CODEX_MARK_END.length).replace(/^\s+/, "");
|
|
343
|
+
next = [before, block, after].filter((s) => s.length > 0).join("\n\n");
|
|
344
|
+
} else if (existing.trim().length === 0) {
|
|
345
|
+
next = block;
|
|
346
|
+
} else {
|
|
347
|
+
next = existing.replace(/\s+$/, "") + "\n\n" + block;
|
|
348
|
+
}
|
|
349
|
+
if (!next.endsWith("\n")) next += "\n";
|
|
350
|
+
await fs2.writeFile(configPath, next);
|
|
351
|
+
console.log(`\u2713 Codex: wrote [mcp_servers.imprint] to ${configPath}`);
|
|
352
|
+
console.log(` Restart Codex CLI to pick it up.`);
|
|
353
|
+
console.log(
|
|
354
|
+
` Note: Codex's HTTP MCP transport support varies by version \u2014 if it doesn't appear, please report and we'll add a stdio bridge.`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/cli.ts
|
|
359
|
+
var HELP = `imprint \u2014 memory layer for AI coding tools
|
|
360
|
+
|
|
361
|
+
Usage:
|
|
362
|
+
imprint Log in (alias for "imprint login").
|
|
363
|
+
imprint login Authenticate via browser; saves token to ~/.imprint/credentials.json.
|
|
364
|
+
imprint install [--tool ID] Log in (if needed) and write MCP config for the target tool.
|
|
365
|
+
Tools: claude-code (default), cursor, codex.
|
|
366
|
+
imprint logout Forget the saved token.
|
|
367
|
+
imprint whoami Print where the credentials are stored.
|
|
368
|
+
|
|
369
|
+
Environment overrides (for development):
|
|
370
|
+
IMPRINT_DASHBOARD_URL Dashboard origin (default: https://imprint.hawkeye-xb.com).
|
|
371
|
+
IMPRINT_API_BASE Worker base URL (default: https://imprint-api.hawkeye-xb.com).
|
|
372
|
+
`;
|
|
373
|
+
async function main() {
|
|
374
|
+
const [, , raw, ...rest] = process.argv;
|
|
375
|
+
const cmd = raw ?? "login";
|
|
376
|
+
switch (cmd) {
|
|
377
|
+
case "login":
|
|
378
|
+
await login();
|
|
379
|
+
break;
|
|
380
|
+
case "install":
|
|
381
|
+
await install(rest);
|
|
382
|
+
break;
|
|
383
|
+
case "logout":
|
|
384
|
+
(await Promise.resolve().then(() => (init_config(), config_exports))).removeCredentials();
|
|
385
|
+
console.log("\u2713 logged out");
|
|
386
|
+
break;
|
|
387
|
+
case "whoami":
|
|
388
|
+
console.log((await Promise.resolve().then(() => (init_config(), config_exports))).credentialsPath());
|
|
389
|
+
break;
|
|
390
|
+
case "-h":
|
|
391
|
+
case "--help":
|
|
392
|
+
case "help":
|
|
393
|
+
console.log(HELP);
|
|
394
|
+
break;
|
|
395
|
+
default:
|
|
396
|
+
console.error(`Unknown command: ${cmd}
|
|
397
|
+
`);
|
|
398
|
+
console.log(HELP);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
main().catch((err) => {
|
|
403
|
+
console.error(`Error: ${err.message ?? err}`);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hawkeye-xb.com/imprint-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
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
|
+
"license": "Elastic-2.0",
|
|
7
|
+
"homepage": "https://imprint.hawkeye-xb.com",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://imprint.hawkeye-xb.com"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/hawkeye-xb/imprint.git",
|
|
14
|
+
"directory": "apps/cli"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"imprint",
|
|
18
|
+
"mcp",
|
|
19
|
+
"memory",
|
|
20
|
+
"ai",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"cursor",
|
|
23
|
+
"codex"
|
|
24
|
+
],
|
|
25
|
+
"bin": {
|
|
26
|
+
"imprint": "./dist/cli.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup",
|
|
37
|
+
"dev": "tsup --watch",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"start": "node dist/cli.js",
|
|
40
|
+
"prepublishOnly": "pnpm build"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.10.2",
|
|
44
|
+
"tsup": "^8.3.5",
|
|
45
|
+
"typescript": "^5.6.3"
|
|
46
|
+
}
|
|
47
|
+
}
|