forkit-connect 0.1.5 → 0.1.6
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/QUICKSTART.md +10 -1
- package/README.md +12 -1
- package/dist/cli.js +763 -37
- package/dist/launcher.js +471 -73
- package/dist/resource-meter.d.ts +15 -0
- package/dist/resource-meter.js +60 -0
- package/dist/v1/api.d.ts +41 -0
- package/dist/v1/api.js +57 -0
- package/dist/v1/daemon.js +9 -0
- package/dist/v1/repo-discovery.d.ts +42 -0
- package/dist/v1/repo-discovery.js +206 -0
- package/dist/v1/runtime-activity.d.ts +55 -0
- package/dist/v1/runtime-activity.js +388 -0
- package/dist/v1/runtime-context.d.ts +18 -0
- package/dist/v1/runtime-context.js +96 -0
- package/dist/v1/runtime-editor-activity.d.ts +47 -0
- package/dist/v1/runtime-editor-activity.js +821 -0
- package/dist/v1/runtime-observation-runner.d.ts +49 -0
- package/dist/v1/runtime-observation-runner.js +508 -0
- package/dist/v1/runtime-observer.d.ts +50 -0
- package/dist/v1/runtime-observer.js +867 -0
- package/dist/v1/runtime-registration.d.ts +58 -0
- package/dist/v1/runtime-registration.js +319 -0
- package/dist/v1/service.d.ts +44 -1
- package/dist/v1/service.js +165 -10
- package/dist/v1/state.d.ts +4 -1
- package/dist/v1/state.js +28 -0
- package/dist/v1/types.d.ts +14 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
5
9
|
const node_readline_1 = require("node:readline");
|
|
6
10
|
const promises_1 = require("node:readline/promises");
|
|
7
11
|
const node_process_1 = require("node:process");
|
|
@@ -9,9 +13,15 @@ const daemon_1 = require("./v1/daemon");
|
|
|
9
13
|
const service_1 = require("./v1/service");
|
|
10
14
|
const discovery_1 = require("./v1/discovery");
|
|
11
15
|
const heartbeat_1 = require("./v1/heartbeat");
|
|
16
|
+
const runtime_activity_1 = require("./v1/runtime-activity");
|
|
17
|
+
const runtime_editor_activity_1 = require("./v1/runtime-editor-activity");
|
|
18
|
+
const runtime_observation_runner_1 = require("./v1/runtime-observation-runner");
|
|
19
|
+
const runtime_context_1 = require("./v1/runtime-context");
|
|
12
20
|
const update_1 = require("./v1/update");
|
|
21
|
+
const repo_discovery_1 = require("./v1/repo-discovery");
|
|
13
22
|
const api_1 = require("./v1/api");
|
|
14
23
|
const credential_store_1 = require("./v1/credential-store");
|
|
24
|
+
const runtime_registration_1 = require("./v1/runtime-registration");
|
|
15
25
|
const DEFAULT_BASE_URL = process.env.FORKIT_API_URL ?? process.env.FORKIT_BASE_URL ?? 'https://www.forkit.dev';
|
|
16
26
|
const TRAIN_EVENT_TYPES = [
|
|
17
27
|
'training_started',
|
|
@@ -78,13 +88,14 @@ const PUBLIC_COMMANDS = [
|
|
|
78
88
|
['inbox', 'Review the Smart Registration Inbox'],
|
|
79
89
|
['sync', 'Flush queued drafts and lifecycle metadata'],
|
|
80
90
|
['workspace', 'List, select, or inspect optional governed workspace/project scope'],
|
|
81
|
-
['runtime', '
|
|
91
|
+
['runtime', 'Register or review governed runtimes for the current repo/worktree'],
|
|
82
92
|
['register', 'Register ready local models into the current scope'],
|
|
83
93
|
['ignore', 'Ignore one detected local model'],
|
|
84
94
|
['doctor', 'Run local environment diagnostics'],
|
|
85
95
|
];
|
|
86
96
|
const ADVANCED_COMMAND_GROUPS = [
|
|
87
97
|
['connect', 'Model connection, runtime review, and handoff utilities'],
|
|
98
|
+
['runtime', 'Repo-scoped runtime observation and journaling utilities'],
|
|
88
99
|
['review', 'Summarize discovered but unconnected items'],
|
|
89
100
|
['doctor', 'Run local environment diagnostics'],
|
|
90
101
|
['workspaces', 'List accessible workspaces after sign-in'],
|
|
@@ -108,7 +119,7 @@ const ADVANCED_COMMAND_GROUPS = [
|
|
|
108
119
|
function usage() {
|
|
109
120
|
console.log('Usage: forkit-connect <init|login|logout|status|changes|start|stop|scan|inbox|sync|workspace|runtime|register|ignore|doctor> [options]');
|
|
110
121
|
console.log(' forkit-connect workspace <list|select|create|status> [options]');
|
|
111
|
-
console.log(' forkit-connect runtime <review|status> [options]');
|
|
122
|
+
console.log(' forkit-connect runtime <register|review|status> [options]');
|
|
112
123
|
console.log('Public commands:');
|
|
113
124
|
for (const [command, description] of PUBLIC_COMMANDS) {
|
|
114
125
|
console.log(` ${command.padEnd(10)} ${description}`);
|
|
@@ -118,7 +129,15 @@ function usage() {
|
|
|
118
129
|
console.log(' --project-name <text> Project name used by workspace create/select');
|
|
119
130
|
console.log(' --project-description <text> Optional project description used by workspace create/select');
|
|
120
131
|
console.log(' --json Print machine-readable output when supported');
|
|
132
|
+
console.log(' --name <value> Runtime or metric name depending on command');
|
|
121
133
|
console.log(' --model <value> Model name used by register');
|
|
134
|
+
console.log(' --entrypoint <path> Relative project path used by runtime register');
|
|
135
|
+
console.log(' --source-url <url> Source URL override used by runtime register');
|
|
136
|
+
console.log(' --subject-type <type> Runtime subject type used by runtime register');
|
|
137
|
+
console.log(' --deployment-environment <value> Runtime environment used by runtime register');
|
|
138
|
+
console.log(' --show-key Print the raw runtime API key when one is created');
|
|
139
|
+
console.log(' --no-api-key Skip automatic runtime API key creation');
|
|
140
|
+
console.log(' --dry-run Show the inferred runtime payload without creating it');
|
|
122
141
|
console.log(' --all-ready Register every ready local model in the current scope');
|
|
123
142
|
console.log(' --ignore-model <name> Ignore detected model by name with --ignore-digest');
|
|
124
143
|
console.log(' --ignore-digest <sha> Digest used with --ignore-model');
|
|
@@ -133,6 +152,8 @@ function advancedUsage() {
|
|
|
133
152
|
console.log('Advanced commands:');
|
|
134
153
|
console.log(' forkit-connect connect <modelNameOrDiscoveryHash>');
|
|
135
154
|
console.log(' forkit-connect connect <start|init|status|inbox|services|permissions|handoff|evolution review|runtime review|runtime status>');
|
|
155
|
+
console.log(' forkit-connect runtime target <add|list|remove|status> [options]');
|
|
156
|
+
console.log(' forkit-connect runtime observe --gaid <gaid> [options]');
|
|
136
157
|
console.log(' forkit-connect <review|workspaces|projects|bind|drafts|publish|bound|heartbeat|update-check|config|daemon|pulse|c2|train|agent|tray|notify> [options]');
|
|
137
158
|
for (const [command, description] of ADVANCED_COMMAND_GROUPS) {
|
|
138
159
|
console.log(` ${command.padEnd(12)} ${description}`);
|
|
@@ -157,13 +178,15 @@ function advancedUsage() {
|
|
|
157
178
|
console.log(' --heartbeat-gaid <gaid> Queue heartbeat runtime signal event for GAID');
|
|
158
179
|
console.log(' --heartbeat-key <key> API key used for heartbeat runtime signal event');
|
|
159
180
|
console.log(' Also used by: c2 set-key (stores key + backfills events)');
|
|
160
|
-
console.log(' --gaid <gaid>
|
|
161
|
-
console.log(' --api-key <key> Runtime signal API key used by c2 run-log emit');
|
|
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)');
|
|
162
184
|
console.log(' --provider <name> Provider label used by c2 run-log emit');
|
|
163
185
|
console.log(' --service-name <name> Service/agent/workflow label used by c2 run-log emit');
|
|
164
186
|
console.log(' --prompt-tokens <n> Prompt tokens used by c2 run-log emit');
|
|
165
187
|
console.log(' --completion-tokens <n> Completion tokens used by c2 run-log emit');
|
|
166
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');
|
|
167
190
|
console.log(' --actor-labels <csv> Actor labels used by c2 run-log emit (for example: Codex,Claude)');
|
|
168
191
|
console.log(' --task-labels <csv> Task or chat labels used by c2 run-log emit');
|
|
169
192
|
console.log(' --folder-labels <csv> Relative folder labels used by c2 run-log emit');
|
|
@@ -172,6 +195,7 @@ function advancedUsage() {
|
|
|
172
195
|
console.log(' --cpu-percent <n> Scoped CPU percentage used by c2 run-log emit');
|
|
173
196
|
console.log(' --memory-mb <n> Scoped memory usage used by c2 run-log emit');
|
|
174
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');
|
|
175
199
|
console.log(' --draft-only Allow draft creation even when governed publish capacity is full');
|
|
176
200
|
}
|
|
177
201
|
function showUsage() {
|
|
@@ -198,6 +222,15 @@ function getNumericArg(flag) {
|
|
|
198
222
|
const parsed = Number(value);
|
|
199
223
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
200
224
|
}
|
|
225
|
+
function getListArg(flag) {
|
|
226
|
+
const value = getArg(flag);
|
|
227
|
+
if (!value)
|
|
228
|
+
return [];
|
|
229
|
+
return value
|
|
230
|
+
.split(',')
|
|
231
|
+
.map((entry) => entry.trim())
|
|
232
|
+
.filter(Boolean);
|
|
233
|
+
}
|
|
201
234
|
function isHelpCommand(command) {
|
|
202
235
|
return command === 'help' || command === '--help' || command === '-h' || command === '--advanced-help';
|
|
203
236
|
}
|
|
@@ -345,6 +378,132 @@ function formatRemainingLimit(limit, used, singular, plural = `${singular}s`) {
|
|
|
345
378
|
function isCapacityExhausted(limit, remaining) {
|
|
346
379
|
return Number.isFinite(limit) && Number(remaining) <= 0;
|
|
347
380
|
}
|
|
381
|
+
function shouldCollectCodexThreads(input) {
|
|
382
|
+
const haystack = [
|
|
383
|
+
input.provider,
|
|
384
|
+
input.serviceName,
|
|
385
|
+
...input.actorLabels,
|
|
386
|
+
]
|
|
387
|
+
.join(' ')
|
|
388
|
+
.trim()
|
|
389
|
+
.toLowerCase();
|
|
390
|
+
return haystack.includes('codex');
|
|
391
|
+
}
|
|
392
|
+
function shouldCollectCopilotActivity(input) {
|
|
393
|
+
const haystack = [
|
|
394
|
+
input.provider,
|
|
395
|
+
input.serviceName,
|
|
396
|
+
...input.actorLabels,
|
|
397
|
+
]
|
|
398
|
+
.join(' ')
|
|
399
|
+
.trim()
|
|
400
|
+
.toLowerCase();
|
|
401
|
+
return haystack.includes('copilot');
|
|
402
|
+
}
|
|
403
|
+
function shouldCollectClaudeActivity(input) {
|
|
404
|
+
const haystack = [
|
|
405
|
+
input.provider,
|
|
406
|
+
input.serviceName,
|
|
407
|
+
...input.actorLabels,
|
|
408
|
+
]
|
|
409
|
+
.join(' ')
|
|
410
|
+
.trim()
|
|
411
|
+
.toLowerCase();
|
|
412
|
+
return haystack.includes('claude');
|
|
413
|
+
}
|
|
414
|
+
function buildCliRuntimeRunActivityEnvelope(input) {
|
|
415
|
+
const cwd = process.cwd();
|
|
416
|
+
const repo = (0, repo_discovery_1.detectRepository)(cwd);
|
|
417
|
+
const changedFiles = (0, repo_discovery_1.listRepositoryChangedFiles)(cwd);
|
|
418
|
+
const runtimeContext = repo.isGitRepo && repo.rootPath
|
|
419
|
+
? ((0, runtime_context_1.readRuntimeContext)(repo.rootPath) ?? (0, runtime_context_1.buildRuntimeContextRecord)({
|
|
420
|
+
clientName: input.clientName || null,
|
|
421
|
+
defaultLabel: node_path_1.default.basename(repo.rootPath),
|
|
422
|
+
}))
|
|
423
|
+
: null;
|
|
424
|
+
const promptTokens = Number.isFinite(input.promptTokens) ? Math.max(0, Math.trunc(Number(input.promptTokens))) : 0;
|
|
425
|
+
const completionTokens = Number.isFinite(input.completionTokens) ? Math.max(0, Math.trunc(Number(input.completionTokens))) : 0;
|
|
426
|
+
const cachedPromptTokens = Number.isFinite(input.cachedPromptTokens) ? Math.max(0, Math.trunc(Number(input.cachedPromptTokens))) : undefined;
|
|
427
|
+
const reasoningTokens = Number.isFinite(input.reasoningTokens) ? Math.max(0, Math.trunc(Number(input.reasoningTokens))) : undefined;
|
|
428
|
+
const totalTokens = promptTokens + completionTokens;
|
|
429
|
+
const actorLabels = getListArg('--actor-labels');
|
|
430
|
+
const explicitTaskLabels = getListArg('--task-labels');
|
|
431
|
+
const explicitModelLabels = getListArg('--model-labels');
|
|
432
|
+
const codexAutoThreads = explicitTaskLabels.length === 0 && shouldCollectCodexThreads({
|
|
433
|
+
provider: input.provider,
|
|
434
|
+
serviceName: input.serviceName,
|
|
435
|
+
actorLabels,
|
|
436
|
+
})
|
|
437
|
+
? (0, runtime_editor_activity_1.collectCodexEditorThreads)(repo)
|
|
438
|
+
: [];
|
|
439
|
+
const copilotAutoActivity = (explicitTaskLabels.length === 0 || explicitModelLabels.length === 0) && shouldCollectCopilotActivity({
|
|
440
|
+
provider: input.provider,
|
|
441
|
+
serviceName: input.serviceName,
|
|
442
|
+
actorLabels,
|
|
443
|
+
})
|
|
444
|
+
? (0, runtime_editor_activity_1.collectCopilotEditorActivity)(repo)
|
|
445
|
+
: { threads: [], modelLabels: [] };
|
|
446
|
+
const claudeAutoActivity = (explicitTaskLabels.length === 0 || explicitModelLabels.length === 0) && shouldCollectClaudeActivity({
|
|
447
|
+
provider: input.provider,
|
|
448
|
+
serviceName: input.serviceName,
|
|
449
|
+
actorLabels,
|
|
450
|
+
})
|
|
451
|
+
? (0, runtime_editor_activity_1.collectClaudeProjectActivity)(repo)
|
|
452
|
+
: { threads: [], modelLabels: [] };
|
|
453
|
+
const autoThreads = [
|
|
454
|
+
...codexAutoThreads,
|
|
455
|
+
...(explicitTaskLabels.length === 0 ? copilotAutoActivity.threads : []),
|
|
456
|
+
...(explicitTaskLabels.length === 0 ? claudeAutoActivity.threads : []),
|
|
457
|
+
];
|
|
458
|
+
const taskLabels = explicitTaskLabels.length > 0 ? explicitTaskLabels : autoThreads.map((thread) => thread.threadLabel);
|
|
459
|
+
const modelLabels = explicitModelLabels.length > 0
|
|
460
|
+
? explicitModelLabels
|
|
461
|
+
: [...copilotAutoActivity.modelLabels, ...claudeAutoActivity.modelLabels];
|
|
462
|
+
const clientName = input.clientName || runtimeContext?.clientName || null;
|
|
463
|
+
return (0, runtime_activity_1.buildRuntimeActivityMetadata)({
|
|
464
|
+
cwd,
|
|
465
|
+
repo,
|
|
466
|
+
changedFiles,
|
|
467
|
+
provider: input.provider,
|
|
468
|
+
model: input.model,
|
|
469
|
+
serviceName: input.serviceName,
|
|
470
|
+
serviceKind: input.serviceKind ?? null,
|
|
471
|
+
commandLabel: input.commandLabel ?? null,
|
|
472
|
+
status: input.status ?? null,
|
|
473
|
+
startedAt: input.startedAt || new Date().toISOString(),
|
|
474
|
+
endedAt: input.endedAt || null,
|
|
475
|
+
summary: input.summary ?? null,
|
|
476
|
+
clientName,
|
|
477
|
+
stableSubjectId: runtimeContext?.subjectId,
|
|
478
|
+
stableWorktreeId: runtimeContext?.worktreeId,
|
|
479
|
+
actorLabels,
|
|
480
|
+
taskLabels,
|
|
481
|
+
activityThreads: autoThreads.map((thread) => ({
|
|
482
|
+
title: thread.threadLabel,
|
|
483
|
+
taskId: thread.threadId ?? null,
|
|
484
|
+
openedAt: thread.createdAt ?? null,
|
|
485
|
+
lastActiveAt: thread.lastActiveAt ?? null,
|
|
486
|
+
provider: thread.provider,
|
|
487
|
+
actorLabel: thread.actorLabel,
|
|
488
|
+
workspaceHost: thread.workspaceHost,
|
|
489
|
+
folderScope: thread.folderScope ?? null,
|
|
490
|
+
})),
|
|
491
|
+
folderLabels: getListArg('--folder-labels'),
|
|
492
|
+
fileLabels: getListArg('--file-labels'),
|
|
493
|
+
modelLabels,
|
|
494
|
+
promptTokens,
|
|
495
|
+
completionTokens,
|
|
496
|
+
cachedPromptTokens,
|
|
497
|
+
reasoningTokens,
|
|
498
|
+
totalTokens,
|
|
499
|
+
latencyMs: input.latencyMs,
|
|
500
|
+
resources: {
|
|
501
|
+
cpuPercent: getNumericArg('--cpu-percent'),
|
|
502
|
+
memoryMb: getNumericArg('--memory-mb'),
|
|
503
|
+
vramMb: getNumericArg('--vram-mb'),
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
}
|
|
348
507
|
function formatWorkspaceAccessLine(workspace) {
|
|
349
508
|
const workspaceId = String(workspace.id || workspace.gaid || workspace.passportGaid || 'unknown');
|
|
350
509
|
return `- ${summarizeWorkspaceLabel(workspace)} | id=${workspaceId}`;
|
|
@@ -481,6 +640,72 @@ function readPassportList(body) {
|
|
|
481
640
|
}
|
|
482
641
|
return passports.filter((item) => !!item && typeof item === 'object' && !Array.isArray(item));
|
|
483
642
|
}
|
|
643
|
+
function isRuntimeListResponse(body) {
|
|
644
|
+
return Boolean(body) && typeof body === 'object' && !Array.isArray(body);
|
|
645
|
+
}
|
|
646
|
+
function isRuntimeCreateResponse(body) {
|
|
647
|
+
return Boolean(body) && typeof body === 'object' && !Array.isArray(body);
|
|
648
|
+
}
|
|
649
|
+
function isApiKeyCreateResponse(body) {
|
|
650
|
+
return Boolean(body) && typeof body === 'object' && !Array.isArray(body);
|
|
651
|
+
}
|
|
652
|
+
function readRuntimeList(body) {
|
|
653
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
654
|
+
return [];
|
|
655
|
+
}
|
|
656
|
+
const runtimes = body.runtimes;
|
|
657
|
+
if (!Array.isArray(runtimes)) {
|
|
658
|
+
return [];
|
|
659
|
+
}
|
|
660
|
+
return runtimes.filter((item) => !!item && typeof item === 'object' && !Array.isArray(item));
|
|
661
|
+
}
|
|
662
|
+
function readRuntimeIdentityMetadata(runtime) {
|
|
663
|
+
const metadata = runtime.metadata;
|
|
664
|
+
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
const runtimeIdentity = metadata.runtimeIdentity;
|
|
668
|
+
return runtimeIdentity && typeof runtimeIdentity === 'object' && !Array.isArray(runtimeIdentity)
|
|
669
|
+
? runtimeIdentity
|
|
670
|
+
: null;
|
|
671
|
+
}
|
|
672
|
+
function findExistingRuntimeForIdentity(runtimes, input) {
|
|
673
|
+
const targetWorkspaceId = input.workspaceId.trim();
|
|
674
|
+
const targetProjectId = input.projectId.trim();
|
|
675
|
+
for (const runtime of runtimes) {
|
|
676
|
+
const runtimeWorkspaceId = String(runtime.workspaceId || runtime.workspace_id || '').trim();
|
|
677
|
+
const runtimeProjectId = String(runtime.projectId || runtime.project_id || '').trim();
|
|
678
|
+
if (runtimeWorkspaceId !== targetWorkspaceId || runtimeProjectId !== targetProjectId) {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
const runtimeIdentity = readRuntimeIdentityMetadata(runtime);
|
|
682
|
+
if (!runtimeIdentity) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const repoRootFingerprint = String(runtimeIdentity.repoRootFingerprint || '').trim();
|
|
686
|
+
const worktreeId = String(runtimeIdentity.worktreeId || '').trim();
|
|
687
|
+
const entrypoint = String(runtimeIdentity.entrypointRelativePath || '.').trim() || '.';
|
|
688
|
+
if (repoRootFingerprint === input.repoRootFingerprint
|
|
689
|
+
&& worktreeId === input.worktreeId
|
|
690
|
+
&& entrypoint === input.entrypointRelativePath) {
|
|
691
|
+
return runtime;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
function formatRuntimeToolingHintList(value) {
|
|
697
|
+
if (!Array.isArray(value))
|
|
698
|
+
return 'No tooling hints recorded';
|
|
699
|
+
const labels = value
|
|
700
|
+
.map((entry) => {
|
|
701
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
702
|
+
return null;
|
|
703
|
+
const label = String(entry.label || entry.key || '').trim();
|
|
704
|
+
return label || null;
|
|
705
|
+
})
|
|
706
|
+
.filter((entry) => Boolean(entry));
|
|
707
|
+
return labels.length ? labels.join(', ') : 'No tooling hints recorded';
|
|
708
|
+
}
|
|
484
709
|
function formatPassportLine(passport) {
|
|
485
710
|
const gaid = String(passport.gaid || passport.id || 'unknown');
|
|
486
711
|
const name = String(passport.name || 'Unnamed passport');
|
|
@@ -1156,6 +1381,25 @@ function printRuntimeStatus(status) {
|
|
|
1156
1381
|
console.log(`- latest event=${status.latest_runtime_lifecycle_event}`);
|
|
1157
1382
|
}
|
|
1158
1383
|
}
|
|
1384
|
+
function printRuntimeTargetList(service, options) {
|
|
1385
|
+
const targets = service.listRuntimeObserverTargets();
|
|
1386
|
+
const daemon = (0, daemon_1.getDaemonHealth)(service);
|
|
1387
|
+
if (options?.json) {
|
|
1388
|
+
printJson({
|
|
1389
|
+
daemon,
|
|
1390
|
+
totalTargets: targets.length,
|
|
1391
|
+
targets: targets.map((target) => ({
|
|
1392
|
+
...target,
|
|
1393
|
+
runtimeSignalKeyConfigured: service.hasRuntimeSignalKey(target.runtime_gaid),
|
|
1394
|
+
})),
|
|
1395
|
+
});
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
console.log(`[forkit-connect] Runtime targets=${targets.length} | daemon=${daemon.running ? `running:${daemon.pid}` : 'stopped'}`);
|
|
1399
|
+
for (const target of targets) {
|
|
1400
|
+
console.log(`- gaid=${shortId(target.runtime_gaid)} | repo=${target.repo_root} | client=${target.client_name || 'n/a'} | key=${service.hasRuntimeSignalKey(target.runtime_gaid) ? 'ready' : 'missing'} | last_emit=${target.last_emitted_at || 'never'}${target.last_error ? ` | error=${target.last_error}` : ''}`);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1159
1403
|
function formatSmartInboxActionValue(action, itemType, connectableModelName) {
|
|
1160
1404
|
switch (action) {
|
|
1161
1405
|
case 'connect_existing_passport':
|
|
@@ -3172,6 +3416,221 @@ async function run() {
|
|
|
3172
3416
|
cachedCliAccountLimitsAt = now;
|
|
3173
3417
|
return resolved;
|
|
3174
3418
|
};
|
|
3419
|
+
const runRuntimeRegister = async () => {
|
|
3420
|
+
const storedSessionRef = String(service.readSessionRef() || '').trim();
|
|
3421
|
+
if (!storedSessionRef) {
|
|
3422
|
+
console.error('Not authenticated. Run forkit-connect login first.');
|
|
3423
|
+
process.exitCode = 2;
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
const sessionState = await checkBackendSessionState(service);
|
|
3427
|
+
if (sessionState === 'expired') {
|
|
3428
|
+
console.error('Session expired. Run forkit-connect login again.');
|
|
3429
|
+
process.exitCode = 2;
|
|
3430
|
+
return;
|
|
3431
|
+
}
|
|
3432
|
+
if (sessionState === 'missing') {
|
|
3433
|
+
console.error('Not authenticated. Run forkit-connect login first.');
|
|
3434
|
+
process.exitCode = 2;
|
|
3435
|
+
return;
|
|
3436
|
+
}
|
|
3437
|
+
if (sessionState === 'unavailable') {
|
|
3438
|
+
console.error('Forkit.dev is unavailable right now. Try again when the backend is reachable.');
|
|
3439
|
+
process.exitCode = 2;
|
|
3440
|
+
return;
|
|
3441
|
+
}
|
|
3442
|
+
const state = service.getStateStore().readState();
|
|
3443
|
+
const boundWorkspaceId = String(workspaceId || state.workspace_binding.workspaceId || '').trim();
|
|
3444
|
+
const boundProjectId = String(projectId || state.project_binding.projectId || '').trim();
|
|
3445
|
+
if (!boundWorkspaceId || !boundProjectId) {
|
|
3446
|
+
console.error('Runtime registration needs a governed workspace and project. Run forkit-connect workspace select first or pass --workspace and --project.');
|
|
3447
|
+
process.exitCode = 2;
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
let runtimeDraft;
|
|
3451
|
+
try {
|
|
3452
|
+
runtimeDraft = (0, runtime_registration_1.inspectRuntimeProject)({
|
|
3453
|
+
cwd: process.cwd(),
|
|
3454
|
+
name: getArg('--name'),
|
|
3455
|
+
description: descriptionArg,
|
|
3456
|
+
sourceUrl: getArg('--source-url'),
|
|
3457
|
+
entrypointRelativePath: getArg('--entrypoint'),
|
|
3458
|
+
subjectType: (getArg('--subject-type') || null),
|
|
3459
|
+
deploymentEnvironment: getArg('--deployment-environment'),
|
|
3460
|
+
connectRuntimeId: state.runtime_identity.runtimeId,
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3463
|
+
catch (error) {
|
|
3464
|
+
console.error(error instanceof Error ? error.message : 'Runtime inspection failed.');
|
|
3465
|
+
process.exitCode = 2;
|
|
3466
|
+
return;
|
|
3467
|
+
}
|
|
3468
|
+
const resultPayload = {
|
|
3469
|
+
name: runtimeDraft.name,
|
|
3470
|
+
description: runtimeDraft.description,
|
|
3471
|
+
sourceUrl: runtimeDraft.sourceUrl,
|
|
3472
|
+
workspaceId: boundWorkspaceId,
|
|
3473
|
+
projectId: boundProjectId,
|
|
3474
|
+
metadata: runtimeDraft.metadata,
|
|
3475
|
+
runtimeIdentity: runtimeDraft.runtimeIdentity,
|
|
3476
|
+
};
|
|
3477
|
+
if (hasFlag('--dry-run')) {
|
|
3478
|
+
printJson({
|
|
3479
|
+
ok: true,
|
|
3480
|
+
action: 'dry_run',
|
|
3481
|
+
payload: resultPayload,
|
|
3482
|
+
inferred_project: {
|
|
3483
|
+
repo_root: runtimeDraft.snapshot.repoRootName,
|
|
3484
|
+
branch: runtimeDraft.snapshot.branch,
|
|
3485
|
+
commit: runtimeDraft.snapshot.commit,
|
|
3486
|
+
dirty: runtimeDraft.snapshot.dirty,
|
|
3487
|
+
entrypoint: runtimeDraft.snapshot.relativeEntrypoint,
|
|
3488
|
+
tooling: runtimeDraft.snapshot.detectedTooling,
|
|
3489
|
+
},
|
|
3490
|
+
});
|
|
3491
|
+
return;
|
|
3492
|
+
}
|
|
3493
|
+
const api = buildWorkspaceApi();
|
|
3494
|
+
const runtimeListResult = await api.getRuntimesMine(100);
|
|
3495
|
+
if (!runtimeListResult.ok || !isRuntimeListResponse(runtimeListResult.body)) {
|
|
3496
|
+
console.error(`[forkit-connect] Runtime lookup failed (${runtimeListResult.status}).`);
|
|
3497
|
+
if (runtimeListResult.body) {
|
|
3498
|
+
console.error(typeof runtimeListResult.body === 'string' ? runtimeListResult.body : JSON.stringify(runtimeListResult.body));
|
|
3499
|
+
}
|
|
3500
|
+
process.exitCode = 2;
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
const runtimes = readRuntimeList(runtimeListResult.body);
|
|
3504
|
+
const existingRuntime = findExistingRuntimeForIdentity(runtimes, {
|
|
3505
|
+
workspaceId: boundWorkspaceId,
|
|
3506
|
+
projectId: boundProjectId,
|
|
3507
|
+
repoRootFingerprint: runtimeDraft.runtimeIdentity.repoRootFingerprint,
|
|
3508
|
+
worktreeId: runtimeDraft.runtimeIdentity.worktreeId,
|
|
3509
|
+
entrypointRelativePath: runtimeDraft.runtimeIdentity.entrypointRelativePath,
|
|
3510
|
+
});
|
|
3511
|
+
let action = existingRuntime ? 'existing' : 'created';
|
|
3512
|
+
let runtimeRecord = existingRuntime;
|
|
3513
|
+
if (!runtimeRecord) {
|
|
3514
|
+
const createResult = await api.createRuntime(resultPayload);
|
|
3515
|
+
if (!createResult.ok || !isRuntimeCreateResponse(createResult.body) || !createResult.body.runtime) {
|
|
3516
|
+
const errorBody = createResult.body && typeof createResult.body === 'object'
|
|
3517
|
+
? createResult.body
|
|
3518
|
+
: null;
|
|
3519
|
+
const message = typeof errorBody?.error === 'string'
|
|
3520
|
+
? errorBody.error
|
|
3521
|
+
: `[forkit-connect] Runtime registration failed (${createResult.status}).`;
|
|
3522
|
+
console.error(message);
|
|
3523
|
+
if (createResult.body) {
|
|
3524
|
+
console.error(typeof createResult.body === 'string' ? createResult.body : JSON.stringify(createResult.body));
|
|
3525
|
+
}
|
|
3526
|
+
process.exitCode = 2;
|
|
3527
|
+
return;
|
|
3528
|
+
}
|
|
3529
|
+
runtimeRecord = createResult.body.runtime;
|
|
3530
|
+
}
|
|
3531
|
+
const runtimeGaid = String(runtimeRecord?.gaid || '').trim();
|
|
3532
|
+
if (!runtimeGaid) {
|
|
3533
|
+
console.error('Runtime registration returned no GAID.');
|
|
3534
|
+
process.exitCode = 2;
|
|
3535
|
+
return;
|
|
3536
|
+
}
|
|
3537
|
+
const shouldCreateApiKey = !hasFlag('--no-api-key') && !service.hasStoredRuntimeSignalKey(runtimeGaid);
|
|
3538
|
+
let apiKeyStored = false;
|
|
3539
|
+
let apiKeyCreated = false;
|
|
3540
|
+
let keyId = null;
|
|
3541
|
+
let rawKeyToDisplay = null;
|
|
3542
|
+
let keyStorageWarning = null;
|
|
3543
|
+
if (shouldCreateApiKey) {
|
|
3544
|
+
const apiKeyResult = await api.createRuntimeApiKey(runtimeGaid, 'Forkit Connect runtime');
|
|
3545
|
+
if (!apiKeyResult.ok || !isApiKeyCreateResponse(apiKeyResult.body) || !apiKeyResult.body.rawKey) {
|
|
3546
|
+
keyStorageWarning = `Runtime created, but runtime key provisioning failed (${apiKeyResult.status}).`;
|
|
3547
|
+
if (apiKeyResult.body) {
|
|
3548
|
+
const detail = typeof apiKeyResult.body === 'string' ? apiKeyResult.body : JSON.stringify(apiKeyResult.body);
|
|
3549
|
+
keyStorageWarning = `${keyStorageWarning} ${detail}`;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
else {
|
|
3553
|
+
apiKeyCreated = true;
|
|
3554
|
+
keyId = String(apiKeyResult.body.keyId || '').trim() || null;
|
|
3555
|
+
const rawKey = String(apiKeyResult.body.rawKey || '').trim();
|
|
3556
|
+
try {
|
|
3557
|
+
const configured = service.configureRuntimeSignalKey({
|
|
3558
|
+
gaid: runtimeGaid,
|
|
3559
|
+
apiKey: rawKey,
|
|
3560
|
+
});
|
|
3561
|
+
apiKeyStored = configured.stored;
|
|
3562
|
+
if (hasFlag('--show-key')) {
|
|
3563
|
+
rawKeyToDisplay = rawKey;
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
catch (error) {
|
|
3567
|
+
if (error instanceof credential_store_1.ConnectCredentialStoreError) {
|
|
3568
|
+
keyStorageWarning = error.message;
|
|
3569
|
+
rawKeyToDisplay = rawKey;
|
|
3570
|
+
}
|
|
3571
|
+
else {
|
|
3572
|
+
throw error;
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
else if (!hasFlag('--no-api-key')) {
|
|
3578
|
+
apiKeyStored = true;
|
|
3579
|
+
}
|
|
3580
|
+
if (hasFlag('--json')) {
|
|
3581
|
+
printJson({
|
|
3582
|
+
ok: true,
|
|
3583
|
+
action,
|
|
3584
|
+
runtime: runtimeRecord,
|
|
3585
|
+
logging_ready: hasFlag('--no-api-key') ? false : apiKeyStored,
|
|
3586
|
+
api_key: {
|
|
3587
|
+
created: apiKeyCreated,
|
|
3588
|
+
stored: apiKeyStored,
|
|
3589
|
+
keyId,
|
|
3590
|
+
...(rawKeyToDisplay ? { rawKey: rawKeyToDisplay } : {}),
|
|
3591
|
+
warning: keyStorageWarning,
|
|
3592
|
+
},
|
|
3593
|
+
inferred_project: {
|
|
3594
|
+
repo_root: runtimeDraft.snapshot.repoRootName,
|
|
3595
|
+
branch: runtimeDraft.snapshot.branch,
|
|
3596
|
+
commit: runtimeDraft.snapshot.commit,
|
|
3597
|
+
dirty: runtimeDraft.snapshot.dirty,
|
|
3598
|
+
entrypoint: runtimeDraft.snapshot.relativeEntrypoint,
|
|
3599
|
+
tooling: runtimeDraft.snapshot.detectedTooling,
|
|
3600
|
+
},
|
|
3601
|
+
});
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
console.log(action === 'created' ? 'Runtime registered.' : 'Runtime already exists. Reusing governed runtime.');
|
|
3605
|
+
console.log(`Runtime: ${runtimeDraft.name}`);
|
|
3606
|
+
console.log(`GAID: ${runtimeGaid}`);
|
|
3607
|
+
console.log(`Scope: workspace=${boundWorkspaceId} | project=${boundProjectId}`);
|
|
3608
|
+
console.log(`Project: ${runtimeDraft.snapshot.repoRootName} | branch=${runtimeDraft.snapshot.branch || 'detached'} | commit=${String(runtimeDraft.snapshot.commit || 'unknown').slice(0, 12)} | dirty=${String(runtimeDraft.snapshot.dirty)}`);
|
|
3609
|
+
console.log(`Entrypoint: ${runtimeDraft.snapshot.relativeEntrypoint}`);
|
|
3610
|
+
console.log(`Tooling: ${formatRuntimeToolingHintList(runtimeDraft.snapshot.detectedTooling)}`);
|
|
3611
|
+
if (runtimeDraft.sourceUrl) {
|
|
3612
|
+
console.log(`Source URL: ${runtimeDraft.sourceUrl}`);
|
|
3613
|
+
}
|
|
3614
|
+
if (hasFlag('--no-api-key')) {
|
|
3615
|
+
console.log('Runtime key: skipped by request (--no-api-key).');
|
|
3616
|
+
return;
|
|
3617
|
+
}
|
|
3618
|
+
if (apiKeyStored) {
|
|
3619
|
+
console.log(apiKeyCreated
|
|
3620
|
+
? `Runtime key stored securely${keyId ? ` (keyId=${keyId})` : ''}.`
|
|
3621
|
+
: 'Runtime key already stored locally for this runtime.');
|
|
3622
|
+
}
|
|
3623
|
+
if (keyStorageWarning) {
|
|
3624
|
+
console.log(`Runtime key warning: ${keyStorageWarning}`);
|
|
3625
|
+
}
|
|
3626
|
+
if (rawKeyToDisplay) {
|
|
3627
|
+
console.log('Runtime API key (shown once):');
|
|
3628
|
+
console.log(rawKeyToDisplay);
|
|
3629
|
+
}
|
|
3630
|
+
if (!apiKeyStored && !rawKeyToDisplay) {
|
|
3631
|
+
console.log('Runtime logging is not ready yet. Create or attach a runtime key from Forkit.dev.');
|
|
3632
|
+
}
|
|
3633
|
+
};
|
|
3175
3634
|
const createProjectInWorkspace = async (api, selectedWorkspaceId, options) => {
|
|
3176
3635
|
let nextProjectName = projectNameArg;
|
|
3177
3636
|
let nextProjectDescription = projectDescriptionArg;
|
|
@@ -3616,30 +4075,6 @@ async function run() {
|
|
|
3616
4075
|
await runPublicSync();
|
|
3617
4076
|
return;
|
|
3618
4077
|
}
|
|
3619
|
-
if (command === 'runtime') {
|
|
3620
|
-
const subcommand = args[1] || 'review';
|
|
3621
|
-
if (subcommand === 'review') {
|
|
3622
|
-
const summary = service.getRuntimePassportReview();
|
|
3623
|
-
if (hasFlag('--json')) {
|
|
3624
|
-
printJson(summary);
|
|
3625
|
-
return;
|
|
3626
|
-
}
|
|
3627
|
-
printRuntimeReview(summary);
|
|
3628
|
-
return;
|
|
3629
|
-
}
|
|
3630
|
-
if (subcommand === 'status') {
|
|
3631
|
-
const status = service.getRuntimePassportStatus();
|
|
3632
|
-
if (hasFlag('--json')) {
|
|
3633
|
-
printJson(status);
|
|
3634
|
-
return;
|
|
3635
|
-
}
|
|
3636
|
-
printRuntimeStatus(status);
|
|
3637
|
-
return;
|
|
3638
|
-
}
|
|
3639
|
-
console.error('Usage: forkit-connect runtime <review|status>');
|
|
3640
|
-
process.exitCode = 2;
|
|
3641
|
-
return;
|
|
3642
|
-
}
|
|
3643
4078
|
if (command === 'workspace') {
|
|
3644
4079
|
const subcommand = args[1] || 'status';
|
|
3645
4080
|
try {
|
|
@@ -3693,6 +4128,269 @@ async function run() {
|
|
|
3693
4128
|
process.exitCode = 2;
|
|
3694
4129
|
return;
|
|
3695
4130
|
}
|
|
4131
|
+
if (command === 'runtime') {
|
|
4132
|
+
const subcommand = args[1];
|
|
4133
|
+
const nestedCommand = args[2];
|
|
4134
|
+
if (isHelpCommand(subcommand) || (subcommand === 'register' && isHelpCommand(nestedCommand))) {
|
|
4135
|
+
showUsage();
|
|
4136
|
+
return;
|
|
4137
|
+
}
|
|
4138
|
+
if ((subcommand === 'target' || subcommand === 'observe') && isHelpCommand(nestedCommand)) {
|
|
4139
|
+
advancedUsage();
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
if (subcommand === 'review') {
|
|
4143
|
+
const summary = service.getRuntimePassportReview();
|
|
4144
|
+
if (hasFlag('--json')) {
|
|
4145
|
+
printJson(summary);
|
|
4146
|
+
return;
|
|
4147
|
+
}
|
|
4148
|
+
printRuntimeReview(summary);
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
4151
|
+
if (subcommand === 'status') {
|
|
4152
|
+
const status = service.getRuntimePassportStatus();
|
|
4153
|
+
if (hasFlag('--json')) {
|
|
4154
|
+
printJson(status);
|
|
4155
|
+
return;
|
|
4156
|
+
}
|
|
4157
|
+
printRuntimeStatus(status);
|
|
4158
|
+
return;
|
|
4159
|
+
}
|
|
4160
|
+
if (subcommand === 'register' || !subcommand) {
|
|
4161
|
+
await runRuntimeRegister();
|
|
4162
|
+
return;
|
|
4163
|
+
}
|
|
4164
|
+
if (subcommand === 'target') {
|
|
4165
|
+
const action = args[2];
|
|
4166
|
+
if (action === 'list' || action === 'status') {
|
|
4167
|
+
printRuntimeTargetList(service, { json: hasFlag('--json') });
|
|
4168
|
+
return;
|
|
4169
|
+
}
|
|
4170
|
+
if (action === 'remove') {
|
|
4171
|
+
const gaid = getArg('--gaid') ?? getArg('--heartbeat-gaid');
|
|
4172
|
+
if (!gaid) {
|
|
4173
|
+
console.error('[forkit-connect] runtime target remove requires --gaid.');
|
|
4174
|
+
process.exitCode = 1;
|
|
4175
|
+
return;
|
|
4176
|
+
}
|
|
4177
|
+
const removed = service.removeRuntimeObserverTarget(gaid);
|
|
4178
|
+
if (!removed) {
|
|
4179
|
+
console.error('[forkit-connect] Runtime target not found for that GAID.');
|
|
4180
|
+
process.exitCode = 2;
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
if (hasFlag('--json')) {
|
|
4184
|
+
printJson({ removed: true, gaid });
|
|
4185
|
+
return;
|
|
4186
|
+
}
|
|
4187
|
+
console.log(`Removed runtime target ${gaid}.`);
|
|
4188
|
+
return;
|
|
4189
|
+
}
|
|
4190
|
+
if (action === 'add') {
|
|
4191
|
+
const gaid = getArg('--gaid') ?? getArg('--heartbeat-gaid');
|
|
4192
|
+
if (!gaid) {
|
|
4193
|
+
console.error('[forkit-connect] runtime target add requires --gaid.');
|
|
4194
|
+
process.exitCode = 1;
|
|
4195
|
+
return;
|
|
4196
|
+
}
|
|
4197
|
+
const repoRootArg = getArg('--repo-root');
|
|
4198
|
+
const cwd = repoRootArg ? node_path_1.default.resolve(repoRootArg) : process.cwd();
|
|
4199
|
+
const repo = (0, repo_discovery_1.detectRepository)(cwd);
|
|
4200
|
+
const rootPath = repo.rootPath || repo.cwd;
|
|
4201
|
+
const existingTarget = service.listRuntimeObserverTargets().find((item) => item.runtime_gaid === gaid) ?? null;
|
|
4202
|
+
const runtimeContext = repo.isGitRepo && repo.rootPath
|
|
4203
|
+
? ((0, runtime_context_1.readRuntimeContext)(repo.rootPath) ?? (0, runtime_context_1.buildRuntimeContextRecord)({
|
|
4204
|
+
existing: existingTarget
|
|
4205
|
+
? {
|
|
4206
|
+
schemaVersion: 'forkit.runtime-context.v1',
|
|
4207
|
+
...(existingTarget.client_name ? { clientName: existingTarget.client_name } : {}),
|
|
4208
|
+
...(existingTarget.subject_id ? { subjectId: existingTarget.subject_id } : {}),
|
|
4209
|
+
...(existingTarget.worktree_id ? { worktreeId: existingTarget.worktree_id } : {}),
|
|
4210
|
+
}
|
|
4211
|
+
: null,
|
|
4212
|
+
clientName: getArg('--client-name') || existingTarget?.client_name || null,
|
|
4213
|
+
defaultLabel: node_path_1.default.basename(repo.rootPath),
|
|
4214
|
+
}))
|
|
4215
|
+
: null;
|
|
4216
|
+
const apiKey = getArg('--api-key') ?? getArg('--heartbeat-key') ?? process.env.FORKIT_RUNTIME_SIGNAL_API_KEY ?? null;
|
|
4217
|
+
if (apiKey) {
|
|
4218
|
+
service.configureRuntimeSignalKey({ gaid, apiKey });
|
|
4219
|
+
}
|
|
4220
|
+
const target = service.registerRuntimeObserverTarget({
|
|
4221
|
+
gaid,
|
|
4222
|
+
repoRoot: rootPath,
|
|
4223
|
+
clientName: getArg('--client-name') || runtimeContext?.clientName || existingTarget?.client_name || node_path_1.default.basename(rootPath),
|
|
4224
|
+
subjectId: runtimeContext?.subjectId ?? existingTarget?.subject_id ?? null,
|
|
4225
|
+
worktreeId: runtimeContext?.worktreeId ?? existingTarget?.worktree_id ?? null,
|
|
4226
|
+
});
|
|
4227
|
+
let autoProvisioned = false;
|
|
4228
|
+
if (!service.hasRuntimeSignalKey(gaid)) {
|
|
4229
|
+
try {
|
|
4230
|
+
const provisioning = await service.autoProvisionRuntimeSignalKeys({ suppressErrors: true });
|
|
4231
|
+
autoProvisioned = provisioning.provisioned.includes(gaid);
|
|
4232
|
+
}
|
|
4233
|
+
catch {
|
|
4234
|
+
autoProvisioned = false;
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
const daemon = (0, daemon_1.getDaemonHealth)(service);
|
|
4238
|
+
const runtimeSignalKeyConfigured = service.hasRuntimeSignalKey(gaid);
|
|
4239
|
+
if (hasFlag('--json')) {
|
|
4240
|
+
printJson({
|
|
4241
|
+
stored: true,
|
|
4242
|
+
daemon,
|
|
4243
|
+
target,
|
|
4244
|
+
runtimeSignalKeyConfigured,
|
|
4245
|
+
autoProvisioned,
|
|
4246
|
+
});
|
|
4247
|
+
return;
|
|
4248
|
+
}
|
|
4249
|
+
console.log(`Stored runtime target ${gaid}.`);
|
|
4250
|
+
console.log(`Repo root: ${target.repo_root}`);
|
|
4251
|
+
console.log(`Client: ${target.client_name || 'n/a'}`);
|
|
4252
|
+
console.log(`Runtime key: ${runtimeSignalKeyConfigured ? (autoProvisioned ? 'ready (auto-provisioned)' : 'ready') : 'missing'}`);
|
|
4253
|
+
console.log(`Daemon: ${daemon.running ? `running (${daemon.pid})` : 'stopped'}`);
|
|
4254
|
+
if (!daemon.running) {
|
|
4255
|
+
console.log('Next: run `forkit-connect start` to keep this runtime connected in the background.');
|
|
4256
|
+
}
|
|
4257
|
+
return;
|
|
4258
|
+
}
|
|
4259
|
+
console.error('Usage: forkit-connect runtime target <add|list|remove|status> [options]');
|
|
4260
|
+
process.exitCode = 2;
|
|
4261
|
+
return;
|
|
4262
|
+
}
|
|
4263
|
+
if (subcommand === 'observe') {
|
|
4264
|
+
const gaid = getArg('--gaid') ?? getArg('--heartbeat-gaid');
|
|
4265
|
+
if (!gaid) {
|
|
4266
|
+
console.error('[forkit-connect] runtime observe requires --gaid.');
|
|
4267
|
+
console.error('[forkit-connect] Use the governed runtime GAID from Forkit.dev.');
|
|
4268
|
+
process.exitCode = 1;
|
|
4269
|
+
return;
|
|
4270
|
+
}
|
|
4271
|
+
try {
|
|
4272
|
+
const cwd = process.cwd();
|
|
4273
|
+
const repo = (0, repo_discovery_1.detectRepository)(cwd);
|
|
4274
|
+
const apiKey = getArg('--api-key') ?? getArg('--heartbeat-key') ?? process.env.FORKIT_RUNTIME_SIGNAL_API_KEY ?? null;
|
|
4275
|
+
if (apiKey) {
|
|
4276
|
+
service.configureRuntimeSignalKey({ gaid, apiKey });
|
|
4277
|
+
}
|
|
4278
|
+
const existingTarget = service.listRuntimeObserverTargets().find((item) => item.runtime_gaid === gaid) ?? null;
|
|
4279
|
+
const runtimeContext = repo.isGitRepo && repo.rootPath
|
|
4280
|
+
? ((0, runtime_context_1.readRuntimeContext)(repo.rootPath) ?? (0, runtime_context_1.buildRuntimeContextRecord)({
|
|
4281
|
+
existing: existingTarget
|
|
4282
|
+
? {
|
|
4283
|
+
schemaVersion: 'forkit.runtime-context.v1',
|
|
4284
|
+
...(existingTarget.client_name ? { clientName: existingTarget.client_name } : {}),
|
|
4285
|
+
...(existingTarget.subject_id ? { subjectId: existingTarget.subject_id } : {}),
|
|
4286
|
+
...(existingTarget.worktree_id ? { worktreeId: existingTarget.worktree_id } : {}),
|
|
4287
|
+
}
|
|
4288
|
+
: null,
|
|
4289
|
+
clientName: getArg('--client-name') || existingTarget?.client_name || null,
|
|
4290
|
+
defaultLabel: node_path_1.default.basename(repo.rootPath),
|
|
4291
|
+
}))
|
|
4292
|
+
: null;
|
|
4293
|
+
const target = service.registerRuntimeObserverTarget({
|
|
4294
|
+
gaid,
|
|
4295
|
+
repoRoot: repo.rootPath || repo.cwd,
|
|
4296
|
+
clientName: getArg('--client-name') || runtimeContext?.clientName || existingTarget?.client_name || null,
|
|
4297
|
+
subjectId: runtimeContext?.subjectId ?? existingTarget?.subject_id ?? null,
|
|
4298
|
+
worktreeId: runtimeContext?.worktreeId ?? existingTarget?.worktree_id ?? null,
|
|
4299
|
+
});
|
|
4300
|
+
const collection = await (0, runtime_observation_runner_1.collectRuntimeObservation)({
|
|
4301
|
+
gaid,
|
|
4302
|
+
cwd,
|
|
4303
|
+
clientName: target.client_name,
|
|
4304
|
+
commandLabel: getArg('--command-label') ?? 'runtime-observer',
|
|
4305
|
+
target,
|
|
4306
|
+
});
|
|
4307
|
+
if (!collection.accepted) {
|
|
4308
|
+
service.updateRuntimeObserverTargetResult({
|
|
4309
|
+
gaid,
|
|
4310
|
+
observedAt: collection.observedAt,
|
|
4311
|
+
summary: null,
|
|
4312
|
+
error: collection.reason,
|
|
4313
|
+
});
|
|
4314
|
+
if (hasFlag('--json')) {
|
|
4315
|
+
printJson({
|
|
4316
|
+
accepted: false,
|
|
4317
|
+
reason: collection.reason,
|
|
4318
|
+
runtime: { gaid },
|
|
4319
|
+
observations: hasFlag('--emit-ambient') ? [...collection.scopedObservations, ...collection.ambientObservations] : [],
|
|
4320
|
+
ambient_observations: collection.ambientObservations,
|
|
4321
|
+
});
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
console.log('No repo-scoped active tooling was observed for this runtime.');
|
|
4325
|
+
if (collection.ambientObservations.length > 0) {
|
|
4326
|
+
console.log(`Ambient tooling seen but not attributed: ${collection.ambientObservations.map((item) => item.label).join(', ')}`);
|
|
4327
|
+
}
|
|
4328
|
+
return;
|
|
4329
|
+
}
|
|
4330
|
+
const result = await (0, runtime_observation_runner_1.emitCollectedRuntimeObservation)(service, collection, { apiKey });
|
|
4331
|
+
if (!result.ok) {
|
|
4332
|
+
const bodyRecord = result.body && typeof result.body === 'object' && !Array.isArray(result.body)
|
|
4333
|
+
? result.body
|
|
4334
|
+
: null;
|
|
4335
|
+
service.updateRuntimeObserverTargetResult({
|
|
4336
|
+
gaid,
|
|
4337
|
+
observedAt: collection.observedAt,
|
|
4338
|
+
fingerprint: collection.fingerprint,
|
|
4339
|
+
summary: collection.summary,
|
|
4340
|
+
error: typeof result.body === 'string'
|
|
4341
|
+
? result.body
|
|
4342
|
+
: bodyRecord && typeof bodyRecord.error === 'string'
|
|
4343
|
+
? bodyRecord.error
|
|
4344
|
+
: `runtime_observer_emit_failed_${result.status || 'local'}`,
|
|
4345
|
+
});
|
|
4346
|
+
console.error(`[forkit-connect] Runtime observe failed (${result.status || 'local'}).`);
|
|
4347
|
+
if (result.body) {
|
|
4348
|
+
console.error(typeof result.body === 'string' ? result.body : JSON.stringify(result.body));
|
|
4349
|
+
}
|
|
4350
|
+
process.exitCode = 2;
|
|
4351
|
+
return;
|
|
4352
|
+
}
|
|
4353
|
+
service.updateRuntimeObserverTargetResult({
|
|
4354
|
+
gaid,
|
|
4355
|
+
observedAt: collection.observedAt,
|
|
4356
|
+
emittedAt: collection.observedAt,
|
|
4357
|
+
fingerprint: collection.fingerprint,
|
|
4358
|
+
summary: collection.summary,
|
|
4359
|
+
error: null,
|
|
4360
|
+
});
|
|
4361
|
+
if (hasFlag('--json')) {
|
|
4362
|
+
printJson({
|
|
4363
|
+
accepted: true,
|
|
4364
|
+
runtime: {
|
|
4365
|
+
gaid,
|
|
4366
|
+
rootPath: collection.repoRoot,
|
|
4367
|
+
relativePath: collection.relativePath,
|
|
4368
|
+
},
|
|
4369
|
+
observations: collection.scopedObservations,
|
|
4370
|
+
ambient_observations: hasFlag('--emit-ambient') ? collection.ambientObservations : [],
|
|
4371
|
+
response: result.body,
|
|
4372
|
+
});
|
|
4373
|
+
return;
|
|
4374
|
+
}
|
|
4375
|
+
console.log('Runtime observation recorded.');
|
|
4376
|
+
console.log(`Runtime GAID: ${gaid}`);
|
|
4377
|
+
console.log(`Repo root: ${collection.repoRoot}`);
|
|
4378
|
+
console.log(`Scoped tooling: ${collection.scopedObservations.map((item) => `${item.label}${item.details && typeof item.details.primaryRole === 'string' ? `/${item.details.primaryRole}` : ''} (${item.activeProcessCount}${item.helperProcessCount > 0 ? `+${item.helperProcessCount} helper` : ''})`).join(', ')}`);
|
|
4379
|
+
if (collection.ambientObservations.length > 0) {
|
|
4380
|
+
console.log(`Ambient tooling ignored: ${collection.ambientObservations.map((item) => item.label).join(', ')}`);
|
|
4381
|
+
}
|
|
4382
|
+
return;
|
|
4383
|
+
}
|
|
4384
|
+
catch (error) {
|
|
4385
|
+
console.error(error instanceof Error ? error.message : 'runtime_observe_failed');
|
|
4386
|
+
process.exitCode = 2;
|
|
4387
|
+
return;
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
console.error('Usage: forkit-connect runtime <register|target|observe> [options]');
|
|
4391
|
+
process.exitCode = 2;
|
|
4392
|
+
return;
|
|
4393
|
+
}
|
|
3696
4394
|
if (command === 'ignore') {
|
|
3697
4395
|
runPublicIgnore();
|
|
3698
4396
|
return;
|
|
@@ -3890,6 +4588,32 @@ async function run() {
|
|
|
3890
4588
|
process.exitCode = 1;
|
|
3891
4589
|
return;
|
|
3892
4590
|
}
|
|
4591
|
+
const status = getArg('--status') ?? 'completed';
|
|
4592
|
+
const startedAt = getArg('--started-at');
|
|
4593
|
+
const endedAt = getArg('--ended-at');
|
|
4594
|
+
const promptTokens = getNumericArg('--prompt-tokens');
|
|
4595
|
+
const completionTokens = getNumericArg('--completion-tokens');
|
|
4596
|
+
const cachedPromptTokens = getNumericArg('--cached-prompt-tokens');
|
|
4597
|
+
const reasoningTokens = getNumericArg('--reasoning-tokens');
|
|
4598
|
+
const latencyMs = getNumericArg('--latency-ms');
|
|
4599
|
+
const summary = getArg('--summary');
|
|
4600
|
+
const activityEnvelope = buildCliRuntimeRunActivityEnvelope({
|
|
4601
|
+
provider,
|
|
4602
|
+
model: runModel,
|
|
4603
|
+
serviceName,
|
|
4604
|
+
serviceKind: getArg('--service-kind') ?? 'custom',
|
|
4605
|
+
commandLabel: getArg('--command-label'),
|
|
4606
|
+
status,
|
|
4607
|
+
startedAt,
|
|
4608
|
+
endedAt,
|
|
4609
|
+
summary,
|
|
4610
|
+
clientName: getArg('--client-name'),
|
|
4611
|
+
promptTokens,
|
|
4612
|
+
completionTokens,
|
|
4613
|
+
cachedPromptTokens,
|
|
4614
|
+
reasoningTokens,
|
|
4615
|
+
latencyMs,
|
|
4616
|
+
});
|
|
3893
4617
|
const result = await service.emitRuntimeRunLog({
|
|
3894
4618
|
gaid,
|
|
3895
4619
|
apiKey,
|
|
@@ -3899,17 +4623,19 @@ async function run() {
|
|
|
3899
4623
|
serviceKind: getArg('--service-kind') ?? 'custom',
|
|
3900
4624
|
runId: getArg('--run-id'),
|
|
3901
4625
|
externalRunId: getArg('--external-run-id'),
|
|
3902
|
-
status
|
|
3903
|
-
startedAt
|
|
3904
|
-
endedAt
|
|
3905
|
-
promptTokens
|
|
3906
|
-
completionTokens
|
|
3907
|
-
cachedPromptTokens
|
|
3908
|
-
reasoningTokens
|
|
3909
|
-
latencyMs
|
|
4626
|
+
status,
|
|
4627
|
+
startedAt,
|
|
4628
|
+
endedAt,
|
|
4629
|
+
promptTokens,
|
|
4630
|
+
completionTokens,
|
|
4631
|
+
cachedPromptTokens,
|
|
4632
|
+
reasoningTokens,
|
|
4633
|
+
latencyMs,
|
|
3910
4634
|
estimatedCostCents: getNumericArg('--estimated-cost-cents'),
|
|
3911
4635
|
currency: getArg('--currency') ?? 'USD',
|
|
3912
|
-
summary:
|
|
4636
|
+
summary: activityEnvelope.summary ?? summary,
|
|
4637
|
+
metadata: activityEnvelope.metadata,
|
|
4638
|
+
steps: activityEnvelope.steps,
|
|
3913
4639
|
});
|
|
3914
4640
|
if (!result.ok) {
|
|
3915
4641
|
console.error(`[forkit-connect] Runtime run log emit failed (${result.status || 'local'}).`);
|