nolo-cli 0.1.7 → 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.
@@ -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 = "http://127.0.0.1:38123";
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
- export async function runSelfUpdate(output: NodeJS.WritableStream = process.stdout) {
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 proc = Bun.spawn({
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
- return await proc.exited;
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
  }