@tenex-chat/backend 0.9.5 → 0.9.6

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 (148) hide show
  1. package/README.md +5 -1
  2. package/dist/daemon-wrapper.cjs +47 -0
  3. package/dist/index.js +59268 -0
  4. package/dist/wrapper.js +171 -0
  5. package/package.json +19 -27
  6. package/src/agents/AgentRegistry.ts +9 -7
  7. package/src/agents/AgentStorage.ts +24 -1
  8. package/src/agents/agent-installer.ts +6 -0
  9. package/src/agents/agent-loader.ts +7 -2
  10. package/src/agents/constants.ts +10 -2
  11. package/src/agents/execution/AgentExecutor.ts +35 -6
  12. package/src/agents/execution/StreamCallbacks.ts +53 -13
  13. package/src/agents/execution/StreamExecutionHandler.ts +110 -16
  14. package/src/agents/execution/StreamSetup.ts +19 -9
  15. package/src/agents/execution/ToolEventHandlers.ts +112 -0
  16. package/src/agents/role-categories.ts +53 -0
  17. package/src/agents/types/runtime.ts +7 -0
  18. package/src/agents/types/storage.ts +7 -0
  19. package/src/commands/agent/import/openclaw-distiller.ts +63 -7
  20. package/src/commands/agent/import/openclaw-reader.ts +54 -0
  21. package/src/commands/agent/import/openclaw.ts +120 -29
  22. package/src/commands/agent/index.ts +83 -2
  23. package/src/commands/setup/display.ts +123 -0
  24. package/src/commands/setup/embed.ts +13 -13
  25. package/src/commands/setup/global-system-prompt.ts +15 -17
  26. package/src/commands/setup/image.ts +17 -20
  27. package/src/commands/setup/interactive.ts +37 -20
  28. package/src/commands/setup/llm.ts +12 -7
  29. package/src/commands/setup/onboarding.ts +1580 -248
  30. package/src/commands/setup/providers.ts +3 -3
  31. package/src/conversations/ConversationStore.ts +23 -2
  32. package/src/conversations/MessageBuilder.ts +51 -73
  33. package/src/conversations/formatters/utils/conversation-transcript-formatter.ts +425 -0
  34. package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +40 -98
  35. package/src/conversations/search/embeddings/ConversationIndexingJob.ts +40 -52
  36. package/src/conversations/services/ConversationSummarizer.ts +1 -2
  37. package/src/conversations/types.ts +11 -0
  38. package/src/daemon/Daemon.ts +78 -57
  39. package/src/daemon/ProjectRuntime.ts +6 -12
  40. package/src/daemon/SubscriptionManager.ts +13 -0
  41. package/src/daemon/index.ts +0 -1
  42. package/src/event-handler/index.ts +1 -0
  43. package/src/index.ts +20 -1
  44. package/src/llm/ChunkHandler.ts +1 -1
  45. package/src/llm/FinishHandler.ts +28 -4
  46. package/src/llm/LLMConfigEditor.ts +218 -106
  47. package/src/llm/index.ts +0 -4
  48. package/src/llm/meta/MetaModelResolver.ts +3 -18
  49. package/src/llm/middleware/message-sanitizer.ts +153 -0
  50. package/src/llm/providers/ollama-models.ts +0 -38
  51. package/src/llm/service.ts +50 -15
  52. package/src/llm/types.ts +0 -12
  53. package/src/llm/utils/ConfigurationManager.ts +88 -465
  54. package/src/llm/utils/ConfigurationTester.ts +42 -185
  55. package/src/llm/utils/ModelSelector.ts +156 -92
  56. package/src/llm/utils/ProviderConfigUI.ts +10 -141
  57. package/src/llm/utils/models-dev-cache.ts +102 -23
  58. package/src/llm/utils/provider-select-prompt.ts +284 -0
  59. package/src/llm/utils/provider-setup.ts +81 -34
  60. package/src/llm/utils/variant-list-prompt.ts +361 -0
  61. package/src/nostr/AgentEventDecoder.ts +1 -0
  62. package/src/nostr/AgentEventEncoder.ts +37 -0
  63. package/src/nostr/AgentProfilePublisher.ts +13 -0
  64. package/src/nostr/AgentPublisher.ts +26 -0
  65. package/src/nostr/kinds.ts +1 -0
  66. package/src/nostr/ndkClient.ts +4 -1
  67. package/src/nostr/types.ts +12 -0
  68. package/src/prompts/fragments/25-rag-instructions.ts +22 -21
  69. package/src/prompts/fragments/31-agents-md-guidance.ts +7 -21
  70. package/src/prompts/fragments/index.ts +2 -0
  71. package/src/prompts/utils/systemPromptBuilder.ts +18 -28
  72. package/src/services/AgentDefinitionMonitor.ts +8 -0
  73. package/src/services/ConfigService.ts +34 -0
  74. package/src/services/PubkeyService.ts +7 -1
  75. package/src/services/compression/CompressionService.ts +133 -74
  76. package/src/services/compression/compression-utils.ts +110 -19
  77. package/src/services/config/types.ts +0 -6
  78. package/src/services/dispatch/AgentDispatchService.ts +79 -0
  79. package/src/services/intervention/InterventionService.ts +78 -5
  80. package/src/services/nip46/Nip46SigningService.ts +30 -1
  81. package/src/services/projects/ProjectContext.ts +8 -6
  82. package/src/services/rag/RAGCollectionRegistry.ts +199 -0
  83. package/src/services/rag/RAGDatabaseService.ts +2 -7
  84. package/src/services/rag/RAGOperations.ts +25 -45
  85. package/src/services/rag/RAGService.ts +0 -31
  86. package/src/services/rag/RagSubscriptionService.ts +71 -122
  87. package/src/services/rag/rag-utils.ts +13 -0
  88. package/src/services/ral/RALRegistry.ts +25 -184
  89. package/src/services/reports/ReportEmbeddingService.ts +63 -113
  90. package/src/services/search/UnifiedSearchService.ts +115 -4
  91. package/src/services/search/index.ts +1 -0
  92. package/src/services/search/projectFilter.ts +20 -4
  93. package/src/services/search/providers/ConversationSearchProvider.ts +1 -0
  94. package/src/services/search/providers/GenericCollectionSearchProvider.ts +81 -0
  95. package/src/services/search/providers/LessonSearchProvider.ts +1 -8
  96. package/src/services/search/providers/ReportSearchProvider.ts +1 -0
  97. package/src/services/search/types.ts +24 -3
  98. package/src/services/trust-pubkeys/SystemPubkeyListService.ts +148 -0
  99. package/src/services/trust-pubkeys/TrustPubkeyService.ts +70 -9
  100. package/src/telemetry/setup.ts +2 -13
  101. package/src/tools/implementations/ask.ts +3 -3
  102. package/src/tools/implementations/conversation_get.ts +28 -268
  103. package/src/tools/implementations/fs_grep.ts +6 -6
  104. package/src/tools/implementations/fs_read.ts +2 -0
  105. package/src/tools/implementations/fs_write.ts +2 -0
  106. package/src/tools/implementations/learn.ts +38 -50
  107. package/src/tools/implementations/rag_add_documents.ts +6 -4
  108. package/src/tools/implementations/rag_create_collection.ts +37 -4
  109. package/src/tools/implementations/rag_delete_collection.ts +9 -0
  110. package/src/tools/implementations/{search.ts → rag_search.ts} +31 -25
  111. package/src/tools/registry.ts +7 -8
  112. package/src/tools/types.ts +11 -2
  113. package/src/tools/utils/transcript-args.ts +13 -0
  114. package/src/utils/cli-theme.ts +13 -0
  115. package/src/utils/logger.ts +55 -0
  116. package/src/utils/metadataKeys.ts +17 -0
  117. package/src/utils/sqlEscaping.ts +39 -0
  118. package/src/wrapper.ts +7 -3
  119. package/dist/src/index.js +0 -46790
  120. package/dist/tenex-backend-wrapper.cjs +0 -3
  121. package/src/agents/execution/constants.ts +0 -16
  122. package/src/agents/execution/index.ts +0 -3
  123. package/src/agents/index.ts +0 -4
  124. package/src/commands/agent.ts +0 -235
  125. package/src/conversations/formatters/DelegationXmlFormatter.ts +0 -64
  126. package/src/conversations/formatters/index.ts +0 -9
  127. package/src/conversations/index.ts +0 -2
  128. package/src/conversations/utils/content-utils.ts +0 -69
  129. package/src/daemon/UnixSocketTransport.ts +0 -318
  130. package/src/event-handler/newConversation.ts +0 -165
  131. package/src/events/NDKProjectStatus.ts +0 -384
  132. package/src/events/index.ts +0 -4
  133. package/src/lib/json-parser.ts +0 -30
  134. package/src/llm/RecordingState.ts +0 -37
  135. package/src/llm/StreamPublisher.ts +0 -40
  136. package/src/llm/middleware/flight-recorder.ts +0 -188
  137. package/src/llm/utils/claudeCodePromptCompiler.ts +0 -141
  138. package/src/nostr/constants.ts +0 -38
  139. package/src/prompts/core/index.ts +0 -3
  140. package/src/prompts/index.ts +0 -21
  141. package/src/services/image/index.ts +0 -12
  142. package/src/services/status/index.ts +0 -11
  143. package/src/telemetry/diagnostics.ts +0 -27
  144. package/src/tools/implementations/rag_query.ts +0 -107
  145. package/src/types/index.ts +0 -46
  146. package/src/utils/agentFetcher.ts +0 -107
  147. package/src/utils/conversation-utils.ts +0 -1
  148. package/src/utils/process.ts +0 -49
@@ -4,10 +4,11 @@ import { Command } from "commander";
4
4
  import chalk from "chalk";
5
5
  import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
6
6
  import { agentStorage, createStoredAgent } from "@/agents/AgentStorage";
7
+ import { getAgentHomeDirectory } from "@/lib/agent-home";
7
8
  import { config as configService } from "@/services/ConfigService";
8
9
  import type { LLMConfiguration } from "@/services/config/types";
9
10
  import { detectOpenClawStateDir, readOpenClawAgents, convertModelFormat } from "./openclaw-reader";
10
- import { distillAgentIdentity } from "./openclaw-distiller";
11
+ import { distillAgentIdentity, distillUserContext } from "./openclaw-distiller";
11
12
  import type { OpenClawAgent } from "./openclaw-reader";
12
13
 
13
14
  function toSlug(name: string): string {
@@ -18,23 +19,46 @@ function toSlug(name: string): string {
18
19
  .replace(/^-+|-+$/g, "");
19
20
  }
20
21
 
21
- async function createHomeDir(pubkey: string, workspacePath: string): Promise<string> {
22
- const homeDir = configService.getConfigPath(`agents/${pubkey}`);
22
+ interface CreateHomeDirOptions {
23
+ noSync?: boolean;
24
+ }
25
+
26
+ export async function createHomeDir(
27
+ pubkey: string,
28
+ workspacePath: string,
29
+ options: CreateHomeDirOptions = {},
30
+ ): Promise<string> {
31
+ const homeDir = getAgentHomeDirectory(pubkey);
23
32
  await fs.mkdir(homeDir, { recursive: true });
24
33
 
25
- // Symlink MEMORY.md (dangling is ok — file may not exist yet)
26
- const memoryMdTarget = path.join(workspacePath, "MEMORY.md");
27
- const memoryMdLink = path.join(homeDir, "MEMORY.md");
28
- await fs.rm(memoryMdLink, { force: true });
29
- await fs.symlink(memoryMdTarget, memoryMdLink);
34
+ if (options.noSync) {
35
+ // Copy all workspace files into the home directory
36
+ await fs.cp(workspacePath, homeDir, { recursive: true });
37
+
38
+ const indexContent = `# Memory Files
30
39
 
31
- // Symlink memory/ directory (dangling is ok)
32
- const memoryDirTarget = path.join(workspacePath, "memory");
33
- const memoryDirLink = path.join(homeDir, "memory");
34
- await fs.rm(memoryDirLink, { force: true });
35
- await fs.symlink(memoryDirTarget, memoryDirLink);
40
+ This agent's memory was copied from an OpenClaw installation.
36
41
 
37
- const indexContent = `# Memory Files
42
+ - \`MEMORY.md\` long-term curated memory (copied from OpenClaw)
43
+ - \`memory/YYYY-MM-DD.md\` — daily session logs (copied from OpenClaw)
44
+
45
+ Source: ${workspacePath}
46
+ `;
47
+ await fs.writeFile(path.join(homeDir, "+INDEX.md"), indexContent, "utf-8");
48
+ } else {
49
+ // Symlink MEMORY.md (dangling is ok — file may not exist yet)
50
+ const memoryMdTarget = path.join(workspacePath, "MEMORY.md");
51
+ const memoryMdLink = path.join(homeDir, "MEMORY.md");
52
+ await fs.rm(memoryMdLink, { force: true });
53
+ await fs.symlink(memoryMdTarget, memoryMdLink);
54
+
55
+ // Symlink memory/ directory (dangling is ok)
56
+ const memoryDirTarget = path.join(workspacePath, "memory");
57
+ const memoryDirLink = path.join(homeDir, "memory");
58
+ await fs.rm(memoryDirLink, { force: true });
59
+ await fs.symlink(memoryDirTarget, memoryDirLink);
60
+
61
+ const indexContent = `# Memory Files
38
62
 
39
63
  This agent's memory is synced live from an OpenClaw installation.
40
64
 
@@ -43,16 +67,23 @@ This agent's memory is synced live from an OpenClaw installation.
43
67
 
44
68
  Source: ${workspacePath}
45
69
  `;
46
- await fs.writeFile(path.join(homeDir, "+INDEX.md"), indexContent, "utf-8");
70
+ await fs.writeFile(path.join(homeDir, "+INDEX.md"), indexContent, "utf-8");
71
+ }
47
72
 
48
73
  return homeDir;
49
74
  }
50
75
 
51
- async function appendUserMdToGlobalPrompt(userMdContent: string): Promise<void> {
76
+ async function appendUserMdToGlobalPrompt(
77
+ rawUserMd: string,
78
+ llmConfigs: LLMConfiguration[],
79
+ ): Promise<void> {
80
+ const distilled = await distillUserContext(rawUserMd, llmConfigs);
81
+ if (!distilled) return;
82
+
52
83
  const globalPath = configService.getGlobalPath();
53
84
  const existingConfig = await configService.loadTenexConfig(globalPath);
54
85
 
55
- const userSection = `\n## About the User (imported from OpenClaw)\n\n${userMdContent.trim()}`;
86
+ const userSection = `\n## About the User (imported from OpenClaw)\n\n${distilled}`;
56
87
  const existingContent = existingConfig.globalSystemPrompt?.content ?? "";
57
88
  const newContent = existingContent ? `${existingContent}${userSection}` : userSection.trim();
58
89
 
@@ -66,11 +97,15 @@ async function appendUserMdToGlobalPrompt(userMdContent: string): Promise<void>
66
97
  await configService.saveGlobalConfig(newConfig);
67
98
  }
68
99
 
69
- async function importOneAgent(agent: OpenClawAgent, llmConfig: LLMConfiguration): Promise<void> {
100
+ async function importOneAgent(
101
+ agent: OpenClawAgent,
102
+ llmConfigs: LLMConfiguration[],
103
+ options: { noSync?: boolean } = {},
104
+ ): Promise<void> {
70
105
  const tenexModel = convertModelFormat(agent.modelPrimary);
71
106
 
72
107
  console.log(chalk.blue(`\nDistilling identity for agent '${agent.id}'...`));
73
- const identity = await distillAgentIdentity(agent.workspaceFiles, llmConfig);
108
+ const identity = await distillAgentIdentity(agent.workspaceFiles, llmConfigs);
74
109
 
75
110
  const slug = toSlug(identity.name) || agent.id;
76
111
 
@@ -96,21 +131,35 @@ async function importOneAgent(agent: OpenClawAgent, llmConfig: LLMConfiguration)
96
131
  });
97
132
 
98
133
  await agentStorage.saveAgent(storedAgent);
99
- const homeDir = await createHomeDir(pubkey, agent.workspacePath);
134
+ const homeDir = await createHomeDir(pubkey, agent.workspacePath, { noSync: options.noSync });
100
135
 
101
136
  console.log(chalk.green(` ✓ Imported: ${identity.name} (${slug})`));
102
137
  console.log(chalk.gray(` Keypair: ${pubkey}`));
103
138
  console.log(chalk.gray(` Model: ${tenexModel}`));
104
139
  console.log(chalk.gray(` Home dir: ${homeDir}`));
105
- console.log(chalk.gray(` Symlinks: MEMORY.md, memory/`));
140
+ console.log(chalk.gray(` Files: ${options.noSync ? "copied" : "symlinked"} from ${agent.workspacePath}`));
141
+ }
142
+
143
+ function filterAgents(agents: OpenClawAgent[], slugs?: string): OpenClawAgent[] {
144
+ if (!slugs) return agents;
145
+ const allowed = slugs.split(",").map((s) => s.trim());
146
+ return agents.filter((a) => allowed.includes(a.id));
106
147
  }
107
148
 
108
149
  export const openclawImportCommand = new Command("openclaw")
109
150
  .description("Import agents from a local OpenClaw installation")
110
- .action(async () => {
151
+ .option("--dry-run", "Preview what would be imported without making changes")
152
+ .option("--json", "Output as JSON array (implies --dry-run)")
153
+ .option("--no-sync", "Copy workspace files instead of symlinking them")
154
+ .option("--slugs <slugs>", "Comma-separated list of agent IDs to import (default: all)")
155
+ .action(async (options: { dryRun?: boolean; json?: boolean; noSync?: boolean; slugs?: string }) => {
111
156
  try {
112
157
  const stateDir = await detectOpenClawStateDir();
113
158
  if (!stateDir) {
159
+ if (options.json) {
160
+ console.log("[]");
161
+ return;
162
+ }
114
163
  console.error(chalk.red("No OpenClaw installation detected."));
115
164
  console.error(
116
165
  chalk.gray(
@@ -121,23 +170,65 @@ export const openclawImportCommand = new Command("openclaw")
121
170
  return;
122
171
  }
123
172
 
124
- console.log(chalk.blue(`Found OpenClaw installation at: ${stateDir}`));
173
+ const allAgents = await readOpenClawAgents(stateDir);
174
+ const agents = filterAgents(allAgents, options.slugs);
125
175
 
126
- const agents = await readOpenClawAgents(stateDir);
127
- console.log(chalk.blue(`Found ${agents.length} agent(s) to import.`));
176
+ if (agents.length === 0) {
177
+ if (options.json) {
178
+ console.log("[]");
179
+ } else {
180
+ console.log(chalk.yellow("No matching OpenClaw agents found."));
181
+ }
182
+ return;
183
+ }
128
184
 
129
185
  await configService.loadConfig();
186
+ const llmConfigs = configService.getAllLLMConfigs();
187
+
188
+ if (options.dryRun || options.json) {
189
+ const previews = [];
190
+ for (const agent of agents) {
191
+ const identity = await distillAgentIdentity(agent.workspaceFiles, llmConfigs);
192
+ const slug = toSlug(identity.name) || agent.id;
193
+ previews.push({
194
+ id: agent.id,
195
+ slug,
196
+ model: convertModelFormat(agent.modelPrimary),
197
+ ...identity,
198
+ });
199
+ }
200
+
201
+ if (options.json) {
202
+ console.log(JSON.stringify(previews, null, 2));
203
+ } else {
204
+ console.log(chalk.blue(`Would import ${previews.length} agent(s):\n`));
205
+ for (const p of previews) {
206
+ console.log(chalk.green(` ${p.slug}`) + chalk.gray(` (${p.name})`));
207
+ console.log(chalk.gray(` Role: ${p.role}`));
208
+ console.log(chalk.gray(` Model: ${p.model}`));
209
+ console.log(chalk.gray(` Description: ${p.description}`));
210
+ console.log(chalk.gray(` Instructions: ${p.instructions.slice(0, 120)}...`));
211
+ }
212
+ }
213
+ return;
214
+ }
215
+
216
+ if (!options.json) {
217
+ console.log(chalk.blue(`Found OpenClaw installation at: ${stateDir}`));
218
+ console.log(chalk.blue(`Found ${agents.length} agent(s) to import.`));
219
+ }
220
+
130
221
  await agentStorage.initialize();
131
222
 
132
- const llmConfig = configService.getLLMConfig();
133
223
  let userMdProcessed = false;
134
224
 
135
225
  for (const agent of agents) {
136
- await importOneAgent(agent, llmConfig);
226
+ await importOneAgent(agent, llmConfigs, { noSync: options.noSync });
137
227
 
138
228
  if (!userMdProcessed && agent.workspaceFiles.user) {
139
- await appendUserMdToGlobalPrompt(agent.workspaceFiles.user);
140
- console.log(chalk.green(" ✓ USER.md appended to global system prompt"));
229
+ console.log(chalk.blue(`\nDistilling user context from USER.md...`));
230
+ await appendUserMdToGlobalPrompt(agent.workspaceFiles.user, llmConfigs);
231
+ console.log(chalk.green(" ✓ USER.md distilled and appended to global system prompt"));
141
232
  userMdProcessed = true;
142
233
  }
143
234
  }
@@ -1,6 +1,87 @@
1
1
  import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { NDKPrivateKeySigner, NDKEvent } from "@nostr-dev-kit/ndk";
4
+ import { agentStorage } from "@/agents/AgentStorage";
5
+ import { installAgentFromNostr, installAgentFromNostrEvent } from "@/agents/agent-installer";
6
+ import { initNDK } from "@/nostr/ndkClient";
2
7
  import { importCommand } from "./import/index";
3
8
 
9
+ // ─── tenex agent add ─────────────────────────────────────────────────────────
10
+
11
+ async function readStdin(): Promise<string> {
12
+ return new Promise((resolve, reject) => {
13
+ let data = "";
14
+ process.stdin.setEncoding("utf-8");
15
+ process.stdin.on("data", (chunk) => {
16
+ data += chunk;
17
+ });
18
+ process.stdin.on("end", () => resolve(data.trim()));
19
+ process.stdin.on("error", reject);
20
+ });
21
+ }
22
+
23
+ async function addAgent(eventId: string | undefined): Promise<void> {
24
+ await agentStorage.initialize();
25
+
26
+ if (!process.stdin.isTTY) {
27
+ const raw = await readStdin();
28
+ const rawEvent = JSON.parse(raw);
29
+ const event = new NDKEvent(undefined, rawEvent);
30
+ const stored = await installAgentFromNostrEvent(event);
31
+ const pubkey = new NDKPrivateKeySigner(stored.nsec).pubkey;
32
+ console.log(chalk.green(`✓ Installed agent "${stored.name}" (${stored.slug})`));
33
+ console.log(chalk.gray(` pubkey: ${pubkey}`));
34
+ return;
35
+ }
36
+
37
+ if (!eventId) {
38
+ console.error(chalk.red("Error: provide an event ID or pipe event JSON via stdin"));
39
+ process.exit(1);
40
+ }
41
+
42
+ await initNDK();
43
+ const stored = await installAgentFromNostr(eventId);
44
+ const pubkey = new NDKPrivateKeySigner(stored.nsec).pubkey;
45
+ console.log(chalk.green(`✓ Installed agent "${stored.name}" (${stored.slug})`));
46
+ console.log(chalk.gray(` pubkey: ${pubkey}`));
47
+ }
48
+
49
+ // ─── tenex agent assign ─────────────────────────────────────────────────────
50
+
51
+ async function assignAgent(slug: string, projectDTag: string): Promise<void> {
52
+ await agentStorage.initialize();
53
+
54
+ const agent = await agentStorage.getAgentBySlug(slug);
55
+ if (!agent) {
56
+ console.error(chalk.red(`Error: no agent found with slug "${slug}"`));
57
+ process.exit(1);
58
+ }
59
+
60
+ const pubkey = new NDKPrivateKeySigner(agent.nsec).pubkey;
61
+ await agentStorage.addAgentToProject(pubkey, projectDTag);
62
+
63
+ console.log(chalk.green(`✓ Assigned agent "${slug}" to project "${projectDTag}"`));
64
+ }
65
+
66
+ // ─── Command registration ────────────────────────────────────────────────────
67
+
68
+ const addCommand = new Command("add")
69
+ .description("Install an agent from a Nostr event ID or stdin JSON")
70
+ .argument("[event-id]", "Nostr event ID of the agent definition")
71
+ .action(async (eventId: string | undefined) => {
72
+ await addAgent(eventId);
73
+ });
74
+
75
+ const assignCommand = new Command("assign")
76
+ .description("Assign an existing agent to a project")
77
+ .argument("<slug>", "Agent slug")
78
+ .argument("<project-dtag>", "Project d-tag to assign the agent to")
79
+ .action(async (slug: string, projectDTag: string) => {
80
+ await assignAgent(slug, projectDTag);
81
+ });
82
+
4
83
  export const agentCommand = new Command("agent")
5
- .description("Agent management commands")
6
- .addCommand(importCommand);
84
+ .description("Manage TENEX agents")
85
+ .addCommand(importCommand)
86
+ .addCommand(addCommand)
87
+ .addCommand(assignCommand);
@@ -0,0 +1,123 @@
1
+ import chalk from "chalk";
2
+
3
+ // Match Rust TUI's xterm-256 color scheme exactly
4
+ const ACCENT = chalk.ansi256(214); // amber #FFC107
5
+ const INFO = chalk.ansi256(117); // sky blue
6
+ const SELECTED = chalk.ansi256(114); // bright green
7
+
8
+ // Logo palette (xterm-256)
9
+ const DARK = chalk.ansi256(130);
10
+ const MID = chalk.ansi256(172);
11
+ const BRIGHT = chalk.ansi256(220);
12
+ const GLOW = chalk.ansi256(222);
13
+
14
+ /**
15
+ * Print an onboarding step header with step number and color rule.
16
+ *
17
+ * 3/8 AI Providers
18
+ * ─────────────────────────────────────────────
19
+ */
20
+ export function step(number: number, total: number, title: string): void {
21
+ const rule = "─".repeat(45);
22
+ console.log();
23
+ console.log(` ${ACCENT.bold(`${number}/${total}`)} ${ACCENT.bold(title)}`);
24
+ console.log(` ${ACCENT(chalk.dim(rule))}`);
25
+ console.log();
26
+ }
27
+
28
+ /**
29
+ * Print dim context/explanation text, 2-space indent.
30
+ */
31
+ export function context(text: string): void {
32
+ for (const line of text.split("\n")) {
33
+ console.log(` ${chalk.dim(line)}`);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Print a success message: ✓ text
39
+ */
40
+ export function success(text: string): void {
41
+ console.log(` ${chalk.green.bold("✓")} ${text}`);
42
+ }
43
+
44
+ /**
45
+ * Print a hint/tip with a colored arrow.
46
+ */
47
+ export function hint(text: string): void {
48
+ console.log(` ${ACCENT("→")} ${ACCENT(text)}`);
49
+ }
50
+
51
+ /**
52
+ * Print a blank line.
53
+ */
54
+ export function blank(): void {
55
+ console.log();
56
+ }
57
+
58
+ /**
59
+ * Print the welcome banner — stippled Sierpinski triangle
60
+ * with "T E N E X" and tagline to the right.
61
+ * Matches the Rust TUI's logo.rs exactly.
62
+ */
63
+ export function welcome(): void {
64
+ const art: Array<[string, typeof ACCENT]> = [
65
+ [" • ", GLOW],
66
+ [" • • ", BRIGHT],
67
+ [" • • ", ACCENT],
68
+ [" • • • • • ", MID],
69
+ [" • • • • • • ", DARK],
70
+ ];
71
+
72
+ console.log();
73
+ for (let i = 0; i < art.length; i++) {
74
+ const [line, color] = art[i];
75
+ let row = " ";
76
+ for (const ch of line) {
77
+ row += ch === " " ? " " : color.bold(ch);
78
+ }
79
+ if (i === 2) row += " " + ACCENT.bold("T E N E X");
80
+ if (i === 3) row += " " + chalk.bold("Your AI agent team, powered by Nostr.");
81
+ if (i === 4) row += " " + chalk.dim("Let's get everything set up.");
82
+ process.stdout.write(row + "\n");
83
+ }
84
+ console.log();
85
+ }
86
+
87
+ /**
88
+ * Print the final setup summary banner.
89
+ */
90
+ export function setupComplete(): void {
91
+ console.log();
92
+ console.log(` ${ACCENT.bold("▲")} ${ACCENT.bold("Setup complete!")}`);
93
+ console.log();
94
+ }
95
+
96
+ /**
97
+ * Print a summary line for the final recap.
98
+ */
99
+ export function summaryLine(label: string, value: string): void {
100
+ const paddedLabel = `${label}:`.padEnd(16);
101
+ console.log(` ${INFO(paddedLabel)}${value}`);
102
+ }
103
+
104
+ /**
105
+ * Format a provider label with checkmark (for use in choices).
106
+ */
107
+ export function providerCheck(text: string): string {
108
+ return `${SELECTED.bold("[✓]")} ${text}`;
109
+ }
110
+
111
+ /**
112
+ * Format a provider label with unchecked brackets (for use in choices).
113
+ */
114
+ export function providerUncheck(text: string): string {
115
+ return `${chalk.dim("[ ]")} ${text}`;
116
+ }
117
+
118
+ /**
119
+ * Format a "Done" label in amber style.
120
+ */
121
+ export function doneLabel(): string {
122
+ return ACCENT.bold(" Done");
123
+ }
@@ -5,7 +5,8 @@ import {
5
5
  EmbeddingProviderFactory,
6
6
  } from "@/services/rag/EmbeddingProviderFactory";
7
7
  import { config as configService } from "@/services/ConfigService";
8
- import { logger } from "@/utils/logger";
8
+ import { inquirerTheme } from "@/utils/cli-theme";
9
+ import chalk from "chalk";
9
10
  import { Command } from "commander";
10
11
  import inquirer from "inquirer";
11
12
 
@@ -61,9 +62,7 @@ export const embedCommand = new Command("embed")
61
62
  if (scope === "project") {
62
63
  // Check if we're in a TENEX project
63
64
  if (!(await fileSystem.directoryExists(baseDir))) {
64
- logger.error(
65
- "No .tenex directory found. Make sure you're in a TENEX project directory."
66
- );
65
+ console.log(chalk.red("❌ No .tenex directory found. Make sure you're in a TENEX project directory."));
67
66
  process.exitCode = 1;
68
67
  return;
69
68
  }
@@ -81,9 +80,7 @@ export const embedCommand = new Command("embed")
81
80
  // Load configured providers from providers.json
82
81
  const providersConfig = await configService.loadTenexProviders(configService.getGlobalPath());
83
82
  if (Object.keys(providersConfig.providers).length === 0) {
84
- logger.error(
85
- "No providers configured. Run `tenex setup providers` before configuring embeddings."
86
- );
83
+ console.log(chalk.red("❌ No providers configured. Run `tenex setup providers` before configuring embeddings."));
87
84
  process.exitCode = 1;
88
85
  return;
89
86
  }
@@ -112,6 +109,7 @@ export const embedCommand = new Command("embed")
112
109
  message: "Select embedding provider:",
113
110
  choices: providerChoices,
114
111
  default: existing?.provider || "local",
112
+ theme: inquirerTheme,
115
113
  },
116
114
  ]);
117
115
 
@@ -136,6 +134,7 @@ export const embedCommand = new Command("embed")
136
134
  message: `Select ${displayName} embedding model:`,
137
135
  choices: modelChoices,
138
136
  default: existing?.provider === provider ? existing?.model : modelChoices[0]?.value,
137
+ theme: inquirerTheme,
139
138
  },
140
139
  ]);
141
140
 
@@ -145,6 +144,7 @@ export const embedCommand = new Command("embed")
145
144
  type: "input",
146
145
  name: "customModel",
147
146
  message: "Enter model ID:",
147
+ theme: inquirerTheme,
148
148
  validate: (input: string) =>
149
149
  input.trim().length > 0 || "Model ID cannot be empty",
150
150
  },
@@ -160,6 +160,7 @@ export const embedCommand = new Command("embed")
160
160
  type: "select",
161
161
  name: "model",
162
162
  message: "Select local embedding model:",
163
+ theme: inquirerTheme,
163
164
  choices: [
164
165
  {
165
166
  name: "all-MiniLM-L6-v2 (default, fast, good for general use)",
@@ -189,6 +190,7 @@ export const embedCommand = new Command("embed")
189
190
  name: "customModel",
190
191
  message:
191
192
  "Enter HuggingFace model ID (e.g., sentence-transformers/all-MiniLM-L6-v2):",
193
+ theme: inquirerTheme,
192
194
  validate: (input: string) =>
193
195
  input.trim().length > 0 || "Model ID cannot be empty",
194
196
  },
@@ -210,11 +212,9 @@ export const embedCommand = new Command("embed")
210
212
  projectPath: scope === "project" ? projectPath : undefined,
211
213
  });
212
214
 
213
- logger.info(
214
- `✅ Embedding configuration saved to ${scope} config\n` +
215
- ` Provider: ${provider}\n` +
216
- ` Model: ${model}`
217
- );
215
+ console.log("\n" + chalk.green("✓") + chalk.bold(` Embedding configuration saved to ${scope} config`));
216
+ console.log(chalk.gray(` Provider: ${provider}`));
217
+ console.log(chalk.gray(` Model: ${model}`));
218
218
  } catch (error: unknown) {
219
219
  // Handle SIGINT (Ctrl+C) gracefully
220
220
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -222,7 +222,7 @@ export const embedCommand = new Command("embed")
222
222
  return;
223
223
  }
224
224
 
225
- logger.error(`Failed to configure embedding model: ${error}`);
225
+ console.log(chalk.red(`❌ Failed to configure embedding model: ${error}`));
226
226
  process.exitCode = 1;
227
227
  }
228
228
  });
@@ -1,6 +1,8 @@
1
1
  import * as fileSystem from "@/lib/fs";
2
2
  import { config as configService } from "@/services/ConfigService";
3
+ import { amber, amberBold } from "@/utils/cli-theme";
3
4
  import { logger } from "@/utils/logger";
5
+ import chalk from "chalk";
4
6
  import { Command } from "commander";
5
7
  import { spawn } from "node:child_process";
6
8
  import * as fs from "node:fs/promises";
@@ -88,12 +90,12 @@ export const globalSystemPromptCommand = new Command("global-system-prompt")
88
90
  const enabled = existingConfig.globalSystemPrompt?.enabled !== false;
89
91
 
90
92
  if (!content || content.trim().length === 0) {
91
- logger.info("No global system prompt configured.");
93
+ console.log(chalk.gray("No global system prompt configured."));
92
94
  } else {
93
- logger.info(`Global System Prompt (${enabled ? "enabled" : "disabled"}):`);
94
- logger.info("─".repeat(50));
95
+ console.log(amberBold(`Global System Prompt (${enabled ? "enabled" : "disabled"}):`));
96
+ console.log(amber("─".repeat(50)));
95
97
  console.log(content);
96
- logger.info("─".repeat(50));
98
+ console.log(amber("─".repeat(50)));
97
99
  }
98
100
  return;
99
101
  }
@@ -108,7 +110,7 @@ export const globalSystemPromptCommand = new Command("global-system-prompt")
108
110
  },
109
111
  };
110
112
  await configService.saveGlobalConfig(newConfig);
111
- logger.info("Global system prompt disabled.");
113
+ console.log(chalk.green("✓") + chalk.bold(" Global system prompt disabled."));
112
114
  return;
113
115
  }
114
116
 
@@ -122,7 +124,7 @@ export const globalSystemPromptCommand = new Command("global-system-prompt")
122
124
  },
123
125
  };
124
126
  await configService.saveGlobalConfig(newConfig);
125
- logger.info("Global system prompt enabled.");
127
+ console.log(chalk.green("✓") + chalk.bold(" Global system prompt enabled."));
126
128
  return;
127
129
  }
128
130
 
@@ -157,10 +159,8 @@ ${CONTENT_DELIMITER}
157
159
 
158
160
  await fs.writeFile(tempFile, templateContent, "utf-8");
159
161
 
160
- logger.info(
161
- "Opening editor to configure global system prompt...\n" +
162
- `(Using editor: ${getEditor()})\n`
163
- );
162
+ console.log(amberBold("Opening editor to configure global system prompt..."));
163
+ console.log(chalk.gray(`(Using editor: ${getEditor()})\n`));
164
164
 
165
165
  // Open editor and wait for it to close - use try/finally for cleanup
166
166
  let editedContent: string;
@@ -202,13 +202,11 @@ ${CONTENT_DELIMITER}
202
202
  await configService.saveGlobalConfig(newConfig);
203
203
 
204
204
  if (cleanedContent.length === 0) {
205
- logger.info("Global system prompt cleared (no content).");
205
+ console.log(chalk.green("✓") + chalk.bold(" Global system prompt cleared (no content)."));
206
206
  } else {
207
- logger.info(
208
- "Global system prompt saved successfully!\n" +
209
- `Content length: ${cleanedContent.length} characters\n\n` +
210
- "This prompt will be added to all agents' system prompts."
211
- );
207
+ console.log(chalk.green("✓") + chalk.bold(" Global system prompt saved successfully!"));
208
+ console.log(chalk.gray(`Content length: ${cleanedContent.length} characters`));
209
+ console.log(chalk.gray("\nThis prompt will be added to all agents' system prompts."));
212
210
  }
213
211
  } catch (error: unknown) {
214
212
  // Handle SIGINT (Ctrl+C) gracefully
@@ -217,7 +215,7 @@ ${CONTENT_DELIMITER}
217
215
  return;
218
216
  }
219
217
 
220
- logger.error(`Failed to configure global system prompt: ${error}`);
218
+ console.log(chalk.red(`❌ Failed to configure global system prompt: ${error}`));
221
219
  process.exitCode = 1;
222
220
  }
223
221
  });