@byted-las/contextlake-openclaw 1.0.8 → 1.0.11

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/dist/index.js CHANGED
@@ -4,7 +4,7 @@ const commands_1 = require("./src/commands");
4
4
  const plugin = {
5
5
  id: 'contextlake-openclaw',
6
6
  name: 'ContextLake',
7
- version: '1.0.2',
7
+ version: '1.0.11',
8
8
  description: 'A lightweight knowledge base plugin for OpenClaw using LanceDB and TOS, with data profiling support',
9
9
  configSchema: {
10
10
  type: 'object',
@@ -1,5 +1,8 @@
1
1
  import { ContextLakeConfig } from '../utils/config';
2
2
  export declare function getCliCommands(pluginConfig: ContextLakeConfig, logger: any): {
3
+ listS3ObjectsAction: (url: string, options: any) => Promise<void>;
4
+ readS3ObjectAction: (url: string, options: any) => Promise<void>;
5
+ generatePresignedUrlAction: (url: string, options: any) => Promise<void>;
3
6
  searchAction: (query: any, options: any) => Promise<void>;
4
7
  listAction: (options: any) => Promise<void>;
5
8
  deleteAction: (options: any) => Promise<void>;
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCliCommands = getCliCommands;
4
4
  const retrieve_1 = require("../lib/actions/retrieve");
5
5
  const manage_1 = require("../lib/actions/manage");
6
+ const las_tools_1 = require("../lib/actions/las-tools");
7
+ const s3_tools_1 = require("../lib/actions/s3-tools");
6
8
  const credentials_1 = require("../utils/credentials");
7
9
  function parseOptionalInt(value, fallback) {
8
10
  const parsed = Number.parseInt(String(value), 10);
@@ -30,7 +32,80 @@ function parseMetadata(metadata) {
30
32
  }
31
33
  }
32
34
  function getCliCommands(pluginConfig, logger) {
35
+ const lasTools = (0, las_tools_1.getLasTools)(pluginConfig, logger);
36
+ const lasActions = {};
37
+ for (const tool of lasTools) {
38
+ lasActions[`${tool.name}Action`] = async (dataStr) => {
39
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI ${tool.name} started`);
40
+ try {
41
+ let data = {};
42
+ try {
43
+ data = JSON.parse(dataStr);
44
+ }
45
+ catch (e) {
46
+ // if it's not valid JSON and the tool accepts a simple string/url, try to handle it.
47
+ // But LAS tools typically require a data object.
48
+ data = { url: dataStr };
49
+ }
50
+ let params = tool.name === 'las_bare_image_text_embedding'
51
+ ? data
52
+ : { data };
53
+ const result = await tool.execute('cli', params);
54
+ // eslint-disable-next-line no-console
55
+ console.log(JSON.stringify(result, null, 2));
56
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI ${tool.name} success`);
57
+ }
58
+ catch (e) {
59
+ console.error('Error:', e.message);
60
+ logger.error(`[${new Date().toISOString()}] [ContextLake] CLI ${tool.name} failed`, { error: e.message, stack: e.stack });
61
+ }
62
+ };
63
+ }
33
64
  return {
65
+ ...lasActions,
66
+ listS3ObjectsAction: async (url, options) => {
67
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI list-s3-objects started`, { url, options });
68
+ try {
69
+ const result = await (0, s3_tools_1.listS3Objects)({ url }, options.prefix || '', parseOptionalInt(options.maxKeys, 1000), options.continuationToken);
70
+ // eslint-disable-next-line no-console
71
+ console.log(JSON.stringify(result, null, 2));
72
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI list-s3-objects success`);
73
+ }
74
+ catch (e) {
75
+ console.error('Error:', e.message);
76
+ logger.error(`[${new Date().toISOString()}] [ContextLake] CLI list-s3-objects failed`, { error: e.message, stack: e.stack });
77
+ }
78
+ },
79
+ readS3ObjectAction: async (url, options) => {
80
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI read-s3-object started`, { url, options });
81
+ try {
82
+ const parsedUrl = new URL(url);
83
+ const key = parsedUrl.pathname.replace(/^\//, '');
84
+ const result = await (0, s3_tools_1.readS3Object)({ url }, key, options.maxBytes ? parseOptionalInt(options.maxBytes, 0) : undefined);
85
+ // eslint-disable-next-line no-console
86
+ console.log(result.toString('utf-8'));
87
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI read-s3-object success`);
88
+ }
89
+ catch (e) {
90
+ console.error('Error:', e.message);
91
+ logger.error(`[${new Date().toISOString()}] [ContextLake] CLI read-s3-object failed`, { error: e.message, stack: e.stack });
92
+ }
93
+ },
94
+ generatePresignedUrlAction: async (url, options) => {
95
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI generate-presigned-url started`, { url, options });
96
+ try {
97
+ const parsedUrl = new URL(url);
98
+ const key = parsedUrl.pathname.replace(/^\//, '');
99
+ const result = await (0, s3_tools_1.getPresignedUrl)({ url }, key, parseOptionalInt(options.expiresIn, 3600));
100
+ // eslint-disable-next-line no-console
101
+ console.log(result);
102
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI generate-presigned-url success`);
103
+ }
104
+ catch (e) {
105
+ console.error('Error:', e.message);
106
+ logger.error(`[${new Date().toISOString()}] [ContextLake] CLI generate-presigned-url failed`, { error: e.message, stack: e.stack });
107
+ }
108
+ },
34
109
  searchAction: async (query, options) => {
35
110
  logger.info(`[${new Date().toISOString()}] [ContextLake] CLI search started`, { query, options });
36
111
  try {
@@ -67,6 +67,30 @@ function registerAll(ctx, logger) {
67
67
  contextlake.command('onboard')
68
68
  .description('Configure credentials for ContextLake')
69
69
  .action(commands.onboardAction);
70
+ // S3 Tools
71
+ contextlake.command('list-s3-objects <url>')
72
+ .description('List objects in an S3-compatible bucket or local directory')
73
+ .option('--prefix <string>', 'Prefix to list')
74
+ .option('--max-keys <number>', 'Maximum number of keys to return', '1000')
75
+ .option('--continuation-token <string>', 'Continuation token for pagination')
76
+ .action(commands.listS3ObjectsAction);
77
+ contextlake.command('read-s3-object <url>')
78
+ .description('Read the contents of an S3 object')
79
+ .option('--max-bytes <number>', 'Maximum number of bytes to read')
80
+ .action(commands.readS3ObjectAction);
81
+ contextlake.command('generate-presigned-url <url>')
82
+ .description('Generate a presigned HTTP URL for an S3/TOS object')
83
+ .option('--expires-in <number>', 'Expiration time in seconds', '3600')
84
+ .action(commands.generatePresignedUrlAction);
85
+ // LAS Tools
86
+ const tools = (0, tools_1.getAgentTools)(pluginConfig, logger);
87
+ const lasTools = tools.lasTools;
88
+ for (const tool of lasTools) {
89
+ const cmdName = tool.name.replace(/_/g, '-');
90
+ contextlake.command(`${cmdName} <data>`)
91
+ .description(tool.label)
92
+ .action(commands[`${tool.name}Action`]);
93
+ }
70
94
  }, { commands: ['contextlake'] });
71
95
  logger.info(`[${new Date().toISOString()}] [ContextLake] CLI commands registered`);
72
96
  }
@@ -105,6 +129,34 @@ function registerAll(ctx, logger) {
105
129
  acceptsArgs: false,
106
130
  handler: slashCommands.listDatasourceHandler
107
131
  });
132
+ ctx.registerCommand({
133
+ name: 'contextlake-list-s3-objects',
134
+ description: 'List objects in an S3-compatible bucket or local directory',
135
+ acceptsArgs: true,
136
+ handler: slashCommands.listS3ObjectsHandler
137
+ });
138
+ ctx.registerCommand({
139
+ name: 'contextlake-read-s3-object',
140
+ description: 'Read the contents of an S3 object',
141
+ acceptsArgs: true,
142
+ handler: slashCommands.readS3ObjectHandler
143
+ });
144
+ ctx.registerCommand({
145
+ name: 'contextlake-generate-presigned-url',
146
+ description: 'Generate a presigned HTTP URL for an S3/TOS object',
147
+ acceptsArgs: true,
148
+ handler: slashCommands.generatePresignedUrlHandler
149
+ });
150
+ const tools = (0, tools_1.getAgentTools)(pluginConfig, logger);
151
+ for (const tool of tools.lasTools) {
152
+ const cmdName = tool.name.replace(/_/g, '-');
153
+ ctx.registerCommand({
154
+ name: `contextlake-${cmdName}`,
155
+ description: tool.label,
156
+ acceptsArgs: true,
157
+ handler: slashCommands[`${tool.name}Handler`]
158
+ });
159
+ }
108
160
  logger.info(`[${new Date().toISOString()}] [ContextLake] Slash commands registered`);
109
161
  }
110
162
  catch (error) {
@@ -1,5 +1,14 @@
1
1
  import { ContextLakeConfig } from '../utils/config';
2
2
  export declare function getSlashCommands(pluginConfig: ContextLakeConfig, logger: any): {
3
+ listS3ObjectsHandler: (commandCtx: any) => Promise<{
4
+ text: string;
5
+ }>;
6
+ readS3ObjectHandler: (commandCtx: any) => Promise<{
7
+ text: string;
8
+ }>;
9
+ generatePresignedUrlHandler: (commandCtx: any) => Promise<{
10
+ text: string;
11
+ }>;
3
12
  listHandler: (commandCtx: any) => Promise<{
4
13
  text: string;
5
14
  }>;
@@ -4,8 +4,105 @@ exports.getSlashCommands = getSlashCommands;
4
4
  const retrieve_1 = require("../lib/actions/retrieve");
5
5
  const manage_1 = require("../lib/actions/manage");
6
6
  const profiler_1 = require("../lib/actions/profiler");
7
+ const las_tools_1 = require("../lib/actions/las-tools");
8
+ const s3_tools_1 = require("../lib/actions/s3-tools");
9
+ function parseOptionalInt(value, fallback) {
10
+ const parsed = Number.parseInt(String(value), 10);
11
+ return Number.isFinite(parsed) ? parsed : fallback;
12
+ }
7
13
  function getSlashCommands(pluginConfig, logger) {
14
+ const lasTools = (0, las_tools_1.getLasTools)(pluginConfig, logger);
15
+ const lasHandlers = {};
16
+ for (const tool of lasTools) {
17
+ lasHandlers[`${tool.name}Handler`] = async (commandCtx) => {
18
+ const rawArgs = commandCtx.args || "";
19
+ logger.info(`[${new Date().toISOString()}] [ContextLake] Slash command ${tool.name} started`, { args: rawArgs });
20
+ try {
21
+ if (!rawArgs.trim()) {
22
+ return { text: `**Error:** Missing arguments. Please provide JSON data or URL.` };
23
+ }
24
+ let data = {};
25
+ try {
26
+ data = JSON.parse(rawArgs);
27
+ }
28
+ catch (e) {
29
+ data = { url: rawArgs.trim() };
30
+ }
31
+ let params = tool.name === 'las_bare_image_text_embedding'
32
+ ? data
33
+ : { data };
34
+ const result = await tool.execute('slash', params);
35
+ logger.info(`[${new Date().toISOString()}] [ContextLake] Slash command ${tool.name} completed`);
36
+ return { text: `**${tool.label} Results:**\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`` };
37
+ }
38
+ catch (e) {
39
+ logger.error(`[ContextLake] Slash ${tool.name} failed`, { error: e.message });
40
+ return { text: `**Error executing ${tool.label}:** ${e.message}` };
41
+ }
42
+ };
43
+ }
8
44
  return {
45
+ ...lasHandlers,
46
+ listS3ObjectsHandler: async (commandCtx) => {
47
+ const rawArgs = commandCtx.args || "";
48
+ const args = rawArgs.split(' ').filter((arg) => arg.trim() !== '');
49
+ logger.info(`[${new Date().toISOString()}] [ContextLake] Slash command list-s3-objects started`, { args });
50
+ try {
51
+ if (args.length === 0) {
52
+ return { text: `**Error:** Missing URL. Usage: /contextlake-list-s3-objects <url> [prefix] [maxKeys]` };
53
+ }
54
+ const url = args[0];
55
+ const prefix = args.length > 1 ? args[1] : '';
56
+ const maxKeys = args.length > 2 ? parseOptionalInt(args[2], 1000) : 1000;
57
+ const result = await (0, s3_tools_1.listS3Objects)({ url }, prefix, maxKeys);
58
+ return { text: `**List S3 Objects Results:**\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`` };
59
+ }
60
+ catch (e) {
61
+ logger.error(`[ContextLake] Slash list-s3-objects failed`, { error: e.message });
62
+ return { text: `**Error executing list-s3-objects:** ${e.message}` };
63
+ }
64
+ },
65
+ readS3ObjectHandler: async (commandCtx) => {
66
+ const rawArgs = commandCtx.args || "";
67
+ const args = rawArgs.split(' ').filter((arg) => arg.trim() !== '');
68
+ logger.info(`[${new Date().toISOString()}] [ContextLake] Slash command read-s3-object started`, { args });
69
+ try {
70
+ if (args.length === 0) {
71
+ return { text: `**Error:** Missing URL. Usage: /contextlake-read-s3-object <url> [maxBytes]` };
72
+ }
73
+ const url = args[0];
74
+ const parsedUrl = new URL(url);
75
+ const key = parsedUrl.pathname.replace(/^\//, '');
76
+ const maxBytes = args.length > 1 ? parseOptionalInt(args[1], 0) : undefined;
77
+ const result = await (0, s3_tools_1.readS3Object)({ url }, key, maxBytes);
78
+ // Return base64 or string based on content? Returning string for simplicity in slash commands.
79
+ return { text: `**Read S3 Object Results:**\n\`\`\`\n${result.toString('utf-8')}\n\`\`\`` };
80
+ }
81
+ catch (e) {
82
+ logger.error(`[ContextLake] Slash read-s3-object failed`, { error: e.message });
83
+ return { text: `**Error executing read-s3-object:** ${e.message}` };
84
+ }
85
+ },
86
+ generatePresignedUrlHandler: async (commandCtx) => {
87
+ const rawArgs = commandCtx.args || "";
88
+ const args = rawArgs.split(' ').filter((arg) => arg.trim() !== '');
89
+ logger.info(`[${new Date().toISOString()}] [ContextLake] Slash command generate-presigned-url started`, { args });
90
+ try {
91
+ if (args.length === 0) {
92
+ return { text: `**Error:** Missing URL. Usage: /contextlake-generate-presigned-url <url> [expiresIn]` };
93
+ }
94
+ const url = args[0];
95
+ const parsedUrl = new URL(url);
96
+ const key = parsedUrl.pathname.replace(/^\//, '');
97
+ const expiresIn = args.length > 1 ? parseOptionalInt(args[1], 3600) : 3600;
98
+ const result = await (0, s3_tools_1.getPresignedUrl)({ url }, key, expiresIn);
99
+ return { text: `**Presigned URL:**\n${result}` };
100
+ }
101
+ catch (e) {
102
+ logger.error(`[ContextLake] Slash generate-presigned-url failed`, { error: e.message });
103
+ return { text: `**Error executing generate-presigned-url:** ${e.message}` };
104
+ }
105
+ },
9
106
  listHandler: async (commandCtx) => {
10
107
  const rawArgs = commandCtx.args || "";
11
108
  const args = rawArgs.split(' ').filter((arg) => arg.trim() !== '');
package/index.ts CHANGED
@@ -5,7 +5,7 @@ import { registerAll } from './src/commands';
5
5
  const plugin = {
6
6
  id: 'contextlake-openclaw',
7
7
  name: 'ContextLake',
8
- version: '1.0.2',
8
+ version: '1.0.11',
9
9
  description: 'A lightweight knowledge base plugin for OpenClaw using LanceDB and TOS, with data profiling support',
10
10
  configSchema: {
11
11
  type: 'object',
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "contextlake-openclaw",
3
3
  "name": "ContextLake",
4
- "version": "1.0.8",
4
+ "version": "1.0.11",
5
5
  "description": "A lightweight knowledge base plugin for OpenClaw using LanceDB and TOS, with data profiling support",
6
6
  "skills": ["./src/skills"],
7
7
  "configSchema": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byted-las/contextlake-openclaw",
3
- "version": "1.0.8",
3
+ "version": "1.0.11",
4
4
  "description": "ContextLake OpenClaw Plugin for managing knowledge base",
5
5
  "main": "index.ts",
6
6
  "files": [
@@ -3,6 +3,8 @@ import { ingestSource } from '../lib/actions/ingest';
3
3
  import { retrieveAssets } from '../lib/actions/retrieve';
4
4
  import { listAssets, deleteAssets } from '../lib/actions/manage';
5
5
  import { connectDataSource, ConnectParams } from '../lib/actions/profiler';
6
+ import { getLasTools } from '../lib/actions/las-tools';
7
+ import { listS3Objects, readS3Object, getPresignedUrl } from '../lib/actions/s3-tools';
6
8
  import { loadCredentials, saveCredentials, promptForInput } from '../utils/credentials';
7
9
  import { ContextLakeConfig } from '../utils/config';
8
10
 
@@ -36,7 +38,87 @@ function parseMetadata(metadata: any): Record<string, any> {
36
38
  }
37
39
 
38
40
  export function getCliCommands(pluginConfig: ContextLakeConfig, logger: any) {
41
+ const lasTools = getLasTools(pluginConfig, logger);
42
+ const lasActions: Record<string, any> = {};
43
+
44
+ for (const tool of lasTools) {
45
+ lasActions[`${tool.name}Action`] = async (dataStr: string) => {
46
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI ${tool.name} started`);
47
+ try {
48
+ let data = {};
49
+ try {
50
+ data = JSON.parse(dataStr);
51
+ } catch (e) {
52
+ // if it's not valid JSON and the tool accepts a simple string/url, try to handle it.
53
+ // But LAS tools typically require a data object.
54
+ data = { url: dataStr };
55
+ }
56
+
57
+ let params = tool.name === 'las_bare_image_text_embedding'
58
+ ? data
59
+ : { data };
60
+
61
+ const result = await tool.execute('cli', params);
62
+ // eslint-disable-next-line no-console
63
+ console.log(JSON.stringify(result, null, 2));
64
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI ${tool.name} success`);
65
+ } catch (e: any) {
66
+ console.error('Error:', e.message);
67
+ logger.error(`[${new Date().toISOString()}] [ContextLake] CLI ${tool.name} failed`, { error: e.message, stack: e.stack });
68
+ }
69
+ };
70
+ }
71
+
39
72
  return {
73
+ ...lasActions,
74
+
75
+ listS3ObjectsAction: async (url: string, options: any) => {
76
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI list-s3-objects started`, { url, options });
77
+ try {
78
+ const result = await listS3Objects(
79
+ { url },
80
+ options.prefix || '',
81
+ parseOptionalInt(options.maxKeys, 1000),
82
+ options.continuationToken
83
+ );
84
+ // eslint-disable-next-line no-console
85
+ console.log(JSON.stringify(result, null, 2));
86
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI list-s3-objects success`);
87
+ } catch (e: any) {
88
+ console.error('Error:', e.message);
89
+ logger.error(`[${new Date().toISOString()}] [ContextLake] CLI list-s3-objects failed`, { error: e.message, stack: e.stack });
90
+ }
91
+ },
92
+
93
+ readS3ObjectAction: async (url: string, options: any) => {
94
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI read-s3-object started`, { url, options });
95
+ try {
96
+ const parsedUrl = new URL(url);
97
+ const key = parsedUrl.pathname.replace(/^\//, '');
98
+ const result = await readS3Object({ url }, key, options.maxBytes ? parseOptionalInt(options.maxBytes, 0) : undefined);
99
+ // eslint-disable-next-line no-console
100
+ console.log(result.toString('utf-8'));
101
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI read-s3-object success`);
102
+ } catch (e: any) {
103
+ console.error('Error:', e.message);
104
+ logger.error(`[${new Date().toISOString()}] [ContextLake] CLI read-s3-object failed`, { error: e.message, stack: e.stack });
105
+ }
106
+ },
107
+
108
+ generatePresignedUrlAction: async (url: string, options: any) => {
109
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI generate-presigned-url started`, { url, options });
110
+ try {
111
+ const parsedUrl = new URL(url);
112
+ const key = parsedUrl.pathname.replace(/^\//, '');
113
+ const result = await getPresignedUrl({ url }, key, parseOptionalInt(options.expiresIn, 3600));
114
+ // eslint-disable-next-line no-console
115
+ console.log(result);
116
+ logger.info(`[${new Date().toISOString()}] [ContextLake] CLI generate-presigned-url success`);
117
+ } catch (e: any) {
118
+ console.error('Error:', e.message);
119
+ logger.error(`[${new Date().toISOString()}] [ContextLake] CLI generate-presigned-url failed`, { error: e.message, stack: e.stack });
120
+ }
121
+ },
40
122
  searchAction: async (query: any, options: any) => {
41
123
  logger.info(`[${new Date().toISOString()}] [ContextLake] CLI search started`, { query, options });
42
124
  try {
@@ -57,7 +57,7 @@ export function registerAll(ctx: OpenClawPluginApi, logger: PluginLogger) {
57
57
  const contextlake = program.command('contextlake')
58
58
  .description('Manage ContextLake knowledge base');
59
59
 
60
- const commands = getCliCommands(pluginConfig, logger);
60
+ const commands = getCliCommands(pluginConfig, logger) as Record<string, any>;
61
61
 
62
62
  // Search
63
63
  contextlake.command('search <query>')
@@ -85,6 +85,34 @@ export function registerAll(ctx: OpenClawPluginApi, logger: PluginLogger) {
85
85
  .description('Configure credentials for ContextLake')
86
86
  .action(commands.onboardAction);
87
87
 
88
+ // S3 Tools
89
+ contextlake.command('list-s3-objects <url>')
90
+ .description('List objects in an S3-compatible bucket or local directory')
91
+ .option('--prefix <string>', 'Prefix to list')
92
+ .option('--max-keys <number>', 'Maximum number of keys to return', '1000')
93
+ .option('--continuation-token <string>', 'Continuation token for pagination')
94
+ .action(commands.listS3ObjectsAction);
95
+
96
+ contextlake.command('read-s3-object <url>')
97
+ .description('Read the contents of an S3 object')
98
+ .option('--max-bytes <number>', 'Maximum number of bytes to read')
99
+ .action(commands.readS3ObjectAction);
100
+
101
+ contextlake.command('generate-presigned-url <url>')
102
+ .description('Generate a presigned HTTP URL for an S3/TOS object')
103
+ .option('--expires-in <number>', 'Expiration time in seconds', '3600')
104
+ .action(commands.generatePresignedUrlAction);
105
+
106
+ // LAS Tools
107
+ const tools = getAgentTools(pluginConfig, logger);
108
+ const lasTools = tools.lasTools;
109
+ for (const tool of lasTools) {
110
+ const cmdName = tool.name.replace(/_/g, '-');
111
+ contextlake.command(`${cmdName} <data>`)
112
+ .description(tool.label)
113
+ .action(commands[`${tool.name}Action`]);
114
+ }
115
+
88
116
  }, { commands: ['contextlake'] });
89
117
  logger.info(`[${new Date().toISOString()}] [ContextLake] CLI commands registered`);
90
118
  } catch (error: any) {
@@ -129,6 +157,38 @@ export function registerAll(ctx: OpenClawPluginApi, logger: PluginLogger) {
129
157
  handler: slashCommands.listDatasourceHandler
130
158
  });
131
159
 
160
+ ctx.registerCommand({
161
+ name: 'contextlake-list-s3-objects',
162
+ description: 'List objects in an S3-compatible bucket or local directory',
163
+ acceptsArgs: true,
164
+ handler: (slashCommands as any).listS3ObjectsHandler
165
+ });
166
+
167
+ ctx.registerCommand({
168
+ name: 'contextlake-read-s3-object',
169
+ description: 'Read the contents of an S3 object',
170
+ acceptsArgs: true,
171
+ handler: (slashCommands as any).readS3ObjectHandler
172
+ });
173
+
174
+ ctx.registerCommand({
175
+ name: 'contextlake-generate-presigned-url',
176
+ description: 'Generate a presigned HTTP URL for an S3/TOS object',
177
+ acceptsArgs: true,
178
+ handler: (slashCommands as any).generatePresignedUrlHandler
179
+ });
180
+
181
+ const tools = getAgentTools(pluginConfig, logger);
182
+ for (const tool of tools.lasTools) {
183
+ const cmdName = tool.name.replace(/_/g, '-');
184
+ ctx.registerCommand({
185
+ name: `contextlake-${cmdName}`,
186
+ description: tool.label,
187
+ acceptsArgs: true,
188
+ handler: (slashCommands as any)[`${tool.name}Handler`]
189
+ });
190
+ }
191
+
132
192
  logger.info(`[${new Date().toISOString()}] [ContextLake] Slash commands registered`);
133
193
  } catch (error: any) {
134
194
  logger.error(`[${new Date().toISOString()}] [ContextLake] Error registering Slash commands: ${error.message}${error.stack ? '\\n' + error.stack : ''}`);
@@ -2,13 +2,121 @@ import { ingestSource } from '../lib/actions/ingest';
2
2
  import { retrieveAssets } from '../lib/actions/retrieve';
3
3
  import { listAssets, deleteAssets } from '../lib/actions/manage';
4
4
  import { connectDataSource, listDataSources, ConnectParams } from '../lib/actions/profiler';
5
+ import { getLasTools } from '../lib/actions/las-tools';
6
+ import { listS3Objects, readS3Object, getPresignedUrl } from '../lib/actions/s3-tools';
5
7
  import { ContextLakeConfig } from '../utils/config';
6
8
  import * as fs from 'fs';
7
9
  import * as path from 'path';
8
10
  import * as os from 'os';
9
11
 
12
+ function parseOptionalInt(value: any, fallback: number): number {
13
+ const parsed = Number.parseInt(String(value), 10);
14
+ return Number.isFinite(parsed) ? parsed : fallback;
15
+ }
16
+
10
17
  export function getSlashCommands(pluginConfig: ContextLakeConfig, logger: any) {
18
+ const lasTools = getLasTools(pluginConfig, logger);
19
+ const lasHandlers: Record<string, any> = {};
20
+
21
+ for (const tool of lasTools) {
22
+ lasHandlers[`${tool.name}Handler`] = async (commandCtx: any) => {
23
+ const rawArgs = commandCtx.args || "";
24
+ logger.info(`[${new Date().toISOString()}] [ContextLake] Slash command ${tool.name} started`, { args: rawArgs });
25
+ try {
26
+ if (!rawArgs.trim()) {
27
+ return { text: `**Error:** Missing arguments. Please provide JSON data or URL.` };
28
+ }
29
+
30
+ let data = {};
31
+ try {
32
+ data = JSON.parse(rawArgs);
33
+ } catch (e) {
34
+ data = { url: rawArgs.trim() };
35
+ }
36
+
37
+ let params = tool.name === 'las_bare_image_text_embedding'
38
+ ? data
39
+ : { data };
40
+
41
+ const result = await tool.execute('slash', params);
42
+ logger.info(`[${new Date().toISOString()}] [ContextLake] Slash command ${tool.name} completed`);
43
+ return { text: `**${tool.label} Results:**\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`` };
44
+ } catch (e: any) {
45
+ logger.error(`[ContextLake] Slash ${tool.name} failed`, { error: e.message });
46
+ return { text: `**Error executing ${tool.label}:** ${e.message}` };
47
+ }
48
+ };
49
+ }
50
+
11
51
  return {
52
+ ...lasHandlers,
53
+
54
+ listS3ObjectsHandler: async (commandCtx: any) => {
55
+ const rawArgs = commandCtx.args || "";
56
+ const args = rawArgs.split(' ').filter((arg: string) => arg.trim() !== '');
57
+ logger.info(`[${new Date().toISOString()}] [ContextLake] Slash command list-s3-objects started`, { args });
58
+
59
+ try {
60
+ if (args.length === 0) {
61
+ return { text: `**Error:** Missing URL. Usage: /contextlake-list-s3-objects <url> [prefix] [maxKeys]` };
62
+ }
63
+ const url = args[0];
64
+ const prefix = args.length > 1 ? args[1] : '';
65
+ const maxKeys = args.length > 2 ? parseOptionalInt(args[2], 1000) : 1000;
66
+
67
+ const result = await listS3Objects({ url }, prefix, maxKeys);
68
+ return { text: `**List S3 Objects Results:**\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`` };
69
+ } catch (e: any) {
70
+ logger.error(`[ContextLake] Slash list-s3-objects failed`, { error: e.message });
71
+ return { text: `**Error executing list-s3-objects:** ${e.message}` };
72
+ }
73
+ },
74
+
75
+ readS3ObjectHandler: async (commandCtx: any) => {
76
+ const rawArgs = commandCtx.args || "";
77
+ const args = rawArgs.split(' ').filter((arg: string) => arg.trim() !== '');
78
+ logger.info(`[${new Date().toISOString()}] [ContextLake] Slash command read-s3-object started`, { args });
79
+
80
+ try {
81
+ if (args.length === 0) {
82
+ return { text: `**Error:** Missing URL. Usage: /contextlake-read-s3-object <url> [maxBytes]` };
83
+ }
84
+ const url = args[0];
85
+ const parsedUrl = new URL(url);
86
+ const key = parsedUrl.pathname.replace(/^\//, '');
87
+ const maxBytes = args.length > 1 ? parseOptionalInt(args[1], 0) : undefined;
88
+
89
+ const result = await readS3Object({ url }, key, maxBytes);
90
+ // Return base64 or string based on content? Returning string for simplicity in slash commands.
91
+ return { text: `**Read S3 Object Results:**\n\`\`\`\n${result.toString('utf-8')}\n\`\`\`` };
92
+ } catch (e: any) {
93
+ logger.error(`[ContextLake] Slash read-s3-object failed`, { error: e.message });
94
+ return { text: `**Error executing read-s3-object:** ${e.message}` };
95
+ }
96
+ },
97
+
98
+ generatePresignedUrlHandler: async (commandCtx: any) => {
99
+ const rawArgs = commandCtx.args || "";
100
+ const args = rawArgs.split(' ').filter((arg: string) => arg.trim() !== '');
101
+ logger.info(`[${new Date().toISOString()}] [ContextLake] Slash command generate-presigned-url started`, { args });
102
+
103
+ try {
104
+ if (args.length === 0) {
105
+ return { text: `**Error:** Missing URL. Usage: /contextlake-generate-presigned-url <url> [expiresIn]` };
106
+ }
107
+ const url = args[0];
108
+ const parsedUrl = new URL(url);
109
+ const key = parsedUrl.pathname.replace(/^\//, '');
110
+ const expiresIn = args.length > 1 ? parseOptionalInt(args[1], 3600) : 3600;
111
+
112
+ const result = await getPresignedUrl({ url }, key, expiresIn);
113
+ return { text: `**Presigned URL:**\n${result}` };
114
+ } catch (e: any) {
115
+ logger.error(`[ContextLake] Slash generate-presigned-url failed`, { error: e.message });
116
+ return { text: `**Error executing generate-presigned-url:** ${e.message}` };
117
+ }
118
+ },
119
+
12
120
  listHandler: async (commandCtx: any) => {
13
121
  const rawArgs = commandCtx.args || "";
14
122
  const args = rawArgs.split(' ').filter((arg: string) => arg.trim() !== '');