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