@kal-elsam/kairo-runtime 0.1.4 → 0.2.0

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.
@@ -0,0 +1,77 @@
1
+ import { BACKEND_IDS } from "./types.js";
2
+ import { createOllamaBackend } from "./backends/ollama.js";
3
+ import { createOpenRouterBackend } from "./backends/openrouter.js";
4
+ import { createCustomHttpBackend } from "./backends/custom-http.js";
5
+ import { CAPABILITY_STATES } from "../capability-states.js";
6
+
7
+ export function createDefaultBackends({
8
+ env = process.env,
9
+ fetchImpl = globalThis.fetch,
10
+ customProviders = []
11
+ } = {}) {
12
+ const backends = [
13
+ createOllamaBackend({ env, fetchImpl }),
14
+ createOpenRouterBackend({ env, fetchImpl })
15
+ ];
16
+
17
+ for (const provider of customProviders) {
18
+ backends.push(createCustomHttpBackend({
19
+ ...provider,
20
+ env,
21
+ fetchImpl
22
+ }));
23
+ }
24
+
25
+ return backends;
26
+ }
27
+
28
+ export async function inspectIntelligenceBackends({
29
+ env = process.env,
30
+ fetchImpl = globalThis.fetch,
31
+ customProviders = [],
32
+ backends = null
33
+ } = {}) {
34
+ const resolved = backends ?? createDefaultBackends({ env, fetchImpl, customProviders });
35
+ return Promise.all(resolved.map(async (backend) => {
36
+ const detection = await backend.detect();
37
+ const models = detection.detected || detection.available
38
+ ? await backend.listModels()
39
+ : [];
40
+ const capabilities = await backend.capabilities();
41
+
42
+ return {
43
+ ...detection,
44
+ models,
45
+ capabilities
46
+ };
47
+ }));
48
+ }
49
+
50
+ export function summarizeIntelligenceBackends(inspections) {
51
+ const byState = {};
52
+ for (const state of Object.values(CAPABILITY_STATES)) {
53
+ byState[state] = 0;
54
+ }
55
+
56
+ for (const entry of inspections) {
57
+ if (byState[entry.state] != null) {
58
+ byState[entry.state] += 1;
59
+ }
60
+ }
61
+
62
+ return {
63
+ total: inspections.length,
64
+ available: inspections.filter((entry) => entry.available).length,
65
+ localAvailable: inspections.some(
66
+ (entry) => entry.id === BACKEND_IDS.OLLAMA && entry.available
67
+ ),
68
+ cloudAuthenticated: inspections.some(
69
+ (entry) => entry.id === BACKEND_IDS.OPENROUTER && entry.hasApiKey
70
+ ),
71
+ byState
72
+ };
73
+ }
74
+
75
+ export function resolveBackendById(backends, backendId) {
76
+ return backends.find((backend) => backend.id === backendId) ?? null;
77
+ }
@@ -0,0 +1,191 @@
1
+ import {
2
+ BACKEND_IDS,
3
+ OPENROUTER_FREE_MODEL,
4
+ PRIVACY_CLASSES,
5
+ ROUTING_MODES,
6
+ createRoutingDecision
7
+ } from "./types.js";
8
+ import { estimateTokens } from "./context-compiler.js";
9
+
10
+ const TASK_WEIGHTS = {
11
+ architecture: "heavy",
12
+ security: "heavy",
13
+ review: "heavy",
14
+ diagnose: "light",
15
+ explain: "light",
16
+ scaffold: "light",
17
+ test: "light",
18
+ default: "light"
19
+ };
20
+
21
+ export function classifyTaskWeight(task = "") {
22
+ const text = String(task).toLowerCase();
23
+ if (/architect|adr|design system|security|threat/.test(text)) return "heavy";
24
+ if (/review|refactor complex|debug complex/.test(text)) return "heavy";
25
+ if (/test|scaffold|explain|status|diagnose|lint|format/.test(text)) return "light";
26
+ return TASK_WEIGHTS.default;
27
+ }
28
+
29
+ /**
30
+ * Resolve which backend/model to use.
31
+ * Precedence: user override > Ollama local > OpenRouter free (consent) > diagnostics.
32
+ */
33
+ export function resolveRoutingDecision({
34
+ backends = [],
35
+ profile = {},
36
+ contextPack = null,
37
+ task = null,
38
+ cloudConsent = false,
39
+ tokenBudget = null
40
+ } = {}) {
41
+ const estimatedTokens = contextPack?.estimatedTokens
42
+ ?? estimateTokens(contextPack?.systemPrompt ?? "")
43
+ + estimateTokens(task ?? "");
44
+
45
+ const budget = tokenBudget ?? profile.tokenBudget ?? null;
46
+ if (budget != null && estimatedTokens > budget) {
47
+ return createRoutingDecision({
48
+ backendId: null,
49
+ model: null,
50
+ reason: `Estimated tokens (${estimatedTokens}) exceed budget (${budget}). Compact context or raise tokenBudget.`,
51
+ estimatedTokens,
52
+ privacyImpact: PRIVACY_CLASSES.UNKNOWN,
53
+ mode: ROUTING_MODES.DIAGNOSTICS,
54
+ requiresCloudConsent: false,
55
+ canInvoke: false
56
+ });
57
+ }
58
+
59
+ const override = resolveUserOverride(profile, backends);
60
+ if (override) {
61
+ return createRoutingDecision({
62
+ backendId: override.backend.id,
63
+ model: override.model,
64
+ reason: `User override: ${override.backend.id}/${override.model.modelId}`,
65
+ estimatedTokens,
66
+ privacyImpact: override.model.privacyClass,
67
+ mode: ROUTING_MODES.USER_OVERRIDE,
68
+ requiresCloudConsent: !override.model.local,
69
+ canInvoke: override.model.local || cloudConsent,
70
+ fallback: buildLocalFallback(backends)
71
+ });
72
+ }
73
+
74
+ const ollama = backends.find((entry) => entry.id === BACKEND_IDS.OLLAMA);
75
+ if (ollama?.available && Array.isArray(ollama.models) && ollama.models.length > 0) {
76
+ const model = selectLocalModel(ollama.models, task);
77
+ return createRoutingDecision({
78
+ backendId: BACKEND_IDS.OLLAMA,
79
+ model,
80
+ reason: `Local-first: Ollama model ${model.modelId}`,
81
+ estimatedTokens,
82
+ privacyImpact: PRIVACY_CLASSES.LOCAL,
83
+ mode: ROUTING_MODES.LOCAL,
84
+ requiresCloudConsent: false,
85
+ canInvoke: true,
86
+ fallback: buildCloudFallback(backends, cloudConsent)
87
+ });
88
+ }
89
+
90
+ const openrouter = backends.find((entry) => entry.id === BACKEND_IDS.OPENROUTER);
91
+ if (openrouter?.hasApiKey) {
92
+ const model = openrouter.models?.find((entry) => entry.modelId === OPENROUTER_FREE_MODEL)
93
+ ?? openrouter.models?.[0]
94
+ ?? {
95
+ provider: BACKEND_IDS.OPENROUTER,
96
+ modelId: OPENROUTER_FREE_MODEL,
97
+ local: false,
98
+ privacyClass: PRIVACY_CLASSES.CLOUD,
99
+ costClass: "free",
100
+ opaque: true
101
+ };
102
+
103
+ return createRoutingDecision({
104
+ backendId: BACKEND_IDS.OPENROUTER,
105
+ model,
106
+ reason: cloudConsent
107
+ ? `Cloud fallback approved: ${model.modelId}`
108
+ : `OpenRouter available (${model.modelId}) but cloud consent required before invoke`,
109
+ estimatedTokens,
110
+ privacyImpact: PRIVACY_CLASSES.CLOUD,
111
+ mode: ROUTING_MODES.CLOUD_CONSENT,
112
+ requiresCloudConsent: true,
113
+ canInvoke: cloudConsent,
114
+ fallback: null
115
+ });
116
+ }
117
+
118
+ return createRoutingDecision({
119
+ backendId: null,
120
+ model: null,
121
+ reason: "No intelligence backend available. Remaining in diagnostics/configuration mode.",
122
+ estimatedTokens,
123
+ privacyImpact: PRIVACY_CLASSES.UNKNOWN,
124
+ mode: ROUTING_MODES.DIAGNOSTICS,
125
+ requiresCloudConsent: false,
126
+ canInvoke: false
127
+ });
128
+ }
129
+
130
+ function resolveUserOverride(profile, backends) {
131
+ const preferredBackend = profile.preferredBackend ?? null;
132
+ const preferredModel = profile.preferredModel ?? null;
133
+ if (!preferredBackend && !preferredModel) return null;
134
+
135
+ if (preferredBackend) {
136
+ const backend = backends.find((entry) => entry.id === preferredBackend);
137
+ if (!backend || (!backend.available && !backend.hasApiKey && !backend.detected)) {
138
+ return null;
139
+ }
140
+ const model = (backend.models ?? []).find((entry) => entry.modelId === preferredModel)
141
+ ?? backend.models?.[0]
142
+ ?? (preferredModel
143
+ ? {
144
+ provider: preferredBackend,
145
+ modelId: preferredModel,
146
+ local: backend.id === BACKEND_IDS.OLLAMA,
147
+ privacyClass: backend.id === BACKEND_IDS.OLLAMA ? PRIVACY_CLASSES.LOCAL : PRIVACY_CLASSES.CLOUD,
148
+ costClass: "unknown",
149
+ opaque: true
150
+ }
151
+ : null);
152
+ if (!model) return null;
153
+ return { backend, model };
154
+ }
155
+
156
+ if (preferredModel) {
157
+ for (const backend of backends) {
158
+ const model = (backend.models ?? []).find((entry) => entry.modelId === preferredModel);
159
+ if (model) return { backend, model };
160
+ }
161
+ }
162
+
163
+ return null;
164
+ }
165
+
166
+ function selectLocalModel(models, task) {
167
+ const weight = classifyTaskWeight(task);
168
+ if (weight === "heavy" && models.length > 1) {
169
+ return [...models].sort((a, b) => String(b.modelId).localeCompare(String(a.modelId)))[0];
170
+ }
171
+ return models[0];
172
+ }
173
+
174
+ function buildLocalFallback(backends) {
175
+ const ollama = backends.find((entry) => entry.id === BACKEND_IDS.OLLAMA && entry.available);
176
+ if (!ollama?.models?.length) return null;
177
+ return {
178
+ backendId: BACKEND_IDS.OLLAMA,
179
+ modelId: ollama.models[0].modelId
180
+ };
181
+ }
182
+
183
+ function buildCloudFallback(backends, cloudConsent) {
184
+ const openrouter = backends.find((entry) => entry.id === BACKEND_IDS.OPENROUTER && entry.hasApiKey);
185
+ if (!openrouter) return null;
186
+ return {
187
+ backendId: BACKEND_IDS.OPENROUTER,
188
+ modelId: OPENROUTER_FREE_MODEL,
189
+ requiresConsent: !cloudConsent
190
+ };
191
+ }
@@ -0,0 +1,99 @@
1
+ export const BACKEND_IDS = {
2
+ OLLAMA: "ollama",
3
+ OPENROUTER: "openrouter",
4
+ CUSTOM: "custom"
5
+ };
6
+
7
+ export const COST_CLASSES = {
8
+ FREE: "free",
9
+ LOCAL: "local",
10
+ PAID: "paid",
11
+ UNKNOWN: "unknown"
12
+ };
13
+
14
+ export const PRIVACY_CLASSES = {
15
+ LOCAL: "local",
16
+ CLOUD: "cloud",
17
+ UNKNOWN: "unknown"
18
+ };
19
+
20
+ export const ROUTING_MODES = {
21
+ DIAGNOSTICS: "diagnostics",
22
+ LOCAL: "local",
23
+ CLOUD_CONSENT: "cloud_consent",
24
+ USER_OVERRIDE: "user_override"
25
+ };
26
+
27
+ export const OPENROUTER_FREE_MODEL = "openrouter/free";
28
+
29
+ export const DEFAULT_OLLAMA_HOST = "http://127.0.0.1:11434";
30
+
31
+ export function createModelDescriptor({
32
+ provider,
33
+ modelId,
34
+ local = false,
35
+ costClass = COST_CLASSES.UNKNOWN,
36
+ privacyClass = PRIVACY_CLASSES.UNKNOWN,
37
+ contextLimit = null,
38
+ tools = false,
39
+ reasoning = false,
40
+ rateLimits = null,
41
+ opaque = false
42
+ }) {
43
+ return {
44
+ provider,
45
+ modelId,
46
+ local,
47
+ costClass,
48
+ privacyClass,
49
+ contextLimit,
50
+ tools,
51
+ reasoning,
52
+ rateLimits,
53
+ opaque
54
+ };
55
+ }
56
+
57
+ export function createRoutingDecision({
58
+ backendId,
59
+ model,
60
+ reason,
61
+ estimatedTokens = null,
62
+ privacyImpact = PRIVACY_CLASSES.UNKNOWN,
63
+ fallback = null,
64
+ mode = ROUTING_MODES.DIAGNOSTICS,
65
+ requiresCloudConsent = false,
66
+ canInvoke = false
67
+ }) {
68
+ return {
69
+ backendId,
70
+ model,
71
+ reason,
72
+ estimatedTokens,
73
+ privacyImpact,
74
+ fallback,
75
+ mode,
76
+ requiresCloudConsent,
77
+ canInvoke
78
+ };
79
+ }
80
+
81
+ export function createUsageTelemetry({
82
+ inputTokens = null,
83
+ outputTokens = null,
84
+ cachedTokens = null,
85
+ estimatedCost = null,
86
+ model = null,
87
+ backendId = null,
88
+ fallbackUsed = false
89
+ } = {}) {
90
+ return {
91
+ inputTokens,
92
+ outputTokens,
93
+ cachedTokens,
94
+ estimatedCost,
95
+ model,
96
+ backendId,
97
+ fallbackUsed
98
+ };
99
+ }
@@ -0,0 +1,323 @@
1
+ import { resolveHomeDir } from "./paths.js";
2
+ import { resolveProfile, buildProfileJson } from "./profile.js";
3
+ import {
4
+ compileContextPack,
5
+ inspectIntelligenceBackends,
6
+ resolveRoutingDecision,
7
+ runIntelligenceRequest,
8
+ summarizeIntelligenceBackends
9
+ } from "./intelligence/index.js";
10
+ import { BRAND } from "./brand/index.js";
11
+ import { formatCliCommand } from "./brand/cli.js";
12
+
13
+ export async function runIntelligenceCli(options, packageManifest) {
14
+ const action = options.intelligenceAction ?? "status";
15
+ const homeDir = resolveHomeDir();
16
+ const workspaceRoot = options.cwd;
17
+ const { profile, sources } = await resolveProfile({ homeDir, workspaceRoot });
18
+
19
+ switch (action) {
20
+ case "status":
21
+ return printIntelligenceStatus({
22
+ homeDir,
23
+ workspaceRoot,
24
+ profile,
25
+ sources,
26
+ json: options.json,
27
+ env: process.env
28
+ });
29
+ case "models":
30
+ return printIntelligenceModels({
31
+ profile,
32
+ json: options.json,
33
+ env: process.env
34
+ });
35
+ case "context":
36
+ return printIntelligenceContext({
37
+ workspaceRoot,
38
+ profile,
39
+ task: options.intelligenceTask,
40
+ relevantPaths: options.intelligencePaths ?? [],
41
+ includePrivate: options.includePrivate,
42
+ confirmed: options.yes || options.confirm,
43
+ json: options.json
44
+ });
45
+ case "route":
46
+ return printIntelligenceRoute({
47
+ workspaceRoot,
48
+ profile,
49
+ task: options.intelligenceTask,
50
+ cloudConsent: options.cloudConsent,
51
+ json: options.json,
52
+ env: process.env
53
+ });
54
+ case "ask":
55
+ return runIntelligenceAsk({
56
+ workspaceRoot,
57
+ profile,
58
+ task: options.intelligenceTask,
59
+ prompt: options.intelligencePrompt,
60
+ relevantPaths: options.intelligencePaths ?? [],
61
+ includePrivate: options.includePrivate,
62
+ cloudConsent: options.cloudConsent,
63
+ confirmed: options.yes || options.confirm,
64
+ json: options.json,
65
+ env: process.env,
66
+ packageManifest
67
+ });
68
+ default:
69
+ throw new Error(
70
+ `Unknown intelligence action "${action}". Use status, models, context, route, or ask.`
71
+ );
72
+ }
73
+ }
74
+
75
+ async function printIntelligenceStatus({ homeDir, workspaceRoot, profile, sources, json, env }) {
76
+ const backends = await inspectIntelligenceBackends({
77
+ env,
78
+ customProviders: profile.customProviders
79
+ });
80
+ const summary = summarizeIntelligenceBackends(backends);
81
+ const routing = resolveRoutingDecision({
82
+ backends,
83
+ profile,
84
+ cloudConsent: false
85
+ });
86
+
87
+ const payload = {
88
+ readOnly: true,
89
+ homeDir,
90
+ workspaceRoot,
91
+ profile: buildProfileJson({ profile, sources }),
92
+ backends,
93
+ summary,
94
+ routing
95
+ };
96
+
97
+ if (json) {
98
+ console.log(JSON.stringify(payload, null, 2));
99
+ return payload;
100
+ }
101
+
102
+ console.log(`${BRAND.displayName} intelligence — status`);
103
+ console.log(`Home: ${homeDir}`);
104
+ console.log(`Workspace: ${workspaceRoot}`);
105
+ console.log("");
106
+ for (const backend of backends) {
107
+ const models = backend.models?.length ?? 0;
108
+ console.log(
109
+ ` ${backend.label.padEnd(14)} ${backend.state.padEnd(14)} models=${models}`
110
+ );
111
+ }
112
+ console.log("");
113
+ console.log(`Routing: ${routing.reason}`);
114
+ console.log(`Can invoke: ${routing.canInvoke ? "yes" : "no"}`);
115
+ if (!summary.localAvailable && !summary.cloudAuthenticated) {
116
+ console.log("");
117
+ console.log("Diagnostics mode: configure Ollama or OPENROUTER_API_KEY to enable inference.");
118
+ }
119
+ return payload;
120
+ }
121
+
122
+ async function printIntelligenceModels({ profile, json, env }) {
123
+ const backends = await inspectIntelligenceBackends({
124
+ env,
125
+ customProviders: profile.customProviders
126
+ });
127
+
128
+ const models = backends.flatMap((backend) =>
129
+ (backend.models ?? []).map((model) => ({
130
+ ...model,
131
+ backendState: backend.state,
132
+ available: backend.available
133
+ }))
134
+ );
135
+
136
+ const payload = { readOnly: true, models, backends: backends.map((entry) => ({
137
+ id: entry.id,
138
+ state: entry.state,
139
+ available: entry.available
140
+ })) };
141
+
142
+ if (json) {
143
+ console.log(JSON.stringify(payload, null, 2));
144
+ return payload;
145
+ }
146
+
147
+ console.log(`${BRAND.displayName} intelligence — models`);
148
+ if (models.length === 0) {
149
+ console.log(" No models detected. Start Ollama or set OPENROUTER_API_KEY.");
150
+ } else {
151
+ for (const model of models) {
152
+ console.log(
153
+ ` ${model.provider.padEnd(12)} ${model.modelId.padEnd(32)} ${model.privacyClass}/${model.costClass}`
154
+ );
155
+ }
156
+ }
157
+ return payload;
158
+ }
159
+
160
+ async function printIntelligenceContext({
161
+ workspaceRoot,
162
+ profile,
163
+ task,
164
+ relevantPaths,
165
+ includePrivate,
166
+ confirmed,
167
+ json
168
+ }) {
169
+ const privateConfirmationRequired = includePrivate && !confirmed;
170
+ const pack = await compileContextPack({
171
+ workspaceRoot,
172
+ task,
173
+ relevantPaths,
174
+ includePrivate: includePrivate && confirmed,
175
+ stableBudgetTokens: profile.stableContextBudget ?? undefined,
176
+ requestBudgetTokens: profile.requestContextBudget ?? undefined
177
+ });
178
+
179
+ const payload = {
180
+ readOnly: true,
181
+ estimatedTokens: pack.estimatedTokens,
182
+ project: pack.stable.project,
183
+ evidence: pack.evidence,
184
+ privacy: pack.privacy,
185
+ skills: pack.stable.skills,
186
+ sdd: pack.stable.sdd,
187
+ tdd: pack.stable.tdd,
188
+ graphify: pack.stable.graphify,
189
+ hasAgentsMd: Boolean(pack.stable.agentsMd),
190
+ relevantFiles: pack.perRequest.files.map((file) => file.path)
191
+ };
192
+
193
+ if (privateConfirmationRequired) {
194
+ payload.privateConfirmationRequired = true;
195
+ payload.error = "Including private context requires explicit confirmation (--include-private --yes / --confirm).";
196
+ }
197
+
198
+ if (json) {
199
+ console.log(JSON.stringify(payload, null, 2));
200
+ return payload;
201
+ }
202
+
203
+ console.log(`${BRAND.displayName} intelligence — context`);
204
+ console.log(`Estimated tokens: ${pack.estimatedTokens}`);
205
+ console.log(`Project: ${pack.stable.project.name} (${pack.stable.project.stack})`);
206
+ console.log(`Evidence files: ${pack.evidence.filter((entry) => entry.kind === "file").length}`);
207
+ if (privateConfirmationRequired) {
208
+ console.log(`Blocked: ${payload.error}`);
209
+ }
210
+ if (pack.privacy.excludedPrivate.length > 0) {
211
+ console.log(`Excluded private: ${pack.privacy.excludedPrivate.join(", ")}`);
212
+ }
213
+ return payload;
214
+ }
215
+
216
+ async function printIntelligenceRoute({
217
+ workspaceRoot,
218
+ profile,
219
+ task,
220
+ cloudConsent,
221
+ json,
222
+ env
223
+ }) {
224
+ const backends = await inspectIntelligenceBackends({
225
+ env,
226
+ customProviders: profile.customProviders
227
+ });
228
+ const pack = await compileContextPack({ workspaceRoot, task });
229
+ const routing = resolveRoutingDecision({
230
+ backends,
231
+ profile,
232
+ contextPack: pack,
233
+ task,
234
+ cloudConsent: Boolean(cloudConsent)
235
+ });
236
+
237
+ const payload = {
238
+ readOnly: true,
239
+ routing,
240
+ estimatedTokens: pack.estimatedTokens,
241
+ evidenceUsed: pack.evidence.filter((entry) => entry.kind === "file").map((entry) => entry.path)
242
+ };
243
+
244
+ if (json) {
245
+ console.log(JSON.stringify(payload, null, 2));
246
+ return payload;
247
+ }
248
+
249
+ console.log(`${BRAND.displayName} intelligence — routing`);
250
+ console.log(`Backend: ${routing.backendId ?? "none"}`);
251
+ console.log(`Model: ${routing.model?.modelId ?? "none"}`);
252
+ console.log(`Reason: ${routing.reason}`);
253
+ console.log(`Estimated tokens: ${routing.estimatedTokens}`);
254
+ console.log(`Privacy: ${routing.privacyImpact}`);
255
+ console.log(`Can invoke: ${routing.canInvoke ? "yes" : "no"}`);
256
+ return payload;
257
+ }
258
+
259
+ async function runIntelligenceAsk({
260
+ workspaceRoot,
261
+ profile,
262
+ task,
263
+ prompt,
264
+ relevantPaths,
265
+ includePrivate,
266
+ cloudConsent,
267
+ confirmed,
268
+ json,
269
+ env
270
+ }) {
271
+ if (!prompt && !task) {
272
+ throw new Error(`Missing prompt. Use: ${formatCliCommand("intelligence ask --prompt \"...\"")}`);
273
+ }
274
+
275
+ const outcome = await runIntelligenceRequest({
276
+ workspaceRoot,
277
+ profile,
278
+ task,
279
+ prompt: prompt ?? task,
280
+ relevantPaths,
281
+ includePrivate,
282
+ cloudConsent,
283
+ confirmed,
284
+ env
285
+ });
286
+
287
+ if (json) {
288
+ console.log(JSON.stringify({
289
+ ok: outcome.ok,
290
+ mode: outcome.mode,
291
+ diagnosticsOnly: outcome.diagnosticsOnly,
292
+ routing: outcome.routing,
293
+ explanation: outcome.explanation,
294
+ telemetry: outcome.telemetry,
295
+ content: outcome.result?.content ?? null,
296
+ error: outcome.error
297
+ }, null, 2));
298
+ return outcome;
299
+ }
300
+
301
+ console.log(`${BRAND.displayName} intelligence — ask`);
302
+ console.log(`Routing: ${outcome.explanation.reason}`);
303
+ console.log(`Estimated tokens: ${outcome.explanation.estimatedTokens}`);
304
+ console.log(`Privacy: ${outcome.explanation.privacyImpact}`);
305
+ console.log("");
306
+
307
+ if (!outcome.ok) {
308
+ console.log(`Blocked: ${outcome.error}`);
309
+ if (outcome.routing?.requiresCloudConsent) {
310
+ console.log(`Retry with --cloud-consent --yes after reviewing context (${formatCliCommand("intelligence context")}).`);
311
+ }
312
+ return outcome;
313
+ }
314
+
315
+ console.log(outcome.result.content);
316
+ if (outcome.telemetry) {
317
+ console.log("");
318
+ console.log(
319
+ `Usage: in=${outcome.telemetry.inputTokens ?? "?"} out=${outcome.telemetry.outputTokens ?? "?"} model=${outcome.telemetry.model}`
320
+ );
321
+ }
322
+ return outcome;
323
+ }