@cleocode/core 2026.10.13 → 2026.10.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-provenance.json +2 -2
- package/dist/cloud/nexus-vault.d.ts +6 -2
- package/dist/cloud/nexus-vault.d.ts.map +1 -1
- package/dist/cloud/nexus-vault.js +53 -3
- package/dist/cloud/nexus-vault.js.map +1 -1
- package/dist/cloud/secret-seal.js +2 -2
- package/dist/cloud/secret-seal.js.map +1 -1
- package/dist/config/registry.d.ts +0 -21
- package/dist/config/registry.d.ts.map +1 -1
- package/dist/config/registry.js +0 -41
- package/dist/config/registry.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/crypto/credentials.d.ts +13 -0
- package/dist/crypto/credentials.d.ts.map +1 -1
- package/dist/crypto/credentials.js +35 -6
- package/dist/crypto/credentials.js.map +1 -1
- package/dist/docs/docs-ops.d.ts +11 -1
- package/dist/docs/docs-ops.d.ts.map +1 -1
- package/dist/docs/docs-ops.js +38 -3
- package/dist/docs/docs-ops.js.map +1 -1
- package/dist/docs/docs-update.d.ts.map +1 -1
- package/dist/docs/docs-update.js +5 -1
- package/dist/docs/docs-update.js.map +1 -1
- package/dist/docs/supersede.d.ts.map +1 -1
- package/dist/docs/supersede.js +5 -1
- package/dist/docs/supersede.js.map +1 -1
- package/dist/docs/wikilinks.d.ts +3 -1
- package/dist/docs/wikilinks.d.ts.map +1 -1
- package/dist/docs/wikilinks.js +23 -12
- package/dist/docs/wikilinks.js.map +1 -1
- package/dist/gateway-client/generated/index.d.ts +2 -2
- package/dist/gateway-client/generated/index.d.ts.map +1 -1
- package/dist/gateway-client/generated/index.js +1 -1
- package/dist/gateway-client/generated/index.js.map +1 -1
- package/dist/gateway-client/generated/namespaces.gen.d.ts +33 -0
- package/dist/gateway-client/generated/namespaces.gen.d.ts.map +1 -1
- package/dist/gateway-client/generated/namespaces.gen.js +34 -1
- package/dist/gateway-client/generated/namespaces.gen.js.map +1 -1
- package/dist/gateway-client/generated/sdk.gen.d.ts +137 -5
- package/dist/gateway-client/generated/sdk.gen.d.ts.map +1 -1
- package/dist/gateway-client/generated/sdk.gen.js +355 -6
- package/dist/gateway-client/generated/sdk.gen.js.map +1 -1
- package/dist/gateway-client/generated/types.gen.d.ts +1811 -187
- package/dist/gateway-client/generated/types.gen.d.ts.map +1 -1
- package/dist/lib/error-message.d.ts +30 -0
- package/dist/lib/error-message.d.ts.map +1 -0
- package/dist/lib/error-message.js +65 -0
- package/dist/lib/error-message.js.map +1 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/llm/credentials-store.d.ts +29 -16
- package/dist/llm/credentials-store.d.ts.map +1 -1
- package/dist/llm/credentials-store.js +158 -139
- package/dist/llm/credentials-store.js.map +1 -1
- package/dist/memory/brain-embedding.d.ts +4 -2
- package/dist/memory/brain-embedding.d.ts.map +1 -1
- package/dist/memory/brain-embedding.js +8 -2
- package/dist/memory/brain-embedding.js.map +1 -1
- package/dist/memory/embedding-local.d.ts +28 -5
- package/dist/memory/embedding-local.d.ts.map +1 -1
- package/dist/memory/embedding-local.js +47 -9
- package/dist/memory/embedding-local.js.map +1 -1
- package/dist/memory/engine-compat.d.ts +4 -0
- package/dist/memory/engine-compat.d.ts.map +1 -1
- package/dist/memory/engine-compat.js +76 -0
- package/dist/memory/engine-compat.js.map +1 -1
- package/dist/memory/global-brain.d.ts +93 -0
- package/dist/memory/global-brain.d.ts.map +1 -0
- package/dist/memory/global-brain.js +183 -0
- package/dist/memory/global-brain.js.map +1 -0
- package/dist/memory/patterns.d.ts +2 -2
- package/dist/memory/quality-feedback.d.ts.map +1 -1
- package/dist/memory/quality-feedback.js +0 -26
- package/dist/memory/quality-feedback.js.map +1 -1
- package/dist/memory/retrieval/observe.js +4 -4
- package/dist/memory/retrieval/observe.js.map +1 -1
- package/dist/memory/transcript-extractor.js +5 -2
- package/dist/memory/transcript-extractor.js.map +1 -1
- package/dist/orchestration/lean-change.d.ts.map +1 -1
- package/dist/orchestration/lean-change.js +2 -3
- package/dist/orchestration/lean-change.js.map +1 -1
- package/dist/orchestration/lean-debt.d.ts +48 -0
- package/dist/orchestration/lean-debt.d.ts.map +1 -0
- package/dist/orchestration/lean-debt.js +167 -0
- package/dist/orchestration/lean-debt.js.map +1 -0
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +10 -0
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/store/attachment-store.d.ts.map +1 -1
- package/dist/store/attachment-store.js +5 -1
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/backup-sidecar.d.ts +5 -2
- package/dist/store/backup-sidecar.d.ts.map +1 -1
- package/dist/store/backup-sidecar.js +31 -18
- package/dist/store/backup-sidecar.js.map +1 -1
- package/dist/store/credential-transfer.d.ts +13 -14
- package/dist/store/credential-transfer.d.ts.map +1 -1
- package/dist/store/credential-transfer.js +75 -23
- package/dist/store/credential-transfer.js.map +1 -1
- package/dist/store/docs-bytes-backup.d.ts +133 -0
- package/dist/store/docs-bytes-backup.d.ts.map +1 -0
- package/dist/store/docs-bytes-backup.js +450 -0
- package/dist/store/docs-bytes-backup.js.map +1 -0
- package/dist/store/llm-accounts-accessor.d.ts +107 -0
- package/dist/store/llm-accounts-accessor.d.ts.map +1 -0
- package/dist/store/llm-accounts-accessor.js +224 -0
- package/dist/store/llm-accounts-accessor.js.map +1 -0
- package/dist/store/memory-accessor.d.ts +44 -0
- package/dist/store/memory-accessor.d.ts.map +1 -1
- package/dist/store/memory-accessor.js +60 -0
- package/dist/store/memory-accessor.js.map +1 -1
- package/dist/store/memory-sqlite.d.ts +18 -0
- package/dist/store/memory-sqlite.d.ts.map +1 -1
- package/dist/store/memory-sqlite.js +40 -23
- package/dist/store/memory-sqlite.js.map +1 -1
- package/dist/store/migration-manager.d.ts.map +1 -1
- package/dist/store/migration-manager.js +7 -1
- package/dist/store/migration-manager.js.map +1 -1
- package/dist/store/restore-store.d.ts +12 -1
- package/dist/store/restore-store.d.ts.map +1 -1
- package/dist/store/restore-store.js +150 -11
- package/dist/store/restore-store.js.map +1 -1
- package/dist/store/row-identity-registry.d.ts +4 -4
- package/dist/store/row-identity-registry.d.ts.map +1 -1
- package/dist/store/row-identity-registry.js +35 -8
- package/dist/store/row-identity-registry.js.map +1 -1
- package/dist/store/schema/cleo-global/accounts.d.ts +6 -5
- package/dist/store/schema/cleo-global/accounts.d.ts.map +1 -1
- package/dist/store/schema/cleo-global/accounts.js +7 -6
- package/dist/store/schema/cleo-global/accounts.js.map +1 -1
- package/dist/store/schema/cleo-project/docs.d.ts +32 -0
- package/dist/store/schema/cleo-project/docs.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/docs.js +9 -0
- package/dist/store/schema/cleo-project/docs.js.map +1 -1
- package/dist/store/sync/apply/applier.d.ts +3 -1
- package/dist/store/sync/apply/applier.d.ts.map +1 -1
- package/dist/store/sync/apply/applier.js +80 -19
- package/dist/store/sync/apply/applier.js.map +1 -1
- package/dist/store/sync/apply/parent-delete.d.ts.map +1 -1
- package/dist/store/sync/apply/parent-delete.js +4 -0
- package/dist/store/sync/apply/parent-delete.js.map +1 -1
- package/dist/store/sync/apply/write-api.d.ts +5 -1
- package/dist/store/sync/apply/write-api.d.ts.map +1 -1
- package/dist/store/sync/apply/write-api.js +22 -0
- package/dist/store/sync/apply/write-api.js.map +1 -1
- package/dist/store/sync/capture.d.ts +27 -0
- package/dist/store/sync/capture.d.ts.map +1 -1
- package/dist/store/sync/capture.js +32 -1
- package/dist/store/sync/capture.js.map +1 -1
- package/dist/store/sync/genesis.d.ts.map +1 -1
- package/dist/store/sync/genesis.js +3 -1
- package/dist/store/sync/genesis.js.map +1 -1
- package/dist/store/sync/inbox.d.ts +18 -2
- package/dist/store/sync/inbox.d.ts.map +1 -1
- package/dist/store/sync/inbox.js +23 -2
- package/dist/store/sync/inbox.js.map +1 -1
- package/dist/store/sync/pull.d.ts +1 -1
- package/dist/store/sync/pull.d.ts.map +1 -1
- package/dist/store/sync/pull.js +4 -1
- package/dist/store/sync/pull.js.map +1 -1
- package/dist/store/sync/timestamp-canon.d.ts.map +1 -1
- package/dist/store/sync/timestamp-canon.js +3 -0
- package/dist/store/sync/timestamp-canon.js.map +1 -1
- package/dist/store/table-classification.d.ts.map +1 -1
- package/dist/store/table-classification.js +29 -3
- package/dist/store/table-classification.js.map +1 -1
- package/dist/system/backup.d.ts +5 -2
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/backup.js +39 -3
- package/dist/system/backup.js.map +1 -1
- package/dist/tasks/affected-scope.d.ts +44 -5
- package/dist/tasks/affected-scope.d.ts.map +1 -1
- package/dist/tasks/affected-scope.js +159 -20
- package/dist/tasks/affected-scope.js.map +1 -1
- package/dist/tasks/complete-ci.d.ts.map +1 -1
- package/dist/tasks/complete-ci.js +11 -9
- package/dist/tasks/complete-ci.js.map +1 -1
- package/dist/tasks/done-plan.d.ts.map +1 -1
- package/dist/tasks/done-plan.js +4 -6
- package/dist/tasks/done-plan.js.map +1 -1
- package/dist/tasks/gate-preview.d.ts.map +1 -1
- package/dist/tasks/gate-preview.js +13 -5
- package/dist/tasks/gate-preview.js.map +1 -1
- package/dist/tasks/gate-result-cache.d.ts +30 -1
- package/dist/tasks/gate-result-cache.d.ts.map +1 -1
- package/dist/tasks/gate-result-cache.js +81 -2
- package/dist/tasks/gate-result-cache.js.map +1 -1
- package/dist/tasks/gate-runner.d.ts +27 -2
- package/dist/tasks/gate-runner.d.ts.map +1 -1
- package/dist/tasks/gate-runner.js +67 -14
- package/dist/tasks/gate-runner.js.map +1 -1
- package/dist/tasks/qa-run-binding.d.ts +15 -0
- package/dist/tasks/qa-run-binding.d.ts.map +1 -1
- package/dist/tasks/qa-run-binding.js +24 -3
- package/dist/tasks/qa-run-binding.js.map +1 -1
- package/migrations/drizzle-brain/20261014180000_t13719-prune-candidate-column/migration.sql +16 -0
- package/migrations/drizzle-cleo-project/20261014100000_t12919-docs-row-uids/migration.sql +27 -0
- package/package.json +13 -13
- package/templates/CLEO-REFERENCE.md +1 -1
- package/templates/global-config.template.json +2 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded, single-line formatting for thrown values in warnings and logs.
|
|
3
|
+
*
|
|
4
|
+
* Drizzle (and other query builders) interpolate the full SQL text and every
|
|
5
|
+
* bound parameter into `error.message` — on a large failed query that is
|
|
6
|
+
* hundreds of kilobytes on one line of stderr (observed: ~900 KB per docs
|
|
7
|
+
* write on axiom-app, T13711). Warnings should carry the driver cause (the
|
|
8
|
+
* small, actionable "why"), never the parameter dump.
|
|
9
|
+
*
|
|
10
|
+
* Dependency-free; safe to import from any layer (lib barrel contract).
|
|
11
|
+
*
|
|
12
|
+
* @task T13711
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Format an unknown thrown value as a single-line, length-capped message.
|
|
16
|
+
*
|
|
17
|
+
* Prefers the deepest wrapped driver cause (the end of the `error.cause`
|
|
18
|
+
* chain) — the underlying SQLite/driver error, which does not contain the
|
|
19
|
+
* parameter dump — and falls back to the outer message. Everything from a
|
|
20
|
+
* drizzle `params:` section onward is cut, then whitespace runs collapse to
|
|
21
|
+
* single spaces so multi-line dumps cannot flood the log even before the cap.
|
|
22
|
+
*
|
|
23
|
+
* @param err - The thrown value (anything `throw`-able).
|
|
24
|
+
* @param maxLength - Character cap for the message body. Default 500.
|
|
25
|
+
* @returns A single-line message: at most `maxLength` characters of the
|
|
26
|
+
* message, followed (only when cut) by `… [truncated N chars]`, where N is
|
|
27
|
+
* the number of characters removed.
|
|
28
|
+
*/
|
|
29
|
+
export declare function boundedErrorMessage(err: unknown, maxLength?: number): string;
|
|
30
|
+
//# sourceMappingURL=error-message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-message.d.ts","sourceRoot":"","sources":["../../src/lib/error-message.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAWH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,GAAE,MAA2B,GAAG,MAAM,CAwBhG"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded, single-line formatting for thrown values in warnings and logs.
|
|
3
|
+
*
|
|
4
|
+
* Drizzle (and other query builders) interpolate the full SQL text and every
|
|
5
|
+
* bound parameter into `error.message` — on a large failed query that is
|
|
6
|
+
* hundreds of kilobytes on one line of stderr (observed: ~900 KB per docs
|
|
7
|
+
* write on axiom-app, T13711). Warnings should carry the driver cause (the
|
|
8
|
+
* small, actionable "why"), never the parameter dump.
|
|
9
|
+
*
|
|
10
|
+
* Dependency-free; safe to import from any layer (lib barrel contract).
|
|
11
|
+
*
|
|
12
|
+
* @task T13711
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Default cap for a formatted error message, in characters.
|
|
16
|
+
*
|
|
17
|
+
* Chosen to keep a full warning line well under typical log-pipeline line
|
|
18
|
+
* limits while preserving the actionable prefix (error class + SQLSTATE /
|
|
19
|
+
* SQLite extended code + short context).
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_MAX_LENGTH = 500;
|
|
22
|
+
/**
|
|
23
|
+
* Format an unknown thrown value as a single-line, length-capped message.
|
|
24
|
+
*
|
|
25
|
+
* Prefers the deepest wrapped driver cause (the end of the `error.cause`
|
|
26
|
+
* chain) — the underlying SQLite/driver error, which does not contain the
|
|
27
|
+
* parameter dump — and falls back to the outer message. Everything from a
|
|
28
|
+
* drizzle `params:` section onward is cut, then whitespace runs collapse to
|
|
29
|
+
* single spaces so multi-line dumps cannot flood the log even before the cap.
|
|
30
|
+
*
|
|
31
|
+
* @param err - The thrown value (anything `throw`-able).
|
|
32
|
+
* @param maxLength - Character cap for the message body. Default 500.
|
|
33
|
+
* @returns A single-line message: at most `maxLength` characters of the
|
|
34
|
+
* message, followed (only when cut) by `… [truncated N chars]`, where N is
|
|
35
|
+
* the number of characters removed.
|
|
36
|
+
*/
|
|
37
|
+
export function boundedErrorMessage(err, maxLength = DEFAULT_MAX_LENGTH) {
|
|
38
|
+
let deepest = err;
|
|
39
|
+
const seen = new Set([err]);
|
|
40
|
+
while (deepest instanceof Error && deepest.cause instanceof Error && !seen.has(deepest.cause)) {
|
|
41
|
+
deepest = deepest.cause;
|
|
42
|
+
seen.add(deepest);
|
|
43
|
+
}
|
|
44
|
+
let raw;
|
|
45
|
+
if (deepest instanceof Error) {
|
|
46
|
+
raw = deepest === err ? deepest.message : `${deepest.name}: ${deepest.message}`;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
try {
|
|
50
|
+
raw = String(deepest);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// String() throws for values with no toString, e.g. Object.create(null).
|
|
54
|
+
raw = '[unprintable thrown value]';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const singleLine = raw
|
|
58
|
+
.replace(/(?:\n| )params:[\s\S]*$/, '')
|
|
59
|
+
.replace(/\s+/g, ' ')
|
|
60
|
+
.trim();
|
|
61
|
+
if (singleLine.length <= maxLength)
|
|
62
|
+
return singleLine;
|
|
63
|
+
return `${singleLine.slice(0, maxLength)}… [truncated ${singleLine.length - maxLength} chars]`;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=error-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-message.js","sourceRoot":"","sources":["../../src/lib/error-message.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;GAMG;AACH,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY,EAAE,YAAoB,kBAAkB;IACtF,IAAI,OAAO,GAAG,GAAG,CAAC;IAClB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACrC,OAAO,OAAO,YAAY,KAAK,IAAI,OAAO,CAAC,KAAK,YAAY,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9F,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpB,CAAC;IACD,IAAI,GAAW,CAAC;IAChB,IAAI,OAAO,YAAY,KAAK,EAAE,CAAC;QAC7B,GAAG,GAAG,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;IAClF,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;YACzE,GAAG,GAAG,4BAA4B,CAAC;QACrC,CAAC;IACH,CAAC;IACD,MAAM,UAAU,GAAG,GAAG;SACnB,OAAO,CAAC,yBAAyB,EAAE,EAAE,CAAC;SACtC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;IACV,IAAI,UAAU,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,UAAU,CAAC;IACtD,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,gBAAgB,UAAU,CAAC,MAAM,GAAG,SAAS,SAAS,CAAC;AACjG,CAAC"}
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @module lib
|
|
8
8
|
*/
|
|
9
9
|
export { runWithConcurrency } from './concurrency.js';
|
|
10
|
+
export { boundedErrorMessage } from './error-message.js';
|
|
10
11
|
export { computeDelay, type RetryablePredicate, type RetryContext, type RetryOptions, withRetry, } from './retry.js';
|
|
11
12
|
export { detectLanguage, grammarPackage, SUPPORTED_EXTENSIONS, SUPPORTED_LANGUAGES, type TreeSitterLanguage, } from './tree-sitter-languages.js';
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EACL,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,kBAAkB,GACxB,MAAM,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EACL,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,kBAAkB,GACxB,MAAM,4BAA4B,CAAC"}
|
package/dist/lib/index.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @module lib
|
|
8
8
|
*/
|
|
9
9
|
export { runWithConcurrency } from './concurrency.js';
|
|
10
|
+
export { boundedErrorMessage } from './error-message.js';
|
|
10
11
|
export { computeDelay, withRetry, } from './retry.js';
|
|
11
12
|
export { detectLanguage, grammarPackage, SUPPORTED_EXTENSIONS, SUPPORTED_LANGUAGES, } from './tree-sitter-languages.js';
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
package/dist/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EACL,YAAY,EAIZ,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,mBAAmB,GAEpB,MAAM,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EACL,YAAY,EAIZ,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,mBAAmB,GAEpB,MAAM,4BAA4B,CAAC"}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Multi-credential pool storage for the CLEO LLM layer (T-LLM-CRED Phase 2).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
*
|
|
4
|
+
* The pool lives in the global `accounts` table (T13715), written through
|
|
5
|
+
* the encrypting accessor `store/llm-accounts-accessor.ts`: secrets are
|
|
6
|
+
* stored under the machine key and the pool syncs on the home stream.
|
|
7
|
+
*
|
|
8
|
+
* `~/.cleo/llm-credentials.json` (resolved via `getCleoHome()`) is a LEGACY
|
|
9
|
+
* source only: {@link migrateLegacyCredentialsFile} moves its entries into
|
|
10
|
+
* `accounts` once, idempotently, and removes the plaintext file after reading
|
|
11
|
+
* every entry back. Until then the sync reader also serves the file's entries
|
|
12
|
+
* that `accounts` lacks, so resolution stays correct mid-migration.
|
|
8
13
|
*
|
|
9
14
|
* ## Goals
|
|
10
15
|
*
|
|
11
16
|
* - Multiple credentials per provider (label → entry), each with priority,
|
|
12
17
|
* disabled-flag, expiry, and a free-form `metadata` bag.
|
|
13
|
-
* -
|
|
14
|
-
* - Cross-process locking via `proper-lockfile` reused through `withLock`.
|
|
15
|
-
* - Atomic writes via `writeJsonFileAtomic` (temp → rename + numbered backup).
|
|
18
|
+
* - Secrets encrypted at rest; never written in plaintext (T13715).
|
|
16
19
|
* - Synchronous read path so the resolver in `credentials.ts` can call it
|
|
17
20
|
* from its existing sync function.
|
|
18
21
|
*
|
|
@@ -139,10 +142,8 @@ export interface StoredCredential {
|
|
|
139
142
|
* that a re-add of the same `(provider, label)` pair does not silently
|
|
140
143
|
* drop an existing refresh token.
|
|
141
144
|
*
|
|
142
|
-
* Security:
|
|
143
|
-
*
|
|
144
|
-
* security-review note in the S-07 block above for the Phase 2 rationale
|
|
145
|
-
* for deferral and Phase 3 reintroduction.
|
|
145
|
+
* Security: encrypted at rest in `accounts.refresh_enc` under the same
|
|
146
|
+
* machine-key id as `accessToken` (T13715).
|
|
146
147
|
*
|
|
147
148
|
* @task T9266
|
|
148
149
|
*/
|
|
@@ -173,7 +174,8 @@ export interface CredentialsStoreData {
|
|
|
173
174
|
credentials: StoredCredential[];
|
|
174
175
|
}
|
|
175
176
|
/**
|
|
176
|
-
* Absolute path of the credential store file
|
|
177
|
+
* Absolute path of the LEGACY credential store file (migrated into the
|
|
178
|
+
* `accounts` table by {@link migrateLegacyCredentialsFile}, T13715).
|
|
177
179
|
*
|
|
178
180
|
* Resolved through `getCleoHome()` so XDG overrides apply uniformly with
|
|
179
181
|
* `credentials.ts`'s global-config tier.
|
|
@@ -183,6 +185,16 @@ export interface CredentialsStoreData {
|
|
|
183
185
|
export declare function credentialsStorePath(): string;
|
|
184
186
|
/** Internal test hook: reset the once-warned latch. */
|
|
185
187
|
export declare function _resetPermsWarningForTests(): void;
|
|
188
|
+
/**
|
|
189
|
+
* Move the LEGACY `llm-credentials.json` entries into the `accounts` table,
|
|
190
|
+
* once. Idempotent: an entry `accounts` already holds is kept as it is, and
|
|
191
|
+
* a missing file is a no-op. The plaintext file is removed only after every
|
|
192
|
+
* entry reads back from `accounts`; a malformed file is left in place.
|
|
193
|
+
*
|
|
194
|
+
* @returns How many entries were moved.
|
|
195
|
+
* @task T13715
|
|
196
|
+
*/
|
|
197
|
+
export declare function migrateLegacyCredentialsFile(): Promise<number>;
|
|
186
198
|
/**
|
|
187
199
|
* Type-narrow check used during read to drop malformed entries silently.
|
|
188
200
|
*
|
|
@@ -230,12 +242,13 @@ export declare function getCredentialByLabel(provider: ModelTransport, label: st
|
|
|
230
242
|
*
|
|
231
243
|
* - When `input.priority` is omitted, the new entry receives
|
|
232
244
|
* `max(existing priorities) + 10` so it ranks lowest by default.
|
|
233
|
-
* -
|
|
234
|
-
*
|
|
245
|
+
* - Stored in the `accounts` table with its secrets encrypted under the
|
|
246
|
+
* machine key (T13715).
|
|
235
247
|
*
|
|
236
|
-
* Returns the inserted (or replaced) entry as it now lives in the
|
|
248
|
+
* Returns the inserted (or replaced) entry as it now lives in the pool.
|
|
237
249
|
*
|
|
238
250
|
* @task T9257
|
|
251
|
+
* @task T13715
|
|
239
252
|
*/
|
|
240
253
|
export declare function addCredential(input: Omit<StoredCredential, 'priority'> & {
|
|
241
254
|
priority?: number;
|
|
@@ -244,7 +257,7 @@ export declare function addCredential(input: Omit<StoredCredential, 'priority'>
|
|
|
244
257
|
* Remove a credential by `(provider, label)`.
|
|
245
258
|
*
|
|
246
259
|
* Returns `true` when a matching entry was found and removed, `false`
|
|
247
|
-
* otherwise.
|
|
260
|
+
* otherwise. A legacy-file entry is migrated first, so it cannot reappear.
|
|
248
261
|
*
|
|
249
262
|
* @task T9257
|
|
250
263
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials-store.d.ts","sourceRoot":"","sources":["../../src/llm/credentials-store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"credentials-store.d.ts","sourceRoot":"","sources":["../../src/llm/credentials-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Multi-credential pool storage for the CLEO LLM layer (T-LLM-CRED Phase 2).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
*
|
|
4
|
+
* The pool lives in the global `accounts` table (T13715), written through
|
|
5
|
+
* the encrypting accessor `store/llm-accounts-accessor.ts`: secrets are
|
|
6
|
+
* stored under the machine key and the pool syncs on the home stream.
|
|
7
|
+
*
|
|
8
|
+
* `~/.cleo/llm-credentials.json` (resolved via `getCleoHome()`) is a LEGACY
|
|
9
|
+
* source only: {@link migrateLegacyCredentialsFile} moves its entries into
|
|
10
|
+
* `accounts` once, idempotently, and removes the plaintext file after reading
|
|
11
|
+
* every entry back. Until then the sync reader also serves the file's entries
|
|
12
|
+
* that `accounts` lacks, so resolution stays correct mid-migration.
|
|
8
13
|
*
|
|
9
14
|
* ## Goals
|
|
10
15
|
*
|
|
11
16
|
* - Multiple credentials per provider (label → entry), each with priority,
|
|
12
17
|
* disabled-flag, expiry, and a free-form `metadata` bag.
|
|
13
|
-
* -
|
|
14
|
-
* - Cross-process locking via `proper-lockfile` reused through `withLock`.
|
|
15
|
-
* - Atomic writes via `writeJsonFileAtomic` (temp → rename + numbered backup).
|
|
18
|
+
* - Secrets encrypted at rest; never written in plaintext (T13715).
|
|
16
19
|
* - Synchronous read path so the resolver in `credentials.ts` can call it
|
|
17
20
|
* from its existing sync function.
|
|
18
21
|
*
|
|
@@ -34,24 +37,23 @@
|
|
|
34
37
|
* @task T9257
|
|
35
38
|
* @epic T-LLM-CRED-CENTRALIZATION
|
|
36
39
|
*/
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
40
|
+
import { existsSync, readFileSync, statSync, unlinkSync } from 'node:fs';
|
|
41
|
+
import { join } from 'node:path';
|
|
39
42
|
// Note: `credentials.ts` already depends on `credentials-store.ts`
|
|
40
43
|
// (`pickCredentialForProviderSync`). The reverse arrow here closes a
|
|
41
44
|
// cycle, but every imported symbol is a function reference read at
|
|
42
45
|
// call-time — ESM's late binding makes the cycle safe.
|
|
43
46
|
import { getCleoHome } from '@cleocode/paths';
|
|
44
47
|
import { getLogger } from '../logger.js';
|
|
45
|
-
|
|
46
|
-
// piggy-back on it for atomic writes + numbered backups.
|
|
47
|
-
import { withLock } from '../store/file-utils.js';
|
|
48
|
+
import { deleteLlmAccount, listLlmAccountRowsSync, llmAccountToCredentialSync, upsertLlmAccount, } from '../store/llm-accounts-accessor.js';
|
|
48
49
|
import { clearAnthropicKeyCache } from './credentials.js';
|
|
49
50
|
const logger = getLogger('llm-credentials-store');
|
|
50
51
|
// ---------------------------------------------------------------------------
|
|
51
52
|
// Paths + chmod
|
|
52
53
|
// ---------------------------------------------------------------------------
|
|
53
54
|
/**
|
|
54
|
-
* Absolute path of the credential store file
|
|
55
|
+
* Absolute path of the LEGACY credential store file (migrated into the
|
|
56
|
+
* `accounts` table by {@link migrateLegacyCredentialsFile}, T13715).
|
|
55
57
|
*
|
|
56
58
|
* Resolved through `getCleoHome()` so XDG overrides apply uniformly with
|
|
57
59
|
* `credentials.ts`'s global-config tier.
|
|
@@ -61,53 +63,6 @@ const logger = getLogger('llm-credentials-store');
|
|
|
61
63
|
export function credentialsStorePath() {
|
|
62
64
|
return join(getCleoHome(), 'llm-credentials.json');
|
|
63
65
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Ensure the store file exists with a parseable empty-store body.
|
|
66
|
-
*
|
|
67
|
-
* `withLock` (file-utils) writes a zero-byte file when none exists so
|
|
68
|
-
* `proper-lockfile` can hold the lock. The subsequent `readJsonFile` then
|
|
69
|
-
* throws SyntaxError on the empty content. To avoid that, we pre-seed the
|
|
70
|
-
* file with `{"version":1,"defaultStrategy":"priorityWithFallback",
|
|
71
|
-
* "credentials":[]}` BEFORE acquiring the lock. The pre-seed is itself
|
|
72
|
-
* race-tolerant: `writeFileSync({flag:'wx'})` makes creation idempotent
|
|
73
|
-
* across concurrent callers — only the first one writes; the rest get
|
|
74
|
-
* EEXIST and continue.
|
|
75
|
-
*
|
|
76
|
-
* @task T9257
|
|
77
|
-
*/
|
|
78
|
-
function ensureFileInitialized(path) {
|
|
79
|
-
if (existsSync(path))
|
|
80
|
-
return;
|
|
81
|
-
const dir = dirname(path);
|
|
82
|
-
if (!existsSync(dir)) {
|
|
83
|
-
// 0o700 so other UIDs cannot enumerate the .backups/ subdir contents
|
|
84
|
-
// even though every individual file in it is 0o600.
|
|
85
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
// Existing dir created at a looser mode (e.g. 0o755 from an earlier
|
|
89
|
-
// CLEO release): tighten on first write. Best-effort; ignore failures
|
|
90
|
-
// (Windows, mount-points, NFS without chmod support).
|
|
91
|
-
try {
|
|
92
|
-
chmodSync(dir, 0o700);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
/* non-fatal */
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
writeFileSync(path, `${JSON.stringify(emptyStore(), null, 2)}\n`, {
|
|
100
|
-
encoding: 'utf-8',
|
|
101
|
-
flag: 'wx',
|
|
102
|
-
mode: 0o600,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
// EEXIST → another caller already initialized it; OK.
|
|
107
|
-
if (err.code !== 'EEXIST')
|
|
108
|
-
throw err;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
66
|
/**
|
|
112
67
|
* S-02 (CWE-367 TOCTOU rename→chmod): the previous `enforce0600(path)`
|
|
113
68
|
* helper used to chmod the live file AFTER `withLock` released its
|
|
@@ -161,21 +116,22 @@ function emptyStore() {
|
|
|
161
116
|
return { version: 1, defaultStrategy: 'priorityWithFallback', credentials: [] };
|
|
162
117
|
}
|
|
163
118
|
/**
|
|
164
|
-
* Synchronous
|
|
119
|
+
* Synchronous read + parse of the LEGACY file. Never throws.
|
|
165
120
|
*
|
|
166
|
-
* Returns an empty store when the file is missing
|
|
167
|
-
* or has the wrong `version
|
|
121
|
+
* Returns an empty store when the file is missing or blank, and `null` when
|
|
122
|
+
* it is unreadable, malformed, or has the wrong `version` — such a file is
|
|
123
|
+
* never migrated away.
|
|
168
124
|
*
|
|
169
125
|
* @task T9257
|
|
170
126
|
*/
|
|
171
|
-
function
|
|
127
|
+
function readLegacyFileSync() {
|
|
172
128
|
const path = credentialsStorePath();
|
|
173
129
|
let raw;
|
|
174
130
|
try {
|
|
175
131
|
raw = readFileSync(path, 'utf-8');
|
|
176
132
|
}
|
|
177
|
-
catch {
|
|
178
|
-
return emptyStore();
|
|
133
|
+
catch (err) {
|
|
134
|
+
return err.code === 'ENOENT' ? emptyStore() : null;
|
|
179
135
|
}
|
|
180
136
|
checkPermsOnRead(path);
|
|
181
137
|
if (!raw.trim())
|
|
@@ -183,7 +139,7 @@ function readStoreSync() {
|
|
|
183
139
|
try {
|
|
184
140
|
const parsed = JSON.parse(raw);
|
|
185
141
|
if (parsed.version !== 1 || !Array.isArray(parsed.credentials)) {
|
|
186
|
-
return
|
|
142
|
+
return null;
|
|
187
143
|
}
|
|
188
144
|
return {
|
|
189
145
|
version: 1,
|
|
@@ -192,8 +148,96 @@ function readStoreSync() {
|
|
|
192
148
|
};
|
|
193
149
|
}
|
|
194
150
|
catch {
|
|
195
|
-
return
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** The `(provider, label)` key of a credential. */
|
|
155
|
+
function keyOf(c) {
|
|
156
|
+
return `${c.provider}\u0000${c.label}`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The pool's credentials in the `accounts` table, decrypted. Never throws: a
|
|
160
|
+
* row this machine's key cannot decrypt (a restored or copied store) is left
|
|
161
|
+
* out and logged by `(provider, label)` only.
|
|
162
|
+
*
|
|
163
|
+
* @task T13715
|
|
164
|
+
*/
|
|
165
|
+
function readAccountsSync() {
|
|
166
|
+
let rows;
|
|
167
|
+
try {
|
|
168
|
+
rows = listLlmAccountRowsSync();
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
logger.warn({ err: err.message }, 'llm-credentials-store: accounts unreadable');
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
const out = [];
|
|
175
|
+
for (const row of rows) {
|
|
176
|
+
try {
|
|
177
|
+
const credential = llmAccountToCredentialSync(row);
|
|
178
|
+
if (credential !== null)
|
|
179
|
+
out.push(credential);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
logger.warn({ provider: row.provider, label: row.label }, "llm-credentials-store: this machine's key cannot decrypt the credential; re-add it");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The pool: every `accounts` credential, plus legacy-file entries `accounts`
|
|
189
|
+
* does not hold yet (an unmigrated file). Never throws.
|
|
190
|
+
*
|
|
191
|
+
* @task T13715
|
|
192
|
+
*/
|
|
193
|
+
function readStoreSync() {
|
|
194
|
+
const accounts = readAccountsSync();
|
|
195
|
+
const legacy = readLegacyFileSync() ?? emptyStore();
|
|
196
|
+
const have = new Set(accounts.map(keyOf));
|
|
197
|
+
return {
|
|
198
|
+
version: 1,
|
|
199
|
+
defaultStrategy: legacy.defaultStrategy,
|
|
200
|
+
credentials: [...accounts, ...legacy.credentials.filter((c) => !have.has(keyOf(c)))],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Move the LEGACY `llm-credentials.json` entries into the `accounts` table,
|
|
205
|
+
* once. Idempotent: an entry `accounts` already holds is kept as it is, and
|
|
206
|
+
* a missing file is a no-op. The plaintext file is removed only after every
|
|
207
|
+
* entry reads back from `accounts`; a malformed file is left in place.
|
|
208
|
+
*
|
|
209
|
+
* @returns How many entries were moved.
|
|
210
|
+
* @task T13715
|
|
211
|
+
*/
|
|
212
|
+
export async function migrateLegacyCredentialsFile() {
|
|
213
|
+
const path = credentialsStorePath();
|
|
214
|
+
if (!existsSync(path))
|
|
215
|
+
return 0;
|
|
216
|
+
const legacy = readLegacyFileSync();
|
|
217
|
+
if (legacy === null) {
|
|
218
|
+
logger.warn({ path }, 'llm-credentials-store: legacy file unreadable; left in place');
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
const have = new Set(readAccountsSync().map(keyOf));
|
|
222
|
+
const missing = legacy.credentials.filter((c) => !have.has(keyOf(c)));
|
|
223
|
+
for (const credential of missing)
|
|
224
|
+
await upsertLlmAccount(credential);
|
|
225
|
+
const after = new Set(readAccountsSync().map(keyOf));
|
|
226
|
+
if (!legacy.credentials.every((c) => after.has(keyOf(c)))) {
|
|
227
|
+
logger.warn({ path }, 'llm-credentials-store: migration incomplete; legacy file kept');
|
|
228
|
+
return missing.length;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
unlinkSync(path);
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
// Another process finished the same migration first.
|
|
235
|
+
if (err.code !== 'ENOENT')
|
|
236
|
+
throw err;
|
|
196
237
|
}
|
|
238
|
+
if (missing.length > 0)
|
|
239
|
+
clearAnthropicKeyCache();
|
|
240
|
+
return missing.length;
|
|
197
241
|
}
|
|
198
242
|
/**
|
|
199
243
|
* Type-narrow check used during read to drop malformed entries silently.
|
|
@@ -316,6 +360,18 @@ export function pickCredentialForProviderSync(provider, opts = {}) {
|
|
|
316
360
|
// ---------------------------------------------------------------------------
|
|
317
361
|
// Public async API
|
|
318
362
|
// ---------------------------------------------------------------------------
|
|
363
|
+
/**
|
|
364
|
+
* {@link migrateLegacyCredentialsFile} for read paths: a failed migration is
|
|
365
|
+
* logged, never thrown, because the reader still serves the file's entries.
|
|
366
|
+
*/
|
|
367
|
+
async function migrateForRead() {
|
|
368
|
+
try {
|
|
369
|
+
await migrateLegacyCredentialsFile();
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
logger.warn({ err: err.message }, 'llm-credentials-store: migration deferred');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
319
375
|
/**
|
|
320
376
|
* List credentials in the pool, optionally filtered to a single provider.
|
|
321
377
|
*
|
|
@@ -324,6 +380,7 @@ export function pickCredentialForProviderSync(provider, opts = {}) {
|
|
|
324
380
|
* @task T9257
|
|
325
381
|
*/
|
|
326
382
|
export async function listCredentials(provider) {
|
|
383
|
+
await migrateForRead();
|
|
327
384
|
const data = readStoreSync();
|
|
328
385
|
if (!provider)
|
|
329
386
|
return [...data.credentials];
|
|
@@ -336,6 +393,7 @@ export async function listCredentials(provider) {
|
|
|
336
393
|
* @task T9257
|
|
337
394
|
*/
|
|
338
395
|
export async function getCredentialByLabel(provider, label) {
|
|
396
|
+
await migrateForRead();
|
|
339
397
|
const data = readStoreSync();
|
|
340
398
|
return data.credentials.find((c) => c.provider === provider && c.label === label) ?? null;
|
|
341
399
|
}
|
|
@@ -344,92 +402,52 @@ export async function getCredentialByLabel(provider, label) {
|
|
|
344
402
|
*
|
|
345
403
|
* - When `input.priority` is omitted, the new entry receives
|
|
346
404
|
* `max(existing priorities) + 10` so it ranks lowest by default.
|
|
347
|
-
* -
|
|
348
|
-
*
|
|
405
|
+
* - Stored in the `accounts` table with its secrets encrypted under the
|
|
406
|
+
* machine key (T13715).
|
|
349
407
|
*
|
|
350
|
-
* Returns the inserted (or replaced) entry as it now lives in the
|
|
408
|
+
* Returns the inserted (or replaced) entry as it now lives in the pool.
|
|
351
409
|
*
|
|
352
410
|
* @task T9257
|
|
411
|
+
* @task T13715
|
|
353
412
|
*/
|
|
354
413
|
export async function addCredential(input) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
extraHeaders: input.extraHeaders,
|
|
379
|
-
metadata: input.metadata,
|
|
380
|
-
lastStatus: input.lastStatus,
|
|
381
|
-
lastErrorCode: input.lastErrorCode,
|
|
382
|
-
lastErrorResetAt: input.lastErrorResetAt,
|
|
383
|
-
requestCount: input.requestCount,
|
|
384
|
-
disabled: input.disabled ?? false,
|
|
385
|
-
...(input.refreshToken !== undefined && { refreshToken: input.refreshToken }),
|
|
386
|
-
};
|
|
387
|
-
data.credentials = [...remaining, next];
|
|
388
|
-
inserted = next;
|
|
389
|
-
return data;
|
|
390
|
-
},
|
|
391
|
-
// SECURITY (S-01/S-02): mode is plumbed all the way through to the
|
|
392
|
-
// temp file + rotated backup writes so the file never touches disk
|
|
393
|
-
// at a looser permission, even momentarily, and historical copies
|
|
394
|
-
// under .backups/ are equally locked down.
|
|
395
|
-
{ mode: 0o600 });
|
|
396
|
-
// `inserted` is set inside the transform closure before withLock returns.
|
|
397
|
-
if (!inserted) {
|
|
398
|
-
throw new Error('credentials-store: invariant violation — insert was not recorded');
|
|
399
|
-
}
|
|
414
|
+
await migrateLegacyCredentialsFile();
|
|
415
|
+
const remaining = readStoreSync().credentials.filter((c) => !(c.provider === input.provider && c.label === input.label));
|
|
416
|
+
const maxPriority = remaining.reduce((m, c) => (c.priority > m ? c.priority : m), -10);
|
|
417
|
+
const priority = typeof input.priority === 'number' ? input.priority : maxPriority + 10;
|
|
418
|
+
const next = {
|
|
419
|
+
provider: input.provider,
|
|
420
|
+
label: input.label,
|
|
421
|
+
authType: input.authType,
|
|
422
|
+
accessToken: input.accessToken,
|
|
423
|
+
expiresAt: input.expiresAt ?? null,
|
|
424
|
+
priority,
|
|
425
|
+
source: input.source,
|
|
426
|
+
baseUrl: input.baseUrl ?? null,
|
|
427
|
+
extraHeaders: input.extraHeaders,
|
|
428
|
+
metadata: input.metadata,
|
|
429
|
+
lastStatus: input.lastStatus,
|
|
430
|
+
lastErrorCode: input.lastErrorCode,
|
|
431
|
+
lastErrorResetAt: input.lastErrorResetAt,
|
|
432
|
+
requestCount: input.requestCount,
|
|
433
|
+
disabled: input.disabled ?? false,
|
|
434
|
+
...(input.refreshToken !== undefined && { refreshToken: input.refreshToken }),
|
|
435
|
+
};
|
|
436
|
+
await upsertLlmAccount(next);
|
|
400
437
|
clearAnthropicKeyCache();
|
|
401
|
-
return
|
|
438
|
+
return next;
|
|
402
439
|
}
|
|
403
440
|
/**
|
|
404
441
|
* Remove a credential by `(provider, label)`.
|
|
405
442
|
*
|
|
406
443
|
* Returns `true` when a matching entry was found and removed, `false`
|
|
407
|
-
* otherwise.
|
|
444
|
+
* otherwise. A legacy-file entry is migrated first, so it cannot reappear.
|
|
408
445
|
*
|
|
409
446
|
* @task T9257
|
|
410
447
|
*/
|
|
411
448
|
export async function removeCredential(provider, label) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const snapshot = readStoreSync();
|
|
415
|
-
if (!snapshot.credentials.some((c) => c.provider === provider && c.label === label)) {
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
ensureFileInitialized(path);
|
|
419
|
-
let removed = false;
|
|
420
|
-
await withLock(path, (current) => {
|
|
421
|
-
const data = current ?? emptyStore();
|
|
422
|
-
if (data.version !== 1 || !Array.isArray(data.credentials)) {
|
|
423
|
-
return emptyStore();
|
|
424
|
-
}
|
|
425
|
-
const before = data.credentials.length;
|
|
426
|
-
data.credentials = data.credentials.filter((c) => !(c.provider === provider && c.label === label));
|
|
427
|
-
removed = data.credentials.length < before;
|
|
428
|
-
data.defaultStrategy ??= 'priorityWithFallback';
|
|
429
|
-
return data;
|
|
430
|
-
},
|
|
431
|
-
// SECURITY (S-01/S-02): see addCredential for rationale.
|
|
432
|
-
{ mode: 0o600 });
|
|
449
|
+
await migrateLegacyCredentialsFile();
|
|
450
|
+
const removed = await deleteLlmAccount(provider, label);
|
|
433
451
|
// S-10: invalidate the Anthropic-key cache so a removed entry stops
|
|
434
452
|
// being served by the resolver in the same process.
|
|
435
453
|
if (removed)
|
|
@@ -445,6 +463,7 @@ export async function removeCredential(provider, label) {
|
|
|
445
463
|
* @task T9257
|
|
446
464
|
*/
|
|
447
465
|
export async function pickCredentialForProvider(provider, opts = {}) {
|
|
466
|
+
await migrateForRead();
|
|
448
467
|
return pickCredentialForProviderSync(provider, opts);
|
|
449
468
|
}
|
|
450
469
|
//# sourceMappingURL=credentials-store.js.map
|