@simonsbs/keylore 1.0.0-rc5 → 1.0.0-rc6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,13 +64,13 @@ This repository is incubating privately today, but it is structured to be publis
64
64
 
65
65
  ## What is intentionally deferred
66
66
 
67
- The full `KeyLore.md` specification is broader than a sane `v1.0.0-rc5` delivery. The main remaining work before `v1.0.0` is:
67
+ The full `KeyLore.md` specification is broader than a sane `v1.0.0-rc6` delivery. The main remaining work before `v1.0.0` is:
68
68
 
69
69
  - public release polish and final operator documentation cleanup
70
70
 
71
71
  Those items are tracked in [docs/roadmap.md](/home/simon/keylore/docs/roadmap.md) and mapped back to the spec in [docs/keylore-spec-map.md](/home/simon/keylore/docs/keylore-spec-map.md).
72
72
 
73
- The active post-`v1.0.0-rc5` refocus is documented in [docs/core-mode-plan.md](/home/simon/keylore/docs/core-mode-plan.md): make the default user journey "add secret, add context, connect MCP, use it" and push broader operator features behind an advanced path.
73
+ The active post-`v1.0.0-rc6` refocus is documented in [docs/core-mode-plan.md](/home/simon/keylore/docs/core-mode-plan.md): make the default user journey "add secret, add context, connect MCP, use it" and push broader operator features behind an advanced path.
74
74
 
75
75
  The handoff from local core mode to advanced self-hosted mode is documented in [docs/production-handoff.md](/home/simon/keylore/docs/production-handoff.md).
76
76
 
@@ -118,28 +118,36 @@ KeyLore now redirects `/` to `/admin` and automatically opens a local operator s
118
118
 
119
119
  If that local session bootstrap fails for any reason, use `Start working locally` or the manual sign-in form shown on the page.
120
120
 
121
- 4. In `Save token`, choose the closest template for the token you are adding, such as `GitHub read-only`, `GitHub write-capable`, `npm read-only`, or `Internal service token`, then fill in:
121
+ 4. In `Quick start`, follow the short path: add token, test token, then connect your AI tool.
122
+
123
+ 5. In `Your tokens`, click `Add token`, choose the closest template for the token you are adding, such as `GitHub read-only`, `GitHub write-capable`, `npm read-only`, or `Internal service token`, then fill in:
122
124
  - `Name shown in KeyLore`
123
125
  - `Token key`
124
126
  - `Paste token`
125
127
  - `Where can it be used?`
128
+ - `Explain this token for people`
126
129
  - `Tell the AI when to use this token`
127
130
 
128
- That stores the raw token outside the searchable catalogue and keeps only the LLM-facing metadata in the credential record.
131
+ That stores the raw token outside the searchable catalogue and keeps only the metadata record in the credential catalogue.
129
132
 
130
- 5. Review `Writing help` and `What the AI will see` in the form to confirm the agent-facing record is specific, useful, and secret-free. `Token key` is the unique identifier for the token; if KeyLore says a token already exists, change that field and save again. Open `Advanced token settings` only if you need to change storage mode, risk level, service name, tags, or write access.
133
+ 6. Review `Writing help` and `What the AI will see` in the form to confirm the record is specific, useful, and secret-free. `LLM context` is the primary retrieval hint for agents. `User context` explains the human purpose of the token. `Token key` is the unique identifier for the token; if KeyLore says a token already exists, change that field and save again. Open `Advanced token settings` only if you need to change storage mode, risk level, service name, tags, or write access.
131
134
 
132
- 6. In `Saved tokens`, look under `Your tokens` for the ones you added yourself. `Included examples` are seeded local records and are shown separately so they do not get confused with your own tokens.
135
+ 7. In `Saved tokens`, everything is now listed together in one place. Example records are marked as examples, and they can be edited or deleted from the same list.
133
136
 
134
- 7. In `Test credential`, choose `Token to check`, set the `URL to call with this token`, and run the check.
137
+ 8. In `Test credential`, choose `Token to check`, set the `URL to call with this token`, and run the check.
135
138
 
136
139
  The check makes a real brokered `http.get` call with that token and URL. Success means the token, the target domain, and KeyLore policy all allowed the request.
137
140
 
138
- 8. In `Connect your AI tool`, copy the generated Codex or Gemini CLI local snippet for the easiest setup. Use the built-in `First prompt to try` examples after you restart the client. If you want remote HTTP MCP instead, open `Remote or advanced connection options` and mint an `/mcp` token there.
141
+ 9. In `Connect your AI tool`, pick the tool tab you want:
142
+ - `Codex`: copy the snippet or click `Apply to my Codex settings` to merge it into `~/.codex/config.toml`, then restart Codex
143
+ - `Gemini CLI`: copy the snippet or click `Apply to my Gemini settings` to merge it into `~/.gemini/settings.json`, then restart Gemini
144
+ - `Claude CLI`: copy the command or click `Apply to my Claude settings` to register KeyLore, then restart Claude
145
+
146
+ Use the built-in `First prompt to try` example after restarting the client. If you want remote HTTP MCP instead, open `Remote or advanced connection options` and mint an `/mcp` token there.
139
147
 
140
148
  Everything beyond that now sits behind `Show advanced controls` in the UI, so a first-run user can ignore tenants, OAuth client administration, approvals, backups, audit, and system internals entirely.
141
149
 
142
- After creation, use `Inspect or edit AI-facing context` inside `Save token` if you need to refine the metadata without re-entering or exposing the stored secret. Saved token cards also support lightweight lifecycle actions such as rename, retag, and archive/restore under `More actions`.
150
+ After creation, use `Edit token` from the saved-token list if you need to refine the metadata or replace the stored value for a locally stored token. Token creation and editing both happen in a popup now, while the main page stays focused on the token list, testing, and MCP connection.
143
151
 
144
152
  When that local path stops being enough, use [docs/production-handoff.md](/home/simon/keylore/docs/production-handoff.md) to decide when to switch to external secret backends, real OAuth clients, approvals, and tenant-separated self-hosting.
145
153
 
package/data/catalog.json CHANGED
@@ -13,6 +13,8 @@
13
13
  "expiresAt": null,
14
14
  "rotationPolicy": "Rotate every 90 days",
15
15
  "lastValidatedAt": "2026-03-13T00:00:00.000Z",
16
+ "userContext": "Shared example credential for GitHub metadata lookups in local demos. Keep it read-only and prefer a real user-owned token when one exists.",
17
+ "llmContext": "Fallback GitHub read-only demo credential. Use for repository metadata, issue lookup, release inspection, and rate-limit reads only when no better user-owned GitHub token is available. Never use for write operations.",
16
18
  "selectionNotes": "Use for repository metadata, issue lookup, and release inspection. Never use for write operations.",
17
19
  "binding": {
18
20
  "adapter": "env",
@@ -37,6 +39,8 @@
37
39
  "expiresAt": null,
38
40
  "rotationPolicy": "Rotate every 90 days",
39
41
  "lastValidatedAt": "2026-03-13T00:00:00.000Z",
42
+ "userContext": "Shared example credential for npm package metadata inspection in local demos. Keep it read-only and do not use it for publish workflows.",
43
+ "llmContext": "Fallback npm read-only demo credential. Use for package metadata inspection, dependency lookup, and registry read operations only. Never use for publish or package mutation workflows.",
40
44
  "selectionNotes": "Use for package metadata inspection only. Publishing is intentionally excluded from the default demo policy.",
41
45
  "binding": {
42
46
  "adapter": "env",
package/dist/config.js CHANGED
@@ -205,7 +205,7 @@ export function loadConfig(cwd = process.cwd()) {
205
205
  }
206
206
  return {
207
207
  appName: "keylore",
208
- version: "1.0.0-rc5",
208
+ version: "1.0.0-rc6",
209
209
  dataDir,
210
210
  bootstrapCatalogPath: path.resolve(runtimeRoot, "data", env.KEYLORE_CATALOG_FILE ?? "catalog.json"),
211
211
  bootstrapPolicyPath: path.resolve(runtimeRoot, "data", env.KEYLORE_POLICY_FILE ?? "policies.json"),
@@ -113,6 +113,8 @@ export const credentialRecordSchema = z.object({
113
113
  rotationPolicy: z.string().min(1),
114
114
  lastValidatedAt: z.string().datetime().nullable(),
115
115
  selectionNotes: z.string().min(1),
116
+ userContext: z.string().min(1).optional(),
117
+ llmContext: z.string().min(1).optional(),
116
118
  binding: credentialBindingSchema,
117
119
  tags: z.array(z.string().min(1)).default([]),
118
120
  status: credentialStatusSchema.default("active"),
@@ -210,6 +212,52 @@ export const catalogSearchInputSchema = z.object({
210
212
  export const createCredentialInputSchema = credentialRecordSchema;
211
213
  const secretLikeSelectionNotesPattern = /(gh[pousr]_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+|sk-[A-Za-z0-9_-]+|AKIA[0-9A-Z]{16})/;
212
214
  const vagueSelectionNotesPattern = /^(use when needed|general use|general purpose|for api|api token|token for api|default token|main token)$/i;
215
+ function normalizedLlmContext(value) {
216
+ return (value.llmContext ?? value.selectionNotes ?? "").trim();
217
+ }
218
+ function normalizedUserContext(value) {
219
+ return (value.userContext ?? normalizedLlmContext(value)).trim();
220
+ }
221
+ function validateLlmContext(llmContext, ctx, path) {
222
+ if (!llmContext) {
223
+ ctx.addIssue({
224
+ code: z.ZodIssueCode.custom,
225
+ path,
226
+ message: "LLM context is required. Explain when the agent should use this credential.",
227
+ });
228
+ return;
229
+ }
230
+ if (llmContext.length < 16) {
231
+ ctx.addIssue({
232
+ code: z.ZodIssueCode.custom,
233
+ path,
234
+ message: "LLM context must explain when the agent should use this credential in more detail.",
235
+ });
236
+ }
237
+ if (vagueSelectionNotesPattern.test(llmContext)) {
238
+ ctx.addIssue({
239
+ code: z.ZodIssueCode.custom,
240
+ path,
241
+ message: "LLM context is too vague. Describe the target service, intended use, and what the agent should avoid.",
242
+ });
243
+ }
244
+ if (secretLikeSelectionNotesPattern.test(llmContext)) {
245
+ ctx.addIssue({
246
+ code: z.ZodIssueCode.custom,
247
+ path,
248
+ message: "LLM context must not contain token-like secret material.",
249
+ });
250
+ }
251
+ }
252
+ function validateUserContext(userContext, ctx, path) {
253
+ if (!userContext) {
254
+ ctx.addIssue({
255
+ code: z.ZodIssueCode.custom,
256
+ path,
257
+ message: "User context is required. Describe the human purpose of this credential.",
258
+ });
259
+ }
260
+ }
213
261
  export const coreCredentialCreateInputSchema = z
214
262
  .object({
215
263
  credentialId: z.string().min(1),
@@ -221,7 +269,9 @@ export const coreCredentialCreateInputSchema = z
221
269
  sensitivity: sensitivitySchema.default("high"),
222
270
  allowedDomains: z.array(z.string().min(1)).min(1),
223
271
  permittedOperations: z.array(operationSchema).min(1).default(["http.get"]),
224
- selectionNotes: z.string().min(1),
272
+ selectionNotes: z.string().min(1).optional(),
273
+ userContext: z.string().min(1).optional(),
274
+ llmContext: z.string().min(1).optional(),
225
275
  rotationPolicy: z.string().default("Managed locally"),
226
276
  tags: z.array(z.string().min(1)).default([]),
227
277
  status: credentialStatusSchema.default("active"),
@@ -251,28 +301,8 @@ export const coreCredentialCreateInputSchema = z
251
301
  message: "Bearer credentials should include a non-empty header prefix.",
252
302
  });
253
303
  }
254
- const selectionNotes = value.selectionNotes.trim();
255
- if (selectionNotes.length < 16) {
256
- ctx.addIssue({
257
- code: z.ZodIssueCode.custom,
258
- path: ["selectionNotes"],
259
- message: "Selection notes must explain when the agent should use this credential in more detail.",
260
- });
261
- }
262
- if (vagueSelectionNotesPattern.test(selectionNotes)) {
263
- ctx.addIssue({
264
- code: z.ZodIssueCode.custom,
265
- path: ["selectionNotes"],
266
- message: "Selection notes are too vague. Describe the target service, intended use, and what the agent should avoid.",
267
- });
268
- }
269
- if (secretLikeSelectionNotesPattern.test(selectionNotes)) {
270
- ctx.addIssue({
271
- code: z.ZodIssueCode.custom,
272
- path: ["selectionNotes"],
273
- message: "Selection notes must not contain token-like secret material.",
274
- });
275
- }
304
+ validateLlmContext(normalizedLlmContext(value), ctx, ["llmContext"]);
305
+ validateUserContext(normalizedUserContext(value), ctx, ["userContext"]);
276
306
  if (value.permittedOperations.includes("http.post") && value.scopeTier === "read_only") {
277
307
  ctx.addIssue({
278
308
  code: z.ZodIssueCode.custom,
@@ -293,6 +323,8 @@ export const coreCredentialContextUpdateInputSchema = z
293
323
  allowedDomains: z.array(z.string().min(1)).min(1).optional(),
294
324
  permittedOperations: z.array(operationSchema).min(1).optional(),
295
325
  selectionNotes: z.string().min(1).optional(),
326
+ userContext: z.string().min(1).optional(),
327
+ llmContext: z.string().min(1).optional(),
296
328
  tags: z.array(z.string().min(1)).optional(),
297
329
  status: credentialStatusSchema.optional(),
298
330
  })
@@ -300,29 +332,11 @@ export const coreCredentialContextUpdateInputSchema = z
300
332
  message: "At least one context field must be provided.",
301
333
  })
302
334
  .superRefine((value, ctx) => {
303
- if (value.selectionNotes !== undefined) {
304
- const selectionNotes = value.selectionNotes.trim();
305
- if (selectionNotes.length < 16) {
306
- ctx.addIssue({
307
- code: z.ZodIssueCode.custom,
308
- path: ["selectionNotes"],
309
- message: "Selection notes must explain when the agent should use this credential in more detail.",
310
- });
311
- }
312
- if (vagueSelectionNotesPattern.test(selectionNotes)) {
313
- ctx.addIssue({
314
- code: z.ZodIssueCode.custom,
315
- path: ["selectionNotes"],
316
- message: "Selection notes are too vague. Describe the target service, intended use, and what the agent should avoid.",
317
- });
318
- }
319
- if (secretLikeSelectionNotesPattern.test(selectionNotes)) {
320
- ctx.addIssue({
321
- code: z.ZodIssueCode.custom,
322
- path: ["selectionNotes"],
323
- message: "Selection notes must not contain token-like secret material.",
324
- });
325
- }
335
+ if (value.selectionNotes !== undefined ||
336
+ value.llmContext !== undefined ||
337
+ value.userContext !== undefined) {
338
+ validateLlmContext(normalizedLlmContext(value), ctx, ["llmContext"]);
339
+ validateUserContext(normalizedUserContext(value), ctx, ["userContext"]);
326
340
  }
327
341
  const scopeTier = value.scopeTier;
328
342
  const permittedOperations = value.permittedOperations;