@monadeo.com/grimoire-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/LICENSE +202 -0
- package/README.md +16 -0
- package/dist/index.js +656 -0
- package/dist/index.js.map +7 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @monadeo.com/grimoire-cli
|
|
2
|
+
|
|
3
|
+
`grimoire` — command-line client for [Grimoire](https://grimoire.monadeo.com), the
|
|
4
|
+
documentation-retrieval service for AI coding agents.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install -g @monadeo.com/grimoire-cli
|
|
8
|
+
grimoire login
|
|
9
|
+
grimoire search "revalidateTag on-demand cache invalidation" -s nextjs@15
|
|
10
|
+
grimoire setup claude-code # wire the MCP server into your agent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run `grimoire help` for all commands. CI can set `GRIMOIRE_AUTH_TOKEN` with a machine
|
|
14
|
+
token instead of browser login.
|
|
15
|
+
|
|
16
|
+
Apache-2.0. Source: [monadeo/grimoire](https://github.com/monadeo/grimoire).
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
// ../core/dist/config.js
|
|
8
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
var DEFAULT_API_BASE = "https://grimoire-api.monadeo.com";
|
|
12
|
+
function globalConfigPath() {
|
|
13
|
+
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "grimoire", "config.json");
|
|
14
|
+
}
|
|
15
|
+
function readJsonConfig(path) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
18
|
+
} catch (err) {
|
|
19
|
+
process.stderr.write(`grimoire: ignoring malformed config at ${path}: ${err.message}
|
|
20
|
+
`);
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function loadGlobalConfig() {
|
|
25
|
+
const path = globalConfigPath();
|
|
26
|
+
const base = { apiBaseUrl: process.env.GRIMOIRE_API_URL ?? DEFAULT_API_BASE };
|
|
27
|
+
if (!existsSync(path))
|
|
28
|
+
return base;
|
|
29
|
+
const overrides = readJsonConfig(path);
|
|
30
|
+
return overrides ? { ...base, ...overrides } : base;
|
|
31
|
+
}
|
|
32
|
+
function findProjectConfig(startDir = process.cwd()) {
|
|
33
|
+
let dir = startDir;
|
|
34
|
+
for (; ; ) {
|
|
35
|
+
const candidate = join(dir, ".grimoire.json");
|
|
36
|
+
if (existsSync(candidate)) {
|
|
37
|
+
const parsed = readJsonConfig(candidate);
|
|
38
|
+
if (parsed)
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
const parent = dirname(dir);
|
|
42
|
+
if (parent === dir)
|
|
43
|
+
return void 0;
|
|
44
|
+
dir = parent;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function resolveDefaultSources() {
|
|
48
|
+
return findProjectConfig()?.sources ?? loadGlobalConfig().defaultSources ?? [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ../core/dist/auth.js
|
|
52
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
53
|
+
import { createServer } from "node:http";
|
|
54
|
+
import { Entry } from "@napi-rs/keyring";
|
|
55
|
+
|
|
56
|
+
// ../core/dist/errors.js
|
|
57
|
+
var ApiError = class extends Error {
|
|
58
|
+
status;
|
|
59
|
+
code;
|
|
60
|
+
body;
|
|
61
|
+
constructor(status, code, body) {
|
|
62
|
+
super(`${code} (${status})`);
|
|
63
|
+
this.status = status;
|
|
64
|
+
this.code = code;
|
|
65
|
+
this.body = body;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ../core/dist/http.js
|
|
70
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
71
|
+
async function fetchWithTimeout(url, init = {}) {
|
|
72
|
+
try {
|
|
73
|
+
return await fetch(url, { ...init, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
76
|
+
throw new ApiError(408, "timeout", `Request timed out after ${REQUEST_TIMEOUT_MS / 1e3}s`);
|
|
77
|
+
}
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ../core/dist/auth.js
|
|
83
|
+
var SERVICE = "com.monadeo.grimoire";
|
|
84
|
+
var ACCOUNT = "refresh_token";
|
|
85
|
+
function keyring() {
|
|
86
|
+
return new Entry(SERVICE, ACCOUNT);
|
|
87
|
+
}
|
|
88
|
+
function storeRefreshToken(token) {
|
|
89
|
+
keyring().setPassword(token);
|
|
90
|
+
}
|
|
91
|
+
function readRefreshToken() {
|
|
92
|
+
try {
|
|
93
|
+
return keyring().getPassword() ?? void 0;
|
|
94
|
+
} catch {
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function clearRefreshToken() {
|
|
99
|
+
try {
|
|
100
|
+
keyring().deletePassword();
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function base64url(buf) {
|
|
105
|
+
return buf.toString("base64url");
|
|
106
|
+
}
|
|
107
|
+
async function browserLogin(apiBase, openBrowser2) {
|
|
108
|
+
const broker = apiBase.replace(/\/+$/, "");
|
|
109
|
+
const verifier = base64url(randomBytes(32));
|
|
110
|
+
const challenge = base64url(createHash("sha256").update(verifier).digest());
|
|
111
|
+
const state = base64url(randomBytes(16));
|
|
112
|
+
const code = await new Promise((resolve, reject) => {
|
|
113
|
+
const server = createServer((req, res2) => {
|
|
114
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
115
|
+
const received = url.searchParams.get("code");
|
|
116
|
+
if (req.method !== "GET" || url.pathname !== "/callback" || url.searchParams.get("state") !== state || !received) {
|
|
117
|
+
res2.writeHead(404).end();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
res2.writeHead(200, { "Content-Type": "text/html" });
|
|
121
|
+
res2.end("<p>Login complete \u2014 return to your terminal.</p>");
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
server.close();
|
|
124
|
+
resolve(received);
|
|
125
|
+
});
|
|
126
|
+
server.listen(0, "127.0.0.1", () => {
|
|
127
|
+
const port = server.address().port;
|
|
128
|
+
const redirect = `http://127.0.0.1:${port}/callback`;
|
|
129
|
+
openBrowser2(`${broker}/auth/cli/start?code_challenge=${challenge}&code_challenge_method=S256&state=${state}&redirect_uri=${encodeURIComponent(redirect)}`);
|
|
130
|
+
});
|
|
131
|
+
const timer = setTimeout(() => {
|
|
132
|
+
server.close();
|
|
133
|
+
reject(new Error("Login timed out"));
|
|
134
|
+
}, 3e5);
|
|
135
|
+
});
|
|
136
|
+
const res = await fetchWithTimeout(`${broker}/auth/cli/exchange`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: { "Content-Type": "application/json" },
|
|
139
|
+
body: JSON.stringify({ code, code_verifier: verifier })
|
|
140
|
+
});
|
|
141
|
+
if (!res.ok)
|
|
142
|
+
throw new Error(`Token exchange failed: ${res.status}`);
|
|
143
|
+
const { refresh_token } = await res.json();
|
|
144
|
+
storeRefreshToken(refresh_token);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ../core/dist/client.js
|
|
148
|
+
var GrimoireClient = class {
|
|
149
|
+
baseUrl;
|
|
150
|
+
machineToken;
|
|
151
|
+
cachedIdToken;
|
|
152
|
+
refreshInFlight;
|
|
153
|
+
constructor(opts = {}) {
|
|
154
|
+
this.baseUrl = (opts.baseUrl ?? loadGlobalConfig().apiBaseUrl).replace(/\/+$/, "");
|
|
155
|
+
this.machineToken = opts.machineToken ?? process.env.GRIMOIRE_AUTH_TOKEN;
|
|
156
|
+
}
|
|
157
|
+
async bearer() {
|
|
158
|
+
if (this.machineToken)
|
|
159
|
+
return this.machineToken;
|
|
160
|
+
if (this.cachedIdToken && this.cachedIdToken.expiresAt > Date.now() + 6e4) {
|
|
161
|
+
return this.cachedIdToken.token;
|
|
162
|
+
}
|
|
163
|
+
this.refreshInFlight ??= this.refreshIdToken().finally(() => {
|
|
164
|
+
this.refreshInFlight = void 0;
|
|
165
|
+
});
|
|
166
|
+
return this.refreshInFlight;
|
|
167
|
+
}
|
|
168
|
+
// Exchange the Firebase refresh token for a fresh ID token (silent refresh).
|
|
169
|
+
async refreshIdToken() {
|
|
170
|
+
const refresh = readRefreshToken();
|
|
171
|
+
if (!refresh)
|
|
172
|
+
throw new ApiError(401, "not_logged_in", "Run `grimoire login`");
|
|
173
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/auth/cli/refresh`, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { "Content-Type": "application/json" },
|
|
176
|
+
body: JSON.stringify({ refresh_token: refresh })
|
|
177
|
+
});
|
|
178
|
+
if (!res.ok)
|
|
179
|
+
throw new ApiError(res.status, "refresh_failed", "Run `grimoire login`");
|
|
180
|
+
let payload;
|
|
181
|
+
try {
|
|
182
|
+
payload = await res.json();
|
|
183
|
+
} catch {
|
|
184
|
+
throw new ApiError(res.status, "refresh_failed", "Malformed refresh response");
|
|
185
|
+
}
|
|
186
|
+
const { id_token, expires_in } = payload;
|
|
187
|
+
if (typeof id_token !== "string" || typeof expires_in !== "number") {
|
|
188
|
+
throw new ApiError(res.status, "refresh_failed", "Malformed refresh response");
|
|
189
|
+
}
|
|
190
|
+
this.cachedIdToken = { token: id_token, expiresAt: Date.now() + expires_in * 1e3 };
|
|
191
|
+
return id_token;
|
|
192
|
+
}
|
|
193
|
+
async refreshSession() {
|
|
194
|
+
await this.bearer();
|
|
195
|
+
}
|
|
196
|
+
async send(path, init, auth) {
|
|
197
|
+
const headers = {
|
|
198
|
+
"Content-Type": "application/json",
|
|
199
|
+
...init.headers
|
|
200
|
+
};
|
|
201
|
+
if (auth)
|
|
202
|
+
headers.Authorization = `Bearer ${await this.bearer()}`;
|
|
203
|
+
return fetchWithTimeout(`${this.baseUrl}${path}`, { ...init, headers });
|
|
204
|
+
}
|
|
205
|
+
async request(path, init = {}, auth = true) {
|
|
206
|
+
let res = await this.send(path, init, auth);
|
|
207
|
+
if (res.status === 401 && auth && !this.machineToken) {
|
|
208
|
+
this.cachedIdToken = void 0;
|
|
209
|
+
res = await this.send(path, init, auth);
|
|
210
|
+
}
|
|
211
|
+
return this.parseResponse(res);
|
|
212
|
+
}
|
|
213
|
+
async parseResponse(res) {
|
|
214
|
+
const isJson = res.headers.get("content-type")?.includes("json") ?? false;
|
|
215
|
+
const text = await res.text();
|
|
216
|
+
let body;
|
|
217
|
+
let parsed = false;
|
|
218
|
+
if (isJson && text) {
|
|
219
|
+
try {
|
|
220
|
+
body = JSON.parse(text);
|
|
221
|
+
parsed = true;
|
|
222
|
+
} catch {
|
|
223
|
+
parsed = false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (!res.ok) {
|
|
227
|
+
const code = parsed && typeof body === "object" && body !== null ? body.error ?? "error" : "error";
|
|
228
|
+
throw new ApiError(res.status, code, parsed ? body : text);
|
|
229
|
+
}
|
|
230
|
+
if (isJson)
|
|
231
|
+
return parsed ? body : {};
|
|
232
|
+
return text;
|
|
233
|
+
}
|
|
234
|
+
search(input) {
|
|
235
|
+
return this.request("/v1/search", {
|
|
236
|
+
method: "POST",
|
|
237
|
+
body: JSON.stringify(input)
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
listSources(q) {
|
|
241
|
+
return this.request(`/v1/sources${q ? `?q=${encodeURIComponent(q)}` : ""}`);
|
|
242
|
+
}
|
|
243
|
+
listVersions(sourceId) {
|
|
244
|
+
return this.request(`/v1/sources/${encodeURIComponent(sourceId)}/versions`);
|
|
245
|
+
}
|
|
246
|
+
getContext(chunkId, window = 2) {
|
|
247
|
+
return this.request(`/v1/documents/${encodeURIComponent(chunkId)}/context?window=${window}`);
|
|
248
|
+
}
|
|
249
|
+
reportResult(chunkId, verdict, note) {
|
|
250
|
+
return this.request("/v1/feedback", {
|
|
251
|
+
method: "POST",
|
|
252
|
+
body: JSON.stringify({ chunk_id: chunkId, verdict, note })
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
submitSource(body) {
|
|
256
|
+
return this.request("/v1/sources", { method: "POST", body: JSON.stringify(body) });
|
|
257
|
+
}
|
|
258
|
+
getJob(jobId) {
|
|
259
|
+
return this.request(`/v1/jobs/${encodeURIComponent(jobId)}`);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// src/args.ts
|
|
264
|
+
var UsageError = class extends Error {
|
|
265
|
+
};
|
|
266
|
+
var BOOL_FLAGS = /* @__PURE__ */ new Set(["json", "compact", "watch", "private", "names"]);
|
|
267
|
+
function parseArgs(argv, aliases = {}) {
|
|
268
|
+
const positionals = [];
|
|
269
|
+
const flags = {};
|
|
270
|
+
const bools = /* @__PURE__ */ new Set();
|
|
271
|
+
for (let i = 0; i < argv.length; i++) {
|
|
272
|
+
const arg = argv[i];
|
|
273
|
+
if (arg.startsWith("--") || arg.startsWith("-")) {
|
|
274
|
+
const name = aliases[arg] ?? arg.replace(/^--?/, "");
|
|
275
|
+
if (BOOL_FLAGS.has(name)) {
|
|
276
|
+
bools.add(name);
|
|
277
|
+
} else {
|
|
278
|
+
const value = argv[i + 1];
|
|
279
|
+
if (value === void 0 || value.startsWith("-")) {
|
|
280
|
+
throw new UsageError(`Flag ${arg} requires a value`);
|
|
281
|
+
}
|
|
282
|
+
(flags[name] ??= []).push(value);
|
|
283
|
+
i++;
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
positionals.push(arg);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { positionals, flags, bools };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/output.ts
|
|
293
|
+
var EXIT = { ok: 0, apiError: 1, authRequired: 2, quota: 3, notFound: 4 };
|
|
294
|
+
function printResults(results, confidence) {
|
|
295
|
+
if (confidence === "weak") {
|
|
296
|
+
process.stderr.write(
|
|
297
|
+
"note: low-confidence results \u2014 the docs may not cover this; tell the user rather than guessing.\n"
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
for (const r of results) {
|
|
301
|
+
const path = (r.heading_path ?? []).join(" \u203A ");
|
|
302
|
+
process.stdout.write(`
|
|
303
|
+
${r.score.toFixed(3)} ${r.source}@${r.version} ${path}
|
|
304
|
+
${r.origin_url}
|
|
305
|
+
`);
|
|
306
|
+
const preview = r.text.length > 500 ? `${r.text.slice(0, 500)}\u2026` : r.text;
|
|
307
|
+
process.stdout.write(`${preview}
|
|
308
|
+
`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function printCompact(results) {
|
|
312
|
+
for (const r of results) {
|
|
313
|
+
process.stdout.write(
|
|
314
|
+
`${r.score.toFixed(3)} | ${r.source}@${r.version} | ${(r.heading_path ?? []).join(" \u203A ")} | ${r.origin_url}
|
|
315
|
+
`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/commands/setup.ts
|
|
321
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
322
|
+
import { homedir as homedir2 } from "node:os";
|
|
323
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
324
|
+
var MCP_ENTRY = { command: "npx", args: ["-y", "@monadeo.com/grimoire-mcp"] };
|
|
325
|
+
var CODEX_TABLE = `[mcp_servers.grimoire]
|
|
326
|
+
command = "npx"
|
|
327
|
+
args = ["-y", "@monadeo.com/grimoire-mcp"]
|
|
328
|
+
`;
|
|
329
|
+
function mergeCodexToml(existing) {
|
|
330
|
+
const header = existing.match(/^[ \t]*\[mcp_servers\.grimoire\][ \t]*(\r?\n|$)/m);
|
|
331
|
+
if (!header || header.index === void 0) {
|
|
332
|
+
if (existing.trim() === "") return CODEX_TABLE;
|
|
333
|
+
const sep = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
334
|
+
return existing + sep + CODEX_TABLE;
|
|
335
|
+
}
|
|
336
|
+
const afterHeader = header.index + header[0].length;
|
|
337
|
+
const nextHeader = existing.slice(afterHeader).match(/^[ \t]*\[/m);
|
|
338
|
+
const end = nextHeader && nextHeader.index !== void 0 ? afterHeader + nextHeader.index : existing.length;
|
|
339
|
+
return existing.slice(0, header.index) + CODEX_TABLE + existing.slice(end);
|
|
340
|
+
}
|
|
341
|
+
function mcpServersMerge(key) {
|
|
342
|
+
return (existing) => {
|
|
343
|
+
const servers = existing[key] ?? {};
|
|
344
|
+
return { ...existing, [key]: { ...servers, grimoire: MCP_ENTRY } };
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
var JSON_AGENTS = {
|
|
348
|
+
"claude-code": { path: join2(homedir2(), ".claude.json"), apply: mcpServersMerge("mcpServers") },
|
|
349
|
+
cursor: { path: join2(homedir2(), ".cursor", "mcp.json"), apply: mcpServersMerge("mcpServers") },
|
|
350
|
+
windsurf: {
|
|
351
|
+
path: join2(homedir2(), ".codeium", "windsurf", "mcp_config.json"),
|
|
352
|
+
apply: mcpServersMerge("mcpServers")
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
var AGENT_NAMES = [...Object.keys(JSON_AGENTS), "codex"];
|
|
356
|
+
function setupCodex() {
|
|
357
|
+
const path = join2(homedir2(), ".codex", "config.toml");
|
|
358
|
+
const existing = existsSync2(path) ? readFileSync2(path, "utf8") : "";
|
|
359
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
360
|
+
writeFileSync2(path, mergeCodexToml(existing));
|
|
361
|
+
return finish("codex", path);
|
|
362
|
+
}
|
|
363
|
+
function finish(agent, path) {
|
|
364
|
+
process.stdout.write(`Configured grimoire MCP for ${agent} at ${path}
|
|
365
|
+
`);
|
|
366
|
+
process.stdout.write("Run `grimoire login` if you haven't yet.\n");
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
function runSetup(agent) {
|
|
370
|
+
if (!agent || !AGENT_NAMES.includes(agent)) {
|
|
371
|
+
process.stderr.write(`Usage: grimoire setup <${AGENT_NAMES.join("|")}>
|
|
372
|
+
`);
|
|
373
|
+
return 1;
|
|
374
|
+
}
|
|
375
|
+
if (agent === "codex") return setupCodex();
|
|
376
|
+
const target = JSON_AGENTS[agent];
|
|
377
|
+
let existing = {};
|
|
378
|
+
if (existsSync2(target.path)) {
|
|
379
|
+
try {
|
|
380
|
+
existing = JSON.parse(readFileSync2(target.path, "utf8"));
|
|
381
|
+
} catch (err) {
|
|
382
|
+
process.stderr.write(
|
|
383
|
+
`Cannot parse ${target.path}: ${err.message}
|
|
384
|
+
Fix or remove the file, then re-run setup.
|
|
385
|
+
`
|
|
386
|
+
);
|
|
387
|
+
return 1;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
mkdirSync2(dirname2(target.path), { recursive: true });
|
|
391
|
+
writeFileSync2(target.path, JSON.stringify(target.apply(existing), null, 2));
|
|
392
|
+
return finish(agent, target.path);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/commands/init.ts
|
|
396
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
397
|
+
import { join as join3 } from "node:path";
|
|
398
|
+
function parseJsonFile(path) {
|
|
399
|
+
try {
|
|
400
|
+
return JSON.parse(readFileSync3(path, "utf8"));
|
|
401
|
+
} catch (err) {
|
|
402
|
+
throw new Error(`Cannot parse ${path}: ${err.message}`, { cause: err });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function detectManifestSources(dir = process.cwd()) {
|
|
406
|
+
const pins = [];
|
|
407
|
+
const pkgPath = join3(dir, "package.json");
|
|
408
|
+
if (existsSync3(pkgPath)) {
|
|
409
|
+
const pkg = parseJsonFile(pkgPath);
|
|
410
|
+
for (const [name, range] of Object.entries(pkg.dependencies ?? {})) {
|
|
411
|
+
const major = range.match(/(\d+)/)?.[1];
|
|
412
|
+
pins.push({ source: name.replace(/^@[^/]+\//, ""), ...major ? { version: major } : {} });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const reqPath = join3(dir, "requirements.txt");
|
|
416
|
+
if (existsSync3(reqPath)) {
|
|
417
|
+
for (const line of readFileSync3(reqPath, "utf8").split("\n")) {
|
|
418
|
+
const m = line.match(/^([A-Za-z0-9_.-]+)\s*==\s*(\d+)/);
|
|
419
|
+
if (m) pins.push({ source: m[1].toLowerCase(), version: m[2] });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return pins;
|
|
423
|
+
}
|
|
424
|
+
function runInit() {
|
|
425
|
+
const path = join3(process.cwd(), ".grimoire.json");
|
|
426
|
+
try {
|
|
427
|
+
const sources = detectManifestSources();
|
|
428
|
+
if (sources.length === 0) {
|
|
429
|
+
process.stderr.write("No package.json/requirements.txt dependencies found.\n");
|
|
430
|
+
return 1;
|
|
431
|
+
}
|
|
432
|
+
const existing = existsSync3(path) ? parseJsonFile(path) : {};
|
|
433
|
+
writeFileSync3(path, JSON.stringify({ ...existing, sources }, null, 2));
|
|
434
|
+
process.stdout.write(`Wrote ${sources.length} source pins to .grimoire.json
|
|
435
|
+
`);
|
|
436
|
+
return 0;
|
|
437
|
+
} catch (err) {
|
|
438
|
+
process.stderr.write(`${err.message}
|
|
439
|
+
`);
|
|
440
|
+
return 1;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/index.ts
|
|
445
|
+
function openBrowser(url) {
|
|
446
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
447
|
+
try {
|
|
448
|
+
execFileSync(cmd, [url], { stdio: "ignore" });
|
|
449
|
+
} catch {
|
|
450
|
+
process.stdout.write(`Open this URL to log in:
|
|
451
|
+
${url}
|
|
452
|
+
`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function parseSourceFlags(values) {
|
|
456
|
+
return (values ?? []).map((v) => {
|
|
457
|
+
const [source, version] = v.split("@");
|
|
458
|
+
return version ? { source, version } : { source };
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
var HELP = `grimoire \u2014 documentation retrieval for AI agents
|
|
462
|
+
|
|
463
|
+
grimoire login | logout | whoami
|
|
464
|
+
grimoire setup <claude-code|cursor|windsurf|codex>
|
|
465
|
+
grimoire init
|
|
466
|
+
grimoire search "<query>" [-s nextjs@15 -s react] [--lang en] [--top-k 8] [--json|--compact]
|
|
467
|
+
grimoire sources [--q <kw>] [--json]
|
|
468
|
+
grimoire versions <source> [--json]
|
|
469
|
+
grimoire doc <chunk_id> [--window 2]
|
|
470
|
+
grimoire report <chunk_id> --verdict incorrect|outdated|helpful [--note "..."]
|
|
471
|
+
grimoire ingest <url> [--version 15.2] [--private] [--webhook URL] [--watch]
|
|
472
|
+
grimoire jobs <job_id> [--watch]
|
|
473
|
+
grimoire mcp [--http]
|
|
474
|
+
|
|
475
|
+
env: GRIMOIRE_AUTH_TOKEN \u2014 machine token (CI, instead of login)
|
|
476
|
+
GRIMOIRE_API_URL \u2014 API origin without a path, e.g. https://grimoire-api-qa.monadeo.com
|
|
477
|
+
`;
|
|
478
|
+
async function main(argv) {
|
|
479
|
+
const [command, ...rest] = argv;
|
|
480
|
+
const args = parseArgs(rest, { "-s": "source", "-q": "q" });
|
|
481
|
+
const json = args.bools.has("json");
|
|
482
|
+
switch (command) {
|
|
483
|
+
case "login": {
|
|
484
|
+
await browserLogin(loadGlobalConfig().apiBaseUrl, openBrowser);
|
|
485
|
+
process.stdout.write("Logged in.\n");
|
|
486
|
+
return EXIT.ok;
|
|
487
|
+
}
|
|
488
|
+
case "logout":
|
|
489
|
+
clearRefreshToken();
|
|
490
|
+
process.stdout.write("Logged out.\n");
|
|
491
|
+
return EXIT.ok;
|
|
492
|
+
case "setup":
|
|
493
|
+
return runSetup(args.positionals[0]);
|
|
494
|
+
case "init":
|
|
495
|
+
return runInit();
|
|
496
|
+
case "mcp":
|
|
497
|
+
process.stderr.write("Run the MCP server with: npx @monadeo.com/grimoire-mcp" + (args.bools.has("http") ? " --http\n" : "\n"));
|
|
498
|
+
return EXIT.ok;
|
|
499
|
+
case void 0:
|
|
500
|
+
case "help":
|
|
501
|
+
case "--help":
|
|
502
|
+
process.stdout.write(HELP);
|
|
503
|
+
return EXIT.ok;
|
|
504
|
+
}
|
|
505
|
+
const client = new GrimoireClient();
|
|
506
|
+
try {
|
|
507
|
+
switch (command) {
|
|
508
|
+
case "whoami": {
|
|
509
|
+
if (process.env.GRIMOIRE_AUTH_TOKEN) {
|
|
510
|
+
process.stdout.write("machine token configured (GRIMOIRE_AUTH_TOKEN)\n");
|
|
511
|
+
return EXIT.ok;
|
|
512
|
+
}
|
|
513
|
+
if (!readRefreshToken()) {
|
|
514
|
+
process.stderr.write("not logged in \u2014 run `grimoire login`\n");
|
|
515
|
+
return EXIT.authRequired;
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
await client.refreshSession();
|
|
519
|
+
process.stdout.write("logged in (browser session)\n");
|
|
520
|
+
return EXIT.ok;
|
|
521
|
+
} catch (err) {
|
|
522
|
+
const reason = err instanceof ApiError ? err.code : err.message;
|
|
523
|
+
process.stderr.write(`not logged in (${reason}) \u2014 run \`grimoire login\`
|
|
524
|
+
`);
|
|
525
|
+
return EXIT.authRequired;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
case "search": {
|
|
529
|
+
const query = args.positionals[0];
|
|
530
|
+
if (!query) {
|
|
531
|
+
process.stderr.write('Usage: grimoire search "<query>" -s <source>\n');
|
|
532
|
+
return EXIT.apiError;
|
|
533
|
+
}
|
|
534
|
+
const explicit = parseSourceFlags(args.flags.source);
|
|
535
|
+
const sources = explicit.length > 0 ? explicit : resolveDefaultSources();
|
|
536
|
+
if (sources.length === 0) {
|
|
537
|
+
process.stderr.write("No sources. Pass -s <source> or run `grimoire init`.\n");
|
|
538
|
+
return EXIT.apiError;
|
|
539
|
+
}
|
|
540
|
+
const res = await client.search({
|
|
541
|
+
query,
|
|
542
|
+
sources,
|
|
543
|
+
language: args.flags.lang?.[0],
|
|
544
|
+
top_k: args.flags["top-k"] ? Number(args.flags["top-k"][0]) : void 0
|
|
545
|
+
});
|
|
546
|
+
if (json) process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
547
|
+
else if (args.bools.has("compact")) printCompact(res.results);
|
|
548
|
+
else printResults(res.results, res.confidence);
|
|
549
|
+
return EXIT.ok;
|
|
550
|
+
}
|
|
551
|
+
case "sources": {
|
|
552
|
+
const res = await client.listSources(args.flags.q?.[0]);
|
|
553
|
+
process.stdout.write(JSON.stringify(res.sources ?? [], null, json ? 2 : 0) + "\n");
|
|
554
|
+
return EXIT.ok;
|
|
555
|
+
}
|
|
556
|
+
case "versions": {
|
|
557
|
+
const res = await client.listVersions(args.positionals[0]);
|
|
558
|
+
process.stdout.write(JSON.stringify(res.versions ?? [], null, json ? 2 : 0) + "\n");
|
|
559
|
+
return EXIT.ok;
|
|
560
|
+
}
|
|
561
|
+
case "doc": {
|
|
562
|
+
const res = await client.getContext(args.positionals[0], Number(args.flags.window?.[0] ?? 2));
|
|
563
|
+
process.stdout.write(JSON.stringify(res.chunks ?? [], null, 2) + "\n");
|
|
564
|
+
return EXIT.ok;
|
|
565
|
+
}
|
|
566
|
+
case "report": {
|
|
567
|
+
await client.reportResult(args.positionals[0], args.flags.verdict?.[0] ?? "incorrect", args.flags.note?.[0]);
|
|
568
|
+
process.stdout.write("Reported.\n");
|
|
569
|
+
return EXIT.ok;
|
|
570
|
+
}
|
|
571
|
+
case "ingest": {
|
|
572
|
+
const res = await client.submitSource({
|
|
573
|
+
url: args.positionals[0],
|
|
574
|
+
version: args.flags.version?.[0],
|
|
575
|
+
visibility: args.bools.has("private") ? "private" : "public",
|
|
576
|
+
webhook_url: args.flags.webhook?.[0]
|
|
577
|
+
});
|
|
578
|
+
process.stdout.write(`Job: ${res.job_id}
|
|
579
|
+
`);
|
|
580
|
+
if (args.bools.has("watch")) return watchJob(client, res.job_id);
|
|
581
|
+
return EXIT.ok;
|
|
582
|
+
}
|
|
583
|
+
case "jobs":
|
|
584
|
+
return args.bools.has("watch") ? watchJob(client, args.positionals[0]) : printJob(client, args.positionals[0]);
|
|
585
|
+
default:
|
|
586
|
+
process.stderr.write(`Unknown command: ${command}
|
|
587
|
+
${HELP}`);
|
|
588
|
+
return EXIT.apiError;
|
|
589
|
+
}
|
|
590
|
+
} catch (err) {
|
|
591
|
+
if (err instanceof ApiError) {
|
|
592
|
+
process.stderr.write(`error: ${err.code}
|
|
593
|
+
`);
|
|
594
|
+
if (err.status === 401) return EXIT.authRequired;
|
|
595
|
+
if (err.status === 429) return EXIT.quota;
|
|
596
|
+
if (err.status === 404) return EXIT.notFound;
|
|
597
|
+
return EXIT.apiError;
|
|
598
|
+
}
|
|
599
|
+
throw err;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
var TERMINAL_STATUSES = ["complete", "failed", "rejected"];
|
|
603
|
+
var FAILED_STATUSES = ["failed", "rejected"];
|
|
604
|
+
async function printJob(client, jobId) {
|
|
605
|
+
const job = await client.getJob(jobId);
|
|
606
|
+
const status = job.status ?? "unknown";
|
|
607
|
+
process.stdout.write(`${status} ${JSON.stringify(job.counters ?? {})}
|
|
608
|
+
`);
|
|
609
|
+
return FAILED_STATUSES.includes(status) ? EXIT.apiError : EXIT.ok;
|
|
610
|
+
}
|
|
611
|
+
var WATCH_POLL_MS = 5e3;
|
|
612
|
+
var WATCH_BACKOFF_CAP_MS = 3e4;
|
|
613
|
+
var WATCH_DEADLINE_MS = 30 * 6e4;
|
|
614
|
+
function sleep(ms) {
|
|
615
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
616
|
+
}
|
|
617
|
+
async function watchJob(client, jobId) {
|
|
618
|
+
const deadline = Date.now() + WATCH_DEADLINE_MS;
|
|
619
|
+
let backoff = WATCH_POLL_MS;
|
|
620
|
+
while (Date.now() < deadline) {
|
|
621
|
+
try {
|
|
622
|
+
const job = await client.getJob(jobId);
|
|
623
|
+
const status = job.status ?? "unknown";
|
|
624
|
+
process.stdout.write(`${status} ${JSON.stringify(job.counters ?? {})}
|
|
625
|
+
`);
|
|
626
|
+
if (TERMINAL_STATUSES.includes(status)) {
|
|
627
|
+
return FAILED_STATUSES.includes(status) ? EXIT.apiError : EXIT.ok;
|
|
628
|
+
}
|
|
629
|
+
backoff = WATCH_POLL_MS;
|
|
630
|
+
await sleep(WATCH_POLL_MS);
|
|
631
|
+
} catch (err) {
|
|
632
|
+
if (err instanceof ApiError && [401, 403, 404].includes(err.status)) throw err;
|
|
633
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
634
|
+
process.stderr.write(`poll failed (${reason}); retrying in ${backoff / 1e3}s
|
|
635
|
+
`);
|
|
636
|
+
await sleep(backoff);
|
|
637
|
+
backoff = Math.min(backoff * 2, WATCH_BACKOFF_CAP_MS);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
process.stderr.write(
|
|
641
|
+
`Watch deadline (30m) reached; the job is still running. Check later with: grimoire jobs ${jobId}
|
|
642
|
+
`
|
|
643
|
+
);
|
|
644
|
+
return EXIT.apiError;
|
|
645
|
+
}
|
|
646
|
+
main(process.argv.slice(2)).then((code) => process.exit(code)).catch((err) => {
|
|
647
|
+
if (err instanceof UsageError) {
|
|
648
|
+
process.stderr.write(`${err.message}
|
|
649
|
+
`);
|
|
650
|
+
process.exit(EXIT.apiError);
|
|
651
|
+
}
|
|
652
|
+
process.stderr.write(`fatal: ${err.message}
|
|
653
|
+
`);
|
|
654
|
+
process.exit(EXIT.apiError);
|
|
655
|
+
});
|
|
656
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../../core/dist/config.js", "../../core/dist/auth.js", "../../core/dist/errors.js", "../../core/dist/http.js", "../../core/dist/client.js", "../src/args.ts", "../src/output.ts", "../src/commands/setup.ts", "../src/commands/init.ts"],
|
|
4
|
+
"sourcesContent": ["import { execFileSync } from \"node:child_process\";\nimport {\n GrimoireClient,\n ApiError,\n browserLogin,\n clearRefreshToken,\n readRefreshToken,\n resolveDefaultSources,\n loadGlobalConfig,\n type SourcePin,\n} from \"@monadeo.com/grimoire-core\";\nimport { parseArgs, UsageError } from \"./args.js\";\nimport { printResults, printCompact, EXIT } from \"./output.js\";\nimport { runSetup } from \"./commands/setup.js\";\nimport { runInit } from \"./commands/init.js\";\n\nfunction openBrowser(url: string): void {\n const cmd = process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"start\" : \"xdg-open\";\n try {\n execFileSync(cmd, [url], { stdio: \"ignore\" });\n } catch {\n process.stdout.write(`Open this URL to log in:\\n${url}\\n`);\n }\n}\n\nfunction parseSourceFlags(values: string[] | undefined): SourcePin[] {\n return (values ?? []).map((v) => {\n const [source, version] = v.split(\"@\");\n return version ? { source, version } : { source };\n });\n}\n\nconst HELP = `grimoire \u2014 documentation retrieval for AI agents\n\n grimoire login | logout | whoami\n grimoire setup <claude-code|cursor|windsurf|codex>\n grimoire init\n grimoire search \"<query>\" [-s nextjs@15 -s react] [--lang en] [--top-k 8] [--json|--compact]\n grimoire sources [--q <kw>] [--json]\n grimoire versions <source> [--json]\n grimoire doc <chunk_id> [--window 2]\n grimoire report <chunk_id> --verdict incorrect|outdated|helpful [--note \"...\"]\n grimoire ingest <url> [--version 15.2] [--private] [--webhook URL] [--watch]\n grimoire jobs <job_id> [--watch]\n grimoire mcp [--http]\n\n env: GRIMOIRE_AUTH_TOKEN \u2014 machine token (CI, instead of login)\n GRIMOIRE_API_URL \u2014 API origin without a path, e.g. https://grimoire-api-qa.monadeo.com\n`;\n\nasync function main(argv: string[]): Promise<number> {\n const [command, ...rest] = argv;\n const args = parseArgs(rest, { \"-s\": \"source\", \"-q\": \"q\" });\n const json = args.bools.has(\"json\");\n\n switch (command) {\n case \"login\": {\n await browserLogin(loadGlobalConfig().apiBaseUrl, openBrowser);\n process.stdout.write(\"Logged in.\\n\");\n return EXIT.ok;\n }\n case \"logout\":\n clearRefreshToken();\n process.stdout.write(\"Logged out.\\n\");\n return EXIT.ok;\n case \"setup\":\n return runSetup(args.positionals[0]);\n case \"init\":\n return runInit();\n case \"mcp\":\n process.stderr.write(\"Run the MCP server with: npx @monadeo.com/grimoire-mcp\" + (args.bools.has(\"http\") ? \" --http\\n\" : \"\\n\"));\n return EXIT.ok;\n case undefined:\n case \"help\":\n case \"--help\":\n process.stdout.write(HELP);\n return EXIT.ok;\n }\n\n const client = new GrimoireClient();\n try {\n switch (command) {\n case \"whoami\": {\n if (process.env.GRIMOIRE_AUTH_TOKEN) {\n process.stdout.write(\"machine token configured (GRIMOIRE_AUTH_TOKEN)\\n\");\n return EXIT.ok;\n }\n if (!readRefreshToken()) {\n process.stderr.write(\"not logged in \u2014 run `grimoire login`\\n\");\n return EXIT.authRequired;\n }\n try {\n await client.refreshSession();\n process.stdout.write(\"logged in (browser session)\\n\");\n return EXIT.ok;\n } catch (err) {\n const reason = err instanceof ApiError ? err.code : (err as Error).message;\n process.stderr.write(`not logged in (${reason}) \u2014 run \\`grimoire login\\`\\n`);\n return EXIT.authRequired;\n }\n }\n case \"search\": {\n const query = args.positionals[0];\n if (!query) {\n process.stderr.write(\"Usage: grimoire search \\\"<query>\\\" -s <source>\\n\");\n return EXIT.apiError;\n }\n const explicit = parseSourceFlags(args.flags.source);\n const sources = explicit.length > 0 ? explicit : resolveDefaultSources();\n if (sources.length === 0) {\n process.stderr.write(\"No sources. Pass -s <source> or run `grimoire init`.\\n\");\n return EXIT.apiError;\n }\n const res = await client.search({\n query,\n sources,\n language: args.flags.lang?.[0],\n top_k: args.flags[\"top-k\"] ? Number(args.flags[\"top-k\"][0]) : undefined,\n });\n if (json) process.stdout.write(JSON.stringify(res, null, 2) + \"\\n\");\n else if (args.bools.has(\"compact\")) printCompact(res.results);\n else printResults(res.results, res.confidence);\n return EXIT.ok;\n }\n case \"sources\": {\n const res = await client.listSources(args.flags.q?.[0]);\n process.stdout.write(JSON.stringify(res.sources ?? [], null, json ? 2 : 0) + \"\\n\");\n return EXIT.ok;\n }\n case \"versions\": {\n const res = await client.listVersions(args.positionals[0]);\n process.stdout.write(JSON.stringify(res.versions ?? [], null, json ? 2 : 0) + \"\\n\");\n return EXIT.ok;\n }\n case \"doc\": {\n const res = await client.getContext(args.positionals[0], Number(args.flags.window?.[0] ?? 2));\n process.stdout.write(JSON.stringify(res.chunks ?? [], null, 2) + \"\\n\");\n return EXIT.ok;\n }\n case \"report\": {\n await client.reportResult(args.positionals[0], args.flags.verdict?.[0] ?? \"incorrect\", args.flags.note?.[0]);\n process.stdout.write(\"Reported.\\n\");\n return EXIT.ok;\n }\n case \"ingest\": {\n const res = await client.submitSource({\n url: args.positionals[0],\n version: args.flags.version?.[0],\n visibility: args.bools.has(\"private\") ? \"private\" : \"public\",\n webhook_url: args.flags.webhook?.[0],\n });\n process.stdout.write(`Job: ${res.job_id}\\n`);\n if (args.bools.has(\"watch\")) return watchJob(client, res.job_id);\n return EXIT.ok;\n }\n case \"jobs\":\n return args.bools.has(\"watch\") ? watchJob(client, args.positionals[0]) : printJob(client, args.positionals[0]);\n default:\n process.stderr.write(`Unknown command: ${command}\\n${HELP}`);\n return EXIT.apiError;\n }\n } catch (err) {\n if (err instanceof ApiError) {\n process.stderr.write(`error: ${err.code}\\n`);\n if (err.status === 401) return EXIT.authRequired;\n if (err.status === 429) return EXIT.quota;\n if (err.status === 404) return EXIT.notFound;\n return EXIT.apiError;\n }\n throw err;\n }\n}\n\nconst TERMINAL_STATUSES = [\"complete\", \"failed\", \"rejected\"];\nconst FAILED_STATUSES = [\"failed\", \"rejected\"];\n\nasync function printJob(client: GrimoireClient, jobId: string): Promise<number> {\n const job = await client.getJob(jobId);\n const status = job.status ?? \"unknown\";\n process.stdout.write(`${status} ${JSON.stringify(job.counters ?? {})}\\n`);\n return FAILED_STATUSES.includes(status) ? EXIT.apiError : EXIT.ok;\n}\n\nconst WATCH_POLL_MS = 5_000;\nconst WATCH_BACKOFF_CAP_MS = 30_000;\nconst WATCH_DEADLINE_MS = 30 * 60_000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nasync function watchJob(client: GrimoireClient, jobId: string): Promise<number> {\n const deadline = Date.now() + WATCH_DEADLINE_MS;\n let backoff = WATCH_POLL_MS;\n while (Date.now() < deadline) {\n try {\n const job = await client.getJob(jobId);\n const status = job.status ?? \"unknown\";\n process.stdout.write(`${status} ${JSON.stringify(job.counters ?? {})}\\n`);\n if (TERMINAL_STATUSES.includes(status)) {\n return FAILED_STATUSES.includes(status) ? EXIT.apiError : EXIT.ok;\n }\n backoff = WATCH_POLL_MS;\n await sleep(WATCH_POLL_MS);\n } catch (err) {\n // Auth/permission/missing-job failures cannot heal on their own \u2014 bail out;\n // anything else (network, 5xx, timeout) is worth retrying with backoff.\n if (err instanceof ApiError && [401, 403, 404].includes(err.status)) throw err;\n const reason = err instanceof Error ? err.message : String(err);\n process.stderr.write(`poll failed (${reason}); retrying in ${backoff / 1000}s\\n`);\n await sleep(backoff);\n backoff = Math.min(backoff * 2, WATCH_BACKOFF_CAP_MS);\n }\n }\n process.stderr.write(\n `Watch deadline (30m) reached; the job is still running. Check later with: grimoire jobs ${jobId}\\n`,\n );\n return EXIT.apiError;\n}\n\nmain(process.argv.slice(2))\n .then((code) => process.exit(code))\n .catch((err) => {\n if (err instanceof UsageError) {\n process.stderr.write(`${err.message}\\n`);\n process.exit(EXIT.apiError);\n }\n process.stderr.write(`fatal: ${(err as Error).message}\\n`);\n process.exit(EXIT.apiError);\n });\n", "import { readFileSync, existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n// Bare origin \u2014 API paths are absolute (/v1/..., /auth/cli/...), so the base must\n// never carry a path prefix or auth-broker URLs would be built wrong.\nexport const DEFAULT_API_BASE = \"https://grimoire-api.monadeo.com\";\nfunction globalConfigPath() {\n return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), \".config\"), \"grimoire\", \"config.json\");\n}\n// Malformed config must never crash a consumer (the MCP server constructs a client\n// at import time): warn and behave as if the file were absent.\nfunction readJsonConfig(path) {\n try {\n return JSON.parse(readFileSync(path, \"utf8\"));\n }\n catch (err) {\n process.stderr.write(`grimoire: ignoring malformed config at ${path}: ${err.message}\\n`);\n return undefined;\n }\n}\nexport function loadGlobalConfig() {\n const path = globalConfigPath();\n const base = { apiBaseUrl: process.env.GRIMOIRE_API_URL ?? DEFAULT_API_BASE };\n if (!existsSync(path))\n return base;\n const overrides = readJsonConfig(path);\n return overrides ? { ...base, ...overrides } : base;\n}\nexport function saveGlobalConfig(config) {\n const path = globalConfigPath();\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(config, null, 2));\n}\n// Project config (.grimoire.json in the repo root) pins a codebase's sources and\n// overrides globals \u2014 committed alongside the code it documents.\nexport function findProjectConfig(startDir = process.cwd()) {\n let dir = startDir;\n for (;;) {\n const candidate = join(dir, \".grimoire.json\");\n if (existsSync(candidate)) {\n const parsed = readJsonConfig(candidate);\n if (parsed)\n return parsed;\n }\n const parent = dirname(dir);\n if (parent === dir)\n return undefined;\n dir = parent;\n }\n}\nexport function resolveDefaultSources() {\n return findProjectConfig()?.sources ?? loadGlobalConfig().defaultSources ?? [];\n}\n", "import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer } from \"node:http\";\nimport { Entry } from \"@napi-rs/keyring\";\nimport { fetchWithTimeout } from \"./http.js\";\n// Credential storage in the OS keychain (maintained @napi-rs/keyring \u2014 keytar is\n// archived). A machine token via GRIMOIRE_AUTH_TOKEN overrides interactive auth (CI only).\nconst SERVICE = \"com.monadeo.grimoire\";\nconst ACCOUNT = \"refresh_token\";\nfunction keyring() {\n return new Entry(SERVICE, ACCOUNT);\n}\nexport function storeRefreshToken(token) {\n keyring().setPassword(token);\n}\nexport function readRefreshToken() {\n try {\n return keyring().getPassword() ?? undefined;\n }\n catch {\n return undefined;\n }\n}\nexport function clearRefreshToken() {\n try {\n keyring().deletePassword();\n }\n catch {\n /* already gone */\n }\n}\nfunction base64url(buf) {\n return buf.toString(\"base64url\");\n}\n// Browser login via the Grimoire auth broker (Firebase is not an OAuth AS): open\n// the hosted page with a PKCE challenge, receive the one-time code on a loopback\n// callback, exchange it for the Firebase refresh token. The random state binds the\n// callback to this login attempt; anything but GET /callback with that state gets\n// a 404 and the server keeps waiting.\nexport async function browserLogin(apiBase, openBrowser) {\n // The broker lives at the bare origin (/auth/cli/*, no /v1 prefix): normalize\n // away trailing slashes so the URLs are always built against the origin.\n const broker = apiBase.replace(/\\/+$/, \"\");\n const verifier = base64url(randomBytes(32));\n const challenge = base64url(createHash(\"sha256\").update(verifier).digest());\n const state = base64url(randomBytes(16));\n const code = await new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const received = url.searchParams.get(\"code\");\n if (req.method !== \"GET\" ||\n url.pathname !== \"/callback\" ||\n url.searchParams.get(\"state\") !== state ||\n !received) {\n res.writeHead(404).end();\n return;\n }\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\"<p>Login complete \u2014 return to your terminal.</p>\");\n clearTimeout(timer);\n server.close();\n resolve(received);\n });\n server.listen(0, \"127.0.0.1\", () => {\n const port = server.address().port;\n const redirect = `http://127.0.0.1:${port}/callback`;\n openBrowser(`${broker}/auth/cli/start?code_challenge=${challenge}&code_challenge_method=S256&state=${state}&redirect_uri=${encodeURIComponent(redirect)}`);\n });\n const timer = setTimeout(() => {\n server.close();\n reject(new Error(\"Login timed out\"));\n }, 300_000);\n });\n const res = await fetchWithTimeout(`${broker}/auth/cli/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ code, code_verifier: verifier }),\n });\n if (!res.ok)\n throw new Error(`Token exchange failed: ${res.status}`);\n const { refresh_token } = (await res.json());\n storeRefreshToken(refresh_token);\n}\n", "export class ApiError extends Error {\n status;\n code;\n body;\n constructor(status, code, body) {\n super(`${code} (${status})`);\n this.status = status;\n this.code = code;\n this.body = body;\n }\n}\n", "import { ApiError } from \"./errors.js\";\nexport const REQUEST_TIMEOUT_MS = 30_000;\nexport async function fetchWithTimeout(url, init = {}) {\n try {\n return await fetch(url, { ...init, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });\n }\n catch (err) {\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(408, \"timeout\", `Request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);\n }\n throw err;\n }\n}\n", "import { readRefreshToken } from \"./auth.js\";\nimport { loadGlobalConfig } from \"./config.js\";\nimport { ApiError } from \"./errors.js\";\nimport { fetchWithTimeout } from \"./http.js\";\nexport { ApiError } from \"./errors.js\";\nexport class GrimoireClient {\n baseUrl;\n machineToken;\n cachedIdToken;\n refreshInFlight;\n constructor(opts = {}) {\n this.baseUrl = (opts.baseUrl ?? loadGlobalConfig().apiBaseUrl).replace(/\\/+$/, \"\");\n this.machineToken = opts.machineToken ?? process.env.GRIMOIRE_AUTH_TOKEN;\n }\n async bearer() {\n if (this.machineToken)\n return this.machineToken;\n if (this.cachedIdToken && this.cachedIdToken.expiresAt > Date.now() + 60_000) {\n return this.cachedIdToken.token;\n }\n this.refreshInFlight ??= this.refreshIdToken().finally(() => {\n this.refreshInFlight = undefined;\n });\n return this.refreshInFlight;\n }\n // Exchange the Firebase refresh token for a fresh ID token (silent refresh).\n async refreshIdToken() {\n const refresh = readRefreshToken();\n if (!refresh)\n throw new ApiError(401, \"not_logged_in\", \"Run `grimoire login`\");\n const res = await fetchWithTimeout(`${this.baseUrl}/auth/cli/refresh`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refresh_token: refresh }),\n });\n if (!res.ok)\n throw new ApiError(res.status, \"refresh_failed\", \"Run `grimoire login`\");\n let payload;\n try {\n payload = await res.json();\n }\n catch {\n throw new ApiError(res.status, \"refresh_failed\", \"Malformed refresh response\");\n }\n const { id_token, expires_in } = payload;\n if (typeof id_token !== \"string\" || typeof expires_in !== \"number\") {\n throw new ApiError(res.status, \"refresh_failed\", \"Malformed refresh response\");\n }\n this.cachedIdToken = { token: id_token, expiresAt: Date.now() + expires_in * 1000 };\n return id_token;\n }\n async refreshSession() {\n await this.bearer();\n }\n async send(path, init, auth) {\n const headers = {\n \"Content-Type\": \"application/json\",\n ...init.headers,\n };\n if (auth)\n headers.Authorization = `Bearer ${await this.bearer()}`;\n return fetchWithTimeout(`${this.baseUrl}${path}`, { ...init, headers });\n }\n async request(path, init = {}, auth = true) {\n let res = await this.send(path, init, auth);\n // A 401 despite a locally-unexpired ID token means it was revoked server-side:\n // force one refresh and retry exactly once.\n if (res.status === 401 && auth && !this.machineToken) {\n this.cachedIdToken = undefined;\n res = await this.send(path, init, auth);\n }\n return this.parseResponse(res);\n }\n async parseResponse(res) {\n const isJson = res.headers.get(\"content-type\")?.includes(\"json\") ?? false;\n const text = await res.text();\n let body;\n let parsed = false;\n if (isJson && text) {\n try {\n body = JSON.parse(text);\n parsed = true;\n }\n catch {\n parsed = false;\n }\n }\n if (!res.ok) {\n const code = parsed && typeof body === \"object\" && body !== null\n ? (body.error ?? \"error\")\n : \"error\";\n throw new ApiError(res.status, code, parsed ? body : text);\n }\n if (isJson)\n return (parsed ? body : {});\n return text;\n }\n search(input) {\n return this.request(\"/v1/search\", {\n method: \"POST\",\n body: JSON.stringify(input),\n });\n }\n listSources(q) {\n return this.request(`/v1/sources${q ? `?q=${encodeURIComponent(q)}` : \"\"}`);\n }\n listVersions(sourceId) {\n return this.request(`/v1/sources/${encodeURIComponent(sourceId)}/versions`);\n }\n getContext(chunkId, window = 2) {\n return this.request(`/v1/documents/${encodeURIComponent(chunkId)}/context?window=${window}`);\n }\n reportResult(chunkId, verdict, note) {\n return this.request(\"/v1/feedback\", {\n method: \"POST\",\n body: JSON.stringify({ chunk_id: chunkId, verdict, note }),\n });\n }\n submitSource(body) {\n return this.request(\"/v1/sources\", { method: \"POST\", body: JSON.stringify(body) });\n }\n getJob(jobId) {\n return this.request(`/v1/jobs/${encodeURIComponent(jobId)}`);\n }\n}\n", "// Minimal flag parser: collects repeated --flag/-f values and positionals.\nexport interface ParsedArgs {\n positionals: string[];\n flags: Record<string, string[]>;\n bools: Set<string>;\n}\n\nexport class UsageError extends Error {}\n\nconst BOOL_FLAGS = new Set([\"json\", \"compact\", \"watch\", \"private\", \"names\"]);\n\nexport function parseArgs(argv: string[], aliases: Record<string, string> = {}): ParsedArgs {\n const positionals: string[] = [];\n const flags: Record<string, string[]> = {};\n const bools = new Set<string>();\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg.startsWith(\"--\") || arg.startsWith(\"-\")) {\n const name = aliases[arg] ?? arg.replace(/^--?/, \"\");\n if (BOOL_FLAGS.has(name)) {\n bools.add(name);\n } else {\n const value = argv[i + 1];\n if (value === undefined || value.startsWith(\"-\")) {\n throw new UsageError(`Flag ${arg} requires a value`);\n }\n (flags[name] ??= []).push(value);\n i++;\n }\n } else {\n positionals.push(arg);\n }\n }\n return { positionals, flags, bools };\n}\n", "import type { SearchResultChunk } from \"@monadeo.com/grimoire-core\";\n\n// Exit codes documented for scripting (clients spec).\nexport const EXIT = { ok: 0, apiError: 1, authRequired: 2, quota: 3, notFound: 4 } as const;\n\nexport function printResults(results: SearchResultChunk[], confidence: string): void {\n if (confidence === \"weak\") {\n process.stderr.write(\n \"note: low-confidence results \u2014 the docs may not cover this; tell the user rather than guessing.\\n\",\n );\n }\n for (const r of results) {\n const path = (r.heading_path ?? []).join(\" \u203A \");\n process.stdout.write(`\\n${r.score.toFixed(3)} ${r.source}@${r.version} ${path}\\n${r.origin_url}\\n`);\n const preview = r.text.length > 500 ? `${r.text.slice(0, 500)}\u2026` : r.text;\n process.stdout.write(`${preview}\\n`);\n }\n}\n\nexport function printCompact(results: SearchResultChunk[]): void {\n for (const r of results) {\n process.stdout.write(\n `${r.score.toFixed(3)} | ${r.source}@${r.version} | ${(r.heading_path ?? []).join(\" \u203A \")} | ${r.origin_url}\\n`,\n );\n }\n}\n", "import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\n// One-command agent onboarding (SDD decision 19 / clients spec): writes the MCP\n// registration for the named agent. `npx @monadeo.com/grimoire-mcp` needs no global install.\nconst MCP_ENTRY = { command: \"npx\", args: [\"-y\", \"@monadeo.com/grimoire-mcp\"] };\n\nconst CODEX_TABLE = `[mcp_servers.grimoire]\ncommand = \"npx\"\nargs = [\"-y\", \"@monadeo.com/grimoire-mcp\"]\n`;\n\n// Codex reads ~/.codex/config.toml. Replace our table in place (up to the next\n// [ header or EOF) without a TOML library, so the rest of the user's config\n// passes through byte-for-byte.\nexport function mergeCodexToml(existing: string): string {\n const header = existing.match(/^[ \\t]*\\[mcp_servers\\.grimoire\\][ \\t]*(\\r?\\n|$)/m);\n if (!header || header.index === undefined) {\n if (existing.trim() === \"\") return CODEX_TABLE;\n const sep = existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n return existing + sep + CODEX_TABLE;\n }\n const afterHeader = header.index + header[0].length;\n const nextHeader = existing.slice(afterHeader).match(/^[ \\t]*\\[/m);\n const end =\n nextHeader && nextHeader.index !== undefined ? afterHeader + nextHeader.index : existing.length;\n return existing.slice(0, header.index) + CODEX_TABLE + existing.slice(end);\n}\n\ninterface JsonAgentTarget {\n path: string;\n apply: (existing: Record<string, unknown>) => Record<string, unknown>;\n}\n\nfunction mcpServersMerge(key: string) {\n return (existing: Record<string, unknown>) => {\n const servers = (existing[key] as Record<string, unknown>) ?? {};\n return { ...existing, [key]: { ...servers, grimoire: MCP_ENTRY } };\n };\n}\n\nconst JSON_AGENTS: Record<string, JsonAgentTarget> = {\n \"claude-code\": { path: join(homedir(), \".claude.json\"), apply: mcpServersMerge(\"mcpServers\") },\n cursor: { path: join(homedir(), \".cursor\", \"mcp.json\"), apply: mcpServersMerge(\"mcpServers\") },\n windsurf: {\n path: join(homedir(), \".codeium\", \"windsurf\", \"mcp_config.json\"),\n apply: mcpServersMerge(\"mcpServers\"),\n },\n};\n\nconst AGENT_NAMES = [...Object.keys(JSON_AGENTS), \"codex\"];\n\nfunction setupCodex(): number {\n const path = join(homedir(), \".codex\", \"config.toml\");\n const existing = existsSync(path) ? readFileSync(path, \"utf8\") : \"\";\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, mergeCodexToml(existing));\n return finish(\"codex\", path);\n}\n\nfunction finish(agent: string, path: string): number {\n process.stdout.write(`Configured grimoire MCP for ${agent} at ${path}\\n`);\n process.stdout.write(\"Run `grimoire login` if you haven't yet.\\n\");\n return 0;\n}\n\nexport function runSetup(agent: string | undefined): number {\n if (!agent || !AGENT_NAMES.includes(agent)) {\n process.stderr.write(`Usage: grimoire setup <${AGENT_NAMES.join(\"|\")}>\\n`);\n return 1;\n }\n if (agent === \"codex\") return setupCodex();\n const target = JSON_AGENTS[agent];\n let existing: Record<string, unknown> = {};\n if (existsSync(target.path)) {\n try {\n existing = JSON.parse(readFileSync(target.path, \"utf8\")) as Record<string, unknown>;\n } catch (err) {\n process.stderr.write(\n `Cannot parse ${target.path}: ${(err as Error).message}\\nFix or remove the file, then re-run setup.\\n`,\n );\n return 1;\n }\n }\n mkdirSync(dirname(target.path), { recursive: true });\n writeFileSync(target.path, JSON.stringify(target.apply(existing), null, 2));\n return finish(agent, target.path);\n}\n", "import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SourcePin } from \"@monadeo.com/grimoire-core\";\n\nfunction parseJsonFile<T>(path: string): T {\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as T;\n } catch (err) {\n throw new Error(`Cannot parse ${path}: ${(err as Error).message}`, { cause: err });\n }\n}\n\n// Scan common manifests \u2192 propose .grimoire.json source pins. Alias resolution\n// against the registry (curated to include package-ecosystem names) happens when\n// the sources are actually queried; init proposes candidate names from manifests.\nexport function detectManifestSources(dir = process.cwd()): SourcePin[] {\n const pins: SourcePin[] = [];\n const pkgPath = join(dir, \"package.json\");\n if (existsSync(pkgPath)) {\n const pkg = parseJsonFile<{ dependencies?: Record<string, string> }>(pkgPath);\n for (const [name, range] of Object.entries(pkg.dependencies ?? {})) {\n const major = range.match(/(\\d+)/)?.[1];\n pins.push({ source: name.replace(/^@[^/]+\\//, \"\"), ...(major ? { version: major } : {}) });\n }\n }\n const reqPath = join(dir, \"requirements.txt\");\n if (existsSync(reqPath)) {\n for (const line of readFileSync(reqPath, \"utf8\").split(\"\\n\")) {\n const m = line.match(/^([A-Za-z0-9_.-]+)\\s*==\\s*(\\d+)/);\n if (m) pins.push({ source: m[1].toLowerCase(), version: m[2] });\n }\n }\n return pins;\n}\n\nexport function runInit(): number {\n const path = join(process.cwd(), \".grimoire.json\");\n try {\n const sources = detectManifestSources();\n if (sources.length === 0) {\n process.stderr.write(\"No package.json/requirements.txt dependencies found.\\n\");\n return 1;\n }\n const existing = existsSync(path) ? parseJsonFile<Record<string, unknown>>(path) : {};\n writeFileSync(path, JSON.stringify({ ...existing, sources }, null, 2));\n process.stdout.write(`Wrote ${sources.length} source pins to .grimoire.json\\n`);\n return 0;\n } catch (err) {\n process.stderr.write(`${(err as Error).message}\\n`);\n return 1;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["openBrowser", "res", "existsSync", "mkdirSync", "readFileSync", "writeFileSync", "homedir", "dirname", "join", "existsSync", "readFileSync", "writeFileSync", "join"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@monadeo.com/grimoire-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"bin": {
|
|
7
|
+
"grimoire": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@napi-rs/keyring": "^1.3.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^26.1.0",
|
|
17
|
+
"esbuild": "^0.28.1",
|
|
18
|
+
"@monadeo.com/grimoire-core": "0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"description": "Grimoire CLI — version-correct documentation retrieval for AI coding agents",
|
|
21
|
+
"homepage": "https://grimoire.monadeo.com",
|
|
22
|
+
"bugs": "https://github.com/monadeo/grimoire/issues",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/monadeo/grimoire.git",
|
|
26
|
+
"directory": "packages/cli"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=24"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "node esbuild.config.mjs",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"lint": "eslint src",
|
|
35
|
+
"test": "vitest run"
|
|
36
|
+
}
|
|
37
|
+
}
|