@sodikinnaa/smart-report-plugin 2100.11.3 → 2100.11.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -1
- package/dist/index.js +97 -60
- package/index.js +17 -0
- package/install.sh +359 -0
- package/openclaw.plugin.json +2 -2
- package/package.json +3 -3
- package/src/index.ts +47 -18
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Plugin integrasi resmi untuk menghubungkan OpenClaw dengan ekosistem **Smart Rep
|
|
|
4
4
|
|
|
5
5
|
## 🚀 Quick Start
|
|
6
6
|
```bash
|
|
7
|
-
# 1. Instal Plugin
|
|
7
|
+
# 1. Instal Plugin (NPM)
|
|
8
8
|
openclaw plugins install @sodikinnaa/smart-report-plugin
|
|
9
9
|
|
|
10
10
|
# 2. Aktivasi Token
|
|
@@ -14,6 +14,54 @@ openclaw smart-auth YOUR_SECRET_TOKEN
|
|
|
14
14
|
openclaw smart-status
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## 🛠️ Instalasi Manual via `install.sh` (Recommended untuk server custom)
|
|
18
|
+
Script `install.sh` akan:
|
|
19
|
+
- clone source plugin dari GitHub,
|
|
20
|
+
- install dependency + build,
|
|
21
|
+
- verifikasi export `register/activate`,
|
|
22
|
+
- copy plugin ke `~/.openclaw/extensions/smart-report-plugin`,
|
|
23
|
+
- update `plugins.allow` (jika `~/.openclaw/config.json` tersedia),
|
|
24
|
+
- restart OpenClaw Gateway.
|
|
25
|
+
|
|
26
|
+
### 1) Jalankan langsung dari GitHub
|
|
27
|
+
```bash
|
|
28
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/sodikinnaa/smart-report-plugin/master/install.sh)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2) Opsi parameter
|
|
32
|
+
```bash
|
|
33
|
+
# Install branch tertentu
|
|
34
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/sodikinnaa/smart-report-plugin/master/install.sh) --branch master
|
|
35
|
+
|
|
36
|
+
# Custom repository
|
|
37
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/sodikinnaa/smart-report-plugin/master/install.sh) --repo https://github.com/sodikinnaa/smart-report-plugin.git
|
|
38
|
+
|
|
39
|
+
# Jika repo private (token)
|
|
40
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/sodikinnaa/smart-report-plugin/master/install.sh) --token <GITHUB_TOKEN>
|
|
41
|
+
|
|
42
|
+
# Skip build / restart
|
|
43
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/sodikinnaa/smart-report-plugin/master/install.sh) --skip-build
|
|
44
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/sodikinnaa/smart-report-plugin/master/install.sh) --no-restart
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 3) Verifikasi setelah instalasi
|
|
48
|
+
```bash
|
|
49
|
+
openclaw smart-auth <TOKEN_SMART_REPORT>
|
|
50
|
+
openclaw smart-status
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 4) Troubleshooting cepat
|
|
54
|
+
```bash
|
|
55
|
+
# Cek plugin terpasang
|
|
56
|
+
ls -la ~/.openclaw/extensions/smart-report-plugin
|
|
57
|
+
|
|
58
|
+
# Cek export register/activate
|
|
59
|
+
node -e "const m=require(process.env.HOME+'/.openclaw/extensions/smart-report-plugin/dist/openclaw.cjs'); console.log(typeof m, typeof m.register, typeof m.activate)"
|
|
60
|
+
|
|
61
|
+
# Restart gateway manual
|
|
62
|
+
openclaw gateway restart
|
|
63
|
+
```
|
|
64
|
+
|
|
17
65
|
## ✅ Command Tambahan
|
|
18
66
|
- `openclaw smart-auth <token>` → menyimpan dan validasi token Smart Report.
|
|
19
67
|
- `openclaw smart-status` → health-check semua fungsi MCP utama:
|
package/dist/index.js
CHANGED
|
@@ -8,11 +8,37 @@ exports.activate = exports.register = void 0;
|
|
|
8
8
|
* Smart Report MCP Plugin for OpenClaw
|
|
9
9
|
*/
|
|
10
10
|
const axios_1 = __importDefault(require("axios"));
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
11
13
|
const PLUGIN_ID = 'smart-report-plugin';
|
|
12
|
-
const API_BASE = 'https://
|
|
14
|
+
const API_BASE = 'https://smartreport.siapdigital.my.id/api/mcp';
|
|
15
|
+
function resolveToken(api) {
|
|
16
|
+
return api?.pluginConfig?.apiToken
|
|
17
|
+
|| api?.config?.apiToken
|
|
18
|
+
|| api?.config?.plugins?.entries?.[PLUGIN_ID]?.config?.apiToken;
|
|
19
|
+
}
|
|
20
|
+
function saveTokenFallback(token, companyInfo) {
|
|
21
|
+
const cfgPath = path_1.default.join(process.env.HOME || '/root', '.openclaw', 'openclaw.json');
|
|
22
|
+
if (!fs_1.default.existsSync(cfgPath))
|
|
23
|
+
return;
|
|
24
|
+
const cfg = JSON.parse(fs_1.default.readFileSync(cfgPath, 'utf8'));
|
|
25
|
+
cfg.plugins = cfg.plugins || {};
|
|
26
|
+
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
27
|
+
cfg.plugins.entries[PLUGIN_ID] = cfg.plugins.entries[PLUGIN_ID] || {};
|
|
28
|
+
cfg.plugins.entries[PLUGIN_ID].enabled = true;
|
|
29
|
+
cfg.plugins.entries[PLUGIN_ID].config = cfg.plugins.entries[PLUGIN_ID].config || {};
|
|
30
|
+
cfg.plugins.entries[PLUGIN_ID].config.apiToken = token;
|
|
31
|
+
if (companyInfo?.name)
|
|
32
|
+
cfg.plugins.entries[PLUGIN_ID].config.companyName = companyInfo.name;
|
|
33
|
+
if (companyInfo?.domain)
|
|
34
|
+
cfg.plugins.entries[PLUGIN_ID].config.companyDomain = companyInfo.domain;
|
|
35
|
+
cfg.plugins.allow = Array.isArray(cfg.plugins.allow) ? cfg.plugins.allow : [];
|
|
36
|
+
if (!cfg.plugins.allow.includes(PLUGIN_ID))
|
|
37
|
+
cfg.plugins.allow.push(PLUGIN_ID);
|
|
38
|
+
fs_1.default.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
|
|
39
|
+
}
|
|
13
40
|
async function callMcp(api, method, params = {}) {
|
|
14
|
-
const
|
|
15
|
-
const token = config?.apiToken;
|
|
41
|
+
const token = resolveToken(api);
|
|
16
42
|
if (!token) {
|
|
17
43
|
throw new Error('API Token not found. Please run "openclaw smart-auth <token>" first.');
|
|
18
44
|
}
|
|
@@ -36,7 +62,7 @@ async function callMcp(api, method, params = {}) {
|
|
|
36
62
|
const plugin = {
|
|
37
63
|
id: PLUGIN_ID,
|
|
38
64
|
name: "Smart Report Integration",
|
|
39
|
-
version: "2100.11.
|
|
65
|
+
version: "2100.11.5",
|
|
40
66
|
register(api) {
|
|
41
67
|
// 1. CLI Commands
|
|
42
68
|
api.registerCli(({ program }) => {
|
|
@@ -47,13 +73,19 @@ const plugin = {
|
|
|
47
73
|
process.stdout.write('🔍 Verifying token and fetching company info...');
|
|
48
74
|
try {
|
|
49
75
|
// Temporarily set token to verify
|
|
50
|
-
api.
|
|
76
|
+
api.pluginConfig = api.pluginConfig || {};
|
|
77
|
+
api.pluginConfig.apiToken = token;
|
|
51
78
|
const companyInfo = await callMcp(api, 'company/info', {});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
if (typeof api.saveConfig === 'function') {
|
|
80
|
+
await api.saveConfig({
|
|
81
|
+
apiToken: token,
|
|
82
|
+
companyName: companyInfo.name,
|
|
83
|
+
companyDomain: companyInfo.domain
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
saveTokenFallback(token, companyInfo);
|
|
88
|
+
}
|
|
57
89
|
console.log(`\r✅ Authenticated for: ${companyInfo.name} (${companyInfo.domain})`);
|
|
58
90
|
console.log(' Smart Report API Token saved successfully.');
|
|
59
91
|
}
|
|
@@ -103,56 +135,61 @@ const plugin = {
|
|
|
103
135
|
});
|
|
104
136
|
}, { commands: ['smart-auth', 'smart-status'] });
|
|
105
137
|
// 2. Resources
|
|
106
|
-
api.registerResource
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
138
|
+
if (typeof api.registerResource === 'function')
|
|
139
|
+
api.registerResource({
|
|
140
|
+
uri: 'smartreport://reports',
|
|
141
|
+
name: 'Recent Reports',
|
|
142
|
+
description: 'Stream of latest submitted reports',
|
|
143
|
+
mimeType: 'application/json',
|
|
144
|
+
read: async () => {
|
|
145
|
+
const data = await callMcp(api, 'reports/list', { per_page: 10 });
|
|
146
|
+
return { content: JSON.stringify(data, null, 2) };
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
if (typeof api.registerResource === 'function')
|
|
150
|
+
api.registerResource({
|
|
151
|
+
uri: 'smartreport://employees',
|
|
152
|
+
name: 'Employee List',
|
|
153
|
+
description: 'Complete list of active employees with division names',
|
|
154
|
+
mimeType: 'application/json',
|
|
155
|
+
read: async () => {
|
|
156
|
+
const data = await callMcp(api, 'employees/list', {});
|
|
157
|
+
return { content: JSON.stringify(data, null, 2) };
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
if (typeof api.registerResource === 'function')
|
|
161
|
+
api.registerResource({
|
|
162
|
+
uri: 'smartreport://divisions',
|
|
163
|
+
name: 'Division List',
|
|
164
|
+
description: 'List of all divisions in the company',
|
|
165
|
+
mimeType: 'application/json',
|
|
166
|
+
read: async () => {
|
|
167
|
+
const data = await callMcp(api, 'divisions/list', {});
|
|
168
|
+
return { content: JSON.stringify(data, null, 2) };
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
if (typeof api.registerResource === 'function')
|
|
172
|
+
api.registerResource({
|
|
173
|
+
uri: 'smartreport://guides',
|
|
174
|
+
name: 'Guides List',
|
|
175
|
+
description: 'List of all available dynamic guides',
|
|
176
|
+
mimeType: 'application/json',
|
|
177
|
+
read: async () => {
|
|
178
|
+
const data = await callMcp(api, 'guides/list', {});
|
|
179
|
+
return { content: JSON.stringify(data, null, 2) };
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
if (typeof api.registerResource === 'function')
|
|
183
|
+
api.registerResource({
|
|
184
|
+
uri: 'smartreport://dashboard',
|
|
185
|
+
name: 'Daily Dashboard',
|
|
186
|
+
description: 'Real-time KPI dashboard (stats, highlights, alerts)',
|
|
187
|
+
mimeType: 'application/json',
|
|
188
|
+
read: async (params) => {
|
|
189
|
+
const data = await callMcp(api, 'smartreport/dashboard', params || {});
|
|
190
|
+
return { content: JSON.stringify(data, null, 2) };
|
|
191
|
+
}
|
|
192
|
+
});
|
|
156
193
|
// 3. Agent Tools
|
|
157
194
|
api.registerTool({
|
|
158
195
|
name: 'get_daily_dashboard',
|
package/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Compatibility entry for OpenClaw loaders across versions.
|
|
2
|
+
// Always expose register/activate as direct CommonJS exports.
|
|
3
|
+
const mod = require('./dist/index.js');
|
|
4
|
+
|
|
5
|
+
const register =
|
|
6
|
+
mod.register ||
|
|
7
|
+
mod.activate ||
|
|
8
|
+
(mod.default && (mod.default.register || mod.default.activate)) ||
|
|
9
|
+
(typeof mod === 'function' ? mod : undefined);
|
|
10
|
+
|
|
11
|
+
if (typeof register !== 'function') {
|
|
12
|
+
throw new Error('smart-report-plugin: register/activate export not found');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = register;
|
|
16
|
+
module.exports.register = register;
|
|
17
|
+
module.exports.activate = register;
|
package/install.sh
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install.sh — Manual installer for Smart Report Plugin (OpenClaw)
|
|
3
|
+
# Usage:
|
|
4
|
+
# bash install.sh
|
|
5
|
+
# bash install.sh --branch master
|
|
6
|
+
# bash install.sh --repo https://github.com/sodikinnaa/smart-report-plugin.git --token <gh_token>
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
# -----------------------------
|
|
11
|
+
# Defaults
|
|
12
|
+
# -----------------------------
|
|
13
|
+
PLUGIN_ID="smart-report-plugin"
|
|
14
|
+
REPO_URL="https://github.com/sodikinnaa/smart-report-plugin.git"
|
|
15
|
+
BRANCH="master"
|
|
16
|
+
TARGET_DIR="${HOME}/.openclaw/extensions/${PLUGIN_ID}"
|
|
17
|
+
SKIP_BUILD="0"
|
|
18
|
+
NO_RESTART="0"
|
|
19
|
+
GITHUB_TOKEN="${GITHUB_TOKEN:-}"
|
|
20
|
+
|
|
21
|
+
# -----------------------------
|
|
22
|
+
# UI helpers
|
|
23
|
+
# -----------------------------
|
|
24
|
+
info() { echo " $*"; }
|
|
25
|
+
success() { echo "✅ $*"; }
|
|
26
|
+
warn() { echo "⚠️ $*"; }
|
|
27
|
+
fatal() { echo "❌ $*" >&2; exit 1; }
|
|
28
|
+
|
|
29
|
+
usage() {
|
|
30
|
+
cat <<EOF
|
|
31
|
+
Usage: install.sh [options]
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--repo <url> Git repo URL (default: ${REPO_URL})
|
|
35
|
+
--branch <name> Branch/tag to install (default: ${BRANCH})
|
|
36
|
+
--target <path> Target plugin dir (default: ${TARGET_DIR})
|
|
37
|
+
--token <token> GitHub token for private repo (or set GITHUB_TOKEN env)
|
|
38
|
+
--skip-build Skip npm install/build step
|
|
39
|
+
--no-restart Skip openclaw gateway restart
|
|
40
|
+
-h, --help Show this help
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
bash install.sh
|
|
44
|
+
bash install.sh --branch master
|
|
45
|
+
bash install.sh --repo https://github.com/sodikinnaa/smart-report-plugin.git --token ghp_xxx
|
|
46
|
+
EOF
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
parse_args() {
|
|
50
|
+
while [[ $# -gt 0 ]]; do
|
|
51
|
+
case "$1" in
|
|
52
|
+
--repo)
|
|
53
|
+
[[ $# -ge 2 ]] || fatal "Missing value for --repo"
|
|
54
|
+
REPO_URL="$2"; shift 2 ;;
|
|
55
|
+
--branch)
|
|
56
|
+
[[ $# -ge 2 ]] || fatal "Missing value for --branch"
|
|
57
|
+
BRANCH="$2"; shift 2 ;;
|
|
58
|
+
--target)
|
|
59
|
+
[[ $# -ge 2 ]] || fatal "Missing value for --target"
|
|
60
|
+
TARGET_DIR="$2"; shift 2 ;;
|
|
61
|
+
--token)
|
|
62
|
+
[[ $# -ge 2 ]] || fatal "Missing value for --token"
|
|
63
|
+
GITHUB_TOKEN="$2"; shift 2 ;;
|
|
64
|
+
--skip-build)
|
|
65
|
+
SKIP_BUILD="1"; shift ;;
|
|
66
|
+
--no-restart)
|
|
67
|
+
NO_RESTART="1"; shift ;;
|
|
68
|
+
-h|--help)
|
|
69
|
+
usage; exit 0 ;;
|
|
70
|
+
*)
|
|
71
|
+
fatal "Unknown option: $1"
|
|
72
|
+
;;
|
|
73
|
+
esac
|
|
74
|
+
done
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
need_cmd() {
|
|
78
|
+
command -v "$1" >/dev/null 2>&1 || fatal "Command not found: $1"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
with_token_repo_url() {
|
|
82
|
+
local url="$1"
|
|
83
|
+
local token="$2"
|
|
84
|
+
|
|
85
|
+
if [[ -z "$token" ]]; then
|
|
86
|
+
printf '%s\n' "$url"
|
|
87
|
+
return
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
if [[ "$url" =~ ^https://github.com/ ]]; then
|
|
91
|
+
printf '%s\n' "${url/https:\/\/github.com\//https:\/\/x-access-token:${token}@github.com\/}"
|
|
92
|
+
return
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
printf '%s\n' "$url"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
cleanup_legacy_backups_in_extensions() {
|
|
99
|
+
local ext_dir="${HOME}/.openclaw/extensions"
|
|
100
|
+
if [[ -d "$ext_dir" ]]; then
|
|
101
|
+
find "$ext_dir" -maxdepth 1 -type d -name "${PLUGIN_ID}.backup.*" -print0 2>/dev/null | while IFS= read -r -d '' d; do
|
|
102
|
+
rm -rf "$d" || true
|
|
103
|
+
warn "Removed legacy backup inside extensions: $d"
|
|
104
|
+
done
|
|
105
|
+
fi
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
sync_openclaw_plugin_config() {
|
|
109
|
+
local plugin_id="$1"
|
|
110
|
+
local found_any="0"
|
|
111
|
+
|
|
112
|
+
# Candidate locations across common OpenClaw setups (prioritize openclaw.json)
|
|
113
|
+
local candidates=(
|
|
114
|
+
"${HOME}/.openclaw/openclaw.json"
|
|
115
|
+
"/root/.openclaw/openclaw.json"
|
|
116
|
+
"/etc/openclaw/openclaw.json"
|
|
117
|
+
"${HOME}/.openclaw/config.json"
|
|
118
|
+
"${HOME}/.config/openclaw/config.json"
|
|
119
|
+
"/root/.openclaw/config.json"
|
|
120
|
+
"/root/.config/openclaw/config.json"
|
|
121
|
+
"/etc/openclaw/config.json"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Also discover additional openclaw*.json files (depth-limited, safe)
|
|
125
|
+
while IFS= read -r f; do
|
|
126
|
+
[[ -n "$f" ]] || continue
|
|
127
|
+
candidates+=("$f")
|
|
128
|
+
done < <(find /root /etc -maxdepth 6 -type f \( -name 'openclaw.json' -o -name 'config.json' \) -path '*openclaw*' 2>/dev/null | sort -u)
|
|
129
|
+
|
|
130
|
+
# De-duplicate candidates
|
|
131
|
+
local uniq=()
|
|
132
|
+
local c
|
|
133
|
+
for c in "${candidates[@]}"; do
|
|
134
|
+
[[ -n "$c" ]] || continue
|
|
135
|
+
if [[ ! " ${uniq[*]} " =~ " ${c} " ]]; then
|
|
136
|
+
uniq+=("$c")
|
|
137
|
+
fi
|
|
138
|
+
done
|
|
139
|
+
|
|
140
|
+
for cfg in "${uniq[@]}"; do
|
|
141
|
+
[[ -f "$cfg" ]] || continue
|
|
142
|
+
found_any="1"
|
|
143
|
+
|
|
144
|
+
node <<'NODE' "$cfg" "$plugin_id"
|
|
145
|
+
const fs = require('fs');
|
|
146
|
+
const cfgPath = process.argv[2];
|
|
147
|
+
const pluginId = process.argv[3];
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const raw = fs.readFileSync(cfgPath, 'utf8');
|
|
151
|
+
const cfg = JSON.parse(raw || '{}');
|
|
152
|
+
|
|
153
|
+
cfg.plugins = cfg.plugins || {};
|
|
154
|
+
|
|
155
|
+
// 1) pin trust allowlist
|
|
156
|
+
cfg.plugins.allow = Array.isArray(cfg.plugins.allow) ? cfg.plugins.allow : [];
|
|
157
|
+
if (!cfg.plugins.allow.includes(pluginId)) cfg.plugins.allow.push(pluginId);
|
|
158
|
+
|
|
159
|
+
// 2) hard-clean stale records (entries + installs)
|
|
160
|
+
if (cfg.plugins.entries && typeof cfg.plugins.entries === 'object') {
|
|
161
|
+
delete cfg.plugins.entries[pluginId];
|
|
162
|
+
}
|
|
163
|
+
if (cfg.plugins.installs && typeof cfg.plugins.installs === 'object') {
|
|
164
|
+
delete cfg.plugins.installs[pluginId];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 3) force discovery path for manual fallback installs
|
|
168
|
+
cfg.plugins.load = cfg.plugins.load || {};
|
|
169
|
+
cfg.plugins.load.paths = Array.isArray(cfg.plugins.load.paths) ? cfg.plugins.load.paths : [];
|
|
170
|
+
const manualPath = `${process.env.HOME || '/root'}/.openclaw/extensions/${pluginId}`;
|
|
171
|
+
if (!cfg.plugins.load.paths.includes(manualPath)) cfg.plugins.load.paths.push(manualPath);
|
|
172
|
+
|
|
173
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
|
|
174
|
+
console.log('synced plugin config:', cfgPath);
|
|
175
|
+
} catch (e) {
|
|
176
|
+
console.error('failed to sync config', cfgPath, e.message);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
NODE
|
|
180
|
+
done
|
|
181
|
+
|
|
182
|
+
if [[ "$found_any" == "1" ]]; then
|
|
183
|
+
success "Konfigurasi plugin disinkronkan (allowlist + stale cleanup)"
|
|
184
|
+
else
|
|
185
|
+
warn "Tidak menemukan config OpenClaw; skip sync config otomatis"
|
|
186
|
+
fi
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
TMP_DIR=""
|
|
190
|
+
cleanup() {
|
|
191
|
+
if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then
|
|
192
|
+
rm -rf "${TMP_DIR}" || true
|
|
193
|
+
fi
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
main() {
|
|
197
|
+
parse_args "$@"
|
|
198
|
+
|
|
199
|
+
need_cmd git
|
|
200
|
+
need_cmd node
|
|
201
|
+
need_cmd npm
|
|
202
|
+
|
|
203
|
+
TMP_DIR="$(mktemp -d /tmp/smart-report-plugin-install-XXXXXX)"
|
|
204
|
+
trap cleanup EXIT
|
|
205
|
+
|
|
206
|
+
local clone_url
|
|
207
|
+
clone_url="$(with_token_repo_url "$REPO_URL" "$GITHUB_TOKEN")"
|
|
208
|
+
|
|
209
|
+
info "Cloning repo..."
|
|
210
|
+
git clone --depth 1 --branch "$BRANCH" "$clone_url" "$TMP_DIR/repo" >/dev/null 2>&1 || fatal "Gagal clone repo: $REPO_URL (branch: $BRANCH)"
|
|
211
|
+
success "Repo cloned (${BRANCH})"
|
|
212
|
+
|
|
213
|
+
if [[ "$SKIP_BUILD" != "1" ]]; then
|
|
214
|
+
info "Installing npm dependencies..."
|
|
215
|
+
(cd "$TMP_DIR/repo" && npm ci >/dev/null 2>&1) || (cd "$TMP_DIR/repo" && npm install >/dev/null 2>&1) || fatal "npm install gagal"
|
|
216
|
+
|
|
217
|
+
info "Building plugin..."
|
|
218
|
+
(cd "$TMP_DIR/repo" && npm run build >/dev/null 2>&1) || fatal "npm run build gagal"
|
|
219
|
+
success "Build selesai"
|
|
220
|
+
else
|
|
221
|
+
warn "Build di-skip (--skip-build)"
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
[[ -f "$TMP_DIR/repo/openclaw.plugin.json" ]] || fatal "openclaw.plugin.json tidak ditemukan"
|
|
225
|
+
[[ -f "$TMP_DIR/repo/index.js" ]] || fatal "index.js tidak ditemukan"
|
|
226
|
+
[[ -f "$TMP_DIR/repo/dist/index.js" ]] || fatal "dist/index.js tidak ditemukan"
|
|
227
|
+
|
|
228
|
+
info "Verifying register/activate export..."
|
|
229
|
+
node -e "const m=require('$TMP_DIR/repo/index.js'); const ok=(typeof m==='function'||typeof m.register==='function'||typeof m.activate==='function'); if(!ok){throw new Error('register/activate export not found')} console.log('OK');" >/dev/null || fatal "Plugin export invalid (register/activate tidak ada)"
|
|
230
|
+
success "Export register/activate valid"
|
|
231
|
+
|
|
232
|
+
mkdir -p "$(dirname "$TARGET_DIR")"
|
|
233
|
+
|
|
234
|
+
# Preferred path: let OpenClaw register install metadata itself.
|
|
235
|
+
if command -v openclaw >/dev/null 2>&1; then
|
|
236
|
+
info "Installing via OpenClaw plugin manager..."
|
|
237
|
+
|
|
238
|
+
# Remove stale install record if present (safe if plugin not installed)
|
|
239
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
240
|
+
timeout 20 openclaw plugins uninstall "$PLUGIN_ID" >/dev/null 2>&1 || true
|
|
241
|
+
else
|
|
242
|
+
openclaw plugins uninstall "$PLUGIN_ID" >/dev/null 2>&1 || true
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# Install from local prepared repo path (contains built dist)
|
|
246
|
+
# Remove existing dir first to avoid "plugin already exists" hard-fail.
|
|
247
|
+
rm -rf "$TARGET_DIR" >/dev/null 2>&1 || true
|
|
248
|
+
|
|
249
|
+
local install_ok="0"
|
|
250
|
+
local install_log="$TMP_DIR/openclaw-install.log"
|
|
251
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
252
|
+
if timeout 45 openclaw plugins install "$TMP_DIR/repo" >"$install_log" 2>&1; then
|
|
253
|
+
install_ok="1"
|
|
254
|
+
else
|
|
255
|
+
warn "openclaw plugins install timeout/gagal (>45s), fallback ke manual copy"
|
|
256
|
+
sed -n '1,80p' "$install_log" || true
|
|
257
|
+
fi
|
|
258
|
+
else
|
|
259
|
+
if openclaw plugins install "$TMP_DIR/repo" >"$install_log" 2>&1; then
|
|
260
|
+
install_ok="1"
|
|
261
|
+
else
|
|
262
|
+
warn "openclaw plugins install gagal, fallback ke manual copy"
|
|
263
|
+
sed -n '1,80p' "$install_log" || true
|
|
264
|
+
fi
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
if [[ "$install_ok" == "1" ]]; then
|
|
268
|
+
success "Plugin installed via openclaw plugins install"
|
|
269
|
+
|
|
270
|
+
# Validate installed artifact, because some OpenClaw versions may resolve stale package contents.
|
|
271
|
+
if ! node -e "const p='$TARGET_DIR/dist/index.js'; const m=require(p); const ok=(typeof m.register==='function'||typeof m.activate==='function'||typeof m==='function'); if(!ok) process.exit(1);" >/dev/null 2>&1; then
|
|
272
|
+
warn "Installed artifact invalid (register/activate not found), forcing manual overwrite from built source"
|
|
273
|
+
install_ok="0"
|
|
274
|
+
fi
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
if [[ "$install_ok" != "1" ]]; then
|
|
278
|
+
if [[ -d "$TARGET_DIR" ]]; then
|
|
279
|
+
local backup_root backup_dir
|
|
280
|
+
backup_root="${HOME}/.openclaw/extensions-backup/${PLUGIN_ID}"
|
|
281
|
+
mkdir -p "$backup_root"
|
|
282
|
+
backup_dir="${backup_root}/$(date +%Y%m%d-%H%M%S)"
|
|
283
|
+
mv "$TARGET_DIR" "$backup_dir"
|
|
284
|
+
warn "Backup plugin lama -> ${backup_dir}"
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
mkdir -p "$TARGET_DIR"
|
|
288
|
+
cp -R \
|
|
289
|
+
"$TMP_DIR/repo/openclaw.plugin.json" \
|
|
290
|
+
"$TMP_DIR/repo/package.json" \
|
|
291
|
+
"$TMP_DIR/repo/package-lock.json" \
|
|
292
|
+
"$TMP_DIR/repo/index.js" \
|
|
293
|
+
"$TMP_DIR/repo/dist" \
|
|
294
|
+
"$TMP_DIR/repo/src" \
|
|
295
|
+
"$TMP_DIR/repo/skills" \
|
|
296
|
+
"$TMP_DIR/repo/README.md" \
|
|
297
|
+
"$TARGET_DIR/"
|
|
298
|
+
|
|
299
|
+
(cd "$TARGET_DIR" && npm install --omit=dev >/dev/null 2>&1) || warn "npm install dependency plugin gagal"
|
|
300
|
+
success "Plugin installed to: ${TARGET_DIR}"
|
|
301
|
+
fi
|
|
302
|
+
else
|
|
303
|
+
# Fallback for environments without openclaw CLI in PATH.
|
|
304
|
+
if [[ -d "$TARGET_DIR" ]]; then
|
|
305
|
+
local backup_root backup_dir
|
|
306
|
+
backup_root="${HOME}/.openclaw/extensions-backup/${PLUGIN_ID}"
|
|
307
|
+
mkdir -p "$backup_root"
|
|
308
|
+
backup_dir="${backup_root}/$(date +%Y%m%d-%H%M%S)"
|
|
309
|
+
mv "$TARGET_DIR" "$backup_dir"
|
|
310
|
+
warn "Backup plugin lama -> ${backup_dir}"
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
mkdir -p "$TARGET_DIR"
|
|
314
|
+
cp -R \
|
|
315
|
+
"$TMP_DIR/repo/openclaw.plugin.json" \
|
|
316
|
+
"$TMP_DIR/repo/package.json" \
|
|
317
|
+
"$TMP_DIR/repo/package-lock.json" \
|
|
318
|
+
"$TMP_DIR/repo/index.js" \
|
|
319
|
+
"$TMP_DIR/repo/dist" \
|
|
320
|
+
"$TMP_DIR/repo/skills" \
|
|
321
|
+
"$TMP_DIR/repo/README.md" \
|
|
322
|
+
"$TARGET_DIR/"
|
|
323
|
+
|
|
324
|
+
(cd "$TARGET_DIR" && npm install --omit=dev >/dev/null 2>&1) || warn "npm install dependency plugin gagal"
|
|
325
|
+
success "Plugin installed to: ${TARGET_DIR}"
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
cleanup_legacy_backups_in_extensions
|
|
329
|
+
|
|
330
|
+
if command -v openclaw >/dev/null 2>&1; then
|
|
331
|
+
sync_openclaw_plugin_config "$PLUGIN_ID" || warn "Tidak bisa sinkronisasi config otomatis"
|
|
332
|
+
|
|
333
|
+
if [[ "$NO_RESTART" != "1" ]]; then
|
|
334
|
+
info "Restarting OpenClaw gateway..."
|
|
335
|
+
openclaw gateway restart >/dev/null 2>&1 || warn "Gateway restart gagal. Jalankan manual: openclaw gateway restart"
|
|
336
|
+
else
|
|
337
|
+
warn "Restart di-skip (--no-restart)"
|
|
338
|
+
fi
|
|
339
|
+
else
|
|
340
|
+
warn "openclaw command tidak ditemukan, skip config + restart"
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
cat <<EOF
|
|
344
|
+
|
|
345
|
+
🎉 Instalasi selesai.
|
|
346
|
+
|
|
347
|
+
Langkah verifikasi:
|
|
348
|
+
1) openclaw plugins list --verbose
|
|
349
|
+
2) openclaw smart-auth <TOKEN_SMART_REPORT>
|
|
350
|
+
3) openclaw smart-status
|
|
351
|
+
|
|
352
|
+
Jika masih error:
|
|
353
|
+
- Jalankan: openclaw plugins doctor
|
|
354
|
+
- Cek entrypoint: ${TARGET_DIR}/openclaw.plugin.json
|
|
355
|
+
- Cek export: node -e "const m=require('${TARGET_DIR}/dist/index.js'); console.log(typeof m, typeof m.register, typeof m.activate)"
|
|
356
|
+
EOF
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
main "$@"
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "smart-report-plugin",
|
|
3
3
|
"name": "Smart Report Integration",
|
|
4
|
-
"version": "2100.11.
|
|
4
|
+
"version": "2100.11.5",
|
|
5
5
|
"description": "Integration plugin for Smart Report and AI Analytics with Daily Dashboard and Dynamic Guides",
|
|
6
|
-
"entrypoint": "./
|
|
6
|
+
"entrypoint": "./index.js",
|
|
7
7
|
"skills": [
|
|
8
8
|
"skills/smart-report/SKILL.md"
|
|
9
9
|
],
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sodikinnaa/smart-report-plugin",
|
|
3
|
-
"version": "2100.11.
|
|
3
|
+
"version": "2100.11.5",
|
|
4
4
|
"description": "OpenClaw plugin for Smart Report system integration with Daily Dashboard",
|
|
5
|
-
"main": "./
|
|
5
|
+
"main": "./index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc && cp ./openclaw.cjs ./dist/openclaw.cjs",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"openclaw": {
|
|
20
20
|
"extensions": [
|
|
21
|
-
"./
|
|
21
|
+
"./index.js"
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -2,14 +2,38 @@
|
|
|
2
2
|
* Smart Report MCP Plugin for OpenClaw
|
|
3
3
|
*/
|
|
4
4
|
import axios from 'axios';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
5
7
|
|
|
6
8
|
const PLUGIN_ID = 'smart-report-plugin';
|
|
7
|
-
const API_BASE = 'https://
|
|
9
|
+
const API_BASE = 'https://smartreport.siapdigital.my.id/api/mcp';
|
|
10
|
+
|
|
11
|
+
function resolveToken(api: any): string | undefined {
|
|
12
|
+
return api?.pluginConfig?.apiToken
|
|
13
|
+
|| api?.config?.apiToken
|
|
14
|
+
|| api?.config?.plugins?.entries?.[PLUGIN_ID]?.config?.apiToken;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function saveTokenFallback(token: string, companyInfo?: any) {
|
|
18
|
+
const cfgPath = path.join(process.env.HOME || '/root', '.openclaw', 'openclaw.json');
|
|
19
|
+
if (!fs.existsSync(cfgPath)) return;
|
|
20
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
21
|
+
cfg.plugins = cfg.plugins || {};
|
|
22
|
+
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
23
|
+
cfg.plugins.entries[PLUGIN_ID] = cfg.plugins.entries[PLUGIN_ID] || {};
|
|
24
|
+
cfg.plugins.entries[PLUGIN_ID].enabled = true;
|
|
25
|
+
cfg.plugins.entries[PLUGIN_ID].config = cfg.plugins.entries[PLUGIN_ID].config || {};
|
|
26
|
+
cfg.plugins.entries[PLUGIN_ID].config.apiToken = token;
|
|
27
|
+
if (companyInfo?.name) cfg.plugins.entries[PLUGIN_ID].config.companyName = companyInfo.name;
|
|
28
|
+
if (companyInfo?.domain) cfg.plugins.entries[PLUGIN_ID].config.companyDomain = companyInfo.domain;
|
|
29
|
+
cfg.plugins.allow = Array.isArray(cfg.plugins.allow) ? cfg.plugins.allow : [];
|
|
30
|
+
if (!cfg.plugins.allow.includes(PLUGIN_ID)) cfg.plugins.allow.push(PLUGIN_ID);
|
|
31
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
|
|
32
|
+
}
|
|
8
33
|
|
|
9
34
|
async function callMcp(api: any, method: string, params: any = {}) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
35
|
+
const token = resolveToken(api);
|
|
36
|
+
|
|
13
37
|
if (!token) {
|
|
14
38
|
throw new Error('API Token not found. Please run "openclaw smart-auth <token>" first.');
|
|
15
39
|
}
|
|
@@ -37,7 +61,7 @@ async function callMcp(api: any, method: string, params: any = {}) {
|
|
|
37
61
|
const plugin: any = {
|
|
38
62
|
id: PLUGIN_ID,
|
|
39
63
|
name: "Smart Report Integration",
|
|
40
|
-
version: "2100.11.
|
|
64
|
+
version: "2100.11.5",
|
|
41
65
|
|
|
42
66
|
register(api: any) {
|
|
43
67
|
// 1. CLI Commands
|
|
@@ -49,15 +73,20 @@ const plugin: any = {
|
|
|
49
73
|
process.stdout.write('🔍 Verifying token and fetching company info...');
|
|
50
74
|
try {
|
|
51
75
|
// Temporarily set token to verify
|
|
52
|
-
api.
|
|
76
|
+
api.pluginConfig = api.pluginConfig || {};
|
|
77
|
+
api.pluginConfig.apiToken = token;
|
|
53
78
|
const companyInfo = await callMcp(api, 'company/info', {});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
|
|
80
|
+
if (typeof api.saveConfig === 'function') {
|
|
81
|
+
await api.saveConfig({
|
|
82
|
+
apiToken: token,
|
|
83
|
+
companyName: companyInfo.name,
|
|
84
|
+
companyDomain: companyInfo.domain
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
saveTokenFallback(token, companyInfo);
|
|
88
|
+
}
|
|
89
|
+
|
|
61
90
|
console.log(`\r✅ Authenticated for: ${companyInfo.name} (${companyInfo.domain})`);
|
|
62
91
|
console.log(' Smart Report API Token saved successfully.');
|
|
63
92
|
} catch (err: any) {
|
|
@@ -110,7 +139,7 @@ const plugin: any = {
|
|
|
110
139
|
}, { commands: ['smart-auth', 'smart-status'] });
|
|
111
140
|
|
|
112
141
|
// 2. Resources
|
|
113
|
-
api.registerResource({
|
|
142
|
+
if (typeof api.registerResource === 'function') api.registerResource({
|
|
114
143
|
uri: 'smartreport://reports',
|
|
115
144
|
name: 'Recent Reports',
|
|
116
145
|
description: 'Stream of latest submitted reports',
|
|
@@ -121,7 +150,7 @@ const plugin: any = {
|
|
|
121
150
|
}
|
|
122
151
|
});
|
|
123
152
|
|
|
124
|
-
api.registerResource({
|
|
153
|
+
if (typeof api.registerResource === 'function') api.registerResource({
|
|
125
154
|
uri: 'smartreport://employees',
|
|
126
155
|
name: 'Employee List',
|
|
127
156
|
description: 'Complete list of active employees with division names',
|
|
@@ -132,7 +161,7 @@ const plugin: any = {
|
|
|
132
161
|
}
|
|
133
162
|
});
|
|
134
163
|
|
|
135
|
-
api.registerResource({
|
|
164
|
+
if (typeof api.registerResource === 'function') api.registerResource({
|
|
136
165
|
uri: 'smartreport://divisions',
|
|
137
166
|
name: 'Division List',
|
|
138
167
|
description: 'List of all divisions in the company',
|
|
@@ -143,7 +172,7 @@ const plugin: any = {
|
|
|
143
172
|
}
|
|
144
173
|
});
|
|
145
174
|
|
|
146
|
-
api.registerResource({
|
|
175
|
+
if (typeof api.registerResource === 'function') api.registerResource({
|
|
147
176
|
uri: 'smartreport://guides',
|
|
148
177
|
name: 'Guides List',
|
|
149
178
|
description: 'List of all available dynamic guides',
|
|
@@ -154,7 +183,7 @@ const plugin: any = {
|
|
|
154
183
|
}
|
|
155
184
|
});
|
|
156
185
|
|
|
157
|
-
api.registerResource({
|
|
186
|
+
if (typeof api.registerResource === 'function') api.registerResource({
|
|
158
187
|
uri: 'smartreport://dashboard',
|
|
159
188
|
name: 'Daily Dashboard',
|
|
160
189
|
description: 'Real-time KPI dashboard (stats, highlights, alerts)',
|