forkit-connect 0.1.21 → 0.1.23
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/assets/connect-notifier-512.png +0 -0
- package/dist/cli.js +508 -64
- package/dist/v1/native-shell.d.ts +23 -0
- package/dist/v1/native-shell.js +170 -0
- package/dist/v1/service.js +164 -8
- package/dist/v1/startup.d.ts +12 -0
- package/dist/v1/startup.js +229 -0
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,7 @@ const daemon_1 = require("./v1/daemon");
|
|
|
13
13
|
const service_1 = require("./v1/service");
|
|
14
14
|
const discovery_1 = require("./v1/discovery");
|
|
15
15
|
const heartbeat_1 = require("./v1/heartbeat");
|
|
16
|
+
const startup_1 = require("./v1/startup");
|
|
16
17
|
const runtime_activity_1 = require("./v1/runtime-activity");
|
|
17
18
|
const runtime_editor_activity_1 = require("./v1/runtime-editor-activity");
|
|
18
19
|
const runtime_observation_runner_1 = require("./v1/runtime-observation-runner");
|
|
@@ -81,6 +82,7 @@ const PUBLIC_COMMANDS = [
|
|
|
81
82
|
['login', 'Link this device to your Forkit.dev account'],
|
|
82
83
|
['logout', 'Remove the stored Forkit.dev session from this device'],
|
|
83
84
|
['status', 'Show account, scope, daemon, and queue status'],
|
|
85
|
+
['startup', 'Enable, disable, or inspect CLI startup on login'],
|
|
84
86
|
['changes', 'View all collected local changes and queued sync items'],
|
|
85
87
|
['start', 'Open the interactive launcher or start foreground sync'],
|
|
86
88
|
['stop', 'Stop the local Connect daemon'],
|
|
@@ -117,14 +119,28 @@ const ADVANCED_COMMAND_GROUPS = [
|
|
|
117
119
|
['notify', 'Notification preview and delivery controls'],
|
|
118
120
|
];
|
|
119
121
|
function usage() {
|
|
120
|
-
printCliHeader('CLI', '
|
|
121
|
-
console.log(cliKeyLine('
|
|
122
|
-
console.log(cliKeyLine('
|
|
122
|
+
printCliHeader('CLI', 'Local AI discovery, review, and registration from your terminal.');
|
|
123
|
+
console.log(cliKeyLine('command', 'forkit-connect <command> [options]'));
|
|
124
|
+
console.log(cliKeyLine('always on', 'forkit-connect startup enable'));
|
|
125
|
+
console.log(cliKeyLine('review', 'forkit-connect inbox'));
|
|
123
126
|
console.log(cliKeyLine('runtime', 'forkit-connect runtime <register|review|status>'));
|
|
124
|
-
printCliSection('
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
printCliSection('Start Here');
|
|
128
|
+
console.log(cliKeyLine('login', 'Connect this device to your Forkit.dev account'));
|
|
129
|
+
console.log(cliKeyLine('scan', 'Detect local models, runtimes, and agents'));
|
|
130
|
+
console.log(cliKeyLine('inbox', 'Review what needs action'));
|
|
131
|
+
console.log(cliKeyLine('startup', 'Keep Connect running from sign-in'));
|
|
132
|
+
printCliSection('Daily');
|
|
133
|
+
console.log(cliKeyLine('status', 'See account, queue, and daemon state'));
|
|
134
|
+
console.log(cliKeyLine('start', 'Open the interactive local console'));
|
|
135
|
+
console.log(cliKeyLine('sync', 'Flush queued local metadata'));
|
|
136
|
+
console.log(cliKeyLine('stop', 'Stop the background daemon'));
|
|
137
|
+
printCliSection('Governed');
|
|
138
|
+
console.log(cliKeyLine('workspace', 'Optional workspace and project scope'));
|
|
139
|
+
console.log(cliKeyLine('register', 'Register ready local models'));
|
|
140
|
+
console.log(cliKeyLine('runtime', 'Register or review local runtimes'));
|
|
141
|
+
printCliSection('Safety');
|
|
142
|
+
console.log(cliKeyLine('ignore', 'Deny one detected local model on this device'));
|
|
143
|
+
console.log(cliKeyLine('doctor', 'Check notifications, storage, and runtime wiring'));
|
|
128
144
|
printCliSection('Flags');
|
|
129
145
|
console.log(cliKeyLine('--json', 'Machine-readable output when supported'));
|
|
130
146
|
console.log(cliKeyLine('--all-ready', 'Register every ready local model in the current scope'));
|
|
@@ -1399,42 +1415,65 @@ function printCollectedChanges(service, limit) {
|
|
|
1399
1415
|
for (const event of state.evidence_events) {
|
|
1400
1416
|
evidenceTypeCounts.set(event.type, (evidenceTypeCounts.get(event.type) || 0) + 1);
|
|
1401
1417
|
}
|
|
1402
|
-
|
|
1403
|
-
console.log(
|
|
1404
|
-
console.log(
|
|
1405
|
-
console.log(
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
console.log(
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1418
|
+
printCliHeader('Changes', 'Local evidence, queue, and runtime activity collected on this device.');
|
|
1419
|
+
console.log(cliKeyLine('state dir', paths.stateDir));
|
|
1420
|
+
console.log(cliKeyLine('state file', paths.stateFile));
|
|
1421
|
+
console.log(cliKeyLine('summary', joinCliSummary([
|
|
1422
|
+
formatCliCompactCount(state.evidence_events.length, 'evidence event'),
|
|
1423
|
+
formatCliCompactCount(state.pulse_events.length, 'pulse event'),
|
|
1424
|
+
formatCliCompactCount(state.sync_queue.length, 'queued sync'),
|
|
1425
|
+
formatCliCompactCount(state.pending_reviews.length, 'pending review'),
|
|
1426
|
+
])));
|
|
1427
|
+
console.log(cliKeyLine('local', joinCliSummary([
|
|
1428
|
+
formatCliCompactCount(state.detected_models.length, 'model'),
|
|
1429
|
+
formatCliCompactCount(state.detected_runtimes.length, 'runtime'),
|
|
1430
|
+
formatCliCompactCount(state.detected_agents.length, 'agent'),
|
|
1431
|
+
formatCliCompactCount(state.c2_events.length, 'c2 event'),
|
|
1432
|
+
])));
|
|
1433
|
+
console.log(cliKeyLine('note', 'Only synced queue items become backend-authoritative.'));
|
|
1415
1434
|
const sortedEvidenceTypes = [...evidenceTypeCounts.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
|
|
1416
1435
|
if (sortedEvidenceTypes.length > 0) {
|
|
1417
|
-
|
|
1436
|
+
printCliSection('Evidence Types');
|
|
1418
1437
|
for (const [type, count] of sortedEvidenceTypes) {
|
|
1419
|
-
|
|
1438
|
+
printCliEntry(type, {
|
|
1439
|
+
summary: formatCliCompactCount(count, 'event'),
|
|
1440
|
+
});
|
|
1420
1441
|
}
|
|
1421
1442
|
}
|
|
1422
1443
|
if (recentEvidence.length > 0) {
|
|
1423
|
-
|
|
1444
|
+
printCliSection(`Recent Evidence ${cliTag(String(recentEvidence.length), 'accent')}`);
|
|
1424
1445
|
for (const event of recentEvidence) {
|
|
1425
|
-
|
|
1446
|
+
printCliEntry(event.type, {
|
|
1447
|
+
summary: formatTimestamp(event.createdAt),
|
|
1448
|
+
meta: truncateCliText(formatEvidenceDetails(event.details).replace(/^\s*\|\s*/, ''), 88),
|
|
1449
|
+
});
|
|
1426
1450
|
}
|
|
1427
1451
|
}
|
|
1428
1452
|
if (recentPulse.length > 0) {
|
|
1429
|
-
|
|
1453
|
+
printCliSection(`Runtime Signals ${cliTag(String(recentPulse.length), 'good')}`);
|
|
1430
1454
|
for (const event of recentPulse) {
|
|
1431
|
-
|
|
1455
|
+
printCliEntry(event.runtime_name, {
|
|
1456
|
+
summary: event.pulse_status,
|
|
1457
|
+
meta: joinCliSummary([
|
|
1458
|
+
formatTimestamp(event.measured_at),
|
|
1459
|
+
event.model_name ? `model ${event.model_name}` : null,
|
|
1460
|
+
formatPulseRegistrationLabel(event),
|
|
1461
|
+
]),
|
|
1462
|
+
});
|
|
1432
1463
|
}
|
|
1433
1464
|
}
|
|
1434
1465
|
if (state.sync_queue.length > 0) {
|
|
1435
|
-
|
|
1466
|
+
printCliSection(`Pending Sync ${cliTag(String(Math.min(state.sync_queue.length, limit)), 'warm')}`);
|
|
1436
1467
|
for (const item of state.sync_queue.slice(0, limit)) {
|
|
1437
|
-
|
|
1468
|
+
printCliEntry(item.type, {
|
|
1469
|
+
summary: item.endpoint,
|
|
1470
|
+
meta: joinCliSummary([
|
|
1471
|
+
`attempts ${item.attempts}`,
|
|
1472
|
+
`created ${formatTimestamp(item.createdAt)}`,
|
|
1473
|
+
item.nextRetryAt ? `retry ${formatTimestamp(item.nextRetryAt)}` : null,
|
|
1474
|
+
item.lastError ? `error ${truncateCliText(item.lastError, 48)}` : null,
|
|
1475
|
+
]),
|
|
1476
|
+
});
|
|
1438
1477
|
}
|
|
1439
1478
|
}
|
|
1440
1479
|
}
|
|
@@ -1468,14 +1507,25 @@ function printReviewSnapshot(snapshot) {
|
|
|
1468
1507
|
'shadow_candidate',
|
|
1469
1508
|
'provider_unavailable',
|
|
1470
1509
|
];
|
|
1471
|
-
|
|
1510
|
+
printCliHeader('Review', `${formatCliCompactCount(snapshot.total, 'local item')} across models and runtimes.`);
|
|
1472
1511
|
for (const status of order) {
|
|
1473
1512
|
const entries = snapshot.groups[status];
|
|
1474
1513
|
if (entries.length === 0)
|
|
1475
1514
|
continue;
|
|
1476
|
-
|
|
1515
|
+
printCliSection(`${formatReviewStatusLabel(status)} ${cliTag(String(entries.length), status === 'bound_passport' ? 'good' : status === 'provider_unavailable' ? 'danger' : 'warm')}`);
|
|
1477
1516
|
for (const entry of entries) {
|
|
1478
|
-
|
|
1517
|
+
printCliEntry(formatCliReviewSubject(entry), {
|
|
1518
|
+
tag: entry.kind,
|
|
1519
|
+
tagTone: entry.kind === 'provider' ? 'danger' : entry.kind === 'process' ? 'warm' : 'accent',
|
|
1520
|
+
summary: formatReviewSuggestedActionCompact(entry.suggested_action),
|
|
1521
|
+
meta: joinCliSummary([
|
|
1522
|
+
formatCliReviewRuntime(entry),
|
|
1523
|
+
entry.discovery_hash ? `discovery ${shortId(entry.discovery_hash)}` : null,
|
|
1524
|
+
entry.registration_key ? `registration ${shortId(entry.registration_key)}` : null,
|
|
1525
|
+
entry.passport_gaid ? `passport ${shortId(entry.passport_gaid)}` : null,
|
|
1526
|
+
formatReviewEvidence(entry).replace(/^\s*\|\s*/, ''),
|
|
1527
|
+
]),
|
|
1528
|
+
});
|
|
1479
1529
|
}
|
|
1480
1530
|
}
|
|
1481
1531
|
}
|
|
@@ -1504,17 +1554,93 @@ function printTrainStatus(status) {
|
|
|
1504
1554
|
}
|
|
1505
1555
|
function printAgentReview(snapshot) {
|
|
1506
1556
|
const order = ['new_agent', 'known_agent', 'linked_agent', 'inactive_agent', 'unknown_ai_tool'];
|
|
1507
|
-
|
|
1557
|
+
printCliHeader('Agent Review', `${formatCliCompactCount(snapshot.total, 'agent item')} detected on this device.`);
|
|
1508
1558
|
for (const status of order) {
|
|
1509
1559
|
const entries = snapshot.groups[status];
|
|
1510
1560
|
if (entries.length === 0)
|
|
1511
1561
|
continue;
|
|
1512
|
-
|
|
1562
|
+
const tone = status === 'linked_agent' ? 'good' : status === 'inactive_agent' ? 'danger' : status === 'unknown_ai_tool' ? 'muted' : 'warm';
|
|
1563
|
+
const label = status === 'new_agent'
|
|
1564
|
+
? 'New agents'
|
|
1565
|
+
: status === 'known_agent'
|
|
1566
|
+
? 'Needs link'
|
|
1567
|
+
: status === 'linked_agent'
|
|
1568
|
+
? 'Linked agents'
|
|
1569
|
+
: status === 'inactive_agent'
|
|
1570
|
+
? 'Inactive agents'
|
|
1571
|
+
: 'Unknown AI tools';
|
|
1572
|
+
printCliSection(`${label} ${cliTag(String(entries.length), tone)}`);
|
|
1513
1573
|
for (const entry of entries) {
|
|
1514
|
-
|
|
1574
|
+
printCliEntry(entry.agent_name, {
|
|
1575
|
+
tag: entry.agent_type.replaceAll('_', ' '),
|
|
1576
|
+
tagTone: entry.agent_type === 'unknown_ai_tool' ? 'muted' : 'accent',
|
|
1577
|
+
summary: formatAgentSuggestedActionCompact(entry.suggested_action),
|
|
1578
|
+
meta: joinCliSummary([
|
|
1579
|
+
entry.status.replaceAll('_', ' '),
|
|
1580
|
+
entry.linked_model_name ? `model ${entry.linked_model_name}` : null,
|
|
1581
|
+
entry.linked_passport_gaid ? `passport ${shortId(entry.linked_passport_gaid)}` : null,
|
|
1582
|
+
formatReviewEvidence(entry).replace(/^\s*\|\s*/, ''),
|
|
1583
|
+
]),
|
|
1584
|
+
});
|
|
1515
1585
|
}
|
|
1516
1586
|
}
|
|
1517
1587
|
}
|
|
1588
|
+
function printWorkspaceListSurface(workspaces, options) {
|
|
1589
|
+
printCliHeader('Workspaces', `${formatCliCompactCount(workspaces.length, 'accessible workspace')} on this account.`);
|
|
1590
|
+
const identityBits = [
|
|
1591
|
+
options?.displayName || null,
|
|
1592
|
+
options?.accountEmail || null,
|
|
1593
|
+
options?.platformRole ? `role ${options.platformRole}` : null,
|
|
1594
|
+
];
|
|
1595
|
+
if (identityBits.some(Boolean)) {
|
|
1596
|
+
console.log(cliKeyLine('account', joinCliSummary(identityBits)));
|
|
1597
|
+
}
|
|
1598
|
+
if (options?.accountLimits) {
|
|
1599
|
+
console.log(cliKeyLine('plan', options.accountLimits.planName));
|
|
1600
|
+
console.log(cliKeyLine('capacity', joinCliSummary([
|
|
1601
|
+
formatRemainingLimit(options.accountLimits.workspaceLimit, options.accountLimits.workspacesUsed, 'workspace'),
|
|
1602
|
+
formatRemainingLimit(options.accountLimits.projectLimit, options.accountLimits.projectsUsed, 'project'),
|
|
1603
|
+
])));
|
|
1604
|
+
}
|
|
1605
|
+
if (workspaces.length === 0) {
|
|
1606
|
+
console.log(cliKeyLine('next', 'Create a workspace from forkit-connect workspace create.'));
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
printCliSection('Accessible');
|
|
1610
|
+
for (const workspace of workspaces) {
|
|
1611
|
+
printCliEntry(String(workspace.name || 'Unnamed workspace').trim() || 'Unnamed workspace', {
|
|
1612
|
+
tag: String(workspace.role || 'unknown').trim() || 'unknown',
|
|
1613
|
+
tagTone: 'accent',
|
|
1614
|
+
summary: formatWorkspaceStateCell(workspace),
|
|
1615
|
+
meta: joinCliSummary([
|
|
1616
|
+
String(workspace.id || workspace.gaid || workspace.passportGaid || 'unknown'),
|
|
1617
|
+
workspace.verificationStatus ? `verification ${workspace.verificationStatus}` : null,
|
|
1618
|
+
workspace.description ? truncateCliText(workspace.description, 52) : null,
|
|
1619
|
+
]),
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
function printProjectListSurface(projects, workspaceId) {
|
|
1624
|
+
printCliHeader('Projects', `${formatCliCompactCount(projects.length, 'project')} inside workspace ${shortId(workspaceId)}.`);
|
|
1625
|
+
if (projects.length === 0) {
|
|
1626
|
+
console.log(cliKeyLine('next', 'Create the first project with forkit-connect workspace select --workspace <id> --project-name "<name>".'));
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
printCliSection('Available');
|
|
1630
|
+
for (const project of projects) {
|
|
1631
|
+
const passportCount = Number(project.passportCount);
|
|
1632
|
+
printCliEntry(String(project.name || 'Unnamed project').trim() || 'Unnamed project', {
|
|
1633
|
+
tag: String(project.status || 'active').trim() || 'active',
|
|
1634
|
+
tagTone: 'good',
|
|
1635
|
+
summary: Number.isFinite(passportCount) && passportCount >= 0 ? `${passportCount} passports` : 'passport count unavailable',
|
|
1636
|
+
meta: joinCliSummary([
|
|
1637
|
+
String(project.id || 'unknown'),
|
|
1638
|
+
project.updatedAt ? `updated ${formatTimestamp(project.updatedAt)}` : null,
|
|
1639
|
+
project.description ? truncateCliText(project.description, 52) : null,
|
|
1640
|
+
]),
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1518
1644
|
function printAgentStatus(status) {
|
|
1519
1645
|
console.log(JSON.stringify({
|
|
1520
1646
|
detected_agents: status.detectedAgents,
|
|
@@ -1697,6 +1823,242 @@ function printSmartInbox(inbox) {
|
|
|
1697
1823
|
printInboxGroup('Connected', inbox.groups.connected);
|
|
1698
1824
|
printInboxGroup('Denied on this device', inbox.groups.ignored);
|
|
1699
1825
|
}
|
|
1826
|
+
function formatCliEvidenceSummary(confidence, sourceLabel) {
|
|
1827
|
+
const normalizedConfidence = String(confidence || '').trim().toLowerCase();
|
|
1828
|
+
if (normalizedConfidence === 'high' || normalizedConfidence === 'medium' || normalizedConfidence === 'low') {
|
|
1829
|
+
return formatCliEvidenceCell(normalizedConfidence);
|
|
1830
|
+
}
|
|
1831
|
+
const source = String(sourceLabel || '').trim();
|
|
1832
|
+
return source ? truncateCliText(source, 14) : 'local review';
|
|
1833
|
+
}
|
|
1834
|
+
function formatCliReviewSubject(entry) {
|
|
1835
|
+
if (entry.kind === 'process') {
|
|
1836
|
+
return String(entry.process_name || 'Unknown process').trim() || 'Unknown process';
|
|
1837
|
+
}
|
|
1838
|
+
return String(entry.model_name || entry.runtime_name || 'Unknown item').trim() || 'Unknown item';
|
|
1839
|
+
}
|
|
1840
|
+
function formatCliReviewRuntime(entry) {
|
|
1841
|
+
if (entry.kind === 'provider') {
|
|
1842
|
+
return String(entry.runtime_name || 'runtime').trim() || 'runtime';
|
|
1843
|
+
}
|
|
1844
|
+
return String(entry.runtime_name || 'local').trim() || 'local';
|
|
1845
|
+
}
|
|
1846
|
+
function formatReviewSuggestedActionCompact(action) {
|
|
1847
|
+
switch (action) {
|
|
1848
|
+
case 'connect_model':
|
|
1849
|
+
return 'Connect model';
|
|
1850
|
+
case 'review_drafts':
|
|
1851
|
+
return 'Open draft';
|
|
1852
|
+
case 'already_connected':
|
|
1853
|
+
return 'Already linked';
|
|
1854
|
+
case 'review/runtime':
|
|
1855
|
+
return 'Check runtime';
|
|
1856
|
+
case 'review/connect':
|
|
1857
|
+
case 'review_model':
|
|
1858
|
+
return 'Review item';
|
|
1859
|
+
default:
|
|
1860
|
+
return truncateCliText(action.replaceAll('_', ' '), 24);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
function buildDiscoveryInteractiveSections(snapshot) {
|
|
1864
|
+
const mapRows = (groupLabel, items) => items.map((entry) => ({ groupLabel, entry }));
|
|
1865
|
+
const sections = [
|
|
1866
|
+
{ id: 'new', label: 'New', tone: 'accent', rows: mapRows('New models', snapshot.groups.new_unregistered) },
|
|
1867
|
+
{ id: 'known', label: 'Known', tone: 'warm', rows: mapRows('Known models', snapshot.groups.known_unregistered) },
|
|
1868
|
+
{ id: 'drafts', label: 'Drafts', tone: 'good', rows: mapRows('Pending drafts', snapshot.groups.pending_draft) },
|
|
1869
|
+
{ id: 'linked', label: 'Linked', tone: 'muted', rows: mapRows('Connected', snapshot.groups.bound_passport) },
|
|
1870
|
+
{ id: 'runtime', label: 'Runtime', tone: 'warm', rows: mapRows('Unconnected runtimes', snapshot.groups.shadow_candidate) },
|
|
1871
|
+
{ id: 'unavailable', label: 'Offline', tone: 'danger', rows: mapRows('Unavailable runtimes', snapshot.groups.provider_unavailable) },
|
|
1872
|
+
];
|
|
1873
|
+
return sections.filter((section) => section.rows.length > 0);
|
|
1874
|
+
}
|
|
1875
|
+
function buildDiscoveryInteractiveDetailLines(row) {
|
|
1876
|
+
const entry = row.entry;
|
|
1877
|
+
const lines = [
|
|
1878
|
+
formatCliDetailLine('Review', row.groupLabel),
|
|
1879
|
+
formatCliDetailLine('Item', formatCliReviewSubject(entry)),
|
|
1880
|
+
formatCliDetailLine('Runtime', formatCliReviewRuntime(entry)),
|
|
1881
|
+
formatCliDetailLine('Next', formatReviewSuggestedActionCompact(entry.suggested_action)),
|
|
1882
|
+
formatCliDetailLine('Evidence', formatCliEvidenceSummary(entry.confidence, entry.source_label)),
|
|
1883
|
+
];
|
|
1884
|
+
if (entry.source_label) {
|
|
1885
|
+
lines.push(formatCliDetailLine('Source', entry.source_label));
|
|
1886
|
+
}
|
|
1887
|
+
if (entry.discovery_hash) {
|
|
1888
|
+
lines.push(formatCliDetailLine('Discovery', shortId(entry.discovery_hash)));
|
|
1889
|
+
}
|
|
1890
|
+
if (entry.registration_key) {
|
|
1891
|
+
lines.push(formatCliDetailLine('Registration', shortId(entry.registration_key)));
|
|
1892
|
+
}
|
|
1893
|
+
if (entry.draft_id) {
|
|
1894
|
+
lines.push(formatCliDetailLine('Draft', shortId(entry.draft_id)));
|
|
1895
|
+
}
|
|
1896
|
+
if (entry.passport_gaid) {
|
|
1897
|
+
lines.push(formatCliDetailLine('Passport', shortId(entry.passport_gaid)));
|
|
1898
|
+
}
|
|
1899
|
+
if (entry.reason) {
|
|
1900
|
+
lines.push(formatCliDetailLine('Reason', truncateCliText(entry.reason, 64)));
|
|
1901
|
+
}
|
|
1902
|
+
return lines;
|
|
1903
|
+
}
|
|
1904
|
+
async function maybeShowInteractiveDiscoveryReviewTable(snapshot) {
|
|
1905
|
+
return await viewInteractiveTable({
|
|
1906
|
+
title: '[forkit-connect] Review',
|
|
1907
|
+
subtitle: `${formatCliCompactCount(snapshot.total, 'local item')} across models and runtimes`,
|
|
1908
|
+
sections: buildDiscoveryInteractiveSections(snapshot),
|
|
1909
|
+
columns: [
|
|
1910
|
+
{ header: 'Item', width: 30, render: (row) => formatCliReviewSubject(row.entry) },
|
|
1911
|
+
{ header: 'Runtime', width: 18, render: (row) => formatCliReviewRuntime(row.entry) },
|
|
1912
|
+
{ header: 'Next', width: 24, render: (row) => formatReviewSuggestedActionCompact(row.entry.suggested_action) },
|
|
1913
|
+
{ header: 'Evidence', width: 14, render: (row) => formatCliEvidenceSummary(row.entry.confidence, row.entry.source_label) },
|
|
1914
|
+
],
|
|
1915
|
+
detailLines: buildDiscoveryInteractiveDetailLines,
|
|
1916
|
+
detailTitle: (row) => `${row.entry.kind.toUpperCase()} SNAPSHOT`,
|
|
1917
|
+
emptyState: 'No local review items are available right now.',
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
function formatAgentSuggestedActionCompact(action) {
|
|
1921
|
+
switch (action) {
|
|
1922
|
+
case 'none':
|
|
1923
|
+
return 'Already linked';
|
|
1924
|
+
case 'rescan_agent':
|
|
1925
|
+
return 'Rescan agent';
|
|
1926
|
+
case 'review_agent':
|
|
1927
|
+
return 'Review agent';
|
|
1928
|
+
case 'link_model':
|
|
1929
|
+
return 'Link model';
|
|
1930
|
+
case 'connect_agent':
|
|
1931
|
+
return 'Connect agent';
|
|
1932
|
+
default:
|
|
1933
|
+
return truncateCliText(action.replaceAll('_', ' '), 24);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
function buildAgentInteractiveSections(snapshot) {
|
|
1937
|
+
const rows = (groupLabel, items) => items.map((entry) => ({ groupLabel, entry }));
|
|
1938
|
+
const sections = [
|
|
1939
|
+
{ id: 'new', label: 'New', tone: 'accent', rows: rows('New agents', snapshot.groups.new_agent) },
|
|
1940
|
+
{ id: 'known', label: 'Needs link', tone: 'warm', rows: rows('Needs link', snapshot.groups.known_agent) },
|
|
1941
|
+
{ id: 'linked', label: 'Linked', tone: 'good', rows: rows('Linked agents', snapshot.groups.linked_agent) },
|
|
1942
|
+
{ id: 'inactive', label: 'Inactive', tone: 'danger', rows: rows('Inactive agents', snapshot.groups.inactive_agent) },
|
|
1943
|
+
{ id: 'tooling', label: 'Tooling', tone: 'muted', rows: rows('Unknown AI tools', snapshot.groups.unknown_ai_tool) },
|
|
1944
|
+
];
|
|
1945
|
+
return sections.filter((section) => section.rows.length > 0);
|
|
1946
|
+
}
|
|
1947
|
+
function buildAgentInteractiveDetailLines(row) {
|
|
1948
|
+
const entry = row.entry;
|
|
1949
|
+
const lines = [
|
|
1950
|
+
formatCliDetailLine('Review', row.groupLabel),
|
|
1951
|
+
formatCliDetailLine('Status', entry.status.replaceAll('_', ' ')),
|
|
1952
|
+
formatCliDetailLine('Next', formatAgentSuggestedActionCompact(entry.suggested_action)),
|
|
1953
|
+
formatCliDetailLine('Evidence', formatCliEvidenceSummary(entry.confidence, entry.source_label)),
|
|
1954
|
+
formatCliDetailLine('Agent ID', shortId(entry.agent_id)),
|
|
1955
|
+
];
|
|
1956
|
+
if (entry.source_label) {
|
|
1957
|
+
lines.push(formatCliDetailLine('Source', entry.source_label));
|
|
1958
|
+
}
|
|
1959
|
+
if (entry.linked_model_name) {
|
|
1960
|
+
lines.push(formatCliDetailLine('Model', entry.linked_model_name));
|
|
1961
|
+
}
|
|
1962
|
+
if (entry.linked_passport_gaid) {
|
|
1963
|
+
lines.push(formatCliDetailLine('Passport', shortId(entry.linked_passport_gaid)));
|
|
1964
|
+
}
|
|
1965
|
+
if (entry.matched_terms?.length) {
|
|
1966
|
+
lines.push(formatCliDetailLine('Matched', truncateCliText(entry.matched_terms.join(', '), 64)));
|
|
1967
|
+
}
|
|
1968
|
+
return lines;
|
|
1969
|
+
}
|
|
1970
|
+
async function maybeShowInteractiveAgentReviewTable(snapshot) {
|
|
1971
|
+
return await viewInteractiveTable({
|
|
1972
|
+
title: '[forkit-connect] Agent Review',
|
|
1973
|
+
subtitle: `${formatCliCompactCount(snapshot.total, 'agent item')} discovered on this device`,
|
|
1974
|
+
sections: buildAgentInteractiveSections(snapshot),
|
|
1975
|
+
columns: [
|
|
1976
|
+
{ header: 'Agent', width: 28, render: (row) => row.entry.agent_name },
|
|
1977
|
+
{ header: 'Type', width: 18, render: (row) => row.entry.agent_type.replaceAll('_', ' ') },
|
|
1978
|
+
{ header: 'State', width: 16, render: (row) => row.entry.status.replaceAll('_', ' ') },
|
|
1979
|
+
{ header: 'Next', width: 22, render: (row) => formatAgentSuggestedActionCompact(row.entry.suggested_action) },
|
|
1980
|
+
],
|
|
1981
|
+
detailLines: buildAgentInteractiveDetailLines,
|
|
1982
|
+
detailTitle: () => 'AGENT SNAPSHOT',
|
|
1983
|
+
emptyState: 'No agent review items are available right now.',
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
function formatWorkspaceStateCell(workspace) {
|
|
1987
|
+
return [String(workspace.visibility || '').trim(), String(workspace.lifecycleStatus || '').trim()]
|
|
1988
|
+
.filter(Boolean)
|
|
1989
|
+
.join(' · ') || 'active';
|
|
1990
|
+
}
|
|
1991
|
+
function buildWorkspaceInteractiveDetailLines(row) {
|
|
1992
|
+
const workspace = row.workspace;
|
|
1993
|
+
const lines = [
|
|
1994
|
+
formatCliDetailLine('Workspace', String(workspace.name || 'Unnamed workspace').trim() || 'Unnamed workspace'),
|
|
1995
|
+
formatCliDetailLine('Role', String(workspace.role || 'unknown').trim() || 'unknown'),
|
|
1996
|
+
formatCliDetailLine('Visibility', String(workspace.visibility || 'not set').trim() || 'not set'),
|
|
1997
|
+
formatCliDetailLine('Lifecycle', String(workspace.lifecycleStatus || 'active').trim() || 'active'),
|
|
1998
|
+
formatCliDetailLine('Verification', String(workspace.verificationStatus || 'unverified').trim() || 'unverified'),
|
|
1999
|
+
formatCliDetailLine('ID', String(workspace.id || workspace.gaid || workspace.passportGaid || 'unknown').trim() || 'unknown'),
|
|
2000
|
+
];
|
|
2001
|
+
if (workspace.ownerName) {
|
|
2002
|
+
lines.push(formatCliDetailLine('Owner', String(workspace.ownerName).trim()));
|
|
2003
|
+
}
|
|
2004
|
+
if (workspace.description) {
|
|
2005
|
+
lines.push(formatCliDetailLine('Notes', truncateCliText(workspace.description, 64)));
|
|
2006
|
+
}
|
|
2007
|
+
return lines;
|
|
2008
|
+
}
|
|
2009
|
+
async function maybeShowInteractiveWorkspaceTable(workspaces) {
|
|
2010
|
+
return await viewInteractiveTable({
|
|
2011
|
+
title: '[forkit-connect] Workspaces',
|
|
2012
|
+
subtitle: `${formatCliCompactCount(workspaces.length, 'accessible workspace')} on this account`,
|
|
2013
|
+
sections: [{ id: 'all', label: 'Accessible', tone: 'accent', rows: workspaces.map((workspace) => ({ workspace })) }],
|
|
2014
|
+
columns: [
|
|
2015
|
+
{ header: 'Workspace', width: 28, render: (row) => String(row.workspace.name || 'Unnamed workspace').trim() || 'Unnamed workspace' },
|
|
2016
|
+
{ header: 'Role', width: 14, render: (row) => String(row.workspace.role || 'unknown').trim() || 'unknown' },
|
|
2017
|
+
{ header: 'State', width: 26, render: (row) => formatWorkspaceStateCell(row.workspace) },
|
|
2018
|
+
{ header: 'ID', width: 14, render: (row) => shortId(String(row.workspace.id || row.workspace.gaid || row.workspace.passportGaid || 'unknown')) },
|
|
2019
|
+
],
|
|
2020
|
+
detailLines: buildWorkspaceInteractiveDetailLines,
|
|
2021
|
+
detailTitle: () => 'WORKSPACE SNAPSHOT',
|
|
2022
|
+
emptyState: 'No workspaces are available right now.',
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
function buildProjectInteractiveDetailLines(row) {
|
|
2026
|
+
const project = row.project;
|
|
2027
|
+
const passportCount = Number(project.passportCount);
|
|
2028
|
+
const lines = [
|
|
2029
|
+
formatCliDetailLine('Project', String(project.name || 'Unnamed project').trim() || 'Unnamed project'),
|
|
2030
|
+
formatCliDetailLine('Status', String(project.status || 'active').trim() || 'active'),
|
|
2031
|
+
formatCliDetailLine('Passports', Number.isFinite(passportCount) && passportCount >= 0 ? String(passportCount) : 'n/a'),
|
|
2032
|
+
formatCliDetailLine('Project ID', String(project.id || 'unknown').trim() || 'unknown'),
|
|
2033
|
+
formatCliDetailLine('Workspace', shortId(row.workspaceId || String(project.workspaceId || ''))),
|
|
2034
|
+
];
|
|
2035
|
+
if (project.updatedAt) {
|
|
2036
|
+
lines.push(formatCliDetailLine('Updated', formatTimestamp(project.updatedAt)));
|
|
2037
|
+
}
|
|
2038
|
+
if (project.description) {
|
|
2039
|
+
lines.push(formatCliDetailLine('Notes', truncateCliText(project.description, 64)));
|
|
2040
|
+
}
|
|
2041
|
+
return lines;
|
|
2042
|
+
}
|
|
2043
|
+
async function maybeShowInteractiveProjectTable(projects, workspaceId) {
|
|
2044
|
+
return await viewInteractiveTable({
|
|
2045
|
+
title: '[forkit-connect] Projects',
|
|
2046
|
+
subtitle: `${formatCliCompactCount(projects.length, 'project')} inside workspace ${shortId(workspaceId)}`,
|
|
2047
|
+
sections: [{ id: 'all', label: 'Available', tone: 'accent', rows: projects.map((project) => ({ workspaceId, project })) }],
|
|
2048
|
+
columns: [
|
|
2049
|
+
{ header: 'Project', width: 28, render: (row) => String(row.project.name || 'Unnamed project').trim() || 'Unnamed project' },
|
|
2050
|
+
{ header: 'Status', width: 16, render: (row) => String(row.project.status || 'active').trim() || 'active' },
|
|
2051
|
+
{ header: 'Passports', width: 12, render: (row) => {
|
|
2052
|
+
const passportCount = Number(row.project.passportCount);
|
|
2053
|
+
return Number.isFinite(passportCount) && passportCount >= 0 ? String(passportCount) : '-';
|
|
2054
|
+
}, align: 'right' },
|
|
2055
|
+
{ header: 'Project ID', width: 14, render: (row) => shortId(String(row.project.id || 'unknown')) },
|
|
2056
|
+
],
|
|
2057
|
+
detailLines: buildProjectInteractiveDetailLines,
|
|
2058
|
+
detailTitle: () => 'PROJECT SNAPSHOT',
|
|
2059
|
+
emptyState: 'No projects are available right now.',
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
1700
2062
|
function buildInboxInteractiveSections(inbox) {
|
|
1701
2063
|
const { privateReview, ready } = splitReadyInboxItems(inbox);
|
|
1702
2064
|
const rows = (groupLabel, items) => (items.map((item) => ({ groupLabel, item })));
|
|
@@ -1784,6 +2146,7 @@ function printPublicStatusOverview(status) {
|
|
|
1784
2146
|
console.log(cliKeyLine('device', status.device_paired ? 'paired' : 'approval pending'));
|
|
1785
2147
|
console.log(cliKeyLine('scope', formatCliScopeLabel(status.workspace_id, status.project_id)));
|
|
1786
2148
|
console.log(cliKeyLine('daemon', status.daemon_status));
|
|
2149
|
+
console.log(cliKeyLine('background', status.daemon_status === 'running' ? 'always on' : 'start required'));
|
|
1787
2150
|
console.log(cliKeyLine('privacy', status.privacy_mode));
|
|
1788
2151
|
console.log(cliKeyLine('inventory', joinCliSummary([
|
|
1789
2152
|
formatCliCompactCount(status.models_discovered, 'model'),
|
|
@@ -1804,6 +2167,47 @@ function printPublicStatusOverview(status) {
|
|
|
1804
2167
|
console.log(cliKeyLine('warning', 'Local governed scope exists, but login is still required.'));
|
|
1805
2168
|
}
|
|
1806
2169
|
}
|
|
2170
|
+
function formatStartupSummary(status) {
|
|
2171
|
+
if (!status.supported)
|
|
2172
|
+
return 'not supported';
|
|
2173
|
+
if (status.enabled === true)
|
|
2174
|
+
return 'enabled';
|
|
2175
|
+
if (status.enabled === false)
|
|
2176
|
+
return 'disabled';
|
|
2177
|
+
return 'unknown';
|
|
2178
|
+
}
|
|
2179
|
+
function printStartupStatusLine(status) {
|
|
2180
|
+
console.log(cliKeyLine('startup', formatStartupSummary(status)));
|
|
2181
|
+
}
|
|
2182
|
+
function printPublicStartupStatus(status) {
|
|
2183
|
+
const enabled = status.enabled === true;
|
|
2184
|
+
const platform = status.mode === 'launch_agent'
|
|
2185
|
+
? 'macOS LaunchAgent'
|
|
2186
|
+
: status.mode === 'registry_run'
|
|
2187
|
+
? 'Windows sign-in registry'
|
|
2188
|
+
: status.mode === 'systemd_user'
|
|
2189
|
+
? 'Linux user service'
|
|
2190
|
+
: 'not available';
|
|
2191
|
+
printCliHeader('Startup', 'Keep Forkit Connect running from sign-in without a tray app.');
|
|
2192
|
+
console.log(cliKeyLine('auto start', enabled ? 'on' : status.enabled === false ? 'off' : 'unknown'));
|
|
2193
|
+
console.log(cliKeyLine('behavior', enabled
|
|
2194
|
+
? 'Forkit Connect launches its background daemon when you sign in.'
|
|
2195
|
+
: 'Forkit Connect starts only when you run it manually.'));
|
|
2196
|
+
console.log(cliKeyLine('platform', platform));
|
|
2197
|
+
console.log(cliKeyLine('next', enabled ? 'forkit-connect startup disable' : 'forkit-connect startup enable'));
|
|
2198
|
+
if (hasFlag('--advanced-help') && status.configPath) {
|
|
2199
|
+
console.log(cliKeyLine('config', status.configPath));
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
function getNotificationReadiness() {
|
|
2203
|
+
if (process.platform === 'darwin') {
|
|
2204
|
+
return 'Forkit Connect click-through ready';
|
|
2205
|
+
}
|
|
2206
|
+
if (process.platform === 'win32') {
|
|
2207
|
+
return 'native Windows toasts ready';
|
|
2208
|
+
}
|
|
2209
|
+
return 'native desktop alerts when a GUI session is available';
|
|
2210
|
+
}
|
|
1807
2211
|
function getOtherReadyCount(readyToConnectCount, draftFirstCount) {
|
|
1808
2212
|
return Math.max(0, readyToConnectCount - draftFirstCount);
|
|
1809
2213
|
}
|
|
@@ -2284,6 +2688,7 @@ async function run() {
|
|
|
2284
2688
|
const runPublicConnectStatus = async () => {
|
|
2285
2689
|
const sessionState = await checkBackendSessionState(service);
|
|
2286
2690
|
const secureStorage = service.getCredentialStoreStatus();
|
|
2691
|
+
const startupStatus = (0, startup_1.getCliStartupStatus)();
|
|
2287
2692
|
if (service.readSessionRef()) {
|
|
2288
2693
|
await withTimeout(service.refreshEffectiveBinding(), STATUS_BINDING_TIMEOUT_MS, undefined);
|
|
2289
2694
|
}
|
|
@@ -2309,6 +2714,10 @@ async function run() {
|
|
|
2309
2714
|
session_truth: accountTrusted ? 'account_verified_or_offline' : 'local_scope_cached_login_required',
|
|
2310
2715
|
local_scope_cached: !accountTrusted && localScopeCached,
|
|
2311
2716
|
binding_truth: accountTrusted ? 'account_binding_active' : 'account_login_required',
|
|
2717
|
+
startup_on_login: startupStatus,
|
|
2718
|
+
notifications: {
|
|
2719
|
+
readiness: getNotificationReadiness(),
|
|
2720
|
+
},
|
|
2312
2721
|
secure_storage: {
|
|
2313
2722
|
backend: secureStorage.backend,
|
|
2314
2723
|
available: secureStorage.available,
|
|
@@ -2324,8 +2733,28 @@ async function run() {
|
|
|
2324
2733
|
if (!secureStorage.available || secureStorage.plaintextFallbackActive) {
|
|
2325
2734
|
console.log(cliKeyLine('secure note', secureStorage.detail));
|
|
2326
2735
|
}
|
|
2736
|
+
console.log(cliKeyLine('notifications', getNotificationReadiness()));
|
|
2737
|
+
printStartupStatusLine(startupStatus);
|
|
2327
2738
|
printPublicStatusGuidance(displayOverview, sessionState);
|
|
2328
2739
|
};
|
|
2740
|
+
const runPublicStartupStatus = () => {
|
|
2741
|
+
const startupStatus = (0, startup_1.getCliStartupStatus)();
|
|
2742
|
+
if (hasFlag('--json')) {
|
|
2743
|
+
printJson(startupStatus);
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
printPublicStartupStatus(startupStatus);
|
|
2747
|
+
};
|
|
2748
|
+
const runPublicStartupEnable = () => {
|
|
2749
|
+
const startupStatus = (0, startup_1.enableCliStartup)(process.execPath, __filename);
|
|
2750
|
+
printPublicStartupStatus(startupStatus);
|
|
2751
|
+
console.log('[forkit-connect] CLI startup on login is enabled. The background daemon will come up when your session starts.');
|
|
2752
|
+
};
|
|
2753
|
+
const runPublicStartupDisable = () => {
|
|
2754
|
+
const startupStatus = (0, startup_1.disableCliStartup)();
|
|
2755
|
+
printPublicStartupStatus(startupStatus);
|
|
2756
|
+
console.log('[forkit-connect] CLI startup on login is disabled. Use forkit-connect stop if you also want to stop the daemon right now.');
|
|
2757
|
+
};
|
|
2329
2758
|
const runPublicConnectInbox = async () => {
|
|
2330
2759
|
const forceRefresh = hasFlag('--refresh');
|
|
2331
2760
|
const inbox = service.getSmartRegistrationInbox({
|
|
@@ -4588,6 +5017,24 @@ async function run() {
|
|
|
4588
5017
|
await runPublicConnectStatus();
|
|
4589
5018
|
return;
|
|
4590
5019
|
}
|
|
5020
|
+
if (command === 'startup') {
|
|
5021
|
+
const subcommand = args[1] || 'status';
|
|
5022
|
+
if (subcommand === 'status') {
|
|
5023
|
+
runPublicStartupStatus();
|
|
5024
|
+
return;
|
|
5025
|
+
}
|
|
5026
|
+
if (subcommand === 'enable') {
|
|
5027
|
+
runPublicStartupEnable();
|
|
5028
|
+
return;
|
|
5029
|
+
}
|
|
5030
|
+
if (subcommand === 'disable') {
|
|
5031
|
+
runPublicStartupDisable();
|
|
5032
|
+
return;
|
|
5033
|
+
}
|
|
5034
|
+
showUsage();
|
|
5035
|
+
process.exitCode = 1;
|
|
5036
|
+
return;
|
|
5037
|
+
}
|
|
4591
5038
|
if (command === 'changes') {
|
|
4592
5039
|
runPublicCollectedChanges();
|
|
4593
5040
|
return;
|
|
@@ -4622,16 +5069,13 @@ async function run() {
|
|
|
4622
5069
|
return;
|
|
4623
5070
|
}
|
|
4624
5071
|
const accountLimits = await loadCliAccountLimits().catch(() => null);
|
|
4625
|
-
|
|
4626
|
-
|
|
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));
|
|
5072
|
+
if (await maybeShowInteractiveWorkspaceTable(workspaces)) {
|
|
5073
|
+
return;
|
|
4634
5074
|
}
|
|
5075
|
+
printWorkspaceListSurface(workspaces, {
|
|
5076
|
+
displayName: accountLimits?.displayName || null,
|
|
5077
|
+
accountLimits,
|
|
5078
|
+
});
|
|
4635
5079
|
return;
|
|
4636
5080
|
}
|
|
4637
5081
|
if (subcommand === 'select') {
|
|
@@ -4938,10 +5382,17 @@ async function run() {
|
|
|
4938
5382
|
if (command === 'review') {
|
|
4939
5383
|
const reviewScan = await service.scanRuntime();
|
|
4940
5384
|
const snapshot = service.buildReviewSnapshot(reviewScan.summary);
|
|
5385
|
+
if (hasFlag('--json')) {
|
|
5386
|
+
printJson(snapshot);
|
|
5387
|
+
return;
|
|
5388
|
+
}
|
|
4941
5389
|
if (snapshot.total === 0) {
|
|
4942
5390
|
console.log('No review items found. Run `forkit-connect scan` first or ensure local runtimes are available.');
|
|
4943
5391
|
return;
|
|
4944
5392
|
}
|
|
5393
|
+
if (await maybeShowInteractiveDiscoveryReviewTable(snapshot)) {
|
|
5394
|
+
return;
|
|
5395
|
+
}
|
|
4945
5396
|
printReviewSnapshot(snapshot);
|
|
4946
5397
|
return;
|
|
4947
5398
|
}
|
|
@@ -5435,10 +5886,17 @@ async function run() {
|
|
|
5435
5886
|
}
|
|
5436
5887
|
if (subcommand === 'review') {
|
|
5437
5888
|
const snapshot = service.buildAgentReviewSnapshot();
|
|
5889
|
+
if (hasFlag('--json')) {
|
|
5890
|
+
printJson(snapshot);
|
|
5891
|
+
return;
|
|
5892
|
+
}
|
|
5438
5893
|
if (snapshot.total === 0) {
|
|
5439
5894
|
console.log('No agent items found. Run `forkit-connect agent scan` first.');
|
|
5440
5895
|
return;
|
|
5441
5896
|
}
|
|
5897
|
+
if (await maybeShowInteractiveAgentReviewTable(snapshot)) {
|
|
5898
|
+
return;
|
|
5899
|
+
}
|
|
5442
5900
|
printAgentReview(snapshot);
|
|
5443
5901
|
return;
|
|
5444
5902
|
}
|
|
@@ -5834,22 +6292,14 @@ async function run() {
|
|
|
5834
6292
|
: typeof tokenPayload?.role === 'string'
|
|
5835
6293
|
? tokenPayload.role
|
|
5836
6294
|
: 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
6295
|
const workspaces = Array.isArray(payload.workspaces) ? payload.workspaces : [];
|
|
5845
|
-
|
|
5846
|
-
if (workspaces.length === 0) {
|
|
5847
|
-
console.log('[forkit-connect] No accessible workspaces returned by /api/profiles/access.');
|
|
6296
|
+
if (await maybeShowInteractiveWorkspaceTable(workspaces)) {
|
|
5848
6297
|
return;
|
|
5849
6298
|
}
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
6299
|
+
printWorkspaceListSurface(workspaces, {
|
|
6300
|
+
accountEmail: email,
|
|
6301
|
+
platformRole,
|
|
6302
|
+
});
|
|
5853
6303
|
return;
|
|
5854
6304
|
}
|
|
5855
6305
|
if (command === 'projects') {
|
|
@@ -5888,17 +6338,11 @@ async function run() {
|
|
|
5888
6338
|
process.exitCode = 2;
|
|
5889
6339
|
return;
|
|
5890
6340
|
}
|
|
5891
|
-
console.log('Authenticated successfully.');
|
|
5892
6341
|
const projects = Array.isArray(result.body.projects) ? result.body.projects : [];
|
|
5893
|
-
|
|
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.');
|
|
6342
|
+
if (await maybeShowInteractiveProjectTable(projects, workspaceId)) {
|
|
5897
6343
|
return;
|
|
5898
6344
|
}
|
|
5899
|
-
|
|
5900
|
-
console.log(formatWorkspaceProjectLine(project));
|
|
5901
|
-
}
|
|
6345
|
+
printProjectListSurface(projects, workspaceId);
|
|
5902
6346
|
return;
|
|
5903
6347
|
}
|
|
5904
6348
|
if (command === 'bind') {
|