@forloop-cc/forloop-opencode-plugin-planner 2.2.4 → 2.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +5 -1
- package/plugins/exports.ts +14 -0
- package/plugins/forloop-plugin.ts +3 -11
- package/plugins/tools/file-tools.ts +60 -5
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
**AI-powered sprint planning, story management, and task automation for opencode**
|
|
9
9
|
|
|
10
|
+
[English](README.md) · [中文](README_zh.md) · [日本語](README_ja.md)
|
|
11
|
+
|
|
10
12
|
[Quick Install](#quick-install) · [How It Works](#how-it-works) · [Capabilities](#capabilities) · [FAQ](#faq)
|
|
11
13
|
|
|
12
14
|
</div>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@forloop-cc/forloop-opencode-plugin-planner",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.6",
|
|
5
5
|
"description": "ForLoop integration plugin for opencode — Sprint, story, and agent tools for the ForLoop platform",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
".": {
|
|
25
25
|
"import": "./plugins/forloop-plugin.ts",
|
|
26
26
|
"types": "./plugins/forloop-plugin.ts"
|
|
27
|
+
},
|
|
28
|
+
"./exports": {
|
|
29
|
+
"import": "./plugins/exports.ts",
|
|
30
|
+
"types": "./plugins/exports.ts"
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
"files": [
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Re-exports for @forloop-cc/forloop-cli consumption.
|
|
2
|
+
// These were moved out of forloop-plugin.ts because Bun's TypeScript
|
|
3
|
+
// transpiler in opencode-ai@1.15.6 fails when a file has both
|
|
4
|
+
// `export default async` and additional `export { }` / `export type { }` lines.
|
|
5
|
+
export { ForLoopAPIClient } from './capabilities/api-client';
|
|
6
|
+
export type { APIError, CreateStoryRequest, CreateSprintRequest, ListSprintsParams, SprintOptions } from './capabilities/api-client';
|
|
7
|
+
export { getConfig, isLambdaExecution, PROD_API_URL, DEV_API_URL } from './capabilities/config';
|
|
8
|
+
export type { ForLoopConfig } from './capabilities/config';
|
|
9
|
+
export { getToken, setToken, clearToken } from './capabilities/token-storage';
|
|
10
|
+
export type { TokenProfile } from './capabilities/token-storage';
|
|
11
|
+
export { validateToken, detectTokenType } from './capabilities/auth';
|
|
12
|
+
export type { TokenValidationResult } from './capabilities/auth';
|
|
13
|
+
export { resolveSprintId, getCurrentBranch, resolveSprintFromBranch, getForloopRoot, getManifestPath } from './capabilities/context-resolver';
|
|
14
|
+
export type { ContextResolution } from './capabilities/context-resolver';
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
createFileListTool,
|
|
36
36
|
createFileDeleteTool,
|
|
37
37
|
createFileDownloadUrlTool,
|
|
38
|
+
createFileDownloadTool,
|
|
38
39
|
} from './tools/file-tools';
|
|
39
40
|
import {
|
|
40
41
|
createScheduleTool,
|
|
@@ -123,7 +124,8 @@ export default async (ctx: any) => {
|
|
|
123
124
|
'forloopFileUpload': createFileUploadTool(client),
|
|
124
125
|
'forloopFileList': createFileListTool(client),
|
|
125
126
|
'forloopFileDelete': createFileDeleteTool(client),
|
|
126
|
-
'
|
|
127
|
+
'forloopFileDownloadUrl': createFileDownloadUrlTool(client),
|
|
128
|
+
'forloopFileDownload': createFileDownloadTool(client),
|
|
127
129
|
|
|
128
130
|
// Schedule Meetings
|
|
129
131
|
'forloopScheduleCreate': createScheduleTool(client),
|
|
@@ -174,13 +176,3 @@ export default async (ctx: any) => {
|
|
|
174
176
|
};
|
|
175
177
|
};
|
|
176
178
|
|
|
177
|
-
export { ForLoopAPIClient } from './capabilities/api-client';
|
|
178
|
-
export type { APIError, CreateStoryRequest, CreateSprintRequest, ListSprintsParams, SprintOptions } from './capabilities/api-client';
|
|
179
|
-
export { getConfig, isLambdaExecution, PROD_API_URL, DEV_API_URL } from './capabilities/config';
|
|
180
|
-
export type { ForLoopConfig } from './capabilities/config';
|
|
181
|
-
export { getToken, setToken, clearToken } from './capabilities/token-storage';
|
|
182
|
-
export type { TokenProfile } from './capabilities/token-storage';
|
|
183
|
-
export { validateToken, detectTokenType } from './capabilities/auth';
|
|
184
|
-
export type { TokenValidationResult } from './capabilities/auth';
|
|
185
|
-
export { resolveSprintId, getCurrentBranch, resolveSprintFromBranch, getForloopRoot, getManifestPath } from './capabilities/context-resolver';
|
|
186
|
-
export type { ContextResolution } from './capabilities/context-resolver';
|
|
@@ -176,10 +176,10 @@ export function createFileDeleteTool(client: ForLoopAPIClient) {
|
|
|
176
176
|
|
|
177
177
|
export function createFileDownloadUrlTool(client: ForLoopAPIClient) {
|
|
178
178
|
return tool({
|
|
179
|
-
description: 'Get a download URL for a file',
|
|
179
|
+
description: 'Get a presigned download URL for a file (does not download the file itself)',
|
|
180
180
|
args: {
|
|
181
181
|
fileId: tool.schema.number()
|
|
182
|
-
.describe('File ID to download'),
|
|
182
|
+
.describe('File ID to get download URL for'),
|
|
183
183
|
},
|
|
184
184
|
async execute(args, _context) {
|
|
185
185
|
const tokenResult = await validateToken();
|
|
@@ -189,10 +189,9 @@ export function createFileDownloadUrlTool(client: ForLoopAPIClient) {
|
|
|
189
189
|
client.setToken?.(tokenResult.token);
|
|
190
190
|
|
|
191
191
|
try {
|
|
192
|
-
//
|
|
193
|
-
|
|
192
|
+
// Fire-and-forget audit log — don't block download on audit failure
|
|
193
|
+
client.logFileRead(args.fileId).catch(() => {});
|
|
194
194
|
|
|
195
|
-
// Get download URL
|
|
196
195
|
const download = await client.getFileDownloadUrl(args.fileId);
|
|
197
196
|
|
|
198
197
|
return [
|
|
@@ -209,3 +208,59 @@ export function createFileDownloadUrlTool(client: ForLoopAPIClient) {
|
|
|
209
208
|
},
|
|
210
209
|
});
|
|
211
210
|
}
|
|
211
|
+
|
|
212
|
+
export function createFileDownloadTool(client: ForLoopAPIClient) {
|
|
213
|
+
return tool({
|
|
214
|
+
description: 'Download a sprint file by ID to the local sandbox',
|
|
215
|
+
args: {
|
|
216
|
+
fileId: tool.schema.number()
|
|
217
|
+
.describe('File ID to download (from forloopFileList)'),
|
|
218
|
+
destPath: tool.schema.string()
|
|
219
|
+
.optional()
|
|
220
|
+
.describe('Destination path relative to sandbox downloads dir (default: <filename>)'),
|
|
221
|
+
},
|
|
222
|
+
async execute(args, _context) {
|
|
223
|
+
const tokenResult = await validateToken();
|
|
224
|
+
if (!tokenResult.valid) {
|
|
225
|
+
return tokenResult.error ?? '❌ No API token configured.';
|
|
226
|
+
}
|
|
227
|
+
client.setToken?.(tokenResult.token);
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const open = await client.getFileOpenUrl(Number(args.fileId));
|
|
231
|
+
const url = String(open?.openUrl || open?.url || '');
|
|
232
|
+
if (!url) {
|
|
233
|
+
return `❌ Could not get download URL for file #${args.fileId}.`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const response = await fetch(url);
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
return `❌ Download failed: HTTP ${response.status}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const buf = Buffer.from(await response.arrayBuffer());
|
|
242
|
+
const fileName = open?.originalName || open?.filename || `file-${args.fileId}`;
|
|
243
|
+
const downloadDir = path.join(process.env.HOME || '/tmp', 'downloads');
|
|
244
|
+
if (!fs.existsSync(downloadDir)) {
|
|
245
|
+
fs.mkdirSync(downloadDir, { recursive: true });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const destFile = args.destPath
|
|
249
|
+
? path.join(downloadDir, args.destPath)
|
|
250
|
+
: path.join(downloadDir, fileName);
|
|
251
|
+
const destDir = path.dirname(destFile);
|
|
252
|
+
if (!fs.existsSync(destDir)) {
|
|
253
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
254
|
+
}
|
|
255
|
+
fs.writeFileSync(destFile, buf);
|
|
256
|
+
|
|
257
|
+
// Log file read (fire-and-forget audit)
|
|
258
|
+
client.logFileRead(args.fileId).catch(() => {});
|
|
259
|
+
|
|
260
|
+
return `✅ Downloaded file #${args.fileId} → ${destFile} (${buf.length} bytes)`;
|
|
261
|
+
} catch (error: any) {
|
|
262
|
+
return `❌ File download failed: ${error.message}`;
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|