nolo-cli 0.1.7 → 0.1.9

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 (247) hide show
  1. package/README.md +107 -5
  2. package/agentRuntimeCommands.ts +464 -0
  3. package/ai/agent/_executeModel.ts +118 -0
  4. package/ai/agent/agentSlice.ts +525 -0
  5. package/ai/agent/appWorkingMemory.ts +126 -0
  6. package/ai/agent/avatarUtils.ts +24 -0
  7. package/ai/agent/buildEditingContext.ts +373 -0
  8. package/ai/agent/buildSystemPrompt.ts +532 -0
  9. package/ai/agent/cleanAgentMessages.ts +140 -0
  10. package/ai/agent/cliChatClient.ts +119 -0
  11. package/ai/agent/cliExecutor.ts +733 -0
  12. package/ai/agent/cliPrompt.ts +10 -0
  13. package/ai/agent/contextCompiler.ts +107 -0
  14. package/ai/agent/contextLayerContract.ts +44 -0
  15. package/ai/agent/createAgentSchema.ts +234 -0
  16. package/ai/agent/executeToolCall.ts +58 -0
  17. package/ai/agent/fetchAgentContexts.ts +42 -0
  18. package/ai/agent/generatePrompt.ts +3 -0
  19. package/ai/agent/getFullChatContextKeys.ts +168 -0
  20. package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
  21. package/ai/agent/hooks/useAgentConfig.ts +61 -0
  22. package/ai/agent/hooks/useAgentDialog.ts +35 -0
  23. package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
  24. package/ai/agent/hooks/usePublicAgents.ts +473 -0
  25. package/ai/agent/machineRunPermissions.ts +95 -0
  26. package/ai/agent/persistMessageWithFixedId.ts +37 -0
  27. package/ai/agent/planSlice.ts +259 -0
  28. package/ai/agent/referenceUtils.ts +229 -0
  29. package/ai/agent/runAgentBackground.ts +238 -0
  30. package/ai/agent/runAgentClientLoop.ts +138 -0
  31. package/ai/agent/runtimeGuidance.ts +97 -0
  32. package/ai/agent/runtimeServerBase.ts +37 -0
  33. package/ai/agent/server/fetchPublicAgents.ts +128 -0
  34. package/ai/agent/startParallelAgentStreams.ts +424 -0
  35. package/ai/agent/startupProtocol.ts +53 -0
  36. package/ai/agent/streamAgentChatTurn.ts +1278 -0
  37. package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
  38. package/ai/agent/types.ts +71 -0
  39. package/ai/agent/utils/imageOutput.ts +33 -0
  40. package/ai/agent/utils/sortUtils.ts +250 -0
  41. package/ai/agent/web/referencePickerUtils.ts +146 -0
  42. package/ai/ai.locale.ts +1075 -0
  43. package/ai/chat/accumulateToolCallChunks.ts +95 -0
  44. package/ai/chat/fetchUtils.native.ts +276 -0
  45. package/ai/chat/fetchUtils.ts +153 -0
  46. package/ai/chat/parseApiError.ts +64 -0
  47. package/ai/chat/parseMultilineSSE.ts +95 -0
  48. package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
  49. package/ai/chat/sendOpenAICompletionsRequest.ts +703 -0
  50. package/ai/chat/sendOpenAIResponseRequest.ts +491 -0
  51. package/ai/chat/shouldUseServerProxy.ts +18 -0
  52. package/ai/chat/sseClient.native.ts +91 -0
  53. package/ai/chat/sseClient.ts +67 -0
  54. package/ai/chat/streamReader.native.ts +31 -0
  55. package/ai/chat/streamReader.ts +62 -0
  56. package/ai/chat/updateTotalUsage.ts +72 -0
  57. package/ai/context/buildReferenceContext.ts +437 -0
  58. package/ai/context/calculateContextUsage.ts +133 -0
  59. package/ai/context/retention.ts +165 -0
  60. package/ai/context/tokenUtils.ts +78 -0
  61. package/ai/index.ts +1 -0
  62. package/ai/llm/calculateGeminiImageTokens.ts +57 -0
  63. package/ai/llm/deepinfra.ts +28 -0
  64. package/ai/llm/fireworks.ts +50 -0
  65. package/ai/llm/generateRequestBody.ts +165 -0
  66. package/ai/llm/getModelContextWindow.ts +84 -0
  67. package/ai/llm/getNoloKey.ts +31 -0
  68. package/ai/llm/getPricing.ts +199 -0
  69. package/ai/llm/hooks/useModelPricing.ts +75 -0
  70. package/ai/llm/imagePricing.ts +40 -0
  71. package/ai/llm/isResponseAPIModel.ts +13 -0
  72. package/ai/llm/mimo.ts +71 -0
  73. package/ai/llm/mistral.ts +22 -0
  74. package/ai/llm/modelAvatar.ts +427 -0
  75. package/ai/llm/models.ts +45 -0
  76. package/ai/llm/openrouterModels.ts +269 -0
  77. package/ai/llm/providers.ts +306 -0
  78. package/ai/llm/reasoningModels.ts +28 -0
  79. package/ai/llm/types.ts +59 -0
  80. package/ai/llm/usageRequestOptions.ts +59 -0
  81. package/ai/memory/capture.ts +148 -0
  82. package/ai/memory/consolidate.ts +104 -0
  83. package/ai/memory/delete.ts +147 -0
  84. package/ai/memory/overlay.ts +84 -0
  85. package/ai/memory/query.ts +38 -0
  86. package/ai/memory/queryShared.ts +160 -0
  87. package/ai/memory/rank.ts +105 -0
  88. package/ai/memory/recentRelationshipRecap.ts +249 -0
  89. package/ai/memory/remember.ts +167 -0
  90. package/ai/memory/runtime.ts +76 -0
  91. package/ai/memory/store.ts +20 -0
  92. package/ai/memory/storeShared.ts +76 -0
  93. package/ai/memory/types.ts +46 -0
  94. package/ai/memory/understanding.ts +349 -0
  95. package/ai/memory/understandingGreeting.ts +264 -0
  96. package/ai/messages/type.ts +20 -0
  97. package/ai/policy/personalizationDialog.ts +333 -0
  98. package/ai/policy/runtimePolicy.ts +440 -0
  99. package/ai/policy/selfUpdateFields.ts +48 -0
  100. package/ai/policy/types.ts +64 -0
  101. package/ai/skills/referenceRuntime.ts +274 -0
  102. package/ai/skills/skillDiagnostics.ts +251 -0
  103. package/ai/skills/skillDocBuilder.ts +139 -0
  104. package/ai/skills/skillDocProtocol.ts +434 -0
  105. package/ai/skills/skillReferenceSummary.ts +63 -0
  106. package/ai/skills/skillSummaryMarker.ts +26 -0
  107. package/ai/token/calculatePrice.ts +544 -0
  108. package/ai/token/db.ts +98 -0
  109. package/ai/token/externalToolCost.ts +330 -0
  110. package/ai/token/hooks/useRecords.ts +65 -0
  111. package/ai/token/missingUsageEstimate.ts +42 -0
  112. package/ai/token/modelUsageQuery.ts +252 -0
  113. package/ai/token/normalizeUsage.ts +84 -0
  114. package/ai/token/openaiImageGenerationUsage.ts +56 -0
  115. package/ai/token/prepareTokenUsageData.ts +88 -0
  116. package/ai/token/query.ts +88 -0
  117. package/ai/token/queryUserTokens.ts +59 -0
  118. package/ai/token/resolveBillingTarget.ts +52 -0
  119. package/ai/token/saveTokenRecord.ts +53 -0
  120. package/ai/token/serverDialogProjection.ts +78 -0
  121. package/ai/token/serverTokenWriter.ts +143 -0
  122. package/ai/token/stats.ts +21 -0
  123. package/ai/token/tokenThunks.ts +24 -0
  124. package/ai/token/types.ts +93 -0
  125. package/ai/tools/agent/agentTools.ts +176 -0
  126. package/ai/tools/agent/agentUpdateShared.ts +311 -0
  127. package/ai/tools/agent/callAgentTool.ts +139 -0
  128. package/ai/tools/agent/createAgentTool.ts +512 -0
  129. package/ai/tools/agent/createDialogTool.ts +69 -0
  130. package/ai/tools/agent/createSkillAgentTool.ts +62 -0
  131. package/ai/tools/agent/parallelBudget.ts +221 -0
  132. package/ai/tools/agent/presets/appBuilderPreset.ts +145 -0
  133. package/ai/tools/agent/runLlmTool.ts +96 -0
  134. package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
  135. package/ai/tools/agent/skillAgentArgs.ts +106 -0
  136. package/ai/tools/agent/skillAgentPreset.ts +89 -0
  137. package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
  138. package/ai/tools/agent/updateAgentTool.ts +96 -0
  139. package/ai/tools/agent/updateSelfTool.ts +113 -0
  140. package/ai/tools/amazonProductScraperTool.ts +86 -0
  141. package/ai/tools/apifyActorClient.ts +45 -0
  142. package/ai/tools/appEditGuard.ts +372 -0
  143. package/ai/tools/appReadSnapshot.ts +153 -0
  144. package/ai/tools/appTools.ts +1549 -0
  145. package/ai/tools/applyEditTool.ts +256 -0
  146. package/ai/tools/applyLineEditsTool.ts +312 -0
  147. package/ai/tools/browserTools/click.ts +33 -0
  148. package/ai/tools/browserTools/closeSession.ts +29 -0
  149. package/ai/tools/browserTools/common.ts +27 -0
  150. package/ai/tools/browserTools/openSession.ts +48 -0
  151. package/ai/tools/browserTools/readContent.ts +38 -0
  152. package/ai/tools/browserTools/selectOption.ts +46 -0
  153. package/ai/tools/browserTools/typeText.ts +42 -0
  154. package/ai/tools/category/createCategoryTool.ts +66 -0
  155. package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
  156. package/ai/tools/category/updateContentCategoryTool.ts +75 -0
  157. package/ai/tools/cfBrowserTools.ts +319 -0
  158. package/ai/tools/cfSpeechToTextTool.ts +49 -0
  159. package/ai/tools/checkEnvTool.ts +65 -0
  160. package/ai/tools/cloudflareCrawlTool.ts +289 -0
  161. package/ai/tools/codeSearchTool.ts +111 -0
  162. package/ai/tools/codeTools.ts +101 -0
  163. package/ai/tools/createDocTool.ts +132 -0
  164. package/ai/tools/createPlanTool.ts +999 -0
  165. package/ai/tools/createSkillDocTool.ts +155 -0
  166. package/ai/tools/createWorkflowTool.ts +154 -0
  167. package/ai/tools/deepseekOcrTool.ts +34 -0
  168. package/ai/tools/delayTool.ts +31 -0
  169. package/ai/tools/deleteSpacesTool.ts +325 -0
  170. package/ai/tools/deleteSpacesToolModel.ts +159 -0
  171. package/ai/tools/devReloadUtils.ts +29 -0
  172. package/ai/tools/dialogMessageSearch.ts +137 -0
  173. package/ai/tools/doctorSkillTool.ts +72 -0
  174. package/ai/tools/ecommerceScraperTool.ts +86 -0
  175. package/ai/tools/emailTools.ts +549 -0
  176. package/ai/tools/evalSkillTool.ts +92 -0
  177. package/ai/tools/exaSearchTool.ts +64 -0
  178. package/ai/tools/execBashTool.ts +379 -0
  179. package/ai/tools/executeSqlTool.ts +192 -0
  180. package/ai/tools/fetchWebpageSupport.ts +309 -0
  181. package/ai/tools/fetchWebpageTool.ts +84 -0
  182. package/ai/tools/geminiImagePreviewTool.ts +361 -0
  183. package/ai/tools/generateDocxTool.ts +215 -0
  184. package/ai/tools/googleSearchScraperTool.ts +106 -0
  185. package/ai/tools/importDataTool.ts +133 -0
  186. package/ai/tools/importSkillTool.ts +162 -0
  187. package/ai/tools/index.ts +1858 -0
  188. package/ai/tools/listFilesTool.ts +82 -0
  189. package/ai/tools/listUserSpacesTool.ts +113 -0
  190. package/ai/tools/modelUsageTools.ts +142 -0
  191. package/ai/tools/olmOcrTool.ts +34 -0
  192. package/ai/tools/openaiImageTool.ts +218 -0
  193. package/ai/tools/paddleOcrTool.ts +34 -0
  194. package/ai/tools/prepareTools.ts +23 -0
  195. package/ai/tools/readDocTool.ts +84 -0
  196. package/ai/tools/readFileTool.ts +211 -0
  197. package/ai/tools/readTool.ts +163 -0
  198. package/ai/tools/readXPostTool.ts +233 -0
  199. package/ai/tools/rememberMemoryTool.ts +84 -0
  200. package/ai/tools/remotionVideoTool.ts +151 -0
  201. package/ai/tools/searchDialogMessagesTool.ts +222 -0
  202. package/ai/tools/searchRepoTool.ts +115 -0
  203. package/ai/tools/searchWorkspaceTool.ts +259 -0
  204. package/ai/tools/skillFollowup.ts +86 -0
  205. package/ai/tools/surfWeatherTool.ts +169 -0
  206. package/ai/tools/table/addTableRowTool.ts +217 -0
  207. package/ai/tools/table/createTableTool.ts +315 -0
  208. package/ai/tools/table/rowTools.ts +366 -0
  209. package/ai/tools/table/schemaTools.ts +244 -0
  210. package/ai/tools/table/shareTableTool.ts +148 -0
  211. package/ai/tools/table/toolShared.ts +129 -0
  212. package/ai/tools/toolApiClient.ts +198 -0
  213. package/ai/tools/toolNameAliases.ts +57 -0
  214. package/ai/tools/toolResultError.ts +42 -0
  215. package/ai/tools/toolRunSlice.ts +303 -0
  216. package/ai/tools/toolSchemaCompatibility.ts +53 -0
  217. package/ai/tools/toolVisibility.ts +4 -0
  218. package/ai/tools/types.ts +20 -0
  219. package/ai/tools/uiAskChoiceTool.ts +104 -0
  220. package/ai/tools/updateContentTitleTool.ts +84 -0
  221. package/ai/tools/updateDocTool.ts +105 -0
  222. package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
  223. package/ai/tools/whisperTool.ts +77 -0
  224. package/ai/tools/writeFileTool.ts +210 -0
  225. package/ai/tools/youtubeScraperTool.ts +116 -0
  226. package/ai/tools/ziweiChartTool.ts +678 -0
  227. package/ai/types.ts +55 -0
  228. package/ai/workflow/workflowExecutor.ts +323 -0
  229. package/ai/workflow/workflowSlice.ts +73 -0
  230. package/ai/workflow/workflowTypes.ts +106 -0
  231. package/client/agentRun.ts +198 -167
  232. package/client/compactDialog.ts +222 -0
  233. package/commandRegistry.ts +14 -0
  234. package/connector-experimental/capabilities.ts +73 -0
  235. package/connector-experimental/codexBinary.ts +41 -0
  236. package/connector-experimental/heartbeatLoop.ts +22 -0
  237. package/connector-experimental/index.ts +5 -0
  238. package/connector-experimental/machineInfo.ts +46 -0
  239. package/connector-experimental/protocol.ts +54 -0
  240. package/connectorWebSocketTarget.ts +29 -0
  241. package/defaultServer.ts +1 -0
  242. package/index.ts +158 -104
  243. package/machineCommands.ts +382 -0
  244. package/package.json +12 -2
  245. package/tui/readlineWorkspace.ts +50 -0
  246. package/tui/session.ts +40 -2
  247. package/updateCommands.ts +70 -5
@@ -0,0 +1,73 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { resolveLaunchableCodexCommand } from "./codexBinary";
5
+
6
+ type EnvLike = Record<string, string | undefined>;
7
+
8
+ type DetectRuntimeCapabilitiesOptions = {
9
+ commandExists?: (command: string) => boolean;
10
+ commandLaunchable?: (command: string, args: string[]) => boolean;
11
+ env?: EnvLike;
12
+ probeLaunchable?: boolean;
13
+ };
14
+
15
+ type CommandProbe = {
16
+ command: string;
17
+ args: string[];
18
+ };
19
+
20
+ const COMMAND_CAPABILITIES: Array<[probes: CommandProbe[], capability: string]> = [
21
+ [[{ command: "codex", args: ["--version"] }], "codex-cli"],
22
+ [[{ command: "claude", args: ["--version"] }], "claude-code"],
23
+ [
24
+ [
25
+ { command: "gh", args: ["--version"] },
26
+ { command: "gh", args: ["copilot", "--", "--help"] },
27
+ ],
28
+ "copilot-cli",
29
+ ],
30
+ [[{ command: "gemini", args: ["--version"] }], "gemini-cli"],
31
+ [[{ command: "kimi", args: ["--version"] }], "kimi-cli"],
32
+ ];
33
+
34
+ function defaultCommandExists(command: string) {
35
+ const pathEntries = (process.env.PATH ?? "").split(process.platform === "win32" ? ";" : ":");
36
+ const extensions = process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""];
37
+ return pathEntries.some((entry) =>
38
+ extensions.some((extension) => existsSync(join(entry, `${command}${extension}`)))
39
+ );
40
+ }
41
+
42
+ function defaultCommandLaunchable(command: string, args: string[]) {
43
+ const executable = command === "codex" ? resolveLaunchableCodexCommand() : command;
44
+ const result = spawnSync(executable, args, {
45
+ stdio: "pipe",
46
+ timeout: 3_000,
47
+ windowsHide: true,
48
+ });
49
+ return !result.error && result.status === 0;
50
+ }
51
+
52
+ export function detectRuntimeCapabilities(
53
+ options: DetectRuntimeCapabilitiesOptions = {}
54
+ ): string[] {
55
+ const commandExists = options.commandExists ?? defaultCommandExists;
56
+ const commandLaunchable = options.commandLaunchable ?? defaultCommandLaunchable;
57
+ const env = options.env ?? process.env;
58
+ const capabilities: string[] = [];
59
+
60
+ for (const [probes, capability] of COMMAND_CAPABILITIES) {
61
+ if (!probes.every((probe) => commandExists(probe.command))) continue;
62
+ if (options.probeLaunchable && !probes.every((probe) => commandLaunchable(probe.command, probe.args))) {
63
+ continue;
64
+ }
65
+ capabilities.push(capability);
66
+ }
67
+
68
+ if (env.NOLO_LOCAL_LLM_ENDPOINT || env.LLAMA_SERVER_URL) {
69
+ capabilities.push("local-llm");
70
+ }
71
+
72
+ return capabilities;
73
+ }
@@ -0,0 +1,41 @@
1
+ import { copyFileSync, existsSync, mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+
5
+ type EnvLike = Record<string, string | undefined>;
6
+
7
+ const WINDOWS_APPS_CODEX_PATTERN = /\\WindowsApps\\OpenAI\.Codex_[^\\]+\\app\\resources$/i;
8
+
9
+ function pathEntries(env: EnvLike) {
10
+ return (env.PATH ?? process.env.PATH ?? "").split(process.platform === "win32" ? ";" : ":");
11
+ }
12
+
13
+ function findWindowsAppsCodex(env: EnvLike) {
14
+ if (process.platform !== "win32") return "";
15
+ for (const entry of pathEntries(env)) {
16
+ if (!WINDOWS_APPS_CODEX_PATTERN.test(entry)) continue;
17
+ const candidate = join(entry, "codex.exe");
18
+ if (existsSync(candidate)) return candidate;
19
+ }
20
+ return "";
21
+ }
22
+
23
+ function copyCodexToUserBin(source: string, env: EnvLike) {
24
+ const target =
25
+ env.NOLO_CODEX_SHIM_PATH ??
26
+ join(homedir(), ".nolo", "bin", "codex.exe");
27
+ if (existsSync(target)) return target;
28
+ mkdirSync(dirname(target), { recursive: true });
29
+ copyFileSync(source, target);
30
+ return target;
31
+ }
32
+
33
+ export function resolveLaunchableCodexCommand(env: EnvLike = process.env) {
34
+ const explicit = env.NOLO_CODEX_BIN?.trim();
35
+ if (explicit) return explicit;
36
+
37
+ const windowsAppsCodex = findWindowsAppsCodex(env);
38
+ if (windowsAppsCodex) return copyCodexToUserBin(windowsAppsCodex, env);
39
+
40
+ return "codex";
41
+ }
@@ -0,0 +1,22 @@
1
+ export type HeartbeatLoopOptions = {
2
+ intervalMs: number;
3
+ sendHeartbeat: () => Promise<void>;
4
+ sleep?: (ms: number) => Promise<void>;
5
+ signal?: AbortSignal;
6
+ maxBeats?: number;
7
+ };
8
+
9
+ const defaultSleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
10
+
11
+ export async function runHeartbeatLoop(options: HeartbeatLoopOptions): Promise<void> {
12
+ const sleep = options.sleep ?? defaultSleep;
13
+ let beats = 0;
14
+
15
+ while (!options.signal?.aborted) {
16
+ await options.sendHeartbeat();
17
+ beats += 1;
18
+ if (options.maxBeats && beats >= options.maxBeats) return;
19
+ if (options.signal?.aborted) return;
20
+ await sleep(options.intervalMs);
21
+ }
22
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./capabilities";
2
+ export * from "./codexBinary";
3
+ export * from "./heartbeatLoop";
4
+ export * from "./machineInfo";
5
+ export * from "./protocol";
@@ -0,0 +1,46 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { arch, hostname, homedir, platform } from "node:os";
4
+ import { randomUUID } from "node:crypto";
5
+
6
+ import { detectRuntimeCapabilities } from "./capabilities";
7
+ import type { MachineHeartbeat } from "./protocol";
8
+
9
+ const CONNECTOR_VERSION = "0.1.0-experimental";
10
+
11
+ function defaultMachineIdPath() {
12
+ return join(homedir(), ".nolo", "machine-id");
13
+ }
14
+
15
+ export function resolveMachineId(path = defaultMachineIdPath()) {
16
+ try {
17
+ if (existsSync(path)) {
18
+ const existing = readFileSync(path, "utf8").trim();
19
+ if (existing) return existing;
20
+ }
21
+ const next = `machine-${randomUUID()}`;
22
+ mkdirSync(dirname(path), { recursive: true });
23
+ writeFileSync(path, `${next}\n`, "utf8");
24
+ return next;
25
+ } catch {
26
+ return `machine-${hostname().toLowerCase()}`;
27
+ }
28
+ }
29
+
30
+ export function detectMachineInfo(overrides?: {
31
+ machineId?: string;
32
+ name?: string;
33
+ capabilities?: string[];
34
+ probeLaunchable?: boolean;
35
+ }): MachineHeartbeat {
36
+ return {
37
+ machineId: overrides?.machineId ?? resolveMachineId(),
38
+ name: overrides?.name ?? hostname(),
39
+ platform: platform(),
40
+ arch: arch(),
41
+ connectorVersion: CONNECTOR_VERSION,
42
+ capabilities: overrides?.capabilities ?? detectRuntimeCapabilities({
43
+ probeLaunchable: overrides?.probeLaunchable,
44
+ }),
45
+ };
46
+ }
@@ -0,0 +1,54 @@
1
+ export type MachineHeartbeatInput = {
2
+ machineId?: unknown;
3
+ name?: unknown;
4
+ platform?: unknown;
5
+ arch?: unknown;
6
+ connectorVersion?: unknown;
7
+ capabilities?: unknown;
8
+ };
9
+
10
+ export type MachineHeartbeat = {
11
+ machineId: string;
12
+ name: string;
13
+ platform: string;
14
+ arch: string;
15
+ connectorVersion?: string;
16
+ capabilities: string[];
17
+ };
18
+
19
+ const normalizeRequiredString = (value: unknown, field: string) => {
20
+ if (typeof value !== "string" || !value.trim()) {
21
+ throw new Error(`${field} is required`);
22
+ }
23
+ return value.trim();
24
+ };
25
+
26
+ const normalizeOptionalString = (value: unknown) =>
27
+ typeof value === "string" && value.trim() ? value.trim() : undefined;
28
+
29
+ export function normalizeCapabilityList(value: unknown): string[] {
30
+ if (!Array.isArray(value)) return [];
31
+ const seen = new Set<string>();
32
+ const result: string[] = [];
33
+ for (const item of value) {
34
+ if (typeof item !== "string") continue;
35
+ const normalized = item.trim();
36
+ if (!normalized || seen.has(normalized)) continue;
37
+ seen.add(normalized);
38
+ result.push(normalized);
39
+ }
40
+ return result;
41
+ }
42
+
43
+ export function normalizeMachineHeartbeat(input: MachineHeartbeatInput): MachineHeartbeat {
44
+ const heartbeat: MachineHeartbeat = {
45
+ machineId: normalizeRequiredString(input.machineId, "machineId"),
46
+ name: normalizeRequiredString(input.name, "name"),
47
+ platform: normalizeRequiredString(input.platform, "platform"),
48
+ arch: normalizeRequiredString(input.arch, "arch"),
49
+ capabilities: normalizeCapabilityList(input.capabilities),
50
+ };
51
+ const connectorVersion = normalizeOptionalString(input.connectorVersion);
52
+ if (connectorVersion) heartbeat.connectorVersion = connectorVersion;
53
+ return heartbeat;
54
+ }
@@ -0,0 +1,29 @@
1
+ export async function resolveConnectorWebSocketTarget(input: {
2
+ serverUrl: string;
3
+ machineId: string;
4
+ headers: Record<string, string>;
5
+ fetchImpl?: typeof fetch;
6
+ }) {
7
+ const directWsUrl = new URL(input.serverUrl);
8
+ directWsUrl.protocol = directWsUrl.protocol === "https:" ? "wss:" : "ws:";
9
+ directWsUrl.pathname = "/api/connector/ws";
10
+ directWsUrl.search = "";
11
+ directWsUrl.searchParams.set("machineId", input.machineId);
12
+
13
+ const probeUrl = new URL(input.serverUrl);
14
+ probeUrl.pathname = "/api/connector/ws";
15
+ probeUrl.search = directWsUrl.search;
16
+
17
+ const response = await (input.fetchImpl ?? fetch)(probeUrl, {
18
+ headers: input.headers,
19
+ }).catch(() => null);
20
+
21
+ if (!response || !response.ok) {
22
+ return directWsUrl.toString();
23
+ }
24
+
25
+ const json = await response.json().catch(() => null);
26
+ return typeof json?.wsUrl === "string" && json.wsUrl
27
+ ? json.wsUrl
28
+ : directWsUrl.toString();
29
+ }
@@ -0,0 +1 @@
1
+ export const DEFAULT_NOLO_SERVER_URL = "https://nolo.chat";
package/index.ts CHANGED
@@ -1,105 +1,159 @@
1
1
  #!/usr/bin/env bun
2
-
3
- import { dirname, join } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
-
6
- import { renderHelpText, resolveCommand } from "./commandRegistry";
7
- import { runLoginCommand, runLogoutCommand, runWhoamiCommand } from "./authCommands";
8
- import { buildEnvFromProfile, loadProfileConfig } from "./client/profileConfig";
9
- import { startTuiWorkspace } from "./tui/readlineWorkspace";
10
- import {
11
- buildCliDoctorText,
12
- buildCliVersionText,
13
- readPackageInfo,
14
- runSelfUpdate,
15
- } from "./updateCommands";
16
-
17
- const CLI_DIR = dirname(fileURLToPath(import.meta.url));
18
- const ROOT_DIR = join(CLI_DIR, "..", "..");
19
- const SCRIPT_DIR = join(ROOT_DIR, "scripts");
20
- const packageInfo = readPackageInfo();
21
-
22
- async function runScript(script: string, forwardedArgs: string[]) {
23
- const scriptPath = join(SCRIPT_DIR, script);
24
- const proc = Bun.spawn({
25
- cmd: [process.execPath, scriptPath, ...forwardedArgs],
26
- stdin: "inherit",
27
- stdout: "inherit",
28
- stderr: "inherit",
29
- env: process.env,
30
- });
31
- const exitCode = await proc.exited;
32
- process.exit(exitCode);
33
- }
34
-
35
- const args = process.argv.slice(2);
36
- const profileEnv = buildEnvFromProfile(loadProfileConfig());
37
- const runtimeEnv = {
38
- ...profileEnv,
39
- ...process.env,
40
- NOLO_CLI_VERSION: packageInfo.version,
41
- };
42
-
43
- if (args.length === 0) {
44
- if (process.stdin.isTTY) {
45
- await startTuiWorkspace({ scriptDir: SCRIPT_DIR, env: runtimeEnv });
46
- } else {
47
- console.log(renderHelpText());
48
- }
49
- process.exit(0);
50
- }
51
-
52
- if (args.length === 1 && (args[0] === "tui" || args[0] === "chat")) {
53
- await startTuiWorkspace({ scriptDir: SCRIPT_DIR, env: runtimeEnv });
54
- process.exit(0);
55
- }
56
-
57
- if (args[0] === "login") {
58
- process.exit(await runLoginCommand(args.slice(1)));
59
- }
60
-
61
- if (args[0] === "whoami") {
62
- process.exit(runWhoamiCommand());
63
- }
64
-
65
- if (args[0] === "logout") {
66
- process.exit(runLogoutCommand());
67
- }
68
-
69
- if (args[0] === "--version" || args[0] === "-v" || args[0] === "version") {
70
- console.log(buildCliVersionText(packageInfo));
71
- process.exit(0);
72
- }
73
-
74
- if (args[0] === "doctor") {
75
- console.log(
76
- buildCliDoctorText({
77
- packageName: packageInfo.name,
78
- version: packageInfo.version,
79
- entrypoint: fileURLToPath(import.meta.url),
80
- serverUrl: runtimeEnv.NOLO_SERVER || runtimeEnv.BASE_URL || "http://127.0.0.1:38123",
81
- profileName: runtimeEnv.NOLO_PROFILE || "local",
82
- })
83
- );
84
- process.exit(0);
85
- }
86
-
87
- if (args[0] === "update") {
88
- process.exit(await runSelfUpdate());
89
- }
90
-
91
- if (args[0] === "--help" || args[0] === "-h") {
92
- console.log(renderHelpText());
93
- process.exit(0);
94
- }
95
-
96
- const command = resolveCommand(args);
97
- if (!command) {
98
- console.error(`Unknown command: ${args.join(" ")}`);
99
- console.error("");
100
- console.log(renderHelpText());
101
- process.exit(1);
102
- }
103
-
104
- const forwardedArgs = args.slice(command.path.length);
105
- await runScript(command.script, forwardedArgs);
2
+
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import { renderHelpText, resolveCommand } from "./commandRegistry";
7
+ import { runLoginCommand, runLogoutCommand, runWhoamiCommand } from "./authCommands";
8
+ import {
9
+ runAgentBindCurrentCommand,
10
+ runAgentRuntimeDoctorCommand,
11
+ runAgentSmokeCurrentCommand,
12
+ } from "./agentRuntimeCommands";
13
+ import { buildEnvFromProfile, loadProfileConfig } from "./client/profileConfig";
14
+ import {
15
+ runMachineConnectCommand,
16
+ runMachineStatusCommand,
17
+ } from "./machineCommands";
18
+ import { startTuiWorkspace } from "./tui/readlineWorkspace";
19
+ import {
20
+ buildCliDoctorText,
21
+ buildCliVersionText,
22
+ readPackageInfo,
23
+ runSelfUpdate,
24
+ } from "./updateCommands";
25
+ import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
26
+
27
+ const CLI_DIR = dirname(fileURLToPath(import.meta.url));
28
+ const ROOT_DIR = join(CLI_DIR, "..", "..");
29
+ const SCRIPT_DIR = join(ROOT_DIR, "scripts");
30
+ const packageInfo = readPackageInfo();
31
+
32
+ async function runScript(script: string, forwardedArgs: string[], env: NodeJS.ProcessEnv) {
33
+ const scriptPath = join(SCRIPT_DIR, script);
34
+ const proc = Bun.spawn({
35
+ cmd: [process.execPath, scriptPath, ...forwardedArgs],
36
+ stdin: "inherit",
37
+ stdout: "inherit",
38
+ stderr: "inherit",
39
+ env,
40
+ });
41
+ const exitCode = await proc.exited;
42
+ process.exit(exitCode);
43
+ }
44
+
45
+ const args = process.argv.slice(2);
46
+ const profileEnv = buildEnvFromProfile(loadProfileConfig());
47
+ const runtimeEnv = {
48
+ ...profileEnv,
49
+ ...process.env,
50
+ NOLO_CLI_VERSION: packageInfo.version,
51
+ };
52
+
53
+ function readOption(args: string[], flag: string) {
54
+ const index = args.indexOf(flag);
55
+ return index >= 0 ? args[index + 1] : undefined;
56
+ }
57
+
58
+ function buildDaemonEnv(args: string[]) {
59
+ const serverUrl = readOption(args, "--server-url") || readOption(args, "--server");
60
+ const apiKey = readOption(args, "--api-key") || readOption(args, "--token");
61
+ return {
62
+ ...runtimeEnv,
63
+ ...(serverUrl ? { NOLO_SERVER: serverUrl } : {}),
64
+ ...(apiKey ? { AUTH_TOKEN: apiKey } : {}),
65
+ };
66
+ }
67
+
68
+ function looksLikeDaemonShortcut(args: string[]) {
69
+ return args.includes("--server-url") || args.includes("--api-key");
70
+ }
71
+
72
+ if (args.length === 0) {
73
+ if (process.stdin.isTTY) {
74
+ await startTuiWorkspace({ scriptDir: SCRIPT_DIR, env: runtimeEnv });
75
+ } else {
76
+ console.log(renderHelpText());
77
+ }
78
+ process.exit(0);
79
+ }
80
+
81
+ if (args.length === 1 && (args[0] === "tui" || args[0] === "chat")) {
82
+ await startTuiWorkspace({ scriptDir: SCRIPT_DIR, env: runtimeEnv });
83
+ process.exit(0);
84
+ }
85
+
86
+ if (args[0] === "login") {
87
+ process.exit(await runLoginCommand(args.slice(1)));
88
+ }
89
+
90
+ if (args[0] === "whoami") {
91
+ process.exit(runWhoamiCommand());
92
+ }
93
+
94
+ if (args[0] === "logout") {
95
+ process.exit(runLogoutCommand());
96
+ }
97
+
98
+ if (args[0] === "connect") {
99
+ process.exit(await runMachineConnectCommand(args.slice(1), { env: runtimeEnv }));
100
+ }
101
+
102
+ if (args[0] === "daemon" || looksLikeDaemonShortcut(args)) {
103
+ const daemonArgs = args[0] === "daemon" ? args.slice(1) : args;
104
+ process.exit(await runMachineConnectCommand(["--ws"], { env: buildDaemonEnv(daemonArgs) }));
105
+ }
106
+
107
+ if (args[0] === "machine" && args[1] === "status") {
108
+ process.exit(await runMachineStatusCommand(args.slice(2), { env: runtimeEnv }));
109
+ }
110
+
111
+ if (args[0] === "agent" && args[1] === "bind-current") {
112
+ process.exit(await runAgentBindCurrentCommand(args.slice(2), { env: runtimeEnv }));
113
+ }
114
+
115
+ if (args[0] === "agent" && args[1] === "smoke-current") {
116
+ process.exit(await runAgentSmokeCurrentCommand(args.slice(2), { env: runtimeEnv }));
117
+ }
118
+
119
+ if (args[0] === "agent" && args[1] === "runtime-doctor") {
120
+ process.exit(await runAgentRuntimeDoctorCommand(args.slice(2), { env: runtimeEnv }));
121
+ }
122
+
123
+ if (args[0] === "--version" || args[0] === "-v" || args[0] === "version") {
124
+ console.log(buildCliVersionText(packageInfo));
125
+ process.exit(0);
126
+ }
127
+
128
+ if (args[0] === "doctor") {
129
+ console.log(
130
+ buildCliDoctorText({
131
+ packageName: packageInfo.name,
132
+ version: packageInfo.version,
133
+ entrypoint: fileURLToPath(import.meta.url),
134
+ serverUrl: runtimeEnv.NOLO_SERVER || runtimeEnv.BASE_URL || DEFAULT_NOLO_SERVER_URL,
135
+ profileName: runtimeEnv.NOLO_PROFILE || "local",
136
+ })
137
+ );
138
+ process.exit(0);
139
+ }
140
+
141
+ if (args[0] === "update") {
142
+ process.exit(await runSelfUpdate());
143
+ }
144
+
145
+ if (args[0] === "--help" || args[0] === "-h") {
146
+ console.log(renderHelpText());
147
+ process.exit(0);
148
+ }
149
+
150
+ const command = resolveCommand(args);
151
+ if (!command) {
152
+ console.error(`Unknown command: ${args.join(" ")}`);
153
+ console.error("");
154
+ console.log(renderHelpText());
155
+ process.exit(1);
156
+ }
157
+
158
+ const forwardedArgs = args.slice(command.path.length);
159
+ await runScript(command.script, forwardedArgs, runtimeEnv);