@j-o-r/hello-dave 0.1.1 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -25
- package/README.md +81 -221
- package/TODO.md +173 -35
- package/agents/agent_creator.js +105 -0
- package/agents/agent_creator.prompt.md +371 -0
- package/agents/ask_agent.js +64 -127
- package/agents/claude_agent.js +68 -0
- package/agents/code_agent.js +55 -135
- package/agents/code_agent.prompt.md +50 -0
- package/agents/echo_agent.js +76 -0
- package/agents/financial_expert.js +75 -0
- package/agents/gpt_agent.js +52 -103
- package/agents/gpt_code.js +81 -0
- package/agents/grok_agent.js +58 -114
- package/agents/minimax_agent.js +92 -0
- package/agents/mureka_agent.js +77 -0
- package/agents/planner_agent.js +172 -0
- package/agents/stability_agent.js +87 -0
- package/agents/test_agent.js +75 -157
- package/agents/weather_agent.js +73 -0
- package/agents/workflow_agent.js +189 -0
- package/bin/dave.js +436 -184
- package/docs/bin-dave.md +85 -35
- package/docs/cdn-ssh.md +100 -0
- package/docs/creating-agents.md +301 -0
- package/docs/creating-toolsets.md +336 -0
- package/docs/docs-organization.md +48 -0
- package/docs/project-overview.md +86 -51
- package/lib/API/elevenlabs.io/music.compose.md +441 -0
- package/lib/API/elevenlabs.io/music.create-composition-plan.md +370 -0
- package/lib/API/elevenlabs.io/music.stream.md +425 -0
- package/lib/API/lalal.ai/lalal.js +445 -0
- package/lib/API/lalal.ai/openapi.json +2614 -0
- package/lib/API/minimax/ImageToolset.js +82 -37
- package/lib/API/minimax/MusicToolset.js +125 -79
- package/lib/API/minimax/VideoToolset.js +170 -167
- package/lib/API/minimax/image.js +5 -1
- package/lib/API/minimax/music.js +210 -23
- package/lib/API/minimax/video.js +242 -53
- package/lib/API/mureka/MusicToolset.js +646 -0
- package/lib/API/mureka/README.md +41 -0
- package/lib/API/mureka/index.js +7 -0
- package/lib/API/mureka/music.js +658 -0
- package/lib/API/openai.com/index.js +7 -0
- package/lib/API/openai.com/{reponses/text.js → responses.js} +64 -18
- package/lib/API/openai.com/video.create.character.md +40 -0
- package/lib/API/openai.com/video.create.md +219 -0
- package/lib/API/openai.com/video.delete.md +44 -0
- package/lib/API/openai.com/video.download.md +31 -0
- package/lib/API/openai.com/video.edit.md +155 -0
- package/lib/API/openai.com/video.extend.md +166 -0
- package/lib/API/openai.com/video.fetch.character.md +43 -0
- package/lib/API/openai.com/video.js +784 -0
- package/lib/API/openai.com/video.list.md +201 -0
- package/lib/API/openai.com/video.remix.md +175 -0
- package/lib/API/openai.com/video.retrieve.md +139 -0
- package/lib/API/openai.com/videoToolset.js +616 -0
- package/lib/API/stability.ai/ImageToolset.js +131 -40
- package/lib/API/stability.ai/MusicToolset.js +79 -47
- package/lib/API/stability.ai/audio.js +63 -131
- package/lib/API/x.ai/chat.responses.md +1040 -0
- package/lib/API/x.ai/image.js +229 -59
- package/lib/API/x.ai/imageToolset.js +376 -0
- package/lib/API/x.ai/index.js +1 -1
- package/lib/API/x.ai/responses.js +9 -18
- package/lib/Agent.js +271 -0
- package/lib/Agent.js.old +284 -0
- package/lib/AgentLauncher.js +593 -0
- package/lib/Cli.js +87 -13
- package/lib/Prompt.js +23 -1
- package/lib/Session.js +5 -4
- package/lib/ToolSet.js +102 -6
- package/lib/agentLoader.js +369 -0
- package/lib/cdn.js +67 -231
- package/lib/{CdnToolset.js → cdnToolset.js} +47 -64
- package/lib/defaultToolsets.js +43 -0
- package/lib/fafs.js +1 -1
- package/lib/genericToolset.js +442 -119
- package/lib/handOffToolset.js +179 -0
- package/lib/index.js +34 -27
- package/lib/toolsetLoader.js +248 -0
- package/package.json +10 -4
- package/types/API/lalal.ai/lalal.d.ts +116 -0
- package/types/API/minimax/image.d.ts +2 -1
- package/types/API/minimax/music.d.ts +189 -26
- package/types/API/minimax/video.d.ts +100 -31
- package/types/API/mureka/index.d.ts +7 -0
- package/types/API/mureka/music.d.ts +472 -0
- package/types/API/openai.com/index.d.ts +7 -0
- package/types/API/openai.com/{reponses/text.d.ts → responses.d.ts} +11 -11
- package/types/API/openai.com/video.d.ts +409 -0
- package/types/API/openai.com/videoToolset.d.ts +24 -0
- package/types/API/stability.ai/audio.d.ts +14 -103
- package/types/API/stability.ai/image.d.ts +2 -2
- package/types/API/x.ai/image.d.ts +138 -26
- package/types/API/x.ai/imageToolset.d.ts +3 -0
- package/types/API/x.ai/index.d.ts +1 -1
- package/types/API/x.ai/responses.d.ts +4 -4
- package/types/Agent.d.ts +123 -0
- package/types/AgentLauncher.d.ts +250 -0
- package/types/Cli.d.ts +28 -8
- package/types/Prompt.d.ts +23 -5
- package/types/Session.d.ts +1 -1
- package/types/ToolSet.d.ts +10 -0
- package/types/agentLoader.d.ts +78 -0
- package/types/cdn.d.ts +15 -90
- package/types/defaultToolsets.d.ts +9 -0
- package/types/fafs.d.ts +1 -1
- package/types/genericToolset.d.ts +1 -1
- package/types/handOffToolset.d.ts +28 -0
- package/types/index.d.ts +19 -17
- package/types/toolsetLoader.d.ts +114 -0
- package/utils/format_log.js +101 -23
- package/utils/launch_agent.js +18 -0
- package/utils/list_sessions.sh +13 -5
- package/utils/search_sessions.sh +65 -29
- package/utils/toolsets.js +33 -0
- package/README.md.bak.1779452127 +0 -240
- package/agents/codeserver.sh +0 -47
- package/agents/daisy_agent.js +0 -173
- package/agents/docs_agent.js +0 -148
- package/agents/memory_agent.js +0 -263
- package/agents/minimax.js +0 -173
- package/agents/npm_agent.js +0 -202
- package/agents/prompt_agent.js +0 -133
- package/agents/readme_agent.js +0 -148
- package/agents/spawn_agent.js +0 -160
- package/agents/stability.js +0 -173
- package/agents/todo_agent.js +0 -175
- package/bin/codeDave +0 -58
- package/docs/agent-dave-websocket-protocol.md +0 -180
- package/docs/agent-manager.md +0 -244
- package/docs/codeserver-pattern.md +0 -191
- package/docs/generic-toolset.md +0 -326
- package/docs/howtos/agent-networking.md +0 -253
- package/docs/howtos/spawn-agents.md.bak +0 -200
- package/docs/howtos/spawn-agents.md.bak_new +0 -200
- package/docs/multi-agent-clusters.md +0 -265
- package/docs/music-toolsets.md +0 -137
- package/docs/path-resolution-best-practices.md +0 -104
- package/docs/plans/minimax-music-generation.md +0 -80
- package/docs/plans/unified-agent-architecture.md +0 -146
- package/docs/plans/websocket-streaming-plan.md.bak +0 -317
- package/docs/prompt/spawn_agent.md +0 -175
- package/docs/prompt/spawn_agent.md.bak +0 -201
- package/docs/prompt/task_clarification_and_documentation.md +0 -35
- package/docs/prompt-class.md +0 -141
- package/docs/todo-archive-infra-2026-04-21.md +0 -15
- package/docs/todo-archive-v0.0.8.md +0 -1
- package/docs/todo-archive-v0.1.0.md +0 -32
- package/docs/todo-archive.md +0 -44
- package/docs/tools-syntax-validation.md +0 -121
- package/docs/toolset.md +0 -164
- package/docs/xai-responses.md +0 -111
- package/docs/xai_collections.md +0 -106
- package/lib/API/x.ai/ImageToolset.js +0 -165
- package/lib/API/x.ai/text.js +0 -415
- package/lib/AgentClient.js +0 -248
- package/lib/AgentManager.js +0 -245
- package/lib/AgentServer.js +0 -404
- package/lib/wsCli.js +0 -287
- package/lib/wsIO.js +0 -90
- package/types/API/x.ai/text.d.ts +0 -286
- package/types/AgentClient.d.ts +0 -109
- package/types/AgentManager.d.ts +0 -100
- package/types/AgentServer.d.ts +0 -89
- package/types/wsCli.d.ts +0 -17
- package/types/wsIO.d.ts +0 -30
- package/utils/test.sh +0 -46
- /package/docs/{suggestions.md → _notes/token-counts.md} +0 -0
- /package/lib/API/openai.com/{reponses/MESSAGES.md → MESSAGES.md} +0 -0
- /package/types/API/{x.ai/ImageToolset.d.ts → mureka/MusicToolset.d.ts} +0 -0
- /package/types/{CdnToolset.d.ts → cdnToolset.d.ts} +0 -0
package/lib/cdn.js
CHANGED
|
@@ -6,10 +6,8 @@
|
|
|
6
6
|
* Designed to be used by any toolset or agent (music, images, documents, etc.).
|
|
7
7
|
*
|
|
8
8
|
* Core capabilities:
|
|
9
|
-
* - Publish single or multiple files into
|
|
10
|
-
* -
|
|
11
|
-
* - List all existing projects on the remote server
|
|
12
|
-
* - Delete entire projects when no longer needed
|
|
9
|
+
* - Publish single or multiple files into public temp folders
|
|
10
|
+
* - Unpublish previously published files from public temp folders
|
|
13
11
|
*
|
|
14
12
|
* All public URLs are built dynamically from the SSH host (assumes a web server
|
|
15
13
|
* is serving the `htdocs` directory publicly).
|
|
@@ -17,25 +15,6 @@
|
|
|
17
15
|
* @requires SSH_EP environment variable (format: ssh://user@host:port)
|
|
18
16
|
* @requires Remote host must have a web server serving the 'htdocs' folder
|
|
19
17
|
*
|
|
20
|
-
* @example
|
|
21
|
-
* import cdn from './cdn.js';
|
|
22
|
-
*
|
|
23
|
-
* // Publish multiple files into one project
|
|
24
|
-
* const result = await cdn.publishToProject(
|
|
25
|
-
* ['/tmp/audio.mp3', '/tmp/cover.jpg'],
|
|
26
|
-
* 'my-song-cover-2026',
|
|
27
|
-
* {
|
|
28
|
-
* description: 'Cover of original song with new lyrics',
|
|
29
|
-
* plan: 'Two-step cover using Minimax voice features'
|
|
30
|
-
* }
|
|
31
|
-
* );
|
|
32
|
-
* console.log(result.public_urls);
|
|
33
|
-
*
|
|
34
|
-
* // List all projects
|
|
35
|
-
* const projects = await cdn.listProjects();
|
|
36
|
-
*
|
|
37
|
-
* // Delete a project
|
|
38
|
-
* await cdn.deleteProject('my-song-cover-2026');
|
|
39
18
|
*/
|
|
40
19
|
|
|
41
20
|
import { SH } from '@j-o-r/sh';
|
|
@@ -53,7 +32,7 @@ import path from 'node:path';
|
|
|
53
32
|
*
|
|
54
33
|
* @example
|
|
55
34
|
* // Required environment variable:
|
|
56
|
-
* // export SSH_EP='ssh://
|
|
35
|
+
* // export SSH_EP='ssh://user@your-cdn.example.com:4301'
|
|
57
36
|
*/
|
|
58
37
|
function getSshConfig() {
|
|
59
38
|
if (!process.env.SSH_EP) {
|
|
@@ -92,6 +71,31 @@ function getSshConfig() {
|
|
|
92
71
|
return { user, host, port, raw: ep };
|
|
93
72
|
}
|
|
94
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Validate a CDN-relative path and normalize it for remote use.
|
|
76
|
+
*
|
|
77
|
+
* Paths are always relative to `htdocs/aaztmp/`. Absolute paths and parent
|
|
78
|
+
* directory traversal are rejected so delete operations cannot escape the CDN
|
|
79
|
+
* temp folder.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} remoteRelativePath - Relative path under htdocs/aaztmp.
|
|
82
|
+
* @returns {string} Safe normalized POSIX-style relative path.
|
|
83
|
+
* @throws {Error} If the path is empty, absolute, or escapes the CDN temp root.
|
|
84
|
+
*/
|
|
85
|
+
function normalizeRemoteRelativePath(remoteRelativePath) {
|
|
86
|
+
if (typeof remoteRelativePath !== 'string' || !remoteRelativePath.trim()) {
|
|
87
|
+
throw new Error('remoteRelativePath must be a non-empty string');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const normalized = path.posix.normalize(remoteRelativePath.replaceAll('\\', '/'));
|
|
91
|
+
|
|
92
|
+
if (normalized === '.' || normalized.startsWith('../') || normalized === '..' || path.posix.isAbsolute(normalized)) {
|
|
93
|
+
throw new Error(`Invalid remoteRelativePath: "${remoteRelativePath}"`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
|
|
95
99
|
/**
|
|
96
100
|
* Ensure a remote directory exists under `htdocs/`.
|
|
97
101
|
* Creates all parent directories as needed (`mkdir -p`).
|
|
@@ -101,7 +105,7 @@ function getSshConfig() {
|
|
|
101
105
|
*/
|
|
102
106
|
async function ensureRemoteDir(remoteRelativeDir) {
|
|
103
107
|
const ssh = getSshConfig();
|
|
104
|
-
const remoteDir = `htdocs/${remoteRelativeDir}`;
|
|
108
|
+
const remoteDir = `htdocs/aaztmp/${remoteRelativeDir}`;
|
|
105
109
|
|
|
106
110
|
console.log(`[cdn] Ensuring remote directory: ${remoteDir}`);
|
|
107
111
|
await SH`ssh -p ${ssh.port} ${ssh.user}@${ssh.host} "mkdir -p ${remoteDir}"`.run();
|
|
@@ -125,198 +129,8 @@ function getOutput(result) {
|
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* Scans the `htdocs/projects/` directory and returns metadata for each project.
|
|
131
|
-
* Useful when starting without prior context about what has already been published.
|
|
132
|
-
*
|
|
133
|
-
* @returns {Promise<Array<{slug: string, url: string, fileCount: number, created?: string}>>}
|
|
134
|
-
* Array of project objects.
|
|
135
|
-
*
|
|
136
|
-
* @throws {Error} If the SSH connection or directory listing fails.
|
|
137
|
-
*
|
|
138
|
-
* @example
|
|
139
|
-
* const projects = await cdn.listProjects();
|
|
140
|
-
* console.log(projects[0].url); // https://drive.duin.xyz/projects/my-project/
|
|
141
|
-
*/
|
|
142
|
-
async function listProjects() {
|
|
143
|
-
const ssh = getSshConfig();
|
|
144
|
-
const projectsBase = 'htdocs/projects';
|
|
145
|
-
|
|
146
|
-
console.log(`[cdn] Listing projects from ${ssh.host}...`);
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
const lsResult = await SH`ssh -p ${ssh.port} ${ssh.user}@${ssh.host} "ls -1 ${projectsBase} 2>/dev/null || echo ''"`.run();
|
|
150
|
-
const output = getOutput(lsResult);
|
|
151
|
-
const slugs = output.trim().split('\n').filter(Boolean);
|
|
152
|
-
|
|
153
|
-
const projects = [];
|
|
154
|
-
|
|
155
|
-
for (const slug of slugs) {
|
|
156
|
-
const projectDir = `${projectsBase}/${slug}`;
|
|
157
|
-
|
|
158
|
-
const countResult = await SH`ssh -p ${ssh.port} ${ssh.user}@${ssh.host} "ls -1 ${projectDir} 2>/dev/null | wc -l"`.run();
|
|
159
|
-
const countOutput = getOutput(countResult);
|
|
160
|
-
const fileCount = parseInt(countOutput.trim(), 10) || 0;
|
|
161
|
-
|
|
162
|
-
let created;
|
|
163
|
-
try {
|
|
164
|
-
const metaResult = await SH`ssh -p ${ssh.port} ${ssh.user}@${ssh.host} "cat ${projectDir}/meta.json 2>/dev/null || echo '{}'"`.run();
|
|
165
|
-
const metaOutput = getOutput(metaResult);
|
|
166
|
-
const meta = JSON.parse(metaOutput.trim());
|
|
167
|
-
created = meta.created;
|
|
168
|
-
} catch (_) {
|
|
169
|
-
// meta.json may not exist yet
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
projects.push({
|
|
173
|
-
slug,
|
|
174
|
-
url: `https://${ssh.host}/projects/${slug}/`,
|
|
175
|
-
fileCount,
|
|
176
|
-
created
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
console.log(`[cdn] Found ${projects.length} project(s)`);
|
|
181
|
-
return projects;
|
|
182
|
-
} catch (err) {
|
|
183
|
-
throw new Error(`Failed to list projects: ${err.message}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Delete an entire project folder from the remote server.
|
|
189
|
-
*
|
|
190
|
-
* Permanently removes `htdocs/projects/<slug>` and all its contents.
|
|
191
|
-
* Use with caution — this operation cannot be undone.
|
|
192
|
-
*
|
|
193
|
-
* @param {string} projectSlug - Project identifier (will be sanitized to kebab-case)
|
|
194
|
-
* @returns {Promise<{deleted: boolean, project: string}>}
|
|
195
|
-
*
|
|
196
|
-
* @example
|
|
197
|
-
* await cdn.deleteProject('my-old-project');
|
|
198
|
-
*/
|
|
199
|
-
async function deleteProject(projectSlug) {
|
|
200
|
-
const ssh = getSshConfig();
|
|
201
|
-
const slug = projectSlug.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
202
|
-
const remoteDir = `htdocs/projects/${slug}`;
|
|
203
|
-
|
|
204
|
-
console.log(`[cdn] Deleting project: ${remoteDir}`);
|
|
205
|
-
await SH`ssh -p ${ssh.port} ${ssh.user}@${ssh.host} "rm -rf ${remoteDir}"`.run();
|
|
206
|
-
|
|
207
|
-
console.log(`[cdn] ✅ Project deleted: ${slug}`);
|
|
208
|
-
return { deleted: true, project: slug };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Ensure the remote project directory structure exists.
|
|
213
|
-
* Creates `htdocs/projects/<slug>` (and the `generated` subfolder).
|
|
214
|
-
*
|
|
215
|
-
* @param {string} projectSlug - Project identifier (will be sanitized)
|
|
216
|
-
* @returns {Promise<string>} The remote directory path under htdocs
|
|
217
|
-
*/
|
|
218
|
-
async function ensureProjectStructure(projectSlug) {
|
|
219
|
-
const slug = projectSlug.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
220
|
-
const remoteRelative = `projects/${slug}`;
|
|
221
|
-
return await ensureRemoteDir(remoteRelative);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Internal helper to upload a single file via SCP.
|
|
226
|
-
*
|
|
227
|
-
* @param {string} localPath - Local file path
|
|
228
|
-
* @param {string} remoteFilePath - Full remote path (including htdocs)
|
|
229
|
-
* @param {object} ssh - SSH config object from getSshConfig()
|
|
230
|
-
*/
|
|
231
|
-
async function uploadSingleFile(localPath, remoteFilePath, ssh) {
|
|
232
|
-
const scpTarget = `${ssh.user}@${ssh.host}:${remoteFilePath}`;
|
|
233
|
-
console.log(`[cdn] Uploading: ${path.basename(localPath)} → ${remoteFilePath}`);
|
|
234
|
-
await SH`scp -P ${ssh.port} ${localPath} ${scpTarget}`.run();
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Publish one or more local files into an organized project folder on the public CDN.
|
|
239
|
-
*
|
|
240
|
-
* Supports both single file (string) and multiple files (array).
|
|
241
|
-
* Automatically creates the project directory and generates `meta.json`.
|
|
242
|
-
* Optional `description.md` and `plan.md` files can be created.
|
|
243
|
-
*
|
|
244
|
-
* @param {string|string[]} localPaths - Single path or array of local file paths
|
|
245
|
-
* @param {string} projectSlug - Project identifier (sanitized to kebab-case)
|
|
246
|
-
* @param {Object} [options]
|
|
247
|
-
* @param {string} [options.description] - Human-readable project description
|
|
248
|
-
* @param {string} [options.plan] - Planning notes or step-by-step evolution
|
|
249
|
-
* @param {string} [options.filename] - Custom filename (only used when uploading a single file)
|
|
250
|
-
*
|
|
251
|
-
* @returns {Promise<{public_urls: string[], project_folder: string, meta: object}>}
|
|
252
|
-
*
|
|
253
|
-
* @example
|
|
254
|
-
* // Multiple files
|
|
255
|
-
* const result = await cdn.publishToProject(
|
|
256
|
-
* ['audio.mp3', 'cover.jpg'],
|
|
257
|
-
* 'my-project',
|
|
258
|
-
* { description: 'Music cover project' }
|
|
259
|
-
* );
|
|
260
|
-
*
|
|
261
|
-
* // Single file with custom name
|
|
262
|
-
* await cdn.publishToProject('/tmp/file.mp3', 'my-project', { filename: 'final.mp3' });
|
|
263
|
-
*/
|
|
264
|
-
async function publishToProject(localPaths, projectSlug, options = {}) {
|
|
265
|
-
const ssh = getSshConfig();
|
|
266
|
-
const slug = projectSlug.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
267
|
-
const remoteDir = await ensureProjectStructure(slug);
|
|
268
|
-
|
|
269
|
-
const paths = Array.isArray(localPaths) ? localPaths : [localPaths];
|
|
270
|
-
const uploadedFiles = [];
|
|
271
|
-
|
|
272
|
-
for (const localPath of paths) {
|
|
273
|
-
const filename = options.filename && paths.length === 1
|
|
274
|
-
? options.filename
|
|
275
|
-
: path.basename(localPath);
|
|
276
|
-
|
|
277
|
-
const remoteFile = `${remoteDir}/${filename}`;
|
|
278
|
-
await uploadSingleFile(localPath, remoteFile, ssh);
|
|
279
|
-
uploadedFiles.push(filename);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const meta = {
|
|
283
|
-
project: slug,
|
|
284
|
-
created: new Date().toISOString(),
|
|
285
|
-
files: uploadedFiles,
|
|
286
|
-
source: 'cdn.js',
|
|
287
|
-
ssh_ep: ssh.raw
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
console.log(`[cdn] Writing meta.json`);
|
|
291
|
-
await SH`ssh -p ${ssh.port} ${ssh.user}@${ssh.host} "cat > ${remoteDir}/meta.json << 'EOF'
|
|
292
|
-
${JSON.stringify(meta, null, 2)}
|
|
293
|
-
EOF"`.run();
|
|
294
|
-
|
|
295
|
-
if (options.description) {
|
|
296
|
-
await SH`ssh -p ${ssh.port} ${ssh.user}@${ssh.host} "cat > ${remoteDir}/description.md << 'EOF'
|
|
297
|
-
${options.description}
|
|
298
|
-
EOF"`.run();
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (options.plan) {
|
|
302
|
-
await SH`ssh -p ${ssh.port} ${ssh.user}@${ssh.host} "cat > ${remoteDir}/plan.md << 'EOF'
|
|
303
|
-
${options.plan}
|
|
304
|
-
EOF"`.run();
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const publicUrls = uploadedFiles.map(f => `https://${ssh.host}/projects/${slug}/${f}`);
|
|
308
|
-
const projectFolder = `https://${ssh.host}/projects/${slug}/`;
|
|
309
|
-
|
|
310
|
-
return {
|
|
311
|
-
public_urls: publicUrls,
|
|
312
|
-
project_folder: projectFolder,
|
|
313
|
-
meta
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Publish a single local file to an arbitrary location on the public CDN.
|
|
319
|
-
*
|
|
132
|
+
* Publish a single local file to an arbitrary temp location on the public CDN.
|
|
133
|
+
* this folder is cleared regulary.
|
|
320
134
|
* Automatically creates any missing parent directories.
|
|
321
135
|
* Returns a direct public HTTPS URL.
|
|
322
136
|
*
|
|
@@ -334,40 +148,62 @@ EOF"`.run();
|
|
|
334
148
|
*/
|
|
335
149
|
async function publishFile(localPath, remoteRelativePath) {
|
|
336
150
|
const ssh = getSshConfig();
|
|
337
|
-
const
|
|
151
|
+
const safeRemoteRelativePath = normalizeRemoteRelativePath(remoteRelativePath);
|
|
152
|
+
const remoteDir = path.dirname(safeRemoteRelativePath);
|
|
338
153
|
|
|
339
154
|
if (remoteDir && remoteDir !== '.') {
|
|
340
155
|
await ensureRemoteDir(remoteDir);
|
|
341
156
|
}
|
|
342
157
|
|
|
343
|
-
const remoteTarget = `${ssh.user}@${ssh.host}:htdocs/${
|
|
158
|
+
const remoteTarget = `${ssh.user}@${ssh.host}:htdocs/aaztmp/${safeRemoteRelativePath}`;
|
|
344
159
|
|
|
345
160
|
console.log(`[cdn] Uploading via SCP: ${localPath} → ${remoteTarget}`);
|
|
346
161
|
await SH`scp -P ${ssh.port} ${localPath} ${remoteTarget}`.run();
|
|
347
162
|
console.log(`[cdn] ✅ Upload successful`);
|
|
348
163
|
|
|
349
164
|
return {
|
|
350
|
-
public_url: `https://${ssh.host}/${
|
|
165
|
+
public_url: `https://${ssh.host}/aaztmp/${safeRemoteRelativePath}`,
|
|
351
166
|
remote_path: remoteTarget
|
|
352
167
|
};
|
|
353
168
|
}
|
|
354
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Delete a previously published file from the public CDN temp folder.
|
|
172
|
+
*
|
|
173
|
+
* The target path is resolved relative to `htdocs/aaztmp/`. Missing files are
|
|
174
|
+
* treated as already unpublished because `rm -f` is used.
|
|
175
|
+
*
|
|
176
|
+
* @param {string} remoteRelativePath - File path under htdocs/aaztmp (e.g. "tmp/quick-reference.mp3")
|
|
177
|
+
* @returns {Promise<{public_url: string, remote_path: string, unpublished: boolean}>}
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* const result = await cdn.unpublishFile('tmp/quick-reference.mp3');
|
|
181
|
+
* console.log(result.unpublished);
|
|
182
|
+
*/
|
|
183
|
+
async function unpublishFile(remoteRelativePath) {
|
|
184
|
+
const ssh = getSshConfig();
|
|
185
|
+
const safeRemoteRelativePath = normalizeRemoteRelativePath(remoteRelativePath);
|
|
186
|
+
const remoteFile = `htdocs/aaztmp/${safeRemoteRelativePath}`;
|
|
187
|
+
|
|
188
|
+
console.log(`[cdn] Deleting remote file: ${remoteFile}`);
|
|
189
|
+
await SH`ssh -p ${ssh.port} ${ssh.user}@${ssh.host} rm -f ${remoteFile}`.run();
|
|
190
|
+
console.log('[cdn] ✅ Delete successful');
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
public_url: `https://${ssh.host}/aaztmp/${safeRemoteRelativePath}`,
|
|
194
|
+
remote_path: `${ssh.user}@${ssh.host}:${remoteFile}`,
|
|
195
|
+
unpublished: true
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
355
199
|
export {
|
|
356
200
|
getSshConfig,
|
|
357
|
-
ensureRemoteDir,
|
|
358
|
-
ensureProjectStructure,
|
|
359
|
-
listProjects,
|
|
360
|
-
deleteProject,
|
|
361
201
|
publishFile,
|
|
362
|
-
|
|
202
|
+
unpublishFile,
|
|
363
203
|
};
|
|
364
204
|
|
|
365
205
|
export default {
|
|
366
206
|
getSshConfig,
|
|
367
|
-
ensureRemoteDir,
|
|
368
|
-
ensureProjectStructure,
|
|
369
|
-
listProjects,
|
|
370
|
-
deleteProject,
|
|
371
207
|
publishFile,
|
|
372
|
-
|
|
373
|
-
};
|
|
208
|
+
unpublishFile,
|
|
209
|
+
};
|
|
@@ -21,6 +21,17 @@ import cdn from './cdn.js';
|
|
|
21
21
|
|
|
22
22
|
const tools = new ToolSet('auto');
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Stringify a structured tool response with stable formatting.
|
|
26
|
+
* Structured CDN responses preserve exact public URLs and remote paths so the
|
|
27
|
+
* assistant can repeat them in normal conversation before raw tool-call history
|
|
28
|
+
* is pruned.
|
|
29
|
+
*
|
|
30
|
+
* @param {Record<string, *>} payload - JSON-serializable tool response payload.
|
|
31
|
+
* @returns {string} Formatted JSON function response.
|
|
32
|
+
*/
|
|
33
|
+
const json = (payload) => JSON.stringify(payload, null, 2);
|
|
34
|
+
|
|
24
35
|
/**
|
|
25
36
|
* Single source of truth for cleaning over-escaped strings from LLMs.
|
|
26
37
|
* (Duplicated here for self-containment of the CDN toolset)
|
|
@@ -89,32 +100,21 @@ const guessOverEscaping = (s) => {
|
|
|
89
100
|
* to a remote web server defined by the SSH_EP environment variable).
|
|
90
101
|
*
|
|
91
102
|
* Logical workflow for LLMs:
|
|
92
|
-
* 1.
|
|
93
|
-
* 2.
|
|
94
|
-
* 3. publishToProject() → Organize one or multiple files into a project
|
|
95
|
-
* 4. deleteProject() → Clean up when no longer needed
|
|
103
|
+
* 1. publishFile() → Quick single-file exposure
|
|
104
|
+
* 2. unpublishFile() → Delete a previously published file
|
|
96
105
|
*
|
|
97
106
|
* Requirements:
|
|
98
107
|
* - SSH_EP environment variable must be set
|
|
99
108
|
* - Remote host serves the 'htdocs' folder publicly over HTTPS
|
|
100
109
|
*/
|
|
101
110
|
|
|
102
|
-
tools.add(
|
|
103
|
-
'list_projects',
|
|
104
|
-
'List all existing projects published on the remote public server.\n' +
|
|
105
|
-
'Returns slug, public URL, file count, and creation date for each project.\n' +
|
|
106
|
-
'Use this first when you have no prior context about what has already been published.',
|
|
107
|
-
{ type: 'object', properties: {} },
|
|
108
|
-
async () => {
|
|
109
|
-
return await cdn.listProjects();
|
|
110
|
-
}
|
|
111
|
-
);
|
|
112
111
|
|
|
113
112
|
tools.add(
|
|
114
113
|
'publish_file',
|
|
115
|
-
'Publish a single local file to a public HTTPS URL via SSH.\n' +
|
|
114
|
+
'Publish a single local file to a public temp folder HTTPS URL via SSH.\n' +
|
|
116
115
|
'Automatically creates any missing parent directories.\n' +
|
|
117
|
-
'Best for quick one-off references or when you only need a direct link to one file
|
|
116
|
+
'Best for quick one-off references or when you only need a direct link to one file.\n' +
|
|
117
|
+
'Returns structured JSON with public_url and remote_path. After success, include the exact public_url in your assistant response so it remains available after old tool calls are pruned.',
|
|
118
118
|
{
|
|
119
119
|
type: 'object',
|
|
120
120
|
properties: {
|
|
@@ -126,66 +126,49 @@ tools.add(
|
|
|
126
126
|
async (params) => {
|
|
127
127
|
let local = guessOverEscaping(params.localPath).trim();
|
|
128
128
|
let remote = guessOverEscaping(params.remoteRelativePath).trim();
|
|
129
|
-
|
|
129
|
+
const result = await cdn.publishFile(local, remote);
|
|
130
|
+
return json({
|
|
131
|
+
tool: 'publish_file',
|
|
132
|
+
success: true,
|
|
133
|
+
localPath: local,
|
|
134
|
+
remoteRelativePath: remote,
|
|
135
|
+
public_url: result.public_url,
|
|
136
|
+
publicUrl: result.public_url,
|
|
137
|
+
remote_path: result.remote_path,
|
|
138
|
+
note: 'Assistant: tell the user the exact public_url. Use public_url as the durable reference for later image_url/audio_url/file URL inputs.'
|
|
139
|
+
});
|
|
130
140
|
}
|
|
131
141
|
);
|
|
132
142
|
|
|
133
|
-
tools.add(
|
|
134
|
-
'publish_to_project',
|
|
135
|
-
'Publish one or multiple local files into a structured public project folder.\n' +
|
|
136
|
-
'Creates the project directory, uploads all files, and generates meta.json.\n' +
|
|
137
|
-
'Optional description.md and plan.md can be added for context.\n' +
|
|
138
|
-
'Returns an array of public URLs plus the project folder URL.\n' +
|
|
139
|
-
'Recommended when you want to keep related files together for remote AI inspection.',
|
|
140
|
-
{
|
|
141
|
-
type: 'object',
|
|
142
|
-
properties: {
|
|
143
|
-
localPaths: {
|
|
144
|
-
oneOf: [
|
|
145
|
-
{ type: 'string', description: 'Single file path' },
|
|
146
|
-
{ type: 'array', items: { type: 'string' }, description: 'Array of file paths' }
|
|
147
|
-
]
|
|
148
|
-
},
|
|
149
|
-
projectSlug: { type: 'string', description: 'Project folder name (will be sanitized to kebab-case)' },
|
|
150
|
-
options: {
|
|
151
|
-
type: 'object',
|
|
152
|
-
properties: {
|
|
153
|
-
description: { type: 'string', description: 'Human-readable description of the project' },
|
|
154
|
-
plan: { type: 'string', description: 'Planning notes or step-by-step evolution' },
|
|
155
|
-
filename: { type: 'string', description: 'Custom filename (only used for single-file uploads)' }
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
required: ['localPaths', 'projectSlug']
|
|
160
|
-
},
|
|
161
|
-
async (params) => {
|
|
162
|
-
let localPaths = guessOverEscaping(params.localPaths);
|
|
163
|
-
if (typeof localPaths === 'string') localPaths = [localPaths];
|
|
164
|
-
|
|
165
|
-
let projectSlug = guessOverEscaping(params.projectSlug).trim();
|
|
166
|
-
let options = params.options || {};
|
|
167
|
-
if (typeof options === 'string') options = JSON.parse(guessOverEscaping(options));
|
|
168
|
-
|
|
169
|
-
return await cdn.publishToProject(localPaths, projectSlug, options);
|
|
170
|
-
}
|
|
171
|
-
);
|
|
172
143
|
|
|
173
144
|
tools.add(
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
'
|
|
145
|
+
'unpublish_file',
|
|
146
|
+
'Delete a previously published file from the public temp folder via SSH.\n' +
|
|
147
|
+
'The path is relative to htdocs/aaztmp and must match the remoteRelativePath used for publish_file.\n' +
|
|
148
|
+
'Missing files are treated as already unpublished.\n' +
|
|
149
|
+
'Returns structured JSON with public_url, remote_path, and unpublished status. After success, tell the user the exact public_url that was removed.',
|
|
178
150
|
{
|
|
179
151
|
type: 'object',
|
|
180
152
|
properties: {
|
|
181
|
-
|
|
153
|
+
remoteRelativePath: { type: 'string', description: 'File path under htdocs/aaztmp to delete (e.g. "tmp/reference.mp3" or "projects/my-project/audio.wav")' }
|
|
182
154
|
},
|
|
183
|
-
required: ['
|
|
155
|
+
required: ['remoteRelativePath']
|
|
184
156
|
},
|
|
185
157
|
async (params) => {
|
|
186
|
-
let
|
|
187
|
-
|
|
158
|
+
let remote = guessOverEscaping(params.remoteRelativePath).trim();
|
|
159
|
+
const result = await cdn.unpublishFile(remote);
|
|
160
|
+
return json({
|
|
161
|
+
tool: 'unpublish_file',
|
|
162
|
+
success: true,
|
|
163
|
+
remoteRelativePath: remote,
|
|
164
|
+
public_url: result.public_url,
|
|
165
|
+
publicUrl: result.public_url,
|
|
166
|
+
remote_path: result.remote_path,
|
|
167
|
+
unpublished: result.unpublished,
|
|
168
|
+
note: 'Assistant: tell the user the exact public_url that was removed and do not use it as a durable reference anymore.'
|
|
169
|
+
});
|
|
188
170
|
}
|
|
189
171
|
);
|
|
190
172
|
|
|
173
|
+
|
|
191
174
|
export default tools;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import minimax from './API/minimax/index.js';
|
|
2
|
+
import mureka from './API/mureka/index.js';
|
|
3
|
+
import stability from './API/stability.ai/index.js';
|
|
4
|
+
import xaitools from './API/x.ai/index.js';
|
|
5
|
+
import openaitools from './API/openai.com/index.js';
|
|
6
|
+
import genTools from './genericToolset.js';
|
|
7
|
+
import cdnTools from './cdnToolset.js';
|
|
8
|
+
import handoffTools, { createHandOffToolset } from './handOffToolset.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default framework-owned toolset tree exposed as API.toolset.
|
|
12
|
+
*
|
|
13
|
+
* @type {Record<string, Record<string, any>>}
|
|
14
|
+
*/
|
|
15
|
+
const defaultToolsets = {
|
|
16
|
+
minimax: {
|
|
17
|
+
music: minimax.musicToolset,
|
|
18
|
+
image: minimax.imageToolset,
|
|
19
|
+
video: minimax.videoToolset
|
|
20
|
+
},
|
|
21
|
+
stability: {
|
|
22
|
+
image: stability.imageToolset,
|
|
23
|
+
music: stability.musicToolset
|
|
24
|
+
},
|
|
25
|
+
mureka: {
|
|
26
|
+
music: mureka.musicToolset
|
|
27
|
+
},
|
|
28
|
+
xai: {
|
|
29
|
+
image: xaitools.imageToolset
|
|
30
|
+
},
|
|
31
|
+
openai: {
|
|
32
|
+
video: openaitools.videoToolset
|
|
33
|
+
},
|
|
34
|
+
generic: {
|
|
35
|
+
bash: genTools,
|
|
36
|
+
cdn: cdnTools,
|
|
37
|
+
handoff: handoffTools,
|
|
38
|
+
createHandoff: createHandOffToolset
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export { createHandOffToolset };
|
|
43
|
+
export default defaultToolsets;
|
package/lib/fafs.js
CHANGED
|
@@ -79,7 +79,7 @@ async function systemInfo() {
|
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Gathers comprehensive environment information: user name, fresh system details, IP-based geolocation,
|
|
82
|
-
* and normalized current working directory. Results are cached in ~/.cache/hello-dave/env for 31 days,
|
|
82
|
+
* and normalized current working directory. Results are cached in ~/.cache/@j-o-r/hello-dave/env for 31 days,
|
|
83
83
|
* refreshed on expiry. On cache hit, updates with fresh system info and cwd.
|
|
84
84
|
*
|
|
85
85
|
* @returns {Promise<EnvironmentInfo>} The environment information object.
|