codelark 0.1.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/LICENSE +21 -0
- package/README.md +193 -0
- package/SECURITY.md +34 -0
- package/SKILL.md +67 -0
- package/agents/openai.yaml +4 -0
- package/dist/cli.mjs +8794 -0
- package/dist/daemon.mjs +47172 -0
- package/dist/ui-server.mjs +22165 -0
- package/package.json +73 -0
- package/schemas/config.v1.schema.json +259 -0
- package/schemas/data/audit.v1.schema.json +44 -0
- package/schemas/data/auto-tasks.v1.schema.json +94 -0
- package/schemas/data/channel-chats.v1.schema.json +159 -0
- package/schemas/data/channel-default-targets.v1.schema.json +43 -0
- package/schemas/data/messages.v1.schema.json +23 -0
- package/schemas/data/number-map.v1.schema.json +9 -0
- package/schemas/data/permissions.v1.schema.json +35 -0
- package/schemas/data/sessions.v1.schema.json +330 -0
- package/schemas/data/string-map.v1.schema.json +9 -0
- package/schemas/manifest.json +121 -0
- package/scripts/analyze-bridge-log.js +838 -0
- package/scripts/build-preflight.d.ts +21 -0
- package/scripts/build-preflight.js +70 -0
- package/scripts/build.js +53 -0
- package/scripts/check-npm-pack.js +46 -0
- package/scripts/daemon.ps1 +16 -0
- package/scripts/daemon.sh +206 -0
- package/scripts/doctor.ps1 +27 -0
- package/scripts/doctor.sh +185 -0
- package/scripts/hot-update-bridge.sh +298 -0
- package/scripts/install-codex-skills.sh +127 -0
- package/scripts/install-codex.sh +10 -0
- package/scripts/migrate-bindings-to-channel-chats.js +228 -0
- package/scripts/patch-codex-sdk-windows-hide.js +96 -0
- package/scripts/real-feishu-e2e.ts +5804 -0
- package/scripts/run-tests.js +83 -0
- package/scripts/setup-wizard-real-e2e.ts +195 -0
- package/scripts/supervisor-linux.sh +49 -0
- package/scripts/supervisor-macos.sh +167 -0
- package/scripts/supervisor-windows.ps1 +481 -0
- package/skills/codelark/SKILL.md +67 -0
- package/skills/codelark-auto/SKILL.md +80 -0
- package/skills/codelark-question/SKILL.md +54 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
5
|
+
CODELARK_HOME="${CODELARK_HOME:-$HOME/.codelark}"
|
|
6
|
+
LOG_DIR="$CODELARK_HOME/logs"
|
|
7
|
+
BRIDGE_LOG="$LOG_DIR/bridge.log"
|
|
8
|
+
|
|
9
|
+
usage() {
|
|
10
|
+
cat <<'USAGE'
|
|
11
|
+
Usage: bash scripts/hot-update-bridge.sh [--pull] [--skip-tests] [--dry-run] [--run]
|
|
12
|
+
|
|
13
|
+
Dispatch a detached CodeLark hot update so the current bridge-hosted
|
|
14
|
+
Codex session can survive the bridge stop/start sequence.
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--pull Run git pull before build/test/restart.
|
|
18
|
+
--skip-tests Skip npm test during this hot update.
|
|
19
|
+
--dry-run Validate environment and print the planned detached update
|
|
20
|
+
without dispatching a worker, building, testing, or restarting.
|
|
21
|
+
--run Internal worker mode. Do not call directly from a bridge session.
|
|
22
|
+
USAGE
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
USE_PULL=0
|
|
26
|
+
SKIP_TESTS=0
|
|
27
|
+
RUN_WORKER=0
|
|
28
|
+
DRY_RUN=0
|
|
29
|
+
|
|
30
|
+
while [ "$#" -gt 0 ]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
--pull)
|
|
33
|
+
USE_PULL=1
|
|
34
|
+
;;
|
|
35
|
+
--skip-tests)
|
|
36
|
+
SKIP_TESTS=1
|
|
37
|
+
;;
|
|
38
|
+
--run)
|
|
39
|
+
RUN_WORKER=1
|
|
40
|
+
;;
|
|
41
|
+
--dry-run)
|
|
42
|
+
DRY_RUN=1
|
|
43
|
+
;;
|
|
44
|
+
-h|--help)
|
|
45
|
+
usage
|
|
46
|
+
exit 0
|
|
47
|
+
;;
|
|
48
|
+
*)
|
|
49
|
+
echo "Unknown option: $1" >&2
|
|
50
|
+
usage >&2
|
|
51
|
+
exit 2
|
|
52
|
+
;;
|
|
53
|
+
esac
|
|
54
|
+
shift
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
validate_project_dir() {
|
|
58
|
+
if [ ! -f "$PROJECT_DIR/package.json" ] || [ ! -f "$PROJECT_DIR/scripts/hot-update-bridge.sh" ]; then
|
|
59
|
+
echo "[hot-update] refusing to run outside a CodeLark/codelark project directory" >&2
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
local package_name
|
|
64
|
+
package_name="$(env -u NODE_OPTIONS node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(String(pkg.name || ''));" "$PROJECT_DIR/package.json" 2>/dev/null || true)"
|
|
65
|
+
if [ "$package_name" != "codelark" ]; then
|
|
66
|
+
echo "[hot-update] refusing to run outside a CodeLark/codelark project directory" >&2
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
bridge_cli_display() {
|
|
72
|
+
if [ -f "$PROJECT_DIR/dist/cli.mjs" ]; then
|
|
73
|
+
echo "node $PROJECT_DIR/dist/cli.mjs"
|
|
74
|
+
return
|
|
75
|
+
fi
|
|
76
|
+
if command -v codelark >/dev/null 2>&1; then
|
|
77
|
+
echo "codelark"
|
|
78
|
+
return
|
|
79
|
+
fi
|
|
80
|
+
echo "codelark"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
run_bridge_cli() {
|
|
84
|
+
if [ -f "$PROJECT_DIR/dist/cli.mjs" ]; then
|
|
85
|
+
node "$PROJECT_DIR/dist/cli.mjs" "$@"
|
|
86
|
+
return
|
|
87
|
+
fi
|
|
88
|
+
if command -v codelark >/dev/null 2>&1; then
|
|
89
|
+
codelark "$@"
|
|
90
|
+
return
|
|
91
|
+
fi
|
|
92
|
+
echo "[hot-update] codelark CLI not found" >&2
|
|
93
|
+
return 127
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ensure_node24() {
|
|
97
|
+
# npm test can export npm_config_prefix, which makes nvm refuse to run.
|
|
98
|
+
# Hot update owns its Node runtime selection, so clear the incompatible prefix.
|
|
99
|
+
unset npm_config_prefix NPM_CONFIG_PREFIX
|
|
100
|
+
|
|
101
|
+
# Check if Node 24 is already available in the current environment.
|
|
102
|
+
# This handles CI environments (GitHub Actions, etc.) where Node is installed
|
|
103
|
+
# via setup-node rather than nvm.
|
|
104
|
+
if command -v node >/dev/null 2>&1; then
|
|
105
|
+
local current_major
|
|
106
|
+
current_major="$(node -p "process.versions.node.split('.')[0]" 2>/dev/null || true)"
|
|
107
|
+
if [ "$current_major" = "24" ]; then
|
|
108
|
+
return
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Try to switch to Node 24 via nvm if available.
|
|
113
|
+
local nvm_root="${NVM_DIR:-$HOME/.nvm}"
|
|
114
|
+
if [ -s "$nvm_root/nvm.sh" ]; then
|
|
115
|
+
# shellcheck source=/dev/null
|
|
116
|
+
source "$nvm_root/nvm.sh"
|
|
117
|
+
# Allow nvm use to fail gracefully if Node 24 is not installed via nvm.
|
|
118
|
+
# We'll check again below and search for a manually-installed Node 24.
|
|
119
|
+
nvm use 24 >/dev/null 2>&1 || true
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Check again after attempting nvm use.
|
|
123
|
+
if command -v node >/dev/null 2>&1; then
|
|
124
|
+
local current_major
|
|
125
|
+
current_major="$(node -p "process.versions.node.split('.')[0]" 2>/dev/null || true)"
|
|
126
|
+
if [ "$current_major" = "24" ]; then
|
|
127
|
+
return
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Search for Node 24 in common nvm installation directories.
|
|
132
|
+
local node24=""
|
|
133
|
+
local root
|
|
134
|
+
for root in \
|
|
135
|
+
"${NVM_DIR:-}" \
|
|
136
|
+
"$HOME/.nvm" \
|
|
137
|
+
"/home/${USER:-}/.nvm" \
|
|
138
|
+
"/data00/home/${USER:-}/.nvm"
|
|
139
|
+
do
|
|
140
|
+
[ -n "$root" ] || continue
|
|
141
|
+
[ -d "$root/versions/node" ] || continue
|
|
142
|
+
local candidate
|
|
143
|
+
for candidate in "$root"/versions/node/v24.*/bin/node; do
|
|
144
|
+
[ -x "$candidate" ] || continue
|
|
145
|
+
node24="$candidate"
|
|
146
|
+
done
|
|
147
|
+
done
|
|
148
|
+
if [ -n "$node24" ]; then
|
|
149
|
+
PATH="$(dirname "$node24"):$PATH"
|
|
150
|
+
export PATH
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# Final check: ensure Node 24 is now available.
|
|
154
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
155
|
+
echo "[hot-update] node is not available in PATH" >&2
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
local major
|
|
160
|
+
major="$(node -p "process.versions.node.split('.')[0]")"
|
|
161
|
+
if [ "$major" != "24" ]; then
|
|
162
|
+
echo "[hot-update] Node.js 24 is required, found $(node -v)" >&2
|
|
163
|
+
exit 1
|
|
164
|
+
fi
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
node_supports_env_proxy() {
|
|
168
|
+
node --help 2>/dev/null | grep -F -- --use-env-proxy >/dev/null 2>&1
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
run_logged() {
|
|
172
|
+
echo "[hot-update] $*"
|
|
173
|
+
"$@"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
run_worker() {
|
|
177
|
+
cd "$PROJECT_DIR"
|
|
178
|
+
mkdir -p "$LOG_DIR"
|
|
179
|
+
|
|
180
|
+
echo "[hot-update] started $(date -Is)"
|
|
181
|
+
echo "[hot-update] project: $PROJECT_DIR"
|
|
182
|
+
echo "[hot-update] bridge log: $BRIDGE_LOG"
|
|
183
|
+
|
|
184
|
+
validate_project_dir
|
|
185
|
+
|
|
186
|
+
ensure_node24
|
|
187
|
+
echo "[hot-update] node: $(node -v)"
|
|
188
|
+
|
|
189
|
+
local proxy_supported=0
|
|
190
|
+
if node_supports_env_proxy; then
|
|
191
|
+
proxy_supported=1
|
|
192
|
+
echo "[hot-update] --use-env-proxy: supported"
|
|
193
|
+
else
|
|
194
|
+
echo "[hot-update] --use-env-proxy: not supported"
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
if [ "$USE_PULL" = "1" ]; then
|
|
198
|
+
run_logged git pull
|
|
199
|
+
else
|
|
200
|
+
echo "[hot-update] git pull: skipped"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
run_logged npm run build
|
|
204
|
+
if [ "$SKIP_TESTS" = "1" ]; then
|
|
205
|
+
echo "[hot-update] npm test: skipped by --skip-tests"
|
|
206
|
+
else
|
|
207
|
+
run_logged npm test
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
local cli
|
|
211
|
+
cli="$(bridge_cli_display)"
|
|
212
|
+
if [ "$proxy_supported" = "1" ]; then
|
|
213
|
+
echo "[hot-update] restart command: NODE_OPTIONS=--use-env-proxy LITELLM_KEY=sk-local-dev $cli stop && NODE_OPTIONS=--use-env-proxy LITELLM_KEY=sk-local-dev $cli start"
|
|
214
|
+
NODE_OPTIONS=--use-env-proxy LITELLM_KEY="sk-local-dev" run_bridge_cli stop
|
|
215
|
+
NODE_OPTIONS=--use-env-proxy LITELLM_KEY="sk-local-dev" run_bridge_cli start
|
|
216
|
+
else
|
|
217
|
+
echo "[hot-update] restart command: LITELLM_KEY=sk-local-dev $cli stop && LITELLM_KEY=sk-local-dev $cli start"
|
|
218
|
+
LITELLM_KEY="sk-local-dev" run_bridge_cli stop
|
|
219
|
+
LITELLM_KEY="sk-local-dev" run_bridge_cli start
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
echo "[hot-update] completed $(date -Is)"
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
run_dry_run() {
|
|
226
|
+
cd "$PROJECT_DIR"
|
|
227
|
+
validate_project_dir
|
|
228
|
+
ensure_node24
|
|
229
|
+
|
|
230
|
+
local args=(--run)
|
|
231
|
+
if [ "$USE_PULL" = "1" ]; then
|
|
232
|
+
args+=(--pull)
|
|
233
|
+
fi
|
|
234
|
+
if [ "$SKIP_TESTS" = "1" ]; then
|
|
235
|
+
args+=(--skip-tests)
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
echo "[hot-update] dry-run: yes"
|
|
239
|
+
echo "[hot-update] project: $PROJECT_DIR"
|
|
240
|
+
echo "[hot-update] pwd: $(pwd)"
|
|
241
|
+
echo "[hot-update] script: $PROJECT_DIR/scripts/hot-update-bridge.sh"
|
|
242
|
+
echo "[hot-update] CODELARK_HOME: $CODELARK_HOME"
|
|
243
|
+
echo "[hot-update] log dir: $LOG_DIR"
|
|
244
|
+
echo "[hot-update] bridge log: $BRIDGE_LOG"
|
|
245
|
+
echo "[hot-update] node: $(node -v)"
|
|
246
|
+
if node_supports_env_proxy; then
|
|
247
|
+
echo "[hot-update] --use-env-proxy: supported"
|
|
248
|
+
else
|
|
249
|
+
echo "[hot-update] --use-env-proxy: not supported"
|
|
250
|
+
fi
|
|
251
|
+
echo "[hot-update] worker args: ${args[*]}"
|
|
252
|
+
echo "[hot-update] dispatch command: bash scripts/hot-update-bridge.sh ${args[*]}"
|
|
253
|
+
echo "[hot-update] git pull: $([ "$USE_PULL" = "1" ] && echo planned || echo skipped)"
|
|
254
|
+
echo "[hot-update] npm run build: planned"
|
|
255
|
+
echo "[hot-update] npm test: $([ "$SKIP_TESTS" = "1" ] && echo skipped || echo planned)"
|
|
256
|
+
echo "[hot-update] restart cli: $(bridge_cli_display)"
|
|
257
|
+
echo "[hot-update] restart: planned"
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
dispatch_worker() {
|
|
261
|
+
local log_stamp
|
|
262
|
+
log_stamp="$(date +%Y%m%d-%H%M%S)"
|
|
263
|
+
local log_file="$LOG_DIR/hot-update-$log_stamp.log"
|
|
264
|
+
if ! { mkdir -p "$LOG_DIR" && : >"$log_file"; } 2>/dev/null; then
|
|
265
|
+
local fallback_log_dir="${TMPDIR:-/tmp}/codelark-logs"
|
|
266
|
+
mkdir -p "$fallback_log_dir"
|
|
267
|
+
log_file="$fallback_log_dir/hot-update-$log_stamp.log"
|
|
268
|
+
: >"$log_file"
|
|
269
|
+
fi
|
|
270
|
+
local args=(--run)
|
|
271
|
+
if [ "$USE_PULL" = "1" ]; then
|
|
272
|
+
args+=(--pull)
|
|
273
|
+
fi
|
|
274
|
+
if [ "$SKIP_TESTS" = "1" ]; then
|
|
275
|
+
args+=(--skip-tests)
|
|
276
|
+
fi
|
|
277
|
+
|
|
278
|
+
if command -v setsid >/dev/null 2>&1; then
|
|
279
|
+
nohup setsid bash "$0" "${args[@]}" >"$log_file" 2>&1 </dev/null &
|
|
280
|
+
else
|
|
281
|
+
nohup bash "$0" "${args[@]}" >"$log_file" 2>&1 </dev/null &
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
echo "Dispatched CodeLark hot update."
|
|
285
|
+
echo "PID: $!"
|
|
286
|
+
echo "Hot update log: $log_file"
|
|
287
|
+
echo "Bridge log: $BRIDGE_LOG"
|
|
288
|
+
echo "Pull requested: $([ "$USE_PULL" = "1" ] && echo yes || echo no)"
|
|
289
|
+
echo "Tests skipped: $([ "$SKIP_TESTS" = "1" ] && echo yes || echo no)"
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
293
|
+
run_dry_run
|
|
294
|
+
elif [ "$RUN_WORKER" = "1" ]; then
|
|
295
|
+
run_worker
|
|
296
|
+
else
|
|
297
|
+
dispatch_worker
|
|
298
|
+
fi
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Install bundled CodeLark skills and the official Lark lark-doc skill.
|
|
5
|
+
# Usage:
|
|
6
|
+
# bash scripts/install-codex-skills.sh [--link] [skill ...]
|
|
7
|
+
#
|
|
8
|
+
# If no skill name is provided, all default skills are installed. Supported names:
|
|
9
|
+
# codelark IM attachment send-back skill
|
|
10
|
+
# codelark-question explicit question-card skill
|
|
11
|
+
# codelark-auto /auto-script helper skill
|
|
12
|
+
# lark-doc official Lark document skill from larksuite/cli
|
|
13
|
+
#
|
|
14
|
+
# --link only symlinks the primary package skill for local development.
|
|
15
|
+
|
|
16
|
+
CODEX_SKILLS_DIR="$HOME/.codex/skills"
|
|
17
|
+
SOURCE_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
18
|
+
LINK_PRIMARY=0
|
|
19
|
+
REQUESTED_SKILLS=()
|
|
20
|
+
|
|
21
|
+
for arg in "$@"; do
|
|
22
|
+
case "$arg" in
|
|
23
|
+
--link)
|
|
24
|
+
LINK_PRIMARY=1
|
|
25
|
+
;;
|
|
26
|
+
-h|--help)
|
|
27
|
+
sed -n '3,18p' "$0"
|
|
28
|
+
exit 0
|
|
29
|
+
;;
|
|
30
|
+
*)
|
|
31
|
+
REQUESTED_SKILLS+=("$arg")
|
|
32
|
+
;;
|
|
33
|
+
esac
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
if [ "${#REQUESTED_SKILLS[@]}" -eq 0 ]; then
|
|
37
|
+
REQUESTED_SKILLS=(codelark codelark-question codelark-auto lark-doc)
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "Installing CodeLark skills..."
|
|
41
|
+
echo "Target: $CODEX_SKILLS_DIR"
|
|
42
|
+
echo "Note: lark-doc is installed through the official larksuite/cli skills package."
|
|
43
|
+
echo ""
|
|
44
|
+
|
|
45
|
+
mkdir -p "$CODEX_SKILLS_DIR"
|
|
46
|
+
|
|
47
|
+
skill_source_dir() {
|
|
48
|
+
case "$1" in
|
|
49
|
+
codelark|codelark-auto|codelark-question)
|
|
50
|
+
printf '%s\n' "$SOURCE_DIR/skills/$1"
|
|
51
|
+
;;
|
|
52
|
+
lark-doc)
|
|
53
|
+
printf '%s\n' ""
|
|
54
|
+
;;
|
|
55
|
+
*)
|
|
56
|
+
echo "Error: unknown skill '$1'" >&2
|
|
57
|
+
echo "Supported skills: codelark codelark-question codelark-auto lark-doc" >&2
|
|
58
|
+
exit 1
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
install_skill_dir() {
|
|
64
|
+
local name="$1"
|
|
65
|
+
local source_dir="$2"
|
|
66
|
+
local target_dir="$CODEX_SKILLS_DIR/$name"
|
|
67
|
+
if [ ! -f "$source_dir/SKILL.md" ]; then
|
|
68
|
+
echo "Error: SKILL.md not found in $source_dir"
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
if [ -e "$target_dir" ]; then
|
|
72
|
+
if [ -L "$target_dir" ]; then
|
|
73
|
+
local existing
|
|
74
|
+
existing=$(readlink "$target_dir")
|
|
75
|
+
echo "Already installed: $name -> $existing"
|
|
76
|
+
else
|
|
77
|
+
echo "Already installed: $target_dir"
|
|
78
|
+
fi
|
|
79
|
+
return
|
|
80
|
+
fi
|
|
81
|
+
cp -R "$source_dir" "$target_dir"
|
|
82
|
+
echo "Copied $name to: $target_dir"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for skill in "${REQUESTED_SKILLS[@]}"; do
|
|
86
|
+
if [ "$skill" = "lark-doc" ]; then
|
|
87
|
+
echo "Installing official lark-doc skill..."
|
|
88
|
+
npx skills add larksuite/cli -s lark-doc -y -g -a claude-code
|
|
89
|
+
continue
|
|
90
|
+
fi
|
|
91
|
+
source_dir="$(skill_source_dir "$skill")"
|
|
92
|
+
target_dir="$CODEX_SKILLS_DIR/$skill"
|
|
93
|
+
if [ "$skill" = "codelark" ] && [ "$LINK_PRIMARY" -eq 1 ]; then
|
|
94
|
+
if [ -e "$target_dir" ]; then
|
|
95
|
+
echo "Already installed: $target_dir"
|
|
96
|
+
else
|
|
97
|
+
ln -s "$source_dir" "$target_dir"
|
|
98
|
+
echo "Symlinked: $target_dir -> $source_dir"
|
|
99
|
+
fi
|
|
100
|
+
else
|
|
101
|
+
install_skill_dir "$skill" "$source_dir"
|
|
102
|
+
fi
|
|
103
|
+
done
|
|
104
|
+
|
|
105
|
+
TARGET_DIR="$CODEX_SKILLS_DIR/codelark"
|
|
106
|
+
if [[ " ${REQUESTED_SKILLS[*]} " == *" codelark "* ]] && [ "$LINK_PRIMARY" -eq 0 ]; then
|
|
107
|
+
if [ ! -d "$TARGET_DIR/node_modules" ] || [ ! -d "$TARGET_DIR/node_modules/@openai/codex-sdk" ]; then
|
|
108
|
+
echo "Installing dependencies for codelark..."
|
|
109
|
+
(cd "$TARGET_DIR" && npm install)
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
if [ ! -f "$TARGET_DIR/dist/daemon.mjs" ]; then
|
|
113
|
+
echo "Building daemon bundle for codelark..."
|
|
114
|
+
(cd "$TARGET_DIR" && npm run build)
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
echo "Pruning dev dependencies for codelark..."
|
|
118
|
+
(cd "$TARGET_DIR" && npm prune --production)
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
if [ "$LINK_PRIMARY" -eq 1 ]; then
|
|
122
|
+
echo ""
|
|
123
|
+
echo "Development mode: no install/build/prune steps were run against the source repo."
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
echo ""
|
|
127
|
+
echo "Done. Start a new Codex session for newly installed skills to be discoverable."
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
|
|
6
|
+
echo "scripts/install-codex.sh is kept for compatibility."
|
|
7
|
+
echo "Use scripts/install-codex-skills.sh for the clearer manual CodeLark skill installer."
|
|
8
|
+
echo ""
|
|
9
|
+
|
|
10
|
+
exec "$SCRIPT_DIR/install-codex-skills.sh" "$@"
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
function usage() {
|
|
7
|
+
return [
|
|
8
|
+
'Usage: node scripts/migrate-bindings-to-channel-chats.js [--codelark-home <path>] [--clk-home <path>] [--dry-run]',
|
|
9
|
+
'',
|
|
10
|
+
'Migrates data/bindings.json to data/channel-chats.json.',
|
|
11
|
+
'For each channel/chat pair, keeps the newest active legacy record where active=true and drops inactive records.',
|
|
12
|
+
'Moves binding workingDirectory/model/mode/chatDisplayName into the linked session when session fields are empty.',
|
|
13
|
+
].join('\n');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseArgs(argv) {
|
|
17
|
+
const out = {
|
|
18
|
+
codelarkHome: process.env.CODELARK_HOME || path.join(os.homedir(), '.codelark'),
|
|
19
|
+
dryRun: false,
|
|
20
|
+
};
|
|
21
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
22
|
+
const arg = argv[i];
|
|
23
|
+
if (arg === '--dry-run') {
|
|
24
|
+
out.dryRun = true;
|
|
25
|
+
} else if (arg === '--codelark-home' || arg === '--clk-home') {
|
|
26
|
+
const value = argv[++i];
|
|
27
|
+
if (!value) throw new Error(`${arg} requires a path`);
|
|
28
|
+
out.codelarkHome = value;
|
|
29
|
+
} else if (arg === '-h' || arg === '--help') {
|
|
30
|
+
console.log(usage());
|
|
31
|
+
process.exit(0);
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readJsonObject(filePath) {
|
|
40
|
+
if (!fs.existsSync(filePath)) return {};
|
|
41
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
44
|
+
throw new Error(`${filePath} must contain a JSON object`);
|
|
45
|
+
}
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function atomicWriteJson(filePath, data) {
|
|
50
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
51
|
+
const tmp = `${filePath}.tmp`;
|
|
52
|
+
fs.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}\n`, 'utf-8');
|
|
53
|
+
fs.renameSync(tmp, filePath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readString(record, key) {
|
|
57
|
+
const value = record?.[key];
|
|
58
|
+
return typeof value === 'string' ? value : '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readBoolean(record, key) {
|
|
62
|
+
const value = record?.[key];
|
|
63
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalizeChatKind(value) {
|
|
67
|
+
if (value === 'p2p' || value === 'group') return value;
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function updatedTime(record) {
|
|
72
|
+
const parsed = Date.parse(readString(record, 'updatedAt') || readString(record, 'createdAt'));
|
|
73
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeMode(value) {
|
|
77
|
+
return value === 'yolo' ? 'yolo' : 'normal';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ensureRecord(parent, key) {
|
|
81
|
+
if (!parent[key] || typeof parent[key] !== 'object' || Array.isArray(parent[key])) {
|
|
82
|
+
parent[key] = {};
|
|
83
|
+
}
|
|
84
|
+
return parent[key];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function chooseActiveBinding(records) {
|
|
88
|
+
return records
|
|
89
|
+
.filter((record) => readBoolean(record, 'active') === true)
|
|
90
|
+
.sort((a, b) => updatedTime(b) - updatedTime(a))[0];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function toChannelChat(binding) {
|
|
94
|
+
return {
|
|
95
|
+
id: readString(binding, 'id'),
|
|
96
|
+
channelType: readString(binding, 'channelType'),
|
|
97
|
+
...(readString(binding, 'channelProvider') ? { channelProvider: readString(binding, 'channelProvider') } : {}),
|
|
98
|
+
...(readString(binding, 'channelAlias') ? { channelAlias: readString(binding, 'channelAlias') } : {}),
|
|
99
|
+
chatId: readString(binding, 'chatId'),
|
|
100
|
+
...(normalizeChatKind(binding.chatKind) ? { chatKind: normalizeChatKind(binding.chatKind) } : {}),
|
|
101
|
+
...(readString(binding, 'chatUserId') ? { chatUserId: readString(binding, 'chatUserId') } : {}),
|
|
102
|
+
bridgeSessionId: readString(binding, 'bridgeSessionId'),
|
|
103
|
+
createdAt: readString(binding, 'createdAt') || new Date().toISOString(),
|
|
104
|
+
updatedAt: readString(binding, 'updatedAt') || new Date().toISOString(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function migrateSessionFromBinding(session, binding) {
|
|
109
|
+
let changed = false;
|
|
110
|
+
const workingDirectory = readString(binding, 'workingDirectory');
|
|
111
|
+
const model = readString(binding, 'model');
|
|
112
|
+
const chatDisplayName = readString(binding, 'chatDisplayName');
|
|
113
|
+
const mode = normalizeMode(binding?.mode);
|
|
114
|
+
const runtime = ensureRecord(session, 'runtime');
|
|
115
|
+
const codex = ensureRecord(runtime, 'codex');
|
|
116
|
+
const existingModel = readString(session, 'model');
|
|
117
|
+
const existingMode = readString(session, 'preferred_mode');
|
|
118
|
+
|
|
119
|
+
if (existingModel && !readString(codex, 'model')) {
|
|
120
|
+
codex.model = existingModel;
|
|
121
|
+
changed = true;
|
|
122
|
+
}
|
|
123
|
+
if (existingMode && !readString(codex, 'mode')) {
|
|
124
|
+
codex.mode = normalizeMode(existingMode);
|
|
125
|
+
changed = true;
|
|
126
|
+
}
|
|
127
|
+
if ('model' in session) {
|
|
128
|
+
delete session.model;
|
|
129
|
+
changed = true;
|
|
130
|
+
}
|
|
131
|
+
if ('preferred_mode' in session) {
|
|
132
|
+
delete session.preferred_mode;
|
|
133
|
+
changed = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (workingDirectory && !readString(session, 'working_directory')) {
|
|
137
|
+
session.working_directory = workingDirectory;
|
|
138
|
+
changed = true;
|
|
139
|
+
}
|
|
140
|
+
if (model && !readString(codex, 'model')) {
|
|
141
|
+
codex.model = model;
|
|
142
|
+
changed = true;
|
|
143
|
+
}
|
|
144
|
+
if (mode && !readString(codex, 'mode')) {
|
|
145
|
+
codex.mode = mode;
|
|
146
|
+
changed = true;
|
|
147
|
+
}
|
|
148
|
+
if (chatDisplayName && !readString(session, 'name')) {
|
|
149
|
+
session.name = chatDisplayName;
|
|
150
|
+
changed = true;
|
|
151
|
+
}
|
|
152
|
+
if (changed) {
|
|
153
|
+
session.updated_at = readString(session, 'updated_at') || new Date().toISOString();
|
|
154
|
+
}
|
|
155
|
+
return changed;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function main() {
|
|
159
|
+
const options = parseArgs(process.argv.slice(2));
|
|
160
|
+
const dataDir = path.join(options.codelarkHome, 'data');
|
|
161
|
+
const bindingsPath = path.join(dataDir, 'bindings.json');
|
|
162
|
+
const channelChatsPath = path.join(dataDir, 'channel-chats.json');
|
|
163
|
+
const sessionsPath = path.join(dataDir, 'sessions.json');
|
|
164
|
+
|
|
165
|
+
const bindings = readJsonObject(bindingsPath);
|
|
166
|
+
const sessions = readJsonObject(sessionsPath);
|
|
167
|
+
const byChat = new Map();
|
|
168
|
+
for (const [key, binding] of Object.entries(bindings)) {
|
|
169
|
+
if (!binding || typeof binding !== 'object') continue;
|
|
170
|
+
const id = readString(binding, 'id') || key;
|
|
171
|
+
const normalized = { ...binding, id };
|
|
172
|
+
const channelType = readString(normalized, 'channelType');
|
|
173
|
+
const chatId = readString(normalized, 'chatId');
|
|
174
|
+
const bridgeSessionId = readString(normalized, 'bridgeSessionId');
|
|
175
|
+
if (!channelType || !chatId || !bridgeSessionId) continue;
|
|
176
|
+
const chatKey = `${channelType}:${chatId}`;
|
|
177
|
+
byChat.set(chatKey, [...(byChat.get(chatKey) || []), normalized]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const channelChats = {};
|
|
181
|
+
let sessionsChanged = 0;
|
|
182
|
+
let droppedBindings = 0;
|
|
183
|
+
let skippedInactiveBindings = 0;
|
|
184
|
+
for (const records of byChat.values()) {
|
|
185
|
+
const kept = chooseActiveBinding(records);
|
|
186
|
+
if (!kept) {
|
|
187
|
+
skippedInactiveBindings += records.length;
|
|
188
|
+
droppedBindings += records.length;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const inactiveCount = records.filter((record) => readBoolean(record, 'active') !== true).length;
|
|
192
|
+
skippedInactiveBindings += inactiveCount;
|
|
193
|
+
droppedBindings += records.length - 1;
|
|
194
|
+
const chat = toChannelChat(kept);
|
|
195
|
+
channelChats[chat.id] = chat;
|
|
196
|
+
const session = sessions[chat.bridgeSessionId];
|
|
197
|
+
if (session && typeof session === 'object' && migrateSessionFromBinding(session, kept)) {
|
|
198
|
+
sessionsChanged += 1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const summary = {
|
|
203
|
+
codelarkHome: options.codelarkHome,
|
|
204
|
+
inputBindings: Object.keys(bindings).length,
|
|
205
|
+
outputChannelChats: Object.keys(channelChats).length,
|
|
206
|
+
droppedBindings,
|
|
207
|
+
skippedInactiveBindings,
|
|
208
|
+
sessionsChanged,
|
|
209
|
+
dryRun: options.dryRun,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (!options.dryRun) {
|
|
213
|
+
atomicWriteJson(channelChatsPath, channelChats);
|
|
214
|
+
atomicWriteJson(sessionsPath, sessions);
|
|
215
|
+
fs.rmSync(bindingsPath, { force: true });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
main();
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
225
|
+
console.error('');
|
|
226
|
+
console.error(usage());
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|