cli-jaw 1.7.10 → 1.7.11
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/dist/bin/commands/dispatch.js +8 -1
- package/dist/bin/commands/dispatch.js.map +1 -1
- package/dist/bin/commands/launchd.js +6 -0
- package/dist/bin/commands/launchd.js.map +1 -1
- package/dist/bin/postinstall.js +181 -0
- package/dist/bin/postinstall.js.map +1 -1
- package/dist/lib/mcp/skills-utils.js +5 -1
- package/dist/lib/mcp/skills-utils.js.map +1 -1
- package/dist/server.js +4 -0
- package/dist/server.js.map +1 -1
- package/dist/src/agent/args.js +11 -6
- package/dist/src/agent/args.js.map +1 -1
- package/dist/src/agent/spawn.js +30 -1
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/cli/compact.js +32 -36
- package/dist/src/cli/compact.js.map +1 -1
- package/dist/src/core/boss-auth.js +38 -0
- package/dist/src/core/boss-auth.js.map +1 -0
- package/dist/src/core/compact.js +205 -0
- package/dist/src/core/compact.js.map +1 -1
- package/dist/src/core/employees.js +115 -0
- package/dist/src/core/employees.js.map +1 -1
- package/dist/src/core/launchd-plist.js +27 -10
- package/dist/src/core/launchd-plist.js.map +1 -1
- package/dist/src/core/main-session.js +14 -0
- package/dist/src/core/main-session.js.map +1 -1
- package/dist/src/core/runtime-path.js +29 -0
- package/dist/src/core/runtime-path.js.map +1 -1
- package/dist/src/orchestrator/distribute.js +40 -30
- package/dist/src/orchestrator/distribute.js.map +1 -1
- package/dist/src/orchestrator/worker-registry.js +17 -0
- package/dist/src/orchestrator/worker-registry.js.map +1 -1
- package/dist/src/prompt/builder.js +52 -2
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +87 -26
- package/dist/src/prompt/templates/control-system.md +48 -0
- package/dist/src/routes/orchestrate.js +86 -7
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/package.json +1 -1
- package/scripts/darwin/spike-tcc-attribution.sh +333 -0
- package/scripts/darwin/test-computeruse-e2e.sh +204 -0
|
@@ -4,10 +4,12 @@ import { getLiveRun } from '../agent/live-run-state.js';
|
|
|
4
4
|
import { orchestrateContinue, orchestrateReset } from '../orchestrator/pipeline.js';
|
|
5
5
|
import { getState, getCtx, setState, resetState, canTransition } from '../orchestrator/state-machine.js';
|
|
6
6
|
import { resolveOrcScope } from '../orchestrator/scope.js';
|
|
7
|
-
import { getActiveWorkers, claimWorker, finishWorker, failWorker, markWorkerReplayed } from '../orchestrator/worker-registry.js';
|
|
7
|
+
import { getActiveWorkers, claimWorker, finishWorker, failWorker, markWorkerReplayed, getWorkerSlot, WorkerBusyError } from '../orchestrator/worker-registry.js';
|
|
8
8
|
import { findEmployee, runSingleAgent } from '../orchestrator/distribute.js';
|
|
9
9
|
import { getEmployees } from '../core/db.js';
|
|
10
10
|
import { settings } from '../core/config.js';
|
|
11
|
+
import { verifyBossToken } from '../core/boss-auth.js';
|
|
12
|
+
import { resolveDispatchableEmployee, checkRuntimeHints } from '../core/employees.js';
|
|
11
13
|
function getRuntimeSnapshot() {
|
|
12
14
|
return {
|
|
13
15
|
uptimeSec: Math.floor(process.uptime()),
|
|
@@ -56,8 +58,12 @@ export function registerOrchestrateRoutes(app, requireAuth) {
|
|
|
56
58
|
});
|
|
57
59
|
// Pipe-mode employee dispatch
|
|
58
60
|
app.post('/api/orchestrate/dispatch', requireAuth, async (req, res) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
// Phase 8: server-authoritative dispatch guard. Boss-only token required.
|
|
62
|
+
// Employees do not have this token (stripped in spawn.ts makeCleanEnv).
|
|
63
|
+
const bossToken = String(req.headers['x-jaw-boss-token'] || '');
|
|
64
|
+
if (!verifyBossToken(bossToken)) {
|
|
65
|
+
console.warn(`[dispatch:deny] ip=${req.ip} ua=${String(req.headers['user-agent'] || '').slice(0, 80)}`);
|
|
66
|
+
return fail(res, 403, 'Dispatch requires boss-scoped token. Employees cannot dispatch.');
|
|
61
67
|
}
|
|
62
68
|
const { agent: agentName, task, phase } = req.body || {};
|
|
63
69
|
if (!agentName || !task)
|
|
@@ -67,10 +73,59 @@ export function registerOrchestrateRoutes(app, requireAuth) {
|
|
|
67
73
|
const currentOrcState = getState(dispatchScope);
|
|
68
74
|
const resolvedPhase = phase ?? PABCD_PHASE_MAP[currentOrcState] ?? 3;
|
|
69
75
|
const emps = getEmployees.all();
|
|
70
|
-
|
|
76
|
+
// Try DB first (preserves existing id-based matching), then fall
|
|
77
|
+
// through to static employees for entries like Control.
|
|
78
|
+
let emp = findEmployee(emps, { agent: agentName });
|
|
79
|
+
let staticSpec = null;
|
|
80
|
+
if (!emp) {
|
|
81
|
+
staticSpec = resolveDispatchableEmployee(agentName, emps);
|
|
82
|
+
if (staticSpec)
|
|
83
|
+
emp = staticSpec.row;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
staticSpec = resolveDispatchableEmployee(emp.name, emps);
|
|
87
|
+
}
|
|
71
88
|
if (!emp)
|
|
72
89
|
return fail(res, 404, `Employee not found: ${agentName}`);
|
|
73
|
-
|
|
90
|
+
// Runtime preflight for static employees (darwin/CUA/Jaw.app).
|
|
91
|
+
// Hard-fail on missing prerequisites; warn-only on optional ones.
|
|
92
|
+
if (staticSpec?.spec) {
|
|
93
|
+
const checks = checkRuntimeHints(staticSpec.spec);
|
|
94
|
+
if (checks.fail.length > 0) {
|
|
95
|
+
return fail(res, 412, `Preconditions not met: ${checks.fail.join('; ')}`);
|
|
96
|
+
}
|
|
97
|
+
for (const w of checks.warn)
|
|
98
|
+
console.warn(`[dispatch] ⚠️ ${w}`);
|
|
99
|
+
}
|
|
100
|
+
// Phase 7-2: reject concurrent dispatch of the same employee.
|
|
101
|
+
// Caller should poll GET /api/orchestrate/worker/:agentId/result.
|
|
102
|
+
let slot;
|
|
103
|
+
try {
|
|
104
|
+
slot = claimWorker(emp, task);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
if (err instanceof WorkerBusyError) {
|
|
108
|
+
return res.status(409).json({
|
|
109
|
+
ok: false,
|
|
110
|
+
error: 'worker_busy',
|
|
111
|
+
existing: {
|
|
112
|
+
agentId: err.existing.agentId,
|
|
113
|
+
employeeName: err.existing.employeeName,
|
|
114
|
+
task: err.existing.task.slice(0, 200),
|
|
115
|
+
startedAt: err.existing.startedAt,
|
|
116
|
+
},
|
|
117
|
+
hint: 'Poll GET /api/orchestrate/worker/:agentId/result for the in-flight run.',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
// Phase 7-1: detect client disconnect so we keep pendingReplay=true
|
|
123
|
+
// (so the next boss turn can drain the result) instead of discarding.
|
|
124
|
+
let clientDisconnected = false;
|
|
125
|
+
req.on('close', () => {
|
|
126
|
+
if (!res.writableEnded)
|
|
127
|
+
clientDisconnected = true;
|
|
128
|
+
});
|
|
74
129
|
try {
|
|
75
130
|
const ap = {
|
|
76
131
|
agent: emp.name, role: emp.role || 'general developer',
|
|
@@ -80,13 +135,37 @@ export function registerOrchestrateRoutes(app, requireAuth) {
|
|
|
80
135
|
};
|
|
81
136
|
const result = await runSingleAgent(ap, emp, {}, 1, { origin: 'api' }, []);
|
|
82
137
|
finishWorker(slot.agentId, result.text || '');
|
|
83
|
-
|
|
138
|
+
if (clientDisconnected) {
|
|
139
|
+
console.warn(`[dispatch] client disconnected — keeping pendingReplay for ${slot.agentId}`);
|
|
140
|
+
// pendingReplay stays true; next boss orchestrate() call drains it.
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Only clear replay flag after response is actually flushed to client.
|
|
144
|
+
res.on('finish', () => markWorkerReplayed(slot.agentId));
|
|
84
145
|
res.json({ ok: true, result });
|
|
85
146
|
}
|
|
86
147
|
catch (err) {
|
|
87
148
|
failWorker(slot.agentId, err.message);
|
|
88
|
-
res.
|
|
149
|
+
if (!res.writableEnded)
|
|
150
|
+
res.status(500).json({ ok: false, error: err.message });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
// Phase 7-4: explicit result polling for 409 retries and reconnects.
|
|
154
|
+
app.get('/api/orchestrate/worker/:agentId/result', requireAuth, (req, res) => {
|
|
155
|
+
const agentId = String(req.params.agentId || '');
|
|
156
|
+
if (!agentId)
|
|
157
|
+
return fail(res, 400, 'missing agentId');
|
|
158
|
+
const slot = getWorkerSlot(agentId);
|
|
159
|
+
if (!slot)
|
|
160
|
+
return fail(res, 404, 'worker not found');
|
|
161
|
+
if (slot.state === 'running') {
|
|
162
|
+
return res.json({ ok: true, state: 'running', startedAt: slot.startedAt, task: slot.task });
|
|
163
|
+
}
|
|
164
|
+
// Consume pending replay — subsequent polls will return 404.
|
|
165
|
+
if (slot.state === 'done' && slot.pendingReplay) {
|
|
166
|
+
markWorkerReplayed(slot.agentId);
|
|
89
167
|
}
|
|
168
|
+
return res.json({ ok: true, state: slot.state, result: slot.result });
|
|
90
169
|
});
|
|
91
170
|
app.put('/api/orchestrate/state', requireAuth, (req, res) => {
|
|
92
171
|
const target = String(req.body?.state || '').toUpperCase();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrate.js","sourceRoot":"","sources":["../../../src/routes/orchestrate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAM,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,gCAAgC,EAAE,MAAM,mBAAmB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEzG,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"orchestrate.js","sourceRoot":"","sources":["../../../src/routes/orchestrate.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cli-jaw",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.11",
|
|
4
4
|
"description": "Personal AI assistant powered by 5 engines (Claude, Codex, Gemini, OpenCode, Copilot) — Web, Terminal, Telegram, and Discord interfaces with 107 built-in skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# spike-tcc-attribution.sh
|
|
3
|
+
# 목적: macOS TCC가 `Jaw.app` bundle identity로 Automation 권한을 귀속시키는지 실측 검증.
|
|
4
|
+
# 이 스크립트는 31/32 계획의 핵심 가정(shell-shim launcher → com.cli-jaw.agent 책임)을
|
|
5
|
+
# 실제 하드웨어에서 확인한다. 계획을 B로 진입시키기 전 반드시 1회 이상 PASS해야 한다.
|
|
6
|
+
#
|
|
7
|
+
# 사용:
|
|
8
|
+
# bash scripts/darwin/spike-tcc-attribution.sh # dry-run check
|
|
9
|
+
# bash scripts/darwin/spike-tcc-attribution.sh --build # 임시 Jaw.app 번들 생성 후 검증
|
|
10
|
+
# bash scripts/darwin/spike-tcc-attribution.sh --cleanup # 생성된 임시 번들 제거
|
|
11
|
+
#
|
|
12
|
+
# 출력 원칙:
|
|
13
|
+
# - 각 단계마다 PASS/FAIL을 찍고 실패 시 즉시 exit (fail-fast 규칙)
|
|
14
|
+
# - 마지막에 `RESULT=...` 한 줄을 찍어 호출자가 grep하기 쉽게 한다
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
# ── Colors ──
|
|
19
|
+
CYAN='\033[0;36m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'
|
|
20
|
+
DIM='\033[2m'; BOLD='\033[1m'; NC='\033[0m'
|
|
21
|
+
info() { echo -e "${CYAN}▸${NC} $*"; }
|
|
22
|
+
ok() { echo -e "${GREEN}✔${NC} $*"; }
|
|
23
|
+
warn() { echo -e "${YELLOW}⚠${NC} $*"; }
|
|
24
|
+
fail() { echo -e "${RED}✖${NC} $*"; echo "RESULT=FAIL"; exit 1; }
|
|
25
|
+
|
|
26
|
+
if [[ "$(uname -s)" != "Darwin" ]]; then fail "macOS 전용"; fi
|
|
27
|
+
|
|
28
|
+
MODE="${1:-check}"
|
|
29
|
+
SPIKE_APP_DIR="/Applications/JawSpike.app"
|
|
30
|
+
SPIKE_BUNDLE_ID="com.cli-jaw.spike"
|
|
31
|
+
|
|
32
|
+
print_header() {
|
|
33
|
+
echo ""
|
|
34
|
+
echo -e "${CYAN}${BOLD}🔬 TCC Attribution Spike${NC}"
|
|
35
|
+
echo -e "${DIM} 검증: shell-shim launcher가 $SPIKE_BUNDLE_ID 로 귀속되는가${NC}"
|
|
36
|
+
echo ""
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
step_check_prereqs() {
|
|
40
|
+
info "1/6 전제 조건 점검"
|
|
41
|
+
command -v osascript >/dev/null || fail "osascript 없음"
|
|
42
|
+
[[ -d "/Applications/Google Chrome.app" ]] || fail "Google Chrome 미설치 — 설치 후 재시도"
|
|
43
|
+
command -v /usr/libexec/PlistBuddy >/dev/null || fail "PlistBuddy 없음"
|
|
44
|
+
ok "prereq OK"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
step_probe_path_sources() {
|
|
48
|
+
info "2/6 CLI binary PATH 감지 (bun / npm-global / nvm)"
|
|
49
|
+
BUN_BIN_DIR=""
|
|
50
|
+
NPM_BIN_DIR=""
|
|
51
|
+
NVM_BIN_DIR=""
|
|
52
|
+
if command -v bun >/dev/null; then BUN_BIN_DIR="$(dirname "$(command -v bun)")"; fi
|
|
53
|
+
if command -v npm >/dev/null; then NPM_BIN_DIR="$(npm config get prefix 2>/dev/null)/bin"; fi
|
|
54
|
+
if [[ -n "${NVM_DIR:-}" && -d "$NVM_DIR" ]]; then
|
|
55
|
+
NVM_BIN_DIR="$(ls -dt "$NVM_DIR"/versions/node/*/bin 2>/dev/null | head -1 || true)"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo " BUN: ${BUN_BIN_DIR:-∅}"
|
|
59
|
+
echo " NPM: ${NPM_BIN_DIR:-∅}"
|
|
60
|
+
echo " NVM: ${NVM_BIN_DIR:-∅}"
|
|
61
|
+
|
|
62
|
+
# cli-jaw가 실제로 어디서 발견되는지
|
|
63
|
+
JAW_BIN="$(command -v cli-jaw || true)"
|
|
64
|
+
[[ -n "$JAW_BIN" ]] || fail "cli-jaw가 현재 PATH에 없음 — npm i -g cli-jaw 먼저"
|
|
65
|
+
JAW_BIN_DIR="$(dirname "$JAW_BIN")"
|
|
66
|
+
echo " cli-jaw: $JAW_BIN"
|
|
67
|
+
|
|
68
|
+
# PATH 머지 (중복 제거, 존재하는 것만)
|
|
69
|
+
MERGED_PATH=""
|
|
70
|
+
for d in "$JAW_BIN_DIR" "$BUN_BIN_DIR" "$NPM_BIN_DIR" "$NVM_BIN_DIR" \
|
|
71
|
+
"/opt/homebrew/bin" "/usr/local/bin" "/usr/bin" "/bin"; do
|
|
72
|
+
[[ -z "$d" || ! -d "$d" ]] && continue
|
|
73
|
+
case ":$MERGED_PATH:" in *":$d:"*) ;; *) MERGED_PATH="${MERGED_PATH:+$MERGED_PATH:}$d";; esac
|
|
74
|
+
done
|
|
75
|
+
echo " MERGED_PATH=$MERGED_PATH"
|
|
76
|
+
ok "PATH sources detected"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
build_spike_app() {
|
|
80
|
+
info "3/6 임시 Jaw.app (JawSpike.app) 생성"
|
|
81
|
+
if [[ -d "$SPIKE_APP_DIR" ]]; then
|
|
82
|
+
warn "기존 $SPIKE_APP_DIR 감지 — 삭제 후 재생성"
|
|
83
|
+
rm -rf "$SPIKE_APP_DIR"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
mkdir -p "$SPIKE_APP_DIR/Contents/MacOS"
|
|
87
|
+
mkdir -p "$SPIKE_APP_DIR/Contents/Resources"
|
|
88
|
+
|
|
89
|
+
cat > "$SPIKE_APP_DIR/Contents/Info.plist" <<PLIST
|
|
90
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
91
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
92
|
+
<plist version="1.0">
|
|
93
|
+
<dict>
|
|
94
|
+
<key>CFBundleIdentifier</key><string>$SPIKE_BUNDLE_ID</string>
|
|
95
|
+
<key>CFBundleName</key><string>JawSpike</string>
|
|
96
|
+
<key>CFBundleDisplayName</key><string>Jaw Spike</string>
|
|
97
|
+
<key>CFBundleExecutable</key><string>jaw-spike-launcher</string>
|
|
98
|
+
<key>CFBundlePackageType</key><string>APPL</string>
|
|
99
|
+
<key>CFBundleShortVersionString</key><string>0.0.1</string>
|
|
100
|
+
<key>CFBundleVersion</key><string>0.0.1</string>
|
|
101
|
+
<key>LSBackgroundOnly</key><true/>
|
|
102
|
+
<key>LSUIElement</key><true/>
|
|
103
|
+
<key>NSAppleEventsUsageDescription</key><string>Jaw Spike — TCC attribution test</string>
|
|
104
|
+
</dict>
|
|
105
|
+
</plist>
|
|
106
|
+
PLIST
|
|
107
|
+
|
|
108
|
+
cat > "$SPIKE_APP_DIR/Contents/MacOS/jaw-spike-launcher" <<LAUNCHER
|
|
109
|
+
#!/bin/bash
|
|
110
|
+
# PATH pin: CLI discovery from bun / npm / nvm
|
|
111
|
+
export PATH="$MERGED_PATH"
|
|
112
|
+
# 실제 책임 주체가 누구인지 보기 위해 osascript를 직접 실행
|
|
113
|
+
exec /usr/bin/osascript -e 'tell application "Google Chrome" to get URL of active tab of front window'
|
|
114
|
+
LAUNCHER
|
|
115
|
+
chmod +x "$SPIKE_APP_DIR/Contents/MacOS/jaw-spike-launcher"
|
|
116
|
+
|
|
117
|
+
# Launch Services 등록
|
|
118
|
+
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister \
|
|
119
|
+
-f -R "$SPIKE_APP_DIR" 2>/dev/null || warn "lsregister 실패 (무시 가능)"
|
|
120
|
+
xattr -dr com.apple.quarantine "$SPIKE_APP_DIR" 2>/dev/null || true
|
|
121
|
+
|
|
122
|
+
ok "JawSpike.app 생성됨: $SPIKE_APP_DIR"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
probe_attribution() {
|
|
126
|
+
info "4/6 첫 실행 (TCC 프롬프트 예상)"
|
|
127
|
+
echo ""
|
|
128
|
+
echo -e "${YELLOW} 프롬프트가 뜨면 '허용'을 누르세요. 이름은 '${BOLD}Jaw Spike${NC}${YELLOW}'로 보여야 합니다.${NC}"
|
|
129
|
+
echo -e "${YELLOW} 만약 'Terminal' 또는 'osascript'로 뜨면 귀속 실패입니다.${NC}"
|
|
130
|
+
echo ""
|
|
131
|
+
read -r -p " 계속하려면 Enter..." _
|
|
132
|
+
|
|
133
|
+
# open -a는 LaunchServices를 통해 번들을 실행 → responsibility가 번들로 귀속됨
|
|
134
|
+
if ! open -W -a "$SPIKE_APP_DIR" 2>&1; then
|
|
135
|
+
warn "open -W가 non-zero — 권한 거부 또는 Chrome 비실행 상태일 수 있음"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
info "5/6 권한 DB에서 귀속 주체 조회"
|
|
139
|
+
DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
|
|
140
|
+
if [[ ! -f "$DB" ]]; then warn "유저 TCC.db 없음 (조회 스킵)"; return; fi
|
|
141
|
+
|
|
142
|
+
# kTCCServiceAppleEvents 레코드에서 client 필드 확인 (read-only)
|
|
143
|
+
ATTR=$(sqlite3 "$DB" \
|
|
144
|
+
"SELECT client FROM access WHERE service='kTCCServiceAppleEvents' AND client LIKE '%cli-jaw%' OR client LIKE '%jaw-spike%';" \
|
|
145
|
+
2>/dev/null || true)
|
|
146
|
+
|
|
147
|
+
if [[ -z "$ATTR" ]]; then
|
|
148
|
+
warn "TCC.db에 $SPIKE_BUNDLE_ID 관련 레코드 없음 — 아직 허용이 기록되지 않았거나 귀속 실패"
|
|
149
|
+
else
|
|
150
|
+
echo " TCC client 엔트리:"
|
|
151
|
+
echo "$ATTR" | sed 's/^/ /'
|
|
152
|
+
fi
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
verdict() {
|
|
156
|
+
info "6/6 결론 출력"
|
|
157
|
+
DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
|
|
158
|
+
PASS=false
|
|
159
|
+
if [[ -f "$DB" ]]; then
|
|
160
|
+
if sqlite3 "$DB" "SELECT client FROM access WHERE service='kTCCServiceAppleEvents' AND client='$SPIKE_BUNDLE_ID';" 2>/dev/null | grep -q "$SPIKE_BUNDLE_ID"; then
|
|
161
|
+
PASS=true
|
|
162
|
+
fi
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
if $PASS; then
|
|
166
|
+
ok "RESULT=PASS — shell-shim launcher가 $SPIKE_BUNDLE_ID 로 귀속됨"
|
|
167
|
+
echo "RESULT=PASS"
|
|
168
|
+
echo " → 31/32 계획의 shell-shim 전략 진행 가능"
|
|
169
|
+
else
|
|
170
|
+
warn "RESULT=INCONCLUSIVE_OR_FAIL"
|
|
171
|
+
echo "RESULT=INCONCLUSIVE"
|
|
172
|
+
echo ""
|
|
173
|
+
echo " 가능한 원인과 대처:"
|
|
174
|
+
echo " 1) 프롬프트에서 거부/무시함 → 재실행"
|
|
175
|
+
echo " 2) Terminal/osascript로 귀속됨 → shell-shim 전략 불가. 대안:"
|
|
176
|
+
echo " a. Swift/C 네이티브 mini-launcher (앱 번들 executable이 실제 네이티브 바이너리)"
|
|
177
|
+
echo " b. launchd ProgramArguments에 Jaw.app 자체를 지정하는 대신"
|
|
178
|
+
echo " /Applications/Jaw.app/Contents/MacOS/jaw-launcher 를 pid 1 자식으로 두기"
|
|
179
|
+
echo " 3) TCC.db 권한 없음 → Full Disk Access가 필요할 수 있음"
|
|
180
|
+
fi
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
build_native_spike() {
|
|
184
|
+
info "3/6 [V2 native] clang 확인 + Obj-C launcher 컴파일"
|
|
185
|
+
if ! command -v /usr/bin/clang >/dev/null; then
|
|
186
|
+
fail "clang 없음 — Xcode Command Line Tools 설치: xcode-select --install"
|
|
187
|
+
fi
|
|
188
|
+
ok "clang: $(/usr/bin/clang --version | head -1)"
|
|
189
|
+
|
|
190
|
+
if [[ -d "$SPIKE_APP_DIR" ]]; then
|
|
191
|
+
warn "기존 $SPIKE_APP_DIR 제거"
|
|
192
|
+
rm -rf "$SPIKE_APP_DIR"
|
|
193
|
+
fi
|
|
194
|
+
mkdir -p "$SPIKE_APP_DIR/Contents/MacOS"
|
|
195
|
+
mkdir -p "$SPIKE_APP_DIR/Contents/Resources"
|
|
196
|
+
|
|
197
|
+
cat > "$SPIKE_APP_DIR/Contents/Info.plist" <<PLIST
|
|
198
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
199
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
200
|
+
<plist version="1.0">
|
|
201
|
+
<dict>
|
|
202
|
+
<key>CFBundleIdentifier</key><string>$SPIKE_BUNDLE_ID</string>
|
|
203
|
+
<key>CFBundleName</key><string>JawSpike</string>
|
|
204
|
+
<key>CFBundleDisplayName</key><string>Jaw Spike Native</string>
|
|
205
|
+
<key>CFBundleExecutable</key><string>jaw-spike-launcher</string>
|
|
206
|
+
<key>CFBundlePackageType</key><string>APPL</string>
|
|
207
|
+
<key>CFBundleShortVersionString</key><string>0.0.2</string>
|
|
208
|
+
<key>CFBundleVersion</key><string>0.0.2</string>
|
|
209
|
+
<key>LSBackgroundOnly</key><true/>
|
|
210
|
+
<key>LSUIElement</key><true/>
|
|
211
|
+
<key>NSAppleEventsUsageDescription</key><string>Jaw Spike — native launcher TCC test</string>
|
|
212
|
+
</dict>
|
|
213
|
+
</plist>
|
|
214
|
+
PLIST
|
|
215
|
+
|
|
216
|
+
# Objective-C source: Mach-O that fires an AppleEvent (TCC 진짜 트리거)
|
|
217
|
+
local SRC="$(mktemp -t jaw-spike-XXXXXX).m"
|
|
218
|
+
cat > "$SRC" <<'OBJC'
|
|
219
|
+
#import <Foundation/Foundation.h>
|
|
220
|
+
int main(void) {
|
|
221
|
+
@autoreleasepool {
|
|
222
|
+
NSAppleScript *s = [[NSAppleScript alloc] initWithSource:
|
|
223
|
+
@"tell application \"Google Chrome\" to get URL of active tab of front window"];
|
|
224
|
+
NSDictionary *err = nil;
|
|
225
|
+
NSAppleEventDescriptor *r = [s executeAndReturnError:&err];
|
|
226
|
+
if (err) {
|
|
227
|
+
fprintf(stderr, "ERR: %s\n", [[err description] UTF8String]);
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
NSString *v = [r stringValue];
|
|
231
|
+
printf("URL=%s\n", v ? [v UTF8String] : "(null)");
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
OBJC
|
|
236
|
+
|
|
237
|
+
local OUT="$SPIKE_APP_DIR/Contents/MacOS/jaw-spike-launcher"
|
|
238
|
+
if ! /usr/bin/clang -framework Foundation -O2 -o "$OUT" "$SRC"; then
|
|
239
|
+
rm -f "$SRC"
|
|
240
|
+
fail "clang compile 실패"
|
|
241
|
+
fi
|
|
242
|
+
rm -f "$SRC"
|
|
243
|
+
chmod +x "$OUT"
|
|
244
|
+
|
|
245
|
+
# ad-hoc codesign so TCC accepts the bundle identity stably
|
|
246
|
+
/usr/bin/codesign --force --sign - "$SPIKE_APP_DIR" 2>/dev/null || warn "codesign 실패 (무시 가능)"
|
|
247
|
+
|
|
248
|
+
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister \
|
|
249
|
+
-f -R "$SPIKE_APP_DIR" 2>/dev/null || warn "lsregister 실패"
|
|
250
|
+
xattr -dr com.apple.quarantine "$SPIKE_APP_DIR" 2>/dev/null || true
|
|
251
|
+
|
|
252
|
+
ok "Native JawSpike.app 컴파일 완료: $SPIKE_APP_DIR"
|
|
253
|
+
file "$OUT" | sed 's/^/ /'
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
verdict_native() {
|
|
257
|
+
info "6/6 V2 결론"
|
|
258
|
+
DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
|
|
259
|
+
if [[ ! -f "$DB" ]]; then
|
|
260
|
+
warn "TCC.db 접근 불가 — Terminal/iTerm에 Full Disk Access 필요"
|
|
261
|
+
echo "RESULT=INCONCLUSIVE_NO_TCC_ACCESS"
|
|
262
|
+
return
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
local SPIKE_ROW
|
|
266
|
+
SPIKE_ROW=$(sqlite3 "$DB" \
|
|
267
|
+
"SELECT client FROM access WHERE service='kTCCServiceAppleEvents' AND client='$SPIKE_BUNDLE_ID';" \
|
|
268
|
+
2>/dev/null || true)
|
|
269
|
+
|
|
270
|
+
if [[ -n "$SPIKE_ROW" ]]; then
|
|
271
|
+
ok "RESULT=PASS — Mach-O launcher가 $SPIKE_BUNDLE_ID 로 귀속됨"
|
|
272
|
+
echo "RESULT=PASS"
|
|
273
|
+
echo " → 31/32의 native launcher 전략 진행 가능"
|
|
274
|
+
return
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# 실패 상세: 실제 누구에게 귀속됐는지 힌트
|
|
278
|
+
warn "RESULT=FAIL — $SPIKE_BUNDLE_ID TCC 엔트리 없음"
|
|
279
|
+
echo "RESULT=FAIL"
|
|
280
|
+
echo ""
|
|
281
|
+
echo " TCC.db의 최근 AppleEvents 허용 client 상위 10개:"
|
|
282
|
+
sqlite3 "$DB" \
|
|
283
|
+
"SELECT client, datetime(last_modified, 'unixepoch', 'localtime') FROM access WHERE service='kTCCServiceAppleEvents' ORDER BY last_modified DESC LIMIT 10;" \
|
|
284
|
+
2>/dev/null | sed 's/^/ /'
|
|
285
|
+
echo ""
|
|
286
|
+
echo " 프롬프트 제목이 무엇으로 떴는지(예: 'Jaw Spike Native' vs 'osascript' vs 'bash')가 핵심 증거입니다."
|
|
287
|
+
echo " 'Jaw Spike Native'였다면 유저가 허용만 안 누른 것 — 재실행하세요."
|
|
288
|
+
echo " 다른 이름이었다면 native launcher도 귀속 실패 → 계획 재협상 필요."
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
cleanup() {
|
|
292
|
+
info "cleanup"
|
|
293
|
+
if [[ -d "$SPIKE_APP_DIR" ]]; then
|
|
294
|
+
rm -rf "$SPIKE_APP_DIR"
|
|
295
|
+
ok "$SPIKE_APP_DIR 제거됨"
|
|
296
|
+
else
|
|
297
|
+
ok "이미 깨끗함"
|
|
298
|
+
fi
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
case "$MODE" in
|
|
302
|
+
--cleanup|cleanup) cleanup; exit 0 ;;
|
|
303
|
+
--build|build)
|
|
304
|
+
print_header
|
|
305
|
+
warn "V1 (shell-shim) 모드는 이미 FAIL 판정 — 참고용으로만 실행하세요."
|
|
306
|
+
step_check_prereqs
|
|
307
|
+
step_probe_path_sources
|
|
308
|
+
build_spike_app
|
|
309
|
+
probe_attribution
|
|
310
|
+
verdict
|
|
311
|
+
;;
|
|
312
|
+
--build-native|build-native|--v2|v2)
|
|
313
|
+
print_header
|
|
314
|
+
echo -e "${DIM} V2: Mach-O Objective-C launcher 기반 재검증${NC}"
|
|
315
|
+
echo ""
|
|
316
|
+
step_check_prereqs
|
|
317
|
+
step_probe_path_sources
|
|
318
|
+
build_native_spike
|
|
319
|
+
probe_attribution
|
|
320
|
+
verdict_native
|
|
321
|
+
;;
|
|
322
|
+
*)
|
|
323
|
+
print_header
|
|
324
|
+
step_check_prereqs
|
|
325
|
+
step_probe_path_sources
|
|
326
|
+
warn "dry-run 모드 — 번들 생성/권한 요청은 스킵"
|
|
327
|
+
echo ""
|
|
328
|
+
echo "다음 단계:"
|
|
329
|
+
echo " bash $0 --build-native # V2: clang Mach-O 기반 (권장)"
|
|
330
|
+
echo " bash $0 --build # V1: shell-shim (이미 FAIL, 참고용)"
|
|
331
|
+
echo " bash $0 --cleanup # spike 번들 제거"
|
|
332
|
+
;;
|
|
333
|
+
esac
|