runwork 0.4.1 → 0.5.1
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/bundled-types/core-agent.d.ts +8 -0
- package/bundled-types/core-base-agent.d.ts +4 -0
- package/bundled-types/core-types.d.ts +101 -0
- package/bundled-types/core-workspace.d.ts +10 -0
- package/bundled-types/index.d.ts +2 -0
- package/bundled-types/shared/core-types.d.ts +101 -0
- package/bundled-types/shared/shared/core-types.d.ts +101 -0
- package/bundled-types/task-delegation/index.d.ts +50 -0
- package/bundled-types/task-delegation/sandbox-bridge.d.ts +88 -0
- package/bundled-types/task-delegation/session-manager.d.ts +86 -0
- package/bundled-types/task-delegation/stream-parser.d.ts +25 -0
- package/bundled-types/task-delegation/task-delegation/index.d.ts +30 -0
- package/bundled-types/task-delegation/task-delegation/sandbox-bridge.d.ts +88 -0
- package/bundled-types/task-delegation/task-delegation/session-manager.d.ts +86 -0
- package/bundled-types/task-delegation/task-delegation/stream-parser.d.ts +25 -0
- package/bundled-types/task-delegation/task-delegation/types.d.ts +165 -0
- package/bundled-types/task-delegation/types.d.ts +336 -0
- package/bundled-types/task-delegation.d.ts +2 -0
- package/dist/__tests__/curl-parser.test.d.ts +1 -0
- package/dist/__tests__/curl-parser.test.js +250 -0
- package/dist/__tests__/types-manager.test.js +22 -18
- package/dist/agents/claude-code.d.ts +10 -0
- package/dist/agents/claude-code.js +50 -0
- package/dist/agents/claude-desktop.d.ts +10 -0
- package/dist/agents/claude-desktop.js +141 -0
- package/dist/agents/codex.d.ts +10 -0
- package/dist/agents/codex.js +45 -0
- package/dist/agents/copilot-cli.d.ts +10 -0
- package/dist/agents/copilot-cli.js +46 -0
- package/dist/agents/copilot-vscode.d.ts +10 -0
- package/dist/agents/copilot-vscode.js +37 -0
- package/dist/agents/cursor.d.ts +10 -0
- package/dist/agents/cursor.js +55 -0
- package/dist/agents/detect.d.ts +4 -0
- package/dist/agents/detect.js +43 -0
- package/dist/agents/gemini.d.ts +10 -0
- package/dist/agents/gemini.js +47 -0
- package/dist/agents/types.d.ts +35 -0
- package/dist/agents/types.js +13 -0
- package/dist/agents/utils/json-config.d.ts +14 -0
- package/dist/agents/utils/json-config.js +42 -0
- package/dist/agents/utils/toml-config.d.ts +8 -0
- package/dist/agents/utils/toml-config.js +27 -0
- package/dist/agents/windsurf.d.ts +10 -0
- package/dist/agents/windsurf.js +50 -0
- package/dist/api/client.d.ts +97 -1
- package/dist/api/client.js +199 -0
- package/dist/auth/store.js +18 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +160 -0
- package/dist/commands/api-keys.d.ts +2 -0
- package/dist/commands/api-keys.js +95 -0
- package/dist/commands/apps.d.ts +2 -0
- package/dist/commands/apps.js +82 -0
- package/dist/commands/components.d.ts +2 -0
- package/dist/commands/components.js +43 -0
- package/dist/commands/endpoints.d.ts +2 -0
- package/dist/commands/endpoints.js +188 -0
- package/dist/commands/entities.d.ts +2 -0
- package/dist/commands/entities.js +198 -0
- package/dist/commands/files.d.ts +2 -0
- package/dist/commands/files.js +192 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +71 -127
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +10 -8
- package/dist/commands/integrations.js +105 -12
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +117 -0
- package/dist/commands/schedules.d.ts +2 -0
- package/dist/commands/schedules.js +221 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +199 -0
- package/dist/commands/skills.d.ts +2 -0
- package/dist/commands/skills.js +134 -0
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +146 -0
- package/dist/commands/workflows.d.ts +2 -0
- package/dist/commands/workflows.js +187 -0
- package/dist/generated/bundled-types.js +35 -33
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/index.js +26 -0
- package/dist/logs/tailer.js +41 -1
- package/dist/types.d.ts +204 -0
- package/dist/ui/banner.js +7 -6
- package/dist/utils/app-info.d.ts +66 -0
- package/dist/utils/app-info.js +150 -0
- package/dist/utils/curl-parser.d.ts +12 -0
- package/dist/utils/curl-parser.js +44 -0
- package/dist/utils/data-input.d.ts +1 -0
- package/dist/utils/data-input.js +38 -0
- package/dist/workspace/resolve.d.ts +19 -0
- package/dist/workspace/resolve.js +100 -0
- package/package.json +3 -2
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { requireAuth } from '../auth/store.js';
|
|
3
|
+
import { ApiClient } from '../api/client.js';
|
|
4
|
+
import { resolveWorkspace } from '../workspace/resolve.js';
|
|
5
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
6
|
+
function truncate(text, max) {
|
|
7
|
+
if (!text)
|
|
8
|
+
return '';
|
|
9
|
+
const first = text.split('\n')[0];
|
|
10
|
+
if (first.length <= max)
|
|
11
|
+
return first;
|
|
12
|
+
return first.slice(0, max - 3) + '...';
|
|
13
|
+
}
|
|
14
|
+
const listCommand = new Command('list')
|
|
15
|
+
.description('List workspace public endpoints')
|
|
16
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
17
|
+
.option('--app <name>', 'Filter by app name or ID')
|
|
18
|
+
.action(async (opts, command) => {
|
|
19
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
20
|
+
const credentials = requireAuth();
|
|
21
|
+
const client = new ApiClient(credentials);
|
|
22
|
+
const { workspaceId, workspaceName } = await resolveWorkspace(client, opts);
|
|
23
|
+
try {
|
|
24
|
+
let endpoints = await client.listEndpoints(workspaceId);
|
|
25
|
+
if (opts.app) {
|
|
26
|
+
endpoints = endpoints.filter(e => e.appId === opts.app || e.appName === opts.app);
|
|
27
|
+
}
|
|
28
|
+
if (useJson) {
|
|
29
|
+
jsonOut({ endpoints });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (endpoints.length === 0) {
|
|
33
|
+
console.log('No endpoints found in this workspace.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
console.log(`\nWorkspace: ${workspaceName || workspaceId}\n`);
|
|
37
|
+
console.log(` ${'METHOD'.padEnd(8)} ${'PATH'.padEnd(36)} ${'APP'.padEnd(20)} ${'AUTH'.padEnd(10)} DESCRIPTION`);
|
|
38
|
+
console.log(' ' + '-'.repeat(90));
|
|
39
|
+
for (const e of endpoints) {
|
|
40
|
+
const method = e.method.toUpperCase().padEnd(8);
|
|
41
|
+
const path = e.endpointPath.padEnd(36);
|
|
42
|
+
const app = (e.appName || e.appId).padEnd(20);
|
|
43
|
+
const auth = (e.auth || '').padEnd(10);
|
|
44
|
+
const desc = truncate(e.description, 40);
|
|
45
|
+
const mode = e.deploymentMode ? ` [${e.deploymentMode}]` : '';
|
|
46
|
+
console.log(` ${method} ${path} ${app} ${auth} ${desc}${mode}`);
|
|
47
|
+
}
|
|
48
|
+
console.log('');
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error('Failed to list endpoints:', err instanceof Error ? err.message : err);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const callCommand = new Command('call')
|
|
56
|
+
.description('Call a workspace endpoint directly')
|
|
57
|
+
.argument('<method>', 'HTTP method (GET, POST, PUT, DELETE, etc.)')
|
|
58
|
+
.argument('<path>', 'Endpoint path (e.g. /my-endpoint)')
|
|
59
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
60
|
+
.option('--app <name>', 'App name or ID (narrows endpoint lookup)')
|
|
61
|
+
.option('--body <json>', 'Request body as JSON string')
|
|
62
|
+
.option('--header <header>', 'Request header (format: "Key: Value", repeatable)', (val, prev) => [...prev, val], [])
|
|
63
|
+
.option('--query <string>', 'Query string to append to the URL (e.g. "foo=bar&baz=1")')
|
|
64
|
+
.option('--api-key <key>', 'API key for Authorization: Bearer header')
|
|
65
|
+
.action(async (method, path, opts, command) => {
|
|
66
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
67
|
+
const credentials = requireAuth();
|
|
68
|
+
const client = new ApiClient(credentials);
|
|
69
|
+
const { workspaceId, workspaceName } = await resolveWorkspace(client, opts);
|
|
70
|
+
try {
|
|
71
|
+
let endpoints = await client.listEndpoints(workspaceId);
|
|
72
|
+
if (opts.app) {
|
|
73
|
+
endpoints = endpoints.filter(e => e.appId === opts.app || e.appName === opts.app);
|
|
74
|
+
}
|
|
75
|
+
const targetMethod = method.toUpperCase();
|
|
76
|
+
const endpoint = endpoints.find(e => e.method.toUpperCase() === targetMethod && e.endpointPath === path);
|
|
77
|
+
if (!endpoint) {
|
|
78
|
+
console.error(`Endpoint not found: ${targetMethod} ${path}`);
|
|
79
|
+
if (endpoints.length > 0) {
|
|
80
|
+
console.error('\nAvailable endpoints:');
|
|
81
|
+
for (const e of endpoints) {
|
|
82
|
+
console.error(` ${e.method.toUpperCase().padEnd(8)} ${e.endpointPath} (${e.appName || e.appId})`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.error('No endpoints registered in this workspace.');
|
|
87
|
+
}
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const appInfo = await client.getApp(endpoint.appId);
|
|
91
|
+
const appSlug = appInfo.slug;
|
|
92
|
+
// Build URL: https://{workspaceName}.runwork.ai/{appSlug}/endpoints{path}
|
|
93
|
+
if (!workspaceName) {
|
|
94
|
+
console.error('Could not determine workspace name. Provide --workspace with a named workspace.');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const baseUrl = `https://${workspaceName}.runwork.ai`;
|
|
98
|
+
let url = `${baseUrl}/${appSlug}/endpoints${path}`;
|
|
99
|
+
if (opts.query) {
|
|
100
|
+
url = `${url}?${opts.query}`;
|
|
101
|
+
}
|
|
102
|
+
// Build headers
|
|
103
|
+
const headers = {};
|
|
104
|
+
if (opts.apiKey) {
|
|
105
|
+
headers['Authorization'] = `Bearer ${opts.apiKey}`;
|
|
106
|
+
}
|
|
107
|
+
for (const h of opts.header) {
|
|
108
|
+
const colonIndex = h.indexOf(':');
|
|
109
|
+
if (colonIndex === -1) {
|
|
110
|
+
console.error(`Invalid header format "${h}" — expected "Key: Value"`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const key = h.slice(0, colonIndex).trim();
|
|
114
|
+
const value = h.slice(colonIndex + 1).trim();
|
|
115
|
+
headers[key] = value;
|
|
116
|
+
}
|
|
117
|
+
// Parse body
|
|
118
|
+
let body;
|
|
119
|
+
if (opts.body) {
|
|
120
|
+
try {
|
|
121
|
+
// Validate it's valid JSON then re-serialize for consistency
|
|
122
|
+
const parsed = JSON.parse(opts.body);
|
|
123
|
+
body = JSON.stringify(parsed);
|
|
124
|
+
if (!headers['Content-Type']) {
|
|
125
|
+
headers['Content-Type'] = 'application/json';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
console.error('Failed to parse --body as JSON. Provide a valid JSON string.');
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (useJson) {
|
|
134
|
+
// Let the caller know what we're calling before making the request
|
|
135
|
+
process.stderr.write(JSON.stringify({ calling: { method: targetMethod, url } }) + '\n');
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
console.log(`\n${targetMethod} ${url}\n`);
|
|
139
|
+
}
|
|
140
|
+
const response = await fetch(url, {
|
|
141
|
+
method: targetMethod,
|
|
142
|
+
headers,
|
|
143
|
+
body,
|
|
144
|
+
});
|
|
145
|
+
const contentType = response.headers.get('content-type') || '';
|
|
146
|
+
const responseText = await response.text();
|
|
147
|
+
if (useJson) {
|
|
148
|
+
let responseBody = responseText;
|
|
149
|
+
if (contentType.includes('application/json')) {
|
|
150
|
+
try {
|
|
151
|
+
responseBody = JSON.parse(responseText);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// leave as string
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
jsonOut({
|
|
158
|
+
status: response.status,
|
|
159
|
+
statusText: response.statusText,
|
|
160
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
161
|
+
body: responseBody,
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
console.log(`Status: ${response.status} ${response.statusText}\n`);
|
|
166
|
+
if (contentType.includes('application/json')) {
|
|
167
|
+
try {
|
|
168
|
+
const parsed = JSON.parse(responseText);
|
|
169
|
+
console.log(JSON.stringify(parsed, null, 2));
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
console.log(responseText);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
console.log(responseText);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
console.error('Failed to call endpoint:', err instanceof Error ? err.message : err);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
export const endpointsCommand = new Command('endpoints')
|
|
185
|
+
.alias('routes')
|
|
186
|
+
.description('Manage and call workspace endpoints')
|
|
187
|
+
.addCommand(listCommand)
|
|
188
|
+
.addCommand(callCommand);
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { requireAuth } from '../auth/store.js';
|
|
3
|
+
import { ApiClient } from '../api/client.js';
|
|
4
|
+
import { resolveWorkspace } from '../workspace/resolve.js';
|
|
5
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
6
|
+
import { promptConfirm } from '../utils/prompt.js';
|
|
7
|
+
import { parseDataInput } from '../utils/data-input.js';
|
|
8
|
+
const listCommand = new Command('list')
|
|
9
|
+
.description('List entities registered in the workspace')
|
|
10
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
11
|
+
.option('--app <id>', 'Filter by app name or ID')
|
|
12
|
+
.option('--preview', 'Show preview-mode entities only')
|
|
13
|
+
.action(async (opts, command) => {
|
|
14
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
15
|
+
const credentials = requireAuth();
|
|
16
|
+
const client = new ApiClient(credentials);
|
|
17
|
+
const { workspaceId, workspaceName } = await resolveWorkspace(client, opts);
|
|
18
|
+
try {
|
|
19
|
+
let entities = await client.listEntities(workspaceId);
|
|
20
|
+
if (opts.app) {
|
|
21
|
+
entities = entities.filter(e => e.appId === opts.app || e.appName === opts.app);
|
|
22
|
+
}
|
|
23
|
+
if (opts.preview) {
|
|
24
|
+
entities = entities.filter(e => e.deploymentMode === 'preview');
|
|
25
|
+
}
|
|
26
|
+
if (useJson) {
|
|
27
|
+
jsonOut({ entities });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (entities.length === 0) {
|
|
31
|
+
console.log('No entities found in this workspace.');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log(`\nWorkspace: ${workspaceName || workspaceId}\n`);
|
|
35
|
+
console.log(` ${'Entity'.padEnd(28)} ${'App'.padEnd(24)} ${'Mode'.padEnd(12)}`);
|
|
36
|
+
console.log(' ' + '-'.repeat(66));
|
|
37
|
+
for (const e of entities) {
|
|
38
|
+
console.log(` ${e.entityName.padEnd(28)} ${e.appName.padEnd(24)} ${(e.deploymentMode ?? '').padEnd(12)}`);
|
|
39
|
+
}
|
|
40
|
+
console.log('');
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error('Failed to list entities:', err instanceof Error ? err.message : err);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const recordsCommand = new Command('records')
|
|
48
|
+
.description('List records for an entity')
|
|
49
|
+
.argument('<entity>', 'Entity name')
|
|
50
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
51
|
+
.option('--limit <n>', 'Number of records to return', '20')
|
|
52
|
+
.option('--cursor <cursor>', 'Pagination cursor')
|
|
53
|
+
.option('--preview', 'Query preview-mode data')
|
|
54
|
+
.action(async (entity, opts, command) => {
|
|
55
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
56
|
+
const credentials = requireAuth();
|
|
57
|
+
const client = new ApiClient(credentials);
|
|
58
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
59
|
+
try {
|
|
60
|
+
const result = await client.listEntityRecords(workspaceId, entity, {
|
|
61
|
+
limit: parseInt(opts.limit, 10),
|
|
62
|
+
cursor: opts.cursor,
|
|
63
|
+
deploymentMode: opts.preview ? 'preview' : undefined,
|
|
64
|
+
});
|
|
65
|
+
if (useJson) {
|
|
66
|
+
jsonOut(result);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (result.items.length === 0) {
|
|
70
|
+
console.log(`No records found for entity "${entity}".`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
console.log(`\nEntity: ${entity} (${result.items.length} record(s))\n`);
|
|
74
|
+
console.log(JSON.stringify(result.items, null, 2));
|
|
75
|
+
if (result.next) {
|
|
76
|
+
console.log(`\nNext cursor: ${result.next}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
console.error('Failed to list entity records:', err instanceof Error ? err.message : err);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
const getCommand = new Command('get')
|
|
85
|
+
.description('Get a single entity record by ID')
|
|
86
|
+
.argument('<entity>', 'Entity name')
|
|
87
|
+
.argument('<id>', 'Record ID')
|
|
88
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
89
|
+
.action(async (entity, id, opts, command) => {
|
|
90
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
91
|
+
const credentials = requireAuth();
|
|
92
|
+
const client = new ApiClient(credentials);
|
|
93
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
94
|
+
try {
|
|
95
|
+
const result = await client.getEntityRecord(workspaceId, entity, id);
|
|
96
|
+
if (useJson) {
|
|
97
|
+
jsonOut(result);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log(JSON.stringify(result.item, null, 2));
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
console.error('Failed to get entity record:', err instanceof Error ? err.message : err);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const createCommand = new Command('create')
|
|
108
|
+
.description('Create a new entity record')
|
|
109
|
+
.argument('<entity>', 'Entity name')
|
|
110
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
111
|
+
.option('--data <json>', 'Record data as JSON string, @file.json, or pipe via stdin')
|
|
112
|
+
.option('--preview', 'Create in preview-mode data store')
|
|
113
|
+
.action(async (entity, opts, command) => {
|
|
114
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
115
|
+
const credentials = requireAuth();
|
|
116
|
+
const client = new ApiClient(credentials);
|
|
117
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
118
|
+
try {
|
|
119
|
+
const data = await parseDataInput(opts.data);
|
|
120
|
+
const result = await client.createEntityRecord(workspaceId, entity, data);
|
|
121
|
+
if (useJson) {
|
|
122
|
+
jsonOut(result);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
console.log('Record created:');
|
|
126
|
+
console.log(JSON.stringify(result.item, null, 2));
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
console.error('Failed to create entity record:', err instanceof Error ? err.message : err);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
const updateCommand = new Command('update')
|
|
134
|
+
.description('Update an existing entity record')
|
|
135
|
+
.argument('<entity>', 'Entity name')
|
|
136
|
+
.argument('<id>', 'Record ID')
|
|
137
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
138
|
+
.option('--data <json>', 'Record data as JSON string, @file.json, or pipe via stdin')
|
|
139
|
+
.action(async (entity, id, opts, command) => {
|
|
140
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
141
|
+
const credentials = requireAuth();
|
|
142
|
+
const client = new ApiClient(credentials);
|
|
143
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
144
|
+
try {
|
|
145
|
+
const data = await parseDataInput(opts.data);
|
|
146
|
+
const result = await client.updateEntityRecord(workspaceId, entity, id, data);
|
|
147
|
+
if (useJson) {
|
|
148
|
+
jsonOut(result);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
console.log('Record updated:');
|
|
152
|
+
console.log(JSON.stringify(result.item, null, 2));
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
console.error('Failed to update entity record:', err instanceof Error ? err.message : err);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
const deleteCommand = new Command('delete')
|
|
160
|
+
.description('Delete an entity record')
|
|
161
|
+
.argument('<entity>', 'Entity name')
|
|
162
|
+
.argument('<id>', 'Record ID')
|
|
163
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
164
|
+
.option('--yes', 'Skip confirmation prompt')
|
|
165
|
+
.action(async (entity, id, opts, command) => {
|
|
166
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
167
|
+
const credentials = requireAuth();
|
|
168
|
+
const client = new ApiClient(credentials);
|
|
169
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
170
|
+
try {
|
|
171
|
+
if (!opts.yes) {
|
|
172
|
+
const confirmed = await promptConfirm(`Delete record "${id}" from entity "${entity}"? This cannot be undone.`);
|
|
173
|
+
if (!confirmed) {
|
|
174
|
+
console.log('Cancelled.');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const result = await client.deleteEntityRecord(workspaceId, entity, id);
|
|
179
|
+
if (useJson) {
|
|
180
|
+
jsonOut(result);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
console.log(`Record "${id}" deleted from entity "${entity}".`);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
console.error('Failed to delete entity record:', err instanceof Error ? err.message : err);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
export const entitiesCommand = new Command('entities')
|
|
191
|
+
.alias('data')
|
|
192
|
+
.description('Manage workspace entity data')
|
|
193
|
+
.addCommand(listCommand)
|
|
194
|
+
.addCommand(recordsCommand)
|
|
195
|
+
.addCommand(getCommand)
|
|
196
|
+
.addCommand(createCommand)
|
|
197
|
+
.addCommand(updateCommand)
|
|
198
|
+
.addCommand(deleteCommand);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { writeFileSync, readFileSync } from 'fs';
|
|
3
|
+
import { basename } from 'path';
|
|
4
|
+
import { requireAuth } from '../auth/store.js';
|
|
5
|
+
import { ApiClient } from '../api/client.js';
|
|
6
|
+
import { resolveWorkspace } from '../workspace/resolve.js';
|
|
7
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
8
|
+
import { promptConfirm } from '../utils/prompt.js';
|
|
9
|
+
function formatSize(bytes) {
|
|
10
|
+
if (bytes === undefined)
|
|
11
|
+
return '';
|
|
12
|
+
if (bytes < 1024)
|
|
13
|
+
return `${bytes}B`;
|
|
14
|
+
if (bytes < 1024 * 1024)
|
|
15
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
16
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
17
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
18
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
|
|
19
|
+
}
|
|
20
|
+
const listCommand = new Command('list')
|
|
21
|
+
.description('List file storage buckets in the workspace')
|
|
22
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
23
|
+
.action(async (opts, command) => {
|
|
24
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
25
|
+
const credentials = requireAuth();
|
|
26
|
+
const client = new ApiClient(credentials);
|
|
27
|
+
const { workspaceId, workspaceName } = await resolveWorkspace(client, opts);
|
|
28
|
+
try {
|
|
29
|
+
const storages = await client.listFileStorages(workspaceId);
|
|
30
|
+
if (useJson) {
|
|
31
|
+
jsonOut({ storages });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (storages.length === 0) {
|
|
35
|
+
console.log('No file storage buckets found in this workspace.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.log(`\nWorkspace: ${workspaceName || workspaceId}\n`);
|
|
39
|
+
console.log(` ${'Bucket'.padEnd(32)} ${'App'.padEnd(24)} ${'Status'.padEnd(12)} Objects`);
|
|
40
|
+
console.log(' ' + '-'.repeat(76));
|
|
41
|
+
for (const s of storages) {
|
|
42
|
+
const objectCount = s.objectCount !== undefined ? String(s.objectCount) : '-';
|
|
43
|
+
console.log(` ${s.bucketName.padEnd(32)} ${(s.appName || '').padEnd(24)} ${s.bucketStatus.padEnd(12)} ${objectCount}`);
|
|
44
|
+
}
|
|
45
|
+
console.log('');
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.error('Failed to list file storages:', err instanceof Error ? err.message : err);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const lsCommand = new Command('ls')
|
|
53
|
+
.description('List files in a bucket')
|
|
54
|
+
.argument('<bucket>', 'Bucket name')
|
|
55
|
+
.argument('[prefix]', 'Key prefix to filter by')
|
|
56
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
57
|
+
.option('--delimiter <char>', 'Delimiter for hierarchical listing', '/')
|
|
58
|
+
.action(async (bucket, prefix, opts, command) => {
|
|
59
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
60
|
+
const credentials = requireAuth();
|
|
61
|
+
const client = new ApiClient(credentials);
|
|
62
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
63
|
+
try {
|
|
64
|
+
const data = await client.listBucketFiles(workspaceId, bucket, {
|
|
65
|
+
prefix,
|
|
66
|
+
delimiter: opts.delimiter,
|
|
67
|
+
});
|
|
68
|
+
if (useJson) {
|
|
69
|
+
jsonOut({ bucket, prefix, folders: data.folders, files: data.files, truncated: data.truncated });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const totalEntries = data.folders.length + data.files.length;
|
|
73
|
+
if (totalEntries === 0) {
|
|
74
|
+
console.log(`No files found${prefix ? ` under prefix "${prefix}"` : ''} in bucket "${bucket}".`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const folder of data.folders) {
|
|
78
|
+
console.log(` ${folder.prefix}`);
|
|
79
|
+
}
|
|
80
|
+
for (const file of data.files) {
|
|
81
|
+
const size = file.size !== undefined ? formatSize(file.size).padStart(10) : ' ';
|
|
82
|
+
const modified = file.lastModified ? ` ${file.lastModified}` : '';
|
|
83
|
+
console.log(` ${size} ${file.key}${modified}`);
|
|
84
|
+
}
|
|
85
|
+
if (data.truncated) {
|
|
86
|
+
console.log('\n (results truncated)');
|
|
87
|
+
}
|
|
88
|
+
console.log('');
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.error('Failed to list bucket files:', err instanceof Error ? err.message : err);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const downloadCommand = new Command('download')
|
|
96
|
+
.description('Download a file from a bucket')
|
|
97
|
+
.argument('<bucket>', 'Bucket name')
|
|
98
|
+
.argument('<key>', 'Object key to download')
|
|
99
|
+
.argument('[output]', 'Local output path (defaults to basename of key)')
|
|
100
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
101
|
+
.action(async (bucket, key, output, opts, command) => {
|
|
102
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
103
|
+
const credentials = requireAuth();
|
|
104
|
+
const client = new ApiClient(credentials);
|
|
105
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
106
|
+
const outputPath = output || basename(key);
|
|
107
|
+
try {
|
|
108
|
+
const { url } = await client.getPresignedUrl(workspaceId, bucket, { action: 'read', key });
|
|
109
|
+
const response = await fetch(url);
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
112
|
+
}
|
|
113
|
+
writeFileSync(outputPath, Buffer.from(await response.arrayBuffer()));
|
|
114
|
+
if (useJson) {
|
|
115
|
+
jsonOut({ success: true, bucket, key, outputPath });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
console.log(`Downloaded "${key}" to "${outputPath}".`);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
console.error('Failed to download file:', err instanceof Error ? err.message : err);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const uploadCommand = new Command('upload')
|
|
126
|
+
.description('Upload a local file to a bucket')
|
|
127
|
+
.argument('<bucket>', 'Bucket name')
|
|
128
|
+
.argument('<localPath>', 'Local file path to upload')
|
|
129
|
+
.argument('[key]', 'Object key in bucket (defaults to basename of local path)')
|
|
130
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
131
|
+
.action(async (bucket, localPath, key, opts, command) => {
|
|
132
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
133
|
+
const credentials = requireAuth();
|
|
134
|
+
const client = new ApiClient(credentials);
|
|
135
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
136
|
+
const objectKey = key || basename(localPath);
|
|
137
|
+
try {
|
|
138
|
+
const fileBuffer = readFileSync(localPath);
|
|
139
|
+
const { url } = await client.getPresignedUrl(workspaceId, bucket, { action: 'write', key: objectKey });
|
|
140
|
+
const response = await fetch(url, { method: 'PUT', body: fileBuffer });
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
143
|
+
}
|
|
144
|
+
if (useJson) {
|
|
145
|
+
jsonOut({ success: true, bucket, key: objectKey, localPath });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
console.log(`Uploaded "${localPath}" to "${bucket}/${objectKey}".`);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
console.error('Failed to upload file:', err instanceof Error ? err.message : err);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const deleteCommand = new Command('delete')
|
|
156
|
+
.description('Delete a file from a bucket')
|
|
157
|
+
.argument('<bucket>', 'Bucket name')
|
|
158
|
+
.argument('<key>', 'Object key to delete')
|
|
159
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
160
|
+
.option('--yes', 'Skip confirmation prompt')
|
|
161
|
+
.action(async (bucket, key, opts, command) => {
|
|
162
|
+
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
163
|
+
const credentials = requireAuth();
|
|
164
|
+
const client = new ApiClient(credentials);
|
|
165
|
+
const { workspaceId } = await resolveWorkspace(client, opts);
|
|
166
|
+
try {
|
|
167
|
+
if (!opts.yes) {
|
|
168
|
+
const confirmed = await promptConfirm(`Delete "${key}" from bucket "${bucket}"?`);
|
|
169
|
+
if (!confirmed) {
|
|
170
|
+
console.log('Cancelled.');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
await client.deleteBucketFile(workspaceId, bucket, key);
|
|
175
|
+
if (useJson) {
|
|
176
|
+
jsonOut({ success: true, bucket, key });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
console.log(`Deleted "${key}" from bucket "${bucket}".`);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
console.error('Failed to delete file:', err instanceof Error ? err.message : err);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
export const filesCommand = new Command('files')
|
|
187
|
+
.description('Manage workspace file storage')
|
|
188
|
+
.addCommand(listCommand)
|
|
189
|
+
.addCommand(lsCommand)
|
|
190
|
+
.addCommand(downloadCommand)
|
|
191
|
+
.addCommand(uploadCommand)
|
|
192
|
+
.addCommand(deleteCommand);
|
package/dist/commands/info.d.ts
CHANGED