nolo-cli 0.1.16 → 0.1.18

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.
@@ -12,6 +12,7 @@ export type LocalAgentTurnInput = {
12
12
  agentRef: string;
13
13
  input: AgentRuntimeMessageContent;
14
14
  continueDialogId?: string;
15
+ maxToolRounds?: number;
15
16
  };
16
17
 
17
18
  export type LocalAgentTurnResult = AgentRuntimeResult & {
@@ -49,15 +50,48 @@ export async function runLocalAgentTurn(
49
50
  input: input.input,
50
51
  });
51
52
  const provider = await input.adapter.resolveProvider(agentConfig);
52
- const result = await provider.complete(messages);
53
+ const maxToolRounds = input.maxToolRounds ?? 6;
54
+ let toolCallCount = 0;
55
+ let result: AgentRuntimeResult;
56
+ for (let round = 0; round <= maxToolRounds; round += 1) {
57
+ result = await provider.complete(messages);
58
+ const toolCalls = result.tool_calls ?? [];
59
+ if (toolCalls.length === 0) break;
60
+ if (round === maxToolRounds) {
61
+ throw new Error(`Local agent exceeded max tool rounds: ${maxToolRounds}`);
62
+ }
63
+ toolCallCount += toolCalls.length;
64
+ messages.push({
65
+ role: "assistant",
66
+ content: result.content || null,
67
+ tool_calls: toolCalls,
68
+ });
69
+ for (const toolCall of toolCalls) {
70
+ const toolResult = await input.adapter.executeTool({
71
+ id: toolCall.id,
72
+ name: toolCall.function.name,
73
+ arguments: toolCall.function.arguments,
74
+ });
75
+ messages.push({
76
+ role: "tool",
77
+ content: toolResult.content,
78
+ tool_call_id: toolCall.id,
79
+ });
80
+ }
81
+ }
82
+ result = result!;
53
83
  const saved = await input.adapter.saveTurn({
54
84
  agentKey: agentConfig.key,
55
85
  messages,
56
- result,
86
+ result: {
87
+ ...result,
88
+ ...(toolCallCount > 0 ? { toolCallCount } : {}),
89
+ },
57
90
  });
58
91
 
59
92
  return {
60
93
  ...result,
94
+ ...(toolCallCount > 0 ? { toolCallCount } : {}),
61
95
  dialogId: saved.dialogId,
62
96
  };
63
97
  }
@@ -51,6 +51,7 @@ export interface AgentRuntimeResult {
51
51
  outputPrice?: number;
52
52
  usage?: Record<string, any>;
53
53
  trace?: AgentRuntimeChatMessage[];
54
+ tool_calls?: AgentRuntimeToolCall[];
54
55
  runtimeToolNames?: string[];
55
56
  toolCallCount?: number;
56
57
  policyState?: unknown;
@@ -0,0 +1,26 @@
1
+ export const PLATFORM_DEMO_USER_ID = "b2e06f801f";
2
+ export const FRONTEND_IMPLEMENTER_AGENT_ID = "01FRONTENDAG0000000115N4E1";
3
+ export const FRONTEND_IMPLEMENTER_AGENT_KEY =
4
+ `agent-${PLATFORM_DEMO_USER_ID}-${FRONTEND_IMPLEMENTER_AGENT_ID}`;
5
+
6
+ const AGENT_ALIAS_TO_KEY: Record<string, string> = {
7
+ "frontend-implementer": FRONTEND_IMPLEMENTER_AGENT_KEY,
8
+ "frontend-agent": FRONTEND_IMPLEMENTER_AGENT_KEY,
9
+ frontend: FRONTEND_IMPLEMENTER_AGENT_KEY,
10
+ "front-end": FRONTEND_IMPLEMENTER_AGENT_KEY,
11
+ "前端": FRONTEND_IMPLEMENTER_AGENT_KEY,
12
+ "前端实现员": FRONTEND_IMPLEMENTER_AGENT_KEY,
13
+ };
14
+
15
+ function parseAgentKeyFromInput(raw: string): string {
16
+ const trimmed = raw.trim();
17
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
18
+ const url = new URL(trimmed);
19
+ return url.pathname.replace(/^\/+/, "");
20
+ }
21
+ return trimmed;
22
+ }
23
+
24
+ export function resolveCliAgentKeyInput(raw: string): string {
25
+ return AGENT_ALIAS_TO_KEY[raw.trim().toLowerCase()] ?? parseAgentKeyFromInput(raw);
26
+ }
@@ -1,6 +1,7 @@
1
1
  import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
2
2
  import { getDefaultCliLocalRuntimeDb } from "./localRuntimeDb";
3
3
  import type { CliLocalRuntimeDb } from "./client/localRuntimeAdapter";
4
+ import { resolveCliAgentKeyInput } from "./agentAliases";
4
5
 
5
6
  type EnvLike = Record<string, string | undefined>;
6
7
 
@@ -40,7 +41,7 @@ function positionalArgs(args: string[]) {
40
41
  export function parseAgentPullArgs(args: string[]): ParsedAgentPullArgs | null {
41
42
  const agentKey = readFlagValue(args, "--agent") ?? positionalArgs(args)[0];
42
43
  if (!agentKey?.trim()) return null;
43
- return { agentKey: agentKey.trim() };
44
+ return { agentKey: resolveCliAgentKeyInput(agentKey) };
44
45
  }
45
46
 
46
47
  function resolveServerUrl(env: EnvLike) {
@@ -2,7 +2,8 @@ import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
2
2
  import { runAgentTurn, type RunAgentTurnResult } from "./client/agentRun";
3
3
  import type { AgentRuntimeRequestedMode } from "./agentRuntimeLocal";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
- import { extname, resolve } from "node:path";
5
+ import { extname, join, resolve } from "node:path";
6
+ import { resolveCliAgentKeyInput } from "./agentAliases";
6
7
 
7
8
  type EnvLike = Record<string, string | undefined>;
8
9
 
@@ -15,12 +16,14 @@ type AgentRunCommandDeps = {
15
16
  scriptDir: string;
16
17
  output?: OutputLike;
17
18
  runner?: typeof runAgentTurn;
19
+ prepareShellWorktree?: typeof prepareShellWorktree;
18
20
  };
19
21
 
20
22
  type ParsedAgentRunArgs = {
21
23
  agentKey: string;
22
24
  message: string;
23
25
  imageUrls: string[];
26
+ allowShell: boolean;
24
27
  runtimeMode?: AgentRuntimeRequestedMode;
25
28
  continueDialogId?: string;
26
29
  };
@@ -51,9 +54,15 @@ function runtimeModeFromArgs(args: string[]): AgentRuntimeRequestedMode | undefi
51
54
 
52
55
  function positionalArgs(args: string[]) {
53
56
  const values: string[] = [];
57
+ const valuelessFlags = new Set([
58
+ "--local",
59
+ "--server",
60
+ "--auto",
61
+ "--dangerously-allow-shell",
62
+ ]);
54
63
  for (let index = 0; index < args.length; index += 1) {
55
64
  const arg = args[index];
56
- if (arg === "--local" || arg === "--server" || arg === "--auto") continue;
65
+ if (valuelessFlags.has(arg)) continue;
57
66
  if (arg.startsWith("--")) {
58
67
  index += 1;
59
68
  continue;
@@ -75,9 +84,10 @@ export function parseAgentRunArgs(args: string[]): ParsedAgentRunArgs | null {
75
84
  ...readRepeatedFlagValues(args, "--image-url"),
76
85
  ];
77
86
  return {
78
- agentKey,
87
+ agentKey: resolveCliAgentKeyInput(agentKey),
79
88
  message: message.trim(),
80
89
  imageUrls,
90
+ allowShell: args.includes("--dangerously-allow-shell"),
81
91
  ...(runtimeMode ? { runtimeMode } : {}),
82
92
  ...(continueDialogId ? { continueDialogId } : {}),
83
93
  };
@@ -109,10 +119,53 @@ function resolveServerUrl(env: EnvLike) {
109
119
  return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
110
120
  }
111
121
 
122
+ function safePathSegment(input: string) {
123
+ return input.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "agent";
124
+ }
125
+
126
+ function createTaskId() {
127
+ return Date.now().toString(36).toUpperCase();
128
+ }
129
+
130
+ async function runGit(args: string[], cwd: string) {
131
+ const proc = Bun.spawn(["git", ...args], {
132
+ cwd,
133
+ stdout: "pipe",
134
+ stderr: "pipe",
135
+ stdin: "ignore",
136
+ });
137
+ const [stdout, stderr, exitCode] = await Promise.all([
138
+ new Response(proc.stdout).text(),
139
+ new Response(proc.stderr).text(),
140
+ proc.exited,
141
+ ]);
142
+ if (exitCode !== 0) {
143
+ throw new Error(stderr.trim() || stdout.trim() || `git ${args.join(" ")} failed`);
144
+ }
145
+ return stdout.trim();
146
+ }
147
+
148
+ export async function prepareShellWorktree(args: {
149
+ agentKey: string;
150
+ cwd?: string;
151
+ env?: EnvLike;
152
+ }) {
153
+ if (args.env?.NOLO_LOCAL_WORKTREE) return args.env.NOLO_LOCAL_WORKTREE;
154
+ const cwd = args.cwd ?? process.cwd();
155
+ const root = await runGit(["rev-parse", "--show-toplevel"], cwd);
156
+ const parent = join(root, ".worktrees");
157
+ const safeAgent = safePathSegment(args.agentKey);
158
+ const taskId = createTaskId();
159
+ const worktreePath = join(parent, `nolo-agent-${safeAgent}-${taskId}`);
160
+ const branchName = `nolo-agent-${safeAgent}-${taskId}`;
161
+ await runGit(["worktree", "add", worktreePath, "-b", branchName, "HEAD"], root);
162
+ return worktreePath;
163
+ }
164
+
112
165
  function writeUsage(output: OutputLike) {
113
166
  output.write(
114
167
  "Usage: nolo agent run <agent> <message> [--local|--server|--auto] [--continue <dialogId>]\n" +
115
- " nolo agent run --agent <agent> --msg <message> [--image <url-or-path>] [--local|--server|--auto]\n"
168
+ " nolo agent run --agent <agent> --msg <message> [--image <url-or-path>] [--dangerously-allow-shell] [--local|--server|--auto]\n"
116
169
  );
117
170
  }
118
171
 
@@ -126,6 +179,20 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
126
179
  }
127
180
 
128
181
  const runner = deps.runner ?? runAgentTurn;
182
+ let localRuntimeCwd: string | undefined;
183
+ if (parsed.allowShell) {
184
+ localRuntimeCwd = await (deps.prepareShellWorktree ?? prepareShellWorktree)({
185
+ agentKey: parsed.agentKey,
186
+ env,
187
+ });
188
+ }
189
+ const runEnv = parsed.allowShell
190
+ ? {
191
+ ...env,
192
+ NOLO_LOCAL_SHELL_MODE: "worktree",
193
+ ...(localRuntimeCwd ? { NOLO_LOCAL_WORKTREE: localRuntimeCwd } : {}),
194
+ }
195
+ : env;
129
196
  const result: RunAgentTurnResult = await runner({
130
197
  agentName: parsed.agentKey,
131
198
  agentKey: parsed.agentKey,
@@ -133,8 +200,9 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
133
200
  message: parsed.message,
134
201
  imageUrls: parsed.imageUrls.map(normalizeCliImageInput),
135
202
  scriptDir: deps.scriptDir,
136
- env,
203
+ env: runEnv,
137
204
  output,
205
+ ...(localRuntimeCwd ? { localRuntimeCwd } : {}),
138
206
  ...(parsed.runtimeMode ? { runtimeMode: parsed.runtimeMode } : {}),
139
207
  ...(parsed.continueDialogId ? { continueDialogId: parsed.continueDialogId } : {}),
140
208
  });
@@ -24,6 +24,7 @@ type RunAgentTurnOptions = {
24
24
  runtimeMode?: AgentRuntimeRequestedMode;
25
25
  localRuntimeAdapter?: AgentRuntimeHostAdapter;
26
26
  localRuntimeAdapterFactory?: (env: EnvLike) => AgentRuntimeHostAdapter;
27
+ localRuntimeCwd?: string;
27
28
  scriptPathExists?: (path: string) => boolean;
28
29
  fetchImpl?: typeof fetch;
29
30
  };
@@ -61,6 +62,7 @@ function buildDefaultLocalRuntimeAdapter(options: RunAgentTurnOptions) {
61
62
  return createCliLocalRuntimeAdapter({
62
63
  env: options.env,
63
64
  fetchImpl: options.fetchImpl,
65
+ cwd: options.localRuntimeCwd,
64
66
  });
65
67
  }
66
68
 
@@ -173,7 +173,7 @@ describe("CLI local runtime adapter", () => {
173
173
  id: "call-1",
174
174
  name: "execShell",
175
175
  arguments: "{}",
176
- })).rejects.toThrow("blocked by the local CLI safety policy");
176
+ })).rejects.toThrow("execShell requires NOLO_LOCAL_SHELL_MODE");
177
177
  });
178
178
 
179
179
  test("executes explicitly allowed registered local tools declared by the agent", async () => {
@@ -214,4 +214,71 @@ describe("CLI local runtime adapter", () => {
214
214
 
215
215
  expect(result.content).toContain("README.md");
216
216
  });
217
+
218
+ test("advertises execShell to OpenAI-compatible providers when shell mode is enabled", async () => {
219
+ const requests: Array<{ body: any }> = [];
220
+ const adapter = createCliLocalRuntimeAdapter({
221
+ env: {
222
+ OPENAI_API_KEY: "sk-local",
223
+ NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
224
+ NOLO_LOCAL_SHELL_MODE: "worktree",
225
+ },
226
+ db: {
227
+ get: async () => ({
228
+ dbKey: "agent-local-shell",
229
+ prompt: "Use shell.",
230
+ model: "gpt-4.1-mini",
231
+ }),
232
+ put: async () => {},
233
+ batch: async () => {},
234
+ iterator: () => (async function* () {})(),
235
+ },
236
+ fetchImpl: async (_url, init) => {
237
+ requests.push({ body: JSON.parse(String(init?.body)) });
238
+ return Response.json({
239
+ choices: [{ message: { content: "done" } }],
240
+ });
241
+ },
242
+ });
243
+
244
+ await runLocalAgentTurn({
245
+ adapter,
246
+ agentRef: "shell",
247
+ input: "pwd",
248
+ });
249
+
250
+ expect(requests[0]?.body.tools).toEqual([{
251
+ type: "function",
252
+ function: expect.objectContaining({
253
+ name: "execShell",
254
+ }),
255
+ }]);
256
+ });
257
+
258
+ test("runs execShell locally in explicit worktree shell mode", async () => {
259
+ const adapter = createCliLocalRuntimeAdapter({
260
+ env: { NOLO_LOCAL_SHELL_MODE: "worktree" },
261
+ db: {
262
+ get: async () => ({
263
+ dbKey: "agent-local-shell",
264
+ toolNames: ["execShell"],
265
+ }),
266
+ put: async () => {},
267
+ batch: async () => {},
268
+ iterator: () => (async function* () {})(),
269
+ },
270
+ cwd: import.meta.dir,
271
+ fetchImpl: async () => Response.json({}),
272
+ });
273
+
274
+ await adapter.loadAgentConfig("shell");
275
+ const result = await adapter.executeTool({
276
+ id: "call-1",
277
+ name: "execShell",
278
+ arguments: "{\"cmd\":\"pwd\"}",
279
+ });
280
+
281
+ expect(result.content).toContain(import.meta.dir);
282
+ expect(result.metadata).toMatchObject({ exitCode: 0 });
283
+ });
217
284
  });
@@ -22,6 +22,7 @@ export type CliLocalRuntimeAdapterDeps = {
22
22
  now?: () => number;
23
23
  createId?: () => string;
24
24
  fetchImpl?: typeof fetch;
25
+ cwd?: string;
25
26
  localToolExecutors?: Record<string, (call: any) => Promise<{ content: string; metadata?: Record<string, unknown> }>>;
26
27
  };
27
28
 
@@ -46,6 +47,83 @@ function resolveLocalUserId(env: EnvLike) {
46
47
  return env.NOLO_LOCAL_USER_ID || env.NOLO_USER_ID || "local";
47
48
  }
48
49
 
50
+ function buildExecShellTool() {
51
+ return {
52
+ type: "function",
53
+ function: {
54
+ name: "execShell",
55
+ description: "Run a shell command in the current isolated local workspace.",
56
+ parameters: {
57
+ type: "object",
58
+ properties: {
59
+ cmd: {
60
+ type: "string",
61
+ description: "Shell command to run.",
62
+ },
63
+ },
64
+ required: ["cmd"],
65
+ },
66
+ },
67
+ };
68
+ }
69
+
70
+ function shellModeEnabled(env: EnvLike) {
71
+ return ["worktree", "dangerous", "1", "true"].includes(env.NOLO_LOCAL_SHELL_MODE || "");
72
+ }
73
+
74
+ function buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
75
+ return args.toolNames?.includes("execShell") || shellModeEnabled(args.env)
76
+ ? [buildExecShellTool()]
77
+ : [];
78
+ }
79
+
80
+ function parseToolArguments(raw: string) {
81
+ try {
82
+ const parsed = JSON.parse(raw || "{}");
83
+ return parsed && typeof parsed === "object" ? parsed : {};
84
+ } catch {
85
+ return {};
86
+ }
87
+ }
88
+
89
+ async function executeShellCommand(args: {
90
+ call: any;
91
+ cwd: string;
92
+ }) {
93
+ const parsed = parseToolArguments(String(args.call.arguments ?? ""));
94
+ const cmd = String(parsed.cmd ?? parsed.command ?? "").trim();
95
+ if (!cmd) throw new Error("execShell requires a non-empty cmd argument.");
96
+ const proc = Bun.spawn(["/bin/sh", "-lc", cmd], {
97
+ cwd: args.cwd,
98
+ stdout: "pipe",
99
+ stderr: "pipe",
100
+ stdin: "ignore",
101
+ });
102
+ const [stdout, stderr, exitCode] = await Promise.all([
103
+ new Response(proc.stdout).text(),
104
+ new Response(proc.stderr).text(),
105
+ proc.exited,
106
+ ]);
107
+ return {
108
+ content: [
109
+ stdout.trim() ? `stdout:\n${stdout.trim()}` : "",
110
+ stderr.trim() ? `stderr:\n${stderr.trim()}` : "",
111
+ `exitCode: ${exitCode}`,
112
+ ].filter(Boolean).join("\n\n"),
113
+ metadata: { exitCode },
114
+ };
115
+ }
116
+
117
+ function buildLocalToolExecutors(deps: CliLocalRuntimeAdapterDeps) {
118
+ return {
119
+ execShell: (call: any) => executeShellCommand({
120
+ call,
121
+ cwd: deps.cwd ?? process.cwd(),
122
+ }),
123
+ ...(deps.localToolExecutors ?? {}),
124
+ };
125
+ }
126
+
49
127
  async function resolveDb(deps: CliLocalRuntimeAdapterDeps) {
50
128
  return deps.db ?? defaultLocalRuntimeDb();
51
129
  }
@@ -191,6 +269,7 @@ export function createCliLocalRuntimeAdapter(
191
269
  const fetchImpl = deps.fetchImpl ?? fetch;
192
270
  const userId = resolveLocalUserId(deps.env);
193
271
  let activeAgentToolNames: string[] = [];
272
+ const localToolExecutors = buildLocalToolExecutors(deps);
194
273
 
195
274
  return {
196
275
  host: "cli",
@@ -218,6 +297,10 @@ export function createCliLocalRuntimeAdapter(
218
297
  resolveProvider: async (agentConfig) => ({
219
298
  model: agentConfig.model || "gpt-4.1-mini",
220
299
  complete: async (messages) => {
300
+ const tools = buildOpenAiTools({
301
+ toolNames: agentConfig.toolNames,
302
+ env: deps.env,
303
+ });
221
304
  const res = await fetchImpl(`${resolveOpenAiCompatibleBaseUrl(deps.env)}/chat/completions`, {
222
305
  method: "POST",
223
306
  headers: {
@@ -230,17 +313,20 @@ export function createCliLocalRuntimeAdapter(
230
313
  model: agentConfig.model || "gpt-4.1-mini",
231
314
  messages: toOpenAiMessages(messages),
232
315
  stream: false,
316
+ ...(tools.length > 0 ? { tools } : {}),
233
317
  }),
234
318
  });
235
319
  const data = await res.json().catch(() => ({}));
236
320
  if (!res.ok) {
237
321
  throw new Error(`local provider failed: HTTP ${res.status} ${JSON.stringify(data)}`);
238
322
  }
239
- const content = String(data?.choices?.[0]?.message?.content ?? "");
323
+ const choiceMessage = data?.choices?.[0]?.message ?? {};
324
+ const content = String(choiceMessage?.content ?? "");
240
325
  return {
241
326
  content,
242
327
  model: agentConfig.model || "gpt-4.1-mini",
243
328
  provider: agentConfig.provider || "openai-compatible",
329
+ ...(Array.isArray(choiceMessage?.tool_calls) ? { tool_calls: choiceMessage.tool_calls } : {}),
244
330
  usage: data?.usage,
245
331
  trace: messages,
246
332
  };
@@ -250,7 +336,7 @@ export function createCliLocalRuntimeAdapter(
250
336
  env: deps.env,
251
337
  agentToolNames: activeAgentToolNames,
252
338
  call,
253
- executors: deps.localToolExecutors,
339
+ executors: localToolExecutors,
254
340
  }),
255
341
  };
256
342
  }
@@ -3,15 +3,20 @@ import { describe, expect, test } from "bun:test";
3
3
  import { executeLocalToolWithPolicy, resolveLocalToolPolicy } from "./localToolPolicy";
4
4
 
5
5
  describe("CLI local tool policy", () => {
6
- test("blocks dangerous tools even when env tries to allow them", () => {
6
+ test("allows execShell in explicit worktree shell mode", () => {
7
+ expect(resolveLocalToolPolicy({
8
+ env: { NOLO_LOCAL_SHELL_MODE: "worktree" },
9
+ agentToolNames: [],
10
+ toolName: "execShell",
11
+ })).toEqual({ allowed: true, toolName: "execShell" });
12
+ });
13
+
14
+ test("blocks execShell unless explicit shell mode is enabled", () => {
7
15
  expect(resolveLocalToolPolicy({
8
16
  env: { NOLO_LOCAL_ALLOWED_TOOLS: "execShell" },
9
17
  agentToolNames: ["execShell"],
10
18
  toolName: "execShell",
11
- })).toMatchObject({
12
- allowed: false,
13
- reason: expect.stringContaining("blocked"),
14
- });
19
+ })).toMatchObject({ allowed: false });
15
20
  });
16
21
 
17
22
  test("requires both env allowlist and agent declaration", () => {
@@ -7,7 +7,6 @@ export type LocalToolPolicyDecision =
7
7
  | { allowed: false; toolName: string; reason: string };
8
8
 
9
9
  const NEVER_LOCAL_TOOLS = new Set([
10
- "execShell",
11
10
  "deleteSpaces",
12
11
  "updateAgent",
13
12
  "updateSelf",
@@ -26,6 +25,18 @@ export function resolveLocalToolPolicy(args: {
26
25
  agentToolNames?: string[];
27
26
  toolName: string;
28
27
  }): LocalToolPolicyDecision {
28
+ if (args.toolName === "execShell") {
29
+ const shellMode = args.env.NOLO_LOCAL_SHELL_MODE || "";
30
+ if (["worktree", "dangerous", "1", "true"].includes(shellMode)) {
31
+ return { allowed: true, toolName: args.toolName };
32
+ }
33
+ return {
34
+ allowed: false,
35
+ toolName: args.toolName,
36
+ reason: "execShell requires NOLO_LOCAL_SHELL_MODE=worktree.",
37
+ };
38
+ }
39
+
29
40
  if (NEVER_LOCAL_TOOLS.has(args.toolName)) {
30
41
  return {
31
42
  allowed: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nolo-cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "description": "Agent-first terminal workspace for Nolo",
6
6
  "bin": {
@@ -10,6 +10,7 @@
10
10
  "files": [
11
11
  "index.ts",
12
12
  "agentRuntimeLocal.ts",
13
+ "agentAliases.ts",
13
14
  "localRuntimeDb.ts",
14
15
  "agentRuntimeCommands.ts",
15
16
  "agentPullCommand.ts",