openclaw-productboard 1.0.0
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 +35 -0
- package/LICENSE +21 -0
- package/README.md +230 -0
- package/dist/client/api-client.d.ts +64 -0
- package/dist/client/api-client.js +379 -0
- package/dist/client/errors.d.ts +51 -0
- package/dist/client/errors.js +128 -0
- package/dist/client/types.d.ts +262 -0
- package/dist/client/types.js +6 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +62 -0
- package/dist/tools/features.d.ts +6 -0
- package/dist/tools/features.js +318 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.js +15 -0
- package/dist/tools/notes.d.ts +6 -0
- package/dist/tools/notes.js +176 -0
- package/dist/tools/products.d.ts +6 -0
- package/dist/tools/products.js +148 -0
- package/dist/tools/search.d.ts +6 -0
- package/dist/tools/search.js +116 -0
- package/dist/utils/cache.d.ts +54 -0
- package/dist/utils/cache.js +123 -0
- package/dist/utils/rate-limiter.d.ts +58 -0
- package/dist/utils/rate-limiter.js +118 -0
- package/openclaw.plugin.json +57 -0
- package/package.json +53 -0
- package/skills/productboard-feedback/SKILL.md +105 -0
- package/skills/productboard-release/SKILL.md +146 -0
- package/skills/productboard-search/SKILL.md +62 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Token Bucket Rate Limiter for ProductBoard API
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RateLimiter = void 0;
|
|
7
|
+
exports.getRateLimiter = getRateLimiter;
|
|
8
|
+
exports.resetRateLimiter = resetRateLimiter;
|
|
9
|
+
const DEFAULT_OPTIONS = {
|
|
10
|
+
maxTokens: 100, // 100 requests
|
|
11
|
+
refillRate: 100, // Refill completely
|
|
12
|
+
refillInterval: 60 * 1000, // Per minute
|
|
13
|
+
};
|
|
14
|
+
class RateLimiter {
|
|
15
|
+
tokens;
|
|
16
|
+
lastRefill;
|
|
17
|
+
options;
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
20
|
+
this.tokens = this.options.maxTokens;
|
|
21
|
+
this.lastRefill = Date.now();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Refill tokens based on elapsed time
|
|
25
|
+
*/
|
|
26
|
+
refill() {
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
const elapsed = now - this.lastRefill;
|
|
29
|
+
const intervals = Math.floor(elapsed / this.options.refillInterval);
|
|
30
|
+
if (intervals > 0) {
|
|
31
|
+
this.tokens = Math.min(this.options.maxTokens, this.tokens + intervals * this.options.refillRate);
|
|
32
|
+
this.lastRefill = now - (elapsed % this.options.refillInterval);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Try to acquire a token
|
|
37
|
+
* @returns true if token was acquired, false if rate limited
|
|
38
|
+
*/
|
|
39
|
+
tryAcquire() {
|
|
40
|
+
this.refill();
|
|
41
|
+
if (this.tokens > 0) {
|
|
42
|
+
this.tokens--;
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Acquire a token, waiting if necessary
|
|
49
|
+
* @returns Promise that resolves when a token is available
|
|
50
|
+
*/
|
|
51
|
+
async acquire() {
|
|
52
|
+
if (this.tryAcquire()) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Wait until next refill
|
|
56
|
+
const waitTime = this.getWaitTime();
|
|
57
|
+
await this.sleep(waitTime);
|
|
58
|
+
await this.acquire();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the time in milliseconds until a token is available
|
|
62
|
+
*/
|
|
63
|
+
getWaitTime() {
|
|
64
|
+
this.refill();
|
|
65
|
+
if (this.tokens > 0) {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
const elapsed = Date.now() - this.lastRefill;
|
|
69
|
+
return this.options.refillInterval - elapsed;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get current token count
|
|
73
|
+
*/
|
|
74
|
+
getTokens() {
|
|
75
|
+
this.refill();
|
|
76
|
+
return this.tokens;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Reset the rate limiter to full capacity
|
|
80
|
+
*/
|
|
81
|
+
reset() {
|
|
82
|
+
this.tokens = this.options.maxTokens;
|
|
83
|
+
this.lastRefill = Date.now();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if rate limited without consuming a token
|
|
87
|
+
*/
|
|
88
|
+
isRateLimited() {
|
|
89
|
+
this.refill();
|
|
90
|
+
return this.tokens <= 0;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get rate limiter statistics
|
|
94
|
+
*/
|
|
95
|
+
stats() {
|
|
96
|
+
return {
|
|
97
|
+
tokens: this.getTokens(),
|
|
98
|
+
maxTokens: this.options.maxTokens,
|
|
99
|
+
waitTime: this.getWaitTime(),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
sleep(ms) {
|
|
103
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.RateLimiter = RateLimiter;
|
|
107
|
+
// Singleton instance for the plugin
|
|
108
|
+
let rateLimiterInstance = null;
|
|
109
|
+
function getRateLimiter(options) {
|
|
110
|
+
if (!rateLimiterInstance) {
|
|
111
|
+
rateLimiterInstance = new RateLimiter(options);
|
|
112
|
+
}
|
|
113
|
+
return rateLimiterInstance;
|
|
114
|
+
}
|
|
115
|
+
function resetRateLimiter() {
|
|
116
|
+
rateLimiterInstance = null;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmF0ZS1saW1pdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL3JhdGUtbGltaXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7O0dBRUc7OztBQW9JSCx3Q0FLQztBQUVELDRDQUVDO0FBbElELE1BQU0sZUFBZSxHQUF1QjtJQUMxQyxTQUFTLEVBQUUsR0FBRyxFQUFFLGVBQWU7SUFDL0IsVUFBVSxFQUFFLEdBQUcsRUFBRSxvQkFBb0I7SUFDckMsY0FBYyxFQUFFLEVBQUUsR0FBRyxJQUFJLEVBQUUsYUFBYTtDQUN6QyxDQUFDO0FBRUYsTUFBYSxXQUFXO0lBQ2QsTUFBTSxDQUFTO0lBQ2YsVUFBVSxDQUFTO0lBQ1YsT0FBTyxDQUFxQjtJQUU3QyxZQUFZLFVBQXVDLEVBQUU7UUFDbkQsSUFBSSxDQUFDLE9BQU8sR0FBRyxFQUFFLEdBQUcsZUFBZSxFQUFFLEdBQUcsT0FBTyxFQUFFLENBQUM7UUFDbEQsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQztRQUNyQyxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUMvQixDQUFDO0lBRUQ7O09BRUc7SUFDSyxNQUFNO1FBQ1osTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sT0FBTyxHQUFHLEdBQUcsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDO1FBQ3RDLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUM7UUFFcEUsSUFBSSxTQUFTLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDbEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsR0FBRyxDQUNwQixJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFDdEIsSUFBSSxDQUFDLE1BQU0sR0FB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cmQgQVBJXG4gKi9cblxuZXhwb3J0IGludGVyZmFjZSBSYXRlTGltaXRlck9wdGlvbnMge1xuICAvKiogTWF4aW11bSB0b2tlbnMgaW4gdGhlIGJ1Y2tldCAqL1xuICBtYXhUb2tlbnM6IG51bWJlcjtcbiAgLyoqIFRva2VucyBhZGRlZCBwZXIgaW50ZXJ2YWwgKi9cbiAgcmVmaWxsUmF0ZTogbnVtYmVyO1xuICAvKiogUmVmaWxsIGludGVydmFsIGluIG1pbGxpc2Vjb25kcyAqL1xuICByZWZpbGxJbnRlcnZhbDogbnVtYmVyO1xufVxuXG5jb25zdCBERUZBVUxUX09QVElPTlM6IFJhdGVMaW1pdGVyT3B0aW9ucyA9IHtcbiAgbWF4VG9rZW5zOiAxMDAsIC8vIDEwMCByZXF1ZXN0c1xuICByZWZpbGxSYXRlOiAxMDAsIC8vIFJlZmlsbCBjb21wbGV0ZWx5XG4gIHJlZmlsbEludGVydmFsOiA2MCAqIDEwMDAsIC8vIFBlciBtaW51dGVcbn07XG5cbmV4cG9ydCBjbGFzcyBSYXRlTGltaXRlciB7XG4gIHByaXZhdGUgdG9rZW5zOiBudW1iZXI7XG4gIHByaXZhdGUgbGFzdFJlZmlsbDogbnVtYmVyO1xuICBwcml2YXRlIHJlYWRvbmx5IG9wdGlvbnM6IFJhdGVMaW1pdGVyT3B0aW9ucztcblxuICBjb25zdHJ1Y3RvcihvcHRpb25zOiBQYXJ0aWFsPFJhdGVMaW1pdGVyT3B0aW9ucz4gPSB7fSkge1xuICAgIHRoaXMub3B0aW9ucyA9IHsgLi4uREVGQVVMVF9PUFRJT05TLCAuLi5vcHRpb25zIH07XG4gICAgdGhpcy50b2tlbnMgPSB0aGlzLm9wdGlvbnMubWF4VG9rZW5zO1xuICAgIHRoaXMubGFzdFJlZmlsbCA9IERhdGUubm93KCk7XG4gIH1cblxuICAvKipcbiAgICogUmVmaWxsIHRva2VucyBiYXNlZCBvbiBlbGFwc2VkIHRpbWVcbiAgICovXG4gIHByaXZhdGUgcmVmaWxsKCk6IHZvaWQge1xuICAgIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG4gICAgY29uc3QgZWxhcHNlZCA9IG5vdyAtIHRoaXMubGFzdFJlZmlsbDtcbiAgICBjb25zdCBpbnRlcnZhbHMgPSBNYXRoLmZsb29yKGVsYXBzZWQgLyB0aGlzLm9wdGlvbnMucmVmaWxsSW50ZXJ2YWwpO1xuXG4gICAgaWYgKGludGVydmFscyA+IDApIHtcbiAgICAgIHRoaXMudG9rZW5zID0gTWF0aC5taW4oXG4gICAgICAgIHRoaXMub3B0aW9ucy5tYXhUb2tlbnMsXG4gICAgICAgIHRoaXMudG9rZW5zICsgaW50ZXJ2YWxzICogdGhpcy5vcHRpb25zLnJlZmlsbFJhdGVcbiAgICAgICk7XG4gICAgICB0aGlzLmxhc3RSZWZpbGwgPSBub3cgLSAoZWxhcHNlZCAlIHRoaXMub3B0aW9ucy5yZWZpbGxJbnRlcnZhbCk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFRyeSB0byBhY3F1aXJlIGEgdG9rZW5cbiAgICogQHJldHVybnMgdHJ1ZSBpZiB0b2tlbiB3YXMgYWNxdWlyZWQsIGZhbHNlIGlmIHJhdGUgbGltaXRlZFxuICAgKi9cbiAgdHJ5QWNxdWlyZSgpOiBib29sZWFuIHtcbiAgICB0aGlzLnJlZmlsbCgpO1xuXG4gICAgaWYgKHRoaXMudG9rZW5zID4gMCkge1xuICAgICAgdGhpcy50b2tlbnMtLTtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBBY3F1aXJlIGEgdG9rZW4sIHdhaXRpbmcgaWYgbmVjZXNzYXJ5XG4gICAqIEByZXR1cm5zIFByb21pc2UgdGhhdCByZXNvbHZlcyB3aGVuIGEgdG9rZW4gaXMgYXZhaWxhYmxlXG4gICAqL1xuICBhc3luYyBhY3F1aXJlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGlmICh0aGlzLnRyeUFjcXVpcmUoKSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIFdhaXQgdW50aWwgbmV4dCByZWZpbGxcbiAgICBjb25zdCB3YWl0VGltZSA9IHRoaXMuZ2V0V2FpdFRpbWUoKTtcbiAgICBhd2FpdCB0aGlzLnNsZWVwKHdhaXRUaW1lKTtcbiAgICBhd2FpdCB0aGlzLmFjcXVpcmUoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgdGhlIHRpbWUgaW4gbWlsbGlzZWNvbmRzIHVudGlsIGEgdG9rZW4gaXMgYXZhaWxhYmxlXG4gICAqL1xuICBnZXRXYWl0VGltZSgpOiBudW1iZXIge1xuICAgIHRoaXMucmVmaWxsKCk7XG5cbiAgICBpZiAodGhpcy50b2tlbnMgPiAwKSB7XG4gICAgICByZXR1cm4gMDtcbiAgICB9XG5cbiAgICBjb25zdCBlbGFwc2VkID0gRGF0ZS5ub3coKSAtIHRoaXMubGFzdFJlZmlsbDtcbiAgICByZXR1cm4gdGhpcy5vcHRpb25zLnJlZmlsbEludGVydmFsIC0gZWxhcHNlZDtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgY3VycmVudCB0b2tlbiBjb3VudFxuICAgKi9cbiAgZ2V0VG9rZW5zKCk6IG51bWJlciB7XG4gICAgdGhpcy5yZWZpbGwoKTtcbiAgICByZXR1cm4gdGhpcy50b2tlbnM7XG4gIH1cblxuICAvKipcbiAgICogUmVzZXQgdGhlIHJhdGUgbGltaXRlciB0byBmdWxsIGNhcGFjaXR5XG4gICAqL1xuICByZXNldCgpOiB2b2lkIHtcbiAgICB0aGlzLnRva2VucyA9IHRoaXMub3B0aW9ucy5tYXhUb2tlbnM7XG4gICAgdGhpcy5sYXN0UmVmaWxsID0gRGF0ZS5ub3coKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiByYXRlIGxpbWl0ZWQgd2l0aG91dCBjb25zdW1pbmcgYSB0b2tlblxuICAgKi9cbiAgaXNSYXRlTGltaXRlZCgpOiBib29sZWFuIHtcbiAgICB0aGlzLnJlZmlsbCgpO1xuICAgIHJldHVybiB0aGlzLnRva2VucyA8PSAwO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCByYXRlIGxpbWl0ZXIgc3RhdGlzdGljc1xuICAgKi9cbiAgc3RhdHMoKTogeyB0b2tlbnM6IG51bWJlcjsgbWF4VG9rZW5zOiBudW1iZXI7IHdhaXRUaW1lOiBudW1iZXIgfSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHRva2VuczogdGhpcy5nZXRUb2tlbnMoKSxcbiAgICAgIG1heFRva2VuczogdGhpcy5vcHRpb25zLm1heFRva2VucyxcbiAgICAgIHdhaXRUaW1lOiB0aGlzLmdldFdhaXRUaW1lKCksXG4gICAgfTtcbiAgfVxuXG4gIHByaXZhdGUgc2xlZXAobXM6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gc2V0VGltZW91dChyZXNvbHZlLCBtcykpO1xuICB9XG59XG5cbi8vIFNpbmdsZXRvbiBpbnN0YW5jZSBmb3IgdGhlIHBsdWdpblxubGV0IHJhdGVMaW1pdGVySW5zdGFuY2U6IFJhdGVMaW1pdGVyIHwgbnVsbCA9IG51bGw7XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRSYXRlTGltaXRlcihvcHRpb25zPzogUGFydGlhbDxSYXRlTGltaXRlck9wdGlvbnM+KTogUmF0ZUxpbWl0ZXIge1xuICBpZiAoIXJhdGVMaW1pdGVySW5zdGFuY2UpIHtcbiAgICByYXRlTGltaXRlckluc3RhbmNlID0gbmV3IFJhdGVMaW1pdGVyKG9wdGlvbnMpO1xuICB9XG4gIHJldHVybiByYXRlTGltaXRlckluc3RhbmNlO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVzZXRSYXRlTGltaXRlcigpOiB2b2lkIHtcbiAgcmF0ZUxpbWl0ZXJJbnN0YW5jZSA9IG51bGw7XG59XG4iXX0=
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "productboard",
|
|
3
|
+
"name": "ProductBoard",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "ProductBoard integration for managing features, products, and customer feedback",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"apiToken": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "ProductBoard API bearer token"
|
|
12
|
+
},
|
|
13
|
+
"apiBaseUrl": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"default": "https://api.productboard.com",
|
|
16
|
+
"description": "ProductBoard API base URL"
|
|
17
|
+
},
|
|
18
|
+
"cacheTtlSeconds": {
|
|
19
|
+
"type": "number",
|
|
20
|
+
"default": 300,
|
|
21
|
+
"description": "Cache TTL in seconds for read operations"
|
|
22
|
+
},
|
|
23
|
+
"rateLimitPerMinute": {
|
|
24
|
+
"type": "number",
|
|
25
|
+
"default": 100,
|
|
26
|
+
"description": "Maximum API requests per minute"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"required": ["apiToken"]
|
|
30
|
+
},
|
|
31
|
+
"uiHints": {
|
|
32
|
+
"apiToken": {
|
|
33
|
+
"label": "ProductBoard API Token",
|
|
34
|
+
"placeholder": "pb_...",
|
|
35
|
+
"sensitive": true,
|
|
36
|
+
"helpText": "Get your API token from ProductBoard Settings > Integrations > Public API"
|
|
37
|
+
},
|
|
38
|
+
"apiBaseUrl": {
|
|
39
|
+
"label": "API Base URL",
|
|
40
|
+
"placeholder": "https://api.productboard.com",
|
|
41
|
+
"helpText": "Only change if using a custom ProductBoard instance"
|
|
42
|
+
},
|
|
43
|
+
"cacheTtlSeconds": {
|
|
44
|
+
"label": "Cache TTL (seconds)",
|
|
45
|
+
"helpText": "How long to cache read operations"
|
|
46
|
+
},
|
|
47
|
+
"rateLimitPerMinute": {
|
|
48
|
+
"label": "Rate Limit (requests/minute)",
|
|
49
|
+
"helpText": "Maximum API requests per minute"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"skills": [
|
|
53
|
+
"skills/productboard-search",
|
|
54
|
+
"skills/productboard-feedback",
|
|
55
|
+
"skills/productboard-release"
|
|
56
|
+
]
|
|
57
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openclaw-productboard",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenClaw plugin for ProductBoard integration",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"openclaw": {
|
|
8
|
+
"extensions": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"clean": "rm -rf dist",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"openclaw",
|
|
18
|
+
"productboard",
|
|
19
|
+
"plugin",
|
|
20
|
+
"product-management",
|
|
21
|
+
"agent-tools",
|
|
22
|
+
"api-integration"
|
|
23
|
+
],
|
|
24
|
+
"author": "Roberto Moreno",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/robertoamoreno/openclaw-productboard.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/robertoamoreno/openclaw-productboard#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/robertoamoreno/openclaw-productboard/issues"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"axios": "^1.6.0",
|
|
36
|
+
"lru-cache": "^10.0.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"typescript": "^5.3.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"skills",
|
|
48
|
+
"openclaw.plugin.json",
|
|
49
|
+
"README.md",
|
|
50
|
+
"CHANGELOG.md",
|
|
51
|
+
"LICENSE"
|
|
52
|
+
]
|
|
53
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: productboard-feedback
|
|
3
|
+
description: Create and manage customer feedback notes in ProductBoard
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ProductBoard Feedback Skill
|
|
8
|
+
|
|
9
|
+
Capture customer feedback, feature requests, and user insights in ProductBoard. Link feedback to features to aggregate customer evidence.
|
|
10
|
+
|
|
11
|
+
## Available Tools
|
|
12
|
+
|
|
13
|
+
- `pb_note_create` - Create a new customer feedback note
|
|
14
|
+
- `pb_note_list` - List existing notes
|
|
15
|
+
- `pb_note_attach` - Link a note to a feature
|
|
16
|
+
- `pb_feature_search` - Find features to link notes to
|
|
17
|
+
- `pb_feature_list` - List features for context
|
|
18
|
+
|
|
19
|
+
## Capturing Feedback
|
|
20
|
+
|
|
21
|
+
### Creating Notes
|
|
22
|
+
|
|
23
|
+
Use `pb_note_create` with:
|
|
24
|
+
|
|
25
|
+
**Required**:
|
|
26
|
+
- `content` - The feedback text (supports HTML)
|
|
27
|
+
|
|
28
|
+
**Recommended**:
|
|
29
|
+
- `title` - Brief summary of the feedback
|
|
30
|
+
- `userEmail` - Who provided the feedback
|
|
31
|
+
- `userName` - User's name
|
|
32
|
+
- `companyName` - Their company/organization
|
|
33
|
+
- `tags` - Categories like "bug", "feature-request", "ux"
|
|
34
|
+
|
|
35
|
+
**Optional**:
|
|
36
|
+
- `displayUrl` - Link to original source (support ticket, Slack, etc.)
|
|
37
|
+
- `sourceOrigin` - System identifier (e.g., "zendesk", "intercom")
|
|
38
|
+
- `sourceRecordId` - ID in the source system
|
|
39
|
+
|
|
40
|
+
### Linking to Features
|
|
41
|
+
|
|
42
|
+
1. Find the relevant feature using `pb_feature_search` or `pb_feature_list`
|
|
43
|
+
2. Use `pb_note_attach` with the `noteId` and `featureId`
|
|
44
|
+
|
|
45
|
+
## Workflow Examples
|
|
46
|
+
|
|
47
|
+
### Capture Support Ticket Feedback
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
1. pb_note_create:
|
|
51
|
+
- content: "User reports login takes too long on mobile devices"
|
|
52
|
+
- title: "Slow mobile login"
|
|
53
|
+
- userEmail: "customer@company.com"
|
|
54
|
+
- userName: "Jane Smith"
|
|
55
|
+
- companyName: "Acme Corp"
|
|
56
|
+
- tags: ["performance", "mobile", "authentication"]
|
|
57
|
+
- sourceOrigin: "zendesk"
|
|
58
|
+
- sourceRecordId: "12345"
|
|
59
|
+
- displayUrl: "https://zendesk.com/tickets/12345"
|
|
60
|
+
|
|
61
|
+
2. pb_feature_search with query "mobile login" to find related feature
|
|
62
|
+
|
|
63
|
+
3. pb_note_attach to link the note to the feature
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Capture Feature Request
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
1. pb_note_create:
|
|
70
|
+
- content: "Would love to have dark mode support in the app"
|
|
71
|
+
- title: "Dark mode request"
|
|
72
|
+
- userEmail: "user@example.com"
|
|
73
|
+
- tags: ["feature-request", "ui", "accessibility"]
|
|
74
|
+
|
|
75
|
+
2. pb_feature_search with query "dark mode"
|
|
76
|
+
|
|
77
|
+
3. If feature exists: pb_note_attach
|
|
78
|
+
If not: Consider creating feature with pb_feature_create
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Batch Feedback Processing
|
|
82
|
+
|
|
83
|
+
1. Use `pb_note_list` to review recent unlinked notes
|
|
84
|
+
2. For each note, search for relevant features
|
|
85
|
+
3. Attach notes to appropriate features
|
|
86
|
+
|
|
87
|
+
## Best Practices
|
|
88
|
+
|
|
89
|
+
- **Always include context**: Add user email, company, and source URL when available
|
|
90
|
+
- **Use consistent tags**: Establish a tagging convention (bug, feature-request, ux, performance, etc.)
|
|
91
|
+
- **Link to features**: Attached notes contribute to feature insights and prioritization
|
|
92
|
+
- **Keep titles concise**: Use title for quick scanning, content for details
|
|
93
|
+
- **Track the source**: Include sourceOrigin and sourceRecordId for traceability
|
|
94
|
+
|
|
95
|
+
## Integration Points
|
|
96
|
+
|
|
97
|
+
- **Support Systems**: Zendesk, Intercom, Freshdesk
|
|
98
|
+
- **Communication**: Slack, Microsoft Teams, Email
|
|
99
|
+
- **Sales/CRM**: Salesforce, HubSpot, Pipedrive
|
|
100
|
+
- **User Research**: UserTesting, Hotjar, surveys
|
|
101
|
+
|
|
102
|
+
When capturing feedback from these systems, always include:
|
|
103
|
+
1. The original source URL (displayUrl)
|
|
104
|
+
2. The system name (sourceOrigin)
|
|
105
|
+
3. The record ID (sourceRecordId)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: productboard-release
|
|
3
|
+
description: Manage ProductBoard releases and roadmap planning
|
|
4
|
+
user-invocable: false
|
|
5
|
+
metadata:
|
|
6
|
+
openclaw:
|
|
7
|
+
requires:
|
|
8
|
+
env:
|
|
9
|
+
- PRODUCTBOARD_API_TOKEN
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ProductBoard Release Planning Skill
|
|
13
|
+
|
|
14
|
+
Plan and manage product releases by organizing features, tracking progress, and updating statuses in ProductBoard.
|
|
15
|
+
|
|
16
|
+
## Available Tools
|
|
17
|
+
|
|
18
|
+
- `pb_feature_create` - Create new features for releases
|
|
19
|
+
- `pb_feature_update` - Update feature status and details
|
|
20
|
+
- `pb_feature_list` - List features by status or product
|
|
21
|
+
- `pb_feature_get` - Get detailed feature information
|
|
22
|
+
- `pb_product_list` - List products
|
|
23
|
+
- `pb_product_hierarchy` - View product structure
|
|
24
|
+
- `pb_user_list` - Find users to assign as owners
|
|
25
|
+
|
|
26
|
+
## Release Planning Workflow
|
|
27
|
+
|
|
28
|
+
### 1. Review Current State
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
1. pb_product_hierarchy - Understand workspace structure
|
|
32
|
+
2. pb_feature_list with status "candidate" - Review feature candidates
|
|
33
|
+
3. pb_feature_list with status "in-progress" - Check ongoing work
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Prioritize Features
|
|
37
|
+
|
|
38
|
+
Review candidates and update their status:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
pb_feature_update:
|
|
42
|
+
- id: "feature-id"
|
|
43
|
+
- status: "in-progress" // Move to active development
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. Assign Owners
|
|
47
|
+
|
|
48
|
+
Find users and assign feature ownership:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
1. pb_user_list - Get available team members
|
|
52
|
+
2. pb_feature_update:
|
|
53
|
+
- id: "feature-id"
|
|
54
|
+
- ownerEmail: "developer@company.com"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 4. Set Timeframes
|
|
58
|
+
|
|
59
|
+
Add planning dates to features:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
pb_feature_update:
|
|
63
|
+
- id: "feature-id"
|
|
64
|
+
- startDate: "2024-01-15"
|
|
65
|
+
- endDate: "2024-02-15"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 5. Track Progress
|
|
69
|
+
|
|
70
|
+
Monitor feature statuses:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
pb_feature_list with status "in-progress" - Active development
|
|
74
|
+
pb_feature_list with status "shipped" - Completed features
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Feature Status Lifecycle
|
|
78
|
+
|
|
79
|
+
| Status | Description |
|
|
80
|
+
|--------|-------------|
|
|
81
|
+
| `new` | Just created, not yet evaluated |
|
|
82
|
+
| `candidate` | Being considered for development |
|
|
83
|
+
| `in-progress` | Actively being developed |
|
|
84
|
+
| `shipped` | Released to customers |
|
|
85
|
+
| `postponed` | Deferred to future planning |
|
|
86
|
+
| `archived` | No longer relevant |
|
|
87
|
+
|
|
88
|
+
## Planning Scenarios
|
|
89
|
+
|
|
90
|
+
### Sprint Planning
|
|
91
|
+
|
|
92
|
+
1. List candidates: `pb_feature_list` with status "candidate"
|
|
93
|
+
2. Review each feature: `pb_feature_get` for details
|
|
94
|
+
3. Move selected features to in-progress: `pb_feature_update`
|
|
95
|
+
4. Assign owners: `pb_feature_update` with ownerEmail
|
|
96
|
+
5. Set sprint dates: `pb_feature_update` with startDate/endDate
|
|
97
|
+
|
|
98
|
+
### Release Retrospective
|
|
99
|
+
|
|
100
|
+
1. List shipped features: `pb_feature_list` with status "shipped"
|
|
101
|
+
2. Review feedback on features: Use feedback skill tools
|
|
102
|
+
3. Archive completed work: `pb_feature_update` with status "archived"
|
|
103
|
+
|
|
104
|
+
### Quarterly Planning
|
|
105
|
+
|
|
106
|
+
1. Review product hierarchy: `pb_product_hierarchy`
|
|
107
|
+
2. List all active features by product
|
|
108
|
+
3. Reassess priorities and update statuses
|
|
109
|
+
4. Create new features as needed: `pb_feature_create`
|
|
110
|
+
|
|
111
|
+
## Organizing Features
|
|
112
|
+
|
|
113
|
+
### By Product
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
pb_feature_create:
|
|
117
|
+
- name: "Feature name"
|
|
118
|
+
- productId: "product-id"
|
|
119
|
+
- status: "candidate"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### By Component
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
pb_feature_create:
|
|
126
|
+
- name: "Feature name"
|
|
127
|
+
- componentId: "component-id"
|
|
128
|
+
- status: "candidate"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### As Sub-feature
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
pb_feature_create:
|
|
135
|
+
- name: "Sub-feature name"
|
|
136
|
+
- parentFeatureId: "parent-feature-id"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Best Practices
|
|
140
|
+
|
|
141
|
+
1. **Use consistent statuses**: Move features through the lifecycle systematically
|
|
142
|
+
2. **Assign owners early**: Clear ownership improves accountability
|
|
143
|
+
3. **Set realistic timeframes**: Update dates as plans change
|
|
144
|
+
4. **Organize hierarchically**: Use products, components, and sub-features
|
|
145
|
+
5. **Archive completed work**: Keep the backlog clean by archiving shipped features
|
|
146
|
+
6. **Review regularly**: Use listing tools to audit feature states
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: productboard-search
|
|
3
|
+
description: Search and explore ProductBoard features, products, and feedback
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ProductBoard Search Skill
|
|
8
|
+
|
|
9
|
+
Search and explore your ProductBoard workspace to find features, products, components, and customer feedback.
|
|
10
|
+
|
|
11
|
+
## Available Tools
|
|
12
|
+
|
|
13
|
+
- `pb_search` - Global search across all ProductBoard entities
|
|
14
|
+
- `pb_feature_list` - List features with filters
|
|
15
|
+
- `pb_feature_get` - Get detailed feature information
|
|
16
|
+
- `pb_feature_search` - Search features by name/description
|
|
17
|
+
- `pb_product_list` - List all products
|
|
18
|
+
- `pb_product_get` - Get product details with components
|
|
19
|
+
- `pb_product_hierarchy` - View full product/component tree
|
|
20
|
+
- `pb_note_list` - List customer feedback notes
|
|
21
|
+
|
|
22
|
+
## Search Strategies
|
|
23
|
+
|
|
24
|
+
### Finding Features
|
|
25
|
+
|
|
26
|
+
1. **By keyword**: Use `pb_feature_search` with a query term
|
|
27
|
+
2. **By product**: Use `pb_feature_list` with `productId` filter
|
|
28
|
+
3. **By status**: Use `pb_feature_list` with `status` filter (new, in-progress, shipped, archived)
|
|
29
|
+
4. **By component**: Use `pb_feature_list` with `componentId` filter
|
|
30
|
+
|
|
31
|
+
### Understanding Structure
|
|
32
|
+
|
|
33
|
+
1. Start with `pb_product_hierarchy` to see the complete workspace organization
|
|
34
|
+
2. Use `pb_product_get` to explore a specific product and its components
|
|
35
|
+
3. Filter features by product or component to narrow down results
|
|
36
|
+
|
|
37
|
+
### Finding Customer Feedback
|
|
38
|
+
|
|
39
|
+
1. Use `pb_note_list` to see recent feedback
|
|
40
|
+
2. Filter by date range using `createdFrom` and `createdTo`
|
|
41
|
+
3. Use `pb_search` with type `note` to find specific feedback
|
|
42
|
+
|
|
43
|
+
## Example Queries
|
|
44
|
+
|
|
45
|
+
**User**: "Find all features related to authentication"
|
|
46
|
+
**Action**: Use `pb_feature_search` with query "authentication"
|
|
47
|
+
|
|
48
|
+
**User**: "What features are currently in progress?"
|
|
49
|
+
**Action**: Use `pb_feature_list` with status "in-progress"
|
|
50
|
+
|
|
51
|
+
**User**: "Show me the product structure"
|
|
52
|
+
**Action**: Use `pb_product_hierarchy` to get the full tree
|
|
53
|
+
|
|
54
|
+
**User**: "Find customer feedback about performance"
|
|
55
|
+
**Action**: Use `pb_search` with query "performance" and type "note"
|
|
56
|
+
|
|
57
|
+
## Tips
|
|
58
|
+
|
|
59
|
+
- Start broad with `pb_search`, then narrow down with specific tools
|
|
60
|
+
- Use `pb_product_hierarchy` first when exploring an unfamiliar workspace
|
|
61
|
+
- The search is case-insensitive and matches partial words
|
|
62
|
+
- Results include direct links to ProductBoard for quick access
|