nolo-cli 0.1.22 → 0.1.26

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 (128) hide show
  1. package/README.md +25 -3
  2. package/agent-runtime/agentRecordConfig.ts +4 -0
  3. package/agent-runtime/hostAdapter.ts +2 -0
  4. package/agent-runtime/hybridRecordStore.ts +30 -9
  5. package/agent-runtime/index.ts +7 -0
  6. package/agent-runtime/localLoop.ts +14 -4
  7. package/agent-runtime/localToolPolicy.ts +22 -10
  8. package/agent-runtime/localWorkspaceTools.ts +140 -374
  9. package/agent-runtime/platformChatProvider.ts +3 -0
  10. package/agent-runtime/runtimeToolPolicy.ts +133 -0
  11. package/agent-runtime/types.ts +47 -0
  12. package/agentAliases.ts +1 -1
  13. package/agentCommandSupport.ts +77 -0
  14. package/agentInternalCommandEntries.ts +34 -0
  15. package/agentListCommands.ts +123 -0
  16. package/agentListHelpers.ts +187 -0
  17. package/agentMachineCommands.ts +402 -0
  18. package/agentRecordCommands.ts +141 -0
  19. package/agentRecordHelpers.ts +395 -0
  20. package/agentRunCommand.ts +75 -3
  21. package/agentRuntimeCommands.ts +7 -773
  22. package/ai/agent/_executeModel.ts +29 -3
  23. package/ai/agent/buildSystemPrompt.ts +17 -0
  24. package/ai/agent/cliChatClient.ts +38 -4
  25. package/ai/agent/cliExecutor.ts +304 -25
  26. package/ai/agent/createAgentSchema.ts +2 -2
  27. package/ai/agent/guidedCreation/assistant.ts +82 -0
  28. package/ai/agent/guidedCreation/capabilities.ts +84 -0
  29. package/ai/agent/guidedCreation/draft.ts +62 -0
  30. package/ai/agent/guidedCreation/types.ts +38 -0
  31. package/ai/agent/machineRunPermissions.ts +21 -3
  32. package/ai/agent/runtimeGuidance.ts +1 -1
  33. package/ai/agent/server/fetchPublicAgents.ts +6 -1
  34. package/ai/agent/streamAgentChatTurn.ts +593 -36
  35. package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
  36. package/ai/ai.locale.ts +14 -2
  37. package/ai/chat/fetchUtils.native.ts +2 -0
  38. package/ai/chat/fetchUtils.ts +2 -0
  39. package/ai/chat/sendOpenAICompletionsRequest.ts +82 -1
  40. package/ai/chat/sendOpenAIResponseRequest.ts +65 -0
  41. package/ai/llm/getPricing.ts +14 -0
  42. package/ai/llm/kimi.ts +4 -1
  43. package/ai/llm/modelAvatar.ts +1 -0
  44. package/ai/llm/providers.ts +4 -1
  45. package/ai/llm/reasoningModels.ts +1 -0
  46. package/ai/taskRun/taskRunProtocol.ts +25 -10
  47. package/ai/token/modelUsageQuery.ts +17 -0
  48. package/ai/tools/agent/agentTools.ts +38 -0
  49. package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
  50. package/ai/tools/agent/startAgentDialogTool.ts +53 -0
  51. package/ai/tools/checkEnvTool.ts +52 -20
  52. package/ai/tools/convertMarxistsBookTool.ts +103 -0
  53. package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
  54. package/ai/tools/index.ts +24 -14
  55. package/ai/tools/marxistsOfflineBook.ts +253 -0
  56. package/ai/tools/modelUsageTools.ts +9 -2
  57. package/ai/tools/readTool.ts +31 -0
  58. package/ai/tools/toolApiClient.ts +35 -1
  59. package/ai/tools/toolNameAliases.ts +13 -0
  60. package/cliCommandAdapters.ts +28 -0
  61. package/cliCommandDispatch.ts +66 -0
  62. package/cliCommandFactories.ts +34 -0
  63. package/cliCommandTypes.ts +49 -0
  64. package/cliEnvHelpers.ts +44 -0
  65. package/client/agentRun.test.ts +330 -24
  66. package/client/agentRun.ts +117 -35
  67. package/client/hybridRecordStore.test.ts +42 -0
  68. package/client/hybridRecordStore.ts +11 -0
  69. package/client/localRuntimeAdapter.test.ts +191 -21
  70. package/client/localRuntimeAdapter.ts +99 -10
  71. package/client/localRuntimeDryRun.test.ts +5 -2
  72. package/client/taskRunPrompt.ts +1 -0
  73. package/commandRegistry.ts +11 -61
  74. package/connector-experimental/capabilities.ts +1 -0
  75. package/database/actions/common.ts +4 -3
  76. package/database/config.ts +19 -0
  77. package/database/server/coreDataProxy.ts +43 -26
  78. package/index.ts +54 -191
  79. package/internalCommandEntries.ts +12 -0
  80. package/machineCommands.ts +688 -64
  81. package/offlineMarxistsAgentCommand.ts +314 -0
  82. package/package.json +22 -2
  83. package/render/canvas/canvasEditContext.ts +127 -0
  84. package/render/canvas/canvasRuntime.ts +57 -0
  85. package/render/canvas/canvasSnapshotParser.ts +76 -0
  86. package/render/canvas/canvasTree.ts +308 -0
  87. package/render/canvas/types.ts +46 -0
  88. package/render/layout/deleteBehavior.ts +52 -0
  89. package/render/layout/mainLayoutSidebar.ts +17 -0
  90. package/render/layout/mainLayoutViewMode.ts +56 -0
  91. package/render/layout/topbarUtils.ts +87 -0
  92. package/render/layout/useDevReloadPending.ts +30 -0
  93. package/render/page/createPageAction.ts +183 -0
  94. package/render/page/docSlice.ts +468 -0
  95. package/render/page/server/createPage.ts +174 -0
  96. package/render/page/server/handleCreatePage.ts +91 -0
  97. package/render/page/server/index.ts +4 -0
  98. package/render/page/types.ts +17 -0
  99. package/render/page/useKeyboardSave.ts +48 -0
  100. package/render/styles/zIndex.ts +12 -0
  101. package/render/surf/WeatherIconStyles.ts +17 -0
  102. package/render/surf/color.ts +9 -0
  103. package/render/surf/config.ts +46 -0
  104. package/render/surf/screens/style.ts +1 -0
  105. package/render/surf/styles/ToggleButtonStyles.ts +8 -0
  106. package/render/surf/utils/groupedWeatherData.ts +32 -0
  107. package/render/surf/weatherUtils.ts +50 -0
  108. package/render/table/activityColumns.ts +6 -0
  109. package/render/table/createTableAction.ts +270 -0
  110. package/render/table/deleteTableAction.ts +129 -0
  111. package/render/table/fetchAndCacheTableRows.ts +174 -0
  112. package/render/table/tableSlice.ts +1106 -0
  113. package/render/table/tableView.ts +289 -0
  114. package/render/table/toolValueUtils.ts +363 -0
  115. package/render/table/types.ts +252 -0
  116. package/render/table/useCreateTable.ts +72 -0
  117. package/render/table/useTable.ts +61 -0
  118. package/render/table/utils/tableSerialization.ts +50 -0
  119. package/render/web/elements/artifactPreviewCode.ts +43 -0
  120. package/render/web/elements/artifactRuntimePreload.ts +52 -0
  121. package/render/web/elements/codeBlockAutoPreview.ts +10 -0
  122. package/render/web/elements/mermaidPreview.ts +21 -0
  123. package/render/web/ui/useInlineEdit.ts +135 -0
  124. package/runtimeDoctorCommands.ts +118 -0
  125. package/scriptCommandEntries.ts +71 -0
  126. package/systemInternalCommandEntries.ts +71 -0
  127. package/tableCommands.ts +42 -5
  128. package/workflowInternalCommandEntries.ts +11 -0
@@ -23,6 +23,7 @@ const PROVIDER_ENDPOINTS: Record<string, string> = {
23
23
  export type PlatformChatProviderConfig = {
24
24
  serverUrl: string;
25
25
  authToken: string;
26
+ agentKey: string;
26
27
  model: string;
27
28
  provider: string;
28
29
  endpoint: string;
@@ -93,6 +94,7 @@ export function resolvePlatformChatProviderConfig(args: {
93
94
  return {
94
95
  serverUrl: resolvePlatformServerUrl(args.env),
95
96
  authToken: resolvePlatformAuthToken(args.env),
97
+ agentKey: args.agentConfig.key,
96
98
  model: args.agentConfig.model || "gpt-4.1-mini",
97
99
  provider,
98
100
  endpoint: resolveProviderEndpoint(args.agentConfig),
@@ -118,6 +120,7 @@ export function buildPlatformChatCompletionRequest(args: {
118
120
  ...(shouldDisableThinking(args.providerConfig) ? { thinking: { type: "disabled" } } : {}),
119
121
  url: args.providerConfig.endpoint,
120
122
  provider: args.providerConfig.provider,
123
+ agentKey: args.providerConfig.agentKey,
121
124
  ...(args.providerConfig.apiSource ? { apiSource: args.providerConfig.apiSource } : {}),
122
125
  ...(args.providerConfig.apiKey ? { KEY: args.providerConfig.apiKey } : {}),
123
126
  };
@@ -0,0 +1,133 @@
1
+ import type { AgentRuntimeAgentConfig } from "./hostAdapter";
2
+ import type { AgentRuntimeHost, AgentRuntimeToolPolicy } from "./types";
3
+
4
+ function isRecord(value: unknown): value is Record<string, unknown> {
5
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6
+ }
7
+
8
+ function stringArray(value: unknown): string[] {
9
+ if (!Array.isArray(value)) return [];
10
+ return value.filter((item): item is string => typeof item === "string" && !!item.trim());
11
+ }
12
+
13
+ function unique(values: string[]) {
14
+ return [...new Set(values)];
15
+ }
16
+
17
+ function mergeRecords<T extends Record<string, unknown>>(
18
+ base: T | undefined,
19
+ override: T | undefined,
20
+ ): T | undefined {
21
+ if (!base && !override) return undefined;
22
+ return {
23
+ ...(base ?? {}),
24
+ ...(override ?? {}),
25
+ } as T;
26
+ }
27
+
28
+ export function normalizeAgentRuntimeToolPolicy(
29
+ value: unknown,
30
+ ): AgentRuntimeToolPolicy | undefined {
31
+ if (!isRecord(value)) return undefined;
32
+ return {
33
+ version: 1,
34
+ ...(stringArray(value.agentTools).length
35
+ ? { agentTools: unique(stringArray(value.agentTools)) }
36
+ : {}),
37
+ ...(stringArray(value.runtimeTools).length
38
+ ? { runtimeTools: unique(stringArray(value.runtimeTools)) }
39
+ : {}),
40
+ ...(isRecord(value.workspace) ? { workspace: { ...value.workspace } } : {}),
41
+ ...(isRecord(value.shell) ? { shell: { ...value.shell } } : {}),
42
+ ...(isRecord(value.isolation) ? { isolation: { ...value.isolation } } : {}),
43
+ ...(isRecord(value.git) ? { git: { ...value.git } } : {}),
44
+ ...(isRecord(value.budget) ? { budget: { ...value.budget } } : {}),
45
+ ...(isRecord(value.audit) ? { audit: { ...value.audit } } : {}),
46
+ };
47
+ }
48
+
49
+ export function mergeAgentRuntimeToolPolicies(
50
+ base?: AgentRuntimeToolPolicy,
51
+ override?: AgentRuntimeToolPolicy,
52
+ ): AgentRuntimeToolPolicy | undefined {
53
+ if (!base && !override) return undefined;
54
+ return {
55
+ version: 1,
56
+ agentTools: unique([
57
+ ...(base?.agentTools ?? []),
58
+ ...(override?.agentTools ?? []),
59
+ ]),
60
+ runtimeTools: unique([
61
+ ...(base?.runtimeTools ?? []),
62
+ ...(override?.runtimeTools ?? []),
63
+ ]),
64
+ workspace: {
65
+ ...(base?.workspace ?? {}),
66
+ ...(override?.workspace ?? {}),
67
+ writableRoots: unique([
68
+ ...(base?.workspace?.writableRoots ?? []),
69
+ ...(override?.workspace?.writableRoots ?? []),
70
+ ]),
71
+ },
72
+ shell: mergeRecords(base?.shell, override?.shell),
73
+ isolation: mergeRecords(base?.isolation, override?.isolation),
74
+ git: mergeRecords(base?.git, override?.git),
75
+ budget: mergeRecords(base?.budget, override?.budget),
76
+ audit: mergeRecords(base?.audit, override?.audit),
77
+ };
78
+ }
79
+
80
+ function policyRequestsHostedWorkspace(policy: AgentRuntimeToolPolicy | undefined) {
81
+ if (!policy) return false;
82
+ const workspaceMode = policy.workspace?.mode;
83
+ return (
84
+ policy.shell?.enabled === true ||
85
+ policy.runtimeTools?.some((tool) => tool === "execShell") ||
86
+ workspaceMode === "lease" ||
87
+ workspaceMode === "task-worktree"
88
+ );
89
+ }
90
+
91
+ function applyWebRuntimeSafetyBaseline(
92
+ policy: AgentRuntimeToolPolicy | undefined,
93
+ ): AgentRuntimeToolPolicy | undefined {
94
+ if (!policyRequestsHostedWorkspace(policy)) return policy;
95
+ return mergeAgentRuntimeToolPolicies(
96
+ policy,
97
+ {
98
+ version: 1,
99
+ shell: {
100
+ commandPolicy: "approval",
101
+ networkPolicy: "default-deny",
102
+ },
103
+ isolation: {
104
+ mode: "os-sandbox",
105
+ },
106
+ audit: {
107
+ logToolCalls: true,
108
+ logShellCommands: true,
109
+ writeToDialog: true,
110
+ },
111
+ },
112
+ );
113
+ }
114
+
115
+ export function resolveEffectiveRuntimeToolPolicy(args: {
116
+ agentConfig: Pick<AgentRuntimeAgentConfig, "runtimeToolPolicy" | "runtimeBinding">;
117
+ taskRunControl?: { runtimeToolPolicy?: unknown } | null;
118
+ host?: AgentRuntimeHost;
119
+ }): AgentRuntimeToolPolicy | undefined {
120
+ const agentPolicy = normalizeAgentRuntimeToolPolicy(
121
+ args.agentConfig.runtimeToolPolicy ??
122
+ (isRecord(args.agentConfig.runtimeBinding)
123
+ ? args.agentConfig.runtimeBinding.runtimeToolPolicy
124
+ : undefined),
125
+ );
126
+ const taskPolicy = normalizeAgentRuntimeToolPolicy(
127
+ args.taskRunControl?.runtimeToolPolicy,
128
+ );
129
+ const effectivePolicy = mergeAgentRuntimeToolPolicies(agentPolicy, taskPolicy);
130
+ return args.host === "web"
131
+ ? applyWebRuntimeSafetyBaseline(effectivePolicy)
132
+ : effectivePolicy;
133
+ }
@@ -81,6 +81,53 @@ export type AgentRuntimeDecisionInput = {
81
81
  serverFallbackAvailable: boolean;
82
82
  };
83
83
 
84
+ export type AgentRuntimeWorkspaceMode = "none" | "current" | "task-worktree" | "lease";
85
+
86
+ export type AgentRuntimeShellPolicy = {
87
+ enabled?: boolean;
88
+ mode?: "off" | "worktree";
89
+ commandPolicy?: "denylist" | "allowlist" | "approval";
90
+ networkPolicy?: "default-deny" | "allowed" | "approval";
91
+ maxSeconds?: number;
92
+ maxOutputBytes?: number;
93
+ };
94
+
95
+ export type AgentRuntimeGitPolicy = {
96
+ canCommit?: boolean;
97
+ canPushAlpha?: boolean;
98
+ canMergeMain?: boolean;
99
+ };
100
+
101
+ export type AgentRuntimeAuditPolicy = {
102
+ logToolCalls?: boolean;
103
+ logShellCommands?: boolean;
104
+ writeToDialog?: boolean;
105
+ writeToTask?: boolean;
106
+ };
107
+
108
+ export type AgentRuntimeIsolationPolicy = {
109
+ mode?: "none" | "os-sandbox" | "container" | "gvisor" | "microvm" | "dedicated-vm";
110
+ };
111
+
112
+ export type AgentRuntimeToolPolicy = {
113
+ version?: 1;
114
+ agentTools?: string[];
115
+ runtimeTools?: string[];
116
+ workspace?: {
117
+ mode?: AgentRuntimeWorkspaceMode;
118
+ writableRoots?: string[];
119
+ cwd?: string;
120
+ };
121
+ shell?: AgentRuntimeShellPolicy;
122
+ isolation?: AgentRuntimeIsolationPolicy;
123
+ git?: AgentRuntimeGitPolicy;
124
+ budget?: {
125
+ dailyUsdLimit?: number;
126
+ maxRunSeconds?: number;
127
+ };
128
+ audit?: AgentRuntimeAuditPolicy;
129
+ };
130
+
84
131
  export type AgentRuntimeDecision = {
85
132
  mode: AgentRuntimeMode;
86
133
  runnable: boolean;
package/agentAliases.ts CHANGED
@@ -13,7 +13,7 @@ export const NOLO_FULLSTACK_AGENT_KEY =
13
13
  export const NOLO_SENIOR_FULLSTACK_AGENT_ID = "01NOLOFULLSTK00000000T3XR4";
14
14
  export const NOLO_SENIOR_FULLSTACK_AGENT_KEY =
15
15
  `agent-${PLATFORM_DEMO_USER_ID}-${NOLO_SENIOR_FULLSTACK_AGENT_ID}`;
16
- export const NOLO_REVIEW_AGENT_ID = "01NOLOREVIEW0000000000R8V1";
16
+ export const NOLO_REVIEW_AGENT_ID = "01CODEXCLIMAIN00000001OK";
17
17
  export const NOLO_REVIEW_AGENT_KEY =
18
18
  `agent-${PLATFORM_DEMO_USER_ID}-${NOLO_REVIEW_AGENT_ID}`;
19
19
 
@@ -0,0 +1,77 @@
1
+ import type { MachineHeartbeat } from "./connector-experimental/protocol";
2
+ import type { CliKvDb } from "./client/hybridRecordStore";
3
+ import type { EnvLike } from "./cliEnvHelpers";
4
+
5
+ export type OutputLike = { write(chunk: string): unknown };
6
+
7
+ export type SmokeWebSocketOptions = {
8
+ headers: Record<string, string>;
9
+ onMessage: (message: string) => void | Promise<void>;
10
+ onOpen: () => void | Promise<void>;
11
+ sentMessages: string[];
12
+ };
13
+
14
+ export type LocalCliExecutor = (
15
+ provider: string,
16
+ prompt: string,
17
+ options: { model?: string; timeout?: number; cwd?: string; yolo?: boolean }
18
+ ) => Promise<{ text: string; raw?: string; elapsed?: number }>;
19
+
20
+ export type LocalRuntimeProbeResult = {
21
+ ok: boolean;
22
+ dbPath: string;
23
+ agentFound: boolean;
24
+ agentKey?: string;
25
+ error?: string;
26
+ };
27
+
28
+ export type AgentCommandDeps = {
29
+ env?: EnvLike;
30
+ output?: OutputLike;
31
+ db?: CliKvDb;
32
+ fetchImpl?: typeof fetch;
33
+ fallbackFetchImpl?: typeof fetch;
34
+ machineInfo?: () => MachineHeartbeat;
35
+ connectWebSocket?: (url: string, options: SmokeWebSocketOptions) => Promise<void>;
36
+ executeCli?: LocalCliExecutor;
37
+ connectorRunMessageHandler?: (
38
+ machine: MachineHeartbeat,
39
+ message: string,
40
+ pushMessage: (response: string) => void,
41
+ executeCli: LocalCliExecutor,
42
+ env: EnvLike,
43
+ fetchImpl: typeof fetch
44
+ ) => Promise<void>;
45
+ localRuntimeProbe?: (env: EnvLike) => Promise<LocalRuntimeProbeResult>;
46
+ };
47
+
48
+ async function getDefaultCliDb() {
49
+ const { getDefaultCliLocalRuntimeDb } = await import("./localRuntimeDb");
50
+ return getDefaultCliLocalRuntimeDb();
51
+ }
52
+
53
+ function createNullCliDb(): CliKvDb {
54
+ return {
55
+ get: async () => {
56
+ throw new Error("local cache unavailable");
57
+ },
58
+ put: async () => undefined,
59
+ batch: async () => undefined,
60
+ iterator: () => {
61
+ throw new Error("local cache unavailable");
62
+ },
63
+ };
64
+ }
65
+
66
+ export async function getReadableCliDb(output: OutputLike) {
67
+ try {
68
+ return await getDefaultCliDb();
69
+ } catch (error) {
70
+ output.write(
71
+ `[nolo] local agent cache unavailable; falling back to remote reads only: ${
72
+ error instanceof Error ? error.message : String(error)
73
+ }\n`
74
+ );
75
+ return createNullCliDb();
76
+ }
77
+ }
@@ -0,0 +1,34 @@
1
+ import { runAgentListCommand } from "./agentListCommands";
2
+ import {
3
+ runAgentBindCurrentCommand,
4
+ runAgentRuntimeDoctorCommand,
5
+ runAgentSmokeCurrentCommand,
6
+ } from "./agentMachineCommands";
7
+ import { runAgentPullCommand } from "./agentPullCommand";
8
+ import {
9
+ runAgentReadCommand,
10
+ runAgentUpdateCommand,
11
+ } from "./agentRecordCommands";
12
+ import { runAgentRunCommand } from "./agentRunCommand";
13
+ import {
14
+ createEnvCommand,
15
+ createEnvScriptDirCommand,
16
+ } from "./cliCommandFactories";
17
+ import type { CommandEntry } from "./cliCommandTypes";
18
+ import { runSetupOfflineMarxistsAgentCommand } from "./offlineMarxistsAgentCommand";
19
+
20
+ export function getAgentInternalCommandEntries(): CommandEntry[] {
21
+ return [
22
+ createEnvScriptDirCommand(["chat"], "Chat with an agent", runAgentRunCommand),
23
+ createEnvCommand(["agent", "list"], "List owned agents", runAgentListCommand),
24
+ createEnvCommand(["agent", "pull"], "Cache an agent for local runs", runAgentPullCommand),
25
+ createEnvCommand(["agent", "read"], "Read a single agent", runAgentReadCommand),
26
+ createEnvScriptDirCommand(["agent", "run"], "Run an agent", runAgentRunCommand),
27
+ createEnvCommand(["agent", "setup-offline-marxists"], "Create or update the Marxists.org offline book agent", runSetupOfflineMarxistsAgentCommand),
28
+ createEnvCommand(["agent", "update"], "Update agent fields", runAgentUpdateCommand),
29
+ createEnvCommand(["agent", "bind-current"], "Bind an agent to this machine", runAgentBindCurrentCommand),
30
+ createEnvCommand(["agent", "smoke-current"], "Smoke test a bound agent through this machine", runAgentSmokeCurrentCommand),
31
+ createEnvCommand(["agent", "runtime-doctor"], "Diagnose current machine runtime compatibility", runAgentRuntimeDoctorCommand),
32
+ createEnvScriptDirCommand(["agent", "chat"], "Chat with an agent", runAgentRunCommand),
33
+ ];
34
+ }
@@ -0,0 +1,123 @@
1
+ import { getReadableCliDb, type AgentCommandDeps } from "./agentCommandSupport";
2
+ import {
3
+ listLocalCachedAgents,
4
+ listRemoteAgents,
5
+ parseAgentListArgs,
6
+ type ListedAgent,
7
+ } from "./agentListHelpers";
8
+ import {
9
+ queryUserRecords,
10
+ readDbRecord,
11
+ } from "./agentRecordHelpers";
12
+ import {
13
+ parseUserIdFromAuthToken,
14
+ resolveAuthToken,
15
+ resolveServerUrl,
16
+ } from "./cliEnvHelpers";
17
+
18
+ export async function runAgentListCommand(
19
+ args: string[],
20
+ deps: AgentCommandDeps = {}
21
+ ) {
22
+ const env = deps.env ?? process.env;
23
+ const output = deps.output ?? process.stdout;
24
+ const { wantJson, publicOnly, idsOnly, includeLegacy } = parseAgentListArgs(args);
25
+
26
+ const authToken = resolveAuthToken(env);
27
+ if (!authToken) {
28
+ output.write("[nolo] agent list requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
29
+ return 1;
30
+ }
31
+
32
+ const userId = parseUserIdFromAuthToken(authToken);
33
+ if (!userId) {
34
+ output.write("[nolo] agent list could not read userId from AUTH_TOKEN.\n");
35
+ return 1;
36
+ }
37
+
38
+ const db = deps.db ?? await getReadableCliDb(output);
39
+ const fetchImpl = deps.fetchImpl ?? fetch;
40
+ const fallbackFetchImpl = deps.fallbackFetchImpl;
41
+
42
+ try {
43
+ let agents: ListedAgent[];
44
+ let source: "local-cache" | "remote-cache";
45
+ try {
46
+ agents = await listLocalCachedAgents({ db, userId });
47
+ source = "local-cache";
48
+ } catch {
49
+ const serverUrl = resolveServerUrl(env);
50
+ agents = await listRemoteAgents({
51
+ authToken,
52
+ fallbackFetchImpl,
53
+ fetchImpl,
54
+ includeLegacy,
55
+ serverUrl,
56
+ userId,
57
+ queryUserRecords,
58
+ readDbRecord,
59
+ });
60
+ source = "remote-cache";
61
+ }
62
+
63
+ if (!includeLegacy) {
64
+ agents = agents.filter((agent) => !agent.privateKey.startsWith("cybot-"));
65
+ }
66
+ if (publicOnly) {
67
+ agents = agents.filter((agent) => agent.publicRecordExists);
68
+ }
69
+
70
+ if (idsOnly) {
71
+ output.write(`${agents.map((agent) => agent.id).join("\n")}\n`);
72
+ return 0;
73
+ }
74
+
75
+ if (wantJson) {
76
+ output.write(JSON.stringify({
77
+ userId,
78
+ total: agents.length,
79
+ publicCount: agents.filter((agent) => agent.publicRecordExists).length,
80
+ source,
81
+ agents,
82
+ }, null, 2));
83
+ output.write("\n");
84
+ return 0;
85
+ }
86
+
87
+ output.write(`userId: ${userId}\n`);
88
+ output.write(`total agents: ${agents.length}\n`);
89
+ output.write(`public agents: ${agents.filter((agent) => agent.publicRecordExists).length}\n`);
90
+ output.write(`source: ${source}\n`);
91
+ if (agents.length === 0) {
92
+ output.write("\n(no agents found)\n");
93
+ return 0;
94
+ }
95
+ for (const agent of agents) {
96
+ const status = agent.publicRecordExists ? "public" : "private";
97
+ const flagMismatch = agent.isPublicFlag !== agent.publicRecordExists
98
+ ? ` flag=${agent.isPublicFlag}`
99
+ : "";
100
+ output.write(
101
+ [
102
+ `\n[${status}] ${agent.name}`,
103
+ `id=${agent.id}`,
104
+ `type=${agent.type ?? "-"}`,
105
+ `model=${agent.model}`,
106
+ `updatedAt=${agent.updatedAt ?? "-"}`,
107
+ `privateKey=${agent.privateKey}`,
108
+ `publicKey=${agent.publicKey}${flagMismatch}`,
109
+ `tools=${agent.tools.join(", ") || "-"}`,
110
+ ].join("\n")
111
+ );
112
+ output.write("\n");
113
+ }
114
+ return 0;
115
+ } catch (error) {
116
+ output.write(
117
+ `[nolo] agent list failed: ${
118
+ error instanceof Error ? error.message : String(error)
119
+ }\n`
120
+ );
121
+ return 1;
122
+ }
123
+ }
@@ -0,0 +1,187 @@
1
+ import type { CliKvDb } from "./client/hybridRecordStore";
2
+
3
+ export type ListedAgent = {
4
+ id: string;
5
+ privateKey: string;
6
+ publicKey: string;
7
+ name: string;
8
+ model: string;
9
+ updatedAt: string | number | null;
10
+ isPublicFlag: boolean;
11
+ publicRecordExists: boolean;
12
+ type: string | null;
13
+ tools: string[];
14
+ };
15
+
16
+ function parseAgentRecordId(privateKey: string, explicitId?: string) {
17
+ if (explicitId) return explicitId;
18
+ const agentMatch = privateKey.match(/^agent-[^-]+-(.+)$/i);
19
+ if (agentMatch?.[1]) return agentMatch[1];
20
+ const cybotMatch = privateKey.match(/^cybot-[^-]+-([0-9A-HJKMNP-TV-Z]{26})$/i);
21
+ return cybotMatch?.[1] ?? "";
22
+ }
23
+
24
+ function buildCompanionKeys(privateKey: string, rawId: string, userId: string) {
25
+ if (privateKey.startsWith("cybot-")) {
26
+ return {
27
+ privateKey: `cybot-${userId}-${rawId}`,
28
+ publicKey: `cybot-pub-${rawId}`,
29
+ };
30
+ }
31
+ return {
32
+ privateKey: `agent-${userId}-${rawId}`,
33
+ publicKey: `agent-pub-${rawId}`,
34
+ };
35
+ }
36
+
37
+ export function normalizeListedAgent(record: any): ListedAgent | null {
38
+ const privateKey = typeof record?.dbKey === "string" ? record.dbKey : "";
39
+ const explicitId = typeof record?.id === "string" && record.id ? record.id : undefined;
40
+ const ownerUserId = typeof record?.userId === "string" ? record.userId : "";
41
+ const rawId = explicitId
42
+ || (ownerUserId && privateKey.startsWith(`agent-${ownerUserId}-`)
43
+ ? privateKey.slice(`agent-${ownerUserId}-`.length)
44
+ : ownerUserId && privateKey.startsWith(`cybot-${ownerUserId}-`)
45
+ ? privateKey.slice(`cybot-${ownerUserId}-`.length)
46
+ : parseAgentRecordId(privateKey, explicitId));
47
+ if (!privateKey || !rawId || !ownerUserId) return null;
48
+ const keys = buildCompanionKeys(privateKey, rawId, ownerUserId);
49
+ return {
50
+ id: rawId,
51
+ privateKey,
52
+ publicKey: keys.publicKey,
53
+ name: typeof record?.name === "string" && record.name ? record.name : "(unnamed)",
54
+ model: typeof record?.model === "string" && record.model ? record.model : "-",
55
+ updatedAt:
56
+ typeof record?.updatedAt === "string" || typeof record?.updatedAt === "number"
57
+ ? record.updatedAt
58
+ : typeof record?.createdAt === "string" || typeof record?.createdAt === "number"
59
+ ? record.createdAt
60
+ : typeof record?.created === "string" || typeof record?.created === "number"
61
+ ? record.created
62
+ : null,
63
+ isPublicFlag: !!record?.isPublic,
64
+ publicRecordExists: false,
65
+ type: typeof record?.type === "string" ? record.type : null,
66
+ tools: Array.isArray(record?.tools)
67
+ ? record.tools.filter((tool: unknown): tool is string => typeof tool === "string")
68
+ : [],
69
+ };
70
+ }
71
+
72
+ export function sortListedAgents(agents: ListedAgent[]) {
73
+ agents.sort((a, b) => {
74
+ const ta = a.updatedAt == null ? 0 : new Date(a.updatedAt).getTime();
75
+ const tb = b.updatedAt == null ? 0 : new Date(b.updatedAt).getTime();
76
+ return tb - ta;
77
+ });
78
+ return agents;
79
+ }
80
+
81
+ export function parseAgentListArgs(args: string[]) {
82
+ return {
83
+ wantJson: args.includes("--json"),
84
+ publicOnly: args.includes("--public-only"),
85
+ idsOnly: args.includes("--ids-only"),
86
+ includeLegacy: args.includes("--include-legacy"),
87
+ };
88
+ }
89
+
90
+ export async function listLocalCachedAgents(args: {
91
+ db: CliKvDb;
92
+ userId: string;
93
+ }) {
94
+ const privateRecords = new Map<string, any>();
95
+ const publicKeys = new Set<string>();
96
+ for await (const [key, value] of args.db.iterator()) {
97
+ if (typeof key !== "string" || !value || typeof value !== "object") continue;
98
+ if (key.startsWith(`agent-${args.userId}-`) || key.startsWith(`cybot-${args.userId}-`)) {
99
+ privateRecords.set(key, { ...(value as Record<string, unknown>), dbKey: key });
100
+ continue;
101
+ }
102
+ if (key.startsWith("agent-pub-") || key.startsWith("cybot-pub-")) {
103
+ publicKeys.add(key);
104
+ }
105
+ }
106
+
107
+ const agents = sortListedAgents(
108
+ [...privateRecords.values()]
109
+ .map((record) => normalizeListedAgent(record))
110
+ .filter((agent): agent is ListedAgent => agent != null)
111
+ );
112
+
113
+ for (const agent of agents) {
114
+ agent.publicRecordExists = publicKeys.has(agent.publicKey);
115
+ }
116
+ return agents;
117
+ }
118
+
119
+ async function hasReadableRecord(args: {
120
+ authToken: string;
121
+ dbKey: string;
122
+ fallbackFetchImpl?: typeof fetch;
123
+ fetchImpl: typeof fetch;
124
+ serverUrl: string;
125
+ readDbRecord: (args: {
126
+ authToken: string;
127
+ dbKey: string;
128
+ fallbackFetchImpl?: typeof fetch;
129
+ fetchImpl: typeof fetch;
130
+ serverUrl: string;
131
+ }) => Promise<any>;
132
+ }) {
133
+ try {
134
+ await args.readDbRecord(args);
135
+ return true;
136
+ } catch {
137
+ return false;
138
+ }
139
+ }
140
+
141
+ export async function listRemoteAgents(args: {
142
+ authToken: string;
143
+ fallbackFetchImpl?: typeof fetch;
144
+ fetchImpl: typeof fetch;
145
+ includeLegacy: boolean;
146
+ serverUrl: string;
147
+ userId: string;
148
+ queryUserRecords: (args: {
149
+ authToken: string;
150
+ fallbackFetchImpl?: typeof fetch;
151
+ fetchImpl: typeof fetch;
152
+ serverUrl: string;
153
+ userId: string;
154
+ type: "agent" | "cybot";
155
+ }) => Promise<any[]>;
156
+ readDbRecord: (args: {
157
+ authToken: string;
158
+ dbKey: string;
159
+ fallbackFetchImpl?: typeof fetch;
160
+ fetchImpl: typeof fetch;
161
+ serverUrl: string;
162
+ }) => Promise<any>;
163
+ }) {
164
+ const recordGroups = [await args.queryUserRecords({ ...args, type: "agent" as const })];
165
+ if (args.includeLegacy) {
166
+ recordGroups.push(await args.queryUserRecords({ ...args, type: "cybot" as const }));
167
+ }
168
+ const agents = sortListedAgents(
169
+ recordGroups
170
+ .flat()
171
+ .map((record) => normalizeListedAgent(record))
172
+ .filter((agent): agent is ListedAgent => agent != null)
173
+ );
174
+ await Promise.all(
175
+ agents.map(async (agent) => {
176
+ agent.publicRecordExists = await hasReadableRecord({
177
+ authToken: args.authToken,
178
+ dbKey: agent.publicKey,
179
+ fallbackFetchImpl: args.fallbackFetchImpl,
180
+ fetchImpl: args.fetchImpl,
181
+ serverUrl: args.serverUrl,
182
+ readDbRecord: args.readDbRecord,
183
+ });
184
+ })
185
+ );
186
+ return agents;
187
+ }