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.
- package/README.md +107 -5
- package/agentRuntimeCommands.ts +464 -0
- package/ai/agent/_executeModel.ts +118 -0
- package/ai/agent/agentSlice.ts +525 -0
- package/ai/agent/appWorkingMemory.ts +126 -0
- package/ai/agent/avatarUtils.ts +24 -0
- package/ai/agent/buildEditingContext.ts +373 -0
- package/ai/agent/buildSystemPrompt.ts +532 -0
- package/ai/agent/cleanAgentMessages.ts +140 -0
- package/ai/agent/cliChatClient.ts +119 -0
- package/ai/agent/cliExecutor.ts +733 -0
- package/ai/agent/cliPrompt.ts +10 -0
- package/ai/agent/contextCompiler.ts +107 -0
- package/ai/agent/contextLayerContract.ts +44 -0
- package/ai/agent/createAgentSchema.ts +234 -0
- package/ai/agent/executeToolCall.ts +58 -0
- package/ai/agent/fetchAgentContexts.ts +42 -0
- package/ai/agent/generatePrompt.ts +3 -0
- package/ai/agent/getFullChatContextKeys.ts +168 -0
- package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
- package/ai/agent/hooks/useAgentConfig.ts +61 -0
- package/ai/agent/hooks/useAgentDialog.ts +35 -0
- package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
- package/ai/agent/hooks/usePublicAgents.ts +473 -0
- package/ai/agent/machineRunPermissions.ts +95 -0
- package/ai/agent/persistMessageWithFixedId.ts +37 -0
- package/ai/agent/planSlice.ts +259 -0
- package/ai/agent/referenceUtils.ts +229 -0
- package/ai/agent/runAgentBackground.ts +238 -0
- package/ai/agent/runAgentClientLoop.ts +138 -0
- package/ai/agent/runtimeGuidance.ts +97 -0
- package/ai/agent/runtimeServerBase.ts +37 -0
- package/ai/agent/server/fetchPublicAgents.ts +128 -0
- package/ai/agent/startParallelAgentStreams.ts +424 -0
- package/ai/agent/startupProtocol.ts +53 -0
- package/ai/agent/streamAgentChatTurn.ts +1278 -0
- package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
- package/ai/agent/types.ts +71 -0
- package/ai/agent/utils/imageOutput.ts +33 -0
- package/ai/agent/utils/sortUtils.ts +250 -0
- package/ai/agent/web/referencePickerUtils.ts +146 -0
- package/ai/ai.locale.ts +1075 -0
- package/ai/chat/accumulateToolCallChunks.ts +95 -0
- package/ai/chat/fetchUtils.native.ts +276 -0
- package/ai/chat/fetchUtils.ts +153 -0
- package/ai/chat/parseApiError.ts +64 -0
- package/ai/chat/parseMultilineSSE.ts +95 -0
- package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +703 -0
- package/ai/chat/sendOpenAIResponseRequest.ts +491 -0
- package/ai/chat/shouldUseServerProxy.ts +18 -0
- package/ai/chat/sseClient.native.ts +91 -0
- package/ai/chat/sseClient.ts +67 -0
- package/ai/chat/streamReader.native.ts +31 -0
- package/ai/chat/streamReader.ts +62 -0
- package/ai/chat/updateTotalUsage.ts +72 -0
- package/ai/context/buildReferenceContext.ts +437 -0
- package/ai/context/calculateContextUsage.ts +133 -0
- package/ai/context/retention.ts +165 -0
- package/ai/context/tokenUtils.ts +78 -0
- package/ai/index.ts +1 -0
- package/ai/llm/calculateGeminiImageTokens.ts +57 -0
- package/ai/llm/deepinfra.ts +28 -0
- package/ai/llm/fireworks.ts +50 -0
- package/ai/llm/generateRequestBody.ts +165 -0
- package/ai/llm/getModelContextWindow.ts +84 -0
- package/ai/llm/getNoloKey.ts +31 -0
- package/ai/llm/getPricing.ts +199 -0
- package/ai/llm/hooks/useModelPricing.ts +75 -0
- package/ai/llm/imagePricing.ts +40 -0
- package/ai/llm/isResponseAPIModel.ts +13 -0
- package/ai/llm/mimo.ts +71 -0
- package/ai/llm/mistral.ts +22 -0
- package/ai/llm/modelAvatar.ts +427 -0
- package/ai/llm/models.ts +45 -0
- package/ai/llm/openrouterModels.ts +269 -0
- package/ai/llm/providers.ts +306 -0
- package/ai/llm/reasoningModels.ts +28 -0
- package/ai/llm/types.ts +59 -0
- package/ai/llm/usageRequestOptions.ts +59 -0
- package/ai/memory/capture.ts +148 -0
- package/ai/memory/consolidate.ts +104 -0
- package/ai/memory/delete.ts +147 -0
- package/ai/memory/overlay.ts +84 -0
- package/ai/memory/query.ts +38 -0
- package/ai/memory/queryShared.ts +160 -0
- package/ai/memory/rank.ts +105 -0
- package/ai/memory/recentRelationshipRecap.ts +249 -0
- package/ai/memory/remember.ts +167 -0
- package/ai/memory/runtime.ts +76 -0
- package/ai/memory/store.ts +20 -0
- package/ai/memory/storeShared.ts +76 -0
- package/ai/memory/types.ts +46 -0
- package/ai/memory/understanding.ts +349 -0
- package/ai/memory/understandingGreeting.ts +264 -0
- package/ai/messages/type.ts +20 -0
- package/ai/policy/personalizationDialog.ts +333 -0
- package/ai/policy/runtimePolicy.ts +440 -0
- package/ai/policy/selfUpdateFields.ts +48 -0
- package/ai/policy/types.ts +64 -0
- package/ai/skills/referenceRuntime.ts +274 -0
- package/ai/skills/skillDiagnostics.ts +251 -0
- package/ai/skills/skillDocBuilder.ts +139 -0
- package/ai/skills/skillDocProtocol.ts +434 -0
- package/ai/skills/skillReferenceSummary.ts +63 -0
- package/ai/skills/skillSummaryMarker.ts +26 -0
- package/ai/token/calculatePrice.ts +544 -0
- package/ai/token/db.ts +98 -0
- package/ai/token/externalToolCost.ts +330 -0
- package/ai/token/hooks/useRecords.ts +65 -0
- package/ai/token/missingUsageEstimate.ts +42 -0
- package/ai/token/modelUsageQuery.ts +252 -0
- package/ai/token/normalizeUsage.ts +84 -0
- package/ai/token/openaiImageGenerationUsage.ts +56 -0
- package/ai/token/prepareTokenUsageData.ts +88 -0
- package/ai/token/query.ts +88 -0
- package/ai/token/queryUserTokens.ts +59 -0
- package/ai/token/resolveBillingTarget.ts +52 -0
- package/ai/token/saveTokenRecord.ts +53 -0
- package/ai/token/serverDialogProjection.ts +78 -0
- package/ai/token/serverTokenWriter.ts +143 -0
- package/ai/token/stats.ts +21 -0
- package/ai/token/tokenThunks.ts +24 -0
- package/ai/token/types.ts +93 -0
- package/ai/tools/agent/agentTools.ts +176 -0
- package/ai/tools/agent/agentUpdateShared.ts +311 -0
- package/ai/tools/agent/callAgentTool.ts +139 -0
- package/ai/tools/agent/createAgentTool.ts +512 -0
- package/ai/tools/agent/createDialogTool.ts +69 -0
- package/ai/tools/agent/createSkillAgentTool.ts +62 -0
- package/ai/tools/agent/parallelBudget.ts +221 -0
- package/ai/tools/agent/presets/appBuilderPreset.ts +145 -0
- package/ai/tools/agent/runLlmTool.ts +96 -0
- package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
- package/ai/tools/agent/skillAgentArgs.ts +106 -0
- package/ai/tools/agent/skillAgentPreset.ts +89 -0
- package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
- package/ai/tools/agent/updateAgentTool.ts +96 -0
- package/ai/tools/agent/updateSelfTool.ts +113 -0
- package/ai/tools/amazonProductScraperTool.ts +86 -0
- package/ai/tools/apifyActorClient.ts +45 -0
- package/ai/tools/appEditGuard.ts +372 -0
- package/ai/tools/appReadSnapshot.ts +153 -0
- package/ai/tools/appTools.ts +1549 -0
- package/ai/tools/applyEditTool.ts +256 -0
- package/ai/tools/applyLineEditsTool.ts +312 -0
- package/ai/tools/browserTools/click.ts +33 -0
- package/ai/tools/browserTools/closeSession.ts +29 -0
- package/ai/tools/browserTools/common.ts +27 -0
- package/ai/tools/browserTools/openSession.ts +48 -0
- package/ai/tools/browserTools/readContent.ts +38 -0
- package/ai/tools/browserTools/selectOption.ts +46 -0
- package/ai/tools/browserTools/typeText.ts +42 -0
- package/ai/tools/category/createCategoryTool.ts +66 -0
- package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
- package/ai/tools/category/updateContentCategoryTool.ts +75 -0
- package/ai/tools/cfBrowserTools.ts +319 -0
- package/ai/tools/cfSpeechToTextTool.ts +49 -0
- package/ai/tools/checkEnvTool.ts +65 -0
- package/ai/tools/cloudflareCrawlTool.ts +289 -0
- package/ai/tools/codeSearchTool.ts +111 -0
- package/ai/tools/codeTools.ts +101 -0
- package/ai/tools/createDocTool.ts +132 -0
- package/ai/tools/createPlanTool.ts +999 -0
- package/ai/tools/createSkillDocTool.ts +155 -0
- package/ai/tools/createWorkflowTool.ts +154 -0
- package/ai/tools/deepseekOcrTool.ts +34 -0
- package/ai/tools/delayTool.ts +31 -0
- package/ai/tools/deleteSpacesTool.ts +325 -0
- package/ai/tools/deleteSpacesToolModel.ts +159 -0
- package/ai/tools/devReloadUtils.ts +29 -0
- package/ai/tools/dialogMessageSearch.ts +137 -0
- package/ai/tools/doctorSkillTool.ts +72 -0
- package/ai/tools/ecommerceScraperTool.ts +86 -0
- package/ai/tools/emailTools.ts +549 -0
- package/ai/tools/evalSkillTool.ts +92 -0
- package/ai/tools/exaSearchTool.ts +64 -0
- package/ai/tools/execBashTool.ts +379 -0
- package/ai/tools/executeSqlTool.ts +192 -0
- package/ai/tools/fetchWebpageSupport.ts +309 -0
- package/ai/tools/fetchWebpageTool.ts +84 -0
- package/ai/tools/geminiImagePreviewTool.ts +361 -0
- package/ai/tools/generateDocxTool.ts +215 -0
- package/ai/tools/googleSearchScraperTool.ts +106 -0
- package/ai/tools/importDataTool.ts +133 -0
- package/ai/tools/importSkillTool.ts +162 -0
- package/ai/tools/index.ts +1858 -0
- package/ai/tools/listFilesTool.ts +82 -0
- package/ai/tools/listUserSpacesTool.ts +113 -0
- package/ai/tools/modelUsageTools.ts +142 -0
- package/ai/tools/olmOcrTool.ts +34 -0
- package/ai/tools/openaiImageTool.ts +218 -0
- package/ai/tools/paddleOcrTool.ts +34 -0
- package/ai/tools/prepareTools.ts +23 -0
- package/ai/tools/readDocTool.ts +84 -0
- package/ai/tools/readFileTool.ts +211 -0
- package/ai/tools/readTool.ts +163 -0
- package/ai/tools/readXPostTool.ts +233 -0
- package/ai/tools/rememberMemoryTool.ts +84 -0
- package/ai/tools/remotionVideoTool.ts +151 -0
- package/ai/tools/searchDialogMessagesTool.ts +222 -0
- package/ai/tools/searchRepoTool.ts +115 -0
- package/ai/tools/searchWorkspaceTool.ts +259 -0
- package/ai/tools/skillFollowup.ts +86 -0
- package/ai/tools/surfWeatherTool.ts +169 -0
- package/ai/tools/table/addTableRowTool.ts +217 -0
- package/ai/tools/table/createTableTool.ts +315 -0
- package/ai/tools/table/rowTools.ts +366 -0
- package/ai/tools/table/schemaTools.ts +244 -0
- package/ai/tools/table/shareTableTool.ts +148 -0
- package/ai/tools/table/toolShared.ts +129 -0
- package/ai/tools/toolApiClient.ts +198 -0
- package/ai/tools/toolNameAliases.ts +57 -0
- package/ai/tools/toolResultError.ts +42 -0
- package/ai/tools/toolRunSlice.ts +303 -0
- package/ai/tools/toolSchemaCompatibility.ts +53 -0
- package/ai/tools/toolVisibility.ts +4 -0
- package/ai/tools/types.ts +20 -0
- package/ai/tools/uiAskChoiceTool.ts +104 -0
- package/ai/tools/updateContentTitleTool.ts +84 -0
- package/ai/tools/updateDocTool.ts +105 -0
- package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
- package/ai/tools/whisperTool.ts +77 -0
- package/ai/tools/writeFileTool.ts +210 -0
- package/ai/tools/youtubeScraperTool.ts +116 -0
- package/ai/tools/ziweiChartTool.ts +678 -0
- package/ai/types.ts +55 -0
- package/ai/workflow/workflowExecutor.ts +323 -0
- package/ai/workflow/workflowSlice.ts +73 -0
- package/ai/workflow/workflowTypes.ts +106 -0
- package/client/agentRun.ts +198 -167
- package/client/compactDialog.ts +222 -0
- package/commandRegistry.ts +14 -0
- package/connector-experimental/capabilities.ts +73 -0
- package/connector-experimental/codexBinary.ts +41 -0
- package/connector-experimental/heartbeatLoop.ts +22 -0
- package/connector-experimental/index.ts +5 -0
- package/connector-experimental/machineInfo.ts +46 -0
- package/connector-experimental/protocol.ts +54 -0
- package/connectorWebSocketTarget.ts +29 -0
- package/defaultServer.ts +1 -0
- package/index.ts +158 -104
- package/machineCommands.ts +382 -0
- package/package.json +12 -2
- package/tui/readlineWorkspace.ts +50 -0
- package/tui/session.ts +40 -2
- package/updateCommands.ts +70 -5
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import type { MachineHeartbeat } from "./connector-experimental/protocol";
|
|
2
|
+
import { detectMachineInfo } from "./connector-experimental/machineInfo";
|
|
3
|
+
import { mkdirSync, openSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
|
|
7
|
+
import {
|
|
8
|
+
type HeartbeatLoopOptions,
|
|
9
|
+
runHeartbeatLoop as defaultRunHeartbeatLoop,
|
|
10
|
+
} from "./connector-experimental/heartbeatLoop";
|
|
11
|
+
import {
|
|
12
|
+
assertMachineRunAllowed,
|
|
13
|
+
buildMachinePermissionPromptBlock,
|
|
14
|
+
resolveMachineRunPermissionPolicy,
|
|
15
|
+
} from "./ai/agent/machineRunPermissions";
|
|
16
|
+
import { resolveConnectorWebSocketTarget } from "./connectorWebSocketTarget";
|
|
17
|
+
|
|
18
|
+
type EnvLike = Record<string, string | undefined>;
|
|
19
|
+
type OutputLike = { write(chunk: string): unknown };
|
|
20
|
+
type ConnectorWebSocketOptions = {
|
|
21
|
+
headers: Record<string, string>;
|
|
22
|
+
onMessage: (message: string) => void | Promise<void>;
|
|
23
|
+
sentMessages: string[];
|
|
24
|
+
};
|
|
25
|
+
type LocalCliExecutor = (
|
|
26
|
+
provider: string,
|
|
27
|
+
prompt: string,
|
|
28
|
+
options: { model?: string; yolo?: boolean }
|
|
29
|
+
) => Promise<{ text: string; raw?: string; elapsed?: number }>;
|
|
30
|
+
|
|
31
|
+
export type MachineSummary = {
|
|
32
|
+
machineId: string;
|
|
33
|
+
name: string;
|
|
34
|
+
platform: string;
|
|
35
|
+
arch: string;
|
|
36
|
+
connectorVersion: string | null;
|
|
37
|
+
capabilities: string[];
|
|
38
|
+
connectorStatus?: "connected" | "disconnected";
|
|
39
|
+
status: "online" | "offline";
|
|
40
|
+
lastSeenAt: number;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type MachineCommandDeps = {
|
|
44
|
+
env?: EnvLike;
|
|
45
|
+
output?: OutputLike;
|
|
46
|
+
fetchImpl?: typeof fetch;
|
|
47
|
+
machineInfo?: () => MachineHeartbeat;
|
|
48
|
+
runHeartbeatLoop?: (options: HeartbeatLoopOptions) => Promise<void>;
|
|
49
|
+
connectWebSocket?: (url: string, options: ConnectorWebSocketOptions) => Promise<void>;
|
|
50
|
+
executeCli?: LocalCliExecutor;
|
|
51
|
+
spawnDaemon?: (args: {
|
|
52
|
+
cmd: string[];
|
|
53
|
+
cwd: string;
|
|
54
|
+
env: EnvLike;
|
|
55
|
+
logPath: string;
|
|
56
|
+
}) => { pid?: number };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function resolveServerUrl(env: EnvLike) {
|
|
60
|
+
return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveAuthToken(env: EnvLike) {
|
|
64
|
+
return env.AUTH_TOKEN || env.AUTH || "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function writeAuthMissing(output: OutputLike) {
|
|
68
|
+
output.write(
|
|
69
|
+
"[nolo] Machine commands require an auth token. Run `nolo login` or set AUTH_TOKEN.\n"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function hasFlag(args: string[], flag: string) {
|
|
74
|
+
return args.includes(flag);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolveDaemonLogPath(env: EnvLike) {
|
|
78
|
+
return env.NOLO_CONNECT_LOG || join(homedir(), ".nolo", "logs", "connector.log");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function defaultSpawnDaemon(args: {
|
|
82
|
+
cmd: string[];
|
|
83
|
+
cwd: string;
|
|
84
|
+
env: EnvLike;
|
|
85
|
+
logPath: string;
|
|
86
|
+
}) {
|
|
87
|
+
mkdirSync(dirname(args.logPath), { recursive: true });
|
|
88
|
+
const out = openSync(args.logPath, "a");
|
|
89
|
+
const env = Object.fromEntries(
|
|
90
|
+
Object.entries(args.env).filter((entry): entry is [string, string] => typeof entry[1] === "string")
|
|
91
|
+
);
|
|
92
|
+
const proc = Bun.spawn({
|
|
93
|
+
cmd: args.cmd,
|
|
94
|
+
cwd: args.cwd,
|
|
95
|
+
env,
|
|
96
|
+
stdin: "ignore",
|
|
97
|
+
stdout: out,
|
|
98
|
+
stderr: out,
|
|
99
|
+
});
|
|
100
|
+
proc.unref();
|
|
101
|
+
return { pid: proc.pid };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveHeartbeatIntervalMs(env: EnvLike) {
|
|
105
|
+
const raw = Number(env.NOLO_CONNECT_HEARTBEAT_MS ?? "");
|
|
106
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 30_000;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function defaultConnectWebSocket(url: string, options: ConnectorWebSocketOptions) {
|
|
110
|
+
const WebSocketCtor = globalThis.WebSocket;
|
|
111
|
+
if (!WebSocketCtor) {
|
|
112
|
+
throw new Error("WebSocket is not available in this runtime");
|
|
113
|
+
}
|
|
114
|
+
await new Promise<void>((resolve, reject) => {
|
|
115
|
+
const ws = new WebSocketCtor(url, {
|
|
116
|
+
headers: options.headers,
|
|
117
|
+
} as any);
|
|
118
|
+
let opened = false;
|
|
119
|
+
ws.addEventListener("open", () => {
|
|
120
|
+
opened = true;
|
|
121
|
+
}, { once: true });
|
|
122
|
+
ws.addEventListener("error", () => reject(new Error("connector websocket failed")));
|
|
123
|
+
ws.addEventListener("close", () => {
|
|
124
|
+
if (opened) resolve();
|
|
125
|
+
else reject(new Error("connector websocket closed before opening"));
|
|
126
|
+
}, { once: true });
|
|
127
|
+
ws.addEventListener("message", (event) => {
|
|
128
|
+
const startIndex = options.sentMessages.length;
|
|
129
|
+
Promise.resolve(options.onMessage(String(event.data))).then(() => {
|
|
130
|
+
for (const message of options.sentMessages.slice(startIndex)) {
|
|
131
|
+
ws.send(message);
|
|
132
|
+
}
|
|
133
|
+
}).catch(() => undefined);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function defaultExecuteCli(provider: string, prompt: string, options: { model?: string; yolo?: boolean }) {
|
|
139
|
+
const { executeCli } = await import("ai/agent/cliExecutor");
|
|
140
|
+
return executeCli(provider as any, prompt, options);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function detectLaunchableMachineInfo() {
|
|
144
|
+
return detectMachineInfo({ probeLaunchable: true });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildConnectorCliPrompt(agentConfig: any, userInput: string) {
|
|
148
|
+
const policy = resolveMachineRunPermissionPolicy(agentConfig);
|
|
149
|
+
return [
|
|
150
|
+
typeof agentConfig?.prompt === "string" ? agentConfig.prompt.trim() : "",
|
|
151
|
+
buildMachinePermissionPromptBlock(policy),
|
|
152
|
+
`--- User task ---\n${userInput}`,
|
|
153
|
+
].filter(Boolean).join("\n\n");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function handleConnectorRunMessage(
|
|
157
|
+
machine: MachineHeartbeat,
|
|
158
|
+
message: string,
|
|
159
|
+
send: (message: string) => void,
|
|
160
|
+
executeCli: LocalCliExecutor
|
|
161
|
+
) {
|
|
162
|
+
let parsed: any;
|
|
163
|
+
try {
|
|
164
|
+
parsed = JSON.parse(message);
|
|
165
|
+
} catch {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (parsed?.type !== "agent.run" || typeof parsed.requestId !== "string") return;
|
|
169
|
+
const agentConfig = parsed.payload?.agentConfig ?? {};
|
|
170
|
+
try {
|
|
171
|
+
if (agentConfig.apiSource !== "cli") {
|
|
172
|
+
throw new Error("Connector can only execute CLI agents. Set the agent apiSource to cli and choose a cliProvider.");
|
|
173
|
+
}
|
|
174
|
+
const provider = String(agentConfig.cliProvider || "copilot");
|
|
175
|
+
const policy = resolveMachineRunPermissionPolicy(agentConfig);
|
|
176
|
+
const userInput = String(parsed.payload?.userInput ?? "");
|
|
177
|
+
assertMachineRunAllowed(userInput, policy);
|
|
178
|
+
const result = await executeCli(
|
|
179
|
+
provider,
|
|
180
|
+
buildConnectorCliPrompt(agentConfig, userInput),
|
|
181
|
+
{
|
|
182
|
+
model: agentConfig.model || undefined,
|
|
183
|
+
yolo: true,
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
send(JSON.stringify({
|
|
187
|
+
type: "agent.run.result",
|
|
188
|
+
requestId: parsed.requestId,
|
|
189
|
+
result: {
|
|
190
|
+
content: result.text,
|
|
191
|
+
model: agentConfig.model ?? provider,
|
|
192
|
+
trace: [{ role: "assistant", content: result.text }],
|
|
193
|
+
},
|
|
194
|
+
}));
|
|
195
|
+
} catch (error) {
|
|
196
|
+
send(JSON.stringify({
|
|
197
|
+
type: "agent.run.result",
|
|
198
|
+
requestId: parsed.requestId,
|
|
199
|
+
error: error instanceof Error ? error.message : String(error),
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function formatMachineStatus(machines: MachineSummary[]) {
|
|
205
|
+
if (machines.length === 0) {
|
|
206
|
+
return "No connected machines.\nRun `nolo connect` on this computer to register it once.\n";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return [
|
|
210
|
+
"Connected machines:",
|
|
211
|
+
...machines.map((machine) => {
|
|
212
|
+
const caps = machine.capabilities.length ? machine.capabilities.join(", ") : "no capabilities";
|
|
213
|
+
const connector = machine.connectorStatus ? ` ws:${machine.connectorStatus}` : "";
|
|
214
|
+
return `- ${machine.name} ${machine.status}${connector} ${machine.platform}/${machine.arch} ${caps}`;
|
|
215
|
+
}),
|
|
216
|
+
"",
|
|
217
|
+
].join("\n");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function runMachineConnectCommand(
|
|
221
|
+
args: string[],
|
|
222
|
+
deps: MachineCommandDeps = {}
|
|
223
|
+
) {
|
|
224
|
+
const env = deps.env ?? process.env;
|
|
225
|
+
const output = deps.output ?? process.stdout;
|
|
226
|
+
const authToken = resolveAuthToken(env);
|
|
227
|
+
if (!authToken) {
|
|
228
|
+
writeAuthMissing(output);
|
|
229
|
+
return 1;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const serverUrl = resolveServerUrl(env);
|
|
233
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
234
|
+
const machine = (deps.machineInfo ?? detectLaunchableMachineInfo)();
|
|
235
|
+
const sendHeartbeat = async () => {
|
|
236
|
+
const res = await fetchImpl(`${serverUrl}/api/machines/heartbeat`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: {
|
|
239
|
+
Authorization: `Bearer ${authToken}`,
|
|
240
|
+
"Content-Type": "application/json",
|
|
241
|
+
},
|
|
242
|
+
body: JSON.stringify(machine),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!res.ok) {
|
|
246
|
+
const text = await res.text().catch(() => "");
|
|
247
|
+
throw new Error(`HTTP ${res.status}\n${text}`);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
if (hasFlag(args, "--daemon") || hasFlag(args, "--background")) {
|
|
252
|
+
const logPath = resolveDaemonLogPath(env);
|
|
253
|
+
const result = (deps.spawnDaemon ?? defaultSpawnDaemon)({
|
|
254
|
+
cmd: [process.execPath, import.meta.path, "connect", "--ws"],
|
|
255
|
+
cwd: process.cwd(),
|
|
256
|
+
env,
|
|
257
|
+
logPath,
|
|
258
|
+
});
|
|
259
|
+
output.write(`Connector daemon started${result.pid ? ` pid=${result.pid}` : ""}. Log: ${logPath}\n`);
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (hasFlag(args, "--ws")) {
|
|
264
|
+
const sentMessages: string[] = [];
|
|
265
|
+
const heartbeatAbort = new AbortController();
|
|
266
|
+
try {
|
|
267
|
+
await sendHeartbeat();
|
|
268
|
+
output.write(`Connector websocket connected: ${machine.name} (${machine.machineId})\n`);
|
|
269
|
+
const heartbeatLoopPromise = (deps.runHeartbeatLoop ?? defaultRunHeartbeatLoop)({
|
|
270
|
+
intervalMs: resolveHeartbeatIntervalMs(env),
|
|
271
|
+
sendHeartbeat,
|
|
272
|
+
signal: heartbeatAbort.signal,
|
|
273
|
+
});
|
|
274
|
+
const wsTarget = await resolveConnectorWebSocketTarget({
|
|
275
|
+
serverUrl,
|
|
276
|
+
machineId: machine.machineId,
|
|
277
|
+
headers: { Authorization: `Bearer ${authToken}` },
|
|
278
|
+
fetchImpl,
|
|
279
|
+
});
|
|
280
|
+
const websocketPromise = (deps.connectWebSocket ?? defaultConnectWebSocket)(
|
|
281
|
+
wsTarget,
|
|
282
|
+
{
|
|
283
|
+
headers: { Authorization: `Bearer ${authToken}` },
|
|
284
|
+
sentMessages,
|
|
285
|
+
onMessage: (message) => handleConnectorRunMessage(
|
|
286
|
+
machine,
|
|
287
|
+
message,
|
|
288
|
+
(response) => sentMessages.push(response),
|
|
289
|
+
deps.executeCli ?? defaultExecuteCli
|
|
290
|
+
),
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
await Promise.race([websocketPromise, heartbeatLoopPromise]);
|
|
294
|
+
heartbeatAbort.abort();
|
|
295
|
+
await Promise.allSettled([websocketPromise, heartbeatLoopPromise]);
|
|
296
|
+
return 0;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
heartbeatAbort.abort();
|
|
299
|
+
output.write(
|
|
300
|
+
`[nolo] Connector websocket failed: ${
|
|
301
|
+
error instanceof Error ? error.message : String(error)
|
|
302
|
+
}\n`
|
|
303
|
+
);
|
|
304
|
+
return 1;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (hasFlag(args, "--watch")) {
|
|
309
|
+
output.write(`Connecting machine heartbeat loop: ${machine.name} (${machine.platform}/${machine.arch})\n`);
|
|
310
|
+
try {
|
|
311
|
+
await (deps.runHeartbeatLoop ?? defaultRunHeartbeatLoop)({
|
|
312
|
+
intervalMs: resolveHeartbeatIntervalMs(env),
|
|
313
|
+
sendHeartbeat,
|
|
314
|
+
});
|
|
315
|
+
return 0;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
output.write(
|
|
318
|
+
`[nolo] Machine heartbeat loop failed: ${
|
|
319
|
+
error instanceof Error ? error.message : String(error)
|
|
320
|
+
}\n`
|
|
321
|
+
);
|
|
322
|
+
return 1;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
await sendHeartbeat();
|
|
328
|
+
} catch (error) {
|
|
329
|
+
output.write(
|
|
330
|
+
`[nolo] Machine connect failed: ${
|
|
331
|
+
error instanceof Error ? error.message : String(error)
|
|
332
|
+
}\n`
|
|
333
|
+
);
|
|
334
|
+
return 1;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
output.write(`Connected machine: ${machine.name} (${machine.platform}/${machine.arch})\n`);
|
|
338
|
+
return 0;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export async function runMachineStatusCommand(
|
|
342
|
+
_args: string[],
|
|
343
|
+
deps: MachineCommandDeps = {}
|
|
344
|
+
) {
|
|
345
|
+
const env = deps.env ?? process.env;
|
|
346
|
+
const output = deps.output ?? process.stdout;
|
|
347
|
+
const authToken = resolveAuthToken(env);
|
|
348
|
+
if (!authToken) {
|
|
349
|
+
writeAuthMissing(output);
|
|
350
|
+
return 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const serverUrl = resolveServerUrl(env);
|
|
354
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
355
|
+
let res: Response;
|
|
356
|
+
try {
|
|
357
|
+
res = await fetchImpl(`${serverUrl}/api/machines`, {
|
|
358
|
+
method: "GET",
|
|
359
|
+
headers: {
|
|
360
|
+
Authorization: `Bearer ${authToken}`,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
} catch (error) {
|
|
364
|
+
output.write(
|
|
365
|
+
`[nolo] Machine status failed: ${
|
|
366
|
+
error instanceof Error ? error.message : String(error)
|
|
367
|
+
}\n`
|
|
368
|
+
);
|
|
369
|
+
return 1;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!res.ok) {
|
|
373
|
+
const text = await res.text().catch(() => "");
|
|
374
|
+
output.write(`[nolo] Machine status failed: HTTP ${res.status}\n${text}\n`);
|
|
375
|
+
return 1;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const data = await res.json().catch(() => ({ machines: [] }));
|
|
379
|
+
const machines = Array.isArray(data?.machines) ? data.machines : [];
|
|
380
|
+
output.write(formatMachineStatus(machines));
|
|
381
|
+
return 0;
|
|
382
|
+
}
|
package/package.json
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nolo-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first terminal workspace for Nolo",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nolo": "index.ts"
|
|
8
8
|
},
|
|
9
9
|
"module": "index.ts",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build:publish": "bun build.ts"
|
|
12
|
+
},
|
|
10
13
|
"files": [
|
|
11
14
|
"index.ts",
|
|
15
|
+
"agentRuntimeCommands.ts",
|
|
12
16
|
"authCommands.ts",
|
|
13
17
|
"commandRegistry.ts",
|
|
18
|
+
"connectorWebSocketTarget.ts",
|
|
19
|
+
"defaultServer.ts",
|
|
20
|
+
"machineCommands.ts",
|
|
14
21
|
"updateCommands.ts",
|
|
15
22
|
"client/agentRun.ts",
|
|
23
|
+
"client/compactDialog.ts",
|
|
16
24
|
"client/profileConfig.ts",
|
|
17
25
|
"tui/readlineWorkspace.ts",
|
|
18
26
|
"tui/session.ts",
|
|
19
|
-
"README.md"
|
|
27
|
+
"README.md",
|
|
28
|
+
"ai/**/*.ts",
|
|
29
|
+
"connector-experimental/**/*.ts"
|
|
20
30
|
],
|
|
21
31
|
"publishConfig": {
|
|
22
32
|
"access": "public"
|
package/tui/readlineWorkspace.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import { stdin as defaultInput, stdout as defaultOutput } from "node:process";
|
|
3
3
|
import { runAgentTurn, type RunAgentTurnResult } from "../client/agentRun";
|
|
4
|
+
import { compactDialog, type CompactDialogResult } from "../client/compactDialog";
|
|
5
|
+
import { runSelfUpdate } from "../updateCommands";
|
|
4
6
|
import {
|
|
5
7
|
createInitialTuiState,
|
|
6
8
|
handleTuiInput,
|
|
@@ -10,12 +12,22 @@ import {
|
|
|
10
12
|
type TuiState,
|
|
11
13
|
} from "./session";
|
|
12
14
|
|
|
15
|
+
export type SelfUpdater = (
|
|
16
|
+
output: NodeJS.WritableStream
|
|
17
|
+
) => Promise<number>;
|
|
18
|
+
|
|
13
19
|
type WorkspaceOptions = {
|
|
14
20
|
scriptDir: string;
|
|
15
21
|
env?: NodeJS.ProcessEnv;
|
|
16
22
|
input?: NodeJS.ReadableStream;
|
|
17
23
|
output?: NodeJS.WritableStream;
|
|
18
24
|
agentRunner?: typeof runAgentTurn;
|
|
25
|
+
compactRunner?: (options: {
|
|
26
|
+
serverUrl: string;
|
|
27
|
+
authToken: string;
|
|
28
|
+
dialogId: string;
|
|
29
|
+
}) => Promise<CompactDialogResult>;
|
|
30
|
+
selfUpdater?: SelfUpdater;
|
|
19
31
|
};
|
|
20
32
|
|
|
21
33
|
async function runAgentChat(
|
|
@@ -43,6 +55,8 @@ export async function startTuiWorkspace(options: WorkspaceOptions) {
|
|
|
43
55
|
let state = createInitialTuiState(options.env ?? process.env);
|
|
44
56
|
const input = options.input ?? defaultInput;
|
|
45
57
|
const output = options.output ?? defaultOutput;
|
|
58
|
+
const selfUpdater: SelfUpdater =
|
|
59
|
+
options.selfUpdater ?? ((target) => runSelfUpdate({ output: target }));
|
|
46
60
|
const rl = createInterface({ input, output });
|
|
47
61
|
|
|
48
62
|
output.write(renderWelcome(state));
|
|
@@ -62,6 +76,42 @@ export async function startTuiWorkspace(options: WorkspaceOptions) {
|
|
|
62
76
|
break;
|
|
63
77
|
}
|
|
64
78
|
|
|
79
|
+
if (result.action?.type === "compact") {
|
|
80
|
+
const runner = options.compactRunner ?? compactDialog;
|
|
81
|
+
const authToken =
|
|
82
|
+
options.env?.AUTH_TOKEN ?? options.env?.AUTH ?? options.env?.BENCHMARK_AUTH_TOKEN ?? "";
|
|
83
|
+
try {
|
|
84
|
+
const compactResult = await runner({
|
|
85
|
+
serverUrl: state.serverUrl,
|
|
86
|
+
authToken,
|
|
87
|
+
dialogId: result.action.dialogId,
|
|
88
|
+
});
|
|
89
|
+
state = {
|
|
90
|
+
...state,
|
|
91
|
+
dialogId: compactResult.dialogId,
|
|
92
|
+
dialogLabel: compactResult.dialogId,
|
|
93
|
+
};
|
|
94
|
+
} catch (error: any) {
|
|
95
|
+
output.write(
|
|
96
|
+
`[nolo] Compact failed: ${error?.message ?? String(error)}\n`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (result.action?.type === "self-update") {
|
|
102
|
+
try {
|
|
103
|
+
const exitCode = await selfUpdater(output);
|
|
104
|
+
if (exitCode === 0) {
|
|
105
|
+
output.write("Update finished. Restart nolo to use the new version.\n");
|
|
106
|
+
} else {
|
|
107
|
+
output.write("Update failed. Fix the npm error above, then run /update again or use nolo update.\n");
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
output.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
111
|
+
output.write("Update failed. Fix the npm error above, then run /update again or use nolo update.\n");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
65
115
|
if (result.action?.type === "chat") {
|
|
66
116
|
const runResult = await runAgentChat(
|
|
67
117
|
options.scriptDir,
|
package/tui/session.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { DEFAULT_NOLO_SERVER_URL } from "../defaultServer";
|
|
2
|
+
|
|
1
3
|
export const DEFAULT_TUI_AGENT_KEY = "agent-pub-01NOLOAPPBLD000000019KCKT0";
|
|
2
|
-
export const DEFAULT_TUI_SERVER_URL =
|
|
4
|
+
export const DEFAULT_TUI_SERVER_URL = DEFAULT_NOLO_SERVER_URL;
|
|
3
5
|
|
|
4
6
|
const KNOWN_AGENTS: Array<{
|
|
5
7
|
name: string;
|
|
@@ -51,6 +53,13 @@ export type TuiAction =
|
|
|
51
53
|
agentKey: string;
|
|
52
54
|
continueDialogId?: string;
|
|
53
55
|
}
|
|
56
|
+
| {
|
|
57
|
+
type: "compact";
|
|
58
|
+
dialogId: string;
|
|
59
|
+
}
|
|
60
|
+
| {
|
|
61
|
+
type: "self-update";
|
|
62
|
+
}
|
|
54
63
|
| {
|
|
55
64
|
type: "exit";
|
|
56
65
|
};
|
|
@@ -73,7 +82,10 @@ export function createInitialTuiState(env: EnvLike = process.env): TuiState {
|
|
|
73
82
|
dialogId: env.NOLO_DIALOG_ID?.trim() || undefined,
|
|
74
83
|
dialogLabel: env.NOLO_DIALOG?.trim() || "new",
|
|
75
84
|
profileName: env.NOLO_PROFILE?.trim() || "local",
|
|
76
|
-
serverUrl: (env.NOLO_SERVER || env.BASE_URL || DEFAULT_TUI_SERVER_URL).replace(
|
|
85
|
+
serverUrl: (env.NOLO_SERVER || env.BASE_URL || DEFAULT_TUI_SERVER_URL).replace(
|
|
86
|
+
/\/+$/,
|
|
87
|
+
""
|
|
88
|
+
),
|
|
77
89
|
cliVersion: env.NOLO_CLI_VERSION?.trim() || undefined,
|
|
78
90
|
attachedDocs: [],
|
|
79
91
|
};
|
|
@@ -117,6 +129,7 @@ export function renderTuiHelp() {
|
|
|
117
129
|
"Commands:",
|
|
118
130
|
" /help Show this help",
|
|
119
131
|
" /new Start a fresh dialog",
|
|
132
|
+
" /compact Compact current dialog and fork a new one",
|
|
120
133
|
" /context Show workspace context and next actions",
|
|
121
134
|
" /agent Show the current agent",
|
|
122
135
|
" /agents List built-in agent shortcuts",
|
|
@@ -127,6 +140,7 @@ export function renderTuiHelp() {
|
|
|
127
140
|
" /customize Describe how you want to tune nolo",
|
|
128
141
|
" /login Show login/profile hint",
|
|
129
142
|
" /profile Show active profile",
|
|
143
|
+
" /update Update the global nolo install",
|
|
130
144
|
" /version Show version/update hint",
|
|
131
145
|
" /exit Leave the workspace",
|
|
132
146
|
"",
|
|
@@ -221,6 +235,24 @@ export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
|
221
235
|
},
|
|
222
236
|
output: "Started a fresh dialog.",
|
|
223
237
|
};
|
|
238
|
+
case "/compact":
|
|
239
|
+
if (argText) {
|
|
240
|
+
return {
|
|
241
|
+
nextState: state,
|
|
242
|
+
output: `Unknown command: ${trimmed}\n\n${renderTuiHelp()}`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (!state.dialogId) {
|
|
246
|
+
return {
|
|
247
|
+
nextState: state,
|
|
248
|
+
output: "Current dialog: new (nothing to compact yet)",
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
nextState: state,
|
|
253
|
+
output: "Compacting current dialog...",
|
|
254
|
+
action: { type: "compact", dialogId: state.dialogId },
|
|
255
|
+
};
|
|
224
256
|
case "/agent":
|
|
225
257
|
return {
|
|
226
258
|
nextState: state,
|
|
@@ -302,6 +334,12 @@ export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
|
302
334
|
nextState: state,
|
|
303
335
|
output: `Profile: ${state.profileName}`,
|
|
304
336
|
};
|
|
337
|
+
case "/update":
|
|
338
|
+
return {
|
|
339
|
+
nextState: state,
|
|
340
|
+
output: "Starting self-update...",
|
|
341
|
+
action: { type: "self-update" },
|
|
342
|
+
};
|
|
305
343
|
case "/version":
|
|
306
344
|
return {
|
|
307
345
|
nextState: state,
|
package/updateCommands.ts
CHANGED
|
@@ -15,6 +15,13 @@ type DoctorInfo = {
|
|
|
15
15
|
profileName: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
type RunSelfUpdateOptions = {
|
|
19
|
+
output?: NodeJS.WritableStream;
|
|
20
|
+
spawn?: typeof Bun.spawn;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type SpawnOutputChunk = string | ArrayBuffer | ArrayBufferView;
|
|
24
|
+
|
|
18
25
|
const CLI_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
26
|
const PACKAGE_JSON_PATH = join(CLI_DIR, "package.json");
|
|
20
27
|
|
|
@@ -53,17 +60,75 @@ export function buildCliDoctorText(info: DoctorInfo) {
|
|
|
53
60
|
].join("\n");
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
function isRunSelfUpdateOptions(
|
|
64
|
+
value: NodeJS.WritableStream | RunSelfUpdateOptions
|
|
65
|
+
): value is RunSelfUpdateOptions {
|
|
66
|
+
return (
|
|
67
|
+
typeof value === "object" &&
|
|
68
|
+
value !== null &&
|
|
69
|
+
("output" in value || "spawn" in value)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function forwardSpawnOutput(
|
|
74
|
+
stream: AsyncIterable<SpawnOutputChunk> | undefined,
|
|
75
|
+
output: NodeJS.WritableStream
|
|
76
|
+
) {
|
|
77
|
+
if (!stream) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for await (const chunk of stream) {
|
|
82
|
+
output.write(normalizeSpawnChunk(chunk));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeSpawnChunk(
|
|
87
|
+
chunk: SpawnOutputChunk
|
|
88
|
+
): string | Uint8Array<ArrayBufferLike> {
|
|
89
|
+
if (typeof chunk === "string" || chunk instanceof Uint8Array) {
|
|
90
|
+
return chunk;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (chunk instanceof ArrayBuffer) {
|
|
94
|
+
return new Uint8Array(chunk);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function runSelfUpdate(
|
|
101
|
+
outputOrOptions?: NodeJS.WritableStream | RunSelfUpdateOptions
|
|
102
|
+
) {
|
|
103
|
+
const options =
|
|
104
|
+
outputOrOptions === undefined
|
|
105
|
+
? {}
|
|
106
|
+
: isRunSelfUpdateOptions(outputOrOptions)
|
|
107
|
+
? outputOrOptions
|
|
108
|
+
: { output: outputOrOptions };
|
|
109
|
+
const output = options.output ?? process.stdout;
|
|
110
|
+
const spawn = options.spawn ?? Bun.spawn;
|
|
57
111
|
const command = buildSelfUpdateCommand();
|
|
58
112
|
output.write(`Updating nolo with: ${command.join(" ")}\n`);
|
|
59
113
|
|
|
60
|
-
const
|
|
114
|
+
const useCustomSink = options.output !== undefined;
|
|
115
|
+
const proc = spawn({
|
|
61
116
|
cmd: command,
|
|
62
117
|
stdin: "inherit",
|
|
63
|
-
stdout: "inherit",
|
|
64
|
-
stderr: "inherit",
|
|
118
|
+
stdout: useCustomSink ? "pipe" : "inherit",
|
|
119
|
+
stderr: useCustomSink ? "pipe" : "inherit",
|
|
65
120
|
env: process.env,
|
|
66
121
|
});
|
|
67
122
|
|
|
68
|
-
|
|
123
|
+
if (!useCustomSink) {
|
|
124
|
+
return await proc.exited;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const [exitCode] = await Promise.all([
|
|
128
|
+
proc.exited,
|
|
129
|
+
forwardSpawnOutput(proc.stdout, output),
|
|
130
|
+
forwardSpawnOutput(proc.stderr, output),
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
return exitCode;
|
|
69
134
|
}
|