forkit-connect 0.1.0 → 0.1.3
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/README.md +32 -4
- package/dist/cli.js +390 -99
- package/dist/launcher.js +1451 -15
- package/dist/v1/api.d.ts +62 -1
- package/dist/v1/api.js +131 -2
- package/dist/v1/credential-store.d.ts +1 -0
- package/dist/v1/credential-store.js +15 -3
- package/dist/v1/service.d.ts +57 -3
- package/dist/v1/service.js +633 -60
- package/dist/v1/state.d.ts +1 -0
- package/dist/v1/state.js +32 -12
- package/dist/v1/types.d.ts +55 -1
- package/package.json +9 -9
package/dist/cli.js
CHANGED
|
@@ -38,6 +38,7 @@ const CLI_FALLBACK_PLAN_LIMITS = {
|
|
|
38
38
|
draftPassports: 0,
|
|
39
39
|
maxWorkspaces: 0,
|
|
40
40
|
maxProjects: 0,
|
|
41
|
+
maxGovernedPassports: 0,
|
|
41
42
|
runtimeSignalsPerMonth: null,
|
|
42
43
|
},
|
|
43
44
|
signal: {
|
|
@@ -45,6 +46,7 @@ const CLI_FALLBACK_PLAN_LIMITS = {
|
|
|
45
46
|
draftPassports: 25,
|
|
46
47
|
maxWorkspaces: 3,
|
|
47
48
|
maxProjects: 3,
|
|
49
|
+
maxGovernedPassports: 3,
|
|
48
50
|
runtimeSignalsPerMonth: 10000,
|
|
49
51
|
},
|
|
50
52
|
protocol: {
|
|
@@ -52,6 +54,7 @@ const CLI_FALLBACK_PLAN_LIMITS = {
|
|
|
52
54
|
draftPassports: 20,
|
|
53
55
|
maxWorkspaces: null,
|
|
54
56
|
maxProjects: null,
|
|
57
|
+
maxGovernedPassports: null,
|
|
55
58
|
runtimeSignalsPerMonth: 250000,
|
|
56
59
|
},
|
|
57
60
|
sovereign: {
|
|
@@ -59,6 +62,7 @@ const CLI_FALLBACK_PLAN_LIMITS = {
|
|
|
59
62
|
draftPassports: null,
|
|
60
63
|
maxWorkspaces: null,
|
|
61
64
|
maxProjects: null,
|
|
65
|
+
maxGovernedPassports: null,
|
|
62
66
|
runtimeSignalsPerMonth: null,
|
|
63
67
|
},
|
|
64
68
|
};
|
|
@@ -74,6 +78,7 @@ const PUBLIC_COMMANDS = [
|
|
|
74
78
|
['inbox', 'Review the Smart Registration Inbox'],
|
|
75
79
|
['sync', 'Flush queued drafts and lifecycle metadata'],
|
|
76
80
|
['workspace', 'List, select, or inspect optional governed workspace/project scope'],
|
|
81
|
+
['runtime', 'Review local runtime discovery and runtime health'],
|
|
77
82
|
['register', 'Register ready local models into the current scope'],
|
|
78
83
|
['ignore', 'Ignore one detected local model'],
|
|
79
84
|
['doctor', 'Run local environment diagnostics'],
|
|
@@ -101,11 +106,12 @@ const ADVANCED_COMMAND_GROUPS = [
|
|
|
101
106
|
['notify', 'Notification preview and delivery controls'],
|
|
102
107
|
];
|
|
103
108
|
function usage() {
|
|
104
|
-
console.log('Usage: forkit-connect <init|login|logout|status|changes|start|stop|scan|inbox|sync|workspace|register|ignore|doctor> [options]');
|
|
109
|
+
console.log('Usage: forkit-connect <init|login|logout|status|changes|start|stop|scan|inbox|sync|workspace|runtime|register|ignore|doctor> [options]');
|
|
105
110
|
console.log(' forkit-connect workspace <list|select|create|status> [options]');
|
|
111
|
+
console.log(' forkit-connect runtime <review|status> [options]');
|
|
106
112
|
console.log('Public commands:');
|
|
107
113
|
for (const [command, description] of PUBLIC_COMMANDS) {
|
|
108
|
-
console.log(` ${command.padEnd(
|
|
114
|
+
console.log(` ${command.padEnd(10)} ${description}`);
|
|
109
115
|
}
|
|
110
116
|
console.log('Options:');
|
|
111
117
|
console.log(' --description <text> Optional workspace description used by workspace create');
|
|
@@ -151,6 +157,22 @@ function advancedUsage() {
|
|
|
151
157
|
console.log(' --heartbeat-gaid <gaid> Queue heartbeat runtime signal event for GAID');
|
|
152
158
|
console.log(' --heartbeat-key <key> API key used for heartbeat runtime signal event');
|
|
153
159
|
console.log(' Also used by: c2 set-key (stores key + backfills events)');
|
|
160
|
+
console.log(' --gaid <gaid> Passport GAID used by c2 run-log emit');
|
|
161
|
+
console.log(' --api-key <key> Runtime signal API key used by c2 run-log emit');
|
|
162
|
+
console.log(' --provider <name> Provider label used by c2 run-log emit');
|
|
163
|
+
console.log(' --service-name <name> Service/agent/workflow label used by c2 run-log emit');
|
|
164
|
+
console.log(' --prompt-tokens <n> Prompt tokens used by c2 run-log emit');
|
|
165
|
+
console.log(' --completion-tokens <n> Completion tokens used by c2 run-log emit');
|
|
166
|
+
console.log(' --client-name <name> Optional repo/runtime client label used by c2 run-log emit');
|
|
167
|
+
console.log(' --actor-labels <csv> Actor labels used by c2 run-log emit (for example: Codex,Claude)');
|
|
168
|
+
console.log(' --task-labels <csv> Task or chat labels used by c2 run-log emit');
|
|
169
|
+
console.log(' --folder-labels <csv> Relative folder labels used by c2 run-log emit');
|
|
170
|
+
console.log(' --file-labels <csv> Relative file labels used by c2 run-log emit');
|
|
171
|
+
console.log(' --model-labels <csv> Optional model labels used by c2 run-log emit');
|
|
172
|
+
console.log(' --cpu-percent <n> Scoped CPU percentage used by c2 run-log emit');
|
|
173
|
+
console.log(' --memory-mb <n> Scoped memory usage used by c2 run-log emit');
|
|
174
|
+
console.log(' --vram-mb <n> Device VRAM usage used by c2 run-log emit');
|
|
175
|
+
console.log(' --draft-only Allow draft creation even when governed publish capacity is full');
|
|
154
176
|
}
|
|
155
177
|
function showUsage() {
|
|
156
178
|
if (hasFlag('--advanced-help')) {
|
|
@@ -169,6 +191,13 @@ function getArg(flag) {
|
|
|
169
191
|
function hasFlag(flag) {
|
|
170
192
|
return process.argv.slice(2).includes(flag);
|
|
171
193
|
}
|
|
194
|
+
function getNumericArg(flag) {
|
|
195
|
+
const value = getArg(flag);
|
|
196
|
+
if (value === null)
|
|
197
|
+
return undefined;
|
|
198
|
+
const parsed = Number(value);
|
|
199
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
200
|
+
}
|
|
172
201
|
function isHelpCommand(command) {
|
|
173
202
|
return command === 'help' || command === '--help' || command === '-h' || command === '--advanced-help';
|
|
174
203
|
}
|
|
@@ -182,6 +211,20 @@ function sleep(ms) {
|
|
|
182
211
|
delay(resolve, ms);
|
|
183
212
|
});
|
|
184
213
|
}
|
|
214
|
+
async function withTimeout(promise, timeoutMs, fallbackValue) {
|
|
215
|
+
return new Promise((resolve) => {
|
|
216
|
+
const timeout = setTimeout(() => resolve(fallbackValue), Math.max(0, timeoutMs));
|
|
217
|
+
promise
|
|
218
|
+
.then((value) => {
|
|
219
|
+
clearTimeout(timeout);
|
|
220
|
+
resolve(value);
|
|
221
|
+
})
|
|
222
|
+
.catch(() => {
|
|
223
|
+
clearTimeout(timeout);
|
|
224
|
+
resolve(fallbackValue);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
185
228
|
function isDeviceConnectStartResponse(body) {
|
|
186
229
|
if (!body || typeof body !== 'object')
|
|
187
230
|
return false;
|
|
@@ -220,12 +263,11 @@ function printDeviceLoginInstructions(start) {
|
|
|
220
263
|
console.log('[forkit-connect] Keep this terminal open while Forkit Connect waits for approval.');
|
|
221
264
|
}
|
|
222
265
|
function printSessionExportFallback(token) {
|
|
266
|
+
void token;
|
|
223
267
|
console.log('[forkit-connect] Approval succeeded, but secure credential storage is unavailable in this Linux session.');
|
|
224
268
|
console.log('[forkit-connect] Forkit Connect can keep using this approved session in the current interactive run.');
|
|
225
|
-
console.log('[forkit-connect]
|
|
226
|
-
console.log(
|
|
227
|
-
console.log('[forkit-connect] Then rerun: forkit-connect status');
|
|
228
|
-
console.log('[forkit-connect] For a persistent and safer setup, install libsecret-tools and run forkit-connect login again.');
|
|
269
|
+
console.log('[forkit-connect] For persistent login, install libsecret-tools and run forkit-connect login again.');
|
|
270
|
+
console.log('[forkit-connect] Headless automation may pass FORKIT_CONNECT_SESSION_REF explicitly, but Connect will not print session tokens.');
|
|
229
271
|
}
|
|
230
272
|
function hasLinuxGuiSession() {
|
|
231
273
|
if (process.platform !== 'linux') {
|
|
@@ -300,6 +342,9 @@ function formatRemainingLimit(limit, used, singular, plural = `${singular}s`) {
|
|
|
300
342
|
const remaining = Math.max(safeLimit - safeUsed, 0);
|
|
301
343
|
return `${remaining} left (${safeUsed}/${safeLimit} used)`;
|
|
302
344
|
}
|
|
345
|
+
function isCapacityExhausted(limit, remaining) {
|
|
346
|
+
return Number.isFinite(limit) && Number(remaining) <= 0;
|
|
347
|
+
}
|
|
303
348
|
function formatWorkspaceAccessLine(workspace) {
|
|
304
349
|
const workspaceId = String(workspace.id || workspace.gaid || workspace.passportGaid || 'unknown');
|
|
305
350
|
return `- ${summarizeWorkspaceLabel(workspace)} | id=${workspaceId}`;
|
|
@@ -577,6 +622,11 @@ function printJson(value) {
|
|
|
577
622
|
console.log(JSON.stringify(value, null, 2));
|
|
578
623
|
}
|
|
579
624
|
const INTERACTIVE_LABEL_WIDTH = 24;
|
|
625
|
+
const INTERACTIVE_DISCOVERY_TIMEOUT_MS = 800;
|
|
626
|
+
const INTERACTIVE_BINDING_TIMEOUT_MS = 800;
|
|
627
|
+
const INTERACTIVE_ACCOUNT_LIMITS_TIMEOUT_MS = 700;
|
|
628
|
+
const SESSION_STATE_CHECK_TIMEOUT_MS = 3000;
|
|
629
|
+
const STATUS_BINDING_TIMEOUT_MS = 800;
|
|
580
630
|
function canRenderInteractiveShell() {
|
|
581
631
|
return Boolean(node_process_1.stdin.isTTY && node_process_1.stdout.isTTY);
|
|
582
632
|
}
|
|
@@ -628,11 +678,8 @@ function shellListLine(value) {
|
|
|
628
678
|
return `• ${value}`;
|
|
629
679
|
}
|
|
630
680
|
function buildInteractiveOverviewSections(service, sessionState, accountLimits) {
|
|
631
|
-
const
|
|
632
|
-
const
|
|
633
|
-
const leftoverReady = inbox.groups.ready_to_connect.slice(0, 4).map((item) => shellListLine(item.display_name));
|
|
634
|
-
const leftoverNeedsConfirmation = inbox.groups.needs_confirmation.slice(0, 4).map((item) => shellListLine(item.display_name));
|
|
635
|
-
const accountTrusted = sessionState === 'authorized' || sessionState === 'unavailable';
|
|
681
|
+
const accountTrusted = sessionState === 'authorized';
|
|
682
|
+
const overview = service.getConnectStatusOverview({ includeInbox: false });
|
|
636
683
|
const preparedWorkspace = accountTrusted ? String(overview.workspace_id || '').trim() : '';
|
|
637
684
|
const preparedProject = accountTrusted ? String(overview.project_id || '').trim() : '';
|
|
638
685
|
// Base section: always visible
|
|
@@ -673,6 +720,9 @@ function buildInteractiveOverviewSections(service, sessionState, accountLimits)
|
|
|
673
720
|
lines: [
|
|
674
721
|
shellLine('Plan', accountLimits.planName),
|
|
675
722
|
shellLine('Private passports', formatRemainingLimit(accountLimits.privatePassportsLimit, accountLimits.privatePassportsUsed, 'private passport')),
|
|
723
|
+
...(accountLimits.governedPassportsLimit !== null || accountLimits.governedPassportsUsed !== null
|
|
724
|
+
? [shellLine('Governed passports', formatRemainingLimit(accountLimits.governedPassportsLimit, accountLimits.governedPassportsUsed, 'governed passport'))]
|
|
725
|
+
: []),
|
|
676
726
|
shellLine('Drafts', formatRemainingLimit(accountLimits.draftLimit, accountLimits.draftsUsed, 'draft')),
|
|
677
727
|
shellLine('Workspaces', formatRemainingLimit(accountLimits.workspaceLimit, accountLimits.workspacesUsed, 'workspace')),
|
|
678
728
|
shellLine('Projects', formatRemainingLimit(accountLimits.projectLimit, accountLimits.projectsUsed, 'project')),
|
|
@@ -687,10 +737,8 @@ function buildInteractiveOverviewSections(service, sessionState, accountLimits)
|
|
|
687
737
|
lines: [
|
|
688
738
|
shellLine('Workspace', formatScopeReferenceLabel(preparedWorkspace || null, 'workspace')),
|
|
689
739
|
shellLine('Project', formatScopeReferenceLabel(preparedProject || null, 'project')),
|
|
690
|
-
shellLine('Ready to connect',
|
|
691
|
-
shellLine('Needs confirmation',
|
|
692
|
-
...leftoverReady,
|
|
693
|
-
...leftoverNeedsConfirmation,
|
|
740
|
+
shellLine('Ready to connect', overview.ready_to_connect_count),
|
|
741
|
+
shellLine('Needs confirmation', overview.needs_confirmation_count),
|
|
694
742
|
],
|
|
695
743
|
});
|
|
696
744
|
}
|
|
@@ -1081,22 +1129,24 @@ function printAgentLedger(summary) {
|
|
|
1081
1129
|
console.log(JSON.stringify(summary, null, 2));
|
|
1082
1130
|
}
|
|
1083
1131
|
function printRuntimeReview(summary) {
|
|
1084
|
-
console.log(
|
|
1132
|
+
console.log('[forkit-connect] Runtime review');
|
|
1133
|
+
console.log(`- runtimes=${summary.total_runtimes}`);
|
|
1085
1134
|
for (const runtime of summary.runtimes) {
|
|
1086
|
-
console.log(`-
|
|
1135
|
+
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}`);
|
|
1087
1136
|
}
|
|
1088
1137
|
}
|
|
1089
1138
|
function printRuntimeStatus(status) {
|
|
1090
|
-
console.log(
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1139
|
+
console.log('[forkit-connect] Runtime status');
|
|
1140
|
+
console.log(`- total=${status.total_runtimes}`);
|
|
1141
|
+
console.log(`- online=${status.online_runtimes}`);
|
|
1142
|
+
console.log(`- offline=${status.offline_runtimes}`);
|
|
1143
|
+
console.log(`- linked=${status.linked_runtimes}`);
|
|
1144
|
+
console.log(`- unlinked=${status.unlinked_runtimes}`);
|
|
1145
|
+
console.log(`- needs attention=${status.unhealthy_runtimes}`);
|
|
1146
|
+
console.log(`- pending runtime sync=${status.c2_pending_count}`);
|
|
1147
|
+
if (status.latest_runtime_lifecycle_event) {
|
|
1148
|
+
console.log(`- latest event=${status.latest_runtime_lifecycle_event}`);
|
|
1149
|
+
}
|
|
1100
1150
|
}
|
|
1101
1151
|
function formatSmartInboxActionValue(action, itemType, connectableModelName) {
|
|
1102
1152
|
switch (action) {
|
|
@@ -1172,18 +1222,14 @@ function printConnectStatusOverview(status) {
|
|
|
1172
1222
|
}
|
|
1173
1223
|
function printPublicStatusOverview(status) {
|
|
1174
1224
|
console.log('[forkit-connect] Status');
|
|
1175
|
-
console.log(`-
|
|
1176
|
-
console.log(`-
|
|
1177
|
-
console.log(`- project=${status.project_id || 'not selected'}`);
|
|
1225
|
+
console.log(`- device=${status.device_paired ? 'paired' : 'approval pending'}`);
|
|
1226
|
+
console.log(`- scope=${status.workspace_id && status.project_id ? `${status.workspace_id} / ${status.project_id}` : 'not selected'}`);
|
|
1178
1227
|
console.log(`- daemon=${status.daemon_status}`);
|
|
1179
|
-
console.log(`-
|
|
1180
|
-
console.log(`-
|
|
1181
|
-
console.log(`-
|
|
1182
|
-
console.log(`-
|
|
1183
|
-
console.log(`-
|
|
1184
|
-
console.log(`- connected=${status.connected_count}`);
|
|
1185
|
-
console.log(`- c2_sync_pending=${status.c2_sync_pending}`);
|
|
1186
|
-
console.log(`- privacy_mode=${status.privacy_mode}`);
|
|
1228
|
+
console.log(`- local inventory=models ${status.models_discovered} · agents ${status.agents_discovered} · runtimes ${status.runtimes_discovered}`);
|
|
1229
|
+
console.log(`- review queue=ready ${status.ready_to_connect_count} · needs review ${status.needs_confirmation_count}`);
|
|
1230
|
+
console.log(`- connected records=${status.connected_count}`);
|
|
1231
|
+
console.log(`- pending runtime sync=${status.c2_sync_pending}`);
|
|
1232
|
+
console.log(`- privacy=${status.privacy_mode}`);
|
|
1187
1233
|
if (status.lifecycle_note) {
|
|
1188
1234
|
console.log(`- note=${status.lifecycle_note}`);
|
|
1189
1235
|
}
|
|
@@ -1192,6 +1238,14 @@ function printPublicStatusOverview(status) {
|
|
|
1192
1238
|
}
|
|
1193
1239
|
}
|
|
1194
1240
|
function printPublicStatusGuidance(status, sessionState) {
|
|
1241
|
+
const accountLabel = sessionState === 'authorized'
|
|
1242
|
+
? 'connected'
|
|
1243
|
+
: sessionState === 'expired'
|
|
1244
|
+
? 'expired'
|
|
1245
|
+
: sessionState === 'unavailable'
|
|
1246
|
+
? 'unverified'
|
|
1247
|
+
: 'login required';
|
|
1248
|
+
console.log(`- account=${accountLabel}`);
|
|
1195
1249
|
if (sessionState === 'missing') {
|
|
1196
1250
|
console.log('- note=Local discovery is working. Sign in next to pair this device with your Forkit.dev account.');
|
|
1197
1251
|
console.log('- next=run forkit-connect login');
|
|
@@ -1227,7 +1281,9 @@ async function checkBackendSessionState(service) {
|
|
|
1227
1281
|
baseUrl: DEFAULT_BASE_URL,
|
|
1228
1282
|
sessionRef: sessionRefValue,
|
|
1229
1283
|
});
|
|
1230
|
-
const result = await api.getProfileAccess();
|
|
1284
|
+
const result = await withTimeout(api.getProfileAccess(), SESSION_STATE_CHECK_TIMEOUT_MS, null);
|
|
1285
|
+
if (!result)
|
|
1286
|
+
return 'unavailable';
|
|
1231
1287
|
if (result.ok)
|
|
1232
1288
|
return 'authorized';
|
|
1233
1289
|
if (result.status === 401 || result.status === 403)
|
|
@@ -1434,18 +1490,27 @@ async function run() {
|
|
|
1434
1490
|
const runPublicConnectInit = () => {
|
|
1435
1491
|
printConnectInit(service.initializeConnectIdentity());
|
|
1436
1492
|
};
|
|
1493
|
+
const withSmartInboxSnapshotCounts = (overview) => {
|
|
1494
|
+
const inbox = service.getSmartRegistrationInbox({
|
|
1495
|
+
preferSnapshot: true,
|
|
1496
|
+
refreshInBackground: false,
|
|
1497
|
+
});
|
|
1498
|
+
return {
|
|
1499
|
+
...overview,
|
|
1500
|
+
ready_to_connect_count: inbox.summary.ready_to_connect_count,
|
|
1501
|
+
needs_confirmation_count: inbox.summary.needs_confirmation_count,
|
|
1502
|
+
connected_count: inbox.summary.connected_count,
|
|
1503
|
+
next_recommended_action: inbox.summary.next_recommended_action,
|
|
1504
|
+
};
|
|
1505
|
+
};
|
|
1437
1506
|
const runPublicConnectStatus = async () => {
|
|
1438
1507
|
const sessionState = await checkBackendSessionState(service);
|
|
1508
|
+
const secureStorage = service.getCredentialStoreStatus();
|
|
1439
1509
|
if (service.readSessionRef()) {
|
|
1440
|
-
|
|
1441
|
-
await service.refreshEffectiveBinding();
|
|
1442
|
-
}
|
|
1443
|
-
catch {
|
|
1444
|
-
// Status remains useful with the last local binding snapshot.
|
|
1445
|
-
}
|
|
1510
|
+
await withTimeout(service.refreshEffectiveBinding(), STATUS_BINDING_TIMEOUT_MS, undefined);
|
|
1446
1511
|
}
|
|
1447
|
-
const
|
|
1448
|
-
const
|
|
1512
|
+
const accountTrusted = sessionState === 'authorized';
|
|
1513
|
+
const overview = withSmartInboxSnapshotCounts(service.getConnectStatusOverview({ includeInbox: false }));
|
|
1449
1514
|
const localScopeCached = Boolean(String(overview.workspace_id || '').trim() || String(overview.project_id || '').trim());
|
|
1450
1515
|
const displayOverview = accountTrusted
|
|
1451
1516
|
? overview
|
|
@@ -1466,23 +1531,45 @@ async function run() {
|
|
|
1466
1531
|
session_truth: accountTrusted ? 'account_verified_or_offline' : 'local_scope_cached_login_required',
|
|
1467
1532
|
local_scope_cached: !accountTrusted && localScopeCached,
|
|
1468
1533
|
binding_truth: accountTrusted ? 'account_binding_active' : 'account_login_required',
|
|
1534
|
+
secure_storage: {
|
|
1535
|
+
backend: secureStorage.backend,
|
|
1536
|
+
available: secureStorage.available,
|
|
1537
|
+
plaintext_fallback_active: secureStorage.plaintextFallbackActive,
|
|
1538
|
+
legacy_plaintext_file_present: secureStorage.legacyPlaintextFilePresent,
|
|
1539
|
+
detail: secureStorage.detail,
|
|
1540
|
+
},
|
|
1469
1541
|
});
|
|
1470
1542
|
return;
|
|
1471
1543
|
}
|
|
1472
1544
|
printPublicStatusOverview(displayOverview);
|
|
1473
|
-
console.log(`-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
console.log('- local_scope_cached=true');
|
|
1545
|
+
console.log(`- secure storage=${secureStorage.backend}`);
|
|
1546
|
+
if (!secureStorage.available || secureStorage.plaintextFallbackActive) {
|
|
1547
|
+
console.log(`- secure storage detail=${secureStorage.detail}`);
|
|
1477
1548
|
}
|
|
1478
1549
|
printPublicStatusGuidance(displayOverview, sessionState);
|
|
1479
1550
|
};
|
|
1480
|
-
const runPublicConnectInbox = () => {
|
|
1481
|
-
const
|
|
1551
|
+
const runPublicConnectInbox = async () => {
|
|
1552
|
+
const forceRefresh = hasFlag('--refresh');
|
|
1553
|
+
const inbox = service.getSmartRegistrationInbox({
|
|
1554
|
+
forceRefresh,
|
|
1555
|
+
preferSnapshot: !forceRefresh,
|
|
1556
|
+
refreshInBackground: !forceRefresh,
|
|
1557
|
+
});
|
|
1482
1558
|
if (hasFlag('--json')) {
|
|
1483
1559
|
printJson(inbox);
|
|
1484
1560
|
return;
|
|
1485
1561
|
}
|
|
1562
|
+
const freshness = inbox.summary.freshness_state ?? 'fresh';
|
|
1563
|
+
const ageSeconds = Number.isFinite(inbox.summary.snapshot_age_seconds)
|
|
1564
|
+
? Number(inbox.summary.snapshot_age_seconds)
|
|
1565
|
+
: 0;
|
|
1566
|
+
console.log(`[forkit-connect] Inbox snapshot freshness=${freshness} age_seconds=${ageSeconds}`);
|
|
1567
|
+
if (freshness === 'stale') {
|
|
1568
|
+
console.log('[forkit-connect] Snapshot is stale. Run `forkit-connect inbox --refresh` for immediate reconcile.');
|
|
1569
|
+
}
|
|
1570
|
+
else if (freshness === 'syncing') {
|
|
1571
|
+
console.log('[forkit-connect] Background reconcile is running; results will self-refresh after completion.');
|
|
1572
|
+
}
|
|
1486
1573
|
printSmartInbox(inbox);
|
|
1487
1574
|
};
|
|
1488
1575
|
const runPublicCollectedChanges = () => {
|
|
@@ -1571,10 +1658,11 @@ async function run() {
|
|
|
1571
1658
|
const runPublicLogout = () => {
|
|
1572
1659
|
const currentSessionRef = String(service.readSessionRef() || '').trim();
|
|
1573
1660
|
const hadEnvironmentSession = Boolean(String(process.env.FORKIT_CONNECT_SESSION_REF || '').trim());
|
|
1661
|
+
const logoutAt = new Date().toISOString();
|
|
1574
1662
|
delete process.env.FORKIT_CONNECT_SESSION_REF;
|
|
1575
1663
|
try {
|
|
1576
1664
|
service.setSessionRef(null);
|
|
1577
|
-
console.log(
|
|
1665
|
+
console.log(`[forkit-connect] Logged out at ${logoutAt}. Local discovery remains available on this device.`);
|
|
1578
1666
|
if (hadEnvironmentSession) {
|
|
1579
1667
|
console.log('[forkit-connect] The in-process fallback session was cleared for this run.');
|
|
1580
1668
|
}
|
|
@@ -1583,12 +1671,12 @@ async function run() {
|
|
|
1583
1671
|
catch (error) {
|
|
1584
1672
|
if (error instanceof credential_store_1.ConnectCredentialStoreError) {
|
|
1585
1673
|
if (currentSessionRef) {
|
|
1586
|
-
console.log(
|
|
1674
|
+
console.log(`[forkit-connect] Logged out from the current interactive run at ${logoutAt}.`);
|
|
1587
1675
|
console.log('[forkit-connect] If you previously exported a session in your shell, remove it there with:');
|
|
1588
1676
|
console.log('unset FORKIT_CONNECT_SESSION_REF');
|
|
1589
1677
|
return;
|
|
1590
1678
|
}
|
|
1591
|
-
console.log(
|
|
1679
|
+
console.log(`[forkit-connect] No stored session found at ${logoutAt}.`);
|
|
1592
1680
|
console.log('[forkit-connect] If you previously exported a session in this shell, remove it with:');
|
|
1593
1681
|
console.log('unset FORKIT_CONNECT_SESSION_REF');
|
|
1594
1682
|
return;
|
|
@@ -1644,6 +1732,7 @@ async function run() {
|
|
|
1644
1732
|
try {
|
|
1645
1733
|
service.setSessionRef(polled.body.connect_access_token);
|
|
1646
1734
|
await service.refreshEffectiveBinding();
|
|
1735
|
+
await withTimeout(service.prewarmSmartRegistrationInbox(), 1800, undefined);
|
|
1647
1736
|
const displayName = getSessionDisplayName(polled.body.connect_access_token);
|
|
1648
1737
|
console.log(displayName
|
|
1649
1738
|
? `[forkit-connect] Login approved. Welcome, ${displayName}. Session credentials stored securely.`
|
|
@@ -1653,6 +1742,7 @@ async function run() {
|
|
|
1653
1742
|
catch (error) {
|
|
1654
1743
|
if (error instanceof credential_store_1.ConnectCredentialStoreError) {
|
|
1655
1744
|
await activateEnvironmentSessionFallback(polled.body.connect_access_token);
|
|
1745
|
+
await withTimeout(service.prewarmSmartRegistrationInbox(), 1800, undefined);
|
|
1656
1746
|
printSessionExportFallback(polled.body.connect_access_token);
|
|
1657
1747
|
const displayName = getSessionDisplayName(polled.body.connect_access_token);
|
|
1658
1748
|
console.log(displayName
|
|
@@ -1709,16 +1799,11 @@ async function run() {
|
|
|
1709
1799
|
const buildWorkspaceStatusPayload = async () => {
|
|
1710
1800
|
const sessionState = await checkBackendSessionState(service);
|
|
1711
1801
|
if (service.readSessionRef()) {
|
|
1712
|
-
|
|
1713
|
-
await service.refreshEffectiveBinding();
|
|
1714
|
-
}
|
|
1715
|
-
catch {
|
|
1716
|
-
// Keep local scope view available.
|
|
1717
|
-
}
|
|
1802
|
+
await withTimeout(service.refreshEffectiveBinding(), STATUS_BINDING_TIMEOUT_MS, undefined);
|
|
1718
1803
|
}
|
|
1719
|
-
const overview = service.getConnectStatusOverview();
|
|
1720
1804
|
const operatingMode = resolveOperatingMode(service);
|
|
1721
|
-
const accountTrusted = sessionState === 'authorized'
|
|
1805
|
+
const accountTrusted = sessionState === 'authorized';
|
|
1806
|
+
const overview = withSmartInboxSnapshotCounts(service.getConnectStatusOverview({ includeInbox: false }));
|
|
1722
1807
|
const localScopeCached = Boolean(String(overview.workspace_id || '').trim() || String(overview.project_id || '').trim());
|
|
1723
1808
|
return {
|
|
1724
1809
|
session_state: sessionState,
|
|
@@ -1739,7 +1824,9 @@ async function run() {
|
|
|
1739
1824
|
};
|
|
1740
1825
|
};
|
|
1741
1826
|
const renderInteractiveStatusScreen = async (sessionState) => {
|
|
1742
|
-
const accountLimits =
|
|
1827
|
+
const accountLimits = node_process_1.stdin.isTTY && node_process_1.stdout.isTTY
|
|
1828
|
+
? await withTimeout(loadCliAccountLimits().catch(() => null), INTERACTIVE_ACCOUNT_LIMITS_TIMEOUT_MS, null)
|
|
1829
|
+
: null;
|
|
1743
1830
|
const displayName = accountLimits?.displayName ?? getSessionDisplayName(service.readSessionRef());
|
|
1744
1831
|
renderInteractiveScreen('Forkit Connect', {
|
|
1745
1832
|
subtitle: displayName ? `Welcome back, ${displayName}` : 'Interactive overview',
|
|
@@ -1788,18 +1875,8 @@ async function run() {
|
|
|
1788
1875
|
// Only run discovery and binding refresh when the user has a session.
|
|
1789
1876
|
// Pre-login, these calls add significant startup latency with no user benefit.
|
|
1790
1877
|
if (service.readSessionRef()) {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
}
|
|
1794
|
-
catch {
|
|
1795
|
-
// Keep the interactive shell usable even if local discovery has transient issues.
|
|
1796
|
-
}
|
|
1797
|
-
try {
|
|
1798
|
-
await service.refreshEffectiveBinding();
|
|
1799
|
-
}
|
|
1800
|
-
catch {
|
|
1801
|
-
// Use the latest local binding snapshot when backend refresh is unavailable.
|
|
1802
|
-
}
|
|
1878
|
+
await withTimeout(service.runDiscoveryCycle(), INTERACTIVE_DISCOVERY_TIMEOUT_MS, undefined);
|
|
1879
|
+
await withTimeout(service.refreshEffectiveBinding(), INTERACTIVE_BINDING_TIMEOUT_MS, undefined);
|
|
1803
1880
|
}
|
|
1804
1881
|
};
|
|
1805
1882
|
const runInteractiveWorkspaceMenu = async () => {
|
|
@@ -2394,7 +2471,10 @@ async function run() {
|
|
|
2394
2471
|
}
|
|
2395
2472
|
};
|
|
2396
2473
|
const buildInteractiveRegisterCandidates = () => {
|
|
2397
|
-
const inbox = service.
|
|
2474
|
+
const inbox = service.getSmartRegistrationInbox({
|
|
2475
|
+
preferSnapshot: true,
|
|
2476
|
+
refreshInBackground: true,
|
|
2477
|
+
});
|
|
2398
2478
|
const candidates = new Map();
|
|
2399
2479
|
const groups = ['needs_confirmation', 'ready_to_connect'];
|
|
2400
2480
|
const state = service.getStateStore().readState();
|
|
@@ -2654,7 +2734,10 @@ async function run() {
|
|
|
2654
2734
|
await runInteractiveAutoRefresh();
|
|
2655
2735
|
const groupOrder = ['needs_confirmation', 'ready_to_connect', 'connected', 'ignored'];
|
|
2656
2736
|
while (true) {
|
|
2657
|
-
const inbox = service.
|
|
2737
|
+
const inbox = service.getSmartRegistrationInbox({
|
|
2738
|
+
preferSnapshot: true,
|
|
2739
|
+
refreshInBackground: true,
|
|
2740
|
+
});
|
|
2658
2741
|
const entries = groupOrder.flatMap((group) => inbox.groups[group].map((item) => ({ group, item })));
|
|
2659
2742
|
if (entries.length === 0) {
|
|
2660
2743
|
renderInteractiveScreen('Smart Registration Inbox', {
|
|
@@ -2669,7 +2752,7 @@ async function run() {
|
|
|
2669
2752
|
return;
|
|
2670
2753
|
}
|
|
2671
2754
|
renderInteractiveScreen('Smart Registration Inbox', {
|
|
2672
|
-
subtitle: `Generated at ${inbox.summary.generated_at}`,
|
|
2755
|
+
subtitle: `Generated at ${inbox.summary.generated_at} · freshness=${inbox.summary.freshness_state ?? 'fresh'}`,
|
|
2673
2756
|
sections: buildInteractiveInboxSections(inbox),
|
|
2674
2757
|
footerLines: ['Choose an inbox item below.'],
|
|
2675
2758
|
});
|
|
@@ -2704,11 +2787,15 @@ async function run() {
|
|
|
2704
2787
|
}
|
|
2705
2788
|
};
|
|
2706
2789
|
const runInteractiveStart = async () => {
|
|
2790
|
+
if (!node_process_1.stdin.isTTY || !node_process_1.stdout.isTTY) {
|
|
2791
|
+
const sessionState = service.readSessionRef() ? 'unavailable' : 'missing';
|
|
2792
|
+
await renderInteractiveStatusScreen(sessionState);
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2707
2795
|
while (true) {
|
|
2708
2796
|
process.exitCode = 0;
|
|
2709
|
-
await runInteractiveAutoRefresh();
|
|
2710
2797
|
const sessionState = await checkBackendSessionState(service);
|
|
2711
|
-
const authenticated = sessionState === 'authorized'
|
|
2798
|
+
const authenticated = sessionState === 'authorized';
|
|
2712
2799
|
if (!authenticated) {
|
|
2713
2800
|
await renderInteractiveStatusScreen(sessionState);
|
|
2714
2801
|
const selected = await promptSelection('Choose an action', [
|
|
@@ -2765,6 +2852,8 @@ async function run() {
|
|
|
2765
2852
|
{ value: 'exit', label: 'Exit' },
|
|
2766
2853
|
]);
|
|
2767
2854
|
if (!selected || selected === 'exit') {
|
|
2855
|
+
const exitAt = new Date().toISOString();
|
|
2856
|
+
console.log(`[forkit-connect] Quit at ${exitAt}. Interactive work stopped for this device; use logout if you want to clear the session reference.`);
|
|
2768
2857
|
return;
|
|
2769
2858
|
}
|
|
2770
2859
|
if (selected === 'status') {
|
|
@@ -2964,6 +3053,10 @@ async function run() {
|
|
|
2964
3053
|
runtimeSignalsUsed: null,
|
|
2965
3054
|
runtimeSignalsLimit: fallbackPlan.runtimeSignalsPerMonth,
|
|
2966
3055
|
runtimeSignalsRemaining: null,
|
|
3056
|
+
governedPassportsUsed: null,
|
|
3057
|
+
governedPassportsLimit: fallbackPlan.maxGovernedPassports,
|
|
3058
|
+
governedPassportsRemaining: null,
|
|
3059
|
+
publishedModelPassports: [],
|
|
2967
3060
|
};
|
|
2968
3061
|
cachedCliAccountLimits = fallback;
|
|
2969
3062
|
cachedCliAccountLimitsAt = now;
|
|
@@ -3027,6 +3120,21 @@ async function run() {
|
|
|
3027
3120
|
const runtimeSignalsUsed = typeof summaryPayload?.usage?.runtimeSignals === 'number'
|
|
3028
3121
|
? summaryPayload.usage.runtimeSignals
|
|
3029
3122
|
: null;
|
|
3123
|
+
const governedPassportsUsed = typeof summaryPayload?.usage?.passports === 'number'
|
|
3124
|
+
? summaryPayload.usage.passports
|
|
3125
|
+
: passports.length;
|
|
3126
|
+
const governedPassportsLimit = summaryPayload?.planCapabilities?.governance?.maxGovernedPassports
|
|
3127
|
+
?? summaryPayload?.entitlements?.maxGovernedPassports
|
|
3128
|
+
?? fallback.maxGovernedPassports;
|
|
3129
|
+
const publishedModelPassports = passports
|
|
3130
|
+
.filter((passport) => String(passport.passportType || passport.passport_type || passport.type || '').toLowerCase() === 'model')
|
|
3131
|
+
.map((passport) => ({
|
|
3132
|
+
gaid: String(passport.gaid || '').trim(),
|
|
3133
|
+
name: String(passport.name || 'Unnamed model passport').trim(),
|
|
3134
|
+
workspaceId: String(passport.workspaceId || passport.workspace_id || '').trim() || null,
|
|
3135
|
+
projectId: String(passport.projectId || passport.project_id || '').trim() || null,
|
|
3136
|
+
}))
|
|
3137
|
+
.filter((passport) => passport.gaid);
|
|
3030
3138
|
const resolved = {
|
|
3031
3139
|
displayName,
|
|
3032
3140
|
planKey,
|
|
@@ -3047,6 +3155,10 @@ async function run() {
|
|
|
3047
3155
|
runtimeSignalsUsed,
|
|
3048
3156
|
runtimeSignalsLimit,
|
|
3049
3157
|
runtimeSignalsRemaining: remainingFromLimit(runtimeSignalsLimit, runtimeSignalsUsed),
|
|
3158
|
+
governedPassportsUsed,
|
|
3159
|
+
governedPassportsLimit,
|
|
3160
|
+
governedPassportsRemaining: remainingFromLimit(governedPassportsLimit, governedPassportsUsed),
|
|
3161
|
+
publishedModelPassports,
|
|
3050
3162
|
};
|
|
3051
3163
|
cachedCliAccountLimits = resolved;
|
|
3052
3164
|
cachedCliAccountLimitsAt = now;
|
|
@@ -3298,41 +3410,91 @@ async function run() {
|
|
|
3298
3410
|
return 'Draft creation is not active for this binding yet. Complete Connect approval or update consent on Forkit.dev first.';
|
|
3299
3411
|
return raw;
|
|
3300
3412
|
};
|
|
3301
|
-
const
|
|
3413
|
+
const normalizeRegisterSuccessMessage = (action) => {
|
|
3414
|
+
if (action === 'already_bound')
|
|
3415
|
+
return 'Model is already connected to an existing passport.';
|
|
3416
|
+
if (action === 'already_pending')
|
|
3417
|
+
return 'Model already has a pending draft. No duplicate draft created.';
|
|
3418
|
+
if (action === 'passport_registered')
|
|
3419
|
+
return 'Passport published successfully.';
|
|
3420
|
+
if (action === 'draft_created')
|
|
3421
|
+
return 'Draft created successfully.';
|
|
3422
|
+
if (action === 'draft_queued')
|
|
3423
|
+
return 'Draft queued locally and will sync when backend access is available.';
|
|
3424
|
+
return action;
|
|
3425
|
+
};
|
|
3426
|
+
const accountLimits = await loadCliAccountLimits().catch(() => null);
|
|
3427
|
+
const governedPassportCapacityFull = accountLimits
|
|
3428
|
+
? isCapacityExhausted(accountLimits.governedPassportsLimit, accountLimits.governedPassportsRemaining)
|
|
3429
|
+
: false;
|
|
3430
|
+
const buildCapacityPayload = (requestedModel) => ({
|
|
3431
|
+
ok: false,
|
|
3432
|
+
code: 'GOVERNED_PASSPORT_CAPACITY_REACHED',
|
|
3433
|
+
requested_model: requestedModel ?? null,
|
|
3434
|
+
plan: accountLimits?.planName ?? operatingMode.tier ?? null,
|
|
3435
|
+
governed_passports_used: accountLimits?.governedPassportsUsed ?? null,
|
|
3436
|
+
governed_passports_limit: accountLimits?.governedPassportsLimit ?? null,
|
|
3437
|
+
message: 'This account has reached governed passport capacity. Forkit Connect will not create more governed drafts by default because publishing would be blocked.',
|
|
3438
|
+
next_actions: [
|
|
3439
|
+
'Use an existing published model passport for Runtime Signals C2 or run-log testing.',
|
|
3440
|
+
'Free capacity or upgrade before publishing another governed model.',
|
|
3441
|
+
'Use --draft-only if you intentionally want to save another draft for later review.',
|
|
3442
|
+
],
|
|
3443
|
+
existing_model_passports: accountLimits?.publishedModelPassports.slice(0, 8) ?? [],
|
|
3444
|
+
});
|
|
3445
|
+
const runRegisterOne = async (targetModelSelector, displayNameHint) => {
|
|
3302
3446
|
try {
|
|
3303
|
-
const result = await service.connectDetectedModel(
|
|
3447
|
+
const result = await service.connectDetectedModel(targetModelSelector);
|
|
3304
3448
|
return {
|
|
3305
3449
|
ok: true,
|
|
3306
3450
|
model: result.model.model,
|
|
3451
|
+
selector: result.model.discoveryHash,
|
|
3307
3452
|
draftId: result.draftId ?? null,
|
|
3308
3453
|
gaid: result.gaid ?? null,
|
|
3309
|
-
message: result.action,
|
|
3454
|
+
message: normalizeRegisterSuccessMessage(result.action),
|
|
3455
|
+
action: result.action,
|
|
3310
3456
|
};
|
|
3311
3457
|
}
|
|
3312
3458
|
catch (error) {
|
|
3313
3459
|
const rawMessage = error instanceof Error ? error.message : 'register_failed';
|
|
3314
3460
|
return {
|
|
3315
3461
|
ok: false,
|
|
3316
|
-
model:
|
|
3462
|
+
model: displayNameHint || targetModelSelector,
|
|
3463
|
+
selector: targetModelSelector,
|
|
3317
3464
|
message: normalizeRegisterErrorMessage(rawMessage),
|
|
3318
3465
|
};
|
|
3319
3466
|
}
|
|
3320
3467
|
};
|
|
3321
3468
|
if (hasFlag('--all-ready')) {
|
|
3469
|
+
if (governedPassportCapacityFull && !hasFlag('--draft-only')) {
|
|
3470
|
+
printJson({
|
|
3471
|
+
...buildCapacityPayload(null),
|
|
3472
|
+
attempted: 0,
|
|
3473
|
+
skipped: 'all-ready',
|
|
3474
|
+
});
|
|
3475
|
+
process.exitCode = 2;
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3322
3478
|
const inbox = service.buildSmartRegistrationInbox();
|
|
3323
|
-
const
|
|
3479
|
+
const readyModelCandidates = inbox.groups.ready_to_connect
|
|
3324
3480
|
.filter((item) => item.item_type === 'model' && item.recommended_action === 'create_passport_draft')
|
|
3325
|
-
.map((item) =>
|
|
3326
|
-
|
|
3481
|
+
.map((item) => ({
|
|
3482
|
+
selector: extractInboxItemSelector(item),
|
|
3483
|
+
model: item.display_name,
|
|
3484
|
+
}))
|
|
3485
|
+
.filter((item) => String(item.selector || '').trim())
|
|
3486
|
+
.sort((left, right) => left.model.localeCompare(right.model) || left.selector.localeCompare(right.selector));
|
|
3487
|
+
if (readyModelCandidates.length === 0) {
|
|
3327
3488
|
console.log('No ready local models need registration.');
|
|
3328
3489
|
return;
|
|
3329
3490
|
}
|
|
3330
3491
|
const results = [];
|
|
3331
|
-
for (const item of
|
|
3332
|
-
results.push(await runRegisterOne(item));
|
|
3492
|
+
for (const item of readyModelCandidates) {
|
|
3493
|
+
results.push(await runRegisterOne(item.selector, item.model));
|
|
3333
3494
|
}
|
|
3334
3495
|
printJson({
|
|
3335
|
-
attempted:
|
|
3496
|
+
attempted: readyModelCandidates.length,
|
|
3497
|
+
selectors: readyModelCandidates.map((item) => item.selector),
|
|
3336
3498
|
results,
|
|
3337
3499
|
});
|
|
3338
3500
|
if (results.some((item) => !item.ok)) {
|
|
@@ -3345,18 +3507,41 @@ async function run() {
|
|
|
3345
3507
|
const readyModels = inbox.groups.ready_to_connect
|
|
3346
3508
|
.filter((item) => item.item_type === 'model')
|
|
3347
3509
|
.map((item) => item.display_name);
|
|
3510
|
+
const readyModelSelectors = inbox.groups.ready_to_connect
|
|
3511
|
+
.filter((item) => item.item_type === 'model')
|
|
3512
|
+
.map((item) => ({
|
|
3513
|
+
model: item.display_name,
|
|
3514
|
+
selector: extractInboxItemSelector(item),
|
|
3515
|
+
}))
|
|
3516
|
+
.filter((item) => String(item.selector || '').trim())
|
|
3517
|
+
.sort((left, right) => left.model.localeCompare(right.model) || left.selector.localeCompare(right.selector));
|
|
3348
3518
|
printJson({
|
|
3349
3519
|
operating_mode: operatingMode.mode,
|
|
3350
3520
|
tier: operatingMode.tier,
|
|
3351
3521
|
workspace_id: boundWorkspaceId,
|
|
3352
3522
|
project_id: boundProjectId,
|
|
3523
|
+
capacity: accountLimits ? {
|
|
3524
|
+
governed_passports_used: accountLimits.governedPassportsUsed,
|
|
3525
|
+
governed_passports_limit: accountLimits.governedPassportsLimit,
|
|
3526
|
+
governed_passports_remaining: accountLimits.governedPassportsRemaining,
|
|
3527
|
+
capacity_full: governedPassportCapacityFull,
|
|
3528
|
+
} : null,
|
|
3353
3529
|
ready_models: readyModels,
|
|
3530
|
+
ready_model_selectors: readyModelSelectors,
|
|
3531
|
+
existing_model_passports: accountLimits?.publishedModelPassports.slice(0, 8) ?? [],
|
|
3354
3532
|
next: readyModels.length
|
|
3355
|
-
?
|
|
3533
|
+
? governedPassportCapacityFull
|
|
3534
|
+
? 'Capacity is full. Use an existing passport for C2/run-log testing, free capacity, upgrade, or pass --draft-only if you intentionally want another draft.'
|
|
3535
|
+
: 'Run forkit-connect register --model "<name>" or forkit-connect register --all-ready'
|
|
3356
3536
|
: 'Run forkit-connect scan first or review forkit-connect inbox',
|
|
3357
3537
|
});
|
|
3358
3538
|
return;
|
|
3359
3539
|
}
|
|
3540
|
+
if (governedPassportCapacityFull && !hasFlag('--draft-only')) {
|
|
3541
|
+
printJson(buildCapacityPayload(modelName));
|
|
3542
|
+
process.exitCode = 2;
|
|
3543
|
+
return;
|
|
3544
|
+
}
|
|
3360
3545
|
const result = await runRegisterOne(modelName);
|
|
3361
3546
|
printJson(result);
|
|
3362
3547
|
if (!result.ok) {
|
|
@@ -3408,7 +3593,7 @@ async function run() {
|
|
|
3408
3593
|
return;
|
|
3409
3594
|
}
|
|
3410
3595
|
if (command === 'inbox') {
|
|
3411
|
-
runPublicConnectInbox();
|
|
3596
|
+
await runPublicConnectInbox();
|
|
3412
3597
|
return;
|
|
3413
3598
|
}
|
|
3414
3599
|
if (command === 'start') {
|
|
@@ -3423,6 +3608,30 @@ async function run() {
|
|
|
3423
3608
|
await runPublicSync();
|
|
3424
3609
|
return;
|
|
3425
3610
|
}
|
|
3611
|
+
if (command === 'runtime') {
|
|
3612
|
+
const subcommand = args[1] || 'review';
|
|
3613
|
+
if (subcommand === 'review') {
|
|
3614
|
+
const summary = service.getRuntimePassportReview();
|
|
3615
|
+
if (hasFlag('--json')) {
|
|
3616
|
+
printJson(summary);
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
printRuntimeReview(summary);
|
|
3620
|
+
return;
|
|
3621
|
+
}
|
|
3622
|
+
if (subcommand === 'status') {
|
|
3623
|
+
const status = service.getRuntimePassportStatus();
|
|
3624
|
+
if (hasFlag('--json')) {
|
|
3625
|
+
printJson(status);
|
|
3626
|
+
return;
|
|
3627
|
+
}
|
|
3628
|
+
printRuntimeStatus(status);
|
|
3629
|
+
return;
|
|
3630
|
+
}
|
|
3631
|
+
console.error('Usage: forkit-connect runtime <review|status>');
|
|
3632
|
+
process.exitCode = 2;
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3426
3635
|
if (command === 'workspace') {
|
|
3427
3636
|
const subcommand = args[1] || 'status';
|
|
3428
3637
|
try {
|
|
@@ -3505,7 +3714,7 @@ async function run() {
|
|
|
3505
3714
|
return;
|
|
3506
3715
|
}
|
|
3507
3716
|
if (subcommand === 'inbox') {
|
|
3508
|
-
runPublicConnectInbox();
|
|
3717
|
+
await runPublicConnectInbox();
|
|
3509
3718
|
return;
|
|
3510
3719
|
}
|
|
3511
3720
|
if (subcommand === 'services') {
|
|
@@ -3530,12 +3739,23 @@ async function run() {
|
|
|
3530
3739
|
return;
|
|
3531
3740
|
}
|
|
3532
3741
|
if (subcommand === 'runtime') {
|
|
3533
|
-
|
|
3534
|
-
|
|
3742
|
+
const runtimeSubcommand = args[2] || 'review';
|
|
3743
|
+
if (runtimeSubcommand === 'review') {
|
|
3744
|
+
const summary = service.getRuntimePassportReview();
|
|
3745
|
+
if (hasFlag('--json')) {
|
|
3746
|
+
printJson(summary);
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
printRuntimeReview(summary);
|
|
3535
3750
|
return;
|
|
3536
3751
|
}
|
|
3537
|
-
if (
|
|
3538
|
-
|
|
3752
|
+
if (runtimeSubcommand === 'status') {
|
|
3753
|
+
const status = service.getRuntimePassportStatus();
|
|
3754
|
+
if (hasFlag('--json')) {
|
|
3755
|
+
printJson(status);
|
|
3756
|
+
return;
|
|
3757
|
+
}
|
|
3758
|
+
printRuntimeStatus(status);
|
|
3539
3759
|
return;
|
|
3540
3760
|
}
|
|
3541
3761
|
console.error('Usage: forkit-connect connect runtime <review|status>');
|
|
@@ -3650,6 +3870,54 @@ async function run() {
|
|
|
3650
3870
|
}, null, 2));
|
|
3651
3871
|
return;
|
|
3652
3872
|
}
|
|
3873
|
+
if (subcommand === 'run-log' && args[2] === 'emit') {
|
|
3874
|
+
const gaid = getArg('--gaid') ?? getArg('--heartbeat-gaid');
|
|
3875
|
+
const apiKey = getArg('--api-key') ?? getArg('--heartbeat-key') ?? process.env.FORKIT_RUNTIME_SIGNAL_API_KEY ?? null;
|
|
3876
|
+
const provider = getArg('--provider');
|
|
3877
|
+
const runModel = getArg('--model');
|
|
3878
|
+
const serviceName = getArg('--service-name') ?? getArg('--name');
|
|
3879
|
+
if (!gaid || !provider || !runModel || !serviceName) {
|
|
3880
|
+
console.error('[forkit-connect] c2 run-log emit requires --gaid, --provider, --model, and --service-name.');
|
|
3881
|
+
console.error('[forkit-connect] Provide an API key with --api-key, --heartbeat-key, FORKIT_RUNTIME_SIGNAL_API_KEY, or a stored c2 set-key entry.');
|
|
3882
|
+
process.exitCode = 1;
|
|
3883
|
+
return;
|
|
3884
|
+
}
|
|
3885
|
+
const result = await service.emitRuntimeRunLog({
|
|
3886
|
+
gaid,
|
|
3887
|
+
apiKey,
|
|
3888
|
+
provider,
|
|
3889
|
+
model: runModel,
|
|
3890
|
+
serviceName,
|
|
3891
|
+
serviceKind: getArg('--service-kind') ?? 'custom',
|
|
3892
|
+
runId: getArg('--run-id'),
|
|
3893
|
+
externalRunId: getArg('--external-run-id'),
|
|
3894
|
+
status: getArg('--status') ?? 'completed',
|
|
3895
|
+
startedAt: getArg('--started-at'),
|
|
3896
|
+
endedAt: getArg('--ended-at'),
|
|
3897
|
+
promptTokens: getNumericArg('--prompt-tokens'),
|
|
3898
|
+
completionTokens: getNumericArg('--completion-tokens'),
|
|
3899
|
+
cachedPromptTokens: getNumericArg('--cached-prompt-tokens'),
|
|
3900
|
+
reasoningTokens: getNumericArg('--reasoning-tokens'),
|
|
3901
|
+
latencyMs: getNumericArg('--latency-ms'),
|
|
3902
|
+
estimatedCostCents: getNumericArg('--estimated-cost-cents'),
|
|
3903
|
+
currency: getArg('--currency') ?? 'USD',
|
|
3904
|
+
summary: getArg('--summary'),
|
|
3905
|
+
});
|
|
3906
|
+
if (!result.ok) {
|
|
3907
|
+
console.error(`[forkit-connect] Runtime run log emit failed (${result.status || 'local'}).`);
|
|
3908
|
+
if (result.body) {
|
|
3909
|
+
console.error(typeof result.body === 'string' ? result.body : JSON.stringify(result.body));
|
|
3910
|
+
}
|
|
3911
|
+
process.exitCode = 2;
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3914
|
+
console.log(JSON.stringify({
|
|
3915
|
+
accepted: true,
|
|
3916
|
+
status: result.status,
|
|
3917
|
+
response: result.body,
|
|
3918
|
+
}, null, 2));
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3653
3921
|
if (subcommand === 'sync') {
|
|
3654
3922
|
const syncResult = await service.flushC2LifecycleEvents({
|
|
3655
3923
|
runtimeSignalApiKey: getArg('--heartbeat-key'),
|
|
@@ -4441,6 +4709,29 @@ async function run() {
|
|
|
4441
4709
|
process.exitCode = 2;
|
|
4442
4710
|
return;
|
|
4443
4711
|
}
|
|
4712
|
+
const accountLimits = await loadCliAccountLimits().catch(() => null);
|
|
4713
|
+
const governedPassportCapacityFull = accountLimits
|
|
4714
|
+
? isCapacityExhausted(accountLimits.governedPassportsLimit, accountLimits.governedPassportsRemaining)
|
|
4715
|
+
: false;
|
|
4716
|
+
if (governedPassportCapacityFull && !hasFlag('--draft-only')) {
|
|
4717
|
+
printJson({
|
|
4718
|
+
ok: false,
|
|
4719
|
+
code: 'GOVERNED_PASSPORT_CAPACITY_REACHED',
|
|
4720
|
+
requested_model: modelName,
|
|
4721
|
+
plan: accountLimits?.planName ?? null,
|
|
4722
|
+
governed_passports_used: accountLimits?.governedPassportsUsed ?? null,
|
|
4723
|
+
governed_passports_limit: accountLimits?.governedPassportsLimit ?? null,
|
|
4724
|
+
message: 'This account has reached governed passport capacity. Forkit Connect will not create another governed draft by default because publishing would be blocked.',
|
|
4725
|
+
next_actions: [
|
|
4726
|
+
'Use an existing published model passport for Runtime Signals C2 or run-log testing.',
|
|
4727
|
+
'Free capacity or upgrade before publishing another governed model.',
|
|
4728
|
+
'Use --draft-only if you intentionally want to save another draft for later review.',
|
|
4729
|
+
],
|
|
4730
|
+
existing_model_passports: accountLimits?.publishedModelPassports.slice(0, 8) ?? [],
|
|
4731
|
+
});
|
|
4732
|
+
process.exitCode = 2;
|
|
4733
|
+
return;
|
|
4734
|
+
}
|
|
4444
4735
|
const modelKey = `${model.model}#${model.digest}`;
|
|
4445
4736
|
const existingBinding = (state.model_bindings || []).find((binding) => binding.modelKey === modelKey && binding.status !== 'ignored');
|
|
4446
4737
|
if (existingBinding?.draftId) {
|