total-recall-brain 3.40.0 → 3.40.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +152 -41
  2. package/bin/total-recall.mjs +16 -76
  3. package/docs/reference/cli-reference.md +10 -10
  4. package/extension/background.js +6 -2
  5. package/extension/config.js +8 -0
  6. package/extension/lib/brain-client.js +29 -4
  7. package/extension/options/options.html +1 -0
  8. package/extension/sidepanel/sidepanel.html +1 -0
  9. package/frontend/dist/assets/{index-CvQRdT2j.css → index-AGUes-VG.css} +1 -1
  10. package/frontend/dist/assets/index-Bt5iVPZj.js +5618 -0
  11. package/frontend/dist/index.html +2 -2
  12. package/frontend/src/api/keys.ts +9 -2
  13. package/frontend/src/api/plugins.ts +4 -0
  14. package/frontend/src/components/plugins/card/CardEngine.tsx +2 -1
  15. package/frontend/src/components/plugins/card/card.css +1 -1
  16. package/frontend/src/pages/IntegrationsPage.tsx +315 -8
  17. package/frontend/src/pages/PluginsPage.tsx +532 -87
  18. package/package.json +6 -5
  19. package/scaffold/.agent/skills/meta-harness/references/optimized/original.md +1 -1
  20. package/scaffold/.agent/skills/meta-harness/references/optimized/section-09.md +1 -1
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +10 -10
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -6
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +0 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/src/browser-session.mjs +309 -4
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +44 -34
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/src/google-oauth.mjs +6 -4
  27. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-catalog.mjs +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-rotation-recipes.mjs +118 -4
  29. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/auth.mjs +2 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +3 -3
  31. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +1 -1
  32. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-rotate.mjs +24 -6
  33. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +164 -123
  34. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +65 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +76 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +19 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +290 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +446 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +152 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +71 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +36 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +35 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +175 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +18 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/code-quality/LICENSE +21 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/code-quality/README.md +84 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +79 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/check.mjs +6 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/report.mjs +6 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +27 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/snapshot.mjs +163 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +757 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/config.schema.json +86 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/detect.mjs +89 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +145 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +210 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/state.mjs +20 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +2869 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +28 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +186 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +18 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +272 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +129 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/LICENSE +21 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/README.md +73 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/cli.mjs +375 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/echo-tool.mjs +23 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/group.mjs +262 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/project-id.mjs +74 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +2869 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +31 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +221 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/schemas/cli_command.schema.json +107 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/skills/composable-cli/SKILL.md +63 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +313 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compile.mjs +19 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compose.mjs +144 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/contract.mjs +110 -0
  79. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/library.mjs +23 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/registry.mjs +192 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/resolve.mjs +221 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/risk.mjs +126 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/ssss-documents.mjs +29 -0
  84. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +18 -0
  85. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +273 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/embeddings/LICENSE +21 -0
  87. package/scaffold/.agent/skills/total-recall/plugins/embeddings/README.md +20 -0
  88. package/scaffold/.agent/skills/total-recall/plugins/embeddings/cli.mjs +310 -0
  89. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +2826 -0
  90. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +11 -0
  91. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +62 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +297 -0
  93. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/LICENSE +21 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +43 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/cli.mjs +258 -0
  96. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4005 -0
  97. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +20 -0
  98. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +125 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +18 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +278 -0
  101. package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +9 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +6 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4721 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -21
  105. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +14 -1
  106. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +10 -0
  107. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/headscale-client.mjs +5 -1
  108. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +4 -13
  109. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-access-verify.mjs +22 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-files.mjs +18 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +2 -0
  112. package/scaffold/.agent/skills/total-recall/plugins/mesh/test-support/memory-root.mjs +14 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/openwiki/LICENSE +21 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +35 -0
  115. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +124 -0
  116. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4097 -0
  117. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +12 -0
  118. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +165 -0
  119. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +127 -0
  120. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +18 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +279 -0
  122. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +539 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/security/LICENSE +21 -0
  124. package/scaffold/.agent/skills/total-recall/plugins/security/README.md +67 -0
  125. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +932 -0
  126. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3256 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +25 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +297 -0
  129. package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +4 -0
  130. package/scaffold/.agent/skills/total-recall/plugins/security/skills/security/SKILL.md +37 -0
  131. package/scaffold/.agent/skills/total-recall/plugins/security/src/android.mjs +21 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/security/src/devices.mjs +94 -0
  133. package/scaffold/.agent/skills/total-recall/plugins/security/src/machines.mjs +164 -0
  134. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +41 -0
  135. package/scaffold/.agent/skills/total-recall/plugins/security/src/windows.mjs +42 -0
  136. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +18 -0
  137. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +95 -0
  138. package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +98 -0
  139. package/scaffold/.agent/skills/total-recall/references/auth.md +1 -1
  140. package/src/cli/backup.mjs +71 -3
  141. package/src/cli/brief.mjs +33 -9
  142. package/src/cli/command.mjs +2 -7
  143. package/src/cli/config.mjs +1 -0
  144. package/src/cli/connect.mjs +5 -0
  145. package/src/cli/context.mjs +2 -1
  146. package/src/cli/daemon.mjs +36 -1
  147. package/src/cli/doctor.mjs +6 -0
  148. package/src/cli/harness.mjs +3 -0
  149. package/src/cli/help.mjs +26 -0
  150. package/src/cli/init.mjs +4 -4
  151. package/src/cli/memory-entry.mjs +1 -0
  152. package/src/cli/memory-init.mjs +2 -0
  153. package/src/cli/plugin/index.mjs +11 -0
  154. package/src/cli/plugin/install.mjs +2 -1
  155. package/src/cli/rebuild.mjs +3 -1
  156. package/src/cli/startup.mjs +2 -1
  157. package/src/cli/update.mjs +19 -0
  158. package/src/core/automatic-brain-updates.mjs +67 -0
  159. package/src/core/brain-updates.mjs +21 -2
  160. package/src/core/cli-commands.mjs +71 -0
  161. package/src/core/cli-maintenance.mjs +45 -0
  162. package/src/core/cli-quick-reference.mjs +20 -0
  163. package/src/core/cli-semantic-index.mjs +722 -0
  164. package/src/core/context-compiler.mjs +27 -1
  165. package/src/core/crons.mjs +2 -13
  166. package/src/core/daemon-control.mjs +6 -4
  167. package/src/core/daemon-loop.mjs +12 -8
  168. package/src/core/default-plugins.mjs +5 -0
  169. package/src/core/global-brain-updates.mjs +39 -0
  170. package/src/core/mesh-brain-updates.mjs +30 -0
  171. package/src/core/meta-harness.mjs +108 -0
  172. package/src/core/plugin-dependencies.mjs +2 -1
  173. package/src/core/plugin-loader.mjs +2 -1
  174. package/src/core/plugin-runner.mjs +12 -4
  175. package/src/core/plugin-setup.mjs +20 -0
  176. package/src/core/plugin-store.mjs +22 -4
  177. package/src/core/plugin-tasks.mjs +1 -1
  178. package/src/core/project-brain.mjs +2 -0
  179. package/src/core/project-status.mjs +64 -0
  180. package/src/core/required-plugins.mjs +46 -0
  181. package/src/core/scaffold-plugins.mjs +17 -3
  182. package/src/core/source-brain-updates.mjs +47 -0
  183. package/src/core/startup-health.mjs +5 -3
  184. package/src/core/surface.mjs +44 -10
  185. package/src/server/routes/extension.mjs +53 -8
  186. package/templates/onboarding-interview.md +1 -1
  187. package/frontend/dist/assets/index-SOVUlZqm.js +0 -5596
@@ -0,0 +1,262 @@
1
+ /**
2
+ * total-recall group <create|add|remove|list|tree|members|delete|show> …
3
+ *
4
+ * Manage groups of projects and groups of groups.
5
+ * Groups are SSSS project_group documents in the global brain.
6
+ *
7
+ * v2 example demonstrating the composable CLI contract with meta export
8
+ * and process.exitCode usage instead of process.exit().
9
+ *
10
+ * Run: total-recall group [--json] <subcommand> ...
11
+ */
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import os from 'node:os';
15
+ import { randomUUID } from 'node:crypto';
16
+
17
+ export const meta = {
18
+ name: 'group',
19
+ description: 'Manage groups of projects and groups of groups (create, add, remove, list, show, tree, members, delete)',
20
+ risk: 'write',
21
+ version: 2,
22
+ background: false,
23
+ };
24
+
25
+ const DIR = path.join(os.homedir(), '.agent', 'skills', 'total-recall', 'groups');
26
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
27
+
28
+ function die(m, exitCode = 1) {
29
+ process.exitCode = exitCode;
30
+ return { ok: false, error: m, exitCode };
31
+ }
32
+
33
+ const load = () => {
34
+ if (!fs.existsSync(DIR)) return [];
35
+ return fs.readdirSync(DIR).filter((f) => f.endsWith('.md')).map((f) => {
36
+ const raw = fs.readFileSync(path.join(DIR, f), 'utf8');
37
+ const m = raw.match(/^---\n([\s\S]*?)\n---/);
38
+ if (!m) return null;
39
+ const doc = {};
40
+ for (const line of m[1].split('\n')) {
41
+ const ci = line.indexOf(':');
42
+ if (ci < 0) continue;
43
+ const key = line.slice(0, ci).trim();
44
+ let val = line.slice(ci + 1).trim();
45
+ if (val.startsWith('"') && val.endsWith('"')) val = val.slice(1, -1);
46
+ if (val === 'true') val = true;
47
+ else if (val === 'false') val = false;
48
+ else if (/^\d+$/.test(val)) val = parseInt(val, 10);
49
+ doc[key] = val;
50
+ }
51
+ if (doc.type !== 'project_group') return null;
52
+ // Restore members array from YAML-like list
53
+ const membersMatch = m[1].match(/members:\s*(\[[\s\S]*?\])\s*\n/);
54
+ if (membersMatch) {
55
+ try {
56
+ doc.members = JSON.parse(membersMatch[1].replace(/'/g, '"'));
57
+ } catch {
58
+ doc.members = [];
59
+ }
60
+ } else {
61
+ doc.members = [];
62
+ }
63
+ return doc;
64
+ }).filter(Boolean);
65
+ };
66
+
67
+ function save(g, groups) {
68
+ g.timestamp = new Date().toISOString();
69
+ const body = `---\ntype: project_group\nid: ${g.id}\ntitle: ${g.title}\ndescription: "${(g.description || '').replace(/"/g, '\\"')}"\nmembers: ${JSON.stringify(g.members || [])}\ntimestamp: ${g.timestamp}\n---\n\n# ${g.title}\n\n${g.description || 'Project group.'}\n`;
70
+ fs.mkdirSync(DIR, { recursive: true });
71
+ const file = path.join(DIR, `${g.id}.md`);
72
+ const tmp = `${file}.tmp-${process.pid}`;
73
+ fs.writeFileSync(tmp, body, { mode: 0o644 });
74
+ fs.renameSync(tmp, file);
75
+ }
76
+
77
+ function flattenMembers(g, byId, seen = new Set()) {
78
+ const out = new Map();
79
+ if (seen.has(g.id)) return out;
80
+ seen.add(g.id);
81
+ for (const m of g.members || []) {
82
+ if (m.type === 'project') out.set(m.id, m);
83
+ else if (byId.has(m.id)) {
84
+ for (const [k, v] of flattenMembers(byId.get(m.id), byId, seen)) out.set(k, v);
85
+ }
86
+ }
87
+ return out;
88
+ }
89
+
90
+ function reaches(from, target, byId, seen = new Set()) {
91
+ if (from === target) return true;
92
+ if (seen.has(from)) return false;
93
+ seen.add(from);
94
+ return (byId.get(from)?.members || []).some((m) => m.type === 'group' && reaches(m.id, target, byId, seen));
95
+ }
96
+
97
+ function findGroup(ref, groups) {
98
+ return groups.find((g) => g.id === ref) || groups.filter((g) => g.title === ref).at(0) || null;
99
+ }
100
+
101
+ function findAll(ref, groups) {
102
+ return groups.filter((g) => g.id === ref || g.title === ref);
103
+ }
104
+
105
+ function one(ref, groups) {
106
+ const hits = findAll(ref, groups);
107
+ if (hits.length > 1) return { error: `"${ref}" matches ${hits.length} groups — use the id`, group: null };
108
+ if (!hits.length) return { error: `no group "${ref}"`, group: null };
109
+ return { error: null, group: hits[0] };
110
+ }
111
+
112
+ async function printHelp(name) {
113
+ console.log(`Usage: total-recall group <command> [--json]
114
+
115
+ create <name> [--description "<text>"] New group (random UUID id)
116
+ add <group> <member…> Member = project UUID, or group name/UUID
117
+ remove <group> <member…> Remove members
118
+ list All groups
119
+ show <group> One group with direct members
120
+ tree [<group>] Nested view (all roots if omitted)
121
+ members <group> [--recursive] Direct members, or every project reached through nested groups
122
+ delete <group> Delete a group (refused while other groups contain it)
123
+
124
+ Groups are SSSS project_group documents in ${DIR}. Nesting is cycle-checked.`);
125
+ }
126
+
127
+ /**
128
+ * v2 entry point — receives (args, context)
129
+ */
130
+ export default async function main(args = [], context = {}) {
131
+ const { json = false, tr, log } = context;
132
+
133
+ const argl = Array.isArray(args) ? args : [];
134
+ const asJson = json || argl.includes('--json');
135
+ const recursive = argl.includes('--recursive') || argl.includes('-r');
136
+
137
+ // Strip the verb itself from forwarded args
138
+ const cleanArgs = argl.filter((a) => !['group', 'groups'].includes(a) && a !== '--json');
139
+ const pos = cleanArgs.filter((a) => !a.startsWith('-'));
140
+ const [sub, ...rest] = pos;
141
+
142
+ if (!sub || sub === 'help' || cleanArgs.includes('--help') || cleanArgs.includes('-h')) {
143
+ await printHelp(sub);
144
+ return { ok: true, data: null };
145
+ }
146
+
147
+ fs.mkdirSync(DIR, { recursive: true });
148
+ const groups = load();
149
+ const byId = new Map(groups.map((g) => [g.id, g]));
150
+
151
+ if (sub === 'create') {
152
+ const name = rest.join(' ').trim();
153
+ if (!name) return die('group create <name>');
154
+ const di = cleanArgs.indexOf('--description');
155
+ const g = { type: 'project_group', id: randomUUID(), title: name, description: di >= 0 ? cleanArgs[di + 1] : '', members: [] };
156
+ save(g, groups);
157
+ const result = { ok: true, group: g };
158
+ if (asJson) return result;
159
+ console.log(` group "${name}" ${g.id}`);
160
+ return result;
161
+ }
162
+
163
+ if (sub === 'add' || sub === 'remove') {
164
+ const { group: g, error: err } = one(rest[0], groups);
165
+ if (err) return die(err);
166
+ const refs = rest.slice(1);
167
+ if (!refs.length) return die(`group ${sub} <group> <member…>`);
168
+ const results = [];
169
+ for (const ref of refs) {
170
+ let member = null;
171
+ if (UUID_RE.test(ref)) {
172
+ member = { type: 'project', id: ref };
173
+ } else {
174
+ const g2 = findGroup(ref, groups);
175
+ if (g2) member = { type: 'group', id: g2.id, name: g2.title };
176
+ }
177
+ if (!member) {
178
+ results.push({ ref, ok: false, error: 'not a group or project UUID' });
179
+ continue;
180
+ }
181
+ if (sub === 'add') {
182
+ if (member.type === 'group' && reaches(member.id, g.id, byId)) {
183
+ results.push({ ref, ok: false, error: 'cycle detected' });
184
+ continue;
185
+ }
186
+ if ((g.members || []).some((x) => x.id === member.id)) {
187
+ results.push({ ref, ok: true, note: 'already a member' });
188
+ continue;
189
+ }
190
+ g.members = [...(g.members || []), member];
191
+ results.push({ ref, ok: true, added: member });
192
+ } else {
193
+ const before = (g.members || []).length;
194
+ g.members = (g.members || []).filter((x) => x.id !== member.id);
195
+ results.push({ ref, ok: before !== g.members.length, removed: before !== g.members.length });
196
+ }
197
+ }
198
+ save(g, groups);
199
+ if (results.some((r) => !r.ok)) process.exitCode = 1;
200
+ if (asJson) return { ok: !process.exitCode, group: g.id, results };
201
+ results.forEach((r) => console.log(`${r.ok ? '' : ''} ${r.ref}${r.error ? ' — ' + r.error : r.note ? ' — ' + r.note : ''}`));
202
+ return { ok: !process.exitCode, results };
203
+ }
204
+
205
+ if (sub === 'list') {
206
+ const rows = groups.map((g) => ({ id: g.id, name: g.title, members: (g.members || []).length, projects: flattenMembers(g, byId).size }));
207
+ if (asJson) return { ok: true, data: rows };
208
+ if (rows.length) rows.forEach((r) => console.log(`▸ ${r.name} ${r.id} (${r.members} direct, ${r.projects} projects)`));
209
+ else console.log('No groups yet — total-recall group create <name>');
210
+ return { ok: true, data: rows };
211
+ }
212
+
213
+ if (sub === 'show') {
214
+ const { group: g, error: err } = one(rest[0], groups);
215
+ if (err) return die(err);
216
+ if (asJson) return { ok: true, data: g };
217
+ console.log(`▸ ${g.title} ${g.id}`);
218
+ (g.members || []).forEach((m) => console.log(` ${m.type === 'group' ? '▸' : '•'} ${m.name || m.id}`));
219
+ return { ok: true, data: g };
220
+ }
221
+
222
+ if (sub === 'tree') {
223
+ if (rest[0]) {
224
+ const { group: g, error: err } = one(rest[0], groups);
225
+ if (err) return die(err);
226
+ if (asJson) return { ok: true, data: g };
227
+ console.log(`▸ ${g.title} (${g.id})`);
228
+ (g.members || []).forEach((m) => console.log(` ${m.type === 'group' ? '▸' : '•'} ${m.name || m.id}`));
229
+ return { ok: true, data: g };
230
+ }
231
+ const nested = new Set(groups.flatMap((g) => (g.members || []).filter((m) => m.type === 'group').map((m) => m.id)));
232
+ const roots = groups.filter((g) => !nested.has(g.id));
233
+ if (asJson) return { ok: true, data: roots.map((g) => g.id) };
234
+ roots.forEach((g) => {
235
+ console.log(`▸ ${g.title} (${g.id})`);
236
+ (g.members || []).forEach((m) => console.log(` ${m.type === 'group' ? '▸' : '•'} ${m.name || m.id}`));
237
+ });
238
+ return { ok: true, data: roots.map((g) => g.id) };
239
+ }
240
+
241
+ if (sub === 'members') {
242
+ const { group: g, error: err } = one(rest[0], groups);
243
+ if (err) return die(err);
244
+ const list = recursive ? [...flattenMembers(g, byId).values()] : (g.members || []);
245
+ if (asJson) return { ok: true, data: list };
246
+ list.forEach((m) => console.log(`${m.type === 'group' ? '▸' : '•'} ${m.name || m.id}`));
247
+ return { ok: true, data: list };
248
+ }
249
+
250
+ if (sub === 'delete') {
251
+ const { group: g, error: err } = one(rest[0], groups);
252
+ if (err) return die(err);
253
+ const parents = groups.filter((x) => (x.members || []).some((m) => m.type === 'group' && m.id === g.id));
254
+ if (parents.length) return die(`"${g.title}" is inside ${parents.map((p) => p.title).join(', ')} — remove it there first`);
255
+ fs.unlinkSync(path.join(DIR, `${g.id}.md`));
256
+ if (asJson) return { ok: true, data: { deleted: g.id } };
257
+ console.log(` deleted "${g.title}"`);
258
+ return { ok: true, data: { deleted: g.id } };
259
+ }
260
+
261
+ return die(`unknown subcommand "${sub}" — total-recall group --help`);
262
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * total-recall project-id [--json] | --all
3
+ *
4
+ * Prints this project's stable random-UUID id, creating it on first use.
5
+ *
6
+ * v2 example demonstrating the composable CLI contract with meta export,
7
+ * tr() composition, and risk declaration.
8
+ *
9
+ * Run: total-recall project-id [--json]
10
+ */
11
+ import path from 'node:path';
12
+ import fs from 'node:fs';
13
+ import crypto from 'node:crypto';
14
+
15
+ export const meta = {
16
+ name: 'project-id',
17
+ description: 'Print this project\'s random-UUID project id (created on first use, stored in config/brain.json)',
18
+ risk: 'read',
19
+ version: 2,
20
+ background: false,
21
+ };
22
+
23
+ const BRAIN_FILE = path.join(process.cwd(), '.agent', 'skills', 'total-recall', 'config', 'brain.json');
24
+
25
+ function readProjectBrain() {
26
+ try {
27
+ if (fs.existsSync(BRAIN_FILE)) {
28
+ return JSON.parse(fs.readFileSync(BRAIN_FILE, 'utf8'));
29
+ }
30
+ } catch { /* not found or invalid */ }
31
+ return null;
32
+ }
33
+
34
+ function ensureProjectId() {
35
+ const brain = readProjectBrain() || {};
36
+ let created = false;
37
+ if (!brain.project_id) {
38
+ brain.project_id = crypto.randomUUID();
39
+ created = true;
40
+ fs.mkdirSync(path.dirname(BRAIN_FILE), { recursive: true });
41
+ fs.writeFileSync(BRAIN_FILE, JSON.stringify(brain, null, 2) + '\n');
42
+ }
43
+ return { project_id: brain.project_id, created };
44
+ }
45
+
46
+ /**
47
+ * v2 entry point — receives (args, context)
48
+ */
49
+ export default async function main(args = [], context = {}) {
50
+ const { json = false, tr, log } = context;
51
+
52
+ const safeArgs = args.filter((a) => a !== 'project-id');
53
+
54
+ if (safeArgs.includes('--help') || safeArgs.includes('-h')) {
55
+ const usage = 'Usage: total-recall project-id [--json]\n\nPrints this project\'s random-UUID project id.';
56
+ if (json) return { ok: true, data: { usage } };
57
+ console.log(usage);
58
+ return { ok: true, data: null };
59
+ }
60
+
61
+ const { project_id, created } = ensureProjectId();
62
+ const result = {
63
+ project_id,
64
+ name: path.basename(process.cwd()),
65
+ created,
66
+ path: process.cwd(),
67
+ };
68
+
69
+ if (json) {
70
+ return { ok: true, data: result };
71
+ }
72
+ console.log(project_id);
73
+ return { ok: true, data: result };
74
+ }