total-recall-brain 3.41.3 → 3.41.4
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 +10 -1
- package/frontend/dist/assets/{index-DFenQQEQ.js → index-CRzjQLu7.js} +77 -72
- package/frontend/dist/index.html +1 -1
- package/package.json +7 -5
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +48 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/mesh-secrets.mjs +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/secrets.mjs +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-carriers.mjs +76 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-prune.mjs +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +53 -17
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +12 -12
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +12 -12
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +23 -8
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +6 -2
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +23 -8
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -10
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/grants.mjs +7 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +61 -38
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +57 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +220 -36
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/server/routes/mesh.mjs +7 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +65 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +534 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +139 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +2849 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +376 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adapters/dsh.mjs +446 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +398 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +152 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +82 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +90 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +415 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/tracker.mjs +133 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +233 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +845 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/BRICK.md +64 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/index.mjs +250 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +344 -14
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +2917 -140
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +9 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +223 -5
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +355 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +375 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +35 -22
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +947 -323
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +23 -8
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +5 -1
- package/src/cli/config.mjs +3 -0
- package/src/cli/connect.mjs +12 -116
- package/src/cli/harness.mjs +0 -1
- package/src/cli/plugin/create.mjs +56 -3
- package/src/cli/plugin/index.mjs +15 -3
- package/src/cli/plugin/monetization.mjs +232 -12
- package/src/cli/preflight.mjs +161 -0
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/context-compiler.mjs +8 -1
- package/src/core/default-plugins.mjs +1 -1
- package/src/core/meta-harness.mjs +16 -38
- package/src/core/plugin-dependencies.mjs +6 -1
- package/src/core/preflight-gate.mjs +77 -0
- package/src/core/preflight-receipt.mjs +123 -0
- package/src/core/runtime.mjs +0 -3
- package/src/core/surface.mjs +38 -1
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
1
4
|
import { loadPluginModule as __loadOwnedPlugin } from '../../core/plugin-runtime.mjs';
|
|
2
|
-
import { getPlugin, getPluginMonetization } from '../../core/plugin-loader.mjs';
|
|
5
|
+
import { getPlugin, getPluginMonetization, validatePluginManifest } from '../../core/plugin-loader.mjs';
|
|
3
6
|
const { getSecret, setSecret, defaultBrainDir } = await __loadOwnedPlugin('auth', 'secrets-store');
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
|
-
* Handle `total-recall plugin license <id> [
|
|
9
|
+
* Handle `total-recall plugin license <id> [status|activate|checkout|verify]`
|
|
7
10
|
*/
|
|
8
11
|
export async function managePluginLicense(args = []) {
|
|
9
12
|
const pluginId = args.find(a => !a.startsWith('-'));
|
|
10
13
|
if (!pluginId) {
|
|
11
|
-
console.error('❌ Error: Missing plugin ID. Usage: total-recall plugin license <id> [
|
|
14
|
+
console.error('❌ Error: Missing plugin ID. Usage: total-recall plugin license <id> [status|activate|checkout|verify]');
|
|
12
15
|
process.exit(1);
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -20,6 +23,7 @@ export async function managePluginLicense(args = []) {
|
|
|
20
23
|
|
|
21
24
|
const mon = getPluginMonetization(plugin);
|
|
22
25
|
const sub = args[1] && !args[1].startsWith('-') && args[1] !== pluginId ? args[1] : 'status';
|
|
26
|
+
const json = args.includes('--json');
|
|
23
27
|
|
|
24
28
|
if (sub === 'activate') {
|
|
25
29
|
if (!args.includes('--stdin')) throw new Error('Use plugin license <id> activate --stdin');
|
|
@@ -27,34 +31,123 @@ export async function managePluginLicense(args = []) {
|
|
|
27
31
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
28
32
|
const key = Buffer.concat(chunks).toString('utf8').trim();
|
|
29
33
|
if (!key) {
|
|
30
|
-
console.error('❌ Error: Missing license key. Usage: total-recall plugin license <id> activate
|
|
34
|
+
console.error('❌ Error: Missing license key. Usage: total-recall plugin license <id> activate --stdin');
|
|
31
35
|
process.exit(1);
|
|
32
36
|
}
|
|
33
37
|
const secretKey = mon.license_key_secret || `${pluginId.toUpperCase().replace(/-/g, '_')}_LICENSE_KEY`;
|
|
34
38
|
await setSecret(defaultBrainDir(), secretKey, key, { actor: 'plugin-license' });
|
|
35
|
-
|
|
39
|
+
if (json) {
|
|
40
|
+
console.log(JSON.stringify({ activated: true, plugin: pluginId, secret_key: secretKey }));
|
|
41
|
+
} else {
|
|
42
|
+
console.log(`✅ License credential stored for plugin '${pluginId}'. Secret '${secretKey}' recorded.`);
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (sub === 'checkout') {
|
|
48
|
+
if (mon.model === 'free') {
|
|
49
|
+
if (json) console.log(JSON.stringify({ free: true, plugin: pluginId }));
|
|
50
|
+
else console.log(`ℹ️ Plugin '${pluginId}' is free; no checkout required.`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (mon.checkout_url) {
|
|
54
|
+
if (json) console.log(JSON.stringify({ checkout_url: mon.checkout_url, plugin: pluginId }));
|
|
55
|
+
else console.log(`💳 Direct Checkout URL: ${mon.checkout_url}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Stripe checkout integration
|
|
59
|
+
const TR = process.env.TOTAL_RECALL_BIN || 'total-recall';
|
|
60
|
+
const amount = mon.price_cents || 1000;
|
|
61
|
+
const currency = (mon.currency || 'USD').toLowerCase();
|
|
62
|
+
const successUrl = 'https://total-recall.ai/plugins/success';
|
|
63
|
+
const cancelUrl = 'https://total-recall.ai/plugins/cancel';
|
|
64
|
+
try {
|
|
65
|
+
const script = TR.endsWith('.mjs');
|
|
66
|
+
const stripeRes = spawnSync(script ? process.execPath : TR, [
|
|
67
|
+
...(script ? [TR] : []),
|
|
68
|
+
'stripe', 'checkout',
|
|
69
|
+
'--amount', String(amount),
|
|
70
|
+
'--currency', currency,
|
|
71
|
+
'--name', `${plugin.manifest.name} License`,
|
|
72
|
+
'--success-url', successUrl,
|
|
73
|
+
'--cancel-url', cancelUrl,
|
|
74
|
+
'--json'
|
|
75
|
+
], { encoding: 'utf8', timeout: 15000 });
|
|
76
|
+
if (stripeRes.status === 0 && stripeRes.stdout) {
|
|
77
|
+
const parsed = JSON.parse(stripeRes.stdout);
|
|
78
|
+
const url = parsed.url || parsed.checkout_url || parsed.data?.url;
|
|
79
|
+
if (url) {
|
|
80
|
+
if (json) {
|
|
81
|
+
console.log(JSON.stringify({ plugin: pluginId, checkout_url: url, amount, currency }));
|
|
82
|
+
} else {
|
|
83
|
+
console.log(`\n💳 Stripe Checkout Session Created:`);
|
|
84
|
+
console.log(` Plugin: ${plugin.manifest.name}`);
|
|
85
|
+
console.log(` Amount: $${(amount / 100).toFixed(2)} ${currency.toUpperCase()}`);
|
|
86
|
+
console.log(` Checkout URL: ${url}\n`);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
if (json) {
|
|
93
|
+
console.log(JSON.stringify({ plugin: pluginId, price_formatted: mon.priceFormatted, requires_purchase: true }));
|
|
94
|
+
} else {
|
|
95
|
+
console.log(`\n💳 Commercial License Purchase:`);
|
|
96
|
+
console.log(` Plugin: ${plugin.manifest.name} (${mon.priceFormatted})`);
|
|
97
|
+
console.log(` Activate license after purchase: total-recall plugin license ${pluginId} activate --stdin\n`);
|
|
98
|
+
}
|
|
36
99
|
return;
|
|
37
100
|
}
|
|
38
101
|
|
|
39
102
|
if (sub === 'verify') {
|
|
40
103
|
if (!mon.requiresLicense) {
|
|
41
|
-
console.log(
|
|
104
|
+
if (json) console.log(JSON.stringify({ verified: true, free: true, plugin: pluginId }));
|
|
105
|
+
else console.log(`ℹ️ Plugin '${pluginId}' does not require a commercial license (${mon.model}).`);
|
|
42
106
|
return;
|
|
43
107
|
}
|
|
44
108
|
const secretKey = mon.license_key_secret;
|
|
45
109
|
const currentKey = secretKey ? await getSecret(defaultBrainDir(), secretKey, { actor: 'plugin-license' }) : null;
|
|
46
|
-
if (!currentKey?.found) {
|
|
47
|
-
|
|
48
|
-
|
|
110
|
+
if (!currentKey?.found || !currentKey?.value) {
|
|
111
|
+
if (json) {
|
|
112
|
+
console.log(JSON.stringify({ verified: false, plugin: pluginId, secret_key: secretKey, error: 'License missing' }));
|
|
113
|
+
} else {
|
|
114
|
+
console.error(`❌ License missing for '${pluginId}'. Secret '${secretKey}' is not configured.`);
|
|
115
|
+
if (mon.checkout_url) console.error(` Purchase or subscribe at: ${mon.checkout_url}`);
|
|
116
|
+
else console.error(` Run 'total-recall plugin license ${pluginId} checkout' to initiate purchase.`);
|
|
117
|
+
}
|
|
49
118
|
process.exitCode = 1;
|
|
50
119
|
return;
|
|
51
120
|
}
|
|
52
|
-
|
|
53
|
-
|
|
121
|
+
const keyVal = currentKey.value.trim();
|
|
122
|
+
const masked = keyVal.length > 8 ? `${keyVal.slice(0, 4)}...${keyVal.slice(-4)}` : '****';
|
|
123
|
+
if (json) {
|
|
124
|
+
console.log(JSON.stringify({ verified: true, plugin: pluginId, secret_key: secretKey, license_key_masked: masked, status: 'active' }));
|
|
125
|
+
} else {
|
|
126
|
+
console.log(`✅ License verified active for '${pluginId}'.`);
|
|
127
|
+
console.log(` Secret Key: ${secretKey}`);
|
|
128
|
+
console.log(` License Key: ${masked}`);
|
|
129
|
+
console.log(` Status: Active / Validated`);
|
|
130
|
+
}
|
|
54
131
|
return;
|
|
55
132
|
}
|
|
56
133
|
|
|
57
134
|
// Default: status
|
|
135
|
+
if (json) {
|
|
136
|
+
const currentKey = mon.license_key_secret ? await getSecret(defaultBrainDir(), mon.license_key_secret, { actor: 'plugin-license' }) : null;
|
|
137
|
+
console.log(JSON.stringify({
|
|
138
|
+
id: pluginId,
|
|
139
|
+
name: plugin.manifest.name,
|
|
140
|
+
model: mon.model,
|
|
141
|
+
price: mon.priceFormatted,
|
|
142
|
+
price_cents: mon.price_cents,
|
|
143
|
+
currency: mon.currency,
|
|
144
|
+
period: mon.period,
|
|
145
|
+
license_key_secret: mon.license_key_secret,
|
|
146
|
+
configured: Boolean(currentKey?.found),
|
|
147
|
+
checkout_url: mon.checkout_url
|
|
148
|
+
}, null, 2));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
58
151
|
console.log(`\n💳 Plugin Monetization & Licensing: ${plugin.manifest.name}`);
|
|
59
152
|
console.log(` ID: ${pluginId}`);
|
|
60
153
|
console.log(` Model: ${mon.model}`);
|
|
@@ -65,8 +158,135 @@ export async function managePluginLicense(args = []) {
|
|
|
65
158
|
if (mon.license_key_secret) {
|
|
66
159
|
const currentKey = await getSecret(defaultBrainDir(), mon.license_key_secret, { actor: 'plugin-license' });
|
|
67
160
|
console.log(` License Secret: ${mon.license_key_secret}`);
|
|
68
|
-
console.log(` License Status: ${currentKey?.found ? 'Configured ·
|
|
161
|
+
console.log(` License Status: ${currentKey?.found ? 'Configured · active' : '⚠️ Unconfigured'}`);
|
|
69
162
|
}
|
|
70
163
|
if (mon.checkout_url) console.log(` Checkout Link: ${mon.checkout_url}`);
|
|
71
164
|
console.log();
|
|
72
165
|
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Handle `total-recall plugin monetize <id> [options]`
|
|
169
|
+
*/
|
|
170
|
+
export async function monetizePlugin(args = []) {
|
|
171
|
+
const pluginId = args.find(a => !a.startsWith('-'));
|
|
172
|
+
if (!pluginId) {
|
|
173
|
+
console.error('❌ Error: Missing plugin ID. Usage: total-recall plugin monetize <id> [options]');
|
|
174
|
+
console.error(' Options: --model <free|one-time|subscription|take-rate>');
|
|
175
|
+
console.error(' --price-cents <cents>');
|
|
176
|
+
console.error(' --currency <currency>');
|
|
177
|
+
console.error(' --period <monthly|annual>');
|
|
178
|
+
console.error(' --recipient-account <id>');
|
|
179
|
+
console.error(' --recipient-type <stripe_connect>');
|
|
180
|
+
console.error(' --checkout-url <url>');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const plugin = getPlugin(pluginId, process.cwd());
|
|
185
|
+
if (!plugin) {
|
|
186
|
+
console.error(`❌ Plugin '${pluginId}' is not installed.`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const manifestPath = path.join(plugin.dir, 'plugin.json');
|
|
191
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
192
|
+
|
|
193
|
+
const modelIdx = args.indexOf('--model');
|
|
194
|
+
const priceIdx = args.indexOf('--price-cents');
|
|
195
|
+
const currIdx = args.indexOf('--currency');
|
|
196
|
+
const periodIdx = args.indexOf('--period');
|
|
197
|
+
const recAccIdx = args.indexOf('--recipient-account');
|
|
198
|
+
const recTypeIdx = args.indexOf('--recipient-type');
|
|
199
|
+
const urlIdx = args.indexOf('--checkout-url');
|
|
200
|
+
const json = args.includes('--json');
|
|
201
|
+
|
|
202
|
+
const hasUpdates = [modelIdx, priceIdx, currIdx, periodIdx, recAccIdx, recTypeIdx, urlIdx].some(i => i !== -1);
|
|
203
|
+
|
|
204
|
+
if (hasUpdates) {
|
|
205
|
+
manifest.monetization = manifest.monetization || {};
|
|
206
|
+
if (modelIdx !== -1 && args[modelIdx + 1]) manifest.monetization.model = args[modelIdx + 1];
|
|
207
|
+
if (priceIdx !== -1 && args[priceIdx + 1]) manifest.monetization.price_cents = parseInt(args[priceIdx + 1], 10);
|
|
208
|
+
if (currIdx !== -1 && args[currIdx + 1]) manifest.monetization.currency = args[currIdx + 1].toUpperCase();
|
|
209
|
+
if (periodIdx !== -1 && args[periodIdx + 1]) manifest.monetization.period = args[periodIdx + 1];
|
|
210
|
+
if (urlIdx !== -1 && args[urlIdx + 1]) manifest.monetization.checkout_url = args[urlIdx + 1];
|
|
211
|
+
|
|
212
|
+
if (recAccIdx !== -1 && args[recAccIdx + 1]) {
|
|
213
|
+
manifest.monetization.recipient = {
|
|
214
|
+
type: (recTypeIdx !== -1 && args[recTypeIdx + 1]) || 'stripe_connect',
|
|
215
|
+
account_id: args[recAccIdx + 1]
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (manifest.monetization.model && manifest.monetization.model !== 'free' && !manifest.monetization.license_key_secret) {
|
|
220
|
+
manifest.monetization.license_key_secret = `${pluginId.toUpperCase().replace(/-/g, '_')}_LICENSE_KEY`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const validation = validatePluginManifest(manifest);
|
|
224
|
+
if (!validation.valid) {
|
|
225
|
+
console.error(`❌ Invalid plugin manifest update: ${validation.errors.join('; ')}`);
|
|
226
|
+
process.exitCode = 1;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
231
|
+
if (json) {
|
|
232
|
+
console.log(JSON.stringify({ updated: true, plugin: pluginId, monetization: manifest.monetization }, null, 2));
|
|
233
|
+
} else {
|
|
234
|
+
console.log(`✅ Monetization configured for plugin '${pluginId}'.`);
|
|
235
|
+
console.log(` Model: ${manifest.monetization.model}`);
|
|
236
|
+
if (manifest.monetization.price_cents !== undefined) {
|
|
237
|
+
console.log(` Price: $${(manifest.monetization.price_cents / 100).toFixed(2)} ${manifest.monetization.currency || 'USD'}`);
|
|
238
|
+
}
|
|
239
|
+
if (manifest.monetization.period) {
|
|
240
|
+
console.log(` Period: ${manifest.monetization.period}`);
|
|
241
|
+
}
|
|
242
|
+
if (manifest.monetization.license_key_secret) {
|
|
243
|
+
console.log(` License Secret: ${manifest.monetization.license_key_secret}`);
|
|
244
|
+
}
|
|
245
|
+
if (manifest.monetization.checkout_url) {
|
|
246
|
+
console.log(` Checkout URL: ${manifest.monetization.checkout_url}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return managePluginLicense([pluginId, 'status', ...(json ? ['--json'] : [])]);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Handle `total-recall plugin publish <id> [--no-lock] [--json]`
|
|
257
|
+
*/
|
|
258
|
+
export async function publishPluginToStore(args = []) {
|
|
259
|
+
const pluginId = args.find(a => !a.startsWith('-'));
|
|
260
|
+
if (!pluginId) {
|
|
261
|
+
console.error('❌ Error: Missing plugin ID. Usage: total-recall plugin publish <id> [--no-lock] [--json]');
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const { deployPluginToStore } = await import('../../core/plugin-distribution.mjs');
|
|
266
|
+
const json = args.includes('--json');
|
|
267
|
+
const autoLock = !args.includes('--no-lock');
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const result = await deployPluginToStore(pluginId, { autoLock, projectRoot: process.cwd() });
|
|
271
|
+
if (json) {
|
|
272
|
+
console.log(JSON.stringify({
|
|
273
|
+
success: true,
|
|
274
|
+
id: pluginId,
|
|
275
|
+
store_deployed: true,
|
|
276
|
+
share_url: result.share_url,
|
|
277
|
+
version: result.version,
|
|
278
|
+
locked: result.locked
|
|
279
|
+
}, null, 2));
|
|
280
|
+
} else {
|
|
281
|
+
console.log(`\n🚀 Successfully published plugin '${pluginId}' to the Store!`);
|
|
282
|
+
console.log(` ID: ${pluginId}`);
|
|
283
|
+
console.log(` Version: ${result.version || '0.1.0'}`);
|
|
284
|
+
console.log(` Locked: ${result.locked ? 'Yes (immutable release)' : 'No'}`);
|
|
285
|
+
if (result.share_url) console.log(` Share Link: ${result.share_url}`);
|
|
286
|
+
console.log(`\nPeers can now install it with: total-recall plugin install ${result.share_url || `peer:<host>/${pluginId}`}`);
|
|
287
|
+
}
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.error(`❌ Failed to publish plugin '${pluginId}': ${err.message}`);
|
|
290
|
+
process.exitCode = 1;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { decide, SWEEP_CONSULT_MS } from '../core/preflight-gate.mjs';
|
|
3
|
+
import { ACK_COMMAND, acknowledge, checkReceipt, findRoot, lastConsultMs } from '../core/preflight-receipt.mjs';
|
|
4
|
+
|
|
5
|
+
const USAGE = `Usage: total-recall preflight <command>
|
|
6
|
+
ack --task "<task>" [--action read,edit,test] [--json] Record a receipt after context ready:true and rules verify
|
|
7
|
+
check [--json] Exit 1 unless a fresh receipt exists for this directory
|
|
8
|
+
gate [--harness <id>] Hook entry: read a call on stdin, answer allow or deny (exit 2 on deny)
|
|
9
|
+
remind [--json] Prompt-hook entry: advisory text when the receipt is missing; always exits 0
|
|
10
|
+
install | verify | report Harness adapters, owned by the cli-agents plugin
|
|
11
|
+
Aliases: total-recall tr-preflight --ack | --check. A receipt lasts 30 minutes (config preflight_max_age_minutes).`;
|
|
12
|
+
|
|
13
|
+
/** An explicit override only. The configured window is read once, by `ack`, and recorded in the receipt. */
|
|
14
|
+
function envMaxAgeMs() {
|
|
15
|
+
const minutes = Number(process.env.TR_PREFLIGHT_MAX_AGE_MIN);
|
|
16
|
+
return Number.isFinite(minutes) && minutes > 0 ? minutes * 60000 : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Slow path only: the configuration code is heavy, and the gate must not load it on every tool call. */
|
|
20
|
+
async function configuredMaxAgeMs() {
|
|
21
|
+
const fromEnv = envMaxAgeMs();
|
|
22
|
+
if (fromEnv) return fromEnv;
|
|
23
|
+
try {
|
|
24
|
+
const { readConfigValue } = await import('./config.mjs');
|
|
25
|
+
const minutes = readConfigValue('preflight_max_age_minutes', 'global');
|
|
26
|
+
return Number.isFinite(minutes) && minutes > 0 ? minutes * 60000 : undefined;
|
|
27
|
+
} catch { return undefined; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Hooks pipe their payload in; a person running the command by hand has a terminal, which would block. */
|
|
31
|
+
function readStdin() {
|
|
32
|
+
if (process.stdin.isTTY) return '';
|
|
33
|
+
return fs.readFileSync(0, 'utf8') || '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function flag(args, name) {
|
|
37
|
+
const i = args.indexOf(name);
|
|
38
|
+
return i === -1 ? undefined : args[i + 1];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function printFailure(problems) {
|
|
42
|
+
console.error('✖ tr-preflight FAILED — do not begin task work.');
|
|
43
|
+
for (const p of problems) console.error(` ${p}`);
|
|
44
|
+
console.error('');
|
|
45
|
+
console.error(` Run: ${ACK_COMMAND}`);
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function check(args) {
|
|
50
|
+
const root = findRoot();
|
|
51
|
+
const result = checkReceipt(root, { maxAgeMs: envMaxAgeMs() });
|
|
52
|
+
if (args.includes('--json')) {
|
|
53
|
+
console.log(JSON.stringify({ ok: result.ok, problems: result.problems, root, task: result.receipt?.task ?? null, ageMs: result.ageMs, fix: result.ok ? null : ACK_COMMAND }));
|
|
54
|
+
if (!result.ok) process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!result.ok) return printFailure(result.problems);
|
|
58
|
+
console.log(`✔ tr-preflight OK — skills read, context ready:true (${Math.round(result.ageMs / 60000)}m ago), rules verify clean.`);
|
|
59
|
+
console.log(` task: ${result.receipt.task || '(unspecified)'}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function ack(args) {
|
|
63
|
+
const root = findRoot();
|
|
64
|
+
const task = flag(args, '--task') || '';
|
|
65
|
+
const result = acknowledge({ root, task, actions: flag(args, '--action') || 'read,edit,test', maxAgeMs: await configuredMaxAgeMs() });
|
|
66
|
+
if (args.includes('--json')) {
|
|
67
|
+
console.log(JSON.stringify({ ok: result.ok, problems: result.problems, receipt: result.file || null }));
|
|
68
|
+
if (!result.ok) process.exitCode = 1;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!result.ok) return printFailure(result.problems);
|
|
72
|
+
if (!task) console.log('note: no --task given; receipt records an unspecified task');
|
|
73
|
+
console.log('✔ tr-preflight recorded — context ready:true, rules verify clean.');
|
|
74
|
+
console.log(' read: .agent/skills/total-recall/SKILL.md');
|
|
75
|
+
console.log(' read: .agent/skills/ssss/SKILL.md');
|
|
76
|
+
console.log(` receipt: ${result.file}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Hook entry. It starts at exit 2 and only an explicit allow clears it, so any
|
|
81
|
+
* failure, including one nobody anticipated, denies: most harnesses let the
|
|
82
|
+
* action through when a hook crashes or exits with anything but 2.
|
|
83
|
+
* An adapter returns `{ stdout, exitCode }` in its harness's native form.
|
|
84
|
+
*/
|
|
85
|
+
async function gate(args) {
|
|
86
|
+
process.exitCode = 2;
|
|
87
|
+
const deny = (rule, reason) => process.stdout.write(`${JSON.stringify({ decision: 'deny', rule, reason, fix: ACK_COMMAND })}\n`);
|
|
88
|
+
try {
|
|
89
|
+
let call;
|
|
90
|
+
try { call = JSON.parse(readStdin() || '{}'); } catch (err) { return deny('gate-error', `TR-PREFLIGHT gate could not read its input: ${err.message}`); }
|
|
91
|
+
if (!call || typeof call !== 'object' || Array.isArray(call)) return deny('gate-error', 'TR-PREFLIGHT gate input must be a JSON object.');
|
|
92
|
+
const harness = flag(args, '--harness');
|
|
93
|
+
if (harness) {
|
|
94
|
+
let adapters;
|
|
95
|
+
try { adapters = await (await import('../core/plugin-runtime.mjs')).loadPluginModule('meta-harness', 'gate-adapters'); } catch { adapters = null; }
|
|
96
|
+
if (!adapters) {
|
|
97
|
+
try { adapters = await (await import('../core/plugin-runtime.mjs')).loadPluginModule('cli-agents', 'gate-adapters'); } catch { adapters = null; }
|
|
98
|
+
}
|
|
99
|
+
const adapter = adapters?.get?.(harness);
|
|
100
|
+
if (!adapter) return deny('gate-error', `No gate adapter for harness "${harness}": adapters are owned by the meta-harness plugin and it does not provide one. The gate fails closed.`);
|
|
101
|
+
const out = await adapter.run({ stdin: call, decideWith: evaluate });
|
|
102
|
+
if (out?.stdout) process.stdout.write(out.stdout);
|
|
103
|
+
process.exitCode = Number.isInteger(out?.exitCode) ? out.exitCode : 2;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const answer = evaluate(call);
|
|
107
|
+
process.stdout.write(`${JSON.stringify(answer)}\n`);
|
|
108
|
+
if (answer.decision === 'allow') process.exitCode = 0;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
deny('gate-error', `TR-PREFLIGHT gate error: ${err.message}. The gate fails closed.`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function evaluate(call) {
|
|
115
|
+
const root = findRoot(call.cwd || process.cwd());
|
|
116
|
+
const limit = envMaxAgeMs();
|
|
117
|
+
return decide(call, {
|
|
118
|
+
check: () => checkReceipt(root, { maxAgeMs: limit }),
|
|
119
|
+
recentConsult: () => {
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
if (now - lastConsultMs(root) < SWEEP_CONSULT_MS) return true;
|
|
122
|
+
const receipt = checkReceipt(root, { maxAgeMs: limit }).receipt;
|
|
123
|
+
return Boolean(receipt && now - Date.parse(receipt.at) < SWEEP_CONSULT_MS);
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Prompt-hook entry (ENFORCE-R1): never blocks the human. Every path exits 0,
|
|
130
|
+
* including an internal error, because a prompt hook that can fail locks the
|
|
131
|
+
* user out of their own session.
|
|
132
|
+
*/
|
|
133
|
+
function remind(args) {
|
|
134
|
+
try {
|
|
135
|
+
let call = {};
|
|
136
|
+
try { call = JSON.parse(readStdin() || '{}'); } catch { /* the payload is optional here */ }
|
|
137
|
+
const root = findRoot(call.cwd || process.cwd());
|
|
138
|
+
const result = checkReceipt(root, { maxAgeMs: envMaxAgeMs() });
|
|
139
|
+
const text = result.ok ? null : `TR-PREFLIGHT FAILED for ${root}: ${result.problems[0]}. Every tool call except the preflight, context and recall commands will be denied until it passes. First read the full context capsule (never truncate it), then run: ${ACK_COMMAND}`;
|
|
140
|
+
if (args.includes('--json')) console.log(JSON.stringify({ reminder: text }));
|
|
141
|
+
else if (text) console.log(text);
|
|
142
|
+
} catch { /* advisory only */ }
|
|
143
|
+
process.exitCode = 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export default async function preflight(args = []) {
|
|
147
|
+
const [first = '--help'] = args;
|
|
148
|
+
if (first === '--help' || first === '-h' || first === 'help') return console.log(USAGE);
|
|
149
|
+
// Original `tr-preflight` forms.
|
|
150
|
+
if (first === '--ack' || first === 'ack') return ack(args);
|
|
151
|
+
if (first === '--check' || first === 'check') return check(args);
|
|
152
|
+
if (first === 'gate') return gate(args.slice(1));
|
|
153
|
+
if (first === 'remind') return remind(args);
|
|
154
|
+
if (['install', 'verify', 'report'].includes(first)) {
|
|
155
|
+
console.error(`preflight ${first}: harness adapters are owned by the meta-harness (cli-agents) plugin and are not installed yet (ENFORCE-01 EN-03 onward).`);
|
|
156
|
+
process.exitCode = 1;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
console.error(`Unknown preflight command: ${first}\n\n${USAGE}`);
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
}
|
|
@@ -139,8 +139,15 @@ export async function compileContext({
|
|
|
139
139
|
return result;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Second line of every text capsule, so output that is truncated or piped through
|
|
144
|
+
* `head` still carries the rule (the rule once sat at line 544 of the output and
|
|
145
|
+
* an agent that cut the output at 80 lines never saw it).
|
|
146
|
+
*/
|
|
147
|
+
export const PREFLIGHT_NOTICE = 'PREFLIGHT: before any other tool call, read this whole capsule (never pipe or truncate it), then run: total-recall tr-preflight --ack --task "<task>" --action read,edit,test';
|
|
148
|
+
|
|
142
149
|
export function renderCapsule(result, format = 'json') {
|
|
143
|
-
return format === 'text' ? `ready:${result.ready} tokens:${result.stats.total_tokens} version:${result.stats.version}\n${result.context}` : JSON.stringify(result);
|
|
150
|
+
return format === 'text' ? `ready:${result.ready} tokens:${result.stats.total_tokens} version:${result.stats.version}\n${PREFLIGHT_NOTICE}\n${result.context}` : JSON.stringify(result);
|
|
144
151
|
}
|
|
145
152
|
|
|
146
153
|
/** Budget the actual wire representation, including metadata, escaping and status. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Default runtime foundation and routine agent-development tools. */
|
|
2
2
|
export const DEFAULT_PLUGIN_IDS = Object.freeze([
|
|
3
|
-
'auth', 'mesh', 'openwiki', 'composable-cli', '
|
|
3
|
+
'auth', 'mesh', 'openwiki', 'composable-cli', 'meta-harness',
|
|
4
4
|
'code-quality', 'security', 'embeddings', 'git-sentinel',
|
|
5
5
|
]);
|
|
@@ -49,15 +49,6 @@ export const HARNESS_SPECS = {
|
|
|
49
49
|
execType: 'subcommand',
|
|
50
50
|
description: 'Autonomous program synthesis and sandbox execution.'
|
|
51
51
|
},
|
|
52
|
-
gemini: {
|
|
53
|
-
id: 'gemini',
|
|
54
|
-
name: 'Google Gemini CLI',
|
|
55
|
-
binary: 'gemini',
|
|
56
|
-
category: 'fast_utility',
|
|
57
|
-
defaultFlags: ['--sandbox=false', '--yolo', '-o', 'json'],
|
|
58
|
-
execType: 'flag_last',
|
|
59
|
-
description: 'High-speed utility completions and tool chaining.'
|
|
60
|
-
},
|
|
61
52
|
ollama: {
|
|
62
53
|
id: 'ollama',
|
|
63
54
|
name: 'Ollama Local LLM',
|
|
@@ -246,14 +237,6 @@ export async function detectHarnessesWithAuth({ readSecret = readStoreSecret } =
|
|
|
246
237
|
h.authed = true;
|
|
247
238
|
h.authRoute = 'cached-xai-auth';
|
|
248
239
|
}
|
|
249
|
-
} else if (h.id === 'gemini') {
|
|
250
|
-
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) {
|
|
251
|
-
h.authed = true;
|
|
252
|
-
h.authRoute = 'env-api-key';
|
|
253
|
-
} else if (fs.existsSync(path.join(os.homedir(), '.gemini'))) {
|
|
254
|
-
h.authed = true;
|
|
255
|
-
h.authRoute = 'cached-google-signin';
|
|
256
|
-
}
|
|
257
240
|
} else if (h.id === 'hermes') {
|
|
258
241
|
if (process.env.HERMES_API_KEY) {
|
|
259
242
|
h.authed = true;
|
|
@@ -471,6 +454,9 @@ export function getInstalledHarnessVersion(harnessId) {
|
|
|
471
454
|
const m = (res.stdout || '').match(/(\d+\.\d+\.\d+)/);
|
|
472
455
|
if (m) return m[1];
|
|
473
456
|
} else if (harnessId === 'codex') {
|
|
457
|
+
const res = spawnSync('codex', ['--version'], { timeout: 1500, encoding: 'utf8' });
|
|
458
|
+
const m = (res.stdout || '').match(/(\d+\.\d+\.\d+)/);
|
|
459
|
+
if (m) return m[1];
|
|
474
460
|
const candidatePaths = [
|
|
475
461
|
path.join(os.homedir(), '.nvm/versions/node/v24.12.0/lib/node_modules/@openai/codex/package.json'),
|
|
476
462
|
'/usr/local/lib/node_modules/@openai/codex/package.json',
|
|
@@ -496,10 +482,6 @@ export function getInstalledHarnessVersion(harnessId) {
|
|
|
496
482
|
const res = spawnSync('ollama', ['--version'], { timeout: 1500, encoding: 'utf8' });
|
|
497
483
|
const m = (res.stdout || res.stderr || '').match(/(\d+\.\d+\.\d+)/);
|
|
498
484
|
if (m) return m[1];
|
|
499
|
-
} else if (harnessId === 'gemini') {
|
|
500
|
-
const res = spawnSync('gemini', ['--version'], { timeout: 800, encoding: 'utf8' });
|
|
501
|
-
const m = (res.stdout || '').match(/(\d+\.\d+\.\d+)/);
|
|
502
|
-
if (m) return m[1];
|
|
503
485
|
} else if (harnessId === 'hermes') {
|
|
504
486
|
const res = spawnSync('hermes', ['--version'], { timeout: 1500, encoding: 'utf8' });
|
|
505
487
|
const m = (res.stdout || '').match(/(\d+\.\d+\.\d+)/);
|
|
@@ -550,19 +532,24 @@ export async function getLatestHarnessVersion(harnessId, installedVersion = 'unk
|
|
|
550
532
|
if (d.version) latest = d.version;
|
|
551
533
|
}
|
|
552
534
|
} else if (harnessId === 'agy') {
|
|
553
|
-
|
|
535
|
+
try {
|
|
536
|
+
const res = spawnSync('agy', ['changelog'], { timeout: 2000, encoding: 'utf8' });
|
|
537
|
+
const m = (res.stdout || '').match(/^(\d+\.\d+\.\d+):/m);
|
|
538
|
+
if (m) latest = m[1];
|
|
539
|
+
else latest = installedVersion !== 'unknown' ? installedVersion : '1.4.12';
|
|
540
|
+
} catch {
|
|
541
|
+
latest = installedVersion !== 'unknown' ? installedVersion : '1.4.12';
|
|
542
|
+
}
|
|
554
543
|
} else if (harnessId === 'grok') {
|
|
555
|
-
latest = '0.2.118';
|
|
544
|
+
latest = installedVersion !== 'unknown' ? installedVersion : '0.2.118';
|
|
556
545
|
} else if (harnessId === 'ollama') {
|
|
557
|
-
latest = '
|
|
558
|
-
} else if (harnessId === 'gemini') {
|
|
559
|
-
latest = '0.3.0';
|
|
546
|
+
latest = installedVersion !== 'unknown' ? installedVersion : 'unknown';
|
|
560
547
|
} else if (harnessId === 'hermes') {
|
|
561
|
-
latest = '
|
|
548
|
+
latest = installedVersion !== 'unknown' ? installedVersion : 'unknown';
|
|
562
549
|
} else if (harnessId === 'dsh') {
|
|
563
|
-
latest = '
|
|
550
|
+
latest = installedVersion !== 'unknown' ? installedVersion : 'unknown';
|
|
564
551
|
} else if (harnessId === 'openclaw') {
|
|
565
|
-
latest = '
|
|
552
|
+
latest = installedVersion !== 'unknown' ? installedVersion : 'unknown';
|
|
566
553
|
}
|
|
567
554
|
} catch {}
|
|
568
555
|
|
|
@@ -677,15 +664,6 @@ export async function queryHarnessUsage() {
|
|
|
677
664
|
}
|
|
678
665
|
if (!isAuthed) quotaRemaining = 'unavailable — token expired (refresh needed)';
|
|
679
666
|
} catch {}
|
|
680
|
-
} else if (h.id === 'gemini') {
|
|
681
|
-
try {
|
|
682
|
-
const accPath = path.join(os.homedir(), '.gemini', 'google_accounts.json');
|
|
683
|
-
if (fs.existsSync(accPath)) {
|
|
684
|
-
const acc = JSON.parse(fs.readFileSync(accPath, 'utf8'));
|
|
685
|
-
if (acc.active) identity = acc.active;
|
|
686
|
-
}
|
|
687
|
-
plan = 'Google AI API / Subscription';
|
|
688
|
-
} catch {}
|
|
689
667
|
} else if (h.id === 'ollama') {
|
|
690
668
|
identity = 'Local Machine';
|
|
691
669
|
plan = 'Offline Neural';
|
|
@@ -13,7 +13,12 @@ export function preparePluginDependencies(pluginDir) {
|
|
|
13
13
|
if (name === host.name) source = PACKAGE_ROOT;
|
|
14
14
|
else {
|
|
15
15
|
const alias = Object.entries(host.dependencies || {}).find(([, spec]) => typeof spec === 'string' && spec.startsWith(`npm:${name}@`))?.[0];
|
|
16
|
-
|
|
16
|
+
const targetPkg = alias || name;
|
|
17
|
+
try {
|
|
18
|
+
source = path.dirname(require.resolve(targetPkg));
|
|
19
|
+
} catch {
|
|
20
|
+
source = path.dirname(require.resolve(`${targetPkg}/package.json`));
|
|
21
|
+
}
|
|
17
22
|
while (true) {
|
|
18
23
|
const file = path.join(source, 'package.json');
|
|
19
24
|
if (fs.existsSync(file) && JSON.parse(fs.readFileSync(file, 'utf8')).name === name) break;
|