forkit-connect 0.1.21 → 0.1.22

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 +400 -57
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1399,42 +1399,65 @@ function printCollectedChanges(service, limit) {
1399
1399
  for (const event of state.evidence_events) {
1400
1400
  evidenceTypeCounts.set(event.type, (evidenceTypeCounts.get(event.type) || 0) + 1);
1401
1401
  }
1402
- console.log('[forkit-connect] Collected changes');
1403
- console.log(`- state_dir=${paths.stateDir}`);
1404
- console.log(`- state_file=${paths.stateFile}`);
1405
- console.log(`- evidence_events=${state.evidence_events.length}`);
1406
- console.log(`- pulse_events=${state.pulse_events.length}`);
1407
- console.log(`- sync_queue=${state.sync_queue.length}`);
1408
- console.log(`- pending_reviews=${state.pending_reviews.length}`);
1409
- console.log(`- evolution_candidates=${state.evolution_candidates.length}`);
1410
- console.log(`- c2_events=${state.c2_events.length}`);
1411
- console.log(`- detected_models=${state.detected_models.length}`);
1412
- console.log(`- detected_runtimes=${state.detected_runtimes.length}`);
1413
- console.log(`- detected_agents=${state.detected_agents.length}`);
1414
- console.log('[forkit-connect] Some collected changes are local advisory telemetry and review evidence; only synced queue items become backend-authoritative.');
1402
+ printCliHeader('Changes', 'Local evidence, queue, and runtime activity collected on this device.');
1403
+ console.log(cliKeyLine('state dir', paths.stateDir));
1404
+ console.log(cliKeyLine('state file', paths.stateFile));
1405
+ console.log(cliKeyLine('summary', joinCliSummary([
1406
+ formatCliCompactCount(state.evidence_events.length, 'evidence event'),
1407
+ formatCliCompactCount(state.pulse_events.length, 'pulse event'),
1408
+ formatCliCompactCount(state.sync_queue.length, 'queued sync'),
1409
+ formatCliCompactCount(state.pending_reviews.length, 'pending review'),
1410
+ ])));
1411
+ console.log(cliKeyLine('local', joinCliSummary([
1412
+ formatCliCompactCount(state.detected_models.length, 'model'),
1413
+ formatCliCompactCount(state.detected_runtimes.length, 'runtime'),
1414
+ formatCliCompactCount(state.detected_agents.length, 'agent'),
1415
+ formatCliCompactCount(state.c2_events.length, 'c2 event'),
1416
+ ])));
1417
+ console.log(cliKeyLine('note', 'Only synced queue items become backend-authoritative.'));
1415
1418
  const sortedEvidenceTypes = [...evidenceTypeCounts.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
1416
1419
  if (sortedEvidenceTypes.length > 0) {
1417
- console.log('[forkit-connect] Evidence types observed:');
1420
+ printCliSection('Evidence Types');
1418
1421
  for (const [type, count] of sortedEvidenceTypes) {
1419
- console.log(`- ${type}: ${count}`);
1422
+ printCliEntry(type, {
1423
+ summary: formatCliCompactCount(count, 'event'),
1424
+ });
1420
1425
  }
1421
1426
  }
1422
1427
  if (recentEvidence.length > 0) {
1423
- console.log(`[forkit-connect] Recent evidence events showing=${recentEvidence.length} limit=${limit}`);
1428
+ printCliSection(`Recent Evidence ${cliTag(String(recentEvidence.length), 'accent')}`);
1424
1429
  for (const event of recentEvidence) {
1425
- console.log(`- ${formatTimestamp(event.createdAt)} | ${event.type}${formatEvidenceDetails(event.details)}`);
1430
+ printCliEntry(event.type, {
1431
+ summary: formatTimestamp(event.createdAt),
1432
+ meta: truncateCliText(formatEvidenceDetails(event.details).replace(/^\s*\|\s*/, ''), 88),
1433
+ });
1426
1434
  }
1427
1435
  }
1428
1436
  if (recentPulse.length > 0) {
1429
- console.log(`[forkit-connect] Recent runtime signal history showing=${recentPulse.length} limit=${limit}`);
1437
+ printCliSection(`Runtime Signals ${cliTag(String(recentPulse.length), 'good')}`);
1430
1438
  for (const event of recentPulse) {
1431
- console.log(`- ${event.measured_at} | runtime=${event.runtime_name} | model=${event.model_name || 'n/a'} | pulse=${event.pulse_status} | registration=${formatPulseRegistrationLabel(event)}`);
1439
+ printCliEntry(event.runtime_name, {
1440
+ summary: event.pulse_status,
1441
+ meta: joinCliSummary([
1442
+ formatTimestamp(event.measured_at),
1443
+ event.model_name ? `model ${event.model_name}` : null,
1444
+ formatPulseRegistrationLabel(event),
1445
+ ]),
1446
+ });
1432
1447
  }
1433
1448
  }
1434
1449
  if (state.sync_queue.length > 0) {
1435
- console.log('[forkit-connect] Pending sync queue:');
1450
+ printCliSection(`Pending Sync ${cliTag(String(Math.min(state.sync_queue.length, limit)), 'warm')}`);
1436
1451
  for (const item of state.sync_queue.slice(0, limit)) {
1437
- console.log(`- ${item.type} | endpoint=${item.endpoint} | attempts=${item.attempts} | created_at=${item.createdAt} | next_retry_at=${item.nextRetryAt} | last_error=${item.lastError || 'n/a'}`);
1452
+ printCliEntry(item.type, {
1453
+ summary: item.endpoint,
1454
+ meta: joinCliSummary([
1455
+ `attempts ${item.attempts}`,
1456
+ `created ${formatTimestamp(item.createdAt)}`,
1457
+ item.nextRetryAt ? `retry ${formatTimestamp(item.nextRetryAt)}` : null,
1458
+ item.lastError ? `error ${truncateCliText(item.lastError, 48)}` : null,
1459
+ ]),
1460
+ });
1438
1461
  }
1439
1462
  }
1440
1463
  }
@@ -1468,14 +1491,25 @@ function printReviewSnapshot(snapshot) {
1468
1491
  'shadow_candidate',
1469
1492
  'provider_unavailable',
1470
1493
  ];
1471
- console.log(`[forkit-connect] Review items=${snapshot.total}`);
1494
+ printCliHeader('Review', `${formatCliCompactCount(snapshot.total, 'local item')} across models and runtimes.`);
1472
1495
  for (const status of order) {
1473
1496
  const entries = snapshot.groups[status];
1474
1497
  if (entries.length === 0)
1475
1498
  continue;
1476
- console.log(`${formatReviewStatusLabel(status)}: ${entries.length}`);
1499
+ printCliSection(`${formatReviewStatusLabel(status)} ${cliTag(String(entries.length), status === 'bound_passport' ? 'good' : status === 'provider_unavailable' ? 'danger' : 'warm')}`);
1477
1500
  for (const entry of entries) {
1478
- console.log(`- item=${entry.model_name || entry.process_name || 'n/a'} | runtime=${entry.runtime_name || 'n/a'} | discovery=${shortId(entry.discovery_hash)} | registration=${shortId(entry.registration_key)} | next=${formatReviewActionLabel(entry.suggested_action)}${formatReviewEvidence(entry)}`);
1501
+ printCliEntry(formatCliReviewSubject(entry), {
1502
+ tag: entry.kind,
1503
+ tagTone: entry.kind === 'provider' ? 'danger' : entry.kind === 'process' ? 'warm' : 'accent',
1504
+ summary: formatReviewSuggestedActionCompact(entry.suggested_action),
1505
+ meta: joinCliSummary([
1506
+ formatCliReviewRuntime(entry),
1507
+ entry.discovery_hash ? `discovery ${shortId(entry.discovery_hash)}` : null,
1508
+ entry.registration_key ? `registration ${shortId(entry.registration_key)}` : null,
1509
+ entry.passport_gaid ? `passport ${shortId(entry.passport_gaid)}` : null,
1510
+ formatReviewEvidence(entry).replace(/^\s*\|\s*/, ''),
1511
+ ]),
1512
+ });
1479
1513
  }
1480
1514
  }
1481
1515
  }
@@ -1504,17 +1538,93 @@ function printTrainStatus(status) {
1504
1538
  }
1505
1539
  function printAgentReview(snapshot) {
1506
1540
  const order = ['new_agent', 'known_agent', 'linked_agent', 'inactive_agent', 'unknown_ai_tool'];
1507
- console.log(`[forkit-connect] Agent review items=${snapshot.total}`);
1541
+ printCliHeader('Agent Review', `${formatCliCompactCount(snapshot.total, 'agent item')} detected on this device.`);
1508
1542
  for (const status of order) {
1509
1543
  const entries = snapshot.groups[status];
1510
1544
  if (entries.length === 0)
1511
1545
  continue;
1512
- console.log(`${status}: ${entries.length}`);
1546
+ const tone = status === 'linked_agent' ? 'good' : status === 'inactive_agent' ? 'danger' : status === 'unknown_ai_tool' ? 'muted' : 'warm';
1547
+ const label = status === 'new_agent'
1548
+ ? 'New agents'
1549
+ : status === 'known_agent'
1550
+ ? 'Needs link'
1551
+ : status === 'linked_agent'
1552
+ ? 'Linked agents'
1553
+ : status === 'inactive_agent'
1554
+ ? 'Inactive agents'
1555
+ : 'Unknown AI tools';
1556
+ printCliSection(`${label} ${cliTag(String(entries.length), tone)}`);
1513
1557
  for (const entry of entries) {
1514
- console.log(`- agent=${entry.agent_name} | type=${entry.agent_type} | status=${entry.status} | id=${shortId(entry.agent_id)} | linked_model=${entry.linked_model_name || 'n/a'} | passport=${entry.linked_passport_gaid || 'n/a'} | action=${entry.suggested_action}${formatReviewEvidence(entry)}`);
1558
+ printCliEntry(entry.agent_name, {
1559
+ tag: entry.agent_type.replaceAll('_', ' '),
1560
+ tagTone: entry.agent_type === 'unknown_ai_tool' ? 'muted' : 'accent',
1561
+ summary: formatAgentSuggestedActionCompact(entry.suggested_action),
1562
+ meta: joinCliSummary([
1563
+ entry.status.replaceAll('_', ' '),
1564
+ entry.linked_model_name ? `model ${entry.linked_model_name}` : null,
1565
+ entry.linked_passport_gaid ? `passport ${shortId(entry.linked_passport_gaid)}` : null,
1566
+ formatReviewEvidence(entry).replace(/^\s*\|\s*/, ''),
1567
+ ]),
1568
+ });
1515
1569
  }
1516
1570
  }
1517
1571
  }
1572
+ function printWorkspaceListSurface(workspaces, options) {
1573
+ printCliHeader('Workspaces', `${formatCliCompactCount(workspaces.length, 'accessible workspace')} on this account.`);
1574
+ const identityBits = [
1575
+ options?.displayName || null,
1576
+ options?.accountEmail || null,
1577
+ options?.platformRole ? `role ${options.platformRole}` : null,
1578
+ ];
1579
+ if (identityBits.some(Boolean)) {
1580
+ console.log(cliKeyLine('account', joinCliSummary(identityBits)));
1581
+ }
1582
+ if (options?.accountLimits) {
1583
+ console.log(cliKeyLine('plan', options.accountLimits.planName));
1584
+ console.log(cliKeyLine('capacity', joinCliSummary([
1585
+ formatRemainingLimit(options.accountLimits.workspaceLimit, options.accountLimits.workspacesUsed, 'workspace'),
1586
+ formatRemainingLimit(options.accountLimits.projectLimit, options.accountLimits.projectsUsed, 'project'),
1587
+ ])));
1588
+ }
1589
+ if (workspaces.length === 0) {
1590
+ console.log(cliKeyLine('next', 'Create a workspace from forkit-connect workspace create.'));
1591
+ return;
1592
+ }
1593
+ printCliSection('Accessible');
1594
+ for (const workspace of workspaces) {
1595
+ printCliEntry(String(workspace.name || 'Unnamed workspace').trim() || 'Unnamed workspace', {
1596
+ tag: String(workspace.role || 'unknown').trim() || 'unknown',
1597
+ tagTone: 'accent',
1598
+ summary: formatWorkspaceStateCell(workspace),
1599
+ meta: joinCliSummary([
1600
+ String(workspace.id || workspace.gaid || workspace.passportGaid || 'unknown'),
1601
+ workspace.verificationStatus ? `verification ${workspace.verificationStatus}` : null,
1602
+ workspace.description ? truncateCliText(workspace.description, 52) : null,
1603
+ ]),
1604
+ });
1605
+ }
1606
+ }
1607
+ function printProjectListSurface(projects, workspaceId) {
1608
+ printCliHeader('Projects', `${formatCliCompactCount(projects.length, 'project')} inside workspace ${shortId(workspaceId)}.`);
1609
+ if (projects.length === 0) {
1610
+ console.log(cliKeyLine('next', 'Create the first project with forkit-connect workspace select --workspace <id> --project-name "<name>".'));
1611
+ return;
1612
+ }
1613
+ printCliSection('Available');
1614
+ for (const project of projects) {
1615
+ const passportCount = Number(project.passportCount);
1616
+ printCliEntry(String(project.name || 'Unnamed project').trim() || 'Unnamed project', {
1617
+ tag: String(project.status || 'active').trim() || 'active',
1618
+ tagTone: 'good',
1619
+ summary: Number.isFinite(passportCount) && passportCount >= 0 ? `${passportCount} passports` : 'passport count unavailable',
1620
+ meta: joinCliSummary([
1621
+ String(project.id || 'unknown'),
1622
+ project.updatedAt ? `updated ${formatTimestamp(project.updatedAt)}` : null,
1623
+ project.description ? truncateCliText(project.description, 52) : null,
1624
+ ]),
1625
+ });
1626
+ }
1627
+ }
1518
1628
  function printAgentStatus(status) {
1519
1629
  console.log(JSON.stringify({
1520
1630
  detected_agents: status.detectedAgents,
@@ -1697,6 +1807,242 @@ function printSmartInbox(inbox) {
1697
1807
  printInboxGroup('Connected', inbox.groups.connected);
1698
1808
  printInboxGroup('Denied on this device', inbox.groups.ignored);
1699
1809
  }
1810
+ function formatCliEvidenceSummary(confidence, sourceLabel) {
1811
+ const normalizedConfidence = String(confidence || '').trim().toLowerCase();
1812
+ if (normalizedConfidence === 'high' || normalizedConfidence === 'medium' || normalizedConfidence === 'low') {
1813
+ return formatCliEvidenceCell(normalizedConfidence);
1814
+ }
1815
+ const source = String(sourceLabel || '').trim();
1816
+ return source ? truncateCliText(source, 14) : 'local review';
1817
+ }
1818
+ function formatCliReviewSubject(entry) {
1819
+ if (entry.kind === 'process') {
1820
+ return String(entry.process_name || 'Unknown process').trim() || 'Unknown process';
1821
+ }
1822
+ return String(entry.model_name || entry.runtime_name || 'Unknown item').trim() || 'Unknown item';
1823
+ }
1824
+ function formatCliReviewRuntime(entry) {
1825
+ if (entry.kind === 'provider') {
1826
+ return String(entry.runtime_name || 'runtime').trim() || 'runtime';
1827
+ }
1828
+ return String(entry.runtime_name || 'local').trim() || 'local';
1829
+ }
1830
+ function formatReviewSuggestedActionCompact(action) {
1831
+ switch (action) {
1832
+ case 'connect_model':
1833
+ return 'Connect model';
1834
+ case 'review_drafts':
1835
+ return 'Open draft';
1836
+ case 'already_connected':
1837
+ return 'Already linked';
1838
+ case 'review/runtime':
1839
+ return 'Check runtime';
1840
+ case 'review/connect':
1841
+ case 'review_model':
1842
+ return 'Review item';
1843
+ default:
1844
+ return truncateCliText(action.replaceAll('_', ' '), 24);
1845
+ }
1846
+ }
1847
+ function buildDiscoveryInteractiveSections(snapshot) {
1848
+ const mapRows = (groupLabel, items) => items.map((entry) => ({ groupLabel, entry }));
1849
+ const sections = [
1850
+ { id: 'new', label: 'New', tone: 'accent', rows: mapRows('New models', snapshot.groups.new_unregistered) },
1851
+ { id: 'known', label: 'Known', tone: 'warm', rows: mapRows('Known models', snapshot.groups.known_unregistered) },
1852
+ { id: 'drafts', label: 'Drafts', tone: 'good', rows: mapRows('Pending drafts', snapshot.groups.pending_draft) },
1853
+ { id: 'linked', label: 'Linked', tone: 'muted', rows: mapRows('Connected', snapshot.groups.bound_passport) },
1854
+ { id: 'runtime', label: 'Runtime', tone: 'warm', rows: mapRows('Unconnected runtimes', snapshot.groups.shadow_candidate) },
1855
+ { id: 'unavailable', label: 'Offline', tone: 'danger', rows: mapRows('Unavailable runtimes', snapshot.groups.provider_unavailable) },
1856
+ ];
1857
+ return sections.filter((section) => section.rows.length > 0);
1858
+ }
1859
+ function buildDiscoveryInteractiveDetailLines(row) {
1860
+ const entry = row.entry;
1861
+ const lines = [
1862
+ formatCliDetailLine('Review', row.groupLabel),
1863
+ formatCliDetailLine('Item', formatCliReviewSubject(entry)),
1864
+ formatCliDetailLine('Runtime', formatCliReviewRuntime(entry)),
1865
+ formatCliDetailLine('Next', formatReviewSuggestedActionCompact(entry.suggested_action)),
1866
+ formatCliDetailLine('Evidence', formatCliEvidenceSummary(entry.confidence, entry.source_label)),
1867
+ ];
1868
+ if (entry.source_label) {
1869
+ lines.push(formatCliDetailLine('Source', entry.source_label));
1870
+ }
1871
+ if (entry.discovery_hash) {
1872
+ lines.push(formatCliDetailLine('Discovery', shortId(entry.discovery_hash)));
1873
+ }
1874
+ if (entry.registration_key) {
1875
+ lines.push(formatCliDetailLine('Registration', shortId(entry.registration_key)));
1876
+ }
1877
+ if (entry.draft_id) {
1878
+ lines.push(formatCliDetailLine('Draft', shortId(entry.draft_id)));
1879
+ }
1880
+ if (entry.passport_gaid) {
1881
+ lines.push(formatCliDetailLine('Passport', shortId(entry.passport_gaid)));
1882
+ }
1883
+ if (entry.reason) {
1884
+ lines.push(formatCliDetailLine('Reason', truncateCliText(entry.reason, 64)));
1885
+ }
1886
+ return lines;
1887
+ }
1888
+ async function maybeShowInteractiveDiscoveryReviewTable(snapshot) {
1889
+ return await viewInteractiveTable({
1890
+ title: '[forkit-connect] Review',
1891
+ subtitle: `${formatCliCompactCount(snapshot.total, 'local item')} across models and runtimes`,
1892
+ sections: buildDiscoveryInteractiveSections(snapshot),
1893
+ columns: [
1894
+ { header: 'Item', width: 30, render: (row) => formatCliReviewSubject(row.entry) },
1895
+ { header: 'Runtime', width: 18, render: (row) => formatCliReviewRuntime(row.entry) },
1896
+ { header: 'Next', width: 24, render: (row) => formatReviewSuggestedActionCompact(row.entry.suggested_action) },
1897
+ { header: 'Evidence', width: 14, render: (row) => formatCliEvidenceSummary(row.entry.confidence, row.entry.source_label) },
1898
+ ],
1899
+ detailLines: buildDiscoveryInteractiveDetailLines,
1900
+ detailTitle: (row) => `${row.entry.kind.toUpperCase()} SNAPSHOT`,
1901
+ emptyState: 'No local review items are available right now.',
1902
+ });
1903
+ }
1904
+ function formatAgentSuggestedActionCompact(action) {
1905
+ switch (action) {
1906
+ case 'none':
1907
+ return 'Already linked';
1908
+ case 'rescan_agent':
1909
+ return 'Rescan agent';
1910
+ case 'review_agent':
1911
+ return 'Review agent';
1912
+ case 'link_model':
1913
+ return 'Link model';
1914
+ case 'connect_agent':
1915
+ return 'Connect agent';
1916
+ default:
1917
+ return truncateCliText(action.replaceAll('_', ' '), 24);
1918
+ }
1919
+ }
1920
+ function buildAgentInteractiveSections(snapshot) {
1921
+ const rows = (groupLabel, items) => items.map((entry) => ({ groupLabel, entry }));
1922
+ const sections = [
1923
+ { id: 'new', label: 'New', tone: 'accent', rows: rows('New agents', snapshot.groups.new_agent) },
1924
+ { id: 'known', label: 'Needs link', tone: 'warm', rows: rows('Needs link', snapshot.groups.known_agent) },
1925
+ { id: 'linked', label: 'Linked', tone: 'good', rows: rows('Linked agents', snapshot.groups.linked_agent) },
1926
+ { id: 'inactive', label: 'Inactive', tone: 'danger', rows: rows('Inactive agents', snapshot.groups.inactive_agent) },
1927
+ { id: 'tooling', label: 'Tooling', tone: 'muted', rows: rows('Unknown AI tools', snapshot.groups.unknown_ai_tool) },
1928
+ ];
1929
+ return sections.filter((section) => section.rows.length > 0);
1930
+ }
1931
+ function buildAgentInteractiveDetailLines(row) {
1932
+ const entry = row.entry;
1933
+ const lines = [
1934
+ formatCliDetailLine('Review', row.groupLabel),
1935
+ formatCliDetailLine('Status', entry.status.replaceAll('_', ' ')),
1936
+ formatCliDetailLine('Next', formatAgentSuggestedActionCompact(entry.suggested_action)),
1937
+ formatCliDetailLine('Evidence', formatCliEvidenceSummary(entry.confidence, entry.source_label)),
1938
+ formatCliDetailLine('Agent ID', shortId(entry.agent_id)),
1939
+ ];
1940
+ if (entry.source_label) {
1941
+ lines.push(formatCliDetailLine('Source', entry.source_label));
1942
+ }
1943
+ if (entry.linked_model_name) {
1944
+ lines.push(formatCliDetailLine('Model', entry.linked_model_name));
1945
+ }
1946
+ if (entry.linked_passport_gaid) {
1947
+ lines.push(formatCliDetailLine('Passport', shortId(entry.linked_passport_gaid)));
1948
+ }
1949
+ if (entry.matched_terms?.length) {
1950
+ lines.push(formatCliDetailLine('Matched', truncateCliText(entry.matched_terms.join(', '), 64)));
1951
+ }
1952
+ return lines;
1953
+ }
1954
+ async function maybeShowInteractiveAgentReviewTable(snapshot) {
1955
+ return await viewInteractiveTable({
1956
+ title: '[forkit-connect] Agent Review',
1957
+ subtitle: `${formatCliCompactCount(snapshot.total, 'agent item')} discovered on this device`,
1958
+ sections: buildAgentInteractiveSections(snapshot),
1959
+ columns: [
1960
+ { header: 'Agent', width: 28, render: (row) => row.entry.agent_name },
1961
+ { header: 'Type', width: 18, render: (row) => row.entry.agent_type.replaceAll('_', ' ') },
1962
+ { header: 'State', width: 16, render: (row) => row.entry.status.replaceAll('_', ' ') },
1963
+ { header: 'Next', width: 22, render: (row) => formatAgentSuggestedActionCompact(row.entry.suggested_action) },
1964
+ ],
1965
+ detailLines: buildAgentInteractiveDetailLines,
1966
+ detailTitle: () => 'AGENT SNAPSHOT',
1967
+ emptyState: 'No agent review items are available right now.',
1968
+ });
1969
+ }
1970
+ function formatWorkspaceStateCell(workspace) {
1971
+ return [String(workspace.visibility || '').trim(), String(workspace.lifecycleStatus || '').trim()]
1972
+ .filter(Boolean)
1973
+ .join(' · ') || 'active';
1974
+ }
1975
+ function buildWorkspaceInteractiveDetailLines(row) {
1976
+ const workspace = row.workspace;
1977
+ const lines = [
1978
+ formatCliDetailLine('Workspace', String(workspace.name || 'Unnamed workspace').trim() || 'Unnamed workspace'),
1979
+ formatCliDetailLine('Role', String(workspace.role || 'unknown').trim() || 'unknown'),
1980
+ formatCliDetailLine('Visibility', String(workspace.visibility || 'not set').trim() || 'not set'),
1981
+ formatCliDetailLine('Lifecycle', String(workspace.lifecycleStatus || 'active').trim() || 'active'),
1982
+ formatCliDetailLine('Verification', String(workspace.verificationStatus || 'unverified').trim() || 'unverified'),
1983
+ formatCliDetailLine('ID', String(workspace.id || workspace.gaid || workspace.passportGaid || 'unknown').trim() || 'unknown'),
1984
+ ];
1985
+ if (workspace.ownerName) {
1986
+ lines.push(formatCliDetailLine('Owner', String(workspace.ownerName).trim()));
1987
+ }
1988
+ if (workspace.description) {
1989
+ lines.push(formatCliDetailLine('Notes', truncateCliText(workspace.description, 64)));
1990
+ }
1991
+ return lines;
1992
+ }
1993
+ async function maybeShowInteractiveWorkspaceTable(workspaces) {
1994
+ return await viewInteractiveTable({
1995
+ title: '[forkit-connect] Workspaces',
1996
+ subtitle: `${formatCliCompactCount(workspaces.length, 'accessible workspace')} on this account`,
1997
+ sections: [{ id: 'all', label: 'Accessible', tone: 'accent', rows: workspaces.map((workspace) => ({ workspace })) }],
1998
+ columns: [
1999
+ { header: 'Workspace', width: 28, render: (row) => String(row.workspace.name || 'Unnamed workspace').trim() || 'Unnamed workspace' },
2000
+ { header: 'Role', width: 14, render: (row) => String(row.workspace.role || 'unknown').trim() || 'unknown' },
2001
+ { header: 'State', width: 26, render: (row) => formatWorkspaceStateCell(row.workspace) },
2002
+ { header: 'ID', width: 14, render: (row) => shortId(String(row.workspace.id || row.workspace.gaid || row.workspace.passportGaid || 'unknown')) },
2003
+ ],
2004
+ detailLines: buildWorkspaceInteractiveDetailLines,
2005
+ detailTitle: () => 'WORKSPACE SNAPSHOT',
2006
+ emptyState: 'No workspaces are available right now.',
2007
+ });
2008
+ }
2009
+ function buildProjectInteractiveDetailLines(row) {
2010
+ const project = row.project;
2011
+ const passportCount = Number(project.passportCount);
2012
+ const lines = [
2013
+ formatCliDetailLine('Project', String(project.name || 'Unnamed project').trim() || 'Unnamed project'),
2014
+ formatCliDetailLine('Status', String(project.status || 'active').trim() || 'active'),
2015
+ formatCliDetailLine('Passports', Number.isFinite(passportCount) && passportCount >= 0 ? String(passportCount) : 'n/a'),
2016
+ formatCliDetailLine('Project ID', String(project.id || 'unknown').trim() || 'unknown'),
2017
+ formatCliDetailLine('Workspace', shortId(row.workspaceId || String(project.workspaceId || ''))),
2018
+ ];
2019
+ if (project.updatedAt) {
2020
+ lines.push(formatCliDetailLine('Updated', formatTimestamp(project.updatedAt)));
2021
+ }
2022
+ if (project.description) {
2023
+ lines.push(formatCliDetailLine('Notes', truncateCliText(project.description, 64)));
2024
+ }
2025
+ return lines;
2026
+ }
2027
+ async function maybeShowInteractiveProjectTable(projects, workspaceId) {
2028
+ return await viewInteractiveTable({
2029
+ title: '[forkit-connect] Projects',
2030
+ subtitle: `${formatCliCompactCount(projects.length, 'project')} inside workspace ${shortId(workspaceId)}`,
2031
+ sections: [{ id: 'all', label: 'Available', tone: 'accent', rows: projects.map((project) => ({ workspaceId, project })) }],
2032
+ columns: [
2033
+ { header: 'Project', width: 28, render: (row) => String(row.project.name || 'Unnamed project').trim() || 'Unnamed project' },
2034
+ { header: 'Status', width: 16, render: (row) => String(row.project.status || 'active').trim() || 'active' },
2035
+ { header: 'Passports', width: 12, render: (row) => {
2036
+ const passportCount = Number(row.project.passportCount);
2037
+ return Number.isFinite(passportCount) && passportCount >= 0 ? String(passportCount) : '-';
2038
+ }, align: 'right' },
2039
+ { header: 'Project ID', width: 14, render: (row) => shortId(String(row.project.id || 'unknown')) },
2040
+ ],
2041
+ detailLines: buildProjectInteractiveDetailLines,
2042
+ detailTitle: () => 'PROJECT SNAPSHOT',
2043
+ emptyState: 'No projects are available right now.',
2044
+ });
2045
+ }
1700
2046
  function buildInboxInteractiveSections(inbox) {
1701
2047
  const { privateReview, ready } = splitReadyInboxItems(inbox);
1702
2048
  const rows = (groupLabel, items) => (items.map((item) => ({ groupLabel, item })));
@@ -4622,16 +4968,13 @@ async function run() {
4622
4968
  return;
4623
4969
  }
4624
4970
  const accountLimits = await loadCliAccountLimits().catch(() => null);
4625
- console.log(`[forkit-connect] Workspaces: ${workspaces.length}`);
4626
- if (accountLimits) {
4627
- console.log(`- welcome=${accountLimits.displayName || 'there'}`);
4628
- console.log(`- plan=${accountLimits.planName}`);
4629
- console.log(`- workspaces_left=${formatRemainingLimit(accountLimits.workspaceLimit, accountLimits.workspacesUsed, 'workspace')}`);
4630
- console.log(`- projects_left=${formatRemainingLimit(accountLimits.projectLimit, accountLimits.projectsUsed, 'project')}`);
4631
- }
4632
- for (const workspace of workspaces) {
4633
- console.log(formatWorkspaceAccessLine(workspace));
4971
+ if (await maybeShowInteractiveWorkspaceTable(workspaces)) {
4972
+ return;
4634
4973
  }
4974
+ printWorkspaceListSurface(workspaces, {
4975
+ displayName: accountLimits?.displayName || null,
4976
+ accountLimits,
4977
+ });
4635
4978
  return;
4636
4979
  }
4637
4980
  if (subcommand === 'select') {
@@ -4938,10 +5281,17 @@ async function run() {
4938
5281
  if (command === 'review') {
4939
5282
  const reviewScan = await service.scanRuntime();
4940
5283
  const snapshot = service.buildReviewSnapshot(reviewScan.summary);
5284
+ if (hasFlag('--json')) {
5285
+ printJson(snapshot);
5286
+ return;
5287
+ }
4941
5288
  if (snapshot.total === 0) {
4942
5289
  console.log('No review items found. Run `forkit-connect scan` first or ensure local runtimes are available.');
4943
5290
  return;
4944
5291
  }
5292
+ if (await maybeShowInteractiveDiscoveryReviewTable(snapshot)) {
5293
+ return;
5294
+ }
4945
5295
  printReviewSnapshot(snapshot);
4946
5296
  return;
4947
5297
  }
@@ -5435,10 +5785,17 @@ async function run() {
5435
5785
  }
5436
5786
  if (subcommand === 'review') {
5437
5787
  const snapshot = service.buildAgentReviewSnapshot();
5788
+ if (hasFlag('--json')) {
5789
+ printJson(snapshot);
5790
+ return;
5791
+ }
5438
5792
  if (snapshot.total === 0) {
5439
5793
  console.log('No agent items found. Run `forkit-connect agent scan` first.');
5440
5794
  return;
5441
5795
  }
5796
+ if (await maybeShowInteractiveAgentReviewTable(snapshot)) {
5797
+ return;
5798
+ }
5442
5799
  printAgentReview(snapshot);
5443
5800
  return;
5444
5801
  }
@@ -5834,22 +6191,14 @@ async function run() {
5834
6191
  : typeof tokenPayload?.role === 'string'
5835
6192
  ? tokenPayload.role
5836
6193
  : null;
5837
- if (email || platformRole) {
5838
- const identityParts = [
5839
- email ? `account=${email}` : null,
5840
- platformRole ? `platform_role=${platformRole}` : null,
5841
- ].filter(Boolean);
5842
- console.log(`[forkit-connect] ${identityParts.join(' | ')}`);
5843
- }
5844
6194
  const workspaces = Array.isArray(payload.workspaces) ? payload.workspaces : [];
5845
- console.log(`[forkit-connect] Workspaces: ${workspaces.length}`);
5846
- if (workspaces.length === 0) {
5847
- console.log('[forkit-connect] No accessible workspaces returned by /api/profiles/access.');
6195
+ if (await maybeShowInteractiveWorkspaceTable(workspaces)) {
5848
6196
  return;
5849
6197
  }
5850
- for (const workspace of workspaces) {
5851
- console.log(formatWorkspaceAccessLine(workspace));
5852
- }
6198
+ printWorkspaceListSurface(workspaces, {
6199
+ accountEmail: email,
6200
+ platformRole,
6201
+ });
5853
6202
  return;
5854
6203
  }
5855
6204
  if (command === 'projects') {
@@ -5888,17 +6237,11 @@ async function run() {
5888
6237
  process.exitCode = 2;
5889
6238
  return;
5890
6239
  }
5891
- console.log('Authenticated successfully.');
5892
6240
  const projects = Array.isArray(result.body.projects) ? result.body.projects : [];
5893
- console.log(`Projects: ${projects.length}`);
5894
- if (projects.length === 0) {
5895
- console.log('No projects found in this workspace.');
5896
- console.log('Run `forkit-connect workspace select --workspace <id> --project-name "<name>"` to create the first project here.');
6241
+ if (await maybeShowInteractiveProjectTable(projects, workspaceId)) {
5897
6242
  return;
5898
6243
  }
5899
- for (const project of projects) {
5900
- console.log(formatWorkspaceProjectLine(project));
5901
- }
6244
+ printProjectListSurface(projects, workspaceId);
5902
6245
  return;
5903
6246
  }
5904
6247
  if (command === 'bind') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forkit-connect",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Forkit Connect Local Engine - The Global AI Governance Fabric",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",