@fiodos/cli 0.1.14 → 0.1.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiodos/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Fiodos CLI — analyzes your app's source code and generates the in-app voice-agent manifest, then wires the orb. Powers `npx @fiodos/cli analyze`.",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/src/aiAnalyze.js CHANGED
@@ -76,7 +76,7 @@ AppManifest (all fields required unless noted):
76
76
  "requireConfirmation": boolean,
77
77
  "confirmationMessageTemplate": string (REQUIRED if requireConfirmation true),
78
78
  "parameters": { "<name>": {"type": "string"|"number"|"boolean", "required": boolean, "description": string (non-empty)} },
79
- "contextRequirements": {"requiresVisibleContent"?: bool, "requiresAuth"?: bool} (optional),
79
+ "contextRequirements": {"requiresVisibleContent"?: bool, "requiresAuth"?: bool} (optional; set requiresAuth: true for actions that need a signed-in user — see the REQUIRES-AUTH rule below),
80
80
  "examples": string[] (3-4 natural voice phrases, REQUIRED non-empty)
81
81
  }],
82
82
  "policies": {"requireConfirmationForDestructive": true, "allowedWithoutAuth": string[], "contextRetentionTurns": 5}
@@ -164,6 +164,12 @@ ACTIONS: things a user could trigger by voice. ${actionHintLine} CRITICAL RULES:
164
164
  - Destructive/sensitive operations (delete data, logout, place/cancel orders, spend money) need requireConfirmation + confirmationMessageTemplate.
165
165
  - "parameters" only when the user must supply a value by voice.
166
166
 
167
+ REQUIRES-AUTH — mark which actions need a signed-in user (set contextRequirements.requiresAuth):
168
+ This drives a real safety gate: an action marked requiresAuth: true is NOT executed unless the host app reports the user is signed in, so the agent never attempts inside-the-app operations from a logged-out state (e.g. the user is on the login screen). Decide per action by reading the code:
169
+ - Set requiresAuth: true for actions that only make sense for a signed-in user — i.e. anything that reads or changes data tied to a personal session/account: viewing or editing the user's own data, profile/settings, orders/cart history, balances, posting/saving content, in-app operations, or navigating-by-doing into private/authenticated areas. A strong signal: the handler reads a session/token/user id, calls an authenticated API, or lives behind an auth guard/middleware/redirect-to-login. If the app has a global auth gate (a root middleware/layout that redirects unauthenticated users), treat its inside-app actions as requiresAuth: true by default.
170
+ - Leave requiresAuth false/absent for genuinely public actions that work with no session: public navigation, marketing/landing LINK actions (download/get-the-app, social links, contact/email/phone, pricing, legal, in-page anchors), changing language/theme, viewing openly public information, and the sign-in/sign-up flow itself.
171
+ - Be conservative AND fail-safe: do not over-mark clearly public actions (that would needlessly block them when logged out), but when you genuinely cannot tell whether an action is public or requires a session, prefer requiresAuth: true — it is safer to ask the user to sign in than to attempt a private action without a session. requiresAuth never replaces requireConfirmation; an action can need both.
172
+
167
173
  THE MANUAL (description/preconditions/effect/relatedIntents per action; description per screen; appType/appFlow):
168
174
  This is the context the agent reads to understand the app, so it can reason instead of guessing. Derive every field from the REAL code:
169
175
  - description: what the action actually does (read the handler's body).
@@ -511,4 +517,14 @@ async function correctActionWiring({
511
517
  return { wiring, usage, costUSD };
512
518
  }
513
519
 
514
- module.exports = { analyzeWithAI, correctActionWiring, DEFAULT_MODEL, PRICES, resolveModel, isAnthropicModel };
520
+ module.exports = {
521
+ analyzeWithAI,
522
+ correctActionWiring,
523
+ DEFAULT_MODEL,
524
+ PRICES,
525
+ resolveModel,
526
+ isAnthropicModel,
527
+ // Exported for offline tests of the analysis guidance (no API key needed).
528
+ buildSystemPrompt,
529
+ manifestSchemaDoc,
530
+ };
package/src/index.js CHANGED
@@ -93,7 +93,7 @@ const { collectFiles } = require('./collect');
93
93
  const { resolveAnalysisRoot } = require('./resolveAnalysisRoot');
94
94
  const { analyzeWithAI, correctActionWiring, DEFAULT_MODEL, resolveModel } = require('./aiAnalyze');
95
95
  const { verifyManifest } = require('./verify');
96
- const { wireWebOrb, reportWireResult } = require('./wireWeb');
96
+ const { wireWebOrb, reportWireResult, connectOrbRegistries, reportRegistriesResult } = require('./wireWeb');
97
97
  const { wireHandlers, reportHandlerResult } = require('./wireHandlers');
98
98
  const { offerWriteEnv, printEnvManualReminder } = require('./writeEnv');
99
99
  const { scoreSurface, computeSurface } = require('./scoreSurface');
@@ -526,6 +526,9 @@ async function main() {
526
526
 
527
527
  // --no-orb-wire skips ONLY the orb mount (useful to test handler wiring in
528
528
  // isolation without modifying the app's entrypoint / package.json).
529
+ let orbProofPosted = false;
530
+ let orbMounted = false;
531
+ let orbMountFile = '';
529
532
  if (!process.argv.includes('--no-orb-wire')) {
530
533
  setLabel(orbMountSpinnerLabel(platform));
531
534
  if (!assumeYes) pause();
@@ -538,16 +541,22 @@ async function main() {
538
541
  });
539
542
  if (!assumeYes) resume();
540
543
  report(() => reportWireResult(result, fyodosTermColors(), { quiet: !isVerbose() }));
544
+ orbMounted = result.status === 'added' || result.status === 'already';
541
545
  if (
542
546
  publishing &&
543
547
  apiKey &&
544
548
  (result.status === 'added' || result.status === 'already')
545
549
  ) {
546
550
  // Records the install proof so the dashboard onboarding orb check can
547
- // complete without booting the app. If the backend can't record it
548
- // (e.g. an outdated deployment without the endpoint), keep it as a
549
- // dev-only trace it is not actionable noise for the end developer.
550
- const proof = await postOrbWired({ apiKey, apiUrl, file: result.file || '' });
551
+ // complete without booting the app. We post 'pending' here: the orb is
552
+ // mounted, but action wiring (below) has NOT happened yet so the
553
+ // dashboard must keep showing "installing" until we post the terminal
554
+ // status after wireHandlers. If the backend can't record it (e.g. an
555
+ // outdated deployment without the endpoint), keep it as a dev-only
556
+ // trace — it is not actionable noise for the end developer.
557
+ orbMountFile = result.file || '';
558
+ const proof = await postOrbWired({ apiKey, apiUrl, file: orbMountFile, actionsStatus: 'pending' });
559
+ orbProofPosted = true;
551
560
  if (proof && proof.ok === false && proof.reason !== 'no-api-key') {
552
561
  logDev(`[orb-wired] install proof not recorded (status=${proof.status || proof.reason}); dashboard check will rely on a runtime ping`);
553
562
  }
@@ -559,6 +568,40 @@ async function main() {
559
568
  const handlerResult = await wireHandlers(analysisRoot, handlerOpts);
560
569
  if (!assumeYes) resume();
561
570
  report(() => reportHandlerResult(handlerResult, fyodosTermColors(), { quiet: !isVerbose() }));
571
+
572
+ // Second pass — connect the generated registry to the mounted orb so it
573
+ // EXECUTES actions, not just navigates. Removes the manual "paste
574
+ // registries={…}" step. Only when the orb is mounted AND there are
575
+ // auto-wired handlers (autoCount > 0): nav-only / all-manual apps don't
576
+ // need a registry and are left untouched. Idempotent + reversible.
577
+ const handlerApplied =
578
+ handlerResult &&
579
+ (handlerResult.status === 'applied' || handlerResult.status === 'applied-untested') &&
580
+ (handlerResult.autoCount || 0) > 0 &&
581
+ handlerResult.registryFile;
582
+ if (orbMounted && handlerApplied && !process.argv.includes('--no-orb-wire')) {
583
+ const connected = connectOrbRegistries(analysisRoot, {
584
+ registryFileAbs: handlerResult.registryFile,
585
+ });
586
+ report(() => reportRegistriesResult(connected, fyodosTermColors(), { quiet: !isVerbose() }));
587
+ }
588
+
589
+ // Finalize the install proof: now that the (slow) action wiring is done,
590
+ // tell the dashboard the terminal status so its "actions connected" tick
591
+ // turns green for real — never before wiring actually finished. Only do
592
+ // this when we already posted the mount proof, so --no-orb-wire and
593
+ // runtime-only flows don't get a spurious install record.
594
+ if (orbProofPosted) {
595
+ const { status, total, done } = actionsWiredFromHandlerResult(handlerResult);
596
+ await postOrbWired({
597
+ apiKey,
598
+ apiUrl,
599
+ file: orbMountFile,
600
+ actionsStatus: status,
601
+ actionsTotal: total,
602
+ actionsDone: done,
603
+ });
604
+ }
562
605
  };
563
606
 
564
607
  if (publishing) {
@@ -586,11 +629,12 @@ async function main() {
586
629
  log: logUser,
587
630
  logDev,
588
631
  });
589
- if (
590
- envWriteResult &&
591
- (envWriteResult.status === 'declined by user' || envWriteResult.status === 'manual')
592
- ) {
593
- printEnvManualReminder(envWriteResult.writePlan, { logUser, logDev });
632
+ // The reminder decides what to show: the local-env copy-paste block (only
633
+ // when the developer declined the local write) AND/OR the deploy-provider
634
+ // notice for the build-time public key (so the orb is never invisible in
635
+ // production for a missing var). No-ops when there is nothing to show.
636
+ if (envWriteResult) {
637
+ printEnvManualReminder(envWriteResult, { logUser, logDev });
594
638
  }
595
639
  } catch (e) {
596
640
  logDev(`[write-env] skipped: ${e.message}`);
@@ -870,16 +914,29 @@ async function publishManifest(manifest, meta, analysisToken = null, opts = {})
870
914
  * and URL their running app must use. Never hard-fails the publish (the publish
871
915
  * already succeeded); a failed re-fetch is surfaced as an actionable warning.
872
916
  */
873
- async function postOrbWired({ apiKey, apiUrl, file }) {
917
+ async function postOrbWired({
918
+ apiKey,
919
+ apiUrl,
920
+ file,
921
+ actionsStatus = 'pending',
922
+ actionsTotal = 0,
923
+ actionsDone = 0,
924
+ }) {
874
925
  if (!apiKey) return { ok: false, reason: 'no-api-key' };
875
926
  try {
876
927
  const res = await fetch(`${apiUrl}/v1/developer/orb-wired`, {
877
928
  method: 'POST',
878
929
  headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
879
- body: JSON.stringify({ file: file || '', platform: 'web' }),
930
+ body: JSON.stringify({
931
+ file: file || '',
932
+ platform: 'web',
933
+ actionsStatus,
934
+ actionsTotal,
935
+ actionsDone,
936
+ }),
880
937
  });
881
938
  if (res.ok) {
882
- logDev(`[orb-wired] install proof recorded (${file || 'web'})`);
939
+ logDev(`[orb-wired] install proof recorded (${file || 'web'}, actions=${actionsStatus} ${actionsDone}/${actionsTotal})`);
883
940
  return { ok: true };
884
941
  }
885
942
  logDev(`[orb-wired] could not record install proof: HTTP ${res.status}`);
@@ -889,6 +946,35 @@ async function postOrbWired({ apiKey, apiUrl, file }) {
889
946
  return { ok: false, reason: e.message || String(e) };
890
947
  }
891
948
  }
949
+
950
+ // Map a wireHandlers() result to the terminal action-wiring status the backend
951
+ // records, plus how many actions ended up wired. Drives the onboarding's third
952
+ // "actions connected" tick so it only goes green once wiring truly finished.
953
+ function actionsWiredFromHandlerResult(handlerResult) {
954
+ const r = handlerResult || {};
955
+ const total = Number(r.autoCount || 0) + Number(r.manualCount || 0);
956
+ const done = Number(r.autoCount || 0);
957
+ let status;
958
+ switch (r.status) {
959
+ case 'no-actions':
960
+ status = 'none';
961
+ break;
962
+ case 'skipped':
963
+ status = 'skipped';
964
+ break;
965
+ case 'reverted':
966
+ case 'failed':
967
+ status = 'failed';
968
+ break;
969
+ case 'applied':
970
+ case 'applied-untested':
971
+ status = r.partial ? 'partial' : 'complete';
972
+ break;
973
+ default:
974
+ status = 'complete';
975
+ }
976
+ return { status, total, done };
977
+ }
892
978
  async function verifyOrbCanFetch({ apiKey, apiUrl }) {
893
979
  try {
894
980
  const res = await fetch(`${apiUrl}/v1/client/manifest`, {
@@ -736,13 +736,15 @@ function buildRegistryModule(plan, opts = {}) {
736
736
  }
737
737
 
738
738
  // Self-contained types (no @fyodos import) so the file typechecks even before
739
- // the SDK is installed; the shape stays structurally compatible with
740
- // ActionRegistries when passed to <FiodosAgent registries={...}/>.
739
+ // the SDK is installed; the shape MUST stay assignable to @fiodos/core's
740
+ // ActionRegistries so `<FiodosAgent registries={...}/>` type-checks. In
741
+ // particular idempotencyCheckers must match IdempotencyChecker —
742
+ // `(context) => Promise<{ alreadyApplied; message? }>` — not a boolean.
741
743
  const typeDefs = ts
742
744
  ? 'type FiodosActionResult = { success: boolean; data?: Record<string, unknown>; error?: string };\n' +
743
745
  'type FiodosRegistries = {\n' +
744
746
  ' handlers: Record<string, (params: Record<string, any>) => Promise<FiodosActionResult>>;\n' +
745
- ' idempotencyCheckers: Record<string, (params: Record<string, any>) => boolean | Promise<boolean>>;\n' +
747
+ ' idempotencyCheckers: Record<string, (context: Record<string, any>) => Promise<{ alreadyApplied: boolean; message?: string }>>;\n' +
746
748
  '};\n'
747
749
  : '';
748
750
  const resultType = ts ? ': Promise<FiodosActionResult>' : '';
@@ -751,14 +753,11 @@ function buildRegistryModule(plan, opts = {}) {
751
753
  const handlerBlocks = plan.auto.map((e) => {
752
754
  const takesParams = e.usesParams != null ? e.usesParams : /\bparams\b/.test(e.callExpr);
753
755
  const arg = takesParams ? `params${paramsType}` : `_params${paramsType}`;
754
- const confNote = e.requireConfirmation
755
- ? '\n // Confirmation action: the Fiodos engine already asked for confirmation BEFORE calling here.'
756
- : '';
757
756
  // `: unknown` lets the truthiness test below be legal even when the wired
758
757
  // call returns void (e.g. db.delete(...)) — otherwise TS errors on `result &&`.
759
758
  const resultDecl = ts ? 'const result: unknown' : 'const result';
760
759
  return (
761
- ` ${e.handler}: async (${arg})${resultType} => {${confNote}\n` +
760
+ ` ${e.handler}: async (${arg})${resultType} => {\n` +
762
761
  ` try {\n` +
763
762
  ` ${resultDecl} = await Promise.resolve(${e.callExpr});\n` +
764
763
  ` return { success: true, data: (result && typeof result === 'object') ? result as Record<string, unknown> : { result } };\n` +
@@ -772,20 +771,19 @@ function buildRegistryModule(plan, opts = {}) {
772
771
  // Plain-JS variant: drop the `as` cast.
773
772
  const handlerText = (ts ? handlerBlocks.join('\n') : handlerBlocks.join('\n').replace(/ as Record<string, unknown>/g, ''));
774
773
 
775
- const manualNotes = plan.manual.map(
776
- (e) => ` // MANUAL '${e.intent}' (${e.handler}): ${e.reason}`,
774
+ // List each handler that needs manual wiring once. Skip any handler already
775
+ // auto-wired above (the same handler name can back both an auto-wired and a
776
+ // manual intent — listing it as "manual" would contradict the wired entry).
777
+ const autoHandlers = new Set(plan.auto.map((e) => e.handler));
778
+ const manualHandlers = [...new Set(plan.manual.map((e) => e.handler))].filter(
779
+ (h) => !autoHandlers.has(h),
780
+ );
781
+ const manualNotes = manualHandlers.map(
782
+ (h) => ` // ${h}: needs manual wiring — see ${DOC_BASENAME}`,
777
783
  );
778
784
 
779
785
  const header =
780
- `/**\n` +
781
- ` * ${GENERATED_MARKER}.\n` +
782
- ` * Do not edit by hand: re-run the Fiodos installer to regenerate it.\n` +
783
- ` * Read ${DOC_BASENAME} (same folder) to see what it wires and why.\n` +
784
- ` *\n` +
785
- ` * SECURITY: this file only CALLS your functions. It never decides whether a\n` +
786
- ` * sensitive action runs — the Fiodos engine applies the manifest confirmations\n` +
787
- ` * (requireConfirmation / voiceConfirmation) BEFORE invoking any handler here.\n` +
788
- ` */\n`;
786
+ `/** ${GENERATED_MARKER}. Do not edit — re-run the Fiodos installer to regenerate. */\n`;
789
787
 
790
788
  const decl = esm
791
789
  ? `export const ${REGISTRY_EXPORT}${ts ? ': FiodosRegistries' : ''} = {`
@@ -801,8 +799,6 @@ function buildRegistryModule(plan, opts = {}) {
801
799
  `${handlerText}\n` +
802
800
  (manualNotes.length ? `${manualNotes.join('\n')}\n` : '') +
803
801
  ` },\n` +
804
- ` // Idempotency checkers read your app state; implement them by hand if\n` +
805
- ` // an action declares idempotencyCheck (see ${DOC_BASENAME}).\n` +
806
802
  ` idempotencyCheckers: {},\n` +
807
803
  `};\n` +
808
804
  (esm ? '' : `\nmodule.exports = { ${REGISTRY_EXPORT} };\n`);
@@ -821,12 +817,7 @@ function buildBridgeModule(opts = {}) {
821
817
  const esm = opts.esm !== false;
822
818
  const header =
823
819
  `${GENERATED_FILE_DIRECTIVES}` +
824
- `/**\n` +
825
- ` * ${GENERATED_MARKER} (bridge holder).\n` +
826
- ` * Do not edit by hand. Your component registers its real functions here and the\n` +
827
- ` * generated handlers invoke them. This is what connects the agent to the state\n` +
828
- ` * living inside your components, without creating disconnected instances.\n` +
829
- ` */\n`;
820
+ `/** ${GENERATED_MARKER} (bridge holder). Do not edit — re-run the Fiodos installer to regenerate. */\n`;
830
821
  if (ts) {
831
822
  return (
832
823
  `${header}\n` +
@@ -964,134 +955,48 @@ function confLabel(e) {
964
955
  function buildHandlerDoc(plan, ctx = {}) {
965
956
  const {
966
957
  appName = 'your app', framework = 'web', registryRel = '', mountSnippet = '',
967
- bridgeRel = '', edits = [], verification = null,
958
+ bridgeRel = '', edits = [],
968
959
  } = ctx;
969
- const L = [];
970
- L.push('# Fiodos action wiring (handler wiring)');
971
- L.push('');
972
- L.push(
973
- 'This document describes **exactly** what Fiodos will do to connect the ' +
974
- 'actions detected in your app with the real functions that run them. ' +
975
- '**Nothing is applied until you accept in the terminal.**',
976
- );
977
- L.push('');
978
960
  const review = plan.review || plan.manual || [];
979
- const aiCount = plan.auto.filter((e) => e.source === 'ai').length;
980
- L.push(`- App: **${appName}**`);
981
- L.push(`- Detected framework: **${framework}**`);
982
- L.push(`- Actions that will be wired automatically: **${plan.auto.length}** ` +
983
- `(${aiCount} wired by the AI, ${plan.auto.length - aiCount} by the mechanical fallback detector)`);
984
- L.push(`- Actions that need review: **${review.length}**`);
961
+ const bridgeEntries = plan.auto.filter((e) => e.kind === 'bridge');
962
+ const confirmed = plan.entries.filter((e) => e.requireConfirmation);
963
+ const L = [];
964
+
965
+ L.push('# Fiodos action wiring');
985
966
  L.push('');
986
- L.push('## How is this wiring generated?');
967
+ L.push('Connects each action in your manifest to the real function in your app that runs it.');
987
968
  L.push('');
988
- L.push(
989
- 'The **same AI** that analyzed your code to detect the actions also proposed ' +
990
- 'how to run them: which real function/module to call and how to map the parameters. ' +
991
- 'Then a **mechanical verifier** checks that every referenced symbol ' +
992
- 'really exists in your code before writing anything. If the AI proposes something ' +
993
- 'the verifier cannot find, that action is left **for review**, never wired blindly.',
994
- );
969
+ L.push(`- App: \`${appName}\``);
970
+ L.push(`- Framework: \`${framework}\``);
971
+ L.push(`- Wired automatically: ${plan.auto.length}`);
972
+ L.push(`- Need manual wiring: ${review.length}`);
995
973
  L.push('');
996
- if (Array.isArray(verification) && verification.length) {
997
- L.push('## Automatic verification (real effect + retries)');
998
- L.push('');
999
- L.push(
1000
- 'After your "yes", the install **does not trust** that the wiring compiles: for each ' +
1001
- 'action it runs a **wire → verify → diagnose → correct** loop with no ' +
1002
- 'human intervention. Verification checks (1) that the project **still ' +
1003
- 'builds** with that action wired and, when it is safe to simulate, (2) that ' +
1004
- 'invoking the handler **changes the real state** (the task is added, etc.). If an ' +
1005
- 'action fails, the AI receives the **concrete error** and retries with corrected ' +
1006
- 'wiring (up to several attempts). An action is only marked **ready** when it passes ' +
1007
- 'verification; if it is not achieved after the retries, **only that action is ' +
1008
- 'reverted** and marked below as "could not be wired" — never left faking it.',
1009
- );
1010
- L.push('');
1011
- L.push('| Action (intent) | Result | Attempts | Verification | Detail |');
1012
- L.push('| --- | --- | --- | --- | --- |');
1013
- for (const v of verification) {
1014
- const res = v.status === 'ready' ? '✅ ready' : '❌ could not';
1015
- let lvl = '—';
1016
- if (v.status === 'ready') {
1017
- lvl = v.level === 'effect' ? 'real effect (UI)'
1018
- : v.level === 'unverifiable' ? '⚠️ effect NOT verifiable (review by hand)'
1019
- : 'compilation';
1020
- }
1021
- const detail = String(v.detail || v.error || '').replace(/\|/g, '\\|').slice(0, 160);
1022
- L.push(`| \`${v.intent}\` | ${res} | ${v.attempts} | ${lvl} | ${detail} |`);
1023
- }
1024
- L.push('');
1025
- L.push('_"real effect (UI)" = the real component was mounted in a test DOM (jsdom) with ' +
1026
- 'the framework\'s standard tooling (React: react-dom; Vue: vue + @vue/compiler-sfc), ' +
1027
- 'the wired handler was invoked and the **change in the UI/state was observed** (the task appears, ' +
1028
- 'the item toggles, etc.). For the data layer (Dexie/store) the effect is observed by running ' +
1029
- 'the action against a real instance. "compilation" = it compiles and the real symbol exists and is ' +
1030
- 'invoked, but the effect could not be safely simulated here. "⚠️ effect NOT verifiable" = ' +
1031
- 'the wiring was applied but the real effect could not be confirmed automatically (component not ' +
1032
- 'mountable in isolation, or a sensitive action that is not fired) — **review/test by hand**. ' +
1033
- 'Sensitive actions (with confirmation) are **never** fired in the test._');
1034
- L.push('');
1035
- }
1036
974
 
1037
- L.push('## Why is it needed?');
1038
- L.push('');
1039
- L.push(
1040
- 'The orb is already mounted, but the agent cannot **run** actions until ' +
1041
- 'each manifest action is connected to your app\'s real function. ' +
1042
- 'This wiring is that bridge: it translates each action (e.g. `addToCart`) into a ' +
1043
- 'call to your function (e.g. `useShop.getState().addToCart(...)`).',
1044
- );
1045
- L.push('');
1046
- const bridgeEntries = plan.auto.filter((e) => e.kind === 'bridge');
1047
- L.push('## Which files are created / edited');
975
+ L.push('## Files');
1048
976
  L.push('');
1049
- L.push(`- **CREATES** \`${registryRel}\` — the generated handler registry.`);
977
+ L.push(`- Creates \`${registryRel}\` — the handler registry.`);
1050
978
  if (bridgeEntries.length && bridgeRel) {
1051
- L.push(`- **CREATES** \`${bridgeRel}\` — the "bridge" where your component registers its real functions.`);
979
+ L.push(`- Creates \`${bridgeRel}\` — bridge where your components register live functions.`);
1052
980
  }
1053
981
  if (edits.length) {
1054
- L.push('- **EDITS the following files OF YOURS** (only adds lines marked with ' +
1055
- '`FYODOS:BRIDGE`, reversible):');
1056
- for (const ed of edits) L.push(` - \`${ed.file}\` — registers: ${ed.intents.map((i) => `\`${i}\``).join(', ')}`);
1057
- } else {
1058
- L.push('- **No existing file of yours is edited** (all actions were ' +
1059
- 'reachable from a module).');
982
+ L.push('- Edits (only reversible `FYODOS:BRIDGE` blocks):');
983
+ for (const ed of edits) L.push(` - \`${ed.file}\` — ${ed.intents.map((i) => `\`${i}\``).join(', ')}`);
1060
984
  }
1061
- L.push('- To enable it, pass the generated registry to `<FiodosAgent/>` (see "How to enable it").');
1062
985
  L.push('');
1063
986
 
1064
- if (edits.length) {
1065
- L.push('## EXACT changes inside your components');
1066
- L.push('');
1067
- L.push('These are the lines Fiodos will add to your files (each block sits between ' +
1068
- '`FYODOS:BRIDGE:START` / `END` markers, so you can remove or revert them without touching ' +
1069
- 'your own code):');
1070
- L.push('');
1071
- for (const ed of edits) {
1072
- L.push(`### \`${ed.file}\``);
1073
- L.push('');
1074
- L.push('After the last `import` line, this is added:');
1075
- L.push('');
1076
- L.push('```ts');
1077
- L.push(ed.importBlock);
1078
- L.push('```');
1079
- L.push('');
1080
- L.push(ed.file.endsWith('.vue')
1081
- ? 'Right before `</script>`, this is added:'
1082
- : `Next to the anchor \`${(ed.anchor || '').trim().slice(0, 80)}\`, this is added:`);
1083
- L.push('');
1084
- L.push('```ts');
1085
- L.push(ed.registerBlock.replace(/^\s+/gm, (s) => s)); // keep as-is
1086
- L.push('```');
1087
- L.push('');
1088
- }
1089
- }
987
+ L.push('## Enable it');
988
+ L.push('');
989
+ L.push('Pass the generated registry to your orb:');
990
+ L.push('');
991
+ L.push('```tsx');
992
+ L.push(mountSnippet || `import { ${REGISTRY_EXPORT} } from './${registryRel.split('/').pop()}';\n\n<FiodosAgent registries={${REGISTRY_EXPORT}} />`);
993
+ L.push('```');
994
+ L.push('');
1090
995
 
1091
996
  if (plan.auto.length) {
1092
- L.push(`## Actions that will be wired automatically (${plan.auto.length})`);
997
+ L.push(`## Wired automatically (${plan.auto.length})`);
1093
998
  L.push('');
1094
- L.push('| Action (intent) | How it runs | Where | Parameters | Type | Security |');
999
+ L.push('| Action | Runs | Where | Params | Type | Confirmation |');
1095
1000
  L.push('| --- | --- | --- | --- | --- | --- |');
1096
1001
  for (const e of plan.auto) {
1097
1002
  const expr = e.kind === 'bridge' ? e.bridge.invoke : e.callExpr;
@@ -1100,84 +1005,43 @@ function buildHandlerDoc(plan, ctx = {}) {
1100
1005
  const params = e.manifestParams.length
1101
1006
  ? e.manifestParams.map((p) => `${p.name}${p.required ? '*' : ''}`).join(', ')
1102
1007
  : '—';
1103
- const kind = e.kind === 'bridge'
1104
- ? 'bridge (edits your component)'
1105
- : (e.source === 'ai' ? 'module (AI)' : 'module (mechanical)');
1008
+ const kind = e.kind === 'bridge' ? 'bridge' : 'module';
1106
1009
  L.push(`| \`${e.intent}\` | ${call} | ${where} | ${params} | ${kind} | ${confLabel(e)} |`);
1107
1010
  }
1108
1011
  L.push('');
1109
- L.push('_`*` = required parameter. Each call was proposed by the AI reading your ' +
1110
- 'code and **confirmed by the verifier** (the symbols exist). Parameters ' +
1111
- 'are mapped **by name**, never by position._');
1012
+ L.push('`*` = required parameter. Parameters are mapped by name.');
1112
1013
  L.push('');
1113
1014
  }
1114
1015
 
1115
1016
  if (review.length) {
1116
- L.push(`## Actions that need REVIEW (${review.length})`);
1017
+ L.push(`## Needs manual wiring (${review.length})`);
1117
1018
  L.push('');
1118
- L.push(
1119
- 'For these actions, neither did the AI propose **verifiable** wiring nor did the ' +
1120
- 'mechanical fallback detector find the function safely. Wiring them wrong would be ' +
1121
- 'worse than leaving them unwired, so they are marked for review and we explain why:',
1122
- );
1019
+ L.push(`Add a handler in \`${registryRel}\` that calls your function and returns \`{ success: true }\` (or \`{ success: false, message }\`).`);
1123
1020
  L.push('');
1021
+ L.push('| Action | Handler | Reason |');
1022
+ L.push('| --- | --- | --- |');
1124
1023
  for (const e of review) {
1125
- L.push(`### \`${e.intent}\` ${e.label}`);
1126
- L.push('');
1127
- L.push(`- Expected handler: \`${e.handler}\``);
1128
- if (e.file) L.push(`- Evidence file: \`${e.file}\``);
1129
- L.push(`- Reason: ${e.reason}`);
1130
- L.push(
1131
- `- What to do: add a handler \`${e.handler}\` in \`${registryRel}\` (or in your ` +
1132
- 'own registry) that calls the real function and returns ' +
1133
- '`{ success: true }` (or `{ success: false, message }`).',
1134
- );
1135
- L.push('');
1024
+ // Keep the reason to a clean one-liner: collapse whitespace, drop the
1025
+ // npm/compiler dump after the first `|`, and cap length (no leaked paths).
1026
+ const reason = String(e.reason || '')
1027
+ .split('|')[0]
1028
+ .replace(/\s+/g, ' ')
1029
+ .trim()
1030
+ .slice(0, 160);
1031
+ L.push(`| \`${e.intent}\` | \`${e.handler}\` | ${reason} |`);
1136
1032
  }
1033
+ L.push('');
1137
1034
  }
1138
1035
 
1139
- const confirmed = plan.entries.filter((e) => e.requireConfirmation);
1140
1036
  L.push('## Security');
1141
1037
  L.push('');
1142
- L.push(
1143
- '- The generated wiring **only calls** your functions. It **never** decides whether a ' +
1144
- 'sensitive action runs: the Fiodos engine applies the manifest confirmations ' +
1145
- '(`requireConfirmation` / `voiceConfirmation`) **before** invoking ' +
1146
- 'the handler. It is impossible for the auto-wiring to skip a confirmation.',
1147
- );
1038
+ L.push('- The wiring only calls your functions; the Fiodos engine enforces manifest confirmations (`requireConfirmation` / `voiceConfirmation`) before any handler runs.');
1039
+ L.push('- No secrets are read or embedded — only function names and the parameters the manifest declares.');
1148
1040
  if (confirmed.length) {
1149
- L.push('- Actions protected by confirmation (the agent will ask to confirm before running them):');
1150
- for (const e of confirmed) {
1151
- L.push(` - \`${e.intent}\` — ${confLabel(e)}.`);
1152
- }
1153
- } else {
1154
- L.push('- None of the detected actions require confirmation in the manifest.');
1041
+ L.push(`- Confirmation-protected actions: ${confirmed.map((e) => `\`${e.intent}\``).join(', ')}.`);
1155
1042
  }
1156
- L.push('- The wiring does not read, embed or expose secrets; it only references function');
1157
- L.push(' names and the parameters the manifest already declares.');
1158
1043
  L.push('');
1159
1044
 
1160
- L.push('## How to enable it');
1161
- L.push('');
1162
- if (mountSnippet) {
1163
- L.push('Pass the generated registry to your `<FiodosAgent/>`:');
1164
- L.push('');
1165
- L.push('```tsx');
1166
- L.push(mountSnippet);
1167
- L.push('```');
1168
- } else {
1169
- L.push('Import `' + REGISTRY_EXPORT + '` from the generated file and pass it as ' +
1170
- '`registries` to your `<FiodosAgent/>`.');
1171
- }
1172
- L.push('');
1173
- L.push('## If you said "no"');
1174
- L.push('');
1175
- L.push(
1176
- 'Your code was not touched. You can implement the wiring yourself following ' +
1177
- 'this document, or re-run the installer to accept the auto-wiring ' +
1178
- '(`--wire-yes` to accept without prompting).',
1179
- );
1180
- L.push('');
1181
1045
  return L.join('\n');
1182
1046
  }
1183
1047