nolo-cli 0.1.2 → 0.1.5
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 +12 -2
- package/client/agentRun.ts +36 -8
- package/commandRegistry.ts +86 -82
- package/index.ts +61 -31
- package/package.json +18 -17
- package/tui/readlineWorkspace.ts +17 -6
- package/tui/session.ts +50 -17
- package/updateCommands.ts +69 -0
package/README.md
CHANGED
|
@@ -17,14 +17,22 @@ small: status line, text input, lightweight slash commands, and script-backed
|
|
|
17
17
|
Agent chat. A richer Ink UI can replace the rendering layer later without
|
|
18
18
|
changing the session model.
|
|
19
19
|
|
|
20
|
+
Inside `nolo`, normal text continues the current dialog after the first agent
|
|
21
|
+
reply. Use `/new` when you want a clean dialog. Response token details stay
|
|
22
|
+
hidden by default; set `NOLO_SHOW_USAGE=1` when debugging usage.
|
|
23
|
+
|
|
20
24
|
## Usage
|
|
21
25
|
|
|
22
26
|
```bash
|
|
23
27
|
npm install -g nolo-cli
|
|
24
28
|
nolo
|
|
25
29
|
|
|
26
|
-
#
|
|
27
|
-
|
|
30
|
+
# see which install you are running
|
|
31
|
+
nolo doctor
|
|
32
|
+
nolo --version
|
|
33
|
+
|
|
34
|
+
# update later, from inside nolo itself
|
|
35
|
+
nolo update
|
|
28
36
|
```
|
|
29
37
|
|
|
30
38
|
The npm package expects Bun to be available because the executable is a Bun
|
|
@@ -46,6 +54,8 @@ Local repo development can still use the script bridge without `AUTH_TOKEN`.
|
|
|
46
54
|
|
|
47
55
|
```bash
|
|
48
56
|
nolo --help
|
|
57
|
+
nolo doctor
|
|
58
|
+
nolo update
|
|
49
59
|
nolo
|
|
50
60
|
nolo chat
|
|
51
61
|
nolo run "summarize my latest agent dialogs"
|
package/client/agentRun.ts
CHANGED
|
@@ -12,6 +12,7 @@ type RunAgentTurnOptions = {
|
|
|
12
12
|
agentKey: string;
|
|
13
13
|
serverUrl: string;
|
|
14
14
|
message: string;
|
|
15
|
+
continueDialogId?: string;
|
|
15
16
|
scriptDir: string;
|
|
16
17
|
env: EnvLike;
|
|
17
18
|
output: OutputLike;
|
|
@@ -19,6 +20,11 @@ type RunAgentTurnOptions = {
|
|
|
19
20
|
fetchImpl?: typeof fetch;
|
|
20
21
|
};
|
|
21
22
|
|
|
23
|
+
export type RunAgentTurnResult = {
|
|
24
|
+
exitCode: number;
|
|
25
|
+
dialogId?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
22
28
|
type ScriptBridgeDecision = {
|
|
23
29
|
hasAuthToken: boolean;
|
|
24
30
|
scriptPathExists: boolean;
|
|
@@ -32,6 +38,10 @@ function resolveAuthToken(env: EnvLike) {
|
|
|
32
38
|
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
function shouldShowUsage(env: EnvLike) {
|
|
42
|
+
return env.NOLO_DEBUG === "1" || env.NOLO_SHOW_USAGE === "1";
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
function formatUsage(usage: any, dialogId: unknown) {
|
|
36
46
|
const parts: string[] = [];
|
|
37
47
|
if (typeof dialogId === "string" && dialogId) parts.push(`dialog=${dialogId}`);
|
|
@@ -56,6 +66,9 @@ async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string)
|
|
|
56
66
|
"--msg",
|
|
57
67
|
options.message,
|
|
58
68
|
"--no-default-test-root",
|
|
69
|
+
...(options.continueDialogId
|
|
70
|
+
? ["--continue", options.continueDialogId]
|
|
71
|
+
: []),
|
|
59
72
|
],
|
|
60
73
|
stdin: "inherit",
|
|
61
74
|
stdout: "inherit",
|
|
@@ -71,11 +84,11 @@ async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string)
|
|
|
71
84
|
if (exitCode !== 0) {
|
|
72
85
|
options.output.write(`\n[nolo] Agent run exited with code ${exitCode}.\n`);
|
|
73
86
|
}
|
|
74
|
-
return exitCode;
|
|
87
|
+
return { exitCode };
|
|
75
88
|
}
|
|
76
89
|
|
|
77
90
|
async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string) {
|
|
78
|
-
options.output.write(`\n${options.agentName} -> working...\n
|
|
91
|
+
options.output.write(`\n${options.agentName} -> working...\n`);
|
|
79
92
|
|
|
80
93
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
81
94
|
const res = await fetchImpl(`${options.serverUrl}/api/agent/run`, {
|
|
@@ -87,6 +100,16 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
87
100
|
body: JSON.stringify({
|
|
88
101
|
agentKey: options.agentKey,
|
|
89
102
|
userInput: options.message,
|
|
103
|
+
runtimeContext: {
|
|
104
|
+
surface: "cli",
|
|
105
|
+
host: "terminal",
|
|
106
|
+
runtime: "bun",
|
|
107
|
+
entrypoint: "nolo-cli",
|
|
108
|
+
capabilities: ["text-io", "slash-commands"],
|
|
109
|
+
},
|
|
110
|
+
...(options.continueDialogId
|
|
111
|
+
? { continueDialogId: options.continueDialogId }
|
|
112
|
+
: {}),
|
|
90
113
|
stream: false,
|
|
91
114
|
}),
|
|
92
115
|
});
|
|
@@ -103,19 +126,24 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
103
126
|
if (data?.error || data?.message) {
|
|
104
127
|
options.output.write(`${data.error || data.message}\n`);
|
|
105
128
|
}
|
|
106
|
-
return 1;
|
|
129
|
+
return { exitCode: 1 };
|
|
107
130
|
}
|
|
108
131
|
|
|
109
132
|
const content = String(data?.content ?? data?.message ?? "").trim();
|
|
110
133
|
if (content) {
|
|
111
|
-
options.output.write(
|
|
134
|
+
options.output.write(`\n${options.agentName} > ${content}\n`);
|
|
112
135
|
} else {
|
|
113
|
-
options.output.write(
|
|
136
|
+
options.output.write(`\n${options.agentName} > (no text response)\n`);
|
|
114
137
|
}
|
|
115
138
|
|
|
116
139
|
const usage = formatUsage(data?.usage, data?.dialogId);
|
|
117
|
-
if (usage) options.output.write(`${usage}\n`);
|
|
118
|
-
return
|
|
140
|
+
if (usage && shouldShowUsage(options.env)) options.output.write(`${usage}\n`);
|
|
141
|
+
return {
|
|
142
|
+
exitCode: 0,
|
|
143
|
+
...(typeof data?.dialogId === "string" && data.dialogId
|
|
144
|
+
? { dialogId: data.dialogId }
|
|
145
|
+
: {}),
|
|
146
|
+
};
|
|
119
147
|
}
|
|
120
148
|
|
|
121
149
|
export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
@@ -132,7 +160,7 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
|
132
160
|
"[nolo] This install needs an auth token before it can talk to agents.\n" +
|
|
133
161
|
"Set AUTH_TOKEN, or use the repo-local CLI where the dev script bridge is available.\n"
|
|
134
162
|
);
|
|
135
|
-
return 1;
|
|
163
|
+
return { exitCode: 1 };
|
|
136
164
|
}
|
|
137
165
|
|
|
138
166
|
return runHttpAgentTurn(options, authToken);
|
package/commandRegistry.ts
CHANGED
|
@@ -1,97 +1,101 @@
|
|
|
1
|
-
export type CommandEntry = {
|
|
2
|
-
path: string[];
|
|
3
|
-
script: string;
|
|
4
|
-
description: string;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export const COMMANDS: CommandEntry[] = [
|
|
8
|
-
{ path: ["chat"], script: "chatWithAgent.ts", description: "Chat with an agent" },
|
|
9
|
-
|
|
10
|
-
{ path: ["doc", "create"], script: "createDoc.ts", description: "Create a normal doc" },
|
|
11
|
-
{ path: ["doc", "read"], script: "readDoc.ts", description: "Read a normal doc" },
|
|
12
|
-
{ path: ["doc", "update"], script: "updateDoc.ts", description: "Update a normal doc" },
|
|
13
|
-
{ path: ["doc", "delete"], script: "deleteDoc.ts", description: "Delete a normal doc" },
|
|
14
|
-
|
|
15
|
-
{ path: ["skill-doc", "create"], script: "createSkillDoc.ts", description: "Create a skill doc" },
|
|
16
|
-
{ path: ["skill-doc", "read"], script: "readSkillDoc.ts", description: "Read a skill doc" },
|
|
17
|
-
{ path: ["skill-doc", "update"], script: "updateSkillDoc.ts", description: "Update a skill doc" },
|
|
18
|
-
{ path: ["skill-doc", "delete"], script: "deleteSkillDoc.ts", description: "Delete a skill doc" },
|
|
19
|
-
|
|
20
|
-
{ path: ["dialog", "read"], script: "readDialog.ts", description: "Read a dialog" },
|
|
21
|
-
{ path: ["space", "read"], script: "readSpace.ts", description: "Read a space" },
|
|
22
|
-
{ path: ["space", "category"], script: "upsertSpaceCategory.ts", description: "Create or update a space category" },
|
|
23
|
-
{ path: ["space", "content-category"], script: "setSpaceContentCategory.ts", description: "Move content into a space category" },
|
|
24
|
-
|
|
25
|
-
{ path: ["table", "data"], script: "tableData.ts", description: "Query or mutate table rows" },
|
|
26
|
-
{ path: ["table", "meta"], script: "upsertTableMeta.ts", description: "Create or update table metadata" },
|
|
27
|
-
|
|
28
|
-
{ path: ["agent", "list"], script: "listMyAgents.ts", description: "List owned agents" },
|
|
29
|
-
{ path: ["agent", "read"], script: "readAgent.ts", description: "Read a single agent" },
|
|
30
|
-
{ path: ["agent", "update"], script: "updateAgent.ts", description: "Update agent fields" },
|
|
31
|
-
{ path: ["agent", "unpublish"], script: "unpublishAgent.ts", description: "Remove an agent's public record" },
|
|
32
|
-
{ path: ["agent", "chat"], script: "chatWithAgent.ts", description: "Chat with an agent" },
|
|
33
|
-
{ path: ["agent", "dialogs"], script: "queryAgentDialogs.ts", description: "Inspect recent dialogs for an agent" },
|
|
34
|
-
{ path: ["agent", "doctor"], script: "doctorAgentWorkspace.ts", description: "Audit agent workspace health" },
|
|
35
|
-
{ path: ["agent", "create-custom"], script: "createCustomCodingAgent.ts", description: "Create a custom coding agent" },
|
|
36
|
-
{ path: ["agent", "create-space"], script: "createSpaceAgents.ts", description: "Create or attach shared-space agents" },
|
|
37
|
-
{ path: ["agent", "setup-demo"], script: "setupDemoAgent.ts", description: "Bootstrap demo publisher agents" },
|
|
38
|
-
{ path: ["agent", "supervise"], script: "runAutonomousAgent.ts", description: "Run an agent in autonomous cycles" },
|
|
39
|
-
|
|
40
|
-
{ path: ["llama"], script: "llamaServerSupervisor.ts", description: "Manage the llama.cpp runtime" },
|
|
41
|
-
{ path: ["model-runtime"], script: "localModelRuntimeSupervisor.ts", description: "Manage local model runtime processes" },
|
|
42
|
-
{ path: ["dev"], script: "devControl.ts", description: "Manage the local dev environment" },
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
export const GROUP_ORDER = [
|
|
46
|
-
"chat",
|
|
47
|
-
"doc",
|
|
48
|
-
"skill-doc",
|
|
49
|
-
"agent",
|
|
50
|
-
"dialog",
|
|
51
|
-
"space",
|
|
52
|
-
"table",
|
|
53
|
-
"llama",
|
|
54
|
-
"model-runtime",
|
|
55
|
-
"dev",
|
|
56
|
-
] as const;
|
|
57
|
-
|
|
58
|
-
export function renderHelpText() {
|
|
1
|
+
export type CommandEntry = {
|
|
2
|
+
path: string[];
|
|
3
|
+
script: string;
|
|
4
|
+
description: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const COMMANDS: CommandEntry[] = [
|
|
8
|
+
{ path: ["chat"], script: "chatWithAgent.ts", description: "Chat with an agent" },
|
|
9
|
+
|
|
10
|
+
{ path: ["doc", "create"], script: "createDoc.ts", description: "Create a normal doc" },
|
|
11
|
+
{ path: ["doc", "read"], script: "readDoc.ts", description: "Read a normal doc" },
|
|
12
|
+
{ path: ["doc", "update"], script: "updateDoc.ts", description: "Update a normal doc" },
|
|
13
|
+
{ path: ["doc", "delete"], script: "deleteDoc.ts", description: "Delete a normal doc" },
|
|
14
|
+
|
|
15
|
+
{ path: ["skill-doc", "create"], script: "createSkillDoc.ts", description: "Create a skill doc" },
|
|
16
|
+
{ path: ["skill-doc", "read"], script: "readSkillDoc.ts", description: "Read a skill doc" },
|
|
17
|
+
{ path: ["skill-doc", "update"], script: "updateSkillDoc.ts", description: "Update a skill doc" },
|
|
18
|
+
{ path: ["skill-doc", "delete"], script: "deleteSkillDoc.ts", description: "Delete a skill doc" },
|
|
19
|
+
|
|
20
|
+
{ path: ["dialog", "read"], script: "readDialog.ts", description: "Read a dialog" },
|
|
21
|
+
{ path: ["space", "read"], script: "readSpace.ts", description: "Read a space" },
|
|
22
|
+
{ path: ["space", "category"], script: "upsertSpaceCategory.ts", description: "Create or update a space category" },
|
|
23
|
+
{ path: ["space", "content-category"], script: "setSpaceContentCategory.ts", description: "Move content into a space category" },
|
|
24
|
+
|
|
25
|
+
{ path: ["table", "data"], script: "tableData.ts", description: "Query or mutate table rows" },
|
|
26
|
+
{ path: ["table", "meta"], script: "upsertTableMeta.ts", description: "Create or update table metadata" },
|
|
27
|
+
|
|
28
|
+
{ path: ["agent", "list"], script: "listMyAgents.ts", description: "List owned agents" },
|
|
29
|
+
{ path: ["agent", "read"], script: "readAgent.ts", description: "Read a single agent" },
|
|
30
|
+
{ path: ["agent", "update"], script: "updateAgent.ts", description: "Update agent fields" },
|
|
31
|
+
{ path: ["agent", "unpublish"], script: "unpublishAgent.ts", description: "Remove an agent's public record" },
|
|
32
|
+
{ path: ["agent", "chat"], script: "chatWithAgent.ts", description: "Chat with an agent" },
|
|
33
|
+
{ path: ["agent", "dialogs"], script: "queryAgentDialogs.ts", description: "Inspect recent dialogs for an agent" },
|
|
34
|
+
{ path: ["agent", "doctor"], script: "doctorAgentWorkspace.ts", description: "Audit agent workspace health" },
|
|
35
|
+
{ path: ["agent", "create-custom"], script: "createCustomCodingAgent.ts", description: "Create a custom coding agent" },
|
|
36
|
+
{ path: ["agent", "create-space"], script: "createSpaceAgents.ts", description: "Create or attach shared-space agents" },
|
|
37
|
+
{ path: ["agent", "setup-demo"], script: "setupDemoAgent.ts", description: "Bootstrap demo publisher agents" },
|
|
38
|
+
{ path: ["agent", "supervise"], script: "runAutonomousAgent.ts", description: "Run an agent in autonomous cycles" },
|
|
39
|
+
|
|
40
|
+
{ path: ["llama"], script: "llamaServerSupervisor.ts", description: "Manage the llama.cpp runtime" },
|
|
41
|
+
{ path: ["model-runtime"], script: "localModelRuntimeSupervisor.ts", description: "Manage local model runtime processes" },
|
|
42
|
+
{ path: ["dev"], script: "devControl.ts", description: "Manage the local dev environment" },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export const GROUP_ORDER = [
|
|
46
|
+
"chat",
|
|
47
|
+
"doc",
|
|
48
|
+
"skill-doc",
|
|
49
|
+
"agent",
|
|
50
|
+
"dialog",
|
|
51
|
+
"space",
|
|
52
|
+
"table",
|
|
53
|
+
"llama",
|
|
54
|
+
"model-runtime",
|
|
55
|
+
"dev",
|
|
56
|
+
] as const;
|
|
57
|
+
|
|
58
|
+
export function renderHelpText() {
|
|
59
59
|
const lines = [
|
|
60
60
|
"nolo — Agent-first terminal workspace",
|
|
61
61
|
"",
|
|
62
62
|
"Usage:",
|
|
63
63
|
" nolo",
|
|
64
64
|
" nolo <command> [subcommand] [...args]",
|
|
65
|
+
" nolo doctor",
|
|
66
|
+
" nolo update",
|
|
65
67
|
"",
|
|
66
68
|
"Examples:",
|
|
67
69
|
" nolo",
|
|
68
70
|
" nolo chat",
|
|
69
71
|
" nolo login",
|
|
70
72
|
" nolo whoami",
|
|
73
|
+
" nolo doctor",
|
|
74
|
+
" nolo update",
|
|
71
75
|
' nolo doc create --title "Trip Notes" --body "hello"',
|
|
72
76
|
' nolo skill-doc create --title "Agent Query Skill" --description "Inspect recent agent dialogs"',
|
|
73
77
|
" nolo agent list --json",
|
|
74
|
-
" nolo agent read agent-pub-01APPBUILDER00000001YAII3I",
|
|
75
|
-
' nolo chat --agent agent-pub-01APPBUILDER00000001YAII3I --msg "你好"',
|
|
76
|
-
" nolo table data --table 01ABCXYZ --action query",
|
|
77
|
-
" nolo llama status",
|
|
78
|
-
];
|
|
79
|
-
|
|
80
|
-
for (const group of GROUP_ORDER) {
|
|
81
|
-
const commands = COMMANDS.filter((entry) => entry.path[0] === group);
|
|
82
|
-
if (commands.length === 0) continue;
|
|
83
|
-
lines.push("", group);
|
|
84
|
-
for (const entry of commands) {
|
|
85
|
-
lines.push(` ${entry.path.join(" ")} ${entry.description}`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return lines.join("\n");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function resolveCommand(args: string[]) {
|
|
93
|
-
const sorted = [...COMMANDS].sort((a, b) => b.path.length - a.path.length);
|
|
94
|
-
return sorted.find((entry) =>
|
|
95
|
-
entry.path.every((part, index) => args[index] === part)
|
|
96
|
-
);
|
|
97
|
-
}
|
|
78
|
+
" nolo agent read agent-pub-01APPBUILDER00000001YAII3I",
|
|
79
|
+
' nolo chat --agent agent-pub-01APPBUILDER00000001YAII3I --msg "你好"',
|
|
80
|
+
" nolo table data --table 01ABCXYZ --action query",
|
|
81
|
+
" nolo llama status",
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
for (const group of GROUP_ORDER) {
|
|
85
|
+
const commands = COMMANDS.filter((entry) => entry.path[0] === group);
|
|
86
|
+
if (commands.length === 0) continue;
|
|
87
|
+
lines.push("", group);
|
|
88
|
+
for (const entry of commands) {
|
|
89
|
+
lines.push(` ${entry.path.join(" ")} ${entry.description}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function resolveCommand(args: string[]) {
|
|
97
|
+
const sorted = [...COMMANDS].sort((a, b) => b.path.length - a.path.length);
|
|
98
|
+
return sorted.find((entry) =>
|
|
99
|
+
entry.path.every((part, index) => args[index] === part)
|
|
100
|
+
);
|
|
101
|
+
}
|
package/index.ts
CHANGED
|
@@ -1,35 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
import { renderHelpText, resolveCommand } from "./commandRegistry";
|
|
7
7
|
import { runLoginCommand, runLogoutCommand, runWhoamiCommand } from "./authCommands";
|
|
8
8
|
import { buildEnvFromProfile, loadProfileConfig } from "./client/profileConfig";
|
|
9
9
|
import { startTuiWorkspace } from "./tui/readlineWorkspace";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
|
|
28
35
|
const args = process.argv.slice(2);
|
|
29
36
|
const profileEnv = buildEnvFromProfile(loadProfileConfig());
|
|
30
37
|
const runtimeEnv = {
|
|
31
38
|
...profileEnv,
|
|
32
39
|
...process.env,
|
|
40
|
+
NOLO_CLI_VERSION: packageInfo.version,
|
|
33
41
|
};
|
|
34
42
|
|
|
35
43
|
if (args.length === 0) {
|
|
@@ -58,18 +66,40 @@ if (args[0] === "logout") {
|
|
|
58
66
|
process.exit(runLogoutCommand());
|
|
59
67
|
}
|
|
60
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
|
+
|
|
61
91
|
if (args[0] === "--help" || args[0] === "-h") {
|
|
62
92
|
console.log(renderHelpText());
|
|
63
93
|
process.exit(0);
|
|
64
94
|
}
|
|
65
|
-
|
|
66
|
-
const command = resolveCommand(args);
|
|
67
|
-
if (!command) {
|
|
68
|
-
console.error(`Unknown command: ${args.join(" ")}`);
|
|
69
|
-
console.error("");
|
|
70
|
-
console.log(renderHelpText());
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const forwardedArgs = args.slice(command.path.length);
|
|
75
|
-
await runScript(command.script, forwardedArgs);
|
|
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);
|
package/package.json
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
2
|
"name": "nolo-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"type": "module",
|
|
5
5
|
"description": "Agent-first terminal workspace for Nolo",
|
|
6
|
-
"bin": {
|
|
7
|
-
"nolo": "
|
|
8
|
-
},
|
|
9
|
-
"module": "index.ts",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nolo": "index.ts"
|
|
8
|
+
},
|
|
9
|
+
"module": "index.ts",
|
|
10
10
|
"files": [
|
|
11
11
|
"index.ts",
|
|
12
12
|
"authCommands.ts",
|
|
13
13
|
"commandRegistry.ts",
|
|
14
|
+
"updateCommands.ts",
|
|
14
15
|
"client/agentRun.ts",
|
|
15
16
|
"client/profileConfig.ts",
|
|
16
17
|
"tui/readlineWorkspace.ts",
|
|
17
18
|
"tui/session.ts",
|
|
18
19
|
"README.md"
|
|
19
20
|
],
|
|
20
|
-
"publishConfig": {
|
|
21
|
-
"access": "public"
|
|
22
|
-
},
|
|
23
|
-
"devDependencies": {
|
|
24
|
-
"bun-types": "latest"
|
|
25
|
-
},
|
|
26
|
-
"peerDependencies": {
|
|
27
|
-
"typescript": "^5.0.0"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"bun-types": "latest"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"typescript": "^5.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/tui/readlineWorkspace.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import { stdin as defaultInput, stdout as defaultOutput } from "node:process";
|
|
3
|
-
import { runAgentTurn } from "../client/agentRun";
|
|
3
|
+
import { runAgentTurn, type RunAgentTurnResult } from "../client/agentRun";
|
|
4
4
|
import {
|
|
5
5
|
createInitialTuiState,
|
|
6
6
|
handleTuiInput,
|
|
@@ -15,6 +15,7 @@ type WorkspaceOptions = {
|
|
|
15
15
|
env?: NodeJS.ProcessEnv;
|
|
16
16
|
input?: NodeJS.ReadableStream;
|
|
17
17
|
output?: NodeJS.WritableStream;
|
|
18
|
+
agentRunner?: typeof runAgentTurn;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
async function runAgentChat(
|
|
@@ -22,18 +23,20 @@ async function runAgentChat(
|
|
|
22
23
|
state: TuiState,
|
|
23
24
|
message: string,
|
|
24
25
|
env: NodeJS.ProcessEnv,
|
|
25
|
-
output: NodeJS.WritableStream
|
|
26
|
+
output: NodeJS.WritableStream,
|
|
27
|
+
agentRunner: typeof runAgentTurn = runAgentTurn
|
|
26
28
|
) {
|
|
27
|
-
const
|
|
29
|
+
const result: RunAgentTurnResult = await agentRunner({
|
|
28
30
|
agentName: state.agentName,
|
|
29
31
|
agentKey: state.agentKey,
|
|
30
32
|
serverUrl: state.serverUrl,
|
|
31
33
|
message,
|
|
34
|
+
continueDialogId: state.dialogId,
|
|
32
35
|
scriptDir,
|
|
33
36
|
env,
|
|
34
37
|
output,
|
|
35
38
|
});
|
|
36
|
-
return
|
|
39
|
+
return result;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export async function startTuiWorkspace(options: WorkspaceOptions) {
|
|
@@ -60,13 +63,21 @@ export async function startTuiWorkspace(options: WorkspaceOptions) {
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
if (result.action?.type === "chat") {
|
|
63
|
-
await runAgentChat(
|
|
66
|
+
const runResult = await runAgentChat(
|
|
64
67
|
options.scriptDir,
|
|
65
68
|
state,
|
|
66
69
|
result.action.message,
|
|
67
70
|
options.env ?? process.env,
|
|
68
|
-
output
|
|
71
|
+
output,
|
|
72
|
+
options.agentRunner
|
|
69
73
|
);
|
|
74
|
+
if (runResult.dialogId) {
|
|
75
|
+
state = {
|
|
76
|
+
...state,
|
|
77
|
+
dialogId: runResult.dialogId,
|
|
78
|
+
dialogLabel: runResult.dialogId,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
70
81
|
}
|
|
71
82
|
|
|
72
83
|
output.write(`\n${renderStatusLine(state)}\n`);
|
package/tui/session.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const DEFAULT_TUI_AGENT_KEY = "agent-pub-
|
|
1
|
+
export const DEFAULT_TUI_AGENT_KEY = "agent-pub-01NOLOAPPBLD000000019KCKT0";
|
|
2
2
|
export const DEFAULT_TUI_SERVER_URL = "http://127.0.0.1:38123";
|
|
3
3
|
|
|
4
4
|
const KNOWN_AGENT_ALIASES: Record<string, { name: string; key: string }> = {
|
|
@@ -12,12 +12,26 @@ const KNOWN_AGENT_ALIASES: Record<string, { name: string; key: string }> = {
|
|
|
12
12
|
},
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
function shortenDialogId(dialogId: string) {
|
|
16
|
+
return dialogId.length > 12
|
|
17
|
+
? `${dialogId.slice(0, 6)}...${dialogId.slice(-4)}`
|
|
18
|
+
: dialogId;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveDialogLabel(
|
|
22
|
+
state: Pick<TuiState, "dialogId" | "dialogLabel">
|
|
23
|
+
) {
|
|
24
|
+
return state.dialogId ? shortenDialogId(state.dialogId) : state.dialogLabel;
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
export type TuiState = {
|
|
16
28
|
agentKey: string;
|
|
17
29
|
agentName: string;
|
|
30
|
+
dialogId?: string;
|
|
18
31
|
dialogLabel: string;
|
|
19
32
|
profileName: string;
|
|
20
33
|
serverUrl: string;
|
|
34
|
+
cliVersion?: string;
|
|
21
35
|
attachedDocs: string[];
|
|
22
36
|
};
|
|
23
37
|
|
|
@@ -26,6 +40,7 @@ export type TuiAction =
|
|
|
26
40
|
type: "chat";
|
|
27
41
|
message: string;
|
|
28
42
|
agentKey: string;
|
|
43
|
+
continueDialogId?: string;
|
|
29
44
|
}
|
|
30
45
|
| {
|
|
31
46
|
type: "exit";
|
|
@@ -41,14 +56,16 @@ type EnvLike = Record<string, string | undefined>;
|
|
|
41
56
|
|
|
42
57
|
export function createInitialTuiState(env: EnvLike = process.env): TuiState {
|
|
43
58
|
const agentKey = env.NOLO_AGENT?.trim() || DEFAULT_TUI_AGENT_KEY;
|
|
44
|
-
const agentName = env.NOLO_AGENT_NAME?.trim() || "
|
|
59
|
+
const agentName = env.NOLO_AGENT_NAME?.trim() || "nolo";
|
|
45
60
|
|
|
46
61
|
return {
|
|
47
62
|
agentKey,
|
|
48
63
|
agentName,
|
|
64
|
+
dialogId: env.NOLO_DIALOG_ID?.trim() || undefined,
|
|
49
65
|
dialogLabel: env.NOLO_DIALOG?.trim() || "new",
|
|
50
66
|
profileName: env.NOLO_PROFILE?.trim() || "local",
|
|
51
67
|
serverUrl: (env.NOLO_SERVER || env.BASE_URL || DEFAULT_TUI_SERVER_URL).replace(/\/+$/, ""),
|
|
68
|
+
cliVersion: env.NOLO_CLI_VERSION?.trim() || undefined,
|
|
52
69
|
attachedDocs: [],
|
|
53
70
|
};
|
|
54
71
|
}
|
|
@@ -56,29 +73,28 @@ export function createInitialTuiState(env: EnvLike = process.env): TuiState {
|
|
|
56
73
|
export function renderStatusLine(state: TuiState) {
|
|
57
74
|
const docs =
|
|
58
75
|
state.attachedDocs.length > 0
|
|
59
|
-
? state.attachedDocs.
|
|
60
|
-
: "0
|
|
76
|
+
? String(state.attachedDocs.length)
|
|
77
|
+
: "0";
|
|
61
78
|
|
|
62
79
|
return [
|
|
63
80
|
`agent ${state.agentName}`,
|
|
64
|
-
`dialog ${state
|
|
81
|
+
`dialog ${resolveDialogLabel(state)}`,
|
|
65
82
|
`docs ${docs}`,
|
|
66
83
|
`profile ${state.profileName}`,
|
|
67
84
|
].join(" | ");
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
export function renderWelcome(state: TuiState) {
|
|
88
|
+
const docs = state.attachedDocs.length > 0
|
|
89
|
+
? `${state.attachedDocs.length} attached`
|
|
90
|
+
: "none";
|
|
71
91
|
return [
|
|
72
92
|
"",
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
`dialog ${state.dialogLabel}`,
|
|
77
|
-
`docs ${state.attachedDocs.length ? state.attachedDocs.join(", ") : "none"}`,
|
|
78
|
-
`profile ${state.profileName}`,
|
|
79
|
-
`server ${state.serverUrl}`,
|
|
93
|
+
`Nolo workspace${state.cliVersion ? ` nolo ${state.cliVersion}` : ""}`,
|
|
94
|
+
`agent ${state.agentName} | dialog ${resolveDialogLabel(state)} | docs ${docs} | profile ${state.profileName}`,
|
|
95
|
+
`server ${state.serverUrl}`,
|
|
80
96
|
"",
|
|
81
|
-
"Tell nolo what you want. Use /help for commands.",
|
|
97
|
+
"Tell nolo what you want. Use /help for commands. Use /version if this install feels stale.",
|
|
82
98
|
"",
|
|
83
99
|
].join("\n");
|
|
84
100
|
}
|
|
@@ -91,7 +107,7 @@ export function renderTuiHelp() {
|
|
|
91
107
|
return [
|
|
92
108
|
"Commands:",
|
|
93
109
|
" /help Show this help",
|
|
94
|
-
" /new Start a fresh dialog
|
|
110
|
+
" /new Start a fresh dialog",
|
|
95
111
|
" /agent Show the current agent",
|
|
96
112
|
" /switch <agent> Switch the current agent",
|
|
97
113
|
" /dialog Show the current dialog",
|
|
@@ -100,6 +116,7 @@ export function renderTuiHelp() {
|
|
|
100
116
|
" /customize Describe how you want to tune nolo",
|
|
101
117
|
" /login Show login/profile hint",
|
|
102
118
|
" /profile Show active profile",
|
|
119
|
+
" /version Show version/update hint",
|
|
103
120
|
" /exit Leave the workspace",
|
|
104
121
|
"",
|
|
105
122
|
"You can also type normally. nolo will send it to the current agent.",
|
|
@@ -131,6 +148,7 @@ export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
|
131
148
|
type: "chat",
|
|
132
149
|
message: trimmed,
|
|
133
150
|
agentKey: state.agentKey,
|
|
151
|
+
...(state.dialogId ? { continueDialogId: state.dialogId } : {}),
|
|
134
152
|
},
|
|
135
153
|
};
|
|
136
154
|
}
|
|
@@ -146,8 +164,13 @@ export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
|
146
164
|
return { nextState: state, output: "Bye.", action: { type: "exit" } };
|
|
147
165
|
case "/new":
|
|
148
166
|
return {
|
|
149
|
-
nextState: {
|
|
150
|
-
|
|
167
|
+
nextState: {
|
|
168
|
+
...state,
|
|
169
|
+
dialogId: undefined,
|
|
170
|
+
dialogLabel: "new",
|
|
171
|
+
attachedDocs: [],
|
|
172
|
+
},
|
|
173
|
+
output: "Started a fresh dialog.",
|
|
151
174
|
};
|
|
152
175
|
case "/agent":
|
|
153
176
|
return {
|
|
@@ -182,7 +205,9 @@ export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
|
182
205
|
case "/dialog":
|
|
183
206
|
return {
|
|
184
207
|
nextState: state,
|
|
185
|
-
output:
|
|
208
|
+
output: state.dialogId
|
|
209
|
+
? `Current dialog: ${state.dialogId}`
|
|
210
|
+
: "Current dialog: new",
|
|
186
211
|
};
|
|
187
212
|
case "/doc": {
|
|
188
213
|
if (rest[0] === "attach") {
|
|
@@ -223,6 +248,14 @@ export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
|
223
248
|
nextState: state,
|
|
224
249
|
output: `Profile: ${state.profileName}`,
|
|
225
250
|
};
|
|
251
|
+
case "/version":
|
|
252
|
+
return {
|
|
253
|
+
nextState: state,
|
|
254
|
+
output:
|
|
255
|
+
`nolo ${state.cliVersion || "unknown version"}\n` +
|
|
256
|
+
"Update this install with: nolo update\n" +
|
|
257
|
+
"If repo-local output differs, publish/install the latest npm package first.",
|
|
258
|
+
};
|
|
226
259
|
default:
|
|
227
260
|
return {
|
|
228
261
|
nextState: state,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
type PackageInfo = {
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type DoctorInfo = {
|
|
11
|
+
packageName: string;
|
|
12
|
+
version: string;
|
|
13
|
+
entrypoint: string;
|
|
14
|
+
serverUrl: string;
|
|
15
|
+
profileName: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const CLI_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const PACKAGE_JSON_PATH = join(CLI_DIR, "package.json");
|
|
20
|
+
|
|
21
|
+
export function readPackageInfo(): PackageInfo {
|
|
22
|
+
const raw = readFileSync(PACKAGE_JSON_PATH, "utf8");
|
|
23
|
+
const parsed = JSON.parse(raw) as Partial<PackageInfo>;
|
|
24
|
+
return {
|
|
25
|
+
name: parsed.name || "nolo-cli",
|
|
26
|
+
version: parsed.version || "0.0.0",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildCliVersionText(info: PackageInfo) {
|
|
31
|
+
return `${info.name} ${info.version}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function buildSelfUpdateCommand() {
|
|
35
|
+
return ["npm", "install", "-g", "nolo-cli@latest", "--force"];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildCliDoctorText(info: DoctorInfo) {
|
|
39
|
+
const updateCommand = buildSelfUpdateCommand().join(" ");
|
|
40
|
+
return [
|
|
41
|
+
"Nolo CLI doctor",
|
|
42
|
+
"---------------",
|
|
43
|
+
`version ${info.packageName} ${info.version}`,
|
|
44
|
+
`entry ${info.entrypoint}`,
|
|
45
|
+
`server ${info.serverUrl}`,
|
|
46
|
+
`profile ${info.profileName}`,
|
|
47
|
+
`update nolo update`,
|
|
48
|
+
"",
|
|
49
|
+
"If direct `nolo` differs from repo-local `bun ./packages/cli/index.ts`,",
|
|
50
|
+
"the global npm install is older than this checkout.",
|
|
51
|
+
"",
|
|
52
|
+
`Manual update: ${updateCommand}`,
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function runSelfUpdate(output: NodeJS.WritableStream = process.stdout) {
|
|
57
|
+
const command = buildSelfUpdateCommand();
|
|
58
|
+
output.write(`Updating nolo with: ${command.join(" ")}\n`);
|
|
59
|
+
|
|
60
|
+
const proc = Bun.spawn({
|
|
61
|
+
cmd: command,
|
|
62
|
+
stdin: "inherit",
|
|
63
|
+
stdout: "inherit",
|
|
64
|
+
stderr: "inherit",
|
|
65
|
+
env: process.env,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return await proc.exited;
|
|
69
|
+
}
|