total-recall-brain 3.41.2 → 3.41.3
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/bin/total-recall.mjs +9 -3
- package/frontend/dist/assets/{index-BG9NkVUy.css → index-BIQmqDKP.css} +1 -1
- package/frontend/dist/assets/{index-BtuiZqwP.js → index-DFenQQEQ.js} +74 -79
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +8 -5
- package/frontend/src/api/auth.ts +0 -3
- package/frontend/src/components/plugins/PluginPanel.tsx +7 -3
- package/frontend/src/components/plugins/card/CardEngine.tsx +1 -1
- package/frontend/src/pages/LoginPage.tsx +1 -41
- package/frontend/src/pages/PluginsPage.tsx +14 -7
- package/package.json +2 -2
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +197 -31
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +8 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +56 -8
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +1068 -204
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/panel.js +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +436 -274
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +16 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +461 -242
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -7
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +17 -2
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +426 -300
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +350 -283
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +16 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +380 -298
- package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +17 -3
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +38 -15
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +60 -34
- package/src/cli/command.mjs +23 -18
- package/src/cli/daemon.mjs +23 -1
- package/src/cli/plugin/copies.mjs +8 -5
- package/src/cli/plugin/index.mjs +25 -2
- package/src/cli/rebuild.mjs +22 -0
- package/src/cli/reset-password.mjs +3 -2
- package/src/cli/skill.mjs +8 -2
- package/src/cli/status.mjs +25 -7
- package/src/core/automatic-brain-updates.mjs +9 -3
- package/src/core/brain-updates.mjs +63 -5
- package/src/core/global-brain-updates.mjs +21 -1
- package/src/core/mesh-brain-updates.mjs +5 -2
- package/src/core/meta-harness.mjs +16 -41
- package/src/core/plugin-runner.mjs +6 -1
- package/src/core/plugin-store.mjs +67 -1
- package/src/core/plugin-updates.mjs +1 -1
- package/src/core/portable-skills-maintenance.mjs +19 -0
- package/src/core/required-plugins.mjs +7 -0
- package/src/core/skills-registry.mjs +33 -3
- package/src/server/auth.mjs +41 -1
- package/src/server/memory-app.mjs +2 -1
- package/src/server/routes/auth.mjs +25 -1
- package/src/server/routes/plugins.mjs +17 -4
package/frontend/dist/index.html
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
18
18
|
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,800&family=Cabinet+Grotesk:wght@700;800&family=Chivo+Mono:wght@500;700&family=Cinzel:wght@600;700;900&family=Cormorant+Garamond:ital,wght@0,600;0,700;1,600;1,700&family=DM+Mono:wght@500&family=DM+Sans:wght@500;700&family=Fraunces:opsz,wght@9..144,600;9..144,800&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&family=Lexend:wght@600;800&family=Newsreader:ital,opsz,wght@0,6..72,600;1,6..72,600&family=Orbitron:wght@700;900&family=Outfit:wght@500;700;800&family=Plus+Jakarta+Sans:wght@600;700;800&family=Space+Grotesk:wght@600;700&family=Syne:wght@700;800&display=swap" rel="stylesheet" />
|
|
19
19
|
<title>Total Recall — Portable Memory</title>
|
|
20
|
-
<script type="module" crossorigin src="/assets/index-
|
|
21
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
20
|
+
<script type="module" crossorigin src="/assets/index-DFenQQEQ.js"></script>
|
|
21
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BIQmqDKP.css">
|
|
22
22
|
</head>
|
|
23
23
|
<body>
|
|
24
24
|
<div id="root"></div>
|
package/frontend/dist/sw.js
CHANGED
|
@@ -124,12 +124,15 @@ async function staleWhileRevalidate(request) {
|
|
|
124
124
|
// ─── Cache-First ──────────────────────────────────────────────────────────────
|
|
125
125
|
|
|
126
126
|
async function cacheFirst(request) {
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
try {
|
|
128
|
+
const cached = await caches.match(request);
|
|
129
|
+
if (cached) return cached;
|
|
130
|
+
} catch {
|
|
131
|
+
// unreadable cache entry: fall through to the network
|
|
132
|
+
}
|
|
129
133
|
const response = await fetch(request);
|
|
130
|
-
if (response && response.ok && response.status === 200) {
|
|
131
|
-
|
|
132
|
-
cache.put(request, response.clone()).catch(() => {});
|
|
134
|
+
if (response && response.ok && response.status === 200 && response.type === 'basic') {
|
|
135
|
+
caches.open(CACHE_NAME).then((cache) => cache.put(request, response.clone())).catch(() => {});
|
|
133
136
|
}
|
|
134
137
|
return response;
|
|
135
138
|
}
|
package/frontend/src/api/auth.ts
CHANGED
|
@@ -75,7 +75,6 @@ export async function logout(): Promise<void> {
|
|
|
75
75
|
export interface AuthProvidersResponse {
|
|
76
76
|
password: { configured: boolean }
|
|
77
77
|
passkey: { enabled: boolean; has_passkeys: boolean; count: number }
|
|
78
|
-
google: { configured: boolean; client_id: string | null }
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
export async function getAuthProviders(): Promise<AuthProvidersResponse> {
|
|
@@ -85,13 +84,11 @@ export async function getAuthProviders(): Promise<AuthProvidersResponse> {
|
|
|
85
84
|
return {
|
|
86
85
|
password: { configured: true },
|
|
87
86
|
passkey: { enabled: false, has_passkeys: false, count: 0 },
|
|
88
|
-
google: { configured: false, client_id: null },
|
|
89
87
|
}
|
|
90
88
|
} catch {
|
|
91
89
|
return {
|
|
92
90
|
password: { configured: true },
|
|
93
91
|
passkey: { enabled: false, has_passkeys: false, count: 0 },
|
|
94
|
-
google: { configured: false, client_id: null },
|
|
95
92
|
}
|
|
96
93
|
}
|
|
97
94
|
}
|
|
@@ -20,7 +20,8 @@ export function pluginElements(plugin: PluginInfo): PluginElement[] {
|
|
|
20
20
|
/** Loads owner code; no plugin identities or provider behavior belong here. */
|
|
21
21
|
export function PluginPanel({ plugin, elementId, readOnly = false }: { plugin: PluginInfo; elementId?: string; readOnly?: boolean }) {
|
|
22
22
|
const elements = pluginElements(plugin).filter(element => element.kind !== 'settings' && (!elementId || element.id === elementId))
|
|
23
|
-
const
|
|
23
|
+
const cardElement = elements.find(item => item.kind === 'card')
|
|
24
|
+
const [selected, setSelected] = useState(elementId || cardElement?.id || elements[0]?.id || '')
|
|
24
25
|
const [error, setError] = useState('')
|
|
25
26
|
const frame = useRef<HTMLIFrameElement>(null)
|
|
26
27
|
const [document, setDocument] = useState('')
|
|
@@ -67,7 +68,7 @@ export function PluginPanel({ plugin, elementId, readOnly = false }: { plugin: P
|
|
|
67
68
|
if (data.type !== 'run') return
|
|
68
69
|
const target = event.source as Window
|
|
69
70
|
const reply = (result?: unknown, error?: string) => { if (!disposed) target.postMessage({ channel, type: 'result', requestId: data.requestId, result, error }, '*') }
|
|
70
|
-
if (!allowedFrameCommand(data, allowed, secretCommands) || active >=
|
|
71
|
+
if (!allowedFrameCommand(data, allowed, secretCommands) || active >= 12) { reply(undefined, 'Operation is not available'); return }
|
|
71
72
|
active++
|
|
72
73
|
window.dispatchEvent(new CustomEvent('plugin-command-activity', { detail: { pluginId: plugin.id, requestId: data.requestId, command: data.subcommand, running: true } }))
|
|
73
74
|
try {
|
|
@@ -106,8 +107,11 @@ export function PluginPanel({ plugin, elementId, readOnly = false }: { plugin: P
|
|
|
106
107
|
return dispose
|
|
107
108
|
}, [plugin.id, selected, plugin.sha256, elementId, readOnly])
|
|
108
109
|
|
|
110
|
+
const currentElement = elements.find(item => item.id === selected) || elements[0]
|
|
111
|
+
const showNav = elements.length > 1 && currentElement?.kind !== 'card'
|
|
112
|
+
|
|
109
113
|
return <section>
|
|
110
|
-
{
|
|
114
|
+
{showNav && <nav style={{ display: 'flex', gap: 8 }}>
|
|
111
115
|
{elements.map(element => <button key={element.id} className="btn btn-sm" onClick={() => setSelected(element.id)} aria-pressed={selected === element.id}>{element.description || element.id}</button>)}
|
|
112
116
|
</nav>}
|
|
113
117
|
{error && <div role="alert" className="alert alert-error">{error}</div>}
|
|
@@ -98,7 +98,7 @@ export function CardEngine({ plugin }: { plugin: PluginInfo }) {
|
|
|
98
98
|
{card.description && <p className="card-muted card-description">{card.description}</p>}
|
|
99
99
|
<CardScenes plugin={plugin}>
|
|
100
100
|
{card.face
|
|
101
|
-
? <PluginPanel plugin={plugin} elementId={card.face.id}
|
|
101
|
+
? <PluginPanel plugin={plugin} elementId={card.face.id} />
|
|
102
102
|
: <p className="card-error" role="alert">{plugin.name} has not supplied its card face.</p>}
|
|
103
103
|
</CardScenes>
|
|
104
104
|
{card.branding.provider && <footer className="card-footer">{card.branding.provider.url ? <a href={card.branding.provider.url} target="_blank" rel="noreferrer noopener">{card.branding.provider.name}</a> : card.branding.provider.name} is a trademark of its owner. This plugin connects to the service with your own account.</footer>}
|
|
@@ -88,14 +88,6 @@ export default function LoginPage({ onAuthenticated }: Props) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function handleGoogleLogin() {
|
|
92
|
-
if (providers?.google && !providers.google.configured) {
|
|
93
|
-
setError("Google OAuth2 is not configured. Provide GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in the Auth Plugin.")
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
window.location.href = "/auth/google"
|
|
97
|
-
}
|
|
98
|
-
|
|
99
91
|
useEffect(() => {
|
|
100
92
|
inputRef.current?.focus()
|
|
101
93
|
}, [needsReset, isFirstTime])
|
|
@@ -324,7 +316,7 @@ export default function LoginPage({ onAuthenticated }: Props) {
|
|
|
324
316
|
</button>
|
|
325
317
|
</form>
|
|
326
318
|
|
|
327
|
-
{(!needsReset && !isFirstTime &&
|
|
319
|
+
{(!needsReset && !isFirstTime && providers?.passkey?.has_passkeys) && (
|
|
328
320
|
<div style={{ display: "flex", flexDirection: "column", gap: 10, marginTop: -4 }}>
|
|
329
321
|
<div style={{ display: "flex", alignItems: "center", gap: 12, margin: "6px 0" }}>
|
|
330
322
|
<div style={{ flex: 1, height: 1, background: "var(--border)" }} />
|
|
@@ -376,38 +368,6 @@ export default function LoginPage({ onAuthenticated }: Props) {
|
|
|
376
368
|
)}
|
|
377
369
|
</button>
|
|
378
370
|
)}
|
|
379
|
-
|
|
380
|
-
{providers?.google?.configured && (
|
|
381
|
-
<button
|
|
382
|
-
type="button"
|
|
383
|
-
id="btn-google-login"
|
|
384
|
-
onClick={handleGoogleLogin}
|
|
385
|
-
disabled={loading || passkeyLoading}
|
|
386
|
-
className="btn btn-secondary"
|
|
387
|
-
style={{
|
|
388
|
-
width: "100%",
|
|
389
|
-
minHeight: 44,
|
|
390
|
-
justifyContent: "center",
|
|
391
|
-
fontSize: 13.5,
|
|
392
|
-
borderRadius: 12,
|
|
393
|
-
display: "flex",
|
|
394
|
-
alignItems: "center",
|
|
395
|
-
gap: 8,
|
|
396
|
-
background: "rgba(255, 255, 255, 0.05)",
|
|
397
|
-
borderColor: "rgba(255, 255, 255, 0.15)",
|
|
398
|
-
color: "var(--text-primary)",
|
|
399
|
-
cursor: loading || passkeyLoading ? "not-allowed" : "pointer",
|
|
400
|
-
}}
|
|
401
|
-
>
|
|
402
|
-
<svg width="16" height="16" viewBox="0 0 24 24">
|
|
403
|
-
<path fill="#4285F4" d="M23.745 12.27c0-.7-.06-1.4-.19-2.07H12v4.51h6.6c-.29 1.52-1.14 2.82-2.4 3.68v3.05h3.88c2.27-2.09 3.665-5.17 3.665-9.17z"/>
|
|
404
|
-
<path fill="#34A853" d="M12 24c3.24 0 5.95-1.08 7.93-2.91l-3.88-3.05c-1.08.72-2.45 1.16-4.05 1.16-3.12 0-5.77-2.1-6.72-4.93H1.25v3.15C3.26 21.36 7.33 24 12 24z"/>
|
|
405
|
-
<path fill="#FBBC05" d="M5.28 14.27c-.25-.72-.38-1.49-.38-2.27s.13-1.55.38-2.27V6.58H1.25C.45 8.18 0 9.99 0 12s.45 3.82 1.25 5.42l4.03-3.15z"/>
|
|
406
|
-
<path fill="#EA4335" d="M12 4.75c1.77 0 3.35.61 4.6 1.8l3.42-3.42C17.95 1.19 15.24 0 12 0 7.33 0 3.26 2.64 1.25 6.58l4.03 3.15c.95-2.83 3.6-4.98 6.72-4.98z"/>
|
|
407
|
-
</svg>
|
|
408
|
-
Sign in with Google
|
|
409
|
-
</button>
|
|
410
|
-
)}
|
|
411
371
|
</div>
|
|
412
372
|
)}
|
|
413
373
|
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
import { renderMarkdown } from "../components/MarkdownUtils"
|
|
23
23
|
import { PluginPanel, pluginElements } from "../components/plugins/PluginPanel"
|
|
24
24
|
import { PluginSettings } from "../components/plugins/PluginSettings"
|
|
25
|
-
import { CardEngine } from "../components/plugins/card/CardEngine"
|
|
26
25
|
|
|
27
26
|
interface PluginsPageProps {
|
|
28
27
|
activeBrainId?: string | null
|
|
@@ -76,8 +75,8 @@ const AGENT_RECIPE_PROMPT = `You are my coding agent. Author a bespoke Total Rec
|
|
|
76
75
|
- Real interactive controls executing plugin subcommands via host.run(subcommand, args)
|
|
77
76
|
|
|
78
77
|
4. Distribution:
|
|
79
|
-
- Private: keep in .agent/
|
|
80
|
-
-
|
|
78
|
+
- Private: keep in .agent/plugins/<id>
|
|
79
|
+
- Mesh: share peer-to-peer via total-recall plugin share <id>`
|
|
81
80
|
|
|
82
81
|
function resolvePluginBranding(p: { id: string; branding?: { icon?: string | null; color?: string | null; badge?: string | null } }) {
|
|
83
82
|
const custom = p.branding || {}
|
|
@@ -499,7 +498,7 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
|
|
|
499
498
|
setWorkspaceActiveIds(next)
|
|
500
499
|
try { localStorage.setItem("tr_workspace_plugins", JSON.stringify([...next])) } catch {}
|
|
501
500
|
await setPluginActivation(p.id, false)
|
|
502
|
-
setAlert({ type: "success", message: `
|
|
501
|
+
setAlert({ type: "success", message: `Deactivated card for ${p.name}` })
|
|
503
502
|
await loadLocal()
|
|
504
503
|
} catch (err: any) {
|
|
505
504
|
setAlert({ type: "error", message: err?.message || `Could not deactivate ${p.name}` })
|
|
@@ -772,7 +771,7 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
|
|
|
772
771
|
<div style={{ display: "flex", justifyContent: "space-between", gap: "12px", flexWrap: "wrap", marginBottom: "20px" }}>
|
|
773
772
|
<div role="tablist" style={{ display: "flex", gap: "8px" }}>
|
|
774
773
|
{tabButton("installed", "My Cards", myCards.length)}
|
|
775
|
-
{tabButton("bundled", "Plugin
|
|
774
|
+
{tabButton("bundled", "Plugin Catalog", filteredStoreCatalog.length)}
|
|
776
775
|
{tabButton("mesh", "On the mesh")}
|
|
777
776
|
</div>
|
|
778
777
|
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
|
|
@@ -795,7 +794,7 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
|
|
|
795
794
|
</div>
|
|
796
795
|
<div style={muted}>
|
|
797
796
|
{viewMode === "workspace"
|
|
798
|
-
? "Core runtime foundation and activated repository cards.
|
|
797
|
+
? "Core runtime foundation and activated repository cards. Standalone tools live in the Plugin Catalog."
|
|
799
798
|
: "All plugin packages installed on this host."}
|
|
800
799
|
</div>
|
|
801
800
|
</div>
|
|
@@ -839,7 +838,7 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
|
|
|
839
838
|
onTouchEnd={cancelLongPress}
|
|
840
839
|
style={{ display: "flex", flexDirection: "column", gap: "8px", minWidth: 0 }}
|
|
841
840
|
>
|
|
842
|
-
<
|
|
841
|
+
<PluginPanel plugin={p} />
|
|
843
842
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "0 4px", fontSize: "11px", color: "var(--text-tertiary)", flexWrap: "wrap", gap: "6px" }}>
|
|
844
843
|
<div style={{ display: "flex", gap: "6px", alignItems: "center", flexWrap: "wrap" }}>
|
|
845
844
|
<span className="badge">{p.scope}</span>
|
|
@@ -848,6 +847,14 @@ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
|
|
|
848
847
|
{!p.valid && <span className="badge badge-error">invalid manifest</span>}
|
|
849
848
|
{p.modified_since_install && <span className="badge badge-warning">changed since install</span>}
|
|
850
849
|
</div>
|
|
850
|
+
<button
|
|
851
|
+
type="button"
|
|
852
|
+
className="btn btn-ghost btn-xs"
|
|
853
|
+
onClick={() => openDetail(p, "details")}
|
|
854
|
+
style={{ fontSize: "11px", color: "var(--text-secondary)" }}
|
|
855
|
+
>
|
|
856
|
+
Settings & usage
|
|
857
|
+
</button>
|
|
851
858
|
{!DEFAULT_CORE_PLUGINS.has(p.id) && (
|
|
852
859
|
<button
|
|
853
860
|
type="button"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "total-recall-brain",
|
|
3
|
-
"version": "3.41.
|
|
3
|
+
"version": "3.41.3",
|
|
4
4
|
"description": "The Vibe Coding Brain & Autonomous Software Factory: memory, rules, secrets, and 33 visual plugins automatically managed across all your devices.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"homepage": "https://github.com/gregiteen/total-recall#readme",
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@simplewebauthn/server": "^13.3.2",
|
|
79
|
-
"@ssss/cli": "npm:@gregiteen/ssss-cli@0.
|
|
79
|
+
"@ssss/cli": "npm:@gregiteen/ssss-cli@0.12.1",
|
|
80
80
|
"bcrypt": "^6.0.0",
|
|
81
81
|
"better-sqlite3": "^12.11.1",
|
|
82
82
|
"cookie-parser": "^1.4.7",
|
|
@@ -47,7 +47,9 @@ function walk(dir, out = [], base = root) {
|
|
|
47
47
|
/* ── manifest ────────────────────────────────────────────────────────────── */
|
|
48
48
|
|
|
49
49
|
let manifest = null;
|
|
50
|
-
|
|
50
|
+
// Every selected section depends on manifest facts. Section filtering must not
|
|
51
|
+
// skip prerequisite loading and then report a vacuous successful check run.
|
|
52
|
+
{
|
|
51
53
|
const file = path.join(root, 'plugin.json');
|
|
52
54
|
if (!fs.existsSync(file)) {
|
|
53
55
|
record('manifest', 'FAIL', 'plugin.json exists', 'no plugin.json in ' + root);
|
|
@@ -62,7 +64,7 @@ if (want('--manifest')) {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
// Validate with the real contract when this skill is inside a Total Recall checkout.
|
|
65
|
-
if (manifest) {
|
|
67
|
+
if (manifest && want('--manifest')) {
|
|
66
68
|
const loaderPath = path.resolve(import.meta.dirname, '../../../../src/core/plugin-loader.mjs');
|
|
67
69
|
if (fs.existsSync(loaderPath)) {
|
|
68
70
|
try {
|
|
@@ -82,7 +84,8 @@ if (manifest) {
|
|
|
82
84
|
|
|
83
85
|
// Card declaration
|
|
84
86
|
const elements = manifest.ui?.elements || [];
|
|
85
|
-
|
|
87
|
+
// One face (kind 'card') plus any number of screens (kind 'panel'); a panel is never the face.
|
|
88
|
+
const faces = elements.filter((e) => e && e.kind === 'card');
|
|
86
89
|
if (manifest.card_layout !== undefined) {
|
|
87
90
|
record('manifest', 'FAIL', 'no template', "'card_layout' is declared; a face is bespoke owner code");
|
|
88
91
|
} else {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
|
+
import { hostSecretsBrainDir } from './src/host-secrets-store.mjs';
|
|
12
13
|
|
|
13
14
|
function resolveTrPath() {
|
|
14
15
|
const localTr = path.join(process.cwd(), 'bin', 'total-recall.mjs');
|
|
@@ -19,13 +20,13 @@ function resolveTrPath() {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
async function resolveSecret(key) {
|
|
22
|
-
const { getSecret
|
|
23
|
-
const result = await getSecret(
|
|
23
|
+
const { getSecret } = await import('./src/secrets-store.mjs');
|
|
24
|
+
const result = await getSecret(hostSecretsBrainDir(), key, { actor: 'auth-cli', action: 'use' });
|
|
24
25
|
return result.found ? result.value : null;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
function resolveBrainDir() {
|
|
28
|
-
return
|
|
29
|
+
return hostSecretsBrainDir();
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
async function listStoredPasskeys() {
|
|
@@ -33,7 +34,11 @@ async function listStoredPasskeys() {
|
|
|
33
34
|
return listPasskeys(resolveBrainDir());
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
async function handleStatus(json) {
|
|
37
|
+
async function handleStatus(subArgs, json) {
|
|
38
|
+
if (Array.isArray(subArgs) && (subArgs.includes('--help') || subArgs.includes('-h') || subArgs[0] === 'help')) {
|
|
39
|
+
console.log('Usage: total-recall auth status [--json]');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
37
42
|
const passkeys = await listStoredPasskeys();
|
|
38
43
|
const googleClientId = await resolveSecret('GOOGLE_CLIENT_ID');
|
|
39
44
|
const googleClientSecret = await resolveSecret('GOOGLE_CLIENT_SECRET');
|
|
@@ -87,9 +92,15 @@ async function handleStatus(json) {
|
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
async function handlePasskey(subArgs, json) {
|
|
90
|
-
const passkeys = await listStoredPasskeys();
|
|
91
95
|
const action = subArgs[0] || 'list';
|
|
92
96
|
|
|
97
|
+
if (action === 'help' || action === '--help' || action === '-h') {
|
|
98
|
+
console.log('Usage: total-recall auth passkey [list|status|delete <id>] [--json]');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const passkeys = await listStoredPasskeys();
|
|
103
|
+
|
|
93
104
|
if (action === 'list') {
|
|
94
105
|
if (json) {
|
|
95
106
|
console.log(JSON.stringify({ count: passkeys.length, passkeys }, null, 2));
|
|
@@ -112,25 +123,80 @@ async function handlePasskey(subArgs, json) {
|
|
|
112
123
|
return;
|
|
113
124
|
}
|
|
114
125
|
|
|
126
|
+
if (action === 'delete' || action === 'remove' || action === 'rm') {
|
|
127
|
+
const id = subArgs[1];
|
|
128
|
+
if (!id) {
|
|
129
|
+
console.error('Credential ID required: total-recall auth passkey delete <id>');
|
|
130
|
+
process.exitCode = 1;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const { deletePasskey } = await import('./src/webauthn-store.mjs');
|
|
134
|
+
const result = deletePasskey(resolveBrainDir(), id);
|
|
135
|
+
if (!result.found) {
|
|
136
|
+
console.error(`Passkey not found: ${id}`);
|
|
137
|
+
process.exitCode = 1;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (json) {
|
|
141
|
+
console.log(JSON.stringify({ success: true, deleted: id }));
|
|
142
|
+
} else {
|
|
143
|
+
console.log(`Passkey removed: ${id}`);
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
115
148
|
console.error(`Unknown passkey action: ${action}`);
|
|
116
149
|
process.exitCode = 1;
|
|
117
150
|
}
|
|
118
151
|
|
|
119
152
|
async function handleGoogle(subArgs, json) {
|
|
153
|
+
const action = subArgs[0] || 'status';
|
|
154
|
+
if (action === 'help' || action === '--help' || action === '-h') {
|
|
155
|
+
console.log('Usage: total-recall auth google [status|connect|accounts|refresh <id>|disconnect <id>] [--json]');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
120
158
|
const { createGoogleOAuth } = await import('./src/google-oauth.mjs');
|
|
121
159
|
const google = createGoogleOAuth({ brainDir: resolveBrainDir() });
|
|
122
|
-
|
|
123
|
-
|
|
160
|
+
if (action === 'accounts') {
|
|
161
|
+
const accounts = google.status();
|
|
162
|
+
if (json) {
|
|
163
|
+
console.log(JSON.stringify({ accounts }));
|
|
164
|
+
} else {
|
|
165
|
+
console.log(`Google Connected Accounts: ${accounts.length}`);
|
|
166
|
+
for (const a of accounts) {
|
|
167
|
+
console.log(` • [${a.status}] ${a.email || a.account_id} (Expires: ${a.expiry || 'never'})`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
124
172
|
if (action === 'refresh' || action === 'disconnect') {
|
|
125
|
-
if (!subArgs[1])
|
|
126
|
-
|
|
127
|
-
|
|
173
|
+
if (!subArgs[1]) {
|
|
174
|
+
console.error(`Google account identifier required: total-recall auth google ${action} <id>`);
|
|
175
|
+
process.exitCode = 1;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (action === 'refresh') {
|
|
179
|
+
await google.access(subArgs[1]);
|
|
180
|
+
const accounts = google.status();
|
|
181
|
+
if (json) console.log(JSON.stringify({ accounts }));
|
|
182
|
+
else console.log(`Account ${subArgs[1]} refreshed.`);
|
|
183
|
+
} else {
|
|
184
|
+
const res = await google.disconnect(subArgs[1]);
|
|
185
|
+
if (json) console.log(JSON.stringify(res));
|
|
186
|
+
else console.log(`Account ${subArgs[1]} disconnected.`);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (!['status', 'connect'].includes(action)) {
|
|
191
|
+
console.error(`Unknown action: ${action}. Use: total-recall auth google [status|connect|accounts|refresh|disconnect]`);
|
|
192
|
+
process.exitCode = 1;
|
|
128
193
|
return;
|
|
129
194
|
}
|
|
130
|
-
if (!['status', 'connect'].includes(action)) throw new Error('Use google status|connect|accounts|refresh|disconnect');
|
|
131
195
|
let cfg;
|
|
132
196
|
try { cfg = google.config(); } catch {
|
|
133
|
-
console.log(JSON.stringify({ configured: false, error: 'Configure GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET and GOOGLE_REDIRECT_URI in auth' }));
|
|
197
|
+
console.log(JSON.stringify({ configured: false, error: 'Configure GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET and GOOGLE_REDIRECT_URI in auth' }));
|
|
198
|
+
process.exitCode = 2;
|
|
199
|
+
return;
|
|
134
200
|
}
|
|
135
201
|
// Browser initiation sets its own HttpOnly state cookie; never manufacture a state-less consent URL in the CLI.
|
|
136
202
|
const authUrl = new URL('/auth/google', cfg.redirectUri).href;
|
|
@@ -138,7 +204,11 @@ async function handleGoogle(subArgs, json) {
|
|
|
138
204
|
console.log(json ? JSON.stringify(out) : `Open sign-in: ${authUrl}`);
|
|
139
205
|
}
|
|
140
206
|
|
|
141
|
-
async function handleProbe(json) {
|
|
207
|
+
async function handleProbe(subArgs, json) {
|
|
208
|
+
if (Array.isArray(subArgs) && (subArgs.includes('--help') || subArgs.includes('-h') || subArgs[0] === 'help')) {
|
|
209
|
+
console.log('Usage: total-recall auth probe [--json]');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
142
212
|
const host = process.env.TR_HOST || 'http://localhost:3000';
|
|
143
213
|
const endpoints = ['/auth/status', '/auth/providers', '/health'];
|
|
144
214
|
const results = [];
|
|
@@ -182,7 +252,11 @@ async function handleProbe(json) {
|
|
|
182
252
|
if (!allOk) process.exitCode = 1;
|
|
183
253
|
}
|
|
184
254
|
|
|
185
|
-
async function handleSession(json) {
|
|
255
|
+
async function handleSession(subArgs, json) {
|
|
256
|
+
if (Array.isArray(subArgs) && (subArgs.includes('--help') || subArgs.includes('-h') || subArgs[0] === 'help')) {
|
|
257
|
+
console.log('Usage: total-recall auth session [--json]');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
186
260
|
const brain = resolveBrainDir();
|
|
187
261
|
const secretExists = Boolean(await resolveSecret('SESSION_SECRET'));
|
|
188
262
|
|
|
@@ -208,7 +282,11 @@ async function handleSecret(subArgs, json) {
|
|
|
208
282
|
await secretCli(forwarded);
|
|
209
283
|
}
|
|
210
284
|
|
|
211
|
-
async function handleAudit(json) {
|
|
285
|
+
async function handleAudit(subArgs, json) {
|
|
286
|
+
if (Array.isArray(subArgs) && (subArgs.includes('--help') || subArgs.includes('-h') || subArgs[0] === 'help')) {
|
|
287
|
+
console.log('Usage: total-recall auth audit [--json]');
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
212
290
|
const brain = resolveBrainDir();
|
|
213
291
|
const webauthnPath = path.join(brain, 'config', 'webauthn.json');
|
|
214
292
|
const findings = [];
|
|
@@ -284,16 +362,41 @@ async function handleAudit(json) {
|
|
|
284
362
|
}
|
|
285
363
|
|
|
286
364
|
async function handleRotate(subArgs, json) {
|
|
365
|
+
if (Array.isArray(subArgs) && (subArgs.includes('--help') || subArgs.includes('-h') || subArgs[0] === 'help')) {
|
|
366
|
+
console.log('Usage: total-recall auth rotate [--collaboration] [--json]');
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
287
369
|
const { createSessionCredentials } = await import('./src/session-credentials.mjs');
|
|
288
370
|
const { defaultBrainDir } = await import('./src/secrets-store.mjs');
|
|
289
371
|
const purpose = subArgs.includes('--collaboration') ? 'collaboration' : 'dashboard';
|
|
290
372
|
const result = createSessionCredentials({ brainDir: process.env.TR_SECRETS_BRAIN || defaultBrainDir() }).rotateSession(purpose);
|
|
291
373
|
console.log(json ? JSON.stringify(result) : 'Auth signing credential rotated; existing sessions are invalidated.');
|
|
292
374
|
}
|
|
375
|
+
|
|
293
376
|
async function handlePassword(args, json) {
|
|
294
|
-
if (args
|
|
377
|
+
if (!Array.isArray(args) || args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
378
|
+
console.log('Usage: total-recall auth password set --stdin [--force-reset]');
|
|
379
|
+
console.log(' total-recall auth password status [--json]');
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (args[0] === 'status') {
|
|
383
|
+
const { createSessionCredentials } = await import('./src/session-credentials.mjs');
|
|
384
|
+
const state = createSessionCredentials({ brainDir: resolveBrainDir() }).dashboardState();
|
|
385
|
+
if (json) {
|
|
386
|
+
console.log(JSON.stringify(state));
|
|
387
|
+
} else {
|
|
388
|
+
console.log(`Dashboard Password: ${state.configured ? 'Configured' : 'Not configured'}`);
|
|
389
|
+
if (state.forcePasswordReset) console.log(' • Password reset is currently enforced');
|
|
390
|
+
}
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (args[0] !== 'set' || !args.includes('--stdin') || args.slice(1).some(value => !['--stdin', '--force-reset'].includes(value))) {
|
|
394
|
+
console.error('Use: total-recall auth password set --stdin [--force-reset]');
|
|
395
|
+
process.exitCode = 1;
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
295
398
|
const chunks = []; let size = 0;
|
|
296
|
-
for await (const chunk of process.stdin) { size += chunk.length; if (size > 8192)
|
|
399
|
+
for await (const chunk of process.stdin) { size += chunk.length; if (size > 8192) { console.error('Password input exceeds limit'); process.exitCode = 1; return; } chunks.push(chunk); }
|
|
297
400
|
const password = Buffer.concat(chunks).toString('utf8').replace(/\r?\n$/, '');
|
|
298
401
|
const { createSessionCredentials } = await import('./src/session-credentials.mjs');
|
|
299
402
|
const result = await createSessionCredentials({ brainDir: resolveBrainDir() }).setDashboardPassword(password, { forcePasswordReset: args.includes('--force-reset') });
|
|
@@ -313,10 +416,10 @@ export async function run(argv = []) {
|
|
|
313
416
|
|
|
314
417
|
switch (command) {
|
|
315
418
|
case 'status':
|
|
316
|
-
await handleStatus(isJson);
|
|
419
|
+
await handleStatus(rest, isJson);
|
|
317
420
|
break;
|
|
318
421
|
case 'audit':
|
|
319
|
-
await handleAudit(isJson);
|
|
422
|
+
await handleAudit(rest, isJson);
|
|
320
423
|
break;
|
|
321
424
|
case 'rotate':
|
|
322
425
|
await handleRotate(rest, isJson);
|
|
@@ -328,32 +431,95 @@ export async function run(argv = []) {
|
|
|
328
431
|
await handlePasskey(rest, isJson);
|
|
329
432
|
break;
|
|
330
433
|
case 'key': {
|
|
434
|
+
if (rest.includes('--help') || rest.includes('-h') || rest[0] === 'help') {
|
|
435
|
+
console.log('Usage: total-recall auth key list [--json]');
|
|
436
|
+
console.log(' total-recall auth key issue <name> --scopes <csv> [--role <role>] [--kind <kind>] [--expires <date>] [--json]');
|
|
437
|
+
console.log(' total-recall auth key revoke <id> [--json]');
|
|
438
|
+
console.log(' total-recall auth key usage [id] [--json]');
|
|
439
|
+
console.log(' total-recall auth key roles [--json]');
|
|
440
|
+
console.log(' total-recall auth key role-set <name> --scopes <csv> [--json]');
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
331
443
|
const { createKeyStore } = await import('./src/access-keys.mjs');
|
|
332
444
|
const store = createKeyStore({ brainDir: resolveBrainDir() });
|
|
333
445
|
const [action = 'list', id] = rest;
|
|
334
446
|
const flag = name => { const at = rest.indexOf(name); return at < 0 ? undefined : rest[at + 1]; };
|
|
335
447
|
let result;
|
|
336
|
-
if (action === 'list')
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
448
|
+
if (action === 'list') {
|
|
449
|
+
const keys = store.loadKeys().map(({ token_hash, ...key }) => key);
|
|
450
|
+
if (isJson) {
|
|
451
|
+
console.log(JSON.stringify(keys));
|
|
452
|
+
} else {
|
|
453
|
+
console.log(`Access Keys: ${keys.length}`);
|
|
454
|
+
for (const k of keys) {
|
|
455
|
+
const status = k.revoked ? '[REVOKED]' : '[ACTIVE]';
|
|
456
|
+
console.log(` • ${status} ${k.name} (${k.token_prefix}…)`);
|
|
457
|
+
console.log(` ID: ${k.id}`);
|
|
458
|
+
console.log(` Scopes: ${(k.scopes || []).join(', ') || 'none'}`);
|
|
459
|
+
if (k.expires_at) console.log(` Expires: ${k.expires_at}`);
|
|
460
|
+
if (k.last_used_at) console.log(` Last used: ${k.last_used_at} (${k.hit_count} hits)`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
break;
|
|
464
|
+
} else if (action === 'issue') {
|
|
465
|
+
result = store.issueKey(id, { scopes: flag('--scopes')?.split(','), role: flag('--role'), kind: flag('--kind'), expires_at: flag('--expires') });
|
|
466
|
+
} else if (action === 'revoke') {
|
|
467
|
+
result = store.revokeKey(id);
|
|
468
|
+
} else if (action === 'usage') {
|
|
469
|
+
result = store.listKeyUsage(id);
|
|
470
|
+
} else if (action === 'roles') {
|
|
471
|
+
result = store.loadRoles();
|
|
472
|
+
} else if (action === 'role-set') {
|
|
473
|
+
result = store.setRole(id, flag('--scopes')?.split(','));
|
|
474
|
+
} else {
|
|
475
|
+
console.error('Use: total-recall auth key list|issue|revoke|usage|roles|role-set');
|
|
476
|
+
process.exitCode = 1;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
if (isJson) {
|
|
480
|
+
console.log(JSON.stringify(result));
|
|
481
|
+
} else {
|
|
482
|
+
if (action === 'issue') {
|
|
483
|
+
console.log(`Key issued: ${result.name} (${result.id})`);
|
|
484
|
+
console.log(`Token: ${result.token}`);
|
|
485
|
+
console.log('Store this token now; it will not be displayed again.');
|
|
486
|
+
} else if (action === 'revoke') {
|
|
487
|
+
console.log(`Key revoked: ${id}`);
|
|
488
|
+
} else {
|
|
489
|
+
console.log(JSON.stringify(result, null, 2));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
344
493
|
}
|
|
345
494
|
case 'agent': {
|
|
346
495
|
// Sign in an agent CLI that authenticates on its own terminal. The pty lives in
|
|
347
496
|
// scripts/agent-login-pty.py because Node has none and this plugin carries no pty library;
|
|
348
497
|
// this routes the plugin's own surface to it.
|
|
498
|
+
if (rest.includes('--help') || rest.includes('-h') || rest[0] === 'help') {
|
|
499
|
+
console.log('Usage: total-recall auth agent list [--json]');
|
|
500
|
+
console.log(' total-recall auth agent login <agent> [--code <code>] [--code-file <path>] [--url-file <path>] [--json]');
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
349
503
|
const { loginAgent, AGENT_LOGIN_PROFILES } = await import('./src/agent-login.mjs');
|
|
350
504
|
const [action = 'list', name] = rest;
|
|
351
505
|
const flag = (n) => { const at = rest.indexOf(n); return at < 0 ? undefined : rest[at + 1]; };
|
|
352
506
|
if (action === 'list') {
|
|
353
|
-
|
|
507
|
+
const list = Object.keys(AGENT_LOGIN_PROFILES).map((id) => ({ id, ...AGENT_LOGIN_PROFILES[id] }));
|
|
508
|
+
if (isJson) {
|
|
509
|
+
console.log(JSON.stringify(list));
|
|
510
|
+
} else {
|
|
511
|
+
console.log('Supported Agent Profiles:');
|
|
512
|
+
for (const item of list) {
|
|
513
|
+
console.log(` • ${item.id}: ${item.name || item.id}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
if (action !== 'login') {
|
|
519
|
+
console.error('Use: total-recall auth agent list | total-recall auth agent login <agent>');
|
|
520
|
+
process.exitCode = 1;
|
|
354
521
|
break;
|
|
355
522
|
}
|
|
356
|
-
if (action !== 'login') throw new Error('Use auth agent list | auth agent login <agent> [--code <code>] [--code-file <path>] [--url-file <path>]');
|
|
357
523
|
const result = await loginAgent({
|
|
358
524
|
agent: name,
|
|
359
525
|
code: flag('--code'),
|
|
@@ -371,10 +537,10 @@ export async function run(argv = []) {
|
|
|
371
537
|
await handleGoogle(rest, isJson);
|
|
372
538
|
break;
|
|
373
539
|
case 'probe':
|
|
374
|
-
await handleProbe(isJson);
|
|
540
|
+
await handleProbe(rest, isJson);
|
|
375
541
|
break;
|
|
376
542
|
case 'session':
|
|
377
|
-
await handleSession(isJson);
|
|
543
|
+
await handleSession(rest, isJson);
|
|
378
544
|
break;
|
|
379
545
|
case 'secret':
|
|
380
546
|
case 'secrets':
|