total-recall-brain 3.13.0 โ 3.13.2
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 +91 -108
- package/bin/antigravity.mjs +81 -52
- package/bin/total-recall.mjs +32 -57
- package/extension/background.js +1 -1
- package/extension/options/options.js +9 -3
- package/extension/sidepanel/sidepanel.js +0 -2
- package/frontend/index.html +4 -3
- package/frontend/package.json +1 -0
- package/frontend/src/App.tsx +109 -152
- package/frontend/src/api.ts +605 -23
- package/frontend/src/components/BrainSelector.tsx +211 -199
- package/frontend/src/components/DocumentEditorModal.tsx +57 -0
- package/frontend/src/components/DocumentTable.tsx +47 -0
- package/frontend/src/components/Graph3D.tsx +120 -71
- package/frontend/src/components/UsageChart.tsx +3 -3
- package/frontend/src/components/brand/BrandMark.tsx +95 -0
- package/frontend/src/index.css +286 -86
- package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
- package/frontend/src/pages/ChatPage.tsx +103 -45
- package/frontend/src/pages/DesignDocsPage.tsx +5 -5
- package/frontend/src/pages/FilesPage.tsx +10 -6
- package/frontend/src/pages/GraphPage.tsx +5 -3
- package/frontend/src/pages/HelpPage.tsx +2 -2
- package/frontend/src/pages/InboxPage.tsx +82 -0
- package/frontend/src/pages/InstructionsPage.tsx +1 -1
- package/frontend/src/pages/IntegrationsPage.tsx +21 -10
- package/frontend/src/pages/LoginPage.tsx +95 -77
- package/frontend/src/pages/MemoryPage.tsx +31 -8
- package/frontend/src/pages/OnboardingPage.tsx +690 -0
- package/frontend/src/pages/OpenWikiPage.tsx +412 -323
- package/frontend/src/pages/SettingsPage.tsx +825 -922
- package/frontend/src/pages/SkillsPage.tsx +272 -147
- package/frontend/src/pages/UsagePage.tsx +23 -31
- package/frontend/src/pages/VaultPage.tsx +223 -0
- package/frontend/src/types.ts +13 -0
- package/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
- package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
- package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents โ modules/agents}/agents.yml +8 -0
- package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
- package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
- package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
- package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
- package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
- package/src/cli/brain.mjs +63 -5
- package/src/cli/connect.mjs +11 -19
- package/src/cli/daemon.mjs +5 -4
- package/src/cli/deploy-ui.mjs +8 -3
- package/src/cli/deploy.mjs +84 -27
- package/src/cli/ingest/index.mjs +1 -1
- package/src/cli/init.mjs +63 -34
- package/src/cli/remember.mjs +3 -3
- package/src/cli/secret.mjs +812 -0
- package/src/cli/setup.mjs +34 -7
- package/src/cli/share.mjs +2 -2
- package/src/cli/skill.mjs +396 -35
- package/src/cli/sync.mjs +4 -4
- package/src/cli/task.mjs +287 -0
- package/src/core/clarity-rewriter.mjs +5 -5
- package/src/core/config.mjs +44 -2
- package/src/core/conflict-detector.mjs +5 -5
- package/src/core/crons.mjs +54 -0
- package/src/core/daemon-loop.mjs +87 -235
- package/src/core/dream.mjs +170 -10
- package/src/core/env-import.mjs +456 -0
- package/src/core/fact-seeker.mjs +14 -14
- package/src/core/import-rules.mjs +20 -7
- package/src/core/inference-engine.mjs +3 -3
- package/src/core/migrate.mjs +14 -8
- package/src/core/okf-adapter.mjs +2 -2
- package/src/core/operation-validator.mjs +39 -331
- package/src/core/optimizer.mjs +7 -0
- package/src/core/post-mortem.mjs +2 -2
- package/src/core/project-brain.mjs +349 -0
- package/src/core/provider-catalog.mjs +296 -0
- package/src/core/quick-capture.mjs +1 -1
- package/src/core/remote-vault-sync.mjs +194 -0
- package/src/core/research-queue.mjs +7 -11
- package/src/core/runtime.mjs +33 -9
- package/src/core/scheduler.mjs +59 -5
- package/src/core/schema.mjs +14 -0
- package/src/core/search.mjs +1 -1
- package/src/core/secrets-env-export.mjs +323 -0
- package/src/core/secrets-rotate.mjs +245 -0
- package/src/core/secrets-store.mjs +645 -0
- package/src/core/semantic-index.mjs +3 -3
- package/src/core/skills-registry.mjs +1062 -0
- package/src/core/source-adapters.mjs +4 -1
- package/src/core/ssss-host-extension.mjs +94 -0
- package/src/core/ssss-kernel-bridge.mjs +530 -0
- package/src/core/surface.mjs +14 -0
- package/src/core/task-envelope.mjs +298 -0
- package/src/core/task-executors.mjs +600 -0
- package/src/core/validated-write.mjs +124 -56
- package/src/core/vault.mjs +21 -34
- package/src/core/webauthn-store.mjs +272 -0
- package/src/server/api.mjs +26 -16
- package/src/server/auth.mjs +51 -0
- package/src/server/index.mjs +3 -3
- package/src/server/keys.mjs +30 -4
- package/src/server/rest.mjs +198 -25
- package/src/server/route-manifest.json +156 -0
- package/src/server/routes/_shared.mjs +40 -1
- package/src/server/routes/docs.mjs +260 -0
- package/src/server/routes/memory.mjs +145 -32
- package/src/server/routes/research.mjs +30 -0
- package/src/server/routes/secrets.mjs +470 -0
- package/src/server/routes/share.mjs +2 -2
- package/src/server/routes/skills.mjs +452 -54
- package/src/server/routes/sync.mjs +48 -0
- package/src/server/routes/webauthn.mjs +184 -0
- package/src/server/update-route-manifest.mjs +39 -0
- package/templates/onboarding-interview.md +32 -22
- package/templates/openwiki/README.md +10 -0
- package/templates/openwiki/architecture.md +5 -0
- package/templates/openwiki/ide.md +10 -0
- package/templates/openwiki/memory.md +11 -0
- package/templates/openwiki/secrets.md +7 -0
- package/templates/openwiki/skills.md +7 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
- /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals โ modules/skill-deploy/scripts}/.gitkeep +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill โ modules/skill-deploy}/scripts/find-skills.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill โ modules/skill-deploy}/scripts/install-skill.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill โ modules/skill-deploy}/scripts/scan-skill.mjs +0 -0
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* total-recall secret โ manage API keys and credentials (not in the memory vault).
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* npx total-recall secret set <key> <value> [--provider name] [--scope global|project]
|
|
6
|
+
* npx total-recall secret get <key> # prints value (use carefully)
|
|
7
|
+
* npx total-recall secret list
|
|
8
|
+
* npx total-recall secret rotate <key> <new-value>
|
|
9
|
+
* npx total-recall secret delete <key>
|
|
10
|
+
* npx total-recall secret audit [--limit N]
|
|
11
|
+
* npx total-recall secret usage [--days N]
|
|
12
|
+
* npx total-recall secret usage-record --provider x --cost 0.01
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
setSecret,
|
|
17
|
+
getSecret,
|
|
18
|
+
listSecretsMeta,
|
|
19
|
+
rotateSecret,
|
|
20
|
+
deleteSecret,
|
|
21
|
+
readSecretAudit,
|
|
22
|
+
recordUsage,
|
|
23
|
+
summarizeUsage,
|
|
24
|
+
summarizeUsageByKey,
|
|
25
|
+
loadBudgetConfig,
|
|
26
|
+
resolveSecretsPath,
|
|
27
|
+
textContainsSecrets,
|
|
28
|
+
getSecretsCatalog,
|
|
29
|
+
updateSecretMeta,
|
|
30
|
+
listRotationDue,
|
|
31
|
+
} from '../core/secrets-store.mjs';
|
|
32
|
+
import {
|
|
33
|
+
scanEnvSources,
|
|
34
|
+
publicScanResult,
|
|
35
|
+
importEnvSecrets,
|
|
36
|
+
parseEnvText,
|
|
37
|
+
inferProvider,
|
|
38
|
+
} from '../core/env-import.mjs';
|
|
39
|
+
import {
|
|
40
|
+
exportEnvToProject,
|
|
41
|
+
exportEnvToRegistry,
|
|
42
|
+
buildEnvProjection,
|
|
43
|
+
buildDeploySecretsPayload,
|
|
44
|
+
} from '../core/secrets-env-export.mjs';
|
|
45
|
+
import {
|
|
46
|
+
rotateSecretAndExport,
|
|
47
|
+
enqueueRotationDueTasks,
|
|
48
|
+
getBrowserRotateAssist,
|
|
49
|
+
} from '../core/secrets-rotate.mjs';
|
|
50
|
+
import { listProviders, getProvider } from '../core/provider-catalog.mjs';
|
|
51
|
+
import { resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
52
|
+
import fs from 'node:fs';
|
|
53
|
+
import path from 'node:path';
|
|
54
|
+
|
|
55
|
+
function printHelp() {
|
|
56
|
+
console.log(`
|
|
57
|
+
total-recall secret โ Secrets store (separate from memory vault)
|
|
58
|
+
|
|
59
|
+
Usage: total-recall secret <command> [options]
|
|
60
|
+
|
|
61
|
+
Commands:
|
|
62
|
+
set <key> <value> Store a secret (0600 file; optional AES via TR_SECRETS_PASSWORD)
|
|
63
|
+
get <key> Print secret value (audited)
|
|
64
|
+
list List keys + metadata only (no values)
|
|
65
|
+
rotate <key> <value> Replace value and mark rotated
|
|
66
|
+
delete <key> Remove a secret
|
|
67
|
+
audit [--limit N] Recent set/get/rotate events (no values)
|
|
68
|
+
usage [--days N] Sum usage.jsonl for last N days (default 1)
|
|
69
|
+
usage-record Append a usage event (see flags)
|
|
70
|
+
check-surfaces Fail if any secret value appears in INSTRUCTIONS.md / openwiki
|
|
71
|
+
export-env Write .env FROM secrets store (SSOT โ repo/deploy projection)
|
|
72
|
+
--path <dir> Target repo root (default: cwd)
|
|
73
|
+
--all-projects Export to every project-registry path
|
|
74
|
+
--filename .env Output name (default .env)
|
|
75
|
+
--no-example Skip .env.example
|
|
76
|
+
--no-global Only keys bound to this repo (skip unbound)
|
|
77
|
+
--replace-all Overwrite entire .env (default: merge TR block only)
|
|
78
|
+
--dry-run Show keys that would be written
|
|
79
|
+
import-env One-time migrate: scan local .env โ store (not the steady-state path)
|
|
80
|
+
--all / --file / --overwrite / --dry-run
|
|
81
|
+
catalog Full catalog: keys, providers, usage, rotation, cost
|
|
82
|
+
providers List provider registry (docs/schema/tiers)
|
|
83
|
+
meta <key> Update metadata (no value change)
|
|
84
|
+
--repo <name> Bind to exactly ONE product repo (not multi)
|
|
85
|
+
--repos <name> Same as --repo (single name only; multi rejected)
|
|
86
|
+
--tier <id> Subscription tier
|
|
87
|
+
--monthly-cost <n> Planned monthly $
|
|
88
|
+
--monthly-cap <n> Cap $
|
|
89
|
+
--docs <url> API docs URL
|
|
90
|
+
--rotate-days <n> Auto-rotation interval
|
|
91
|
+
--auto-rotate Enable auto-rotate flag
|
|
92
|
+
--notes <text>
|
|
93
|
+
--label <text>
|
|
94
|
+
--project <path>
|
|
95
|
+
rotation-due List keys overdue for rotation
|
|
96
|
+
--enqueue Create daemon tasks + browser-use prompts for each due key
|
|
97
|
+
rotate-browser <key> Print supervised browser-use rotation instructions
|
|
98
|
+
usage Cost/events (add --key-ref for per-key)
|
|
99
|
+
|
|
100
|
+
rotate options:
|
|
101
|
+
--export-env After rotate, write .env to bound repos (and cwd if unbound)
|
|
102
|
+
--export-all After rotate, export-env to all project-registry paths
|
|
103
|
+
|
|
104
|
+
set / rotate options:
|
|
105
|
+
--provider <name> Optional provider tag (e.g. openai, anthropic)
|
|
106
|
+
--scope global|project
|
|
107
|
+
--repo / --repos Bind secret to repo(s)
|
|
108
|
+
--tier / --monthly-cost / --rotate-days
|
|
109
|
+
|
|
110
|
+
usage-record options:
|
|
111
|
+
--provider <name>
|
|
112
|
+
--model <id>
|
|
113
|
+
--input-tokens <n>
|
|
114
|
+
--output-tokens <n>
|
|
115
|
+
--cost <usd>
|
|
116
|
+
--key-ref <secret-name> name only, never the value
|
|
117
|
+
|
|
118
|
+
Env:
|
|
119
|
+
TR_SECRETS_PASSWORD If set, encrypt secrets.enc with AES-256-GCM
|
|
120
|
+
|
|
121
|
+
Never put secrets in remember/vault/openwiki/instruction shims.
|
|
122
|
+
`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseArgs(args) {
|
|
126
|
+
const out = {
|
|
127
|
+
command: null,
|
|
128
|
+
key: null,
|
|
129
|
+
value: null,
|
|
130
|
+
provider: null,
|
|
131
|
+
scope: 'global',
|
|
132
|
+
limit: 50,
|
|
133
|
+
days: 1,
|
|
134
|
+
model: null,
|
|
135
|
+
input_tokens: 0,
|
|
136
|
+
output_tokens: 0,
|
|
137
|
+
cost: null,
|
|
138
|
+
key_ref: null,
|
|
139
|
+
help: false,
|
|
140
|
+
all: false,
|
|
141
|
+
dryRun: false,
|
|
142
|
+
overwrite: false,
|
|
143
|
+
file: null,
|
|
144
|
+
keys: [],
|
|
145
|
+
repos: [],
|
|
146
|
+
tier: null,
|
|
147
|
+
monthly_cost: null,
|
|
148
|
+
monthly_cap: null,
|
|
149
|
+
docs: null,
|
|
150
|
+
rotate_days: null,
|
|
151
|
+
auto_rotate: false,
|
|
152
|
+
notes: null,
|
|
153
|
+
label: null,
|
|
154
|
+
project: null,
|
|
155
|
+
path: null,
|
|
156
|
+
allProjects: false,
|
|
157
|
+
filename: '.env',
|
|
158
|
+
noExample: false,
|
|
159
|
+
noGlobal: false,
|
|
160
|
+
replaceAll: false,
|
|
161
|
+
exportEnv: false,
|
|
162
|
+
exportAll: false,
|
|
163
|
+
enqueue: false,
|
|
164
|
+
};
|
|
165
|
+
if (!args.length || args[0] === '--help' || args[0] === '-h') {
|
|
166
|
+
out.help = true;
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
out.command = args[0];
|
|
170
|
+
let i = 1;
|
|
171
|
+
if (
|
|
172
|
+
['set', 'get', 'rotate', 'delete', 'meta', 'rotate-browser', 'rm'].includes(out.command) &&
|
|
173
|
+
args[i] &&
|
|
174
|
+
!args[i].startsWith('--')
|
|
175
|
+
) {
|
|
176
|
+
out.key = args[i++];
|
|
177
|
+
}
|
|
178
|
+
if (['set', 'rotate'].includes(out.command) && args[i] && !args[i].startsWith('--')) {
|
|
179
|
+
out.value = args[i++];
|
|
180
|
+
}
|
|
181
|
+
while (i < args.length) {
|
|
182
|
+
const a = args[i];
|
|
183
|
+
switch (a) {
|
|
184
|
+
case '--provider':
|
|
185
|
+
out.provider = args[++i];
|
|
186
|
+
break;
|
|
187
|
+
case '--scope':
|
|
188
|
+
out.scope = args[++i];
|
|
189
|
+
break;
|
|
190
|
+
case '--limit':
|
|
191
|
+
out.limit = parseInt(args[++i], 10) || 50;
|
|
192
|
+
break;
|
|
193
|
+
case '--days':
|
|
194
|
+
out.days = parseInt(args[++i], 10) || 1;
|
|
195
|
+
break;
|
|
196
|
+
case '--model':
|
|
197
|
+
out.model = args[++i];
|
|
198
|
+
break;
|
|
199
|
+
case '--input-tokens':
|
|
200
|
+
out.input_tokens = parseInt(args[++i], 10) || 0;
|
|
201
|
+
break;
|
|
202
|
+
case '--output-tokens':
|
|
203
|
+
out.output_tokens = parseInt(args[++i], 10) || 0;
|
|
204
|
+
break;
|
|
205
|
+
case '--cost':
|
|
206
|
+
out.cost = parseFloat(args[++i]);
|
|
207
|
+
break;
|
|
208
|
+
case '--key-ref':
|
|
209
|
+
out.key_ref = args[++i];
|
|
210
|
+
break;
|
|
211
|
+
case '--all':
|
|
212
|
+
out.all = true;
|
|
213
|
+
break;
|
|
214
|
+
case '--dry-run':
|
|
215
|
+
out.dryRun = true;
|
|
216
|
+
break;
|
|
217
|
+
case '--overwrite':
|
|
218
|
+
out.overwrite = true;
|
|
219
|
+
break;
|
|
220
|
+
case '--file':
|
|
221
|
+
out.file = args[++i];
|
|
222
|
+
break;
|
|
223
|
+
case '--repo':
|
|
224
|
+
out.repos.push(args[++i]);
|
|
225
|
+
break;
|
|
226
|
+
case '--repos':
|
|
227
|
+
out.repos.push(
|
|
228
|
+
...String(args[++i] || '')
|
|
229
|
+
.split(',')
|
|
230
|
+
.map((s) => s.trim())
|
|
231
|
+
.filter(Boolean),
|
|
232
|
+
);
|
|
233
|
+
break;
|
|
234
|
+
case '--tier':
|
|
235
|
+
out.tier = args[++i];
|
|
236
|
+
break;
|
|
237
|
+
case '--monthly-cost':
|
|
238
|
+
out.monthly_cost = parseFloat(args[++i]);
|
|
239
|
+
break;
|
|
240
|
+
case '--monthly-cap':
|
|
241
|
+
out.monthly_cap = parseFloat(args[++i]);
|
|
242
|
+
break;
|
|
243
|
+
case '--docs':
|
|
244
|
+
out.docs = args[++i];
|
|
245
|
+
break;
|
|
246
|
+
case '--rotate-days':
|
|
247
|
+
out.rotate_days = parseInt(args[++i], 10);
|
|
248
|
+
break;
|
|
249
|
+
case '--auto-rotate':
|
|
250
|
+
out.auto_rotate = true;
|
|
251
|
+
break;
|
|
252
|
+
case '--notes':
|
|
253
|
+
out.notes = args[++i];
|
|
254
|
+
break;
|
|
255
|
+
case '--label':
|
|
256
|
+
out.label = args[++i];
|
|
257
|
+
break;
|
|
258
|
+
case '--project':
|
|
259
|
+
out.project = args[++i];
|
|
260
|
+
break;
|
|
261
|
+
case '--path':
|
|
262
|
+
out.path = args[++i];
|
|
263
|
+
break;
|
|
264
|
+
case '--all-projects':
|
|
265
|
+
out.allProjects = true;
|
|
266
|
+
break;
|
|
267
|
+
case '--filename':
|
|
268
|
+
out.filename = args[++i] || '.env';
|
|
269
|
+
break;
|
|
270
|
+
case '--no-example':
|
|
271
|
+
out.noExample = true;
|
|
272
|
+
break;
|
|
273
|
+
case '--no-global':
|
|
274
|
+
out.noGlobal = true;
|
|
275
|
+
break;
|
|
276
|
+
case '--replace-all':
|
|
277
|
+
out.replaceAll = true;
|
|
278
|
+
break;
|
|
279
|
+
case '--export-env':
|
|
280
|
+
out.exportEnv = true;
|
|
281
|
+
break;
|
|
282
|
+
case '--export-all':
|
|
283
|
+
out.exportAll = true;
|
|
284
|
+
out.exportEnv = true;
|
|
285
|
+
break;
|
|
286
|
+
case '--enqueue':
|
|
287
|
+
out.enqueue = true;
|
|
288
|
+
break;
|
|
289
|
+
case '--help':
|
|
290
|
+
case '-h':
|
|
291
|
+
out.help = true;
|
|
292
|
+
break;
|
|
293
|
+
default:
|
|
294
|
+
if (!a.startsWith('--') && out.command === 'import-env') {
|
|
295
|
+
out.keys.push(a);
|
|
296
|
+
} else if (!a.startsWith('--') && !out.key) out.key = a;
|
|
297
|
+
else if (!a.startsWith('--') && !out.value) out.value = a;
|
|
298
|
+
}
|
|
299
|
+
i++;
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export default async function secretCli(argv) {
|
|
305
|
+
const { layer, remainingArgs } = parseLayerFlag(argv);
|
|
306
|
+
const opts = parseArgs(remainingArgs);
|
|
307
|
+
if (opts.help || !opts.command) {
|
|
308
|
+
printHelp();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let brainDir;
|
|
313
|
+
try {
|
|
314
|
+
brainDir = resolveBrainDir(layer);
|
|
315
|
+
} catch (err) {
|
|
316
|
+
console.error(`โ ${err.message}`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
if (opts.command === 'set') {
|
|
322
|
+
if (!opts.key || opts.value == null) {
|
|
323
|
+
console.error('Usage: total-recall secret set <key> <value>');
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
await setSecret(brainDir, opts.key, opts.value, {
|
|
327
|
+
provider: opts.provider || inferProvider(opts.key),
|
|
328
|
+
scope: opts.scope,
|
|
329
|
+
repos: opts.repos.length ? opts.repos : undefined,
|
|
330
|
+
subscription_tier: opts.tier,
|
|
331
|
+
monthly_cost_usd: opts.monthly_cost,
|
|
332
|
+
monthly_cap_usd: opts.monthly_cap,
|
|
333
|
+
api_docs_url: opts.docs,
|
|
334
|
+
rotate_every_days: opts.rotate_days,
|
|
335
|
+
auto_rotate: opts.auto_rotate,
|
|
336
|
+
notes: opts.notes,
|
|
337
|
+
label: opts.label,
|
|
338
|
+
project_path: opts.project,
|
|
339
|
+
});
|
|
340
|
+
console.log(`\n โ
Secret set: ${opts.key}`);
|
|
341
|
+
console.log(` store: ${resolveSecretsPath(brainDir)}`);
|
|
342
|
+
console.log(` (value not printed)\n`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (opts.command === 'catalog') {
|
|
347
|
+
const cat = await getSecretsCatalog(brainDir);
|
|
348
|
+
console.log(`\n ๐ฆ Secrets catalog โ ${cat.store}`);
|
|
349
|
+
console.log(
|
|
350
|
+
` keys=${cat.summary.total_keys} providers=${cat.summary.providers_active} planned/mo=$${cat.summary.monthly_subscription_usd.toFixed(2)} usage30d=$${cat.summary.usage_30d.cost_usd.toFixed(4)} rotate_due=${cat.summary.rotation_overdue}\n`,
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Group: multi-repo errors โ one section per product repo โ Developer secrets
|
|
354
|
+
const byRepo = new Map();
|
|
355
|
+
const developer = [];
|
|
356
|
+
const multiError = [];
|
|
357
|
+
for (const k of cat.keys) {
|
|
358
|
+
const repos = Array.isArray(k.repos) ? k.repos.filter(Boolean) : [];
|
|
359
|
+
if (repos.length > 1 || k.multi_repo_error) {
|
|
360
|
+
multiError.push(k);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (!repos.length) {
|
|
364
|
+
developer.push(k);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const r = repos[0];
|
|
368
|
+
if (!byRepo.has(r)) byRepo.set(r, []);
|
|
369
|
+
byRepo.get(r).push(k);
|
|
370
|
+
}
|
|
371
|
+
const sections = [];
|
|
372
|
+
if (multiError.length) sections.push(['โ ๏ธ NEEDS FIX โ multi-repo (one key โ one repo only)', multiError]);
|
|
373
|
+
for (const [repo, list] of [...byRepo.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
374
|
+
sections.push([repo, list]);
|
|
375
|
+
}
|
|
376
|
+
if (developer.length) sections.push(['Developer secrets (tooling / unbound)', developer]);
|
|
377
|
+
|
|
378
|
+
if (multiError.length) {
|
|
379
|
+
console.log(
|
|
380
|
+
` โ ${multiError.length} key(s) bound to multiple repos โ fix with:\n` +
|
|
381
|
+
` secret meta <KEY> --repo <one-repo>\n` +
|
|
382
|
+
` or clear repos for Developer secrets\n`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for (const [repo, list] of sections) {
|
|
387
|
+
console.log(` โโ ${repo} (${list.length})`);
|
|
388
|
+
for (const k of list.sort((a, b) => a.key.localeCompare(b.key))) {
|
|
389
|
+
const due = k.rotation_overdue
|
|
390
|
+
? 'โ ๏ธ ROTATE'
|
|
391
|
+
: k.next_rotate_due
|
|
392
|
+
? `due ${k.next_rotate_due.slice(0, 10)}`
|
|
393
|
+
: '';
|
|
394
|
+
const multi =
|
|
395
|
+
k.multi_repo_error || (k.repos?.length > 1)
|
|
396
|
+
? ` โ multi=[${(k.repos || []).join(',')}]`
|
|
397
|
+
: '';
|
|
398
|
+
console.log(
|
|
399
|
+
` โข ${k.key.padEnd(28)} ${(k.provider || '-').padEnd(12)} tier=${(k.subscription_tier || '-').padEnd(8)} $${String(k.monthly_cost_usd ?? '-').padStart(6)} u30=$${k.usage_30d.cost_usd.toFixed(3)} ${due}${multi}`,
|
|
400
|
+
);
|
|
401
|
+
if (k.api_docs_url) console.log(` docs: ${k.api_docs_url}`);
|
|
402
|
+
}
|
|
403
|
+
console.log('');
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (opts.command === 'providers') {
|
|
409
|
+
console.log('\n Provider registry\n');
|
|
410
|
+
for (const p of listProviders()) {
|
|
411
|
+
console.log(` โข ${p.id.padEnd(14)} ${p.name}`);
|
|
412
|
+
console.log(` docs: ${p.docs_url}`);
|
|
413
|
+
if (p.pricing_url) console.log(` pricing: ${p.pricing_url}`);
|
|
414
|
+
console.log(` keys: ${p.key_patterns.join(', ')}`);
|
|
415
|
+
console.log(` auth: ${p.schema.auth} ${p.schema.header || ''}`);
|
|
416
|
+
if (p.tiers?.length) {
|
|
417
|
+
console.log(` tiers: ${p.tiers.map((t) => t.id).join(', ')}`);
|
|
418
|
+
}
|
|
419
|
+
console.log('');
|
|
420
|
+
}
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (opts.command === 'meta') {
|
|
425
|
+
if (!opts.key) {
|
|
426
|
+
console.error('Usage: total-recall secret meta <key> [--repo ...] [--tier ...]');
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
const patch = {};
|
|
430
|
+
if (opts.repos.length) patch.repos = opts.repos;
|
|
431
|
+
if (opts.tier != null) patch.subscription_tier = opts.tier;
|
|
432
|
+
if (opts.monthly_cost != null) patch.monthly_cost_usd = opts.monthly_cost;
|
|
433
|
+
if (opts.monthly_cap != null) patch.monthly_cap_usd = opts.monthly_cap;
|
|
434
|
+
if (opts.docs) patch.api_docs_url = opts.docs;
|
|
435
|
+
if (opts.rotate_days != null) patch.rotate_every_days = opts.rotate_days;
|
|
436
|
+
if (opts.auto_rotate) patch.auto_rotate = true;
|
|
437
|
+
if (opts.notes) patch.notes = opts.notes;
|
|
438
|
+
if (opts.label) patch.label = opts.label;
|
|
439
|
+
if (opts.project) patch.project_path = opts.project;
|
|
440
|
+
if (opts.provider) patch.provider = opts.provider;
|
|
441
|
+
if (opts.scope) patch.scope = opts.scope;
|
|
442
|
+
// Fill docs from catalog if provider set and no docs
|
|
443
|
+
if (patch.provider && !patch.api_docs_url) {
|
|
444
|
+
const p = getProvider(patch.provider);
|
|
445
|
+
if (p) {
|
|
446
|
+
patch.api_docs_url = p.docs_url;
|
|
447
|
+
if (patch.monthly_cap_usd == null) patch.monthly_cap_usd = p.default_monthly_cap_usd;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const row = await updateSecretMeta(brainDir, opts.key, patch, { actor: 'cli' });
|
|
451
|
+
console.log(`\n โ
Meta updated: ${opts.key}`);
|
|
452
|
+
console.log(` provider=${row.provider} tier=${row.subscription_tier} repos=${(row.repos || []).join(',') || '-'}`);
|
|
453
|
+
console.log(` monthly_cost=$${row.monthly_cost_usd ?? '-'} cap=$${row.monthly_cap_usd ?? '-'}`);
|
|
454
|
+
console.log(` docs=${row.api_docs_url || '-'}`);
|
|
455
|
+
console.log(` rotate_every=${row.rotate_every_days || '-'}d next=${row.next_rotate_due || '-'}\n`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (opts.command === 'rotation-due') {
|
|
460
|
+
const due = await listRotationDue(brainDir, { autoOnly: false });
|
|
461
|
+
if (!due.length) {
|
|
462
|
+
console.log('\n No secrets overdue for rotation.\n');
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
console.log(`\n โ ๏ธ ${due.length} secret(s) overdue for rotation\n`);
|
|
466
|
+
for (const k of due) {
|
|
467
|
+
console.log(
|
|
468
|
+
` โข ${k.key} provider=${k.provider || '-'} due=${k.next_rotate_due} auto=${k.auto_rotate ? 'yes' : 'no'}`,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
if (opts.enqueue) {
|
|
472
|
+
const r = await enqueueRotationDueTasks(brainDir, { autoOnly: false });
|
|
473
|
+
console.log(`\n ๐ Enqueued ${r.tasks.filter((t) => t.status === 'created').length} rotation assist task(s)`);
|
|
474
|
+
}
|
|
475
|
+
console.log('\n Rotate: npx total-recall secret rotate <key> <new> --export-env');
|
|
476
|
+
console.log(' Browser assist: npx total-recall secret rotate-browser <key>');
|
|
477
|
+
console.log(' Enqueue tasks: npx total-recall secret rotation-due --enqueue\n');
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (opts.command === 'get') {
|
|
482
|
+
if (!opts.key) {
|
|
483
|
+
console.error('Usage: total-recall secret get <key>');
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
const r = await getSecret(brainDir, opts.key);
|
|
487
|
+
if (!r.found) {
|
|
488
|
+
console.error(` Secret not found: ${opts.key}`);
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
// Print value only โ user explicitly asked
|
|
492
|
+
console.log(r.value);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (opts.command === 'list') {
|
|
497
|
+
const rows = await listSecretsMeta(brainDir);
|
|
498
|
+
if (!rows.length) {
|
|
499
|
+
console.log(`\n No secrets in ${resolveSecretsPath(brainDir)}\n`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
console.log(`\n Secrets metadata โ ${resolveSecretsPath(brainDir)}\n`);
|
|
503
|
+
for (const r of rows) {
|
|
504
|
+
const repos = r.repos?.length ? r.repos.join(',') : '-';
|
|
505
|
+
console.log(
|
|
506
|
+
` โข ${r.key.padEnd(28)} len=${String(r.length).padStart(4)} ${(r.provider || '-').padEnd(12)} tier=${r.subscription_tier || '-'} $${r.monthly_cost_usd ?? '-'} repos=${repos}`,
|
|
507
|
+
);
|
|
508
|
+
if (r.api_docs_url) console.log(` docs: ${r.api_docs_url}`);
|
|
509
|
+
}
|
|
510
|
+
console.log('');
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (opts.command === 'rotate') {
|
|
515
|
+
if (!opts.key || !opts.value) {
|
|
516
|
+
console.error('Usage: total-recall secret rotate <key> <new-value> [--export-env|--export-all]');
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
if (opts.exportEnv || opts.exportAll) {
|
|
520
|
+
const r = await rotateSecretAndExport(brainDir, opts.key, opts.value, {
|
|
521
|
+
provider: opts.provider,
|
|
522
|
+
actor: 'cli',
|
|
523
|
+
exportEnv: true,
|
|
524
|
+
exportAllProjects: opts.exportAll,
|
|
525
|
+
exportCwd: true,
|
|
526
|
+
includeGlobal: true,
|
|
527
|
+
});
|
|
528
|
+
console.log(` โ
Rotated: ${opts.key}`);
|
|
529
|
+
if (r.next_rotate_due) console.log(` next due: ${r.next_rotate_due}`);
|
|
530
|
+
const ok = (r.exports || []).filter((e) => e.ok !== false);
|
|
531
|
+
console.log(` export-env: ${ok.length} target(s)`);
|
|
532
|
+
for (const e of ok) {
|
|
533
|
+
if (e.envPath) console.log(` โ ${e.envPath} (${e.count} keys)`);
|
|
534
|
+
}
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
await rotateSecret(brainDir, opts.key, opts.value, {
|
|
538
|
+
provider: opts.provider,
|
|
539
|
+
scope: opts.scope,
|
|
540
|
+
});
|
|
541
|
+
console.log(` โ
Rotated: ${opts.key}`);
|
|
542
|
+
console.log(` tip: add --export-env to push .env projections after rotate`);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (opts.command === 'rotate-browser') {
|
|
547
|
+
if (!opts.key) {
|
|
548
|
+
console.error('Usage: total-recall secret rotate-browser <key>');
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
const assist = await getBrowserRotateAssist(brainDir, opts.key);
|
|
552
|
+
console.log(`\n ๐ Supervised browser rotation โ ${assist.key}\n`);
|
|
553
|
+
if (assist.console_url) console.log(` Console: ${assist.console_url}`);
|
|
554
|
+
if (assist.docs_url) console.log(` Docs: ${assist.docs_url}`);
|
|
555
|
+
console.log(` Overdue: ${assist.overdue ? 'YES' : 'no'}`);
|
|
556
|
+
console.log('\n' + assist.prompt + '\n');
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (opts.command === 'delete' || opts.command === 'rm') {
|
|
561
|
+
if (!opts.key) {
|
|
562
|
+
console.error('Usage: total-recall secret delete <key>');
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
const r = await deleteSecret(brainDir, opts.key);
|
|
566
|
+
if (!r.found) {
|
|
567
|
+
console.error(` Secret not found: ${opts.key}`);
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
console.log(` โ
Deleted: ${opts.key}`);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (opts.command === 'audit') {
|
|
575
|
+
const events = readSecretAudit(brainDir, { limit: opts.limit });
|
|
576
|
+
if (!events.length) {
|
|
577
|
+
console.log('\n No secret audit events yet.\n');
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
console.log(`\n Secret audit (last ${events.length})\n`);
|
|
581
|
+
for (const e of events) {
|
|
582
|
+
console.log(` ${e.ts} ${e.action.padEnd(8)} ${e.key} ${e.actor || ''}`);
|
|
583
|
+
}
|
|
584
|
+
console.log('');
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (opts.command === 'usage') {
|
|
589
|
+
const day = summarizeUsage(brainDir, { days: opts.days });
|
|
590
|
+
const week = summarizeUsage(brainDir, { days: 7 });
|
|
591
|
+
const month = summarizeUsage(brainDir, { days: 30 });
|
|
592
|
+
const by = summarizeUsageByKey(brainDir, { days: 30 });
|
|
593
|
+
const budget = loadBudgetConfig(brainDir);
|
|
594
|
+
console.log(`\n Usage summary`);
|
|
595
|
+
console.log(` last ${opts.days}d: events=${day.events} cost=$${day.cost_usd.toFixed(4)} tokens_in=${day.input_tokens} tokens_out=${day.output_tokens}`);
|
|
596
|
+
console.log(` last 7d: events=${week.events} cost=$${week.cost_usd.toFixed(4)}`);
|
|
597
|
+
console.log(` last 30d: events=${month.events} cost=$${month.cost_usd.toFixed(4)}`);
|
|
598
|
+
const dailyCap = budget.config.daily_cap_usd ?? budget.config.dailyCapUsd;
|
|
599
|
+
const weeklyCap = budget.config.weekly_cap_usd ?? budget.config.weeklyCapUsd;
|
|
600
|
+
if (dailyCap != null) {
|
|
601
|
+
console.log(` budget daily_cap_usd=${dailyCap} ${day.cost_usd > dailyCap ? 'โ ๏ธ OVER' : 'ok'}`);
|
|
602
|
+
}
|
|
603
|
+
if (weeklyCap != null) {
|
|
604
|
+
console.log(` budget weekly_cap_usd=${weeklyCap} ${week.cost_usd > weeklyCap ? 'โ ๏ธ OVER' : 'ok'}`);
|
|
605
|
+
}
|
|
606
|
+
if (budget.path) console.log(` budget file: ${budget.path}`);
|
|
607
|
+
const keyEntries = Object.entries(by.by_key).filter(([k]) => k !== '_unattributed');
|
|
608
|
+
if (keyEntries.length) {
|
|
609
|
+
console.log(`\n By key_ref (30d)`);
|
|
610
|
+
for (const [k, v] of keyEntries.sort((a, b) => b[1].cost_usd - a[1].cost_usd)) {
|
|
611
|
+
console.log(` ${k.padEnd(28)} events=${v.events} cost=$${v.cost_usd.toFixed(4)}`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const provEntries = Object.entries(by.by_provider);
|
|
615
|
+
if (provEntries.length) {
|
|
616
|
+
console.log(`\n By provider (30d)`);
|
|
617
|
+
for (const [p, v] of provEntries.sort((a, b) => b[1].cost_usd - a[1].cost_usd)) {
|
|
618
|
+
console.log(` ${p.padEnd(16)} events=${v.events} cost=$${v.cost_usd.toFixed(4)}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
console.log('');
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (opts.command === 'usage-record') {
|
|
626
|
+
const row = recordUsage(brainDir, {
|
|
627
|
+
provider: opts.provider || 'unknown',
|
|
628
|
+
model: opts.model,
|
|
629
|
+
input_tokens: opts.input_tokens,
|
|
630
|
+
output_tokens: opts.output_tokens,
|
|
631
|
+
cost_usd: opts.cost,
|
|
632
|
+
key_ref: opts.key_ref,
|
|
633
|
+
source: 'cli',
|
|
634
|
+
});
|
|
635
|
+
console.log(` โ
Usage recorded: ${JSON.stringify(row)}`);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (opts.command === 'export-env' || opts.command === 'sync-env' || opts.command === 'apply-env') {
|
|
640
|
+
const exportOpts = {
|
|
641
|
+
filename: opts.filename || '.env',
|
|
642
|
+
example: !opts.noExample,
|
|
643
|
+
dryRun: opts.dryRun,
|
|
644
|
+
includeGlobal: !opts.noGlobal,
|
|
645
|
+
replaceAll: !!opts.replaceAll,
|
|
646
|
+
keys: opts.keys.length ? opts.keys : undefined,
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
if (opts.allProjects) {
|
|
650
|
+
const results = await exportEnvToRegistry(brainDir, exportOpts);
|
|
651
|
+
console.log(`\n ๐ค export-env โ ${results.length} project(s) from ${resolveSecretsPath(brainDir)}\n`);
|
|
652
|
+
for (const r of results) {
|
|
653
|
+
if (!r.ok) {
|
|
654
|
+
console.log(` โ ${r.name || r.path}: ${r.error}`);
|
|
655
|
+
} else if (r.dryRun) {
|
|
656
|
+
console.log(` ยท ${r.name || r.projectSlug}: would write ${r.count} keys โ ${r.envPath}`);
|
|
657
|
+
} else {
|
|
658
|
+
console.log(` โ ${r.name || r.projectSlug}: ${r.count} keys โ ${r.envPath}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
console.log('');
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const targetPath = opts.path || opts.project || process.cwd();
|
|
666
|
+
const result = await exportEnvToProject(brainDir, targetPath, exportOpts);
|
|
667
|
+
if (result.dryRun) {
|
|
668
|
+
console.log(`\n ๐ค dry-run export-env`);
|
|
669
|
+
console.log(` store: ${result.store}`);
|
|
670
|
+
console.log(` would write ${result.count} keys โ ${result.envPath}`);
|
|
671
|
+
if (result.keys.length) {
|
|
672
|
+
for (const k of result.keys) console.log(` โข ${k}`);
|
|
673
|
+
}
|
|
674
|
+
console.log('');
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
console.log(`\n โ
Exported ${result.count} secret(s) โ ${result.envPath}`);
|
|
678
|
+
if (result.examplePath) console.log(` example: ${result.examplePath}`);
|
|
679
|
+
console.log(` store: ${result.store}`);
|
|
680
|
+
console.log(` (values written with mode 0600; not printed)\n`);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (opts.command === 'import-env') {
|
|
685
|
+
const existing = await listSecretsMeta(brainDir);
|
|
686
|
+
const existingSet = new Set(existing.map((e) => e.key));
|
|
687
|
+
let scan = scanEnvSources({ brainDir, includeProcessEnv: true, cwd: process.cwd() });
|
|
688
|
+
|
|
689
|
+
if (opts.file) {
|
|
690
|
+
if (!fs.existsSync(opts.file)) {
|
|
691
|
+
console.error(` File not found: ${opts.file}`);
|
|
692
|
+
process.exit(1);
|
|
693
|
+
}
|
|
694
|
+
const text = fs.readFileSync(opts.file, 'utf8');
|
|
695
|
+
const map = parseEnvText(text);
|
|
696
|
+
// merge file into scan candidates via import pairs path
|
|
697
|
+
const pub = publicScanResult(scan, existingSet);
|
|
698
|
+
console.log(`\n ๐ Env scan (${pub.count} candidates)\n`);
|
|
699
|
+
for (const c of pub.candidates) {
|
|
700
|
+
console.log(
|
|
701
|
+
` ${c.already_set ? 'ยท' : '+'} ${c.key.padEnd(32)} ${c.masked.padEnd(28)} ${c.source_label}`,
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
if (Object.keys(map).length) {
|
|
705
|
+
console.log(`\n Also loading --file ${opts.file} (${Object.keys(map).length} vars)`);
|
|
706
|
+
}
|
|
707
|
+
if (opts.dryRun) {
|
|
708
|
+
console.log('\n Dry-run โ nothing written.\n');
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const keys = opts.keys.length
|
|
712
|
+
? opts.keys
|
|
713
|
+
: opts.all
|
|
714
|
+
? undefined
|
|
715
|
+
: pub.candidates.filter((c) => !c.already_set).map((c) => c.key);
|
|
716
|
+
const result = await importEnvSecrets(brainDir, {
|
|
717
|
+
pairs: map,
|
|
718
|
+
keys: opts.keys.length ? opts.keys : undefined,
|
|
719
|
+
all: opts.all || (!opts.keys.length && !Object.keys(map).length),
|
|
720
|
+
overwrite: opts.overwrite,
|
|
721
|
+
actor: 'cli-import-env',
|
|
722
|
+
});
|
|
723
|
+
// If --file provided, also import matching keys from file pairs
|
|
724
|
+
if (Object.keys(map).length) {
|
|
725
|
+
const fileResult = await importEnvSecrets(brainDir, {
|
|
726
|
+
pairs: map,
|
|
727
|
+
keys: opts.keys.length ? opts.keys : Object.keys(map),
|
|
728
|
+
overwrite: opts.overwrite,
|
|
729
|
+
actor: 'cli-import-env',
|
|
730
|
+
});
|
|
731
|
+
console.log(
|
|
732
|
+
`\n โ
Imported ${fileResult.imported_count} from file, skipped ${fileResult.skipped_count}\n`,
|
|
733
|
+
);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
console.log(
|
|
737
|
+
`\n โ
Imported ${result.imported_count}, skipped ${result.skipped_count}, errors ${result.errors.length}\n`,
|
|
738
|
+
);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const pub = publicScanResult(scan, existingSet);
|
|
743
|
+
console.log(`\n ๐ Env scan โ ${pub.count} candidate secrets\n`);
|
|
744
|
+
for (const c of pub.candidates) {
|
|
745
|
+
const mark = c.already_set ? 'ยท' : '+';
|
|
746
|
+
console.log(
|
|
747
|
+
` ${mark} ${c.key.padEnd(32)} ${c.masked.padEnd(28)} ${c.provider || ''} ${c.source_label}`,
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
if (!pub.count) {
|
|
751
|
+
console.log(' No known API keys found in process.env or common .env paths.\n');
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (opts.dryRun || (!opts.all && !opts.keys.length)) {
|
|
755
|
+
console.log(
|
|
756
|
+
'\n Dry-run / preview. Import with:\n npx total-recall secret import-env --all\n npx total-recall secret import-env OPENAI_API_KEY ANTHROPIC_API_KEY\n',
|
|
757
|
+
);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const result = await importEnvSecrets(brainDir, {
|
|
761
|
+
keys: opts.keys.length ? opts.keys : undefined,
|
|
762
|
+
all: opts.all || !opts.keys.length,
|
|
763
|
+
overwrite: opts.overwrite,
|
|
764
|
+
actor: 'cli-import-env',
|
|
765
|
+
});
|
|
766
|
+
console.log(`\n โ
Imported ${result.imported_count}`);
|
|
767
|
+
if (result.skipped_count) console.log(` skipped (already set): ${result.skipped_count}`);
|
|
768
|
+
for (const e of result.errors) console.error(` error ${e.key}: ${e.error}`);
|
|
769
|
+
console.log(` store: ${resolveSecretsPath(brainDir)}\n`);
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (opts.command === 'check-surfaces') {
|
|
774
|
+
const texts = [];
|
|
775
|
+
const candidates = [
|
|
776
|
+
path.join(brainDir, 'INSTRUCTIONS.md'),
|
|
777
|
+
path.join(process.cwd(), 'INSTRUCTIONS.md'),
|
|
778
|
+
path.join(brainDir, 'openwiki'),
|
|
779
|
+
];
|
|
780
|
+
for (const c of candidates) {
|
|
781
|
+
if (!fs.existsSync(c)) continue;
|
|
782
|
+
if (fs.statSync(c).isDirectory()) {
|
|
783
|
+
for (const f of fs.readdirSync(c)) {
|
|
784
|
+
if (f.endsWith('.md')) {
|
|
785
|
+
texts.push({ file: path.join(c, f), body: fs.readFileSync(path.join(c, f), 'utf8') });
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
} else {
|
|
789
|
+
texts.push({ file: c, body: fs.readFileSync(c, 'utf8') });
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
let bad = false;
|
|
793
|
+
for (const t of texts) {
|
|
794
|
+
const r = await textContainsSecrets(brainDir, t.body);
|
|
795
|
+
if (r.leak) {
|
|
796
|
+
bad = true;
|
|
797
|
+
console.error(` โ LEAK in ${t.file}: keys ${r.keys.join(', ')}`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (bad) process.exit(1);
|
|
801
|
+
console.log(` โ
No secret values found in ${texts.length} surface/openwiki file(s)`);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
console.error(`Unknown secret command: ${opts.command}`);
|
|
806
|
+
printHelp();
|
|
807
|
+
process.exit(1);
|
|
808
|
+
} catch (err) {
|
|
809
|
+
console.error(`โ ${err.message}`);
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
}
|