forkit-connect 0.1.20 → 0.1.21

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 (2) hide show
  1. package/dist/cli.js +646 -131
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -87,9 +87,9 @@ const PUBLIC_COMMANDS = [
87
87
  ['scan', 'Discover runtimes, models, and agents on this device'],
88
88
  ['inbox', 'Review the Smart Registration Inbox'],
89
89
  ['sync', 'Flush queued drafts and lifecycle metadata'],
90
- ['workspace', 'List, select, or inspect optional governed workspace/project scope'],
91
- ['runtime', 'Register or review governed runtimes for the current repo/worktree'],
92
- ['register', 'Register ready local models into the current scope'],
90
+ ['workspace', 'Optional governed workspace and project scope'],
91
+ ['runtime', 'Register or review local runtimes for this repo/worktree'],
92
+ ['register', 'Register ready local models'],
93
93
  ['ignore', 'Deny one detected local model on this device'],
94
94
  ['doctor', 'Run local environment diagnostics'],
95
95
  ];
@@ -117,86 +117,42 @@ const ADVANCED_COMMAND_GROUPS = [
117
117
  ['notify', 'Notification preview and delivery controls'],
118
118
  ];
119
119
  function usage() {
120
- console.log('Usage: forkit-connect <init|login|logout|status|changes|start|stop|scan|inbox|sync|workspace|runtime|register|ignore|doctor> [options]');
121
- console.log(' forkit-connect workspace <list|select|create|status> [options]');
122
- console.log(' forkit-connect runtime <register|review|status> [options]');
123
- console.log('Public commands:');
120
+ printCliHeader('CLI', 'Precise local review, runtime detection, and registration handoff.');
121
+ console.log(cliKeyLine('usage', 'forkit-connect <command> [options]'));
122
+ console.log(cliKeyLine('scope', 'forkit-connect workspace <list|select|create|status>'));
123
+ console.log(cliKeyLine('runtime', 'forkit-connect runtime <register|review|status>'));
124
+ printCliSection('Core');
124
125
  for (const [command, description] of PUBLIC_COMMANDS) {
125
- console.log(` ${command.padEnd(10)} ${description}`);
126
- }
127
- console.log('Options:');
128
- console.log(' --description <text> Optional workspace description used by workspace create');
129
- console.log(' --project-name <text> Project name used by workspace create/select');
130
- console.log(' --project-description <text> Optional project description used by workspace create/select');
131
- console.log(' --json Print machine-readable output when supported');
132
- console.log(' --name <value> Runtime or metric name depending on command');
133
- console.log(' --model <value> Model name used by register');
134
- console.log(' --entrypoint <path> Relative project path used by runtime register');
135
- console.log(' --source-url <url> Source URL override used by runtime register');
136
- console.log(' --subject-type <type> Runtime subject type used by runtime register');
137
- console.log(' --deployment-environment <value> Runtime environment used by runtime register');
138
- console.log(' --show-key Print the raw runtime API key when one is created');
139
- console.log(' --no-api-key Skip automatic runtime API key creation');
140
- console.log(' --dry-run Show the inferred runtime payload without creating it');
141
- console.log(' --all-ready Register every ready local model in the current scope');
142
- console.log(' --ignore-model <name> Deny a detected model on this device with --ignore-digest');
143
- console.log(' --ignore-digest <sha> Digest used with --ignore-model');
144
- console.log(' --digest <sha> Digest used by ignore when the model name is ambiguous');
145
- console.log(' --interval-seconds <n> Override daemon scan interval');
146
- console.log(' --no-browser Do not auto-open verification URL during login');
147
- console.log(' --advanced-help Show internal/engineering commands');
126
+ console.log(cliKeyLine(command, description));
127
+ }
128
+ printCliSection('Flags');
129
+ console.log(cliKeyLine('--json', 'Machine-readable output when supported'));
130
+ console.log(cliKeyLine('--all-ready', 'Register every ready local model in the current scope'));
131
+ console.log(cliKeyLine('--model', 'Model name used by register'));
132
+ console.log(cliKeyLine('--entrypoint', 'Relative project path used by runtime register'));
133
+ console.log(cliKeyLine('--dry-run', 'Show inferred runtime payload without creating it'));
134
+ console.log(cliKeyLine('--no-browser', 'Do not auto-open browser verification during login'));
135
+ console.log(cliKeyLine('--plain', 'Disable the interactive terminal table surface'));
136
+ console.log(cliKeyLine('--advanced-help', 'Show internal and engineering commands'));
148
137
  }
149
138
  function advancedUsage() {
150
139
  usage();
151
- console.log('');
152
- console.log('Advanced commands:');
153
- console.log(' forkit-connect connect <modelNameOrDiscoveryHash>');
154
- console.log(' forkit-connect connect <start|init|status|inbox|services|permissions|handoff|evolution review|runtime review|runtime status>');
155
- console.log(' forkit-connect runtime target <add|list|remove|status> [options]');
156
- console.log(' forkit-connect runtime observe --gaid <gaid> [options]');
157
- console.log(' forkit-connect <review|workspaces|projects|bind|drafts|publish|bound|heartbeat|update-check|config|daemon|pulse|c2|train|agent|tray|notify> [options]');
140
+ printCliSection('Advanced');
141
+ console.log(cliKeyLine('connect', 'forkit-connect connect <modelNameOrDiscoveryHash>'));
142
+ console.log(cliKeyLine('connect', 'forkit-connect connect <start|init|status|inbox|services|permissions|handoff|evolution review|runtime review|runtime status>'));
143
+ console.log(cliKeyLine('targets', 'forkit-connect runtime target <add|list|remove|status>'));
144
+ console.log(cliKeyLine('observe', 'forkit-connect runtime observe --gaid <gaid>'));
145
+ console.log(cliKeyLine('more', 'forkit-connect <review|workspaces|projects|bind|drafts|publish|bound|heartbeat|update-check|config|daemon|pulse|c2|train|agent|tray|notify>'));
158
146
  for (const [command, description] of ADVANCED_COMMAND_GROUPS) {
159
- console.log(` ${command.padEnd(12)} ${description}`);
160
- }
161
- console.log('Advanced options:');
162
- console.log(' --session-ref <value> Store/update backend session reference');
163
- console.log(' --workspace <id> Store workspace binding');
164
- console.log(' --project <id> Store project binding');
165
- console.log(' --draft <id> Draft id used by publish');
166
- console.log(' --name <value> Model or metric name depending on command');
167
- console.log(' --framework <value> Training framework used by train init');
168
- console.log(' --task <value> Training task used by train init');
169
- console.log(' --dataset-ref <value> Safe dataset reference used by train init');
170
- console.log(' --type <value> Training lifecycle event type');
171
- console.log(' --ref <value> Dataset reference used by train dataset');
172
- console.log(' --change <value> Dataset change type used by train dataset');
173
- console.log(' --value <number> Metric value used by train metric');
174
- console.log(' --version <value> Version name used by train version');
175
- console.log(' --reason <value> Version or lifecycle reason');
176
- console.log(' --path <value> Artifact path used by train artifact');
177
- console.log(' --hash-artifact Hash artifact contents for train artifact');
178
- console.log(' --heartbeat-gaid <gaid> Queue heartbeat runtime signal event for GAID');
179
- console.log(' --heartbeat-key <key> API key used for heartbeat runtime signal event');
180
- console.log(' Also used by: c2 set-key (stores key + backfills events)');
181
- console.log(' --gaid <gaid> Runtime or passport GAID used by runtime observe and c2 run-log emit');
182
- console.log(' --api-key <key> Runtime signal API key used by runtime observe and c2 run-log emit');
183
- console.log(' --repo-root <path> Repo root used by runtime target add (defaults to current repo/worktree)');
184
- console.log(' --provider <name> Provider label used by c2 run-log emit');
185
- console.log(' --service-name <name> Service/agent/workflow label used by c2 run-log emit');
186
- console.log(' --prompt-tokens <n> Prompt tokens used by c2 run-log emit');
187
- console.log(' --completion-tokens <n> Completion tokens used by c2 run-log emit');
188
- console.log(' --client-name <name> Optional repo/runtime client label used by c2 run-log emit');
189
- console.log(' --command-label <name> Operator label used by runtime observe and c2 run-log emit');
190
- console.log(' --actor-labels <csv> Actor labels used by c2 run-log emit (for example: Codex,Claude)');
191
- console.log(' --task-labels <csv> Task or chat labels used by c2 run-log emit');
192
- console.log(' --folder-labels <csv> Relative folder labels used by c2 run-log emit');
193
- console.log(' --file-labels <csv> Relative file labels used by c2 run-log emit');
194
- console.log(' --model-labels <csv> Optional model labels used by c2 run-log emit');
195
- console.log(' --cpu-percent <n> Scoped CPU percentage used by c2 run-log emit');
196
- console.log(' --memory-mb <n> Scoped memory usage used by c2 run-log emit');
197
- console.log(' --vram-mb <n> Device VRAM usage used by c2 run-log emit');
198
- console.log(' --emit-ambient Include ambient tool detections in runtime observe JSON output');
199
- console.log(' --draft-only Allow draft creation even when governed publish capacity is full');
147
+ console.log(cliKeyLine(command, description));
148
+ }
149
+ printCliSection('Advanced Flags');
150
+ console.log(cliKeyLine('--session-ref', 'Store or update backend session reference'));
151
+ console.log(cliKeyLine('--workspace', 'Store workspace binding'));
152
+ console.log(cliKeyLine('--project', 'Store project binding'));
153
+ console.log(cliKeyLine('--gaid', 'Runtime or passport GAID used by runtime observe'));
154
+ console.log(cliKeyLine('--api-key', 'Runtime signal API key used by runtime observe'));
155
+ console.log(cliKeyLine('--draft-only', 'Allow private continuation when governed publish is blocked'));
200
156
  }
201
157
  function showUsage() {
202
158
  if (hasFlag('--advanced-help')) {
@@ -760,6 +716,13 @@ function formatScopeReferenceLabel(value, kind) {
760
716
  }
761
717
  return `selected on this device (${shortId(normalized)})`;
762
718
  }
719
+ function formatScopeDetailValue(value, kind) {
720
+ const normalized = String(value || '').trim();
721
+ if (!normalized) {
722
+ return kind === 'workspace' ? 'not selected' : 'not selected';
723
+ }
724
+ return `${kind} ${shortId(normalized)}`;
725
+ }
763
726
  function printUpdateCheckLines(lines, useErrorStream = false) {
764
727
  for (const line of lines) {
765
728
  if (useErrorStream) {
@@ -846,6 +809,207 @@ function printConnectInit(result) {
846
809
  function printJson(value) {
847
810
  console.log(JSON.stringify(value, null, 2));
848
811
  }
812
+ const CLI_LABEL_WIDTH = 16;
813
+ const CLI_WIDTH_FALLBACK = 88;
814
+ const ANSI_ESCAPE_PATTERN = /\u001B\[[0-9;]*m/g;
815
+ function canRenderCliColor() {
816
+ return Boolean(node_process_1.stdout.isTTY && process.env.NO_COLOR !== '1' && process.env.TERM !== 'dumb');
817
+ }
818
+ function paintCli(text, code) {
819
+ if (!canRenderCliColor()) {
820
+ return text;
821
+ }
822
+ return `\u001B[${code}m${text}\u001B[0m`;
823
+ }
824
+ function cliBold(text) {
825
+ return paintCli(text, '1');
826
+ }
827
+ function cliDim(text) {
828
+ return paintCli(text, '2');
829
+ }
830
+ function cliAccent(text) {
831
+ return paintCli(text, '1;36');
832
+ }
833
+ function cliGood(text) {
834
+ return paintCli(text, '1;32');
835
+ }
836
+ function cliWarm(text) {
837
+ return paintCli(text, '1;33');
838
+ }
839
+ function cliDanger(text) {
840
+ return paintCli(text, '1;31');
841
+ }
842
+ function cliPlainWidth(text) {
843
+ return text.replace(ANSI_ESCAPE_PATTERN, '').length;
844
+ }
845
+ function truncateCliText(value, maxWidth) {
846
+ const normalized = String(value ?? '').trim();
847
+ if (normalized.length <= maxWidth) {
848
+ return normalized;
849
+ }
850
+ return `${normalized.slice(0, Math.max(0, maxWidth - 3)).trimEnd()}...`;
851
+ }
852
+ function getCliRenderWidth() {
853
+ const columns = node_process_1.stdout.columns;
854
+ if (Number.isFinite(columns) && Number(columns) > 0) {
855
+ return Math.max(72, Number(columns));
856
+ }
857
+ return CLI_WIDTH_FALLBACK;
858
+ }
859
+ function cliRule(char = '-') {
860
+ return cliDim(char.repeat(Math.max(24, Math.min(getCliRenderWidth(), CLI_WIDTH_FALLBACK))));
861
+ }
862
+ function cliTag(label, tone = 'muted') {
863
+ const rendered = `[${label}]`;
864
+ switch (tone) {
865
+ case 'accent':
866
+ return cliAccent(rendered);
867
+ case 'good':
868
+ return cliGood(rendered);
869
+ case 'warm':
870
+ return cliWarm(rendered);
871
+ case 'danger':
872
+ return cliDanger(rendered);
873
+ default:
874
+ return cliDim(rendered);
875
+ }
876
+ }
877
+ function cliKeyLine(label, value) {
878
+ return ` ${cliDim(`${label}`.padEnd(CLI_LABEL_WIDTH, ' '))} ${value}`;
879
+ }
880
+ function printCliHeader(title, subtitle) {
881
+ console.log(`[forkit-connect] ${cliBold(title)}`);
882
+ if (subtitle) {
883
+ console.log(cliDim(subtitle));
884
+ }
885
+ console.log(cliRule());
886
+ }
887
+ function printCliSection(title) {
888
+ console.log('');
889
+ console.log(cliBold(title.toUpperCase()));
890
+ }
891
+ function printCliEntry(title, options) {
892
+ const trimmedTitle = truncateCliText(title, 36);
893
+ const summaryWidth = Math.max(24, Math.min(48, getCliRenderWidth() - cliPlainWidth(trimmedTitle) - 22));
894
+ const pieces = [trimmedTitle];
895
+ if (options?.tag) {
896
+ pieces.push(cliTag(options.tag, options.tagTone ?? 'muted'));
897
+ }
898
+ if (options?.summary) {
899
+ pieces.push(cliDim('->'));
900
+ pieces.push(truncateCliText(options.summary, summaryWidth));
901
+ }
902
+ console.log(` > ${pieces.join(' ')}`);
903
+ if (options?.meta) {
904
+ console.log(` ${cliDim(options.meta)}`);
905
+ }
906
+ }
907
+ function joinCliSummary(parts) {
908
+ return parts.filter((value) => Boolean(value && String(value).trim())).join(cliDim(' | '));
909
+ }
910
+ function formatCliCompactCount(count, singular, plural) {
911
+ const noun = count === 1 ? singular : (plural || `${singular}s`);
912
+ return `${count} ${noun}`;
913
+ }
914
+ function formatCliAge(seconds) {
915
+ if (!Number.isFinite(seconds) || seconds <= 0) {
916
+ return 'fresh';
917
+ }
918
+ if (seconds >= 86400) {
919
+ return `${Math.round(seconds / 86400)}d old`;
920
+ }
921
+ if (seconds >= 3600) {
922
+ return `${Math.round(seconds / 3600)}h old`;
923
+ }
924
+ if (seconds >= 60) {
925
+ return `${Math.round(seconds / 60)}m old`;
926
+ }
927
+ return `${Math.round(seconds)}s old`;
928
+ }
929
+ function formatCliScopeLabel(workspaceId, projectId) {
930
+ const workspace = String(workspaceId || '').trim();
931
+ const project = String(projectId || '').trim();
932
+ if (workspace && project) {
933
+ return `${shortId(workspace)} / ${shortId(project)}`;
934
+ }
935
+ if (workspace) {
936
+ return `${shortId(workspace)} / project needed`;
937
+ }
938
+ return 'solo / no workspace';
939
+ }
940
+ function summarizeReviewQueueCounts(values) {
941
+ const parts = [
942
+ values.draftFirstCount ? formatCliCompactCount(values.draftFirstCount, 'private review') : null,
943
+ values.readyCount ? formatCliCompactCount(values.readyCount, 'ready item') : null,
944
+ values.needsReviewCount ? formatCliCompactCount(values.needsReviewCount, 'review item') : null,
945
+ values.connectedCount ? formatCliCompactCount(values.connectedCount, 'connected item') : null,
946
+ ];
947
+ const joined = joinCliSummary(parts);
948
+ return joined || 'clear';
949
+ }
950
+ function formatCliEvidenceGlyph(confidence) {
951
+ switch (String(confidence || '').trim().toLowerCase()) {
952
+ case 'high':
953
+ return '●●●';
954
+ case 'medium':
955
+ return '●●○';
956
+ case 'low':
957
+ return '●○○';
958
+ default:
959
+ return '○○○';
960
+ }
961
+ }
962
+ function formatCliEvidenceLabel(confidence) {
963
+ const normalized = String(confidence || '').trim().toLowerCase();
964
+ switch (normalized) {
965
+ case 'high':
966
+ return 'high';
967
+ case 'medium':
968
+ return 'medium';
969
+ case 'low':
970
+ return 'low';
971
+ default:
972
+ return normalized || 'unknown';
973
+ }
974
+ }
975
+ function formatCliEvidenceCell(confidence) {
976
+ return `${formatCliEvidenceGlyph(confidence)} ${formatCliEvidenceLabel(confidence)}`;
977
+ }
978
+ function formatCliDetailLine(label, value, labelWidth = 12) {
979
+ const safeLabel = `${label}:`;
980
+ const paddedLabel = `${safeLabel}${' '.repeat(Math.max(1, labelWidth - safeLabel.length))}`;
981
+ const normalizedValue = value == null
982
+ ? ''
983
+ : typeof value === 'string'
984
+ ? value.trim()
985
+ : String(value);
986
+ return `${paddedLabel}${normalizedValue || normalizedValue === '0' ? normalizedValue : 'n/a'}`;
987
+ }
988
+ function formatRuntimeRecommendedActionLabel(action) {
989
+ const normalized = String(action || '').trim().toLowerCase();
990
+ if (normalized === 'link an agent to a model on this runtime.') {
991
+ return 'Link agent to model';
992
+ }
993
+ if (normalized === 'connect a model served by this runtime.') {
994
+ return 'Register served model';
995
+ }
996
+ if (normalized === 'check whether this runtime should be running locally.') {
997
+ return 'Check local runtime';
998
+ }
999
+ return truncateCliText(action, 36);
1000
+ }
1001
+ function getDraftFirstCountFromInbox(inbox) {
1002
+ return inbox.groups.ready_to_connect.filter((item) => (item.item_type === 'model'
1003
+ && item.recommended_action === 'create_passport_draft'
1004
+ && String(item.details_received_automatically.registration_flow_state || '').trim() === 'private_draft_required')).length;
1005
+ }
1006
+ function splitReadyInboxItems(inbox) {
1007
+ const privateReview = inbox.groups.ready_to_connect.filter((item) => (item.item_type === 'model'
1008
+ && item.recommended_action === 'create_passport_draft'
1009
+ && String(item.details_received_automatically.registration_flow_state || '').trim() === 'private_draft_required'));
1010
+ const ready = inbox.groups.ready_to_connect.filter((item) => !privateReview.includes(item));
1011
+ return { privateReview, ready };
1012
+ }
849
1013
  const INTERACTIVE_LABEL_WIDTH = 24;
850
1014
  const INTERACTIVE_DISCOVERY_TIMEOUT_MS = 800;
851
1015
  const INTERACTIVE_BINDING_TIMEOUT_MS = 800;
@@ -1365,23 +1529,47 @@ function printAgentLedger(summary) {
1365
1529
  console.log(JSON.stringify(summary, null, 2));
1366
1530
  }
1367
1531
  function printRuntimeReview(summary) {
1368
- console.log('[forkit-connect] Runtime review');
1369
- console.log(`- runtimes=${summary.total_runtimes}`);
1370
- for (const runtime of summary.runtimes) {
1371
- console.log(`- ${runtime.runtime_name} | ${runtime.status} | linked models=${runtime.linked_models_count} | linked agents=${runtime.linked_agents_count} | health=${runtime.health_status} | next=${runtime.recommended_action}`);
1532
+ const ready = summary.runtimes.filter((runtime) => runtime.status !== 'unavailable' && runtime.health_status === 'detected');
1533
+ const attention = summary.runtimes.filter((runtime) => !ready.includes(runtime));
1534
+ printCliHeader('Runtime Review', `${formatCliCompactCount(summary.total_runtimes, 'runtime')} on this device`);
1535
+ console.log(cliKeyLine('summary', joinCliSummary([
1536
+ formatCliCompactCount(ready.length, 'healthy runtime'),
1537
+ formatCliCompactCount(attention.length, 'runtime needs review', 'runtimes need review'),
1538
+ ])));
1539
+ for (const [label, items] of [['Healthy', ready], ['Needs review', attention]]) {
1540
+ if (items.length === 0) {
1541
+ continue;
1542
+ }
1543
+ printCliSection(`${label} ${cliTag(String(items.length), label === 'Healthy' ? 'good' : 'warm')}`);
1544
+ for (const runtime of items) {
1545
+ printCliEntry(runtime.runtime_name, {
1546
+ tag: runtime.status.replaceAll('_', ' '),
1547
+ tagTone: runtime.status === 'unavailable' ? 'warm' : 'good',
1548
+ meta: joinCliSummary([
1549
+ `health ${runtime.health_status}`,
1550
+ formatCliCompactCount(runtime.linked_models_count, 'model'),
1551
+ formatCliCompactCount(runtime.linked_agents_count, 'agent'),
1552
+ `next ${formatRuntimeRecommendedActionLabel(runtime.recommended_action)}`,
1553
+ ]),
1554
+ });
1555
+ }
1372
1556
  }
1373
1557
  }
1374
1558
  function printRuntimeStatus(status) {
1375
- console.log('[forkit-connect] Runtime status');
1376
- console.log(`- total=${status.total_runtimes}`);
1377
- console.log(`- online=${status.online_runtimes}`);
1378
- console.log(`- offline=${status.offline_runtimes}`);
1379
- console.log(`- linked=${status.linked_runtimes}`);
1380
- console.log(`- unlinked=${status.unlinked_runtimes}`);
1381
- console.log(`- needs attention=${status.unhealthy_runtimes}`);
1382
- console.log(`- pending runtime sync=${status.c2_pending_count}`);
1559
+ printCliHeader('Runtime Status');
1560
+ console.log(cliKeyLine('runtimes', joinCliSummary([
1561
+ formatCliCompactCount(status.total_runtimes, 'total'),
1562
+ formatCliCompactCount(status.online_runtimes, 'online runtime'),
1563
+ formatCliCompactCount(status.offline_runtimes, 'offline runtime'),
1564
+ ])));
1565
+ console.log(cliKeyLine('linking', joinCliSummary([
1566
+ formatCliCompactCount(status.linked_runtimes, 'linked runtime'),
1567
+ formatCliCompactCount(status.unlinked_runtimes, 'unlinked runtime'),
1568
+ formatCliCompactCount(status.unhealthy_runtimes, 'runtime needs review', 'runtimes need review'),
1569
+ ])));
1570
+ console.log(cliKeyLine('sync', `${status.c2_pending_count} pending runtime events`));
1383
1571
  if (status.latest_runtime_lifecycle_event) {
1384
- console.log(`- latest event=${status.latest_runtime_lifecycle_event}`);
1572
+ console.log(cliKeyLine('latest', truncateCliText(status.latest_runtime_lifecycle_event, 60)));
1385
1573
  }
1386
1574
  }
1387
1575
  function printRuntimeTargetList(service, options) {
@@ -1437,24 +1625,133 @@ function formatSmartInboxActionLabel(item) {
1437
1625
  }
1438
1626
  return formatSmartInboxActionValue(item.recommended_action, item.item_type, String(item.details_received_automatically.connectable_model_name || '').trim());
1439
1627
  }
1628
+ function getInboxGroupTone(label) {
1629
+ switch (label) {
1630
+ case 'Private review':
1631
+ return 'accent';
1632
+ case 'Ready to connect':
1633
+ return 'good';
1634
+ case 'Needs review':
1635
+ return 'warm';
1636
+ case 'Denied on this device':
1637
+ return 'danger';
1638
+ default:
1639
+ return 'muted';
1640
+ }
1641
+ }
1642
+ function formatInboxItemMeta(item) {
1643
+ const match = item.matched_passport_gaid ? `match ${shortId(item.matched_passport_gaid)}` : null;
1644
+ const runtime = String(item.details_received_automatically.runtime_name || '').trim();
1645
+ const displayName = String(item.display_name || '').trim().toLowerCase();
1646
+ const sourceCandidate = item.item_type === 'runtime'
1647
+ ? String(item.details_received_automatically.source_endpoint_label || runtime || '').trim()
1648
+ : '';
1649
+ const sourceNormalized = sourceCandidate.toLowerCase();
1650
+ const runtimeNormalized = runtime.toLowerCase();
1651
+ const detectedSource = String(item.detected_source || '').trim().toLowerCase();
1652
+ const source = sourceCandidate && sourceNormalized !== displayName && sourceNormalized !== runtimeNormalized && sourceNormalized !== detectedSource
1653
+ ? sourceCandidate
1654
+ : null;
1655
+ const confidence = item.confidence !== 'high' ? `${item.confidence} confidence` : null;
1656
+ const parts = [source || null, confidence, match];
1657
+ const joined = joinCliSummary(parts);
1658
+ return joined || null;
1659
+ }
1440
1660
  function printInboxGroup(label, items) {
1441
1661
  if (items.length === 0)
1442
1662
  return;
1443
- console.log(`${label}: ${items.length}`);
1663
+ printCliSection(`${label} ${cliTag(String(items.length), getInboxGroupTone(label))}`);
1444
1664
  for (const item of items) {
1445
- console.log(`- ${item.display_name} | type=${item.item_type} | confidence=${item.confidence} | next=${formatSmartInboxActionLabel(item)}${item.matched_passport_gaid ? ` | match=${shortId(item.matched_passport_gaid)}` : ''}`);
1665
+ printCliEntry(item.display_name, {
1666
+ tag: item.item_type.replaceAll('_', ' '),
1667
+ tagTone: item.item_type === 'runtime' ? 'accent' : item.item_type === 'agent' ? 'warm' : 'muted',
1668
+ summary: formatSmartInboxActionLabel(item),
1669
+ meta: formatInboxItemMeta(item),
1670
+ });
1446
1671
  }
1447
1672
  }
1448
1673
  function printSmartInbox(inbox) {
1449
- console.log(`[forkit-connect] Smart Registration Inbox generated_at=${inbox.summary.generated_at}`);
1450
- console.log(`Governance authority: website | user_api_key_required=${String(inbox.user_api_key_required)}`);
1451
- printInboxGroup('Ready to Connect', inbox.groups.ready_to_connect);
1452
- printInboxGroup('Needs Confirmation', inbox.groups.needs_confirmation);
1453
- printInboxGroup('Connected', inbox.groups.connected);
1454
- printInboxGroup('Denied on This Device', inbox.groups.ignored);
1674
+ const draftFirstCount = getDraftFirstCountFromInbox(inbox);
1675
+ const { privateReview, ready } = splitReadyInboxItems(inbox);
1676
+ const freshness = inbox.summary.freshness_state ?? 'fresh';
1677
+ const ageSeconds = Number.isFinite(inbox.summary.snapshot_age_seconds)
1678
+ ? Number(inbox.summary.snapshot_age_seconds)
1679
+ : 0;
1680
+ printCliHeader('Inbox', `${formatTimestamp(inbox.summary.generated_at)} snapshot`);
1681
+ console.log(cliKeyLine('freshness', joinCliSummary([
1682
+ freshness,
1683
+ formatCliAge(ageSeconds),
1684
+ ])));
1685
+ console.log(cliKeyLine('queue', summarizeReviewQueueCounts({
1686
+ draftFirstCount,
1687
+ readyCount: ready.length,
1688
+ needsReviewCount: inbox.groups.needs_confirmation.length,
1689
+ connectedCount: inbox.groups.connected.length,
1690
+ })));
1455
1691
  if (inbox.summary.next_recommended_action) {
1456
- console.log(`Next recommended action: ${formatSmartInboxActionValue(inbox.summary.next_recommended_action)}`);
1692
+ console.log(cliKeyLine('next', formatSmartInboxActionValue(inbox.summary.next_recommended_action)));
1457
1693
  }
1694
+ printInboxGroup('Private review', privateReview);
1695
+ printInboxGroup('Needs review', inbox.groups.needs_confirmation);
1696
+ printInboxGroup('Ready to connect', ready);
1697
+ printInboxGroup('Connected', inbox.groups.connected);
1698
+ printInboxGroup('Denied on this device', inbox.groups.ignored);
1699
+ }
1700
+ function buildInboxInteractiveSections(inbox) {
1701
+ const { privateReview, ready } = splitReadyInboxItems(inbox);
1702
+ const rows = (groupLabel, items) => (items.map((item) => ({ groupLabel, item })));
1703
+ const sections = [
1704
+ { id: 'review', label: 'Review', tone: 'warm', rows: rows('Needs review', inbox.groups.needs_confirmation) },
1705
+ { id: 'private', label: 'Private', tone: 'accent', rows: rows('Private review', privateReview) },
1706
+ { id: 'ready', label: 'Ready', tone: 'good', rows: rows('Ready to connect', ready) },
1707
+ { id: 'connected', label: 'Connected', tone: 'muted', rows: rows('Connected', inbox.groups.connected) },
1708
+ { id: 'denied', label: 'Denied', tone: 'danger', rows: rows('Denied on this device', inbox.groups.ignored) },
1709
+ ];
1710
+ return sections.filter((section) => section.rows.length > 0);
1711
+ }
1712
+ function buildInboxInteractiveDetailLines(row) {
1713
+ const item = row.item;
1714
+ const lines = [
1715
+ formatCliDetailLine('Review', row.groupLabel),
1716
+ formatCliDetailLine('Next', formatSmartInboxActionLabel(item)),
1717
+ formatCliDetailLine('Evidence', `${formatCliEvidenceLabel(item.confidence)} local evidence`),
1718
+ formatCliDetailLine('Source', item.detected_source),
1719
+ ];
1720
+ if (item.workspaceId || item.projectId) {
1721
+ lines.push(formatCliDetailLine('Workspace', formatScopeDetailValue(item.workspaceId, 'workspace')));
1722
+ lines.push(formatCliDetailLine('Project', formatScopeDetailValue(item.projectId, 'project')));
1723
+ }
1724
+ else {
1725
+ lines.push(formatCliDetailLine('Scope', 'solo / no workspace'));
1726
+ }
1727
+ if (item.matched_passport_gaid) {
1728
+ lines.push(formatCliDetailLine('Passport', item.matched_passport_gaid));
1729
+ }
1730
+ return lines;
1731
+ }
1732
+ async function maybeShowInteractiveInboxTable(inbox) {
1733
+ const freshness = inbox.summary.freshness_state ?? 'fresh';
1734
+ const ageSeconds = Number.isFinite(inbox.summary.snapshot_age_seconds)
1735
+ ? Number(inbox.summary.snapshot_age_seconds)
1736
+ : 0;
1737
+ return await viewInteractiveTable({
1738
+ title: '[forkit-connect] Inbox',
1739
+ subtitle: joinCliSummary([
1740
+ `${formatTimestamp(inbox.summary.generated_at)} snapshot`,
1741
+ freshness,
1742
+ formatCliAge(ageSeconds),
1743
+ ]),
1744
+ sections: buildInboxInteractiveSections(inbox),
1745
+ columns: [
1746
+ { header: 'Item', width: 32, render: (row) => row.item.display_name },
1747
+ { header: 'Type', width: 18, render: (row) => row.item.item_type.replaceAll('_', ' ') },
1748
+ { header: 'Next', width: 28, render: (row) => formatSmartInboxActionLabel(row.item) },
1749
+ { header: 'Evidence', width: 14, render: (row) => formatCliEvidenceCell(row.item.confidence) },
1750
+ ],
1751
+ detailLines: buildInboxInteractiveDetailLines,
1752
+ detailTitle: (row) => `${row.item.item_type.replaceAll('_', ' ').toUpperCase()} SNAPSHOT`,
1753
+ emptyState: 'No inbox items are available right now.',
1754
+ });
1458
1755
  }
1459
1756
  function printConnectStatusOverview(status) {
1460
1757
  const sessionMissing = !status.device_paired && status.binding_state === 'active' && !status.credential_reconnect_needed;
@@ -1483,20 +1780,28 @@ function printConnectStatusOverview(status) {
1483
1780
  }
1484
1781
  function printPublicStatusOverview(status) {
1485
1782
  const otherReadyCount = getOtherReadyCount(status.ready_to_connect_count, status.draft_first_count);
1486
- console.log('[forkit-connect] Status');
1487
- console.log(`- device=${status.device_paired ? 'paired' : 'approval pending'}`);
1488
- console.log(`- scope=${status.workspace_id && status.project_id ? `${status.workspace_id} / ${status.project_id}` : 'not selected'}`);
1489
- console.log(`- daemon=${status.daemon_status}`);
1490
- console.log(`- local inventory=models ${status.models_discovered} · agents ${status.agents_discovered} · runtimes ${status.runtimes_discovered}`);
1491
- console.log(`- review queue=private review ${status.draft_first_count} · ready ${otherReadyCount} · needs review ${status.needs_confirmation_count}`);
1492
- console.log(`- connected records=${status.connected_count}`);
1493
- console.log(`- pending runtime sync=${status.c2_sync_pending}`);
1494
- console.log(`- privacy=${status.privacy_mode}`);
1783
+ printCliHeader('Status');
1784
+ console.log(cliKeyLine('device', status.device_paired ? 'paired' : 'approval pending'));
1785
+ console.log(cliKeyLine('scope', formatCliScopeLabel(status.workspace_id, status.project_id)));
1786
+ console.log(cliKeyLine('daemon', status.daemon_status));
1787
+ console.log(cliKeyLine('privacy', status.privacy_mode));
1788
+ console.log(cliKeyLine('inventory', joinCliSummary([
1789
+ formatCliCompactCount(status.models_discovered, 'model'),
1790
+ formatCliCompactCount(status.agents_discovered, 'agent'),
1791
+ formatCliCompactCount(status.runtimes_discovered, 'runtime'),
1792
+ ])));
1793
+ console.log(cliKeyLine('queue', summarizeReviewQueueCounts({
1794
+ draftFirstCount: status.draft_first_count,
1795
+ readyCount: otherReadyCount,
1796
+ needsReviewCount: status.needs_confirmation_count,
1797
+ })));
1798
+ console.log(cliKeyLine('linked', formatCliCompactCount(status.connected_count, 'connected record')));
1799
+ console.log(cliKeyLine('sync', `${status.c2_sync_pending} pending runtime events`));
1495
1800
  if (status.lifecycle_note) {
1496
- console.log(`- note=${status.lifecycle_note}`);
1801
+ console.log(cliKeyLine('governed', truncateCliText(status.lifecycle_note, 88)));
1497
1802
  }
1498
1803
  if (!status.device_paired && status.binding_state === 'active') {
1499
- console.log('- warning=Local workspace/project scope exists, but the account session is missing. Re-login is required before governed actions can continue.');
1804
+ console.log(cliKeyLine('warning', 'Local governed scope exists, but login is still required.'));
1500
1805
  }
1501
1806
  }
1502
1807
  function getOtherReadyCount(readyToConnectCount, draftFirstCount) {
@@ -1510,34 +1815,65 @@ function printPublicStatusGuidance(status, sessionState) {
1510
1815
  : sessionState === 'unavailable'
1511
1816
  ? 'unverified'
1512
1817
  : 'login required';
1513
- console.log(`- account=${accountLabel}`);
1818
+ console.log(cliKeyLine('account', accountLabel));
1514
1819
  if (sessionState === 'missing') {
1515
- console.log('- note=Local discovery is working. Sign in next to pair this device with your Forkit.dev account.');
1516
- console.log('- next=run forkit-connect login');
1820
+ console.log(cliKeyLine('session', 'Local discovery is working. Sign in to pair this device.'));
1821
+ console.log(cliKeyLine('next', 'forkit-connect login'));
1517
1822
  if (status.binding_state === 'active' && status.workspace_id && status.project_id) {
1518
- console.log('- warning=This device has local workspace/project scope saved, but it is not an active account session yet.');
1823
+ console.log(cliKeyLine('scope', 'This device still has saved governed scope from an older session.'));
1519
1824
  }
1520
1825
  return;
1521
1826
  }
1522
1827
  if (sessionState === 'expired') {
1523
- console.log('- note=Your local device is initialized, but the account session needs to be renewed.');
1524
- console.log('- next=run forkit-connect login');
1828
+ console.log(cliKeyLine('session', 'The local device is ready, but the account session needs renewal.'));
1829
+ console.log(cliKeyLine('next', 'forkit-connect login'));
1525
1830
  return;
1526
1831
  }
1527
1832
  if (!status.device_paired) {
1528
- console.log('- note=Device identity is ready locally, but browser approval still needs to complete.');
1529
- console.log('- next=finish login approval on Forkit.dev, then rerun forkit-connect status');
1833
+ console.log(cliKeyLine('session', 'Local identity is ready, but browser approval still needs to finish.'));
1834
+ console.log(cliKeyLine('next', 'Finish approval in the browser, then rerun forkit-connect status'));
1530
1835
  return;
1531
1836
  }
1532
1837
  if (status.daemon_status === 'stopped') {
1533
- console.log('- note=Background sync is idle right now. This is expected until you start the local daemon.');
1534
- console.log('- next=run forkit-connect start');
1838
+ console.log(cliKeyLine('daemon', 'Background sync is idle until you start the local daemon.'));
1839
+ console.log(cliKeyLine('next', 'forkit-connect start'));
1535
1840
  return;
1536
1841
  }
1537
1842
  if (status.next_recommended_action) {
1538
- console.log(`- next_detail=${formatSmartInboxActionValue(status.next_recommended_action)}`);
1843
+ console.log(cliKeyLine('next', formatSmartInboxActionValue(status.next_recommended_action)));
1539
1844
  }
1540
1845
  }
1846
+ function buildRuntimeInteractiveDetailLines(runtime) {
1847
+ return [
1848
+ formatCliDetailLine('State', runtime.status.replaceAll('_', ' ')),
1849
+ formatCliDetailLine('Health', runtime.health_status),
1850
+ formatCliDetailLine('Endpoint', runtime.source_endpoint_label || runtime.runtime_type),
1851
+ formatCliDetailLine('Models', runtime.linked_models_count),
1852
+ formatCliDetailLine('Agents', runtime.linked_agents_count),
1853
+ formatCliDetailLine('Next', formatRuntimeRecommendedActionLabel(runtime.recommended_action)),
1854
+ ];
1855
+ }
1856
+ async function maybeShowInteractiveRuntimeReviewTable(summary) {
1857
+ const healthy = summary.runtimes.filter((runtime) => runtime.status !== 'unavailable' && runtime.health_status === 'detected');
1858
+ const review = summary.runtimes.filter((runtime) => !healthy.includes(runtime));
1859
+ return await viewInteractiveTable({
1860
+ title: '[forkit-connect] Runtime Review',
1861
+ subtitle: `${formatCliCompactCount(summary.total_runtimes, 'runtime')} on this device`,
1862
+ sections: [
1863
+ { id: 'healthy', label: 'Healthy', tone: 'good', rows: healthy },
1864
+ { id: 'review', label: 'Needs review', tone: 'warm', rows: review },
1865
+ ],
1866
+ columns: [
1867
+ { header: 'Runtime', width: 24, render: (runtime) => runtime.runtime_name },
1868
+ { header: 'State', width: 18, render: (runtime) => runtime.status.replaceAll('_', ' ') },
1869
+ { header: 'Health', width: 12, render: (runtime) => runtime.health_status },
1870
+ { header: 'Next', width: 24, render: (runtime) => formatRuntimeRecommendedActionLabel(runtime.recommended_action) },
1871
+ ],
1872
+ detailLines: buildRuntimeInteractiveDetailLines,
1873
+ detailTitle: () => 'RUNTIME SNAPSHOT',
1874
+ emptyState: 'No runtimes are available right now.',
1875
+ });
1876
+ }
1541
1877
  async function checkBackendSessionState(service) {
1542
1878
  const sessionRefValue = String(service.readSessionRef() || '').trim();
1543
1879
  if (!sessionRefValue)
@@ -1676,6 +2012,180 @@ async function promptSelection(label, options) {
1676
2012
  node_process_1.stdin.on('keypress', onKeypress);
1677
2013
  });
1678
2014
  }
2015
+ function paintCliTone(text, tone) {
2016
+ switch (tone) {
2017
+ case 'accent':
2018
+ return cliAccent(text);
2019
+ case 'good':
2020
+ return cliGood(text);
2021
+ case 'warm':
2022
+ return cliWarm(text);
2023
+ case 'danger':
2024
+ return cliDanger(text);
2025
+ default:
2026
+ return cliDim(text);
2027
+ }
2028
+ }
2029
+ function fitCliTableCell(value, width, align = 'left') {
2030
+ const rendered = truncateCliText(value, width);
2031
+ const pad = Math.max(0, width - cliPlainWidth(rendered));
2032
+ return align === 'right'
2033
+ ? `${' '.repeat(pad)}${rendered}`
2034
+ : `${rendered}${' '.repeat(pad)}`;
2035
+ }
2036
+ function buildCliTableBorder(widths, left, middle, right) {
2037
+ return `${left}${widths.map((width) => '─'.repeat(width + 2)).join(middle)}${right}`;
2038
+ }
2039
+ function buildCliTableRow(cells) {
2040
+ return `│${cells.map((cell) => ` ${fitCliTableCell(cell.value, cell.width, cell.align)} `).join('│')}│`;
2041
+ }
2042
+ function buildCliWideBox(title, lines, width) {
2043
+ const innerWidth = Math.max(24, width);
2044
+ const safeTitle = truncateCliText(title, Math.max(8, innerWidth - 4));
2045
+ const topPad = Math.max(0, innerWidth - cliPlainWidth(safeTitle) - 2);
2046
+ const top = `┌─ ${safeTitle} ${'─'.repeat(topPad)}┐`;
2047
+ const body = lines.map((line) => `│ ${fitCliTableCell(line, innerWidth)} │`);
2048
+ const bottom = `└${'─'.repeat(innerWidth + 2)}┘`;
2049
+ return [top, ...body, bottom];
2050
+ }
2051
+ function cliTableCell(value, width, align) {
2052
+ return align ? { value, width, align } : { value, width };
2053
+ }
2054
+ function formatCliTableTabs(sections, activeIndex) {
2055
+ return sections
2056
+ .map((section, index) => {
2057
+ const label = `${section.label} ${section.rows.length}`;
2058
+ if (index === activeIndex) {
2059
+ return paintCli(` ${label} `, '7');
2060
+ }
2061
+ return paintCliTone(`[${label}]`, section.tone ?? 'muted');
2062
+ })
2063
+ .join(' ');
2064
+ }
2065
+ async function viewInteractiveTable(options) {
2066
+ const sections = options.sections.filter((section) => section.rows.length > 0);
2067
+ if (!sections.length || !node_process_1.stdin.isTTY || !node_process_1.stdout.isTTY || hasFlag('--plain')) {
2068
+ return false;
2069
+ }
2070
+ (0, node_readline_1.emitKeypressEvents)(node_process_1.stdin);
2071
+ const canUseRawMode = typeof node_process_1.stdin.setRawMode === 'function';
2072
+ const previousRawMode = canUseRawMode ? node_process_1.stdin.isRaw : false;
2073
+ if (canUseRawMode) {
2074
+ node_process_1.stdin.setRawMode(true);
2075
+ }
2076
+ let activeSectionIndex = 0;
2077
+ let selectedIndex = 0;
2078
+ let renderLineCount = 0;
2079
+ const visibleRows = 8;
2080
+ const reset = '\u001B[0m';
2081
+ const hideCursor = '\u001B[?25l';
2082
+ const showCursor = '\u001B[?25h';
2083
+ const inverse = '\u001B[7m';
2084
+ const render = () => {
2085
+ const activeSection = sections[activeSectionIndex] ?? sections[0];
2086
+ const rows = activeSection?.rows ?? [];
2087
+ const currentRow = rows[selectedIndex] ?? null;
2088
+ const startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(visibleRows / 2), Math.max(0, rows.length - visibleRows)));
2089
+ const visibleSlice = rows.slice(startIndex, startIndex + visibleRows);
2090
+ const selectorWidth = 1;
2091
+ const columnWidths = [selectorWidth, ...options.columns.map((column) => column.width)];
2092
+ const headerLine = buildCliTableRow([
2093
+ cliTableCell(' ', selectorWidth),
2094
+ ...options.columns.map((column) => cliTableCell(column.header.toUpperCase(), column.width, column.align)),
2095
+ ]);
2096
+ const rowLines = visibleSlice.length > 0
2097
+ ? visibleSlice.map((row, offset) => {
2098
+ const absoluteIndex = startIndex + offset;
2099
+ const active = absoluteIndex === selectedIndex;
2100
+ const line = buildCliTableRow([
2101
+ cliTableCell(active ? cliAccent('›') : ' ', selectorWidth),
2102
+ ...options.columns.map((column, index) => cliTableCell(active && index === 0 ? cliBold(column.render(row)) : column.render(row), column.width, column.align)),
2103
+ ]);
2104
+ return active ? cliAccent(line) : line;
2105
+ })
2106
+ : buildCliWideBox('EMPTY', [options.emptyState], columnWidths.reduce((sum, value) => sum + value + 3, -1));
2107
+ const detail = currentRow ? options.detailLines(currentRow) : [options.emptyState];
2108
+ const innerTableWidth = columnWidths.reduce((sum, value) => sum + value + 3, -1);
2109
+ const tableTop = buildCliTableBorder(columnWidths, '┌', '┬', '┐');
2110
+ const tableMiddle = buildCliTableBorder(columnWidths, '├', '┼', '┤');
2111
+ const tableBottom = buildCliTableBorder(columnWidths, '└', '┴', '┘');
2112
+ const detailTitle = currentRow && options.detailTitle ? options.detailTitle(currentRow) : 'SELECTED ITEM';
2113
+ const detailBox = buildCliWideBox(detailTitle, detail, innerTableWidth);
2114
+ const lines = [
2115
+ cliBold(options.title),
2116
+ ...(options.subtitle ? [cliDim(options.subtitle)] : []),
2117
+ cliRule(),
2118
+ formatCliTableTabs(sections, activeSectionIndex),
2119
+ '',
2120
+ cliDim(tableTop),
2121
+ cliDim(headerLine),
2122
+ cliDim(tableMiddle),
2123
+ ...rowLines,
2124
+ cliDim(tableBottom),
2125
+ '',
2126
+ ...detailBox.map((line) => cliDim(line)),
2127
+ '',
2128
+ cliDim('Tab switch lane · Up/Down move · q or Esc exit'),
2129
+ ];
2130
+ if (renderLineCount > 0) {
2131
+ node_process_1.stdout.write(`\u001B[${renderLineCount}A`);
2132
+ node_process_1.stdout.write('\u001B[0J');
2133
+ }
2134
+ node_process_1.stdout.write(`${hideCursor}${lines.join('\n')}\n`);
2135
+ renderLineCount = lines.length;
2136
+ };
2137
+ const clampSelection = () => {
2138
+ const activeRows = sections[activeSectionIndex]?.rows ?? [];
2139
+ selectedIndex = Math.max(0, Math.min(selectedIndex, Math.max(activeRows.length - 1, 0)));
2140
+ };
2141
+ render();
2142
+ await new Promise((resolve) => setTimeout(resolve, 40));
2143
+ await new Promise((resolve) => {
2144
+ const cleanup = () => {
2145
+ node_process_1.stdin.off('keypress', onKeypress);
2146
+ if (canUseRawMode) {
2147
+ node_process_1.stdin.setRawMode(previousRawMode === true);
2148
+ }
2149
+ node_process_1.stdout.write(`\u001B[${renderLineCount}A`);
2150
+ node_process_1.stdout.write(`\u001B[0J${showCursor}`);
2151
+ resolve();
2152
+ };
2153
+ const onKeypress = (_value, key) => {
2154
+ if (key.ctrl && key.name === 'c') {
2155
+ cleanup();
2156
+ return;
2157
+ }
2158
+ if (key.name === 'up' || key.name === 'k') {
2159
+ selectedIndex = Math.max(0, selectedIndex - 1);
2160
+ render();
2161
+ return;
2162
+ }
2163
+ if (key.name === 'down' || key.name === 'j') {
2164
+ const rows = sections[activeSectionIndex]?.rows ?? [];
2165
+ selectedIndex = Math.min(Math.max(rows.length - 1, 0), selectedIndex + 1);
2166
+ render();
2167
+ return;
2168
+ }
2169
+ if (key.name === 'tab' || key.name === 'right' || key.name === 'l') {
2170
+ activeSectionIndex = (activeSectionIndex + 1) % sections.length;
2171
+ clampSelection();
2172
+ render();
2173
+ return;
2174
+ }
2175
+ if (key.name === 'left' || key.name === 'h') {
2176
+ activeSectionIndex = (activeSectionIndex - 1 + sections.length) % sections.length;
2177
+ clampSelection();
2178
+ render();
2179
+ return;
2180
+ }
2181
+ if (key.name === 'escape' || key.name === 'q' || key.name === 'return' || key.name === 'enter') {
2182
+ cleanup();
2183
+ }
2184
+ };
2185
+ node_process_1.stdin.on('keypress', onKeypress);
2186
+ });
2187
+ return true;
2188
+ }
1679
2189
  async function promptText(question, options) {
1680
2190
  const rl = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
1681
2191
  try {
@@ -1810,9 +2320,9 @@ async function run() {
1810
2320
  return;
1811
2321
  }
1812
2322
  printPublicStatusOverview(displayOverview);
1813
- console.log(`- secure storage=${secureStorage.backend}`);
2323
+ console.log(cliKeyLine('secure', secureStorage.backend));
1814
2324
  if (!secureStorage.available || secureStorage.plaintextFallbackActive) {
1815
- console.log(`- secure storage detail=${secureStorage.detail}`);
2325
+ console.log(cliKeyLine('secure note', secureStorage.detail));
1816
2326
  }
1817
2327
  printPublicStatusGuidance(displayOverview, sessionState);
1818
2328
  };
@@ -1828,17 +2338,16 @@ async function run() {
1828
2338
  return;
1829
2339
  }
1830
2340
  const freshness = inbox.summary.freshness_state ?? 'fresh';
1831
- const ageSeconds = Number.isFinite(inbox.summary.snapshot_age_seconds)
1832
- ? Number(inbox.summary.snapshot_age_seconds)
1833
- : 0;
1834
- console.log(`[forkit-connect] Inbox snapshot freshness=${freshness} age_seconds=${ageSeconds}`);
2341
+ if (await maybeShowInteractiveInboxTable(inbox)) {
2342
+ return;
2343
+ }
2344
+ printSmartInbox(inbox);
1835
2345
  if (freshness === 'stale') {
1836
- console.log('[forkit-connect] Snapshot is stale. Run `forkit-connect inbox --refresh` for immediate reconcile.');
2346
+ console.log(cliKeyLine('refresh', 'Run forkit-connect inbox --refresh for an immediate reconcile.'));
1837
2347
  }
1838
2348
  else if (freshness === 'syncing') {
1839
- console.log('[forkit-connect] Background reconcile is running; results will self-refresh after completion.');
2349
+ console.log(cliKeyLine('refresh', 'Background reconcile is still running.'));
1840
2350
  }
1841
- printSmartInbox(inbox);
1842
2351
  };
1843
2352
  const runPublicCollectedChanges = () => {
1844
2353
  const rawLimit = limitArg !== null ? Number(limitArg) : 20;
@@ -4173,6 +4682,9 @@ async function run() {
4173
4682
  printJson(summary);
4174
4683
  return;
4175
4684
  }
4685
+ if (await maybeShowInteractiveRuntimeReviewTable(summary)) {
4686
+ return;
4687
+ }
4176
4688
  printRuntimeReview(summary);
4177
4689
  return;
4178
4690
  }
@@ -4480,6 +4992,9 @@ async function run() {
4480
4992
  printJson(summary);
4481
4993
  return;
4482
4994
  }
4995
+ if (await maybeShowInteractiveRuntimeReviewTable(summary)) {
4996
+ return;
4997
+ }
4483
4998
  printRuntimeReview(summary);
4484
4999
  return;
4485
5000
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forkit-connect",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Forkit Connect Local Engine - The Global AI Governance Fabric",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",