nolo-cli 0.1.6 → 0.1.8
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 +75 -4
- package/agentRuntimeCommands.ts +464 -0
- package/client/agentRun.ts +198 -167
- package/commandRegistry.ts +14 -0
- package/connectorWebSocketTarget.ts +29 -0
- package/defaultServer.ts +1 -0
- package/index.ts +158 -104
- package/machineCommands.ts +382 -0
- package/package.json +9 -1
- package/tui/readlineWorkspace.ts +50 -0
- package/tui/session.ts +64 -2
- package/updateCommands.ts +70 -5
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,8 @@ 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",
|
|
133
|
+
" /context Show workspace context and next actions",
|
|
120
134
|
" /agent Show the current agent",
|
|
121
135
|
" /agents List built-in agent shortcuts",
|
|
122
136
|
" /switch <agent> Switch the current agent",
|
|
@@ -126,6 +140,7 @@ export function renderTuiHelp() {
|
|
|
126
140
|
" /customize Describe how you want to tune nolo",
|
|
127
141
|
" /login Show login/profile hint",
|
|
128
142
|
" /profile Show active profile",
|
|
143
|
+
" /update Update the global nolo install",
|
|
129
144
|
" /version Show version/update hint",
|
|
130
145
|
" /exit Leave the workspace",
|
|
131
146
|
"",
|
|
@@ -133,6 +148,26 @@ export function renderTuiHelp() {
|
|
|
133
148
|
].join("\n");
|
|
134
149
|
}
|
|
135
150
|
|
|
151
|
+
export function renderContextPanel(state: TuiState) {
|
|
152
|
+
const docs = state.attachedDocs.length
|
|
153
|
+
? state.attachedDocs.join(", ")
|
|
154
|
+
: "none";
|
|
155
|
+
return [
|
|
156
|
+
"Workspace context",
|
|
157
|
+
"-----------------",
|
|
158
|
+
`agent ${state.agentName}`,
|
|
159
|
+
`dialog ${resolveDialogLabel(state)}`,
|
|
160
|
+
`docs ${docs}`,
|
|
161
|
+
`profile ${state.profileName}`,
|
|
162
|
+
`server ${state.serverUrl}`,
|
|
163
|
+
"",
|
|
164
|
+
"Next:",
|
|
165
|
+
" /agents see specialist shortcuts",
|
|
166
|
+
" /doc attach <doc> add working context",
|
|
167
|
+
" /new start a clean dialog",
|
|
168
|
+
].join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
136
171
|
export function renderKnownAgents() {
|
|
137
172
|
return [
|
|
138
173
|
"Agents:",
|
|
@@ -184,6 +219,9 @@ export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
|
184
219
|
switch (command) {
|
|
185
220
|
case "/help":
|
|
186
221
|
return { nextState: state, output: renderTuiHelp() };
|
|
222
|
+
case "/context":
|
|
223
|
+
case "/ctx":
|
|
224
|
+
return { nextState: state, output: renderContextPanel(state) };
|
|
187
225
|
case "/exit":
|
|
188
226
|
case "/quit":
|
|
189
227
|
return { nextState: state, output: "Bye.", action: { type: "exit" } };
|
|
@@ -197,6 +235,24 @@ export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
|
197
235
|
},
|
|
198
236
|
output: "Started a fresh dialog.",
|
|
199
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
|
+
};
|
|
200
256
|
case "/agent":
|
|
201
257
|
return {
|
|
202
258
|
nextState: state,
|
|
@@ -278,6 +334,12 @@ export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
|
278
334
|
nextState: state,
|
|
279
335
|
output: `Profile: ${state.profileName}`,
|
|
280
336
|
};
|
|
337
|
+
case "/update":
|
|
338
|
+
return {
|
|
339
|
+
nextState: state,
|
|
340
|
+
output: "Starting self-update...",
|
|
341
|
+
action: { type: "self-update" },
|
|
342
|
+
};
|
|
281
343
|
case "/version":
|
|
282
344
|
return {
|
|
283
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
|
}
|