total-recall-brain 3.35.0 → 3.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/total-recall.mjs +17 -4
- package/frontend/dist/assets/{index-BPrkIYkZ.js → index-DZ4ypNEl.js} +135 -135
- package/frontend/dist/brand/plugins/phone.svg +20 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/App.tsx +3 -30
- package/frontend/src/api/plugins.ts +18 -0
- package/frontend/src/components/plugins/previews/CodeQualityPreview.tsx +78 -86
- package/frontend/src/components/plugins/previews/ComposableCliPreview.tsx +74 -84
- package/frontend/src/components/plugins/previews/DecisionPreview.tsx +158 -106
- package/frontend/src/components/plugins/previews/DesignPreview.tsx +109 -75
- package/frontend/src/components/plugins/previews/DomainsPreview.tsx +69 -88
- package/frontend/src/components/plugins/previews/ImageGeneratorPreview.tsx +286 -0
- package/frontend/src/components/plugins/previews/PhonePreview.tsx +490 -76
- package/frontend/src/components/plugins/previews/SigningPreview.tsx +164 -50
- package/frontend/src/components/plugins/previews/TextPreview.tsx +85 -86
- package/frontend/src/components/plugins/previews/UserAutomationsPreview.tsx +30 -0
- package/frontend/src/components/plugins/previews/index.tsx +6 -1
- package/frontend/src/pages/PluginsPage.tsx +603 -136
- package/package.json +2 -5
- package/scaffold/.agent/skills/plugins/SKILL.md +200 -6
- package/src/cli/plugin/create.mjs +156 -11
- package/src/cli/plugin/index.mjs +5 -0
- package/src/core/embeddings.mjs +2 -0
- package/src/core/plugin-loader.mjs +18 -3
- package/src/core/plugin-store.mjs +122 -11
- package/src/core/surface.mjs +29 -1
- package/src/core/vault.mjs +6 -2
- package/src/server/route-manifest.json +22 -2
- package/src/server/routes/plugins-mesh.mjs +9 -1
- package/src/server/routes/plugins.mjs +65 -1
- package/frontend/src/pages/AutomationsPage.spec.tsx +0 -61
- package/frontend/src/pages/AutomationsPage.tsx +0 -354
- package/plugins/creative-search/README.md +0 -33
- package/plugins/creative-search/cli.mjs +0 -415
- package/plugins/creative-search/config.mjs +0 -129
- package/plugins/creative-search/generator.mjs +0 -13
- package/plugins/creative-search/plugin.json +0 -61
- package/plugins/creative-search/ui/DESIGN.md +0 -50
- package/plugins/creative-search/ui/csearch-main.js +0 -314
- package/plugins/creative-search/ui/csearch-settings.js +0 -392
- package/plugins/creative-search/ui/index.js +0 -2
- package/plugins/creative-search/ui/tokens.css +0 -34
- package/plugins/dsh/README.md +0 -20
- package/plugins/dsh/cli.mjs +0 -232
- package/plugins/dsh/generator.mjs +0 -65
- package/plugins/dsh/plugin.json +0 -57
- package/plugins/dsh/ui/DESIGN.md +0 -48
- package/plugins/dsh/ui/dsh-main.js +0 -226
- package/plugins/dsh/ui/dsh-status.js +0 -265
- package/plugins/dsh/ui/index.js +0 -2
- package/plugins/dsh/ui/tokens.css +0 -32
- package/plugins/git-sentinel/README.md +0 -29
- package/plugins/git-sentinel/cli.mjs +0 -89
- package/plugins/git-sentinel/plugin.json +0 -18
- package/plugins/operator-alerts/README.md +0 -32
- package/plugins/operator-alerts/cli.mjs +0 -57
- package/plugins/operator-alerts/notify-core.mjs +0 -216
- package/plugins/operator-alerts/plugin.json +0 -20
- package/plugins/skill-manager/README.md +0 -57
- package/plugins/skill-manager/cli.mjs +0 -123
- package/plugins/skill-manager/plugin.json +0 -25
- package/plugins/skill-manager/route.mjs +0 -106
- package/plugins/system-monitor/README.md +0 -28
- package/plugins/system-monitor/cli.mjs +0 -80
- package/plugins/system-monitor/generator.mjs +0 -19
- package/plugins/system-monitor/plugin.json +0 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "total-recall-brain",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.36.0",
|
|
4
4
|
"description": "Personal AI system — database-free, SSSS-powered intelligence engine deployable to any POSIX host.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -22,8 +22,6 @@
|
|
|
22
22
|
"frontend/index.html",
|
|
23
23
|
"frontend/src/",
|
|
24
24
|
"extension/",
|
|
25
|
-
"plugins/",
|
|
26
|
-
"!plugins/**/*.spec.mjs",
|
|
27
25
|
"templates/",
|
|
28
26
|
"scaffold/",
|
|
29
27
|
"docs/ARCHITECTURE.md",
|
|
@@ -47,7 +45,7 @@
|
|
|
47
45
|
"routes:manifest": "node scripts/generate-route-manifest.mjs",
|
|
48
46
|
"clean": "rm -rf scratch/* .remember/* *.log",
|
|
49
47
|
"check:dist": "node scripts/check-dist-freshness.mjs",
|
|
50
|
-
"prepublishOnly": "npm run check:dist",
|
|
48
|
+
"prepublishOnly": "npm run check:dist && node scripts/check-scaffold-state.mjs",
|
|
51
49
|
"check:ssss-registry": "node scripts/check-ssss-registry.mjs"
|
|
52
50
|
},
|
|
53
51
|
"repository": {
|
|
@@ -82,7 +80,6 @@
|
|
|
82
80
|
"natural": "^8.1.1",
|
|
83
81
|
"preact": "^10.29.7",
|
|
84
82
|
"sqlite-vss": "^0.1.2",
|
|
85
|
-
"total-recall-brain": "^3.17.0",
|
|
86
83
|
"ws": "^8.18.0",
|
|
87
84
|
"yaml": "^2.9.0",
|
|
88
85
|
"zod": "^4.4.3"
|
|
@@ -1,15 +1,209 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plugins
|
|
3
|
-
description:
|
|
3
|
+
description: Standards, authoring guide, and CLI workflows for Total Recall plugins.
|
|
4
4
|
repo_scoped: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# Total Recall Plugin Standard & Authoring Guide
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Total Recall capabilities beyond portable memory belong in standalone plugins. This specification details the executable plugin contract, step-by-step authoring procedures, and CLI workflows ensuring any user-authored plugin remains completely compatible across the Total Recall ecosystem.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Core Architectural Invariants
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
1. **Portable Memory Separation**: Core Total Recall maintains only portable memory, the SSSS vault, and instruction compilers. Auxiliary tools, domain features, telephony, specialized analyzers, and user interfaces must exist as independent plugins.
|
|
14
|
+
2. **Standard Manifest (`plugin.json`)**: Every plugin defines an authoritative `plugin.json` adhering to `metadata.plugin.schema.json`.
|
|
15
|
+
3. **Canonical State via SSSS**: Plugins must never perform unmanaged raw filesystem writes. Persistent records, configuration overlays, and event telemetry must use SSSS VFS operations.
|
|
16
|
+
4. **Token-Driven UI Elements**: Plugin interface components must avoid hardcoded color values. Visual styling must reference standard design tokens from `DESIGN.md`.
|
|
17
|
+
5. **No AI Slop & Clean Copy**: Plugin documentation and user-facing text must avoid trite terms such as "sovereign", "synergy", or "leverage", and must never conclude any sentence with a preposition.
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## The Plugin Manifest Contract (`plugin.json`)
|
|
22
|
+
|
|
23
|
+
A valid plugin contains `plugin.json` at its root:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
|
|
28
|
+
"id": "my-plugin",
|
|
29
|
+
"name": "My Plugin",
|
|
30
|
+
"version": "1.0.0",
|
|
31
|
+
"description": "Concrete explanation of the exact job this plugin performs.",
|
|
32
|
+
"author": "Author Name",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"use_cases": ["developer-tools"],
|
|
35
|
+
"cli": {
|
|
36
|
+
"command": "my-tool",
|
|
37
|
+
"handler": "./cli.mjs",
|
|
38
|
+
"subcommands": [
|
|
39
|
+
{ "name": "status", "description": "Display runtime status" },
|
|
40
|
+
{ "name": "run", "description": "Execute main capability" }
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"runtime": {
|
|
44
|
+
"jobs": [
|
|
45
|
+
{
|
|
46
|
+
"id": "periodic-check",
|
|
47
|
+
"schedule": "0 * * * *",
|
|
48
|
+
"command": "status",
|
|
49
|
+
"description": "Hourly status verification"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"daemons": [],
|
|
53
|
+
"startup": []
|
|
54
|
+
},
|
|
55
|
+
"deploy": {
|
|
56
|
+
"targets": ["ssss-app", "nextjs", "react", "flask"],
|
|
57
|
+
"required_ssss_version": ">=0.9.3",
|
|
58
|
+
"access_grants": ["ssss:vault:read", "ssss:vault:write"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Manifest Specifications
|
|
64
|
+
|
|
65
|
+
- **`id`** *(required)*: Lowercase kebab-case string (`^[a-z][a-z0-9-]{1,63}$`). Must match the repository or folder identifier.
|
|
66
|
+
- **`name`** *(required)*: Human-readable display label.
|
|
67
|
+
- **`version`** *(required)*: Semantic version string (`x.y.z`).
|
|
68
|
+
- **`description`** *(required)*: Clear, functional explanation (minimum 5 characters).
|
|
69
|
+
- **`use_cases`** *(optional)*: Array of kebab-case tags used for catalog indexing and search filters.
|
|
70
|
+
- **`cli`** *(optional)*: Declares the CLI command name, the relative ES module handler (`cli.mjs`), and an array of subcommands.
|
|
71
|
+
- **`runtime`** *(optional)*:
|
|
72
|
+
- **`jobs`**: Scheduled background cron routines (`schedule`, `command`, `timeout_seconds`, `secrets`).
|
|
73
|
+
- **`daemons`**: Continuously running background services (`handler`, `restart`, `stop_signal`).
|
|
74
|
+
- **`startup`**: Lifecycle initialization hooks executed when the host begins execution.
|
|
75
|
+
- **`deploy`** *(optional)*: Target application hosts and required SSSS permission scopes.
|
|
76
|
+
- **`ui`** *(optional)*: Declares reusable custom web component elements (`elements[]`), property interfaces, and required design tokens.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Step-by-Step Plugin Creation Workflow
|
|
81
|
+
|
|
82
|
+
### Step 1: Scaffold the Plugin
|
|
83
|
+
Initialize a new plugin structure through the CLI:
|
|
84
|
+
```bash
|
|
85
|
+
total-recall plugin create my-plugin --name "My Plugin" --description "Handles custom workflows" --use-case automation --with-cli
|
|
86
|
+
```
|
|
87
|
+
This scaffolds a standard directory containing `plugin.json`, `cli.mjs`, and documentation.
|
|
88
|
+
|
|
89
|
+
### Step 2: Implement the Command Handler (`cli.mjs`)
|
|
90
|
+
The handler module must export a callable function:
|
|
91
|
+
```javascript
|
|
92
|
+
/**
|
|
93
|
+
* CLI Handler for my-plugin
|
|
94
|
+
* @param {string[]} argv - Command-line arguments passed after the command name
|
|
95
|
+
*/
|
|
96
|
+
export async function run(argv) {
|
|
97
|
+
const [subcommand, ...args] = argv.slice(2);
|
|
98
|
+
|
|
99
|
+
if (subcommand === "status") {
|
|
100
|
+
console.log("My Plugin is fully operational.");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (subcommand === "run") {
|
|
105
|
+
console.log("Executing capability with arguments:", args);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log("Usage: total-recall my-tool <status|run>");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default run;
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Step 3: Add SSSS Memory Categories (Optional)
|
|
116
|
+
If your plugin defines new structured memory types, declare them inside `plugin.json`:
|
|
117
|
+
```json
|
|
118
|
+
"ssss_schemas": {
|
|
119
|
+
"categories": [
|
|
120
|
+
{
|
|
121
|
+
"name": "custom-audit",
|
|
122
|
+
"description": "Structured records for compliance tracking",
|
|
123
|
+
"node_type": "memory",
|
|
124
|
+
"template": "./templates/audit.md"
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Step 4: Validate the Plugin
|
|
131
|
+
Run validation before publishing or deploying:
|
|
132
|
+
```bash
|
|
133
|
+
total-recall plugin info my-plugin
|
|
134
|
+
```
|
|
135
|
+
Ensure the validator returns zero schema errors.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Operating Plugins Through the Total Recall CLI
|
|
140
|
+
|
|
141
|
+
The Total Recall CLI provides complete lifecycle management for plugins:
|
|
142
|
+
|
|
143
|
+
### 1. Discover and Search
|
|
144
|
+
- **List installed plugins**:
|
|
145
|
+
```bash
|
|
146
|
+
total-recall plugin list
|
|
147
|
+
```
|
|
148
|
+
- **List available bundled plugins**:
|
|
149
|
+
```bash
|
|
150
|
+
total-recall plugin available
|
|
151
|
+
```
|
|
152
|
+
- **Search plugins by keyword or use case**:
|
|
153
|
+
```bash
|
|
154
|
+
total-recall plugin search telemetry --use-case monitoring
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 2. Install and Link
|
|
158
|
+
- **Install from a local directory (symlink during active development)**:
|
|
159
|
+
```bash
|
|
160
|
+
total-recall plugin install /path/to/my-plugin --link
|
|
161
|
+
```
|
|
162
|
+
- **Install globally for all workspaces across the machine**:
|
|
163
|
+
```bash
|
|
164
|
+
total-recall plugin install /path/to/my-plugin --global
|
|
165
|
+
```
|
|
166
|
+
- **Install from a remote git repository**:
|
|
167
|
+
```bash
|
|
168
|
+
total-recall plugin install https://github.com/org/plugin-repo.git
|
|
169
|
+
```
|
|
170
|
+
- **Install from a mesh peer node**:
|
|
171
|
+
```bash
|
|
172
|
+
total-recall plugin install peer:mac-mini/code-quality
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 3. Inspect and Execute
|
|
176
|
+
- **Inspect detailed plugin metadata, hashes, and scheduled task logs**:
|
|
177
|
+
```bash
|
|
178
|
+
total-recall plugin info my-plugin
|
|
179
|
+
```
|
|
180
|
+
- **Execute installed plugin commands directly**:
|
|
181
|
+
```bash
|
|
182
|
+
total-recall my-tool status
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 4. Share and Distribute
|
|
186
|
+
- **Share a plugin via a verified hash-pinned link**:
|
|
187
|
+
```bash
|
|
188
|
+
total-recall plugin share my-plugin
|
|
189
|
+
```
|
|
190
|
+
- **Revoke sharing access**:
|
|
191
|
+
```bash
|
|
192
|
+
total-recall plugin unshare my-plugin
|
|
193
|
+
```
|
|
194
|
+
- **Remove or uninstall a plugin**:
|
|
195
|
+
```bash
|
|
196
|
+
total-recall plugin remove my-plugin
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Authoring Standards Checklist
|
|
202
|
+
|
|
203
|
+
Before distributing any plugin, confirm each requirement:
|
|
204
|
+
- [ ] `plugin.json` passes validation against `metadata.plugin.schema.json`.
|
|
205
|
+
- [ ] CLI commands exit with code `0` on success and non-zero on failure.
|
|
206
|
+
- [ ] No private secrets, personal user paths, or session tokens exist in the repository.
|
|
207
|
+
- [ ] SSSS data modifications use validated operations rather than raw disk writes.
|
|
208
|
+
- [ ] User interface components utilize design tokens without hardcoded colors.
|
|
209
|
+
- [ ] All written documentation avoids forbidden words and concludes every sentence without a preposition.
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { validatePluginManifest } from "../../core/plugin-loader.mjs";
|
|
5
|
+
import { installPlugin } from "../../core/plugin-store.mjs";
|
|
6
|
+
import { parseCron } from "../../core/plugin-tasks.mjs";
|
|
7
|
+
import { generateUiElements } from "../../core/app-deploy/ui-elements.mjs";
|
|
4
8
|
|
|
5
9
|
function toTitleCase(kebab) {
|
|
6
10
|
return kebab
|
|
@@ -13,19 +17,29 @@ export async function createPlugin(args = []) {
|
|
|
13
17
|
const isGlobal = args.includes("--global") || args.includes("-g");
|
|
14
18
|
const withCli = args.includes("--with-cli");
|
|
15
19
|
const withGenerator = args.includes("--with-generator");
|
|
20
|
+
const withUi = args.includes("--with-ui");
|
|
21
|
+
const withTask = args.includes("--with-task");
|
|
22
|
+
const createGithub = args.includes("--github");
|
|
23
|
+
const publicRepo = args.includes("--public");
|
|
24
|
+
const skipInstall = args.includes("--no-install");
|
|
25
|
+
let repoDirArg = null;
|
|
16
26
|
|
|
17
27
|
let name = null;
|
|
18
28
|
let description = null;
|
|
19
29
|
let category = null;
|
|
20
30
|
let fromSkillPath = null;
|
|
31
|
+
let taskCommand = null;
|
|
32
|
+
let taskSchedule = null;
|
|
33
|
+
let taskIntent = null;
|
|
21
34
|
const useCases = [];
|
|
22
35
|
|
|
23
36
|
const cleanArgs = [];
|
|
24
37
|
for (let i = 0; i < args.length; i++) {
|
|
25
38
|
const arg = args[i];
|
|
26
|
-
if (
|
|
39
|
+
if (["--global", "-g", "--with-cli", "--with-generator", "--with-ui", "--with-task", "--with-skill", "--capability", "--github", "--public", "--no-install"].includes(arg)) {
|
|
27
40
|
continue;
|
|
28
41
|
}
|
|
42
|
+
if (arg === "--repo-dir" && args[i + 1]) { repoDirArg = args[++i]; continue; }
|
|
29
43
|
if (arg === "--name" && args[i + 1]) {
|
|
30
44
|
name = args[i + 1];
|
|
31
45
|
i++;
|
|
@@ -51,6 +65,9 @@ export async function createPlugin(args = []) {
|
|
|
51
65
|
i++;
|
|
52
66
|
continue;
|
|
53
67
|
}
|
|
68
|
+
if (arg === "--task-command" && args[i + 1]) { taskCommand = args[++i]; continue; }
|
|
69
|
+
if (arg === "--task-schedule" && args[i + 1]) { taskSchedule = args[++i]; continue; }
|
|
70
|
+
if (arg === "--task-intent" && args[i + 1]) { taskIntent = args[++i]; continue; }
|
|
54
71
|
cleanArgs.push(arg);
|
|
55
72
|
}
|
|
56
73
|
|
|
@@ -102,18 +119,28 @@ export async function createPlugin(args = []) {
|
|
|
102
119
|
process.exit(1);
|
|
103
120
|
}
|
|
104
121
|
|
|
105
|
-
|
|
122
|
+
if (withTask) {
|
|
123
|
+
if (!taskCommand || !/^[a-z][a-z0-9-]*$/.test(taskCommand) || !taskSchedule || !taskIntent) {
|
|
124
|
+
throw new Error('--with-task requires --task-command <subcommand>, --task-schedule "<five-field cron>", and --task-intent <description>');
|
|
125
|
+
}
|
|
126
|
+
parseCron(taskSchedule);
|
|
127
|
+
} else if (taskCommand || taskSchedule || taskIntent) {
|
|
128
|
+
throw new Error('--task-* options require --with-task');
|
|
129
|
+
}
|
|
106
130
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
131
|
+
// Every plugin is its own repository (tr-plugin-<id>). The repo root IS the
|
|
132
|
+
// plugin directory (plugin.json at the root); the project/global install is
|
|
133
|
+
// a symlink to it, so edits in the repo are what runs.
|
|
134
|
+
const pluginDir = path.resolve(repoDirArg || path.join(path.dirname(process.cwd()), `tr-plugin-${id}`));
|
|
135
|
+
if (fs.existsSync(pluginDir) && fs.readdirSync(pluginDir).length > 0) {
|
|
136
|
+
console.error(`❌ Error: Plugin repository directory already exists and is not empty: ${pluginDir}`);
|
|
110
137
|
process.exit(1);
|
|
111
138
|
}
|
|
112
139
|
|
|
113
140
|
fs.mkdirSync(pluginDir, { recursive: true });
|
|
114
141
|
|
|
115
142
|
const pluginName = name || skillFrontmatter.name || toTitleCase(id);
|
|
116
|
-
const pluginDesc = description || skillFrontmatter.description || `${pluginName} extension for Total Recall
|
|
143
|
+
const pluginDesc = description || skillFrontmatter.description || `${pluginName} extension for Total Recall`;
|
|
117
144
|
|
|
118
145
|
const manifest = {
|
|
119
146
|
$schema: "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
|
|
@@ -140,7 +167,7 @@ export async function createPlugin(args = []) {
|
|
|
140
167
|
const withSkill = args.includes("--with-skill") || args.includes("--capability") || Boolean(fromSkillPath);
|
|
141
168
|
const isCapability = args.includes("--capability") || Boolean(fromSkillPath);
|
|
142
169
|
|
|
143
|
-
if (withCli || isCapability) {
|
|
170
|
+
if (withCli || isCapability || withTask) {
|
|
144
171
|
manifest.cli = {
|
|
145
172
|
command: id,
|
|
146
173
|
handler: "./cli.mjs",
|
|
@@ -171,6 +198,7 @@ export async function run(argv = []) {
|
|
|
171
198
|
: \`${pluginName}: scaffold only — no subcommands implemented yet.\`);
|
|
172
199
|
return;
|
|
173
200
|
}
|
|
201
|
+
|
|
174
202
|
if (IMPLEMENTED[sub]) return IMPLEMENTED[sub](args.slice(1));
|
|
175
203
|
|
|
176
204
|
console.error(\`${pluginName}: "\${sub}" is not implemented.\`);
|
|
@@ -181,6 +209,21 @@ export default run;
|
|
|
181
209
|
fs.writeFileSync(path.join(pluginDir, "cli.mjs"), cliContent, "utf8");
|
|
182
210
|
}
|
|
183
211
|
|
|
212
|
+
if (withTask) {
|
|
213
|
+
manifest.tasks = [{ intent: taskIntent, schedule: taskSchedule, command: taskCommand, placement: 'selected-node' }];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (withUi) {
|
|
217
|
+
manifest.ui = {
|
|
218
|
+
design_tokens: 'ui/DESIGN.md',
|
|
219
|
+
elements: [{ id: 'panel', kind: 'panel', tag: `${id}-panel`, module: 'ui/panel.js', description: `${pluginName} panel`, events: [], tokens: ['color-text', 'color-surface', 'color-border', 'spacing-md', 'radius-md'], optional_tokens: [] }],
|
|
220
|
+
};
|
|
221
|
+
const panelClass = `${id.split('-').map((part) => part[0].toUpperCase() + part.slice(1)).join('')}Panel`;
|
|
222
|
+
fs.mkdirSync(path.join(pluginDir, 'ui'), { recursive: true });
|
|
223
|
+
fs.writeFileSync(path.join(pluginDir, 'ui', 'DESIGN.md'), '---\ncolors:\n text: "#20242a"\n surface: "#ffffff"\n border: "#dce2e8"\nspacing:\n md: 12px\nrounded:\n md: 8px\n---\n\n# Design tokens\n\nReplace these defaults with the host design tokens.\n');
|
|
224
|
+
fs.writeFileSync(path.join(pluginDir, 'ui', 'panel.js'), `class ${panelClass} extends HTMLElement {\n connectedCallback() {\n const root = this.shadowRoot || this.attachShadow({ mode: 'open' });\n root.replaceChildren();\n const style = document.createElement('style');\n style.textContent = ':host { display:block; color:var(--color-text); background:var(--color-surface); border:1px solid var(--color-border); border-radius:var(--radius-md); padding:var(--spacing-md); }';\n const text = document.createElement('p');\n text.textContent = ${JSON.stringify(`${pluginName} panel scaffold. Add the actual capability before sharing.`)};\n root.append(style, text);\n }\n}\ncustomElements.define('${id}-panel', ${panelClass});\n`);
|
|
225
|
+
}
|
|
226
|
+
|
|
184
227
|
if (isCapability) {
|
|
185
228
|
manifest.deploy = {
|
|
186
229
|
targets: ["ssss-app", "nextjs", "react", "flask"],
|
|
@@ -269,6 +312,10 @@ export default generateContext;
|
|
|
269
312
|
fs.rmSync(pluginDir, { recursive: true, force: true });
|
|
270
313
|
process.exit(1);
|
|
271
314
|
}
|
|
315
|
+
if (withUi) {
|
|
316
|
+
try { generateUiElements(manifest, { pluginDir, target: 'web-components' }); }
|
|
317
|
+
catch (error) { fs.rmSync(pluginDir, { recursive: true, force: true }); throw error; }
|
|
318
|
+
}
|
|
272
319
|
|
|
273
320
|
fs.writeFileSync(
|
|
274
321
|
path.join(pluginDir, "plugin.json"),
|
|
@@ -299,17 +346,115 @@ export default generateContext;
|
|
|
299
346
|
if (useCases.length) lines.push(`- **Use cases**: ${useCases.join(", ")}`);
|
|
300
347
|
if (category) lines.push(`- **SSSS Category**: \`${category}\``);
|
|
301
348
|
if (withCli) lines.push(`- **CLI Command**: \`npx total-recall ${id}\``);
|
|
349
|
+
if (withTask) lines.push(`- **Scheduled task**: \`${taskCommand}\` at \`${taskSchedule}\` (selected node; implement the command and choose a node before use)`);
|
|
350
|
+
if (withUi) lines.push('- **UI**: `ui/panel.js` custom element with DESIGN.md tokens');
|
|
302
351
|
lines.push("");
|
|
303
352
|
|
|
304
353
|
fs.writeFileSync(path.join(pluginDir, "README.md"), lines.join("\n"), "utf8");
|
|
305
354
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
355
|
+
// ── Standalone repository files ──────────────────────────────────────────
|
|
356
|
+
const holder = gitOutput(['config', 'user.name']) || 'Plugin authors';
|
|
357
|
+
fs.writeFileSync(path.join(pluginDir, 'package.json'), JSON.stringify({
|
|
358
|
+
name: `tr-plugin-${id}`,
|
|
359
|
+
version: '0.1.0',
|
|
360
|
+
private: true,
|
|
361
|
+
type: 'module',
|
|
362
|
+
description: pluginDesc,
|
|
363
|
+
scripts: { test: 'node --test test/*.test.mjs' },
|
|
364
|
+
devDependencies: { 'total-recall-brain': '^3.35.0' },
|
|
365
|
+
license: 'MIT'
|
|
366
|
+
}, null, 2) + '\n', 'utf8');
|
|
367
|
+
fs.writeFileSync(path.join(pluginDir, '.gitignore'), 'node_modules/\n.env*\n.agent/skills/total-recall/\ndist/\nreports/\n.DS_Store\nINSTRUCTIONS.md\nAGENTS.md\nGEMINI.md\n', 'utf8');
|
|
368
|
+
fs.writeFileSync(path.join(pluginDir, 'LICENSE'), mitLicense(holder), 'utf8');
|
|
369
|
+
fs.mkdirSync(path.join(pluginDir, 'test'), { recursive: true });
|
|
370
|
+
fs.writeFileSync(path.join(pluginDir, 'test', 'manifest.test.mjs'), `import test from 'node:test';
|
|
371
|
+
import assert from 'node:assert/strict';
|
|
372
|
+
import fs from 'node:fs';
|
|
373
|
+
import path from 'node:path';
|
|
374
|
+
import { fileURLToPath } from 'node:url';
|
|
375
|
+
|
|
376
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
377
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'plugin.json'), 'utf8'));
|
|
378
|
+
|
|
379
|
+
test('manifest identifies the plugin', () => {
|
|
380
|
+
assert.equal(manifest.id, ${JSON.stringify(id)});
|
|
381
|
+
assert.match(manifest.version, /^\\\\d+\\\\.\\\\d+\\\\.\\\\d+/);
|
|
382
|
+
assert.equal(manifest.license, 'MIT');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('every file the manifest references exists', () => {
|
|
386
|
+
const refs = [manifest.cli?.handler, manifest.compile?.generator, manifest.ui?.design_tokens, ...(manifest.skills || []).map((s) => s.path), ...(manifest.ui?.elements || []).map((e) => e.module)].filter(Boolean);
|
|
387
|
+
for (const ref of refs) assert.ok(fs.existsSync(path.join(root, ref)), \`missing \${ref}\`);
|
|
388
|
+
});
|
|
389
|
+
`, 'utf8');
|
|
390
|
+
|
|
391
|
+
gitOutput(['init', '-q', '-b', 'main'], pluginDir);
|
|
392
|
+
gitOutput(['remote', 'add', 'origin', `https://github.com/${githubOwner() || 'OWNER'}/tr-plugin-${id}.git`], pluginDir);
|
|
393
|
+
|
|
394
|
+
// ── Install: a link to the repo, so the repo is what runs ───────────────
|
|
395
|
+
let installed = false;
|
|
396
|
+
if (!skipInstall) {
|
|
397
|
+
await installPlugin(pluginDir, { link: true, global: isGlobal, projectRoot: process.cwd() });
|
|
398
|
+
installed = true;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ── Optional GitHub repository (private unless --public) ────────────────
|
|
402
|
+
let githubUrl = null;
|
|
403
|
+
if (createGithub) {
|
|
404
|
+
const out = spawnSync('gh', ['repo', 'create', `tr-plugin-${id}`, publicRepo ? '--public' : '--private', '--description', pluginDesc, '--source', pluginDir, '--remote', 'origin'], { encoding: 'utf8' });
|
|
405
|
+
if (out.status !== 0) {
|
|
406
|
+
console.error(`❌ GitHub repository creation failed: ${(out.stderr || out.stdout || '').trim()}`);
|
|
407
|
+
process.exitCode = 1;
|
|
408
|
+
} else {
|
|
409
|
+
githubUrl = (out.stdout || '').trim().split('\n').pop();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
console.log(`\n🎉 Successfully scaffolded plugin "${pluginName}" as its own repository!`);
|
|
414
|
+
console.log(` Repository: \x1b[36m${pluginDir}\x1b[0m`);
|
|
415
|
+
console.log(` Manifest: \x1b[33m${path.join(pluginDir, "plugin.json")}\x1b[0m`);
|
|
309
416
|
if (withCli) {
|
|
310
417
|
console.log(` CLI Handler: \x1b[32m${path.join(pluginDir, "cli.mjs")}\x1b[0m`);
|
|
311
418
|
}
|
|
419
|
+
console.log(` Installed: ${installed ? `linked into ${isGlobal ? 'the global' : 'this project\'s'} plugins` : 'no (--no-install)'}`);
|
|
420
|
+
console.log(` GitHub: ${githubUrl || (createGithub ? 'creation failed' : 'not created (re-run with --github, or: gh repo create tr-plugin-' + id + ' --private --source ' + pluginDir + ')')}`);
|
|
312
421
|
console.log(`\nNext steps:`);
|
|
422
|
+
console.log(` Test: \x1b[32mcd ${pluginDir} && npm test\x1b[0m`);
|
|
313
423
|
console.log(` Inspect: \x1b[32mnpx total-recall plugin info ${id}\x1b[0m`);
|
|
314
424
|
console.log(` List: \x1b[32mnpx total-recall plugin list\x1b[0m\n`);
|
|
315
425
|
}
|
|
426
|
+
|
|
427
|
+
function gitOutput(gitArgs, cwd) {
|
|
428
|
+
const out = spawnSync('git', gitArgs, { cwd, encoding: 'utf8' });
|
|
429
|
+
return out.status === 0 ? (out.stdout || '').trim() : '';
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function githubOwner() {
|
|
433
|
+
const out = spawnSync('gh', ['api', 'user', '--jq', '.login'], { encoding: 'utf8' });
|
|
434
|
+
return out.status === 0 ? (out.stdout || '').trim() : '';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function mitLicense(holder) {
|
|
438
|
+
return `MIT License
|
|
439
|
+
|
|
440
|
+
Copyright (c) ${new Date().getFullYear()} ${holder}
|
|
441
|
+
|
|
442
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
443
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
444
|
+
in the Software without restriction, including without limitation the rights
|
|
445
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
446
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
447
|
+
furnished to do so, subject to the following conditions:
|
|
448
|
+
|
|
449
|
+
The above copyright notice and this permission notice shall be included in all
|
|
450
|
+
copies or substantial portions of the Software.
|
|
451
|
+
|
|
452
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
453
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
454
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
455
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
456
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
457
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
458
|
+
SOFTWARE.
|
|
459
|
+
`;
|
|
460
|
+
}
|
package/src/cli/plugin/index.mjs
CHANGED
|
@@ -44,6 +44,11 @@ Commands:
|
|
|
44
44
|
--category <cat> Declare an SSSS memory category
|
|
45
45
|
--with-cli Generate CLI command handler
|
|
46
46
|
--with-generator Generate context generator
|
|
47
|
+
--with-ui Generate token-driven panel element
|
|
48
|
+
--with-task Declare a selected-node scheduled task
|
|
49
|
+
--task-command <cmd> Task CLI subcommand (requires --with-task)
|
|
50
|
+
--task-schedule <cron> Five-field local-time schedule
|
|
51
|
+
--task-intent <text> Human description of the task
|
|
47
52
|
--global, -g Create machine-wide
|
|
48
53
|
|
|
49
54
|
Examples:
|
package/src/core/embeddings.mjs
CHANGED
|
@@ -449,6 +449,8 @@ export function nodeToEmbedText(node) {
|
|
|
449
449
|
parts.push(`Title: ${title}`);
|
|
450
450
|
if (node.category) parts.push(`Category: ${node.category}`);
|
|
451
451
|
if (node.tags?.length) parts.push(`Tags: ${node.tags.join(', ')}`);
|
|
452
|
+
if (node.description) parts.push(`Description: ${node.description}`);
|
|
453
|
+
if (node.commands?.length) parts.push(`Commands: ${node.commands.join(', ')}`);
|
|
452
454
|
if (node.body) parts.push(`Body: ${node.body.slice(0, 2000)}`);
|
|
453
455
|
return parts.join('\n\n');
|
|
454
456
|
}
|
|
@@ -347,9 +347,24 @@ export function discoverPlugins(projectRoot = process.cwd()) {
|
|
|
347
347
|
return plugins;
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
/** Plugins shipped inside the total-recall package, installable on any node. */
|
|
351
|
-
export function listBundledPlugins() {
|
|
352
|
-
|
|
350
|
+
/** Plugins shipped inside the total-recall package or repository, installable on any node. */
|
|
351
|
+
export function listBundledPlugins(projectRoot = process.cwd()) {
|
|
352
|
+
const seen = new Set();
|
|
353
|
+
const out = [];
|
|
354
|
+
const dirs = [
|
|
355
|
+
bundledPluginsDir(),
|
|
356
|
+
path.join(PACKAGE_ROOT, '.agent', 'skills', 'total-recall', 'plugins')
|
|
357
|
+
];
|
|
358
|
+
for (const dir of dirs) {
|
|
359
|
+
if (!fs.existsSync(dir)) continue;
|
|
360
|
+
for (const p of readPluginsDir(dir, 'bundled')) {
|
|
361
|
+
if (!seen.has(p.id)) {
|
|
362
|
+
seen.add(p.id);
|
|
363
|
+
out.push(p);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return out;
|
|
353
368
|
}
|
|
354
369
|
|
|
355
370
|
/**
|