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,21 @@
|
|
|
1
|
+
export function readPackageJson(packageJsonUrl: string | URL): Promise<{ dependencies?: Record<string, string> }>;
|
|
2
|
+
|
|
3
|
+
export function hasInstalledPackage(
|
|
4
|
+
dependencyName: string,
|
|
5
|
+
options?: {
|
|
6
|
+
accessFile?: (path: string) => Promise<void>;
|
|
7
|
+
resolvePaths?: (dependencyName: string) => string[] | null;
|
|
8
|
+
},
|
|
9
|
+
): Promise<boolean>;
|
|
10
|
+
|
|
11
|
+
export function findMissingRuntimeDependencies(
|
|
12
|
+
dependencies: Record<string, string> | undefined,
|
|
13
|
+
options?: {
|
|
14
|
+
accessFile?: (path: string) => Promise<void>;
|
|
15
|
+
resolvePaths?: (dependencyName: string) => string[] | null;
|
|
16
|
+
},
|
|
17
|
+
): Promise<string[]>;
|
|
18
|
+
|
|
19
|
+
export function findMissingPackageJsonRuntimeDependencies(packageJsonUrl: string | URL): Promise<string[]>;
|
|
20
|
+
|
|
21
|
+
export function formatMissingRuntimeDependenciesMessage(missingDependencies: string[]): string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const missingPackagePathErrorCodes = new Set(['ENOENT', 'ENOTDIR']);
|
|
6
|
+
|
|
7
|
+
export async function readPackageJson(packageJsonUrl) {
|
|
8
|
+
const raw = await readFile(packageJsonUrl, 'utf8');
|
|
9
|
+
return JSON.parse(raw);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function hasInstalledPackage(dependencyName, options = {}) {
|
|
13
|
+
const accessFile = options.accessFile ?? access;
|
|
14
|
+
const resolvePaths = options.resolvePaths ?? (() => []);
|
|
15
|
+
|
|
16
|
+
for (const nodeModulesPath of resolvePaths(dependencyName) ?? []) {
|
|
17
|
+
const packageJsonPath = path.join(nodeModulesPath, dependencyName, 'package.json');
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
await accessFile(packageJsonPath);
|
|
21
|
+
return true;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (!missingPackagePathErrorCodes.has(error?.code)) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function findMissingRuntimeDependencies(dependencies, options = {}) {
|
|
33
|
+
const dependencyNames = Object.keys(dependencies ?? {}).sort();
|
|
34
|
+
const missing = [];
|
|
35
|
+
|
|
36
|
+
for (const dependencyName of dependencyNames) {
|
|
37
|
+
if (!(await hasInstalledPackage(dependencyName, options))) {
|
|
38
|
+
missing.push(dependencyName);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return missing;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function findMissingPackageJsonRuntimeDependencies(packageJsonUrl) {
|
|
46
|
+
const packageJson = await readPackageJson(packageJsonUrl);
|
|
47
|
+
const packageRequire = createRequire(packageJsonUrl);
|
|
48
|
+
|
|
49
|
+
return findMissingRuntimeDependencies(packageJson.dependencies, {
|
|
50
|
+
resolvePaths: (dependencyName) => packageRequire.resolve.paths(dependencyName),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function formatMissingRuntimeDependenciesMessage(missingDependencies) {
|
|
55
|
+
const dependencyList = missingDependencies.map((dependencyName) => ` - ${dependencyName}`).join('\n');
|
|
56
|
+
|
|
57
|
+
return [
|
|
58
|
+
'CodeLark build cannot start because package.json runtime dependencies are not installed:',
|
|
59
|
+
dependencyList,
|
|
60
|
+
'',
|
|
61
|
+
'Install dependencies first:',
|
|
62
|
+
' npm ci',
|
|
63
|
+
'',
|
|
64
|
+
'If you are updating an existing checkout, npm install is also acceptable:',
|
|
65
|
+
' npm install',
|
|
66
|
+
'',
|
|
67
|
+
'Then rerun:',
|
|
68
|
+
' npm run build',
|
|
69
|
+
].join('\n');
|
|
70
|
+
}
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findMissingPackageJsonRuntimeDependencies,
|
|
3
|
+
formatMissingRuntimeDependenciesMessage,
|
|
4
|
+
} from './build-preflight.js';
|
|
5
|
+
|
|
6
|
+
const nodeMajor = Number((process.versions.node || '0').split('.')[0]);
|
|
7
|
+
if (!Number.isFinite(nodeMajor) || nodeMajor < 24) {
|
|
8
|
+
console.error(`CodeLark build requires Node.js 24 or newer. Current Node.js: ${process.version}.`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const packageJsonUrl = new URL('../package.json', import.meta.url);
|
|
13
|
+
const missingRuntimeDependencies = await findMissingPackageJsonRuntimeDependencies(packageJsonUrl);
|
|
14
|
+
if (missingRuntimeDependencies.length > 0) {
|
|
15
|
+
console.error(formatMissingRuntimeDependenciesMessage(missingRuntimeDependencies));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const esbuild = await import('esbuild');
|
|
20
|
+
|
|
21
|
+
const common = {
|
|
22
|
+
bundle: true,
|
|
23
|
+
platform: 'node',
|
|
24
|
+
format: 'esm',
|
|
25
|
+
target: 'node24',
|
|
26
|
+
external: [
|
|
27
|
+
'@openai/codex-sdk',
|
|
28
|
+
// Keep large IM SDKs external so global/local npm installs resolve them
|
|
29
|
+
// from node_modules instead of inflating daemon.mjs.
|
|
30
|
+
'@larksuiteoapi/node-sdk',
|
|
31
|
+
// ws optional native deps
|
|
32
|
+
'bufferutil', 'utf-8-validate',
|
|
33
|
+
// Node.js built-ins
|
|
34
|
+
'fs', 'path', 'os', 'crypto', 'http', 'https', 'net', 'tls',
|
|
35
|
+
'stream', 'events', 'url', 'util', 'child_process', 'worker_threads',
|
|
36
|
+
'node:*',
|
|
37
|
+
],
|
|
38
|
+
banner: { js: "import { createRequire as __codelarkCreateRequire } from 'module'; const require = __codelarkCreateRequire(import.meta.url);" },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
async function build(entryPoint, outfile) {
|
|
42
|
+
await esbuild.build({
|
|
43
|
+
...common,
|
|
44
|
+
entryPoints: [entryPoint],
|
|
45
|
+
outfile,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await build('src/entrypoints/daemon.ts', 'dist/daemon.mjs');
|
|
50
|
+
await build('src/operator-ui/server.ts', 'dist/ui-server.mjs');
|
|
51
|
+
await build('src/entrypoints/cli.ts', 'dist/cli.mjs');
|
|
52
|
+
|
|
53
|
+
console.log('Built dist/daemon.mjs, dist/ui-server.mjs, dist/cli.mjs');
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
const forbiddenPrefixes = [
|
|
4
|
+
'package/docs/',
|
|
5
|
+
'package/docs/.vitepress/',
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
const forbiddenFiles = new Set([
|
|
9
|
+
'package/README_EN.md',
|
|
10
|
+
'package/config.env.example',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const result = spawnSync('npm', ['pack', '--dry-run', '--json'], {
|
|
14
|
+
cwd: process.cwd(),
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (result.status !== 0) {
|
|
20
|
+
process.stderr.write(result.stderr);
|
|
21
|
+
process.exit(result.status ?? 1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let packEntries;
|
|
25
|
+
try {
|
|
26
|
+
packEntries = JSON.parse(result.stdout);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Unable to parse npm pack --dry-run --json output.');
|
|
29
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const files = packEntries.flatMap((entry) => entry.files?.map((file) => file.path) ?? []);
|
|
34
|
+
const forbiddenMatches = files.filter((file) =>
|
|
35
|
+
forbiddenFiles.has(file) || forbiddenPrefixes.some((prefix) => file.startsWith(prefix)),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (forbiddenMatches.length > 0) {
|
|
39
|
+
console.error('Unexpected files would be included in the npm package:');
|
|
40
|
+
for (const file of forbiddenMatches) {
|
|
41
|
+
console.error(`- ${file}`);
|
|
42
|
+
}
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`npm package dry-run passed: ${files.length} files, no docs or example env files included.`);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Windows entry point — delegates to supervisor-windows.ps1.
|
|
4
|
+
.DESCRIPTION
|
|
5
|
+
Usage: powershell -File scripts\daemon.ps1 start|stop|status|logs|install-service|uninstall-service
|
|
6
|
+
#>
|
|
7
|
+
param(
|
|
8
|
+
[Parameter(Position=0)]
|
|
9
|
+
[string]$Command = 'help',
|
|
10
|
+
|
|
11
|
+
[Parameter(Position=1)]
|
|
12
|
+
[int]$LogLines = 50
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
$supervisorScript = Join-Path (Split-Path -Parent $PSCommandPath) 'supervisor-windows.ps1'
|
|
16
|
+
& $supervisorScript $Command $LogLines
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
CODELARK_HOME="${CODELARK_HOME:-$HOME/.codelark}"
|
|
4
|
+
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
5
|
+
PID_FILE="$CODELARK_HOME/runtime/bridge.pid"
|
|
6
|
+
STATUS_FILE="$CODELARK_HOME/runtime/status.json"
|
|
7
|
+
LOG_FILE="$CODELARK_HOME/logs/bridge.log"
|
|
8
|
+
|
|
9
|
+
# ── Common helpers ──
|
|
10
|
+
|
|
11
|
+
ensure_dirs() { mkdir -p "$CODELARK_HOME"/{data,logs,runtime,data/messages}; }
|
|
12
|
+
|
|
13
|
+
ensure_built() {
|
|
14
|
+
local need_build=0
|
|
15
|
+
if [ ! -f "$SKILL_DIR/dist/daemon.mjs" ]; then
|
|
16
|
+
need_build=1
|
|
17
|
+
else
|
|
18
|
+
# Check if any source file is newer than the bundle
|
|
19
|
+
local newest_src
|
|
20
|
+
newest_src=$(find "$SKILL_DIR/src" -name '*.ts' -newer "$SKILL_DIR/dist/daemon.mjs" 2>/dev/null | head -1)
|
|
21
|
+
if [ -n "$newest_src" ]; then
|
|
22
|
+
need_build=1
|
|
23
|
+
fi
|
|
24
|
+
# Also check if node_modules/codelark was updated (npm update)
|
|
25
|
+
# — its code is bundled into dist, so changes require a rebuild
|
|
26
|
+
if [ "$need_build" = "0" ] && [ -d "$SKILL_DIR/node_modules/codelark/src" ]; then
|
|
27
|
+
local newest_dep
|
|
28
|
+
newest_dep=$(find "$SKILL_DIR/node_modules/codelark/src" -name '*.ts' -newer "$SKILL_DIR/dist/daemon.mjs" 2>/dev/null | head -1)
|
|
29
|
+
if [ -n "$newest_dep" ]; then
|
|
30
|
+
need_build=1
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
33
|
+
fi
|
|
34
|
+
if [ "$need_build" = "1" ]; then
|
|
35
|
+
echo "Building daemon bundle..."
|
|
36
|
+
(cd "$SKILL_DIR" && npm run build)
|
|
37
|
+
fi
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Clean environment for subprocess isolation.
|
|
41
|
+
clean_env() {
|
|
42
|
+
unset CLAUDECODE 2>/dev/null || true
|
|
43
|
+
|
|
44
|
+
local mode="${CODELARK_ENV_ISOLATION:-inherit}"
|
|
45
|
+
if [ "$mode" = "strict" ]; then
|
|
46
|
+
while IFS='=' read -r name _; do
|
|
47
|
+
case "$name" in ANTHROPIC_*) unset "$name" 2>/dev/null || true ;; esac
|
|
48
|
+
done < <(env)
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
read_pid() {
|
|
53
|
+
[ -f "$PID_FILE" ] && cat "$PID_FILE" 2>/dev/null || echo ""
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pid_alive() {
|
|
57
|
+
local pid="$1"
|
|
58
|
+
[ -n "$pid" ] && kill -0 "$pid" 2>/dev/null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
status_running() {
|
|
62
|
+
[ -f "$STATUS_FILE" ] && grep -q '"running"[[:space:]]*:[[:space:]]*true' "$STATUS_FILE" 2>/dev/null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
show_last_exit_reason() {
|
|
66
|
+
if [ -f "$STATUS_FILE" ]; then
|
|
67
|
+
local reason
|
|
68
|
+
reason=$(grep -o '"lastExitReason"[[:space:]]*:[[:space:]]*"[^"]*"' "$STATUS_FILE" 2>/dev/null | head -1 | sed 's/.*: *"//;s/"$//')
|
|
69
|
+
[ -n "$reason" ] && echo "Last exit reason: $reason"
|
|
70
|
+
fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
show_failure_help() {
|
|
74
|
+
echo ""
|
|
75
|
+
echo "Recent logs:"
|
|
76
|
+
tail -20 "$LOG_FILE" 2>/dev/null || echo " (no log file)"
|
|
77
|
+
echo ""
|
|
78
|
+
echo "Next steps:"
|
|
79
|
+
echo " 1. Run diagnostics: bash \"$SKILL_DIR/scripts/doctor.sh\""
|
|
80
|
+
echo " 2. Check full logs: bash \"$SKILL_DIR/scripts/daemon.sh\" logs 100"
|
|
81
|
+
echo " 3. Rebuild bundle: cd \"$SKILL_DIR\" && npm run build"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# ── Load platform-specific supervisor ──
|
|
85
|
+
|
|
86
|
+
case "$(uname -s)" in
|
|
87
|
+
Darwin)
|
|
88
|
+
# shellcheck source=supervisor-macos.sh
|
|
89
|
+
source "$SKILL_DIR/scripts/supervisor-macos.sh"
|
|
90
|
+
;;
|
|
91
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
92
|
+
# Windows detected via Git Bash / MSYS2 / Cygwin — delegate to PowerShell
|
|
93
|
+
echo "Windows detected. Delegating to supervisor-windows.ps1..."
|
|
94
|
+
powershell.exe -ExecutionPolicy Bypass -File "$SKILL_DIR/scripts/supervisor-windows.ps1" "$@"
|
|
95
|
+
exit $?
|
|
96
|
+
;;
|
|
97
|
+
*)
|
|
98
|
+
# shellcheck source=supervisor-linux.sh
|
|
99
|
+
source "$SKILL_DIR/scripts/supervisor-linux.sh"
|
|
100
|
+
;;
|
|
101
|
+
esac
|
|
102
|
+
|
|
103
|
+
# ── Commands ──
|
|
104
|
+
|
|
105
|
+
case "${1:-help}" in
|
|
106
|
+
start)
|
|
107
|
+
ensure_dirs
|
|
108
|
+
ensure_built
|
|
109
|
+
|
|
110
|
+
# Check if already running (supervisor-aware: launchctl on macOS, PID on Linux)
|
|
111
|
+
if supervisor_is_running; then
|
|
112
|
+
EXISTING_PID=$(read_pid)
|
|
113
|
+
echo "Bridge already running${EXISTING_PID:+ (PID: $EXISTING_PID)}"
|
|
114
|
+
cat "$STATUS_FILE" 2>/dev/null
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Source config.env BEFORE clean_env so CODELARK_* flags are available.
|
|
119
|
+
[ -f "$CODELARK_HOME/config.env" ] && set -a && source "$CODELARK_HOME/config.env" && set +a
|
|
120
|
+
|
|
121
|
+
clean_env
|
|
122
|
+
echo "Starting bridge..."
|
|
123
|
+
supervisor_start
|
|
124
|
+
|
|
125
|
+
# Poll for up to 10 seconds waiting for status.json to report running
|
|
126
|
+
STARTED=false
|
|
127
|
+
for _ in $(seq 1 10); do
|
|
128
|
+
sleep 1
|
|
129
|
+
if status_running; then
|
|
130
|
+
STARTED=true
|
|
131
|
+
break
|
|
132
|
+
fi
|
|
133
|
+
# If supervisor process already died, stop waiting
|
|
134
|
+
if ! supervisor_is_running; then
|
|
135
|
+
break
|
|
136
|
+
fi
|
|
137
|
+
done
|
|
138
|
+
|
|
139
|
+
if [ "$STARTED" = "true" ]; then
|
|
140
|
+
NEW_PID=$(read_pid)
|
|
141
|
+
echo "Bridge started${NEW_PID:+ (PID: $NEW_PID)}"
|
|
142
|
+
cat "$STATUS_FILE" 2>/dev/null
|
|
143
|
+
else
|
|
144
|
+
echo "Failed to start bridge."
|
|
145
|
+
supervisor_is_running || echo " Process not running."
|
|
146
|
+
status_running || echo " status.json not reporting running=true."
|
|
147
|
+
show_last_exit_reason
|
|
148
|
+
show_failure_help
|
|
149
|
+
exit 1
|
|
150
|
+
fi
|
|
151
|
+
;;
|
|
152
|
+
|
|
153
|
+
stop)
|
|
154
|
+
if supervisor_is_managed; then
|
|
155
|
+
echo "Stopping bridge..."
|
|
156
|
+
supervisor_stop
|
|
157
|
+
echo "Bridge stopped"
|
|
158
|
+
else
|
|
159
|
+
PID=$(read_pid)
|
|
160
|
+
if [ -z "$PID" ]; then echo "No bridge running"; exit 0; fi
|
|
161
|
+
if pid_alive "$PID"; then
|
|
162
|
+
kill "$PID"
|
|
163
|
+
for _ in $(seq 1 10); do
|
|
164
|
+
pid_alive "$PID" || break
|
|
165
|
+
sleep 1
|
|
166
|
+
done
|
|
167
|
+
pid_alive "$PID" && kill -9 "$PID"
|
|
168
|
+
echo "Bridge stopped"
|
|
169
|
+
else
|
|
170
|
+
echo "Bridge was not running (stale PID file)"
|
|
171
|
+
fi
|
|
172
|
+
rm -f "$PID_FILE"
|
|
173
|
+
fi
|
|
174
|
+
;;
|
|
175
|
+
|
|
176
|
+
status)
|
|
177
|
+
# Platform-specific status info (prints launchd/service state)
|
|
178
|
+
supervisor_status_extra
|
|
179
|
+
|
|
180
|
+
# Process status: supervisor-aware (launchctl on macOS, PID on Linux)
|
|
181
|
+
if supervisor_is_running; then
|
|
182
|
+
PID=$(read_pid)
|
|
183
|
+
echo "Bridge process is running${PID:+ (PID: $PID)}"
|
|
184
|
+
# Business status from status.json
|
|
185
|
+
if status_running; then
|
|
186
|
+
echo "Bridge status: running"
|
|
187
|
+
else
|
|
188
|
+
echo "Bridge status: process alive but status.json not reporting running"
|
|
189
|
+
fi
|
|
190
|
+
cat "$STATUS_FILE" 2>/dev/null
|
|
191
|
+
else
|
|
192
|
+
echo "Bridge is not running"
|
|
193
|
+
[ -f "$PID_FILE" ] && rm -f "$PID_FILE"
|
|
194
|
+
show_last_exit_reason
|
|
195
|
+
fi
|
|
196
|
+
;;
|
|
197
|
+
|
|
198
|
+
logs)
|
|
199
|
+
N="${2:-50}"
|
|
200
|
+
tail -n "$N" "$LOG_FILE" 2>/dev/null | sed -E 's/(token|secret|password)(["\\x27]?\s*[:=]\s*["\\x27]?)[^ "]+/\1\2*****/gi'
|
|
201
|
+
;;
|
|
202
|
+
|
|
203
|
+
*)
|
|
204
|
+
echo "Usage: daemon.sh {start|stop|status|logs [N]}"
|
|
205
|
+
;;
|
|
206
|
+
esac
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Windows wrapper for the existing bash-based doctor script.
|
|
4
|
+
.DESCRIPTION
|
|
5
|
+
Prefers Git Bash / bash.exe when available. Falls back to a clear message
|
|
6
|
+
when bash is missing, because the full diagnostics live in doctor.sh.
|
|
7
|
+
#>
|
|
8
|
+
|
|
9
|
+
$ErrorActionPreference = 'Stop'
|
|
10
|
+
|
|
11
|
+
$doctorScript = Join-Path (Split-Path -Parent $PSCommandPath) 'doctor.sh'
|
|
12
|
+
|
|
13
|
+
if (-not (Test-Path $doctorScript)) {
|
|
14
|
+
Write-Error "doctor.sh not found at $doctorScript"
|
|
15
|
+
exit 1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
$bash = Get-Command bash -ErrorAction SilentlyContinue
|
|
19
|
+
if (-not $bash) {
|
|
20
|
+
Write-Host "bash was not found in PATH."
|
|
21
|
+
Write-Host "Install Git Bash or another bash environment, then run:"
|
|
22
|
+
Write-Host " bash `"$doctorScript`""
|
|
23
|
+
exit 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
& $bash.Source $doctorScript
|
|
27
|
+
exit $LASTEXITCODE
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
CODELARK_HOME="${CODELARK_HOME:-${CODELARK_HOME:-$HOME/.codelark}}"
|
|
4
|
+
CONFIG_FILE="$CODELARK_HOME/config.env"
|
|
5
|
+
PID_FILE="$CODELARK_HOME/runtime/bridge.pid"
|
|
6
|
+
LOG_FILE="$CODELARK_HOME/logs/bridge.log"
|
|
7
|
+
|
|
8
|
+
PASS=0
|
|
9
|
+
FAIL=0
|
|
10
|
+
|
|
11
|
+
check() {
|
|
12
|
+
local label="$1"
|
|
13
|
+
local result="$2"
|
|
14
|
+
if [ "$result" = "0" ]; then
|
|
15
|
+
echo "[OK] $label"
|
|
16
|
+
PASS=$((PASS + 1))
|
|
17
|
+
else
|
|
18
|
+
echo "[FAIL] $label"
|
|
19
|
+
FAIL=$((FAIL + 1))
|
|
20
|
+
fi
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# --- Node.js version ---
|
|
24
|
+
if command -v node &>/dev/null; then
|
|
25
|
+
NODE_VER=$(node -v | sed 's/v//' | cut -d. -f1)
|
|
26
|
+
if [ "$NODE_VER" -ge 20 ] 2>/dev/null; then
|
|
27
|
+
check "Node.js >= 20 (found v$(node -v | sed 's/v//'))" 0
|
|
28
|
+
else
|
|
29
|
+
check "Node.js >= 20 (found v$(node -v | sed 's/v//'), need >= 20)" 1
|
|
30
|
+
fi
|
|
31
|
+
else
|
|
32
|
+
check "Node.js installed" 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# --- Helper: read a value from config.env ---
|
|
36
|
+
get_config() { grep "^$1=" "$CONFIG_FILE" 2>/dev/null | head -1 | cut -d= -f2- | sed 's/^["'"'"']//;s/["'"'"']$//'; }
|
|
37
|
+
|
|
38
|
+
# --- Read runtime setting ---
|
|
39
|
+
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
40
|
+
CODELARK_RUNTIME=$(get_config CODELARK_RUNTIME)
|
|
41
|
+
CODELARK_RUNTIME="codex"
|
|
42
|
+
echo "Runtime: $CODELARK_RUNTIME"
|
|
43
|
+
echo ""
|
|
44
|
+
|
|
45
|
+
# --- Codex checks ---
|
|
46
|
+
if command -v codex &>/dev/null; then
|
|
47
|
+
CODEX_VER=$(codex --version 2>/dev/null || echo "unknown")
|
|
48
|
+
check "Codex CLI available (${CODEX_VER})" 0
|
|
49
|
+
else
|
|
50
|
+
check "Codex CLI available (not found in PATH)" 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Check @openai/codex-sdk
|
|
54
|
+
CODEX_SDK="$SKILL_DIR/node_modules/@openai/codex-sdk"
|
|
55
|
+
if [ -d "$CODEX_SDK" ]; then
|
|
56
|
+
check "@openai/codex-sdk installed" 0
|
|
57
|
+
else
|
|
58
|
+
check "@openai/codex-sdk installed (not found — run 'npm install' in $SKILL_DIR)" 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Check Codex auth: any of CODELARK_CODEX_API_KEY / CODEX_API_KEY / OPENAI_API_KEY,
|
|
62
|
+
# or `codex auth status` showing logged-in (interactive login).
|
|
63
|
+
CODEX_AUTH=1
|
|
64
|
+
if [ -n "${CODELARK_CODEX_API_KEY:-}" ] || [ -n "${CODEX_API_KEY:-}" ] || [ -n "${OPENAI_API_KEY:-}" ]; then
|
|
65
|
+
CODEX_AUTH=0
|
|
66
|
+
elif command -v codex &>/dev/null; then
|
|
67
|
+
CODEX_AUTH_OUT=$(codex auth status 2>&1 || true)
|
|
68
|
+
if echo "$CODEX_AUTH_OUT" | grep -qiE 'logged.in|authenticated'; then
|
|
69
|
+
CODEX_AUTH=0
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
if [ "$CODEX_AUTH" = "0" ]; then
|
|
73
|
+
check "Codex auth available (API key or login)" 0
|
|
74
|
+
else
|
|
75
|
+
check "Codex auth available (set OPENAI_API_KEY or run 'codex auth login')" 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# --- dist/daemon.mjs freshness ---
|
|
79
|
+
DAEMON_MJS="$SKILL_DIR/dist/daemon.mjs"
|
|
80
|
+
if [ -f "$DAEMON_MJS" ]; then
|
|
81
|
+
STALE_SRC=$(find "$SKILL_DIR/src" -name '*.ts' -newer "$DAEMON_MJS" 2>/dev/null | head -1)
|
|
82
|
+
if [ -z "$STALE_SRC" ]; then
|
|
83
|
+
check "dist/daemon.mjs is up to date" 0
|
|
84
|
+
else
|
|
85
|
+
check "dist/daemon.mjs is stale (src changed, run 'npm run build')" 1
|
|
86
|
+
fi
|
|
87
|
+
else
|
|
88
|
+
check "dist/daemon.mjs exists (not built — run 'npm run build')" 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# --- config.env exists ---
|
|
92
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
93
|
+
check "config.env exists" 0
|
|
94
|
+
else
|
|
95
|
+
check "config.env exists ($CONFIG_FILE not found)" 1
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# --- config.env permissions ---
|
|
99
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
100
|
+
PERMS=$(stat -f "%Lp" "$CONFIG_FILE" 2>/dev/null || stat -c "%a" "$CONFIG_FILE" 2>/dev/null || echo "unknown")
|
|
101
|
+
if [ "$PERMS" = "600" ]; then
|
|
102
|
+
check "config.env permissions are 600" 0
|
|
103
|
+
else
|
|
104
|
+
check "config.env permissions are 600 (currently $PERMS)" 1
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# --- Load config for channel checks ---
|
|
109
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
110
|
+
CODELARK_CHANNELS=$(get_config CODELARK_ENABLED_CHANNELS)
|
|
111
|
+
|
|
112
|
+
# --- Feishu ---
|
|
113
|
+
if echo "$CODELARK_CHANNELS" | grep -q feishu; then
|
|
114
|
+
FS_APP_ID=$(get_config CODELARK_FEISHU_APP_ID)
|
|
115
|
+
FS_SECRET=$(get_config CODELARK_FEISHU_APP_SECRET)
|
|
116
|
+
FS_SITE=$(get_config CODELARK_FEISHU_SITE)
|
|
117
|
+
case "$FS_SITE" in
|
|
118
|
+
lark|*open.larksuite.com*)
|
|
119
|
+
FS_DOMAIN="https://open.larksuite.com"
|
|
120
|
+
;;
|
|
121
|
+
*)
|
|
122
|
+
FS_DOMAIN="https://open.feishu.cn"
|
|
123
|
+
;;
|
|
124
|
+
esac
|
|
125
|
+
if [ -n "$FS_APP_ID" ] && [ -n "$FS_SECRET" ]; then
|
|
126
|
+
FEISHU_RESULT=$(curl -s --max-time 5 -X POST "${FS_DOMAIN}/open-apis/auth/v3/tenant_access_token/internal" \
|
|
127
|
+
-H "Content-Type: application/json" \
|
|
128
|
+
-d "{\"app_id\":\"${FS_APP_ID}\",\"app_secret\":\"${FS_SECRET}\"}" 2>/dev/null || echo '{"code":1}')
|
|
129
|
+
if echo "$FEISHU_RESULT" | grep -q '"code"[[:space:]]*:[[:space:]]*0'; then
|
|
130
|
+
check "Feishu app credentials are valid" 0
|
|
131
|
+
else
|
|
132
|
+
check "Feishu app credentials are valid (token request failed)" 1
|
|
133
|
+
fi
|
|
134
|
+
else
|
|
135
|
+
check "Feishu app credentials configured" 1
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# --- Log directory writable ---
|
|
142
|
+
LOG_DIR="$CODELARK_HOME/logs"
|
|
143
|
+
if [ -d "$LOG_DIR" ] && [ -w "$LOG_DIR" ]; then
|
|
144
|
+
check "Log directory is writable" 0
|
|
145
|
+
else
|
|
146
|
+
check "Log directory is writable ($LOG_DIR)" 1
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# --- PID file consistency ---
|
|
150
|
+
if [ -f "$PID_FILE" ]; then
|
|
151
|
+
PID=$(cat "$PID_FILE")
|
|
152
|
+
if kill -0 "$PID" 2>/dev/null; then
|
|
153
|
+
check "PID file consistent (process $PID is running)" 0
|
|
154
|
+
else
|
|
155
|
+
check "PID file consistent (stale PID $PID, process not running)" 1
|
|
156
|
+
fi
|
|
157
|
+
else
|
|
158
|
+
check "PID file consistency (no PID file, OK)" 0
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# --- Recent errors in log ---
|
|
162
|
+
if [ -f "$LOG_FILE" ]; then
|
|
163
|
+
ERROR_COUNT=$(tail -50 "$LOG_FILE" | grep -ciE 'ERROR|Fatal' || true)
|
|
164
|
+
if [ "$ERROR_COUNT" -eq 0 ]; then
|
|
165
|
+
check "No recent errors in log (last 50 lines)" 0
|
|
166
|
+
else
|
|
167
|
+
check "No recent errors in log (found $ERROR_COUNT ERROR/Fatal lines)" 1
|
|
168
|
+
fi
|
|
169
|
+
else
|
|
170
|
+
check "Log file exists (not yet created)" 0
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
echo ""
|
|
174
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
175
|
+
|
|
176
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
177
|
+
echo ""
|
|
178
|
+
echo "Common fixes:"
|
|
179
|
+
echo " SDK cli.js missing → cd $SKILL_DIR && npm install"
|
|
180
|
+
echo " dist/daemon.mjs stale → cd $SKILL_DIR && npm run build"
|
|
181
|
+
echo " config.env missing → run setup wizard"
|
|
182
|
+
echo " Stale PID file → run stop, then start"
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|