nolo-cli 0.1.33 → 0.1.34

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.
Files changed (56) hide show
  1. package/README.md +4 -4
  2. package/agent-runtime/agentRecordConfig.ts +8 -0
  3. package/agent-runtime/hostAdapter.ts +9 -1
  4. package/agent-runtime/index.ts +10 -0
  5. package/agent-runtime/localLoop.ts +49 -2
  6. package/agent-runtime/localToolPolicy.ts +70 -21
  7. package/agent-runtime/openAiCompatibleProvider.ts +7 -3
  8. package/agent-runtime/openAiCompatibleProviderConfig.ts +15 -26
  9. package/agent-runtime/platformChatProvider.ts +48 -71
  10. package/agent-runtime/providerResolution.ts +244 -0
  11. package/agent-runtime/runtimeToolPolicy.ts +33 -0
  12. package/agent-runtime/shellCommandPolicy.ts +120 -0
  13. package/agentInternalCommandEntries.ts +2 -0
  14. package/agentListCommands.ts +2 -2
  15. package/agentPullCommand.ts +6 -11
  16. package/agentRecordCommands.ts +87 -2
  17. package/agentRecordHelpers.ts +87 -4
  18. package/agentRuntimeLocal.ts +3 -0
  19. package/ai/tools/toolNameAliases.ts +70 -0
  20. package/cli/agentInternalCommandEntries.ts +2 -0
  21. package/cli/agentListCommands.ts +2 -2
  22. package/cli/agentPullCommand.ts +6 -11
  23. package/cli/agentRecordCommands.ts +87 -2
  24. package/cli/agentRecordHelpers.ts +87 -4
  25. package/cli/agentRuntimeLocal.ts +3 -0
  26. package/cli/cliCommandDispatch.ts +2 -2
  27. package/cli/cliEnvHelpers.ts +80 -12
  28. package/cli/client/agentRun.ts +1 -0
  29. package/cli/client/localRuntimeAdapter.ts +44 -7
  30. package/cli/commandRegistry.ts +2 -2
  31. package/cli/machineCommands.ts +1 -1
  32. package/cli/machineWsRunDispatch.ts +29 -9
  33. package/cli/offlineMarxistsAgentCommand.ts +1 -1
  34. package/cli/runtimeDoctorCommands.ts +10 -4
  35. package/cli/tableCommands.ts +1 -1
  36. package/cli/taskRunCommand.ts +1 -1
  37. package/cliCommandDispatch.ts +2 -2
  38. package/cliEnvHelpers.ts +80 -12
  39. package/client/agentRun.ts +1 -0
  40. package/client/localProviderResolver.test.ts +26 -2
  41. package/client/localRuntimeAdapter.test.ts +287 -3
  42. package/client/localRuntimeAdapter.ts +44 -7
  43. package/commandRegistry.ts +2 -2
  44. package/database/server/authorityStoreTypes.ts +31 -0
  45. package/database/server/db.ts +15 -34
  46. package/database/server/legacyServerDb.ts +58 -0
  47. package/database/server/levelAuthorityStore.ts +75 -0
  48. package/database/server/memoryAuthorityStore.ts +133 -0
  49. package/database/server/serverStoreFactory.ts +118 -0
  50. package/machineCommands.ts +1 -1
  51. package/machineWsRunDispatch.ts +29 -9
  52. package/offlineMarxistsAgentCommand.ts +1 -1
  53. package/package.json +10 -2
  54. package/runtimeDoctorCommands.ts +10 -4
  55. package/tableCommands.ts +1 -1
  56. package/taskRunCommand.ts +1 -1
@@ -205,14 +205,19 @@ export async function writeAgentRecord(args: {
205
205
 
206
206
  export async function resolveAgentRecordFromHybridStore(args: {
207
207
  agentInput: string;
208
+ cliArgs?: string[];
208
209
  env: EnvLike;
209
210
  db: CliKvDb;
210
211
  fetchImpl: typeof fetch;
211
212
  fallbackFetchImpl?: typeof fetch;
212
213
  }) {
213
214
  const agentKey = resolveCliAgentKeyInput(args.agentInput);
214
- const authToken = resolveAuthToken(args.env);
215
- const defaultServerUrl = resolveServerUrl(args.env);
215
+ const authToken = args.cliArgs
216
+ ? resolveAuthToken(args.cliArgs, args.env)
217
+ : resolveAuthToken(args.env);
218
+ const defaultServerUrl = args.cliArgs
219
+ ? resolveServerUrl(args.cliArgs, args.env)
220
+ : resolveServerUrl(args.env);
216
221
 
217
222
  for (const key of buildLocalAgentLookupKeys({
218
223
  agentRef: agentKey,
@@ -232,7 +237,11 @@ export async function resolveAgentRecordFromHybridStore(args: {
232
237
  }
233
238
 
234
239
  if (!shouldReadAgentKeyRemotely(key)) continue;
235
- for (const serverUrl of resolveServerCandidates(args.env, defaultServerUrl)) {
240
+ for (const serverUrl of (
241
+ args.cliArgs
242
+ ? resolveServerCandidates(args.cliArgs, args.env, defaultServerUrl)
243
+ : resolveServerCandidates(args.env, defaultServerUrl)
244
+ )) {
236
245
  try {
237
246
  const remoteRecord = await readAgentRecord({
238
247
  agentKey: key,
@@ -290,6 +299,8 @@ export function parseAgentUpdateArgs(args: string[]) {
290
299
  const tools = readOption(args, "--tools");
291
300
  const copyProviderFrom = readOption(args, "--copy-provider-from");
292
301
  const name = readOption(args, "--name");
302
+ const customProviderUrl = readOption(args, "--custom-provider-url");
303
+ const apiKey = readOption(args, "--api-key");
293
304
 
294
305
  const promptSources = [prompt, promptFile, promptDoc].filter(Boolean);
295
306
  if (promptSources.length > 1) {
@@ -304,6 +315,8 @@ export function parseAgentUpdateArgs(args: string[]) {
304
315
  if (promptFile) updates.prompt = readFileSync(promptFile, "utf8");
305
316
  if (tools) updates.tools = parseJsonishValue(tools);
306
317
  if (name) updates.name = name;
318
+ if (customProviderUrl) updates.customProviderUrl = customProviderUrl;
319
+ if (apiKey) updates.apiKey = apiKey;
307
320
 
308
321
  for (const entry of readRepeatedOption(args, "--field")) {
309
322
  const index = entry.indexOf("=");
@@ -322,7 +335,73 @@ export function parseAgentUpdateArgs(args: string[]) {
322
335
  return { agentInput, updates, promptDoc, copyProviderFrom };
323
336
  }
324
337
 
338
+ export async function buildCreatedAgentRecord(args: {
339
+ cliArgs?: string[];
340
+ parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
341
+ env: EnvLike;
342
+ db: CliKvDb;
343
+ fetchImpl: typeof fetch;
344
+ fallbackFetchImpl?: typeof fetch;
345
+ authToken: string;
346
+ }) {
347
+ const agentKey = resolveCliAgentKeyInput(args.parsed.agentInput);
348
+ const serverUrl = args.cliArgs ? resolveServerUrl(args.cliArgs, args.env) : resolveServerUrl(args.env);
349
+
350
+ if (args.parsed.copyProviderFrom) {
351
+ const providerSource = await resolveAgentRecordFromHybridStore({
352
+ agentInput: args.parsed.copyProviderFrom,
353
+ cliArgs: args.cliArgs,
354
+ env: args.env,
355
+ db: args.db,
356
+ fetchImpl: args.fetchImpl,
357
+ fallbackFetchImpl: args.fallbackFetchImpl,
358
+ });
359
+ if (!providerSource) {
360
+ throw new Error(`provider source agent not found: ${args.parsed.copyProviderFrom}`);
361
+ }
362
+ for (const field of PROVIDER_COPY_FIELDS) {
363
+ if (field in providerSource.record) {
364
+ args.parsed.updates[field] = providerSource.record[field];
365
+ }
366
+ }
367
+ }
368
+
369
+ if (args.parsed.promptDoc) {
370
+ const promptDoc = await readDbRecord({
371
+ dbKey: args.parsed.promptDoc,
372
+ authToken: args.authToken,
373
+ fallbackFetchImpl: args.fallbackFetchImpl,
374
+ fetchImpl: args.fetchImpl,
375
+ serverUrl,
376
+ });
377
+ const promptBody = typeof promptDoc?.content === "string" ? promptDoc.content : "";
378
+ if (!promptBody.trim()) {
379
+ throw new Error(`prompt doc is empty or unreadable: ${args.parsed.promptDoc}`);
380
+ }
381
+ args.parsed.updates.prompt = promptBody;
382
+ }
383
+
384
+ const userId = parseUserIdFromAuthToken(args.authToken);
385
+
386
+ const nextRecord = {
387
+ ...args.parsed.updates,
388
+ dbKey: agentKey,
389
+ key: agentKey,
390
+ createdAt: Date.now(),
391
+ updatedAt: Date.now(),
392
+ userId,
393
+ };
394
+
395
+ return {
396
+ agentKey,
397
+ serverUrl,
398
+ nextRecord,
399
+ updates: args.parsed.updates,
400
+ };
401
+ }
402
+
325
403
  export async function buildUpdatedAgentRecord(args: {
404
+ cliArgs?: string[];
326
405
  parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
327
406
  env: EnvLike;
328
407
  db: CliKvDb;
@@ -332,13 +411,16 @@ export async function buildUpdatedAgentRecord(args: {
332
411
  }) {
333
412
  const cached = await resolveAgentRecordFromHybridStore({
334
413
  agentInput: args.parsed.agentInput,
414
+ cliArgs: args.cliArgs,
335
415
  env: args.env,
336
416
  db: args.db,
337
417
  fetchImpl: args.fetchImpl,
338
418
  fallbackFetchImpl: args.fallbackFetchImpl,
339
419
  });
340
420
  const agentKey = cached?.agentKey ?? resolveCliAgentKeyInput(args.parsed.agentInput);
341
- const serverUrl = cached?.record?.serverOrigin ?? resolveServerUrl(args.env);
421
+ const serverUrl = cached?.record?.serverOrigin ?? (
422
+ args.cliArgs ? resolveServerUrl(args.cliArgs, args.env) : resolveServerUrl(args.env)
423
+ );
342
424
  const currentRecord = await readAgentRecord({
343
425
  agentKey,
344
426
  authToken: args.authToken,
@@ -350,6 +432,7 @@ export async function buildUpdatedAgentRecord(args: {
350
432
  if (args.parsed.copyProviderFrom) {
351
433
  const providerSource = await resolveAgentRecordFromHybridStore({
352
434
  agentInput: args.parsed.copyProviderFrom,
435
+ cliArgs: args.cliArgs,
353
436
  env: args.env,
354
437
  db: args.db,
355
438
  fetchImpl: args.fetchImpl,
@@ -19,6 +19,9 @@ export {
19
19
  resolvePlatformChatProviderConfig,
20
20
  resolveLocalToolPolicy,
21
21
  resolveOpenAiCompatibleProviderConfig,
22
+ resolveRequestedRuntimeToolNames,
23
+ evaluateShellCommandPolicy,
24
+ isDestructiveShellCommand,
22
25
  shouldUsePlatformChatProvider,
23
26
  shouldCacheHybridRemoteRecord,
24
27
  shouldFetchAgentRuntimeRecordRemotely,
@@ -0,0 +1,70 @@
1
+ const LEGACY_TOOL_NAME_ALIASES: Record<string, string> = {
2
+ createPage: "createDoc",
3
+ updatePage: "updateDoc",
4
+ update_page: "updateDoc",
5
+ create_page: "createDoc",
6
+ fetchwebpage: "fetchWebpage",
7
+ fetch_webpage: "fetchWebpage",
8
+ "fetch-page": "fetchWebpage",
9
+ exasearch: "exa_search",
10
+ "exa-search": "exa_search",
11
+ exa_search: "exa_search",
12
+ readDoc: "readDoc",
13
+ readpage: "readDoc",
14
+ read_page: "readDoc",
15
+ read_doc: "readDoc",
16
+ createdoc: "createDoc",
17
+ updateDocTool: "updateDoc",
18
+ readLocal: "readLocalFile",
19
+ read_local: "readLocalFile",
20
+ readLocalTool: "readLocalFile",
21
+ listLocal: "listLocalFiles",
22
+ list_local: "listLocalFiles",
23
+ terminalCommand: "execShell",
24
+ terminal_command: "execShell",
25
+ runCommand: "execShell",
26
+ run_command: "execShell",
27
+ runInBash: "execShell",
28
+ run_in_bash: "execShell",
29
+ executeCommand: "execShell",
30
+ execute_command: "execShell",
31
+ };
32
+
33
+ const normalizeToolName = (name: string): string =>
34
+ name.replace(/[-_]/g, "").toLowerCase();
35
+
36
+ export const canonicalizeToolName = (rawName: string): string => {
37
+ const trimmedName = rawName.trim();
38
+ if (!trimmedName) return rawName;
39
+
40
+ if (LEGACY_TOOL_NAME_ALIASES[trimmedName]) {
41
+ return LEGACY_TOOL_NAME_ALIASES[trimmedName];
42
+ }
43
+
44
+ const normalizedRawName = normalizeToolName(trimmedName);
45
+ const matchedAlias = Object.entries(LEGACY_TOOL_NAME_ALIASES).find(
46
+ ([alias]) => normalizeToolName(alias) === normalizedRawName,
47
+ );
48
+
49
+ return matchedAlias?.[1] ?? trimmedName;
50
+ };
51
+
52
+ export const canonicalizeToolNames = (toolNames: string[]): string[] =>
53
+ Array.from(
54
+ new Set(
55
+ toolNames
56
+ .filter((toolName): toolName is string => typeof toolName === "string")
57
+ .map(canonicalizeToolName),
58
+ ),
59
+ );
60
+
61
+ export const prioritizeToolNames = (
62
+ toolNames: string[],
63
+ preferredToolNames: string[],
64
+ ): string[] => {
65
+ const canonicalTools = canonicalizeToolNames(toolNames);
66
+ const preferred = new Set(canonicalizeToolNames(preferredToolNames));
67
+ const prioritized = canonicalTools.filter((toolName) => preferred.has(toolName));
68
+ const remaining = canonicalTools.filter((toolName) => !preferred.has(toolName));
69
+ return [...prioritized, ...remaining];
70
+ };
@@ -8,6 +8,7 @@ import { runAgentPullCommand } from "./agentPullCommand";
8
8
  import {
9
9
  runAgentReadCommand,
10
10
  runAgentUpdateCommand,
11
+ runAgentCreateCommand,
11
12
  } from "./agentRecordCommands";
12
13
  import { runAgentRunCommand } from "./agentRunCommand";
13
14
  import {
@@ -21,6 +22,7 @@ export function getAgentInternalCommandEntries(): CommandEntry[] {
21
22
  return [
22
23
  createEnvScriptDirCommand(["chat"], "Chat with an agent", runAgentRunCommand),
23
24
  createEnvCommand(["agent", "list"], "List owned agents", runAgentListCommand),
25
+ createEnvCommand(["agent", "create"], "Create a new agent", runAgentCreateCommand),
24
26
  createEnvCommand(["agent", "pull"], "Cache an agent for local runs", runAgentPullCommand),
25
27
  createEnvCommand(["agent", "read"], "Read a single agent", runAgentReadCommand),
26
28
  createEnvScriptDirCommand(["agent", "run"], "Run an agent", runAgentRunCommand),
@@ -23,7 +23,7 @@ export async function runAgentListCommand(
23
23
  const output = deps.output ?? process.stdout;
24
24
  const { wantJson, publicOnly, idsOnly, includeLegacy } = parseAgentListArgs(args);
25
25
 
26
- const authToken = resolveAuthToken(env);
26
+ const authToken = resolveAuthToken(args, env);
27
27
  if (!authToken) {
28
28
  output.write("[nolo] agent list requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
29
29
  return 1;
@@ -46,7 +46,7 @@ export async function runAgentListCommand(
46
46
  agents = await listLocalCachedAgents({ db, userId });
47
47
  source = "local-cache";
48
48
  } catch {
49
- const serverUrl = resolveServerUrl(env);
49
+ const serverUrl = resolveServerUrl(args, env);
50
50
  agents = await listRemoteAgents({
51
51
  authToken,
52
52
  fallbackFetchImpl,
@@ -1,7 +1,7 @@
1
- import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
2
1
  import { getDefaultCliLocalRuntimeDb } from "./localRuntimeDb";
3
2
  import type { CliLocalRuntimeDb } from "./client/localRuntimeAdapter";
4
3
  import { resolveCliAgentKeyInput } from "./agentAliases";
4
+ import { resolveAuthToken, resolveServerUrl } from "./cliEnvHelpers";
5
5
 
6
6
  type EnvLike = Record<string, string | undefined>;
7
7
 
@@ -44,14 +44,6 @@ export function parseAgentPullArgs(args: string[]): ParsedAgentPullArgs | null {
44
44
  return { agentKey: resolveCliAgentKeyInput(agentKey) };
45
45
  }
46
46
 
47
- function resolveServerUrl(env: EnvLike) {
48
- return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
49
- }
50
-
51
- function resolveAuthToken(env: EnvLike) {
52
- return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
53
- }
54
-
55
47
  function extractToolNames(record: any) {
56
48
  if (Array.isArray(record?.toolNames)) return record.toolNames.filter((tool: unknown) => typeof tool === "string");
57
49
  if (!Array.isArray(record?.tools)) return [];
@@ -113,8 +105,11 @@ export async function runAgentPullCommand(args: string[], deps: AgentPullCommand
113
105
  return 1;
114
106
  }
115
107
 
116
- const serverUrl = resolveServerUrl(env);
117
- const authToken = resolveAuthToken(env);
108
+ const serverUrl = resolveServerUrl(args, env);
109
+ const authToken =
110
+ readFlagValue(args, "--token") ||
111
+ readFlagValue(args, "--machine-key") ||
112
+ resolveAuthToken(env, ["BENCHMARK_AUTH_TOKEN"]);
118
113
  const fetchImpl = deps.fetchImpl ?? fetch;
119
114
  const db = deps.db ?? await getDefaultCliLocalRuntimeDb();
120
115
 
@@ -2,6 +2,7 @@ import { resolveCliAgentKeyInput } from "./agentAliases";
2
2
  import { getReadableCliDb, type AgentCommandDeps } from "./agentCommandSupport";
3
3
  import {
4
4
  buildUpdatedAgentRecord,
5
+ buildCreatedAgentRecord,
5
6
  normalizeAgentRecordForOutput,
6
7
  parseAgentUpdateArgs,
7
8
  resolveAgentRecordFromHybridStore,
@@ -21,7 +22,7 @@ export async function runAgentReadCommand(
21
22
  return agentInput ? 0 : 1;
22
23
  }
23
24
 
24
- const authToken = resolveAuthToken(env);
25
+ const authToken = resolveAuthToken(args, env);
25
26
  if (!authToken) {
26
27
  output.write("[nolo] agent read requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
27
28
  return 1;
@@ -35,6 +36,7 @@ export async function runAgentReadCommand(
35
36
  try {
36
37
  const result = await resolveAgentRecordFromHybridStore({
37
38
  agentInput,
39
+ cliArgs: args,
38
40
  env,
39
41
  db,
40
42
  fetchImpl,
@@ -79,7 +81,7 @@ export async function runAgentUpdateCommand(
79
81
  return args[0] ? 0 : 1;
80
82
  }
81
83
 
82
- const authToken = resolveAuthToken(env);
84
+ const authToken = resolveAuthToken(args, env);
83
85
  if (!authToken) {
84
86
  output.write("[nolo] agent update requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
85
87
  return 1;
@@ -97,6 +99,7 @@ export async function runAgentUpdateCommand(
97
99
 
98
100
  try {
99
101
  const built = await buildUpdatedAgentRecord({
102
+ cliArgs: args,
100
103
  parsed,
101
104
  env,
102
105
  db,
@@ -139,3 +142,85 @@ export async function runAgentUpdateCommand(
139
142
  return 1;
140
143
  }
141
144
  }
145
+
146
+ export async function runAgentCreateCommand(
147
+ args: string[],
148
+ deps: AgentCommandDeps = {}
149
+ ) {
150
+ const env = deps.env ?? process.env;
151
+ const output = deps.output ?? process.stdout;
152
+ let parsed;
153
+ try {
154
+ parsed = parseAgentUpdateArgs(args);
155
+ } catch (error) {
156
+ output.write(`[nolo] agent create failed: ${error instanceof Error ? error.message : String(error)}\n`);
157
+ return 1;
158
+ }
159
+ if (!parsed) {
160
+ output.write(
161
+ "Usage: nolo agent create <agent> [--model <id>] [--cli-provider <provider>] [--api-source <source>] [--prompt <text> | --prompt-file <path> | --prompt-doc <pageKey>] [--tools <json>] [--copy-provider-from <agent>] [--name <name>] [--custom-provider-url <url>] [--provider-api-key <key>] [--field key=value]\n"
162
+ );
163
+ return args[0] ? 0 : 1;
164
+ }
165
+
166
+ const authToken = resolveAuthToken(args, env);
167
+ if (!authToken) {
168
+ output.write("[nolo] agent create requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
169
+ return 1;
170
+ }
171
+
172
+ const userId = parseUserIdFromAuthToken(authToken);
173
+ if (!userId) {
174
+ output.write("[nolo] agent create could not read userId from AUTH_TOKEN.\n");
175
+ return 1;
176
+ }
177
+
178
+ const db = deps.db ?? await getReadableCliDb(output);
179
+ const fetchImpl = deps.fetchImpl ?? fetch;
180
+ const fallbackFetchImpl = deps.fallbackFetchImpl;
181
+
182
+ try {
183
+ const built = await buildCreatedAgentRecord({
184
+ cliArgs: args,
185
+ parsed,
186
+ env,
187
+ db,
188
+ fetchImpl,
189
+ fallbackFetchImpl,
190
+ authToken,
191
+ });
192
+
193
+ await writeAgentRecord({
194
+ agentKey: built.agentKey,
195
+ authToken,
196
+ fallbackFetchImpl,
197
+ fetchImpl,
198
+ serverUrl: built.serverUrl,
199
+ userId,
200
+ record: built.nextRecord,
201
+ });
202
+ await db.put(built.agentKey, {
203
+ ...built.nextRecord,
204
+ dbKey: built.agentKey,
205
+ key: built.nextRecord?.key || built.agentKey,
206
+ serverOrigin: built.serverUrl,
207
+ });
208
+
209
+ output.write(JSON.stringify({
210
+ ok: true,
211
+ agentKey: built.agentKey,
212
+ baseUrl: built.serverUrl,
213
+ updates: built.updates,
214
+ record: built.nextRecord,
215
+ }, null, 2));
216
+ output.write("\n");
217
+ return 0;
218
+ } catch (error) {
219
+ output.write(
220
+ `[nolo] agent create failed: ${
221
+ error instanceof Error ? error.message : String(error)
222
+ }\n`
223
+ );
224
+ return 1;
225
+ }
226
+ }
@@ -205,14 +205,19 @@ export async function writeAgentRecord(args: {
205
205
 
206
206
  export async function resolveAgentRecordFromHybridStore(args: {
207
207
  agentInput: string;
208
+ cliArgs?: string[];
208
209
  env: EnvLike;
209
210
  db: CliKvDb;
210
211
  fetchImpl: typeof fetch;
211
212
  fallbackFetchImpl?: typeof fetch;
212
213
  }) {
213
214
  const agentKey = resolveCliAgentKeyInput(args.agentInput);
214
- const authToken = resolveAuthToken(args.env);
215
- const defaultServerUrl = resolveServerUrl(args.env);
215
+ const authToken = args.cliArgs
216
+ ? resolveAuthToken(args.cliArgs, args.env)
217
+ : resolveAuthToken(args.env);
218
+ const defaultServerUrl = args.cliArgs
219
+ ? resolveServerUrl(args.cliArgs, args.env)
220
+ : resolveServerUrl(args.env);
216
221
 
217
222
  for (const key of buildLocalAgentLookupKeys({
218
223
  agentRef: agentKey,
@@ -232,7 +237,11 @@ export async function resolveAgentRecordFromHybridStore(args: {
232
237
  }
233
238
 
234
239
  if (!shouldReadAgentKeyRemotely(key)) continue;
235
- for (const serverUrl of resolveServerCandidates(args.env, defaultServerUrl)) {
240
+ for (const serverUrl of (
241
+ args.cliArgs
242
+ ? resolveServerCandidates(args.cliArgs, args.env, defaultServerUrl)
243
+ : resolveServerCandidates(args.env, defaultServerUrl)
244
+ )) {
236
245
  try {
237
246
  const remoteRecord = await readAgentRecord({
238
247
  agentKey: key,
@@ -290,6 +299,8 @@ export function parseAgentUpdateArgs(args: string[]) {
290
299
  const tools = readOption(args, "--tools");
291
300
  const copyProviderFrom = readOption(args, "--copy-provider-from");
292
301
  const name = readOption(args, "--name");
302
+ const customProviderUrl = readOption(args, "--custom-provider-url");
303
+ const apiKey = readOption(args, "--api-key");
293
304
 
294
305
  const promptSources = [prompt, promptFile, promptDoc].filter(Boolean);
295
306
  if (promptSources.length > 1) {
@@ -304,6 +315,8 @@ export function parseAgentUpdateArgs(args: string[]) {
304
315
  if (promptFile) updates.prompt = readFileSync(promptFile, "utf8");
305
316
  if (tools) updates.tools = parseJsonishValue(tools);
306
317
  if (name) updates.name = name;
318
+ if (customProviderUrl) updates.customProviderUrl = customProviderUrl;
319
+ if (apiKey) updates.apiKey = apiKey;
307
320
 
308
321
  for (const entry of readRepeatedOption(args, "--field")) {
309
322
  const index = entry.indexOf("=");
@@ -322,7 +335,73 @@ export function parseAgentUpdateArgs(args: string[]) {
322
335
  return { agentInput, updates, promptDoc, copyProviderFrom };
323
336
  }
324
337
 
338
+ export async function buildCreatedAgentRecord(args: {
339
+ cliArgs?: string[];
340
+ parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
341
+ env: EnvLike;
342
+ db: CliKvDb;
343
+ fetchImpl: typeof fetch;
344
+ fallbackFetchImpl?: typeof fetch;
345
+ authToken: string;
346
+ }) {
347
+ const agentKey = resolveCliAgentKeyInput(args.parsed.agentInput);
348
+ const serverUrl = args.cliArgs ? resolveServerUrl(args.cliArgs, args.env) : resolveServerUrl(args.env);
349
+
350
+ if (args.parsed.copyProviderFrom) {
351
+ const providerSource = await resolveAgentRecordFromHybridStore({
352
+ agentInput: args.parsed.copyProviderFrom,
353
+ cliArgs: args.cliArgs,
354
+ env: args.env,
355
+ db: args.db,
356
+ fetchImpl: args.fetchImpl,
357
+ fallbackFetchImpl: args.fallbackFetchImpl,
358
+ });
359
+ if (!providerSource) {
360
+ throw new Error(`provider source agent not found: ${args.parsed.copyProviderFrom}`);
361
+ }
362
+ for (const field of PROVIDER_COPY_FIELDS) {
363
+ if (field in providerSource.record) {
364
+ args.parsed.updates[field] = providerSource.record[field];
365
+ }
366
+ }
367
+ }
368
+
369
+ if (args.parsed.promptDoc) {
370
+ const promptDoc = await readDbRecord({
371
+ dbKey: args.parsed.promptDoc,
372
+ authToken: args.authToken,
373
+ fallbackFetchImpl: args.fallbackFetchImpl,
374
+ fetchImpl: args.fetchImpl,
375
+ serverUrl,
376
+ });
377
+ const promptBody = typeof promptDoc?.content === "string" ? promptDoc.content : "";
378
+ if (!promptBody.trim()) {
379
+ throw new Error(`prompt doc is empty or unreadable: ${args.parsed.promptDoc}`);
380
+ }
381
+ args.parsed.updates.prompt = promptBody;
382
+ }
383
+
384
+ const userId = parseUserIdFromAuthToken(args.authToken);
385
+
386
+ const nextRecord = {
387
+ ...args.parsed.updates,
388
+ dbKey: agentKey,
389
+ key: agentKey,
390
+ createdAt: Date.now(),
391
+ updatedAt: Date.now(),
392
+ userId,
393
+ };
394
+
395
+ return {
396
+ agentKey,
397
+ serverUrl,
398
+ nextRecord,
399
+ updates: args.parsed.updates,
400
+ };
401
+ }
402
+
325
403
  export async function buildUpdatedAgentRecord(args: {
404
+ cliArgs?: string[];
326
405
  parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
327
406
  env: EnvLike;
328
407
  db: CliKvDb;
@@ -332,13 +411,16 @@ export async function buildUpdatedAgentRecord(args: {
332
411
  }) {
333
412
  const cached = await resolveAgentRecordFromHybridStore({
334
413
  agentInput: args.parsed.agentInput,
414
+ cliArgs: args.cliArgs,
335
415
  env: args.env,
336
416
  db: args.db,
337
417
  fetchImpl: args.fetchImpl,
338
418
  fallbackFetchImpl: args.fallbackFetchImpl,
339
419
  });
340
420
  const agentKey = cached?.agentKey ?? resolveCliAgentKeyInput(args.parsed.agentInput);
341
- const serverUrl = cached?.record?.serverOrigin ?? resolveServerUrl(args.env);
421
+ const serverUrl = cached?.record?.serverOrigin ?? (
422
+ args.cliArgs ? resolveServerUrl(args.cliArgs, args.env) : resolveServerUrl(args.env)
423
+ );
342
424
  const currentRecord = await readAgentRecord({
343
425
  agentKey,
344
426
  authToken: args.authToken,
@@ -350,6 +432,7 @@ export async function buildUpdatedAgentRecord(args: {
350
432
  if (args.parsed.copyProviderFrom) {
351
433
  const providerSource = await resolveAgentRecordFromHybridStore({
352
434
  agentInput: args.parsed.copyProviderFrom,
435
+ cliArgs: args.cliArgs,
353
436
  env: args.env,
354
437
  db: args.db,
355
438
  fetchImpl: args.fetchImpl,
@@ -19,6 +19,9 @@ export {
19
19
  resolvePlatformChatProviderConfig,
20
20
  resolveLocalToolPolicy,
21
21
  resolveOpenAiCompatibleProviderConfig,
22
+ resolveRequestedRuntimeToolNames,
23
+ evaluateShellCommandPolicy,
24
+ isDestructiveShellCommand,
22
25
  shouldUsePlatformChatProvider,
23
26
  shouldCacheHybridRemoteRecord,
24
27
  shouldFetchAgentRuntimeRecordRemotely,
@@ -31,7 +31,7 @@ function readOption(args: string[], flag: string) {
31
31
 
32
32
  function buildDaemonEnv(args: string[], runtimeEnv: NodeJS.ProcessEnv) {
33
33
  const serverUrl = readOption(args, "--server-url") || readOption(args, "--server");
34
- const apiKey = readOption(args, "--api-key") || readOption(args, "--token");
34
+ const apiKey = readOption(args, "--machine-key") || readOption(args, "--token");
35
35
  return {
36
36
  ...runtimeEnv,
37
37
  ...(serverUrl ? { NOLO_SERVER: serverUrl } : {}),
@@ -40,7 +40,7 @@ function buildDaemonEnv(args: string[], runtimeEnv: NodeJS.ProcessEnv) {
40
40
  }
41
41
 
42
42
  export function looksLikeDaemonShortcut(args: string[]) {
43
- return args.includes("--server-url") || args.includes("--api-key");
43
+ return args.includes("--server-url") || args.includes("--machine-key");
44
44
  }
45
45
 
46
46
  export function createCliRuntimeContext(args: {