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.
Files changed (66) hide show
  1. package/bin/total-recall.mjs +17 -4
  2. package/frontend/dist/assets/{index-BPrkIYkZ.js → index-DZ4ypNEl.js} +135 -135
  3. package/frontend/dist/brand/plugins/phone.svg +20 -0
  4. package/frontend/dist/index.html +1 -1
  5. package/frontend/src/App.tsx +3 -30
  6. package/frontend/src/api/plugins.ts +18 -0
  7. package/frontend/src/components/plugins/previews/CodeQualityPreview.tsx +78 -86
  8. package/frontend/src/components/plugins/previews/ComposableCliPreview.tsx +74 -84
  9. package/frontend/src/components/plugins/previews/DecisionPreview.tsx +158 -106
  10. package/frontend/src/components/plugins/previews/DesignPreview.tsx +109 -75
  11. package/frontend/src/components/plugins/previews/DomainsPreview.tsx +69 -88
  12. package/frontend/src/components/plugins/previews/ImageGeneratorPreview.tsx +286 -0
  13. package/frontend/src/components/plugins/previews/PhonePreview.tsx +490 -76
  14. package/frontend/src/components/plugins/previews/SigningPreview.tsx +164 -50
  15. package/frontend/src/components/plugins/previews/TextPreview.tsx +85 -86
  16. package/frontend/src/components/plugins/previews/UserAutomationsPreview.tsx +30 -0
  17. package/frontend/src/components/plugins/previews/index.tsx +6 -1
  18. package/frontend/src/pages/PluginsPage.tsx +603 -136
  19. package/package.json +2 -5
  20. package/scaffold/.agent/skills/plugins/SKILL.md +200 -6
  21. package/src/cli/plugin/create.mjs +156 -11
  22. package/src/cli/plugin/index.mjs +5 -0
  23. package/src/core/embeddings.mjs +2 -0
  24. package/src/core/plugin-loader.mjs +18 -3
  25. package/src/core/plugin-store.mjs +122 -11
  26. package/src/core/surface.mjs +29 -1
  27. package/src/core/vault.mjs +6 -2
  28. package/src/server/route-manifest.json +22 -2
  29. package/src/server/routes/plugins-mesh.mjs +9 -1
  30. package/src/server/routes/plugins.mjs +65 -1
  31. package/frontend/src/pages/AutomationsPage.spec.tsx +0 -61
  32. package/frontend/src/pages/AutomationsPage.tsx +0 -354
  33. package/plugins/creative-search/README.md +0 -33
  34. package/plugins/creative-search/cli.mjs +0 -415
  35. package/plugins/creative-search/config.mjs +0 -129
  36. package/plugins/creative-search/generator.mjs +0 -13
  37. package/plugins/creative-search/plugin.json +0 -61
  38. package/plugins/creative-search/ui/DESIGN.md +0 -50
  39. package/plugins/creative-search/ui/csearch-main.js +0 -314
  40. package/plugins/creative-search/ui/csearch-settings.js +0 -392
  41. package/plugins/creative-search/ui/index.js +0 -2
  42. package/plugins/creative-search/ui/tokens.css +0 -34
  43. package/plugins/dsh/README.md +0 -20
  44. package/plugins/dsh/cli.mjs +0 -232
  45. package/plugins/dsh/generator.mjs +0 -65
  46. package/plugins/dsh/plugin.json +0 -57
  47. package/plugins/dsh/ui/DESIGN.md +0 -48
  48. package/plugins/dsh/ui/dsh-main.js +0 -226
  49. package/plugins/dsh/ui/dsh-status.js +0 -265
  50. package/plugins/dsh/ui/index.js +0 -2
  51. package/plugins/dsh/ui/tokens.css +0 -32
  52. package/plugins/git-sentinel/README.md +0 -29
  53. package/plugins/git-sentinel/cli.mjs +0 -89
  54. package/plugins/git-sentinel/plugin.json +0 -18
  55. package/plugins/operator-alerts/README.md +0 -32
  56. package/plugins/operator-alerts/cli.mjs +0 -57
  57. package/plugins/operator-alerts/notify-core.mjs +0 -216
  58. package/plugins/operator-alerts/plugin.json +0 -20
  59. package/plugins/skill-manager/README.md +0 -57
  60. package/plugins/skill-manager/cli.mjs +0 -123
  61. package/plugins/skill-manager/plugin.json +0 -25
  62. package/plugins/skill-manager/route.mjs +0 -106
  63. package/plugins/system-monitor/README.md +0 -28
  64. package/plugins/system-monitor/cli.mjs +0 -80
  65. package/plugins/system-monitor/generator.mjs +0 -19
  66. 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.35.0",
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: Create, validate, install or share Total Recall plugins.
3
+ description: Standards, authoring guide, and CLI workflows for Total Recall plugins.
4
4
  repo_scoped: false
5
5
  ---
6
6
 
7
- <!-- total-recall:skill-router:v1 -->
7
+ # Total Recall Plugin Standard & Authoring Guide
8
8
 
9
- # Plugins
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
- Use the plugin workspace and current `plugin.json`/extension contracts. Keep optional capabilities in plugins rather than portable memory core. Every claimed command, schedule, UI/API action or memory extension needs a real handler and validation.
11
+ ## Core Architectural Invariants
12
12
 
13
- Read implementation or installation references for that operation. Verify scopes, invalid input, canonical SSSS writes, failure reporting and actual execution. Sharing requires explicit authorization; public bundles exclude credentials, private vault content and runtime state. Read sharing procedures only when sharing is requested.
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
- Before an operation, read its [task references](references/optimized/index.md), including prerequisites and constraints. If the applicable procedure is unclear, read the preserved original instead of guessing. Commands assume the skill root.
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 { validatePluginManifest, projectPluginsDir, globalPluginsDir } from "../../core/plugin-loader.mjs";
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 (arg === "--global" || arg === "-g" || arg === "--with-cli" || arg === "--with-generator") {
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
- const pluginsBaseDir = isGlobal ? globalPluginsDir() : projectPluginsDir(process.cwd());
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
- const pluginDir = path.join(pluginsBaseDir, id);
108
- if (fs.existsSync(pluginDir)) {
109
- console.error(`❌ Error: Plugin directory already exists at ${pluginDir}`);
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 AI OS`;
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
- console.log(`\n🎉 Successfully scaffolded plugin "${pluginName}"!`);
307
- console.log(` Location: \x1b[36m${pluginDir}\x1b[0m`);
308
- console.log(` Manifest: \x1b[33m${path.join(pluginDir, "plugin.json")}\x1b[0m`);
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
+ }
@@ -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:
@@ -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
- return readPluginsDir(bundledPluginsDir(), 'bundled');
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
  /**