nolo-cli 0.1.22 → 0.1.26

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.
Files changed (128) hide show
  1. package/README.md +25 -3
  2. package/agent-runtime/agentRecordConfig.ts +4 -0
  3. package/agent-runtime/hostAdapter.ts +2 -0
  4. package/agent-runtime/hybridRecordStore.ts +30 -9
  5. package/agent-runtime/index.ts +7 -0
  6. package/agent-runtime/localLoop.ts +14 -4
  7. package/agent-runtime/localToolPolicy.ts +22 -10
  8. package/agent-runtime/localWorkspaceTools.ts +140 -374
  9. package/agent-runtime/platformChatProvider.ts +3 -0
  10. package/agent-runtime/runtimeToolPolicy.ts +133 -0
  11. package/agent-runtime/types.ts +47 -0
  12. package/agentAliases.ts +1 -1
  13. package/agentCommandSupport.ts +77 -0
  14. package/agentInternalCommandEntries.ts +34 -0
  15. package/agentListCommands.ts +123 -0
  16. package/agentListHelpers.ts +187 -0
  17. package/agentMachineCommands.ts +402 -0
  18. package/agentRecordCommands.ts +141 -0
  19. package/agentRecordHelpers.ts +395 -0
  20. package/agentRunCommand.ts +75 -3
  21. package/agentRuntimeCommands.ts +7 -773
  22. package/ai/agent/_executeModel.ts +29 -3
  23. package/ai/agent/buildSystemPrompt.ts +17 -0
  24. package/ai/agent/cliChatClient.ts +38 -4
  25. package/ai/agent/cliExecutor.ts +304 -25
  26. package/ai/agent/createAgentSchema.ts +2 -2
  27. package/ai/agent/guidedCreation/assistant.ts +82 -0
  28. package/ai/agent/guidedCreation/capabilities.ts +84 -0
  29. package/ai/agent/guidedCreation/draft.ts +62 -0
  30. package/ai/agent/guidedCreation/types.ts +38 -0
  31. package/ai/agent/machineRunPermissions.ts +21 -3
  32. package/ai/agent/runtimeGuidance.ts +1 -1
  33. package/ai/agent/server/fetchPublicAgents.ts +6 -1
  34. package/ai/agent/streamAgentChatTurn.ts +593 -36
  35. package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
  36. package/ai/ai.locale.ts +14 -2
  37. package/ai/chat/fetchUtils.native.ts +2 -0
  38. package/ai/chat/fetchUtils.ts +2 -0
  39. package/ai/chat/sendOpenAICompletionsRequest.ts +82 -1
  40. package/ai/chat/sendOpenAIResponseRequest.ts +65 -0
  41. package/ai/llm/getPricing.ts +14 -0
  42. package/ai/llm/kimi.ts +4 -1
  43. package/ai/llm/modelAvatar.ts +1 -0
  44. package/ai/llm/providers.ts +4 -1
  45. package/ai/llm/reasoningModels.ts +1 -0
  46. package/ai/taskRun/taskRunProtocol.ts +25 -10
  47. package/ai/token/modelUsageQuery.ts +17 -0
  48. package/ai/tools/agent/agentTools.ts +38 -0
  49. package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
  50. package/ai/tools/agent/startAgentDialogTool.ts +53 -0
  51. package/ai/tools/checkEnvTool.ts +52 -20
  52. package/ai/tools/convertMarxistsBookTool.ts +103 -0
  53. package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
  54. package/ai/tools/index.ts +24 -14
  55. package/ai/tools/marxistsOfflineBook.ts +253 -0
  56. package/ai/tools/modelUsageTools.ts +9 -2
  57. package/ai/tools/readTool.ts +31 -0
  58. package/ai/tools/toolApiClient.ts +35 -1
  59. package/ai/tools/toolNameAliases.ts +13 -0
  60. package/cliCommandAdapters.ts +28 -0
  61. package/cliCommandDispatch.ts +66 -0
  62. package/cliCommandFactories.ts +34 -0
  63. package/cliCommandTypes.ts +49 -0
  64. package/cliEnvHelpers.ts +44 -0
  65. package/client/agentRun.test.ts +330 -24
  66. package/client/agentRun.ts +117 -35
  67. package/client/hybridRecordStore.test.ts +42 -0
  68. package/client/hybridRecordStore.ts +11 -0
  69. package/client/localRuntimeAdapter.test.ts +191 -21
  70. package/client/localRuntimeAdapter.ts +99 -10
  71. package/client/localRuntimeDryRun.test.ts +5 -2
  72. package/client/taskRunPrompt.ts +1 -0
  73. package/commandRegistry.ts +11 -61
  74. package/connector-experimental/capabilities.ts +1 -0
  75. package/database/actions/common.ts +4 -3
  76. package/database/config.ts +19 -0
  77. package/database/server/coreDataProxy.ts +43 -26
  78. package/index.ts +54 -191
  79. package/internalCommandEntries.ts +12 -0
  80. package/machineCommands.ts +688 -64
  81. package/offlineMarxistsAgentCommand.ts +314 -0
  82. package/package.json +22 -2
  83. package/render/canvas/canvasEditContext.ts +127 -0
  84. package/render/canvas/canvasRuntime.ts +57 -0
  85. package/render/canvas/canvasSnapshotParser.ts +76 -0
  86. package/render/canvas/canvasTree.ts +308 -0
  87. package/render/canvas/types.ts +46 -0
  88. package/render/layout/deleteBehavior.ts +52 -0
  89. package/render/layout/mainLayoutSidebar.ts +17 -0
  90. package/render/layout/mainLayoutViewMode.ts +56 -0
  91. package/render/layout/topbarUtils.ts +87 -0
  92. package/render/layout/useDevReloadPending.ts +30 -0
  93. package/render/page/createPageAction.ts +183 -0
  94. package/render/page/docSlice.ts +468 -0
  95. package/render/page/server/createPage.ts +174 -0
  96. package/render/page/server/handleCreatePage.ts +91 -0
  97. package/render/page/server/index.ts +4 -0
  98. package/render/page/types.ts +17 -0
  99. package/render/page/useKeyboardSave.ts +48 -0
  100. package/render/styles/zIndex.ts +12 -0
  101. package/render/surf/WeatherIconStyles.ts +17 -0
  102. package/render/surf/color.ts +9 -0
  103. package/render/surf/config.ts +46 -0
  104. package/render/surf/screens/style.ts +1 -0
  105. package/render/surf/styles/ToggleButtonStyles.ts +8 -0
  106. package/render/surf/utils/groupedWeatherData.ts +32 -0
  107. package/render/surf/weatherUtils.ts +50 -0
  108. package/render/table/activityColumns.ts +6 -0
  109. package/render/table/createTableAction.ts +270 -0
  110. package/render/table/deleteTableAction.ts +129 -0
  111. package/render/table/fetchAndCacheTableRows.ts +174 -0
  112. package/render/table/tableSlice.ts +1106 -0
  113. package/render/table/tableView.ts +289 -0
  114. package/render/table/toolValueUtils.ts +363 -0
  115. package/render/table/types.ts +252 -0
  116. package/render/table/useCreateTable.ts +72 -0
  117. package/render/table/useTable.ts +61 -0
  118. package/render/table/utils/tableSerialization.ts +50 -0
  119. package/render/web/elements/artifactPreviewCode.ts +43 -0
  120. package/render/web/elements/artifactRuntimePreload.ts +52 -0
  121. package/render/web/elements/codeBlockAutoPreview.ts +10 -0
  122. package/render/web/elements/mermaidPreview.ts +21 -0
  123. package/render/web/ui/useInlineEdit.ts +135 -0
  124. package/runtimeDoctorCommands.ts +118 -0
  125. package/scriptCommandEntries.ts +71 -0
  126. package/systemInternalCommandEntries.ts +71 -0
  127. package/tableCommands.ts +42 -5
  128. package/workflowInternalCommandEntries.ts +11 -0
@@ -1,773 +1,7 @@
1
- import type { MachineHeartbeat } from "./connector-experimental/protocol";
2
- import { detectMachineInfo } from "./connector-experimental/machineInfo";
3
- import { resolveAgentRuntimeDecision } from "./agent-runtime/runtimeDecision";
4
- import { resolveCliLocalRuntimeDbPath } from "./localRuntimeDb";
5
- import {
6
- assertMachineRunAllowed,
7
- buildMachinePermissionPromptBlock,
8
- resolveMachineRunPermissionPolicy,
9
- } from "./ai/agent/machineRunPermissions";
10
- import { resolveConnectorWebSocketTarget } from "./connectorWebSocketTarget";
11
- import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
12
- import { resolveCliAgentKeyInput } from "./agentAliases";
13
- import {
14
- collectConnectorRunArtifact,
15
- readConnectorGitHead,
16
- resolveConnectorRunCwd,
17
- } from "./connectorRunArtifact";
18
-
19
- type EnvLike = Record<string, string | undefined>;
20
- type OutputLike = { write(chunk: string): unknown };
21
- type SmokeWebSocketOptions = {
22
- headers: Record<string, string>;
23
- onMessage: (message: string) => void | Promise<void>;
24
- onOpen: () => void | Promise<void>;
25
- sentMessages: string[];
26
- };
27
- type LocalCliExecutor = (
28
- provider: string,
29
- prompt: string,
30
- options: { model?: string; timeout?: number; cwd?: string; yolo?: boolean }
31
- ) => Promise<{ text: string; raw?: string; elapsed?: number }>;
32
- type LocalRuntimeProbeResult = {
33
- ok: boolean;
34
- dbPath: string;
35
- agentFound: boolean;
36
- agentKey?: string;
37
- error?: string;
38
- };
39
-
40
- type AgentRuntimeCommandDeps = {
41
- env?: EnvLike;
42
- output?: OutputLike;
43
- fetchImpl?: typeof fetch;
44
- fallbackFetchImpl?: typeof fetch;
45
- machineInfo?: () => MachineHeartbeat;
46
- connectWebSocket?: (url: string, options: SmokeWebSocketOptions) => Promise<void>;
47
- executeCli?: LocalCliExecutor;
48
- localRuntimeProbe?: (env: EnvLike) => Promise<LocalRuntimeProbeResult>;
49
- };
50
-
51
- function resolveServerUrl(env: EnvLike) {
52
- return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
53
- }
54
-
55
- function resolveAuthToken(env: EnvLike) {
56
- return env.AUTH_TOKEN || env.AUTH || "";
57
- }
58
-
59
- function detectLocalAgentConfig(env: EnvLike) {
60
- return Boolean(readLocalAgentKey(env) || env.NOLO_AGENT_CACHE_READY);
61
- }
62
-
63
- function readLocalAgentKey(env: EnvLike) {
64
- return env.NOLO_LOCAL_AGENT_KEY || env.NOLO_AGENT || "";
65
- }
66
-
67
- function detectLocalProvider(env: EnvLike) {
68
- return Boolean(
69
- env.OPENAI_API_KEY ||
70
- env.ANTHROPIC_API_KEY ||
71
- env.GOOGLE_API_KEY ||
72
- env.NOLO_LOCAL_OPENAI_BASE_URL ||
73
- env.OLLAMA_BASE_URL
74
- );
75
- }
76
-
77
- function detectProviderLabel(env: EnvLike) {
78
- if (env.NOLO_LOCAL_OPENAI_BASE_URL) return `openai-compatible endpoint ${env.NOLO_LOCAL_OPENAI_BASE_URL}`;
79
- if (env.OLLAMA_BASE_URL) return `ollama-compatible endpoint ${env.OLLAMA_BASE_URL}`;
80
- if (env.OPENAI_API_KEY) return "openai via env OPENAI_API_KEY";
81
- if (env.ANTHROPIC_API_KEY) return "anthropic via env ANTHROPIC_API_KEY";
82
- if (env.GOOGLE_API_KEY) return "google via env GOOGLE_API_KEY";
83
- return "missing";
84
- }
85
-
86
- async function defaultLocalRuntimeProbe(env: EnvLike): Promise<LocalRuntimeProbeResult> {
87
- const dbPath = resolveCliLocalRuntimeDbPath({ env });
88
- const agentKey = readLocalAgentKey(env);
89
- try {
90
- const { getDefaultCliLocalRuntimeDb } = await import("./localRuntimeDb");
91
- const db = await getDefaultCliLocalRuntimeDb();
92
- if (!agentKey) {
93
- return { ok: true, dbPath, agentFound: false };
94
- }
95
- try {
96
- await db.get(agentKey);
97
- return { ok: true, dbPath, agentFound: true, agentKey };
98
- } catch {
99
- return { ok: true, dbPath, agentFound: false, agentKey };
100
- }
101
- } catch (error) {
102
- return {
103
- ok: false,
104
- dbPath,
105
- agentFound: false,
106
- ...(agentKey ? { agentKey } : {}),
107
- error: error instanceof Error ? error.message : String(error),
108
- };
109
- }
110
- }
111
-
112
- function parseUserIdFromAuthToken(token: string) {
113
- const payload = token.trim().split(".")[0];
114
- if (!payload) return "";
115
- try {
116
- const parsed = JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
117
- return typeof parsed?.userId === "string" ? parsed.userId : "";
118
- } catch {
119
- return "";
120
- }
121
- }
122
-
123
- function readOption(args: string[], flag: string) {
124
- const index = args.indexOf(flag);
125
- return index >= 0 ? args[index + 1] : undefined;
126
- }
127
-
128
- async function defaultExecuteCli(
129
- provider: string,
130
- prompt: string,
131
- options: { model?: string; timeout?: number; cwd?: string; yolo?: boolean }
132
- ) {
133
- const { executeCli } = await import("./ai/agent/cliExecutor");
134
- return executeCli(provider as any, prompt, options);
135
- }
136
-
137
- function detectLaunchableMachineInfo() {
138
- return detectMachineInfo({ probeLaunchable: true });
139
- }
140
-
141
- function buildConnectorCliPrompt(agentConfig: any, userInput: string) {
142
- const policy = resolveMachineRunPermissionPolicy(agentConfig);
143
- return [
144
- typeof agentConfig?.prompt === "string" ? agentConfig.prompt.trim() : "",
145
- buildMachinePermissionPromptBlock(policy),
146
- `--- User task ---\n${userInput}`,
147
- ].filter(Boolean).join("\n\n");
148
- }
149
-
150
- function normalizeConnectorRunTimeoutMs(value: unknown): number | undefined {
151
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
152
- return undefined;
153
- }
154
- return Math.floor(value);
155
- }
156
-
157
- function requiredCapabilityForAgent(agent: any) {
158
- if (agent?.apiSource !== "cli") return "";
159
- const cliProvider = String(agent?.cliProvider || "").trim();
160
- const capabilityByProvider: Record<string, string> = {
161
- codex: "codex-cli",
162
- claude: "claude-code",
163
- copilot: "copilot-cli",
164
- gemini: "gemini-cli",
165
- kimi: "kimi-cli",
166
- };
167
- return capabilityByProvider[cliProvider] ?? "";
168
- }
169
-
170
- function classifyAgentRuntime(agent: any) {
171
- if (agent?.apiSource === "cli") return "cli-machine";
172
- if (agent?.apiSource === "platform" || agent?.useServerProxy) return "platform-local-loop";
173
- if (agent?.apiSource === "local") return "local-provider-loop";
174
- return "unknown";
175
- }
176
-
177
- function shouldRuntimeDoctorPass(args: {
178
- runtimeClass: string;
179
- requiredCapability: string;
180
- hasCapability: boolean;
181
- }) {
182
- if (args.runtimeClass !== "cli-machine") return args.runtimeClass !== "unknown";
183
- return Boolean(args.requiredCapability && args.hasCapability);
184
- }
185
-
186
- function assertSmokeCompatible(agent: any, machine: MachineHeartbeat) {
187
- if (agent?.apiSource !== "cli") {
188
- throw new Error(`Agent ${agent?.name ?? agent?.dbKey ?? "unknown"} is not a CLI agent.`);
189
- }
190
- const requiredCapability = requiredCapabilityForAgent(agent);
191
- if (!requiredCapability) {
192
- throw new Error(`Agent ${agent?.name ?? agent?.dbKey ?? "unknown"} has unsupported cliProvider: ${agent?.cliProvider ?? "missing"}.`);
193
- }
194
- if (!machine.capabilities.includes(requiredCapability)) {
195
- const currentCapabilities = machine.capabilities.length
196
- ? machine.capabilities.join(", ")
197
- : "none";
198
- throw new Error(
199
- `Agent ${agent?.name ?? agent?.dbKey ?? "unknown"} requires ${requiredCapability}; current machine capabilities: ${currentCapabilities}.`
200
- );
201
- }
202
- }
203
-
204
- const CONNECTOR_WS_KEEPALIVE_MS = 25_000;
205
-
206
- function buildConnectorKeepaliveMessage() {
207
- return JSON.stringify({ type: "connector.keepalive", sentAt: Date.now() });
208
- }
209
-
210
- async function defaultConnectWebSocket(url: string, options: SmokeWebSocketOptions) {
211
- const WebSocketCtor = globalThis.WebSocket;
212
- if (!WebSocketCtor) {
213
- throw new Error("WebSocket is not available in this runtime");
214
- }
215
- await new Promise<void>((resolve, reject) => {
216
- const ws = new WebSocketCtor(url, { headers: options.headers } as any);
217
- let keepalive: ReturnType<typeof setInterval> | null = null;
218
- const clearKeepalive = () => {
219
- if (keepalive) {
220
- clearInterval(keepalive);
221
- keepalive = null;
222
- }
223
- };
224
- ws.addEventListener("open", () => {
225
- keepalive = setInterval(() => {
226
- try {
227
- ws.send(buildConnectorKeepaliveMessage());
228
- } catch {
229
- clearKeepalive();
230
- }
231
- }, CONNECTOR_WS_KEEPALIVE_MS);
232
- Promise.resolve(options.onOpen())
233
- .then(() => ws.close())
234
- .catch((error) => {
235
- ws.close();
236
- reject(error);
237
- });
238
- }, { once: true });
239
- ws.addEventListener("error", () => {
240
- clearKeepalive();
241
- reject(new Error("connector websocket failed"));
242
- });
243
- ws.addEventListener("close", () => {
244
- clearKeepalive();
245
- resolve();
246
- }, { once: true });
247
- ws.addEventListener("message", (event) => {
248
- const startIndex = options.sentMessages.length;
249
- Promise.resolve(options.onMessage(String(event.data))).then(() => {
250
- for (const message of options.sentMessages.slice(startIndex)) {
251
- ws.send(message);
252
- }
253
- }).catch(reject);
254
- });
255
- });
256
- }
257
-
258
- async function handleSmokeConnectorMessage(
259
- message: string,
260
- send: (message: string) => void,
261
- executeCli: LocalCliExecutor
262
- ) {
263
- let parsed: any;
264
- try {
265
- parsed = JSON.parse(message);
266
- } catch {
267
- return;
268
- }
269
- if (parsed?.type !== "agent.run" || typeof parsed.requestId !== "string") return;
270
- const agentConfig = parsed.payload?.agentConfig ?? {};
271
- try {
272
- if (agentConfig.apiSource !== "cli") {
273
- throw new Error("Connector can only execute CLI agents. Set the agent apiSource to cli and choose a cliProvider.");
274
- }
275
- const provider = String(agentConfig.cliProvider || "copilot");
276
- const policy = resolveMachineRunPermissionPolicy(agentConfig);
277
- const userInput = String(parsed.payload?.userInput ?? "");
278
- const timeout = normalizeConnectorRunTimeoutMs(parsed.payload?.timeoutMs);
279
- assertMachineRunAllowed(userInput, policy);
280
- const cwd = resolveConnectorRunCwd({ env: process.env, policy });
281
- const baseSha = await readConnectorGitHead(cwd);
282
- const result = await executeCli(
283
- provider,
284
- buildConnectorCliPrompt(agentConfig, userInput),
285
- {
286
- model: agentConfig.model || undefined,
287
- timeout,
288
- cwd,
289
- yolo: true,
290
- }
291
- );
292
- const artifacts = await collectConnectorRunArtifact({
293
- cwd,
294
- baseSha,
295
- exitStatus: "completed",
296
- });
297
- send(JSON.stringify({
298
- type: "agent.run.result",
299
- requestId: parsed.requestId,
300
- result: {
301
- content: result.text,
302
- model: agentConfig.model ?? provider,
303
- trace: [{ role: "assistant", content: result.text }],
304
- artifacts,
305
- },
306
- }));
307
- } catch (error) {
308
- send(JSON.stringify({
309
- type: "agent.run.result",
310
- requestId: parsed.requestId,
311
- error: error instanceof Error ? error.message : String(error),
312
- }));
313
- }
314
- }
315
-
316
- async function readAgentRecord(args: {
317
- agentKey: string;
318
- authToken: string;
319
- fallbackFetchImpl?: typeof fetch;
320
- fetchImpl: typeof fetch;
321
- serverUrl: string;
322
- }) {
323
- const res = await fetchWithTransportFallback(
324
- `${args.serverUrl}/api/v1/db/read/${encodeURIComponent(args.agentKey)}`,
325
- {
326
- method: "GET",
327
- headers: { Authorization: `Bearer ${args.authToken}` },
328
- },
329
- args
330
- );
331
- const data = await res.json().catch(() => ({}));
332
- if (!res.ok) {
333
- throw new Error(`read failed: HTTP ${res.status} ${JSON.stringify(data)}`);
334
- }
335
- return data?.data ?? data;
336
- }
337
-
338
- async function writeAgentRecord(args: {
339
- agentKey: string;
340
- authToken: string;
341
- fallbackFetchImpl?: typeof fetch;
342
- fetchImpl: typeof fetch;
343
- serverUrl: string;
344
- userId: string;
345
- record: Record<string, any>;
346
- }) {
347
- const res = await fetchWithTransportFallback(`${args.serverUrl}/api/v1/db/write/`, {
348
- method: "POST",
349
- headers: {
350
- Authorization: `Bearer ${args.authToken}`,
351
- "Content-Type": "application/json",
352
- },
353
- body: JSON.stringify({
354
- customKey: args.agentKey,
355
- userId: args.userId,
356
- data: {
357
- ...args.record,
358
- dbKey: args.agentKey,
359
- },
360
- }),
361
- }, args);
362
- const data = await res.json().catch(() => ({}));
363
- if (!res.ok) {
364
- throw new Error(`write failed: HTTP ${res.status} ${JSON.stringify(data)}`);
365
- }
366
- }
367
-
368
- function shouldUseCurlTransportFallback(error: unknown) {
369
- const message = error instanceof Error ? error.message : String(error);
370
- return /Unable to connect|ConnectionRefused|ECONNREFUSED|Failed to connect|Was there a typo|timed out|Timeout|handshake|certificate|ECONNRESET|socket|network/i.test(message);
371
- }
372
-
373
- async function curlFetch(url: string, init?: RequestInit): Promise<Response> {
374
- const method = init?.method ?? "GET";
375
- const headers = new Headers(init?.headers ?? {});
376
- const command = ["curl", "-sS", "-L", "-X", method];
377
-
378
- headers.forEach((value, key) => {
379
- command.push("-H", `${key}: ${value}`);
380
- });
381
-
382
- if (typeof init?.body === "string" && init.body.length > 0) {
383
- command.push("--data", init.body);
384
- }
385
-
386
- command.push("-w", "\n__NOLO_STATUS__:%{http_code}", url);
387
- const proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
388
- const [stdout, stderr, exitCode] = await Promise.all([
389
- new Response(proc.stdout).text(),
390
- new Response(proc.stderr).text(),
391
- proc.exited,
392
- ]);
393
- if (exitCode !== 0) {
394
- throw new Error(stderr.trim() || `curl failed for ${url}`);
395
- }
396
-
397
- const marker = "\n__NOLO_STATUS__:";
398
- const markerIndex = stdout.lastIndexOf(marker);
399
- const body = markerIndex >= 0 ? stdout.slice(0, markerIndex) : stdout;
400
- const statusText = markerIndex >= 0 ? stdout.slice(markerIndex + marker.length).trim() : "0";
401
- const status = Number(statusText);
402
- return new Response(body, {
403
- status: Number.isFinite(status) ? status : 0,
404
- headers: { "Content-Type": headers.get("Content-Type") ?? "application/json" },
405
- });
406
- }
407
-
408
- async function fetchWithTransportFallback(
409
- url: string,
410
- init: RequestInit,
411
- options: { fallbackFetchImpl?: typeof fetch; fetchImpl: typeof fetch }
412
- ): Promise<Response> {
413
- try {
414
- return await options.fetchImpl(url, init);
415
- } catch (error) {
416
- if (!shouldUseCurlTransportFallback(error)) throw error;
417
- if (options.fallbackFetchImpl) {
418
- return options.fallbackFetchImpl(url, init);
419
- }
420
- if (options.fetchImpl !== fetch) throw error;
421
- return curlFetch(url, init);
422
- }
423
- }
424
-
425
- async function heartbeatCurrentMachine(args: {
426
- authToken: string;
427
- fetchImpl: typeof fetch;
428
- machine: MachineHeartbeat;
429
- serverUrl: string;
430
- }) {
431
- const res = await args.fetchImpl(`${args.serverUrl}/api/machines/heartbeat`, {
432
- method: "POST",
433
- headers: {
434
- Authorization: `Bearer ${args.authToken}`,
435
- "Content-Type": "application/json",
436
- },
437
- body: JSON.stringify(args.machine),
438
- });
439
- const text = await res.text().catch(() => "");
440
- if (!res.ok) {
441
- throw new Error(`heartbeat failed: HTTP ${res.status} ${text}`);
442
- }
443
- }
444
-
445
- export async function runAgentBindCurrentCommand(
446
- args: string[],
447
- deps: AgentRuntimeCommandDeps = {}
448
- ) {
449
- const env = deps.env ?? process.env;
450
- const output = deps.output ?? process.stdout;
451
- const agentKey = args[0]?.trim();
452
- if (!agentKey || agentKey === "--help" || agentKey === "-h") {
453
- output.write("Usage: nolo agent bind-current <agentKey>\n");
454
- return agentKey ? 0 : 1;
455
- }
456
-
457
- const authToken = resolveAuthToken(env);
458
- if (!authToken) {
459
- output.write("[nolo] agent bind-current requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
460
- return 1;
461
- }
462
-
463
- const userId = parseUserIdFromAuthToken(authToken);
464
- if (!userId) {
465
- output.write("[nolo] agent bind-current could not read userId from AUTH_TOKEN.\n");
466
- return 1;
467
- }
468
-
469
- const serverUrl = resolveServerUrl(env);
470
- const fetchImpl = deps.fetchImpl ?? fetch;
471
- const fallbackFetchImpl = deps.fallbackFetchImpl;
472
- const machine = (deps.machineInfo ?? detectLaunchableMachineInfo)();
473
-
474
- try {
475
- await heartbeatCurrentMachine({ authToken, fetchImpl, machine, serverUrl });
476
- const existing = await readAgentRecord({ agentKey, authToken, fallbackFetchImpl, fetchImpl, serverUrl });
477
- const updated = {
478
- ...existing,
479
- runtimeBinding: {
480
- ...(existing?.runtimeBinding && typeof existing.runtimeBinding === "object"
481
- ? existing.runtimeBinding
482
- : {}),
483
- machineId: machine.machineId,
484
- ownerUserId: userId,
485
- },
486
- updatedAt: Date.now(),
487
- };
488
- await writeAgentRecord({
489
- agentKey,
490
- authToken,
491
- fallbackFetchImpl,
492
- fetchImpl,
493
- serverUrl,
494
- userId,
495
- record: updated,
496
- });
497
- } catch (error) {
498
- output.write(
499
- `[nolo] agent bind-current failed: ${
500
- error instanceof Error ? error.message : String(error)
501
- }\n`
502
- );
503
- return 1;
504
- }
505
-
506
- output.write(`Bound agent ${agentKey} to this machine: ${machine.name} (${machine.machineId})\n`);
507
- return 0;
508
- }
509
-
510
- export async function runAgentReadCommand(
511
- args: string[],
512
- deps: AgentRuntimeCommandDeps = {}
513
- ) {
514
- const env = deps.env ?? process.env;
515
- const output = deps.output ?? process.stdout;
516
- const agentInput = args[0]?.trim();
517
- if (!agentInput || agentInput === "--help" || agentInput === "-h") {
518
- output.write("Usage: nolo agent read <agent>\n");
519
- return agentInput ? 0 : 1;
520
- }
521
-
522
- const authToken = resolveAuthToken(env);
523
- if (!authToken) {
524
- output.write("[nolo] agent read requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
525
- return 1;
526
- }
527
-
528
- const agentKey = resolveCliAgentKeyInput(agentInput);
529
- const serverUrl = resolveServerUrl(env);
530
- const fetchImpl = deps.fetchImpl ?? fetch;
531
- const fallbackFetchImpl = deps.fallbackFetchImpl;
532
-
533
- try {
534
- const agent = await readAgentRecord({ agentKey, authToken, fallbackFetchImpl, fetchImpl, serverUrl });
535
- output.write(JSON.stringify({
536
- agentKey,
537
- baseUrl: serverUrl,
538
- name: agent?.name,
539
- greeting: agent?.greeting,
540
- model: agent?.model,
541
- provider: agent?.provider ?? agent?.apiSource ?? null,
542
- customProviderUrl: agent?.customProviderUrl ?? null,
543
- tools: agent?.tools ?? [],
544
- isPublic: agent?.isPublic,
545
- authUserId: parseUserIdFromAuthToken(authToken),
546
- userId: agent?.userId,
547
- record: agent,
548
- }, null, 2));
549
- output.write("\n");
550
- return 0;
551
- } catch (error) {
552
- output.write(
553
- `[nolo] agent read failed: ${
554
- error instanceof Error ? error.message : String(error)
555
- }\n`
556
- );
557
- return 1;
558
- }
559
- }
560
-
561
- export async function runAgentSmokeCurrentCommand(
562
- args: string[],
563
- deps: AgentRuntimeCommandDeps = {}
564
- ) {
565
- const env = deps.env ?? process.env;
566
- const output = deps.output ?? process.stdout;
567
- const agentInput = args[0]?.trim();
568
- if (!agentInput || agentInput === "--help" || agentInput === "-h") {
569
- output.write("Usage: nolo agent smoke-current <agent> --msg \"hello\"\n");
570
- return agentInput ? 0 : 1;
571
- }
572
- const agentKey = resolveCliAgentKeyInput(agentInput);
573
-
574
- const authToken = resolveAuthToken(env);
575
- if (!authToken) {
576
- output.write("[nolo] agent smoke-current requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
577
- return 1;
578
- }
579
- const userId = parseUserIdFromAuthToken(authToken);
580
- if (!userId) {
581
- output.write("[nolo] agent smoke-current could not read userId from AUTH_TOKEN.\n");
582
- return 1;
583
- }
584
-
585
- const userInput = readOption(args, "--msg") ?? "Smoke test from nolo connector.";
586
- const serverUrl = resolveServerUrl(env);
587
- const fetchImpl = deps.fetchImpl ?? fetch;
588
- const fallbackFetchImpl = deps.fallbackFetchImpl;
589
- const machine = (deps.machineInfo ?? detectLaunchableMachineInfo)();
590
- const sentMessages: string[] = [];
591
-
592
- try {
593
- await heartbeatCurrentMachine({ authToken, fetchImpl, machine, serverUrl });
594
- const existing = await readAgentRecord({ agentKey, authToken, fallbackFetchImpl, fetchImpl, serverUrl });
595
- assertSmokeCompatible(existing, machine);
596
- await writeAgentRecord({
597
- agentKey,
598
- authToken,
599
- fallbackFetchImpl,
600
- fetchImpl,
601
- serverUrl,
602
- userId,
603
- record: {
604
- ...existing,
605
- runtimeBinding: {
606
- ...(existing?.runtimeBinding && typeof existing.runtimeBinding === "object"
607
- ? existing.runtimeBinding
608
- : {}),
609
- machineId: machine.machineId,
610
- ownerUserId: userId,
611
- },
612
- updatedAt: Date.now(),
613
- },
614
- });
615
-
616
- let runResponse: any = null;
617
- const wsTarget = await resolveConnectorWebSocketTarget({
618
- serverUrl,
619
- machineId: machine.machineId,
620
- headers: { Authorization: `Bearer ${authToken}` },
621
- fetchImpl,
622
- });
623
- await (deps.connectWebSocket ?? defaultConnectWebSocket)(
624
- wsTarget,
625
- {
626
- headers: { Authorization: `Bearer ${authToken}` },
627
- sentMessages,
628
- onMessage: (message) => handleSmokeConnectorMessage(
629
- message,
630
- (response) => sentMessages.push(response),
631
- deps.executeCli ?? defaultExecuteCli
632
- ),
633
- onOpen: async () => {
634
- const res = await fetchImpl(`${serverUrl}/api/agent/run`, {
635
- method: "POST",
636
- headers: {
637
- Authorization: `Bearer ${authToken}`,
638
- "Content-Type": "application/json",
639
- },
640
- body: JSON.stringify({
641
- agentKey,
642
- userInput,
643
- stream: false,
644
- }),
645
- });
646
- runResponse = await res.json().catch(() => ({}));
647
- if (!res.ok) {
648
- throw new Error(`agent run failed: HTTP ${res.status} ${JSON.stringify(runResponse)}`);
649
- }
650
- },
651
- }
652
- );
653
- output.write(`Smoke OK: ${runResponse?.dialogId ?? "no-dialog"}\n`);
654
- if (runResponse?.content) output.write(`${runResponse.content}\n`);
655
- return 0;
656
- } catch (error) {
657
- output.write(
658
- `[nolo] agent smoke-current failed: ${
659
- error instanceof Error ? error.message : String(error)
660
- }\n`
661
- );
662
- return 1;
663
- }
664
- }
665
-
666
- export async function runAgentRuntimeDoctorCommand(
667
- args: string[],
668
- deps: AgentRuntimeCommandDeps = {}
669
- ) {
670
- const env = deps.env ?? process.env;
671
- const output = deps.output ?? process.stdout;
672
- const agentInput = args[0]?.trim();
673
- if (!agentInput || agentInput === "--help" || agentInput === "-h") {
674
- output.write("Usage: nolo agent runtime-doctor <agentKey>\n");
675
- return agentInput ? 0 : 1;
676
- }
677
- const agentKey = resolveCliAgentKeyInput(agentInput);
678
-
679
- const authToken = resolveAuthToken(env);
680
- if (!authToken) {
681
- output.write("[nolo] agent runtime-doctor requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
682
- return 1;
683
- }
684
-
685
- const serverUrl = resolveServerUrl(env);
686
- const fetchImpl = deps.fetchImpl ?? fetch;
687
- const fallbackFetchImpl = deps.fallbackFetchImpl;
688
- const machine = (deps.machineInfo ?? detectLaunchableMachineInfo)();
689
-
690
- try {
691
- const agent = await readAgentRecord({ agentKey, authToken, fallbackFetchImpl, fetchImpl, serverUrl });
692
- const requiredCapability = requiredCapabilityForAgent(agent);
693
- const runtimeClass = classifyAgentRuntime(agent);
694
- const isBoundToCurrent = agent?.runtimeBinding?.machineId === machine.machineId;
695
- const hasCapability = requiredCapability
696
- ? machine.capabilities.includes(requiredCapability)
697
- : false;
698
- output.write(`Agent runtime doctor: ${agent?.name ?? agentKey}\n`);
699
- if (agentInput !== agentKey) output.write(`Agent input: ${agentInput}\n`);
700
- output.write(`Agent key: ${agentKey}\n`);
701
- output.write(`Runtime class: ${runtimeClass}\n`);
702
- output.write(`API source: ${agent?.apiSource ?? "unknown"}\n`);
703
- output.write(`CLI provider: ${agent?.cliProvider ?? "none"}\n`);
704
- output.write(`Required capability: ${requiredCapability || "none"}\n`);
705
- output.write(`Current machine: ${machine.name} (${machine.machineId})\n`);
706
- output.write(`Current machine capabilities: ${machine.capabilities.length ? machine.capabilities.join(", ") : "none"}\n`);
707
- output.write(`Current machine binding: ${runtimeClass === "cli-machine" ? (isBoundToCurrent ? "yes" : "no") : "not required"}\n`);
708
- output.write(`Current machine has required capability: ${requiredCapability ? (hasCapability ? "yes" : "no") : "not required"}\n`);
709
-
710
- return shouldRuntimeDoctorPass({ runtimeClass, requiredCapability, hasCapability }) ? 0 : 1;
711
- } catch (error) {
712
- output.write(
713
- `[nolo] agent runtime-doctor failed: ${
714
- error instanceof Error ? error.message : String(error)
715
- }\n`
716
- );
717
- return 1;
718
- }
719
- }
720
-
721
- export async function runDoctorRuntimeCommand(
722
- _args: string[],
723
- deps: AgentRuntimeCommandDeps = {}
724
- ) {
725
- const env = deps.env ?? process.env;
726
- const output = deps.output ?? process.stdout;
727
- const authToken = resolveAuthToken(env);
728
- const serverUrl = resolveServerUrl(env);
729
- const localProbe = await (deps.localRuntimeProbe ?? defaultLocalRuntimeProbe)(env);
730
- const hasLocalAgentConfig = detectLocalAgentConfig(env) && localProbe.ok && localProbe.agentFound;
731
- const hasLocalProvider = detectLocalProvider(env);
732
- const missingLocalCapabilities = [
733
- ...(localProbe.ok ? [] : ["leveldb"]),
734
- ...(hasLocalAgentConfig ? [] : ["agent-config"]),
735
- ...(hasLocalProvider ? [] : ["provider"]),
736
- ];
737
- const decision = resolveAgentRuntimeDecision({
738
- requestedMode: "auto",
739
- syncRequested: false,
740
- host: "cli",
741
- hasLocalAgentConfig,
742
- hasLocalProvider,
743
- hasLocalPersistence: true,
744
- missingLocalCapabilities,
745
- requiresServer: false,
746
- serverFallbackAvailable: Boolean(authToken || env.NOLO_SERVER || env.BASE_URL),
747
- });
748
-
749
- output.write(`Runtime: ${decision.mode}\n`);
750
- output.write(`Reason: ${decision.reason}\n`);
751
- output.write(`LevelDB: ${localProbe.ok ? "ok" : "failed"}\n`);
752
- output.write(`DB path: ${localProbe.dbPath}\n`);
753
- if (!localProbe.ok && localProbe.error) {
754
- output.write(`DB error: ${localProbe.error}\n`);
755
- }
756
- if (localProbe.agentKey) {
757
- output.write(`Agent config: ${localProbe.agentFound ? "found" : "missing"} (${localProbe.agentKey})\n`);
758
- } else {
759
- output.write("Agent config: missing (no local agent key)\n");
760
- }
761
- output.write(`Provider: ${hasLocalProvider ? "available" : "missing"} (${detectProviderLabel(env)})\n`);
762
- output.write("Persistence: LevelDB local store\n");
763
- output.write(`Sync: ${authToken ? "available" : "unavailable"}${authToken ? "" : " (not authenticated)"}\n`);
764
- output.write(`Server fallback: ${decision.runnable && decision.mode === "server" ? serverUrl : authToken || env.NOLO_SERVER || env.BASE_URL ? serverUrl : "unavailable"}\n`);
765
- if (decision.missingLocalCapabilities.length > 0) {
766
- output.write("Missing local capabilities:\n");
767
- for (const capability of decision.missingLocalCapabilities) {
768
- output.write(`- ${capability}\n`);
769
- }
770
- }
771
-
772
- return decision.runnable ? 0 : 1;
773
- }
1
+ export { runAgentListCommand } from "./agentListCommands";
2
+ export {
3
+ runAgentBindCurrentCommand,
4
+ runAgentRuntimeDoctorCommand,
5
+ runAgentSmokeCurrentCommand,
6
+ } from "./agentMachineCommands";
7
+ export { runDoctorRuntimeCommand } from "./runtimeDoctorCommands";