total-recall-brain 3.31.0 → 3.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/bin/total-recall.mjs +2 -2
  2. package/frontend/dist/assets/index-BUEZPLim.js +5742 -0
  3. package/frontend/dist/assets/index-FOJG4jQh.js +5742 -0
  4. package/frontend/dist/index.html +1 -1
  5. package/frontend/package.json +1 -1
  6. package/package.json +2 -2
  7. package/scaffold/.agent/skills/plugins/SKILL.md +5 -0
  8. package/scaffold/.agent/skills/total-recall/SKILL.md +33 -10
  9. package/src/cli/app/index.mjs +181 -1
  10. package/src/cli/command.mjs +269 -5
  11. package/src/cli/config.mjs +2 -1
  12. package/src/cli/connect.mjs +1 -1
  13. package/src/cli/daemon.mjs +3 -3
  14. package/src/cli/deploy-ui.mjs +5 -4
  15. package/src/cli/deploy.mjs +3 -2
  16. package/src/cli/init.mjs +61 -6
  17. package/src/cli/plugin/create.mjs +20 -10
  18. package/src/cli/repo-expert-generate.mjs +37 -3
  19. package/src/cli/reset-password.mjs +2 -1
  20. package/src/cli/skill-config.mjs +211 -0
  21. package/src/cli/skill.mjs +37 -2
  22. package/src/cli/uninstall.mjs +2 -1
  23. package/src/core/app-deploy/adapter-contract.mjs +176 -0
  24. package/src/core/app-deploy/design-tokens.mjs +148 -108
  25. package/src/core/app-deploy/plan.mjs +2 -2
  26. package/src/core/app-deploy/resolve.mjs +30 -2
  27. package/src/core/app-deploy/runtime-cli/node.mjs.tmpl +211 -0
  28. package/src/core/app-deploy/runtime-cli/python.py.tmpl +178 -0
  29. package/src/core/app-deploy/runtime-cli.mjs +254 -0
  30. package/src/core/app-deploy/ui-elements.mjs +473 -0
  31. package/src/core/app-deploy/verify.mjs +18 -0
  32. package/src/core/command-surface.mjs +129 -0
  33. package/src/core/daemon-control.mjs +31 -12
  34. package/src/core/daemon-loop.mjs +14 -7
  35. package/src/core/plugin-loader.mjs +12 -12
  36. package/src/core/project-brain.mjs +2 -1
  37. package/src/core/schema.mjs +72 -13
  38. package/src/core/secrets-store.mjs +5 -4
  39. package/src/core/secure-file.mjs +71 -0
  40. package/src/core/skill-config.mjs +376 -0
  41. package/src/core/skills-registry.mjs +147 -31
  42. package/src/core/surface.mjs +52 -7
  43. package/src/core/webauthn-store.mjs +2 -1
  44. package/src/server/auth.mjs +3 -2
  45. package/src/server/index.mjs +2 -1
  46. package/src/server/routes/collab.mjs +7 -3
  47. package/src/server/routes/config.mjs +4 -3
  48. package/src/server/routes/system.mjs +1 -1
  49. package/plugins/code-quality/README.md +0 -25
  50. package/plugins/code-quality/cli.mjs +0 -25
  51. package/plugins/code-quality/plugin.json +0 -17
@@ -17,7 +17,7 @@
17
17
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
18
18
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
19
19
  <title>Total Recall — Portable Memory</title>
20
- <script type="module" crossorigin src="/assets/index-DGMNZi_1.js"></script>
20
+ <script type="module" crossorigin src="/assets/index-FOJG4jQh.js"></script>
21
21
  <link rel="stylesheet" crossorigin href="/assets/index-C7_Xpk27.css">
22
22
  </head>
23
23
  <body>
@@ -16,7 +16,7 @@
16
16
  "react": "^19.2.6",
17
17
  "react-dom": "^19.2.6",
18
18
  "react-force-graph-3d": "^1.29.1",
19
- "react-router-dom": "^7.15.0",
19
+ "react-router-dom": "^7.18.4",
20
20
  "recharts": "^3.9.2",
21
21
  "three": "^0.185.1",
22
22
  "three-spritetext": "^1.10.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.31.0",
3
+ "version": "3.32.0",
4
4
  "description": "Personal AI system — database-free, SSSS-powered intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -64,7 +64,7 @@
64
64
  "homepage": "https://github.com/gregiteen/total-recall#readme",
65
65
  "dependencies": {
66
66
  "@simplewebauthn/server": "^13.3.2",
67
- "@ssss/cli": "github:gregiteen/ssss#main",
67
+ "@ssss/cli": "github:gregiteen/ssss#v0.10.0",
68
68
  "bcrypt": "^6.0.0",
69
69
  "better-sqlite3": "^12.11.1",
70
70
  "cookie-parser": "^1.4.7",
@@ -39,6 +39,11 @@ An ID is lowercase kebab case, starts with a letter, and is 2–64 characters. `
39
39
  - **Agent context:** add `compile: { "generator": "./generator.mjs", "auto": true }` and export `generateContext()` from that file. Only include information useful to the agent; avoid secrets and noisy telemetry.
40
40
  - **Memory category:** declare it under `ssss_schemas.categories` using the current manifest schema. Preserve SSSS Markdown and operation-contract writes; do not write vault documents directly.
41
41
 
42
+ - **Repo-customized skill:** a layered skill (plugin-owned `core/`, repo-owned files beside it) declares its repo config contract as `core/config.schema.json`, a JSON Schema whose root is an object. Annotate an array with `"x-collection": "<noun>"` (and optionally `"x-key"`, default `name`) to get `<noun> list|add|remove`. An optional `core/detect.mjs` exports `detect({ repoRoot, skillDir })` and returns a proposed config. Users customize the repo layer with `total-recall skill config <id> config set …`, `detect --apply`, or `init`, or with a repo command generated by `total-recall command create <name> --config-for-skill <id>`. Every write validates the whole config and commits a `skill_config` record to the repo's project vault; `config.json` is rebuilt from it. Never tell users to edit `config.json` by hand: `config validate` reports hand edits as drift, and `config import` adopts a pre-existing file. Mark gate-command string fields `"x-command": true`; `skill config <id> check`, `skill status <id>`, and `app verify` confirm each one resolves without running it.
43
+
44
+ - **App-runtime CLI:** declare `app_cli` in `plugin.json`: `name`, `api: { base_url_env, default_base_url }` (loopback only; operators override it with the env var or `--base-url`), optional `auth: { type: "bearer", token_env }`, and `commands: [{ name, method, path, args }]`. Args are typed (`string|integer|number|boolean`, `choices`, `default`, `required`, `positional`); `{name}` in `path` must be a required arg, other args go to the query (GET/DELETE) or JSON body. `total-recall app cli <plugin> --lang node|python` (or `--adapter flask|nextjs|react|ssss-app`) prints a self-contained file; `--out <file>` writes it. The file runs without Total Recall: exit 0 ok, 1 the app returned an error, 2 usage, 3 unreachable; `--json` prints `{ ok, status, data | error }`. Regenerate after changing the spec; never hand-edit the output.
45
+ - **UI elements:** author each element once as a self-contained custom element: an ES module under the plugin (e.g. `ui/<id>.js`) that imports nothing, calls `customElements.define('<tag>', …)` exactly once, and styles itself only with design-token variables (`var(--color-…)`, `--spacing-…`, `--radius-…`, `--typography-<style>-…`, `--font-family-…`; `--_name` for private ones). No literal colors, not even as `var()` fallbacks. Declare it in `plugin.json` `ui.elements[]`: `id`, `kind` (`panel|form|list|detail|settings|widget`), `tag`, `module`, optional `slot`, typed `props` (set as DOM properties), `events`, the `tokens` it requires and `optional_tokens` it uses with a token fallback; `ui.design_tokens` may point at a neutral default `DESIGN.md`. `total-recall app ui <plugin> --ui web-components|react` (or `--adapter flask|ssss-app|nextjs|react`) with `--tokens <app DESIGN.md>` checks the required tokens and generates `tokens.css`, `elements/<id>.js`, `index.js`, React wrappers (`--typescript` for `.tsx` plus `.d.ts`), and `ui-manifest.json`; `--out <dir>` writes `<dir>/<plugin-id>/`. An app restyles elements by editing its `DESIGN.md` and regenerating; the element files do not change.
46
+
42
47
  ## Validate the actual job
43
48
 
44
49
  1. Validate `plugin.json` against the installed schema and `total-recall plugin info <id>`.
@@ -16,7 +16,6 @@ repo_scoped: true
16
16
  > **Only agent skill in this package.** Implementation helpers live in `modules/` (not skills).
17
17
  > Openwiki ships with the brain at `openwiki/`.
18
18
 
19
-
20
19
  Welcome to the master control skill for the **Total Recall Autonomous AI OS**. > [!NOTE]
21
20
  > **Total Recall Architecture:**
22
21
  > The Total Recall memory kernel runs as a standalone REST service. Agents interact with the memory vault, perform semantic searches, and compile rules directly using lightweight, filesystem-native REST API calls via standard shell `curl` commands.
@@ -29,7 +28,7 @@ You MUST actively reference and apply this skill under the following specific ru
29
28
 
30
29
  ### 1. When the user establishes a rule, preference, or correction
31
30
  * **Trigger**: Whenever the user corrects your behavior, shares a coding style, defines an architectural pattern, or establishes a preference you must remember.
32
- * **Action**: Autonomously create a new SSSS v2 memory node under `.agent/memory-vault/` and compile the vault.
31
+ * **Action**: Save the new rule with `npx total-recall remember` in the appropriate category and scope. The CLI validates the node and compiles the vault.
33
32
 
34
33
  ### 2. When referencing invariants, preferences, and corrections (Intrinsically Known Rules)
35
34
  * **⚠️ MANDATORY**: The compiled instruction files (like `INSTRUCTIONS.md`, `GEMINI.md`, `AGENTS.md`, etc.) contain the **entirety** of active invariants, preferences, and behavior corrections (rules/directives).
@@ -37,7 +36,7 @@ You MUST actively reference and apply this skill under the following specific ru
37
36
 
38
37
  ### 3. When searching for complex domain knowledge or general facts
39
38
  * **Trigger**: When you need to retrieve deep factual information (e.g. from the `facts/` category) or conceptual domain structures (e.g. from `concepts/`) that are not present in your active instruction files.
40
- * **Action**: Use the semantic/lexical hybrid search engine (via the local `recall` command, or `/api/memory/search/semantic` endpoint) along with regular text search (e.g., grep):
39
+ * **Action**: Use `npx total-recall recall "<query>"` to search memory. Do not read or grep memory-vault files directly.
41
40
  * **Hybrid Search**: Blends cosine-similarity vector search and lexical density TF-IDF keyword matching using Reciprocal Rank Fusion (RRF). This blends intent-based semantic matching with exact keyword accuracy.
42
41
  * **Hierarchical Parent-Child Search**: Memory node bodies are chunked and indexed as children. The vector store searches both the parent node and child chunks, returning matches based on maximum similarity.
43
42
  * **Regular Text Search**: Use grep to locate exact configurations, literal keys, or symbols.
@@ -59,7 +58,7 @@ You MUST actively reference and apply this skill under the following specific ru
59
58
 
60
59
  ### 5. When you modify, add, or delete files in the memory vault
61
60
  * **Trigger**: Immediately after performing any memory node writes.
62
- * **Action**: Rebuild the instruction shims. If using CLI commands (`npx total-recall remember` / `forget`), recompilation runs automatically in the background (asynchronously via detached subprocesses) to minimize latency. If editing vault files directly, trigger manual compilation by sending a POST request to `/api/vault/compile` or running `npx total-recall compile`.
61
+ * **Action**: The `remember` and `forget` CLI commands compile the instruction shims automatically. Use `npx total-recall compile` only when a manual rebuild is needed; never edit vault files directly.
63
62
 
64
63
  ### 5b. When handling ANY credential, API key, or password
65
64
  Never invent a new place to keep it. It goes in the encrypted secrets store —
@@ -75,6 +74,24 @@ See **TESTS**. The full suite and the quality gates run only on the Mac Mini —
75
74
  never on a laptop or the cloud droplet. A single spec file may run on any mesh node. Builds and installs are fine on any machine; build large apps
76
75
  on the Mac Mini.
77
76
 
77
+ ### 5e. When operating any third-party provider (DNS, registrar, hosting, mail, payments, telephony)
78
+ The user's providers are already integrated: their API keys live in the secrets
79
+ store. "Use the API" for a provider means exactly that.
80
+ 1. `npx total-recall secret catalog` — find the provider's key(s) by provider tag.
81
+ 2. Call the provider's REST API or CLI with the value read inside your process
82
+ (never echoed, never in argv). Check the provider's current API docs first.
83
+ 3. **Do not** fall back to browser automation when an API exists, and **do not**
84
+ ask the user for a key the catalog already has. Browser only for flows with
85
+ no API (e.g. a portal-only compliance form).
86
+ 4. Prefer targeted writes over bulk ones: edit one DNS record by id, never
87
+ replace a whole zone by default; read the current state first and verify after.
88
+ 5. If a key is missing, find the service that already uses it (its deployed
89
+ env), import it with `secret set <key> --stdin`, and record where it lives
90
+ with `remember fact`.
91
+ 6. If the agent host's own permission mode blocks an action category (e.g. DNS
92
+ changes), say so in one line with the exact setting that allows it. Do not
93
+ argue repeatedly and do not route around it through another tool.
94
+
78
95
  ### 6. When troubleshooting connections, port blocks, or sync errors
79
96
  * **Trigger**: If the REST server is unreachable, ports are blocked, or the upstream sync tool encounters errors.
80
97
  * **Action**: Refer to the active diagnostics manual within this skill to heal the local runtime.
@@ -339,6 +356,18 @@ curl -H "Authorization: Bearer <YOUR_PAT_TOKEN>" \
339
356
 
340
357
  ## 🧩 SKILLS MANAGEMENT (`skill` subcommand)
341
358
 
359
+ ### Initializing a new repository
360
+
361
+ After initializing a project brain, initialize that repository's own
362
+ `.agent/skills/repo-expert/` from its current code, manifests, tests, and
363
+ runtime configuration. `skill generate-expert --repo <repo>` is a starting
364
+ point; review and trim generated output before use, especially directory trees
365
+ that include runtime data. The local expert must declare `repo_scoped: true`
366
+ and pass an identity check against the current repository. Register it in the
367
+ project brain. The global `repo-expert` remains a repository-agnostic discovery
368
+ method; never deploy another project's expert or synchronize local experts
369
+ across repositories.
370
+
342
371
  Two distinct layers. Confusing them is the usual cause of "my edit disappeared".
343
372
 
344
373
  | Layer | What it is | Commands |
@@ -555,12 +584,6 @@ that dead-ends back at a manual dashboard step.
555
584
 
556
585
  ---
557
586
 
558
- <!-- BEGIN INJECTED MEMORY: do not edit by hand; rebuilt by total-recall surface -->
559
- <!-- @route: tfidf, generated_at: 2026-05-21T03:34:14.837Z -->
560
-
561
- <!-- END INJECTED MEMORY -->
562
-
563
-
564
587
  ---
565
588
 
566
589
  ## 🕸️ MESH / CONTROL SERVER (`mesh` subcommand)
@@ -8,6 +8,8 @@
8
8
  * app create <dir> --plugin <source> [--adapter <id>] [--name <name>] [--force] [--json]
9
9
  * app upgrade <id> --to <source> [--target <dir>] [--adapter <id>] [--dry-run] [--json]
10
10
  * app verify [--target <dir>] [--json]
11
+ * app cli <source> [--lang node|python | --adapter <id>] [--out <file>] [--force] [--json]
12
+ * app ui <source> [--ui web-components|react | --adapter <id>] [--tokens <DESIGN.md>] [--typescript] [--out <dir>] [--force] [--json]
11
13
  */
12
14
 
13
15
  import path from 'node:path';
@@ -26,6 +28,10 @@ import {
26
28
  AppCreateError
27
29
  } from '../../core/app-deploy/standalone.mjs';
28
30
  import { verifyApplication } from '../../core/app-deploy/verify.mjs';
31
+ import { resolveCapabilitySource } from '../../core/app-deploy/source.mjs';
32
+ import { generateAppCli, languageForAdapter, APP_CLI_LANGUAGES } from '../../core/app-deploy/runtime-cli.mjs';
33
+ import { generateUiElements, writeUiElements, uiTargetForAdapter, UI_TARGETS } from '../../core/app-deploy/ui-elements.mjs';
34
+ import fs from 'node:fs';
29
35
  import {
30
36
  IncompatibleAdapterError,
31
37
  IncompatibleSsssVersionError,
@@ -73,6 +79,23 @@ Commands:
73
79
  --target <dir> Target application directory (default: cwd)
74
80
  --json Output verification report as JSON
75
81
 
82
+ cli <source> Generate the app's own runtime CLI from the plugin's app_cli spec.
83
+ The file needs no Total Recall install at run time.
84
+ --lang <lang> node (ESM script) or python (argparse module)
85
+ --adapter <id> Pick the language from the adapter (flask: python; else node)
86
+ --out <file> Write the file (mode 755) instead of printing it
87
+ --force Replace an existing --out file
88
+ --json Output { language, path, sha256, out } as JSON
89
+
90
+ ui <source> Generate the plugin's UI elements for the app, styled by its DESIGN.md.
91
+ --ui <target> web-components (Flask/Jinja, any HTML) or react (React/Next)
92
+ --adapter <id> Pick the target from the adapter (flask, ssss-app: web-components)
93
+ --tokens <file> The app's DESIGN.md (default: the plugin's own default tokens)
94
+ --typescript React wrappers as .tsx
95
+ --out <dir> Write <dir>/<plugin-id>/ (replaced whole) instead of listing files
96
+ --force Replace a previously generated <dir>/<plugin-id>/
97
+ --json Output { target, files, elements, tokens, warnings, out } as JSON
98
+
76
99
  Exit Codes:
77
100
  0 Success
78
101
  1 User/argument error or internal failure
@@ -418,7 +441,164 @@ export async function run(argv = []) {
418
441
  process.exit(0);
419
442
  }
420
443
 
421
- // 5. APP VERIFY
444
+ // 5. APP CLI
445
+ else if (sub === 'cli') {
446
+ let source = null;
447
+ let language = null;
448
+ let adapter = null;
449
+ let out = null;
450
+ let force = false;
451
+ let isJson = false;
452
+ for (let i = 0; i < rest.length; i++) {
453
+ const arg = rest[i];
454
+ if (arg === '--json') isJson = true;
455
+ else if (arg === '--force') force = true;
456
+ else if (arg === '--lang' && rest[i + 1]) language = rest[++i];
457
+ else if (arg === '--adapter' && rest[i + 1]) adapter = rest[++i];
458
+ else if (arg === '--out' && rest[i + 1]) out = rest[++i];
459
+ else if (!arg.startsWith('--') && !source) source = arg;
460
+ else {
461
+ console.error(`\n❌ Unknown or incomplete option: ${arg}\n`);
462
+ process.exit(1);
463
+ }
464
+ }
465
+ const report = (message, code) => {
466
+ if (isJson) console.error(JSON.stringify({ error: message }));
467
+ else console.error(`\n❌ ${message}\n`);
468
+ process.exit(code);
469
+ };
470
+ if (!source) report('Missing capability source. Usage: total-recall app cli <source> --lang node|python [--out <file>]', 1);
471
+ if (language && adapter) report('Pass --lang or --adapter, not both', 1);
472
+ if (!language && !adapter) report(`Pass --lang (${APP_CLI_LANGUAGES.join('|')}) or --adapter <id>`, 1);
473
+ if (adapter) {
474
+ try {
475
+ language = languageForAdapter(adapter);
476
+ } catch (err) {
477
+ report(err.message, 3);
478
+ }
479
+ }
480
+
481
+ let generated;
482
+ let resolved;
483
+ try {
484
+ resolved = await resolveCapabilitySource(source);
485
+ if (!resolved.manifest.app_cli) throw new Error(`Plugin '${resolved.id}' declares no app_cli`);
486
+ generated = generateAppCli(resolved.manifest.app_cli, { language, source: { id: resolved.id, version: resolved.version } });
487
+ } catch (err) {
488
+ if (err?.message?.startsWith('process.exit(')) throw err;
489
+ report(err.message, 1);
490
+ } finally {
491
+ resolved?.cleanup?.();
492
+ }
493
+
494
+ let written = null;
495
+ if (out) {
496
+ written = path.resolve(out);
497
+ if (fs.existsSync(written) && !force) report(`${written} exists; pass --force to replace it`, 1);
498
+ try {
499
+ fs.mkdirSync(path.dirname(written), { recursive: true });
500
+ const tmp = `${written}.${process.pid}.tmp`;
501
+ fs.writeFileSync(tmp, generated.content, { mode: generated.mode });
502
+ fs.chmodSync(tmp, generated.mode);
503
+ fs.renameSync(tmp, written);
504
+ } catch (err) {
505
+ report(`Could not write ${written}: ${err.message}`, 1);
506
+ }
507
+ }
508
+
509
+ if (isJson) {
510
+ console.log(JSON.stringify({ language: generated.language, path: generated.path, sha256: generated.sha256, out: written }));
511
+ } else if (written) {
512
+ console.log(`✔ Wrote ${generated.language} app CLI '${generated.spec.name}' to ${written} (suggested path: ${generated.path})`);
513
+ } else {
514
+ process.stdout.write(generated.content);
515
+ }
516
+ process.exit(0);
517
+ }
518
+
519
+ // 6. APP UI
520
+ else if (sub === 'ui') {
521
+ let source = null;
522
+ let target = null;
523
+ let adapter = null;
524
+ let tokens = null;
525
+ let out = null;
526
+ let typescript = false;
527
+ let force = false;
528
+ let isJson = false;
529
+ for (let i = 0; i < rest.length; i++) {
530
+ const arg = rest[i];
531
+ if (arg === '--json') isJson = true;
532
+ else if (arg === '--force') force = true;
533
+ else if (arg === '--typescript') typescript = true;
534
+ else if (arg === '--ui' && rest[i + 1]) target = rest[++i];
535
+ else if (arg === '--adapter' && rest[i + 1]) adapter = rest[++i];
536
+ else if (arg === '--tokens' && rest[i + 1]) tokens = rest[++i];
537
+ else if (arg === '--out' && rest[i + 1]) out = rest[++i];
538
+ else if (!arg.startsWith('--') && !source) source = arg;
539
+ else {
540
+ console.error(`\n❌ Unknown or incomplete option: ${arg}\n`);
541
+ process.exit(1);
542
+ }
543
+ }
544
+ const report = (message, code) => {
545
+ if (isJson) console.error(JSON.stringify({ error: message }));
546
+ else console.error(`\n❌ ${message}\n`);
547
+ process.exit(code);
548
+ };
549
+ if (!source) report('Missing capability source. Usage: total-recall app ui <source> --ui web-components|react [--tokens DESIGN.md] [--out <dir>]', 1);
550
+ if (target && adapter) report('Pass --ui or --adapter, not both', 1);
551
+ if (!target && !adapter) report(`Pass --ui (${UI_TARGETS.join('|')}) or --adapter <id>`, 1);
552
+ if (adapter) {
553
+ try {
554
+ target = uiTargetForAdapter(adapter);
555
+ } catch (err) {
556
+ report(err.message, 3);
557
+ }
558
+ }
559
+ if (!UI_TARGETS.includes(target)) report(`Unknown --ui '${target}' (known: ${UI_TARGETS.join(', ')})`, 1);
560
+ if (typescript && target !== 'react') report('--typescript applies to --ui react only', 1);
561
+ if (tokens && !fs.existsSync(tokens)) report(`Design tokens file not found: ${tokens}`, 1);
562
+
563
+ let generated;
564
+ let resolved;
565
+ let written = null;
566
+ try {
567
+ resolved = await resolveCapabilitySource(source);
568
+ generated = generateUiElements(resolved.manifest, { pluginDir: resolved.stagedDir, target, tokens, typescript });
569
+ if (out) written = writeUiElements(generated, out, { force });
570
+ } catch (err) {
571
+ if (err?.message?.startsWith('process.exit(')) throw err;
572
+ report(err.message, 1);
573
+ } finally {
574
+ resolved?.cleanup?.();
575
+ }
576
+
577
+ if (isJson) {
578
+ console.log(JSON.stringify({
579
+ target: generated.target,
580
+ plugin: generated.pluginId,
581
+ files: generated.files.map(({ path: p, sha256 }) => ({ path: p, sha256 })),
582
+ elements: generated.elements,
583
+ tokens: generated.tokens,
584
+ warnings: generated.warnings,
585
+ out: written
586
+ }));
587
+ } else {
588
+ console.log(written
589
+ ? `✔ Wrote ${generated.files.length} ${generated.target} file(s) for '${generated.pluginId}' to ${written}`
590
+ : `${generated.target} files for '${generated.pluginId}' (pass --out <dir> to write them):`);
591
+ for (const f of generated.files) console.log(` ${f.path} ${f.sha256.slice(0, 12)}`);
592
+ console.log(generated.tokens.source
593
+ ? ` tokens: ${generated.tokens.source === 'app' ? "the app's DESIGN.md" : "the plugin's default DESIGN.md"}`
594
+ : ' tokens: none — pass --tokens <DESIGN.md> to check required tokens and emit tokens.css');
595
+ for (const w of generated.warnings) console.log(` ⚠ ${w}`);
596
+ console.log(`\nUse it:\n${generated.usage.replace(/^/gm, ' ')}`);
597
+ }
598
+ process.exit(0);
599
+ }
600
+
601
+ // 7. APP VERIFY
422
602
  else if (sub === 'verify') {
423
603
  let target = process.cwd();
424
604
  let isJson = false;