total-recall-brain 3.46.2 → 3.47.1
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 +5 -0
- package/frontend/dist/assets/index-CpsWeV4X.css +1 -0
- package/frontend/dist/assets/{index-D1goIPjC.js → index-poecbHSl.js} +100 -100
- package/frontend/dist/brand/total-recall-lockup-transparent.png +0 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +1 -1
- package/frontend/src/App.tsx +15 -81
- package/frontend/src/components/DashboardOverview.tsx +164 -0
- package/frontend/src/components/brand/BrandMark.tsx +8 -6
- package/frontend/src/components/plugins/PluginWorkspace.tsx +112 -57
- package/frontend/src/components/workspace/PluginHome.tsx +3 -3
- package/frontend/src/index.css +43 -11
- package/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +30 -5
- package/scaffold/.agent/skills/total-recall/plugins/auth/skills/auth/SKILL.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/home.js +494 -79
- package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +33 -8
- package/scaffold/.agent/skills/total-recall/plugins/automations/skills/automations/SKILL.md +29 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/dream-consolidation.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +31 -4
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/skills/code-quality/SKILL.md +27 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +17 -3
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +24 -4
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/skills/embeddings/SKILL.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/git/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +300 -58
- package/scaffold/.agent/skills/total-recall/plugins/git/skills/git/SKILL.md +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +5 -33
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/git-fleet-ops.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/git-publish.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +47 -14
- package/scaffold/.agent/skills/total-recall/plugins/mesh/skills/mesh/SKILL.md +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +28 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +7 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-selection.mjs +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +9 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +50 -6
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/session-controls.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +175 -17
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +242 -31
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/skills/openwiki/SKILL.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +41 -15
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +29 -22
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +20 -33
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +42 -49
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/markdown-links.mjs +112 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +219 -65
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +21 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +51 -2
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +22 -5
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +169 -37
- package/scaffold/.agent/skills/total-recall/plugins/ssss/skills/ssss/SKILL.md +30 -0
- package/src/cli/context.mjs +6 -2
- package/src/cli/plugin/create.mjs +2 -1
- package/src/cli/plugin/monetization.mjs +66 -32
- package/src/cli/plugin/search.mjs +25 -3
- package/src/cli/preflight.mjs +40 -6
- package/src/core/context-compactor.mjs +2 -0
- package/src/core/gate-mode.mjs +13 -24
- package/src/core/plugin-distribution.mjs +35 -1
- package/src/core/preflight-gate.mjs +28 -8
- package/src/core/preflight-receipt.mjs +11 -0
- package/src/server/routes/vault.mjs +1 -0
- package/frontend/dist/assets/index-BRYE1jmv.css +0 -1
|
@@ -36,6 +36,120 @@ function readJson(file) {
|
|
|
36
36
|
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Paths outside every wiki. Total Recall rewrites them in every repository on each
|
|
41
|
+
* release (version pins in lockfiles, compiled agent instructions, copied skills), so
|
|
42
|
+
* counting them as changes paid a model to re-check pages after every release.
|
|
43
|
+
* `.openwikiignore` syntax: a leading "/" anchors at the repository root, a trailing
|
|
44
|
+
* "/" names a directory, a bare name matches at any depth.
|
|
45
|
+
*/
|
|
46
|
+
export const DEFAULT_IGNORES = Object.freeze([
|
|
47
|
+
'package-lock.json', 'npm-shrinkwrap.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb',
|
|
48
|
+
'/CLAUDE.md', '/AGENTS.md', '/GEMINI.md', '/INSTRUCTIONS.md',
|
|
49
|
+
'/.cursorrules', '/.windsurfrules', '/.clinerules',
|
|
50
|
+
'/.cursor/rules/total-recall.mdc', '/.github/copilot-instructions.md', '/.agents/rules/AGENTS.md',
|
|
51
|
+
'/.agent/skills/total-recall/', '/.agent/skills/ssss/', '/.claude/skills/total-recall/', '/.claude/skills/ssss/',
|
|
52
|
+
]);
|
|
53
|
+
const IGNORE_BLOCK_START = '# >>> total-recall openwiki defaults (managed; add your own rules outside this block)';
|
|
54
|
+
const IGNORE_BLOCK_END = '# <<< total-recall openwiki defaults';
|
|
55
|
+
|
|
56
|
+
/** Does a DEFAULT_IGNORES pattern cover this repository-relative file path? */
|
|
57
|
+
export function ignoreMatches(pattern, file) {
|
|
58
|
+
if (pattern.startsWith('/')) {
|
|
59
|
+
const body = pattern.slice(1);
|
|
60
|
+
return body.endsWith('/') ? file.startsWith(body) : file === body;
|
|
61
|
+
}
|
|
62
|
+
return file === pattern || file.endsWith(`/${pattern}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Repository paths the existing wiki cites as evidence (claims and page sources).
|
|
67
|
+
* OpenWiki refuses a run whose evidence path is ignored ("Evidence path is excluded
|
|
68
|
+
* by .openwikiignore"), so a cited path is never ignored.
|
|
69
|
+
*/
|
|
70
|
+
export function citedPaths(root) {
|
|
71
|
+
const cited = new Set();
|
|
72
|
+
const walk = (dir) => {
|
|
73
|
+
let entries;
|
|
74
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const full = path.join(dir, entry.name);
|
|
77
|
+
if (entry.isDirectory()) walk(full);
|
|
78
|
+
else if (/\.(json|md)$/.test(entry.name)) {
|
|
79
|
+
let text;
|
|
80
|
+
try { text = fs.readFileSync(full, 'utf8'); } catch { continue; }
|
|
81
|
+
for (const match of text.matchAll(/repo:\/\/([^\s"'#)\]]+)/g)) cited.add(match[1].replace(/^\/+/, ''));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
walk(path.join(root, 'openwiki'));
|
|
86
|
+
return cited;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The default ignores that apply to this repository: every one no wiki evidence cites. */
|
|
90
|
+
export function effectiveIgnores(root, cited = citedPaths(root)) {
|
|
91
|
+
return DEFAULT_IGNORES.filter((pattern) => ![...cited].some((file) => ignoreMatches(pattern, file)));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Git pathspecs that leave the ignored paths out of a commit count. */
|
|
95
|
+
export function ignorePathspecs(patterns) {
|
|
96
|
+
return patterns.map((pattern) => (pattern.startsWith('/')
|
|
97
|
+
? `:(exclude)${pattern.slice(1).replace(/\/$/, '')}`
|
|
98
|
+
: `:(exclude,glob)**/${pattern}`));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Keep the managed default block in the repository's `.openwikiignore`, leaving
|
|
103
|
+
* every other line as the owner wrote it. Writes only when the block changes.
|
|
104
|
+
*/
|
|
105
|
+
export function ensureDefaultIgnores(root, patterns = effectiveIgnores(root)) {
|
|
106
|
+
const file = path.join(root, '.openwikiignore');
|
|
107
|
+
const current = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
108
|
+
const start = current.indexOf(IGNORE_BLOCK_START);
|
|
109
|
+
const end = current.indexOf(IGNORE_BLOCK_END);
|
|
110
|
+
const outside = start >= 0 && end > start ? current.slice(0, start) + current.slice(end + IGNORE_BLOCK_END.length) : current;
|
|
111
|
+
const block = [
|
|
112
|
+
IGNORE_BLOCK_START,
|
|
113
|
+
'# Rewritten in every repository by each Total Recall release: lockfiles, compiled agent',
|
|
114
|
+
'# instructions and copied skills. A path the existing wiki cites is left out of this list.',
|
|
115
|
+
...patterns,
|
|
116
|
+
IGNORE_BLOCK_END,
|
|
117
|
+
].join('\n');
|
|
118
|
+
const rest = outside.replace(/^\n+|\n+$/g, '');
|
|
119
|
+
const next = `${rest ? `${rest}\n\n` : ''}${block}\n`;
|
|
120
|
+
if (next === current) return { file, written: false, patterns };
|
|
121
|
+
fs.writeFileSync(file, next);
|
|
122
|
+
return { file, written: true, patterns };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const VERSION_LINE = /^[+-]\s*"([^"]+)"\s*:\s*"([^"]*)"\s*,?\s*$/;
|
|
126
|
+
const VERSION_SPEC = /^(?:npm:(?:@[^/\s]+\/)?[^@\s]+@)?[~^<>=\s]*v?\d+(?:\.\d+)*(?:[-+][\w.-]+)?$/;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* How many commits in `git log -p -U0 --format=%x1eCOMMIT %H` output changed
|
|
130
|
+
* nothing but version strings in package.json files. A Total Recall release pins
|
|
131
|
+
* its new version into every repository this way; that is not a change to the code
|
|
132
|
+
* the wiki documents. Any other edit to package.json (a script, a new dependency)
|
|
133
|
+
* still counts.
|
|
134
|
+
*/
|
|
135
|
+
export function versionOnlyCommits(logPatch) {
|
|
136
|
+
let total = 0;
|
|
137
|
+
for (const chunk of logPatch.split('\x1eCOMMIT ').slice(1)) {
|
|
138
|
+
const files = [...chunk.matchAll(/^diff --git a\/(\S+) b\/(\S+)$/gm)].map((m) => m[2]);
|
|
139
|
+
if (!files.length || files.some((file) => path.posix.basename(file) !== 'package.json')) continue;
|
|
140
|
+
const changed = chunk.split('\n').filter((line) => /^[+-]/.test(line) && !/^(\+\+\+|---) /.test(line));
|
|
141
|
+
const removed = [], added = [];
|
|
142
|
+
let versionsOnly = changed.length > 0;
|
|
143
|
+
for (const line of changed) {
|
|
144
|
+
const match = VERSION_LINE.exec(line);
|
|
145
|
+
if (!match || !VERSION_SPEC.test(match[2].trim())) { versionsOnly = false; break; }
|
|
146
|
+
(line[0] === '-' ? removed : added).push(match[1]);
|
|
147
|
+
}
|
|
148
|
+
if (versionsOnly && removed.sort().join('\n') === added.sort().join('\n')) total += 1;
|
|
149
|
+
}
|
|
150
|
+
return total;
|
|
151
|
+
}
|
|
152
|
+
|
|
39
153
|
/** An unfinished init must resume as init, even after it has written pages. */
|
|
40
154
|
export function generationMode(root) {
|
|
41
155
|
const state = readJson(path.join(root, 'openwiki', '.run.json'));
|
|
@@ -51,7 +165,7 @@ export function registryPath(home = os.homedir()) {
|
|
|
51
165
|
* Read the registered projects. The registry is the same source the updater
|
|
52
166
|
* uses to reach every consumer, so fleet scope is defined in exactly one place.
|
|
53
167
|
*/
|
|
54
|
-
export function readProjects({ registry = registryPath() } = {}) {
|
|
168
|
+
export function readProjects({ registry = registryPath(), exclude = null } = {}) {
|
|
55
169
|
let raw;
|
|
56
170
|
try {
|
|
57
171
|
raw = fs.readFileSync(registry, 'utf8');
|
|
@@ -74,14 +188,18 @@ export function readProjects({ registry = registryPath() } = {}) {
|
|
|
74
188
|
}
|
|
75
189
|
// One project per root: the registry has historically held duplicates.
|
|
76
190
|
const seen = new Set();
|
|
191
|
+
const excluded = new Set((exclude ?? loadOpenWikiConfig().exclude ?? []).map((root) => path.resolve(root)));
|
|
77
192
|
return projects.filter((p) => {
|
|
78
193
|
const key = path.resolve(p.root);
|
|
79
194
|
if (seen.has(key)) return false;
|
|
80
195
|
seen.add(key);
|
|
81
196
|
return true;
|
|
82
|
-
});
|
|
197
|
+
}).map((p) => ({ ...p, excluded: excluded.has(path.resolve(p.root)) }));
|
|
83
198
|
}
|
|
84
199
|
|
|
200
|
+
export const EXCLUDED = "excluded from OpenWiki (total-recall openwiki exclude --remove <name> to include it)";
|
|
201
|
+
export const NO_WIKI = "no finished wiki; the nightly refresh only updates existing wikis. Create one with: total-recall openwiki generate --repo <name>";
|
|
202
|
+
|
|
85
203
|
/** Does this project carry generated Markdown pages? */
|
|
86
204
|
export function hasWiki(root) {
|
|
87
205
|
const dir = path.join(root, 'openwiki');
|
|
@@ -139,15 +257,22 @@ export function staleness(root, { git = gitRun } = {}) {
|
|
|
139
257
|
}
|
|
140
258
|
} catch { files = null; }
|
|
141
259
|
|
|
142
|
-
|
|
260
|
+
// A commit that touches only the ignored paths (a release's version pins and
|
|
261
|
+
// compiled instructions) is not a change to document.
|
|
262
|
+
const scope = ['--', '.', ':!openwiki', ...ignorePathspecs(effectiveIgnores(root))];
|
|
263
|
+
const log = git('git', ['-C', root, 'log', '-1', '--format=%cI', ...scope], { encoding: 'utf8' });
|
|
143
264
|
const lastCodeChange = !log.error && log.status === 0 ? String(log.stdout || '').trim() || null : null;
|
|
144
265
|
|
|
145
266
|
let commitsSince = null;
|
|
146
267
|
if (generationCommit || generatedAt) {
|
|
147
268
|
const range = generationCommit ? [`${generationCommit}..HEAD`] : [`--since=${generatedAt}`, 'HEAD'];
|
|
148
|
-
const count = git('git', ['-C', root, 'rev-list', '--count', ...range,
|
|
269
|
+
const count = git('git', ['-C', root, 'rev-list', '--count', ...range, ...scope], { encoding: 'utf8' });
|
|
149
270
|
const value = String(count.stdout || '').trim();
|
|
150
271
|
if (!count.error && count.status === 0 && /^\d+$/.test(value)) commitsSince = Number(value);
|
|
272
|
+
if (commitsSince > 0) {
|
|
273
|
+
const patches = git('git', ['-C', root, 'log', '-p', '-U0', '--no-color', '--no-ext-diff', '--format=%x1eCOMMIT %H', ...range, ...scope], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
|
|
274
|
+
if (!patches.error && patches.status === 0) commitsSince = Math.max(0, commitsSince - versionOnlyCommits(String(patches.stdout || '')));
|
|
275
|
+
}
|
|
151
276
|
}
|
|
152
277
|
const incomplete = generatedStatus === 'interrupted' || stillGenerating(root);
|
|
153
278
|
const stale = incomplete || generatedAt === null || commitsSince === null || commitsSince > 0;
|
|
@@ -287,12 +412,44 @@ function writeReceipt(root, name, value) {
|
|
|
287
412
|
}
|
|
288
413
|
}
|
|
289
414
|
|
|
290
|
-
|
|
415
|
+
export const CREDIT_REFUSAL = /\b402\b|insufficient credits|requires more credits|credit limit/i;
|
|
416
|
+
|
|
417
|
+
export function redact(value, key) {
|
|
291
418
|
let text = String(value || '');
|
|
292
419
|
if (key) text = text.split(key).join('[REDACTED]');
|
|
293
420
|
return text.replace(/\bBearer\s+\S+/gi, 'Bearer [REDACTED]').replace(/\bsk-[\w-]+/g, '[REDACTED]');
|
|
294
421
|
}
|
|
295
422
|
|
|
423
|
+
// The model is the operator's choice; models and prices change too often for a default here.
|
|
424
|
+
export const NO_MODEL = 'No OpenWiki model is configured. Check current OpenRouter models and prices, then run: total-recall openwiki config set model <model-id>';
|
|
425
|
+
|
|
426
|
+
export function buildGeneratorInvocation({ baseArgs = [], mode = 'update', config = {}, key = '' } = {}) {
|
|
427
|
+
const selectedModel = String(config.model || '').trim();
|
|
428
|
+
if (!selectedModel) throw new Error(NO_MODEL);
|
|
429
|
+
const selectedProvider = config.provider || 'openrouter';
|
|
430
|
+
const cmdArgs = [...baseArgs, `--${mode}`, '-p', '--modelId', selectedModel];
|
|
431
|
+
const childEnv = { ...process.env };
|
|
432
|
+
if (selectedProvider === 'google') {
|
|
433
|
+
childEnv.GOOGLE_API_KEY = key;
|
|
434
|
+
childEnv.GEMINI_API_KEY = key;
|
|
435
|
+
childEnv.OPENWIKI_PROVIDER = 'gemini';
|
|
436
|
+
delete childEnv.OPENAI_API_KEY;
|
|
437
|
+
delete childEnv.OPENROUTER_API_KEY;
|
|
438
|
+
} else {
|
|
439
|
+
childEnv.OPENROUTER_API_KEY = key;
|
|
440
|
+
childEnv.OPENWIKI_PROVIDER = 'openrouter';
|
|
441
|
+
if (config.max_tokens) {
|
|
442
|
+
childEnv.OPENWIKI_OPENROUTER_MAX_TOKENS = String(config.max_tokens);
|
|
443
|
+
childEnv.OPENWIKI_MAX_OUTPUT_TOKENS = String(config.max_tokens);
|
|
444
|
+
}
|
|
445
|
+
delete childEnv.OPENAI_API_KEY;
|
|
446
|
+
}
|
|
447
|
+
if (config.timeout_ms) {
|
|
448
|
+
childEnv.OPENWIKI_TIMEOUT = String(config.timeout_ms);
|
|
449
|
+
}
|
|
450
|
+
return { cmdArgs, childEnv, selectedModel, selectedProvider };
|
|
451
|
+
}
|
|
452
|
+
|
|
296
453
|
function finishRefresh(project, { before, result, git, key, mode, startedAt, via, clock }) {
|
|
297
454
|
const after = staleness(project.root, { git });
|
|
298
455
|
const exitCode = result.error ? (result.error.code || 1) : result.signal ? 1 : result.status;
|
|
@@ -363,34 +520,17 @@ export function refreshProject(project, {
|
|
|
363
520
|
({ command, args, via } = resolved);
|
|
364
521
|
}
|
|
365
522
|
|
|
366
|
-
const config =
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
cmdArgs.push('--modelId', selectedModel);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const childEnv = { ...process.env };
|
|
377
|
-
if (selectedProvider === 'google') {
|
|
378
|
-
childEnv.GOOGLE_API_KEY = key;
|
|
379
|
-
childEnv.GEMINI_API_KEY = key;
|
|
380
|
-
childEnv.OPENWIKI_PROVIDER = 'gemini';
|
|
381
|
-
delete childEnv.OPENAI_API_KEY;
|
|
382
|
-
delete childEnv.OPENROUTER_API_KEY;
|
|
383
|
-
} else {
|
|
384
|
-
childEnv.OPENROUTER_API_KEY = key;
|
|
385
|
-
childEnv.OPENWIKI_PROVIDER = 'openrouter';
|
|
386
|
-
if (config.max_tokens) {
|
|
387
|
-
childEnv.OPENWIKI_OPENROUTER_MAX_TOKENS = String(config.max_tokens);
|
|
388
|
-
childEnv.OPENWIKI_MAX_OUTPUT_TOKENS = String(config.max_tokens);
|
|
389
|
-
}
|
|
390
|
-
delete childEnv.OPENAI_API_KEY;
|
|
391
|
-
}
|
|
523
|
+
const config = loadOpenWikiConfig();
|
|
524
|
+
const { cmdArgs, childEnv } = buildGeneratorInvocation({
|
|
525
|
+
baseArgs: args,
|
|
526
|
+
mode,
|
|
527
|
+
config: { ...config, model: model || config.model, provider: provider || config.provider },
|
|
528
|
+
key,
|
|
529
|
+
});
|
|
392
530
|
|
|
531
|
+
ensureDefaultIgnores(project.root);
|
|
393
532
|
const startedAt = now;
|
|
533
|
+
writeReceipt(project.root, '.refresh-started.json', { startedAt: new Date(startedAt).toISOString(), sourceCommit: before.head || null });
|
|
394
534
|
const result = run(command, cmdArgs, {
|
|
395
535
|
cwd: project.root,
|
|
396
536
|
env: childEnv,
|
|
@@ -421,34 +561,17 @@ export async function refreshProjectAsync(project, {
|
|
|
421
561
|
({ command, args, via } = resolved);
|
|
422
562
|
}
|
|
423
563
|
|
|
424
|
-
const config =
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
cmdArgs.push('--modelId', selectedModel);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const childEnv = { ...process.env };
|
|
435
|
-
if (selectedProvider === 'google') {
|
|
436
|
-
childEnv.GOOGLE_API_KEY = key;
|
|
437
|
-
childEnv.GEMINI_API_KEY = key;
|
|
438
|
-
childEnv.OPENWIKI_PROVIDER = 'gemini';
|
|
439
|
-
delete childEnv.OPENAI_API_KEY;
|
|
440
|
-
delete childEnv.OPENROUTER_API_KEY;
|
|
441
|
-
} else {
|
|
442
|
-
childEnv.OPENROUTER_API_KEY = key;
|
|
443
|
-
childEnv.OPENWIKI_PROVIDER = 'openrouter';
|
|
444
|
-
if (config.max_tokens) {
|
|
445
|
-
childEnv.OPENWIKI_OPENROUTER_MAX_TOKENS = String(config.max_tokens);
|
|
446
|
-
childEnv.OPENWIKI_MAX_OUTPUT_TOKENS = String(config.max_tokens);
|
|
447
|
-
}
|
|
448
|
-
delete childEnv.OPENAI_API_KEY;
|
|
449
|
-
}
|
|
564
|
+
const config = loadOpenWikiConfig();
|
|
565
|
+
const { cmdArgs, childEnv } = buildGeneratorInvocation({
|
|
566
|
+
baseArgs: args,
|
|
567
|
+
mode,
|
|
568
|
+
config: { ...config, model: model || config.model, provider: provider || config.provider },
|
|
569
|
+
key,
|
|
570
|
+
});
|
|
450
571
|
|
|
572
|
+
ensureDefaultIgnores(project.root);
|
|
451
573
|
const startedAt = now;
|
|
574
|
+
writeReceipt(project.root, '.refresh-started.json', { startedAt: new Date(startedAt).toISOString(), sourceCommit: before.head || null });
|
|
452
575
|
const result = await new Promise((resolve) => {
|
|
453
576
|
const proc = spawnProcess(command, cmdArgs, {
|
|
454
577
|
cwd: project.root,
|
|
@@ -465,22 +588,53 @@ export async function refreshProjectAsync(project, {
|
|
|
465
588
|
return finishRefresh(project, { before, result, git, key, mode, startedAt, via, clock });
|
|
466
589
|
}
|
|
467
590
|
|
|
468
|
-
/**
|
|
591
|
+
/**
|
|
592
|
+
* Start time (ms) of the latest generation attempt, or NaN when none is recorded.
|
|
593
|
+
* `.refresh-started.json` is written before the generator runs, so an attempt
|
|
594
|
+
* the task runner killed still counts; the other two are written by the
|
|
595
|
+
* generator when it begins and by the driver when an attempt ends.
|
|
596
|
+
*/
|
|
597
|
+
export function lastAttemptStart(root, generatedAt = null) {
|
|
598
|
+
const times = [
|
|
599
|
+
readJson(path.join(root, 'openwiki', '.refresh-started.json'))?.startedAt,
|
|
600
|
+
readJson(path.join(root, 'openwiki', '.refresh-attempt.json'))?.startedAt,
|
|
601
|
+
generatedAt,
|
|
602
|
+
].map((value) => (typeof value === 'string' ? Date.parse(value) : NaN)).filter(Number.isFinite);
|
|
603
|
+
return times.length ? Math.max(...times) : NaN;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Is a refresh due for this project under the configured interval?
|
|
608
|
+
*
|
|
609
|
+
* Every attempt, including a retry of an interrupted or failed one, waits the
|
|
610
|
+
* full interval after the previous attempt began. Retrying an interrupted run
|
|
611
|
+
* on every scheduler slot spent about $10 a day on 2026-11-12/13: the task
|
|
612
|
+
* runner killed the same generations every 30 minutes and they never finished.
|
|
613
|
+
*/
|
|
469
614
|
export function isDue(project, { lockMs = DEFAULT_LOCK_MS, now = Date.now(), git = gitRun, force = false } = {}) {
|
|
615
|
+
if (project.excluded) return { due: false, excluded: true, reason: EXCLUDED };
|
|
470
616
|
if (!isGitRepo(project.root, { git })) {
|
|
471
617
|
return { due: false, skipped: true, reason: 'not a git repository' };
|
|
472
618
|
}
|
|
473
619
|
const state = staleness(project.root, { git });
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
620
|
+
// Creating a wiki is the most expensive run there is, and a registered
|
|
621
|
+
// repository may be someone else's code. Only an explicit `openwiki generate`
|
|
622
|
+
// creates one or finishes an interrupted creation; refresh, --force included,
|
|
623
|
+
// only updates finished wikis.
|
|
624
|
+
if (generationMode(project.root) === 'init') {
|
|
625
|
+
return { due: false, uninitialized: true, ...state, reason: NO_WIKI };
|
|
477
626
|
}
|
|
627
|
+
if (force) return { due: true, uninitialized: false, ...state };
|
|
628
|
+
const last = lastAttemptStart(project.root, state.generatedAt);
|
|
629
|
+
const retryAt = Number.isFinite(last) && now - last < lockMs ? new Date(last + lockMs).toISOString() : null;
|
|
478
630
|
if (!state.stale) return { due: false, ...state };
|
|
479
|
-
|
|
480
|
-
if (
|
|
481
|
-
|
|
482
|
-
|
|
631
|
+
const incomplete = state.generatedStatus === 'interrupted' || stillGenerating(project.root);
|
|
632
|
+
if (retryAt) {
|
|
633
|
+
return incomplete
|
|
634
|
+
? { due: false, held: true, reason: `incomplete generation attempted recently; resumes after ${retryAt}`, ...state }
|
|
635
|
+
: { due: false, reason: 'refreshed recently', ...state };
|
|
483
636
|
}
|
|
637
|
+
if (incomplete) return { due: true, reason: 'resume incomplete generation', ...state };
|
|
484
638
|
return { due: true, ...state };
|
|
485
639
|
}
|
|
486
640
|
|
|
@@ -29,6 +29,7 @@ export async function searchWikiHubs(query, {
|
|
|
29
29
|
limit = 20,
|
|
30
30
|
cwd = process.cwd(),
|
|
31
31
|
exactTerms = false,
|
|
32
|
+
nested = true,
|
|
32
33
|
} = {}) {
|
|
33
34
|
if (!query || typeof query !== 'string' || !query.trim()) {
|
|
34
35
|
throw new Error('Search query must be a non-empty string');
|
|
@@ -50,7 +51,7 @@ export async function searchWikiHubs(query, {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
const hubsResult = await discoverAllHubs({ allVaults, cwd, nested
|
|
54
|
+
const hubsResult = await discoverAllHubs({ allVaults, cwd, nested });
|
|
54
55
|
const rawTerms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
55
56
|
const terms = exactTerms ? wikiTaskTerms(query) : rawTerms.map(term => term.replace(/[^\w-]/g, '')).filter(Boolean);
|
|
56
57
|
|
|
@@ -10,7 +10,7 @@ import os from 'node:os';
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
12
|
import { compareSemanticVersions, formatPropagationReceipt } from '../capability/index.mjs';
|
|
13
|
-
import {
|
|
13
|
+
import { CREDIT_REFUSAL, generationMode, readProjects, refreshProject, resolveKey } from './refresh.mjs';
|
|
14
14
|
|
|
15
15
|
export const UPSTREAM_NPM_URL = 'https://registry.npmjs.org/openwiki/latest';
|
|
16
16
|
export const UPSTREAM_GITHUB_URL = 'https://api.github.com/repos/langchain-ai/openwiki/releases/latest';
|
|
@@ -338,8 +338,9 @@ export async function propagateUpdates({
|
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
//
|
|
342
|
-
|
|
341
|
+
// Only finished wikis outside the exclude list: propagation updates pages,
|
|
342
|
+
// it never creates a wiki or documents an excluded clone, fork or backup.
|
|
343
|
+
const eligible = projects.filter(p => !p.excluded && generationMode(p.root) === 'update');
|
|
343
344
|
if (eligible.length === 0) {
|
|
344
345
|
return {
|
|
345
346
|
ok: true,
|
|
@@ -378,8 +379,19 @@ export async function propagateUpdates({
|
|
|
378
379
|
return { ok: false, error: error.message, eligibleCount: eligible.length, updatedCount: 0, reports: [] };
|
|
379
380
|
}
|
|
380
381
|
const reports = [];
|
|
382
|
+
let creditStopReason = null;
|
|
381
383
|
|
|
382
384
|
for (const project of eligible) {
|
|
385
|
+
if (creditStopReason) {
|
|
386
|
+
reports.push({
|
|
387
|
+
name: project.name,
|
|
388
|
+
root: project.root,
|
|
389
|
+
action: 'deferred',
|
|
390
|
+
reason: creditStopReason,
|
|
391
|
+
generatorVersion: targetVersion
|
|
392
|
+
});
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
383
395
|
try {
|
|
384
396
|
const outcome = await refresher(project, {
|
|
385
397
|
key,
|
|
@@ -392,6 +404,9 @@ export async function propagateUpdates({
|
|
|
392
404
|
credential: from,
|
|
393
405
|
generatorVersion: targetVersion
|
|
394
406
|
});
|
|
407
|
+
if (CREDIT_REFUSAL.test(String(outcome?.reason || ''))) {
|
|
408
|
+
creditStopReason = `deferred: provider refused ${project.name} for credit; add credit, then propagate again`;
|
|
409
|
+
}
|
|
395
410
|
} catch (err) {
|
|
396
411
|
reports.push({
|
|
397
412
|
name: project.name,
|
|
@@ -400,6 +415,9 @@ export async function propagateUpdates({
|
|
|
400
415
|
reason: err.message,
|
|
401
416
|
generatorVersion: targetVersion
|
|
402
417
|
});
|
|
418
|
+
if (CREDIT_REFUSAL.test(String(err?.message || ''))) {
|
|
419
|
+
creditStopReason = `deferred: provider refused ${project.name} for credit; add credit, then propagate again`;
|
|
420
|
+
}
|
|
403
421
|
}
|
|
404
422
|
}
|
|
405
423
|
|
|
@@ -54,7 +54,8 @@ template.innerHTML = `
|
|
|
54
54
|
.grid { display: grid; gap: var(--spacing-lg, 1.5rem); grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
55
55
|
ul { list-style: none; padding: 0; margin: 0; }
|
|
56
56
|
li { overflow-wrap: anywhere; }
|
|
57
|
-
.index li, .links li { border-bottom: 1px solid var(--color-outline, #cbb497); padding-block: 0.5rem; }
|
|
57
|
+
.index li, .links li, .issues li { border-bottom: 1px solid var(--color-outline, #cbb497); padding-block: 0.5rem; }
|
|
58
|
+
.health { margin-block-start: var(--spacing-lg, 1.5rem); }
|
|
58
59
|
.index small { display: block; }
|
|
59
60
|
small, code { font: 0.8rem/1.5 var(--typography-source-font-family, ui-monospace, monospace); }
|
|
60
61
|
.results li {
|
|
@@ -106,6 +107,14 @@ template.innerHTML = `
|
|
|
106
107
|
<ul id="links" class="links" aria-label="Documentation links"></ul>
|
|
107
108
|
</section>
|
|
108
109
|
</div>
|
|
110
|
+
<section class="health" aria-labelledby="health">
|
|
111
|
+
<div class="graph-tools">
|
|
112
|
+
<h3 id="health">Link health</h3>
|
|
113
|
+
<button type="button" id="lint">Check health</button>
|
|
114
|
+
</div>
|
|
115
|
+
<p id="lint-state" class="state" role="status">Audit broken links, missing titles, and orphaned pages.</p>
|
|
116
|
+
<ul id="issues" class="issues" aria-label="Documentation health issues"></ul>
|
|
117
|
+
</section>
|
|
109
118
|
</section>
|
|
110
119
|
`;
|
|
111
120
|
|
|
@@ -124,13 +133,16 @@ class OpenWikiPanel extends HTMLElement {
|
|
|
124
133
|
this.attachShadow({ mode: 'open' }).append(template.content.cloneNode(true));
|
|
125
134
|
this.#field('reload').addEventListener('click', () => void this.load());
|
|
126
135
|
this.#field('graph').addEventListener('click', () => void this.#graph());
|
|
136
|
+
this.#field('lint').addEventListener('click', () => void this.#lint());
|
|
127
137
|
this.#field('search').addEventListener('click', () => void this.#search());
|
|
128
138
|
this.#field('scope').addEventListener('change', () => {
|
|
129
139
|
this.#field('results').replaceChildren();
|
|
130
140
|
this.#field('links').replaceChildren();
|
|
141
|
+
this.#field('issues').replaceChildren();
|
|
131
142
|
const label = this.#scope() === 'all-vaults' ? 'registered wikis' : 'this workspace';
|
|
132
143
|
this.#state('search-state', `Search headings and passages in ${label}.`);
|
|
133
144
|
this.#state('graph-state', `Trace links between pages in ${label}.`);
|
|
145
|
+
this.#state('lint-state', `Audit broken links, missing titles, and orphaned pages in ${label}.`);
|
|
134
146
|
});
|
|
135
147
|
this.#field('query').addEventListener('keydown', event => {
|
|
136
148
|
if (event.key !== 'Enter' || event.isComposing) return;
|
|
@@ -269,11 +281,14 @@ class OpenWikiPanel extends HTMLElement {
|
|
|
269
281
|
list.append(row);
|
|
270
282
|
}
|
|
271
283
|
const { nodes, edges: total, orphans } = result.counts;
|
|
284
|
+
const orphanNames = Array.isArray(result.orphans) && result.orphans.length > 0
|
|
285
|
+
? ` (${result.orphans.slice(0, 4).map(id => labels.get(id) ?? id).join(', ')}${result.orphans.length > 4 ? ', …' : ''})`
|
|
286
|
+
: '';
|
|
272
287
|
const shown = total > edges.length ? ` Showing ${edges.length}.` : '';
|
|
273
288
|
const pages = nodes === 1 ? '1 page' : `${nodes} pages`;
|
|
274
289
|
const links = total === 1 ? '1 link' : `${total} links`;
|
|
275
290
|
this.#state('graph-state', nodes === 0 ? `No pages to map in ${label}.` :
|
|
276
|
-
`${pages}, ${links}, ${orphans} unlinked in ${label}.${shown}`);
|
|
291
|
+
`${pages}, ${links}, ${orphans} unlinked${orphanNames} in ${label}.${shown}`);
|
|
277
292
|
} catch {
|
|
278
293
|
if (scope !== this.#scope()) return;
|
|
279
294
|
list.replaceChildren();
|
|
@@ -282,6 +297,40 @@ class OpenWikiPanel extends HTMLElement {
|
|
|
282
297
|
button.disabled = false;
|
|
283
298
|
}
|
|
284
299
|
}
|
|
300
|
+
|
|
301
|
+
async #lint() {
|
|
302
|
+
const button = this.#field('lint');
|
|
303
|
+
if (button.disabled) return;
|
|
304
|
+
button.disabled = true;
|
|
305
|
+
const scope = this.#scope();
|
|
306
|
+
const label = scope === 'all-vaults' ? 'registered wikis' : 'this workspace';
|
|
307
|
+
this.#state('lint-state', `Auditing link health in ${label}…`);
|
|
308
|
+
const list = this.#field('issues');
|
|
309
|
+
list.replaceChildren();
|
|
310
|
+
try {
|
|
311
|
+
const result = await this.host.run('lint', scope === 'all-vaults' ? ['--all-vaults'] : []);
|
|
312
|
+
if (scope !== this.#scope()) return;
|
|
313
|
+
if (!result || !number(result.healthScore) || !number(result.totalHubs) || !Array.isArray(result.issues)) {
|
|
314
|
+
throw new Error('Invalid link health report');
|
|
315
|
+
}
|
|
316
|
+
const shownIssues = result.issues.slice(0, 12);
|
|
317
|
+
for (const issue of shownIssues) {
|
|
318
|
+
const row = element('li', '');
|
|
319
|
+
const fileName = typeof issue.file === 'string' ? issue.file.split('/').pop() : 'unknown';
|
|
320
|
+
row.append(element('strong', `[${issue.type}] ${fileName} `), element('span', issue.message || ''));
|
|
321
|
+
list.append(row);
|
|
322
|
+
}
|
|
323
|
+
const clipped = result.issues.length > shownIssues.length ? ` Showing ${shownIssues.length} of ${result.issues.length}.` : '';
|
|
324
|
+
const summary = `${result.healthScore}% health across ${result.totalHubs} page(s), ${result.brokenLinksCount ?? 0} broken link(s), ${result.orphanCount ?? 0} unlinked.${clipped}`;
|
|
325
|
+
this.#state('lint-state', summary, result.issues.length > 0);
|
|
326
|
+
} catch {
|
|
327
|
+
if (scope !== this.#scope()) return;
|
|
328
|
+
list.replaceChildren();
|
|
329
|
+
this.#state('lint-state', 'Link health audit unavailable. Try again.', true);
|
|
330
|
+
} finally {
|
|
331
|
+
button.disabled = false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
285
334
|
}
|
|
286
335
|
|
|
287
336
|
if (!customElements.get('openwiki-panel')) customElements.define('openwiki-panel', OpenWikiPanel);
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"jsdom": "^26.1.0",
|
|
16
|
-
"total-recall-brain": "3.
|
|
16
|
+
"total-recall-brain": "3.47.0"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"node_modules/@alcalzone/ansi-tokenize": {
|
|
@@ -5698,9 +5698,9 @@
|
|
|
5698
5698
|
}
|
|
5699
5699
|
},
|
|
5700
5700
|
"node_modules/total-recall-brain": {
|
|
5701
|
-
"version": "3.
|
|
5702
|
-
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.
|
|
5703
|
-
"integrity": "sha512-
|
|
5701
|
+
"version": "3.47.0",
|
|
5702
|
+
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.47.0.tgz",
|
|
5703
|
+
"integrity": "sha512-S4UIdbbTyxw7C4+rix+7oHMm/6gYkcxwNBcP6kRDySX40p5IgqVTKgHk1n8rmaKiyYFoIX/HZHlHSjNrHgn0Gw==",
|
|
5704
5704
|
"dev": true,
|
|
5705
5705
|
"license": "MIT",
|
|
5706
5706
|
"workspaces": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tr-plugin-security",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Standalone security suite for Total Recall: static analysis, file and permission hardening schemes, AES-256-GCM encryption tools, and egress audit controls",
|
|
6
6
|
"scripts": {
|
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
"$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
|
|
3
3
|
"id": "security",
|
|
4
4
|
"name": "Security Assessments",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.1",
|
|
6
6
|
"description": "Machine and device security assessments for macOS, Linux, Windows, Android, Chromebook, iPhone and iPad, with explicit measured and manual coverage.",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"
|
|
8
|
+
"reuse": {
|
|
9
|
+
"commands": [
|
|
10
|
+
"total-recall auth secret",
|
|
11
|
+
"total-recall ssss status"
|
|
12
|
+
],
|
|
13
|
+
"owned": "Machine and device security assessments, header probes, and security evidence audits across platforms."
|
|
14
|
+
},
|
|
9
15
|
"branding": {
|
|
10
16
|
"name": "Security Assessments",
|
|
11
17
|
"color": "#31564b",
|
|
@@ -66,10 +72,21 @@
|
|
|
66
72
|
}
|
|
67
73
|
],
|
|
68
74
|
"gate": {
|
|
69
|
-
"tasks": [
|
|
75
|
+
"tasks": [
|
|
76
|
+
"Audit a machine, network or the fleet",
|
|
77
|
+
"Scan installed plugins or a source folder",
|
|
78
|
+
"Harden SSH permissions",
|
|
79
|
+
"Audit or protect a website you are authorized to test"
|
|
80
|
+
],
|
|
70
81
|
"owned_commands": [
|
|
71
|
-
{
|
|
72
|
-
|
|
82
|
+
{
|
|
83
|
+
"pattern": "nmap\\s",
|
|
84
|
+
"use": "total-recall security network --json"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"pattern": "lynis\\s",
|
|
88
|
+
"use": "total-recall security machine --json"
|
|
89
|
+
}
|
|
73
90
|
]
|
|
74
91
|
},
|
|
75
92
|
"cli": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tr-plugin-ssss",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Canonical SSSS distribution, scaffolding and validation for Total Recall installations.",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"scripts/",
|
|
13
13
|
"cli.mjs",
|
|
14
14
|
"DESIGN.md",
|
|
15
|
-
"plugin.json"
|
|
15
|
+
"plugin.json",
|
|
16
|
+
"skills/"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"test": "node scripts/refresh-scaffold.mjs --check && node --test test/*.test.mjs",
|