metame-cli 1.4.17 → 1.4.19
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 +118 -34
- package/index.js +12 -5
- package/package.json +2 -2
- package/scripts/check-macos-control-capabilities.sh +77 -0
- package/scripts/daemon-admin-commands.js +350 -12
- package/scripts/daemon-admin-commands.test.js +333 -0
- package/scripts/daemon-agent-commands.js +20 -1
- package/scripts/daemon-claude-engine.js +62 -12
- package/scripts/daemon-command-router.js +257 -12
- package/scripts/daemon-default.yaml +10 -3
- package/scripts/daemon-exec-commands.js +248 -13
- package/scripts/daemon-session-store.js +176 -41
- package/scripts/daemon-task-envelope.js +143 -0
- package/scripts/daemon-task-envelope.test.js +59 -0
- package/scripts/daemon-task-scheduler.js +213 -24
- package/scripts/daemon-task-scheduler.test.js +106 -0
- package/scripts/daemon-user-acl.js +399 -0
- package/scripts/daemon.js +376 -26
- package/scripts/distill.js +184 -34
- package/scripts/memory-extract.js +13 -5
- package/scripts/memory.js +239 -60
- package/scripts/providers.js +1 -1
- package/scripts/reliability-core.test.js +268 -0
- package/scripts/session-analytics.js +123 -35
- package/scripts/signal-capture.js +171 -11
- package/scripts/skill-evolution.js +158 -19
- package/scripts/task-board.js +398 -0
- package/scripts/task-board.test.js +83 -0
- package/scripts/usage-classifier.js +139 -0
- package/scripts/utils.js +107 -0
- package/scripts/utils.test.js +61 -1
package/README.md
CHANGED
|
@@ -14,25 +14,31 @@
|
|
|
14
14
|
<a href="./README.md">English</a> | <a href="./README中文版.md">中文</a>
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
|
-
> **
|
|
17
|
+
> **Your digital twin. Lives on your Mac.**
|
|
18
18
|
|
|
19
|
-
MetaMe
|
|
19
|
+
MetaMe is an AI that lives on your machine — remembers how you think, stays online 24/7, and takes commands from your phone via Telegram or Feishu. Not in the cloud. In your computer.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
No cloud. Your machine, your data.
|
|
22
22
|
|
|
23
|
+
**macOS / Linux / Windows WSL — same command:**
|
|
24
|
+
```bash
|
|
25
|
+
curl -fsSL https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.sh | bash
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Already have Node.js ≥ 18:**
|
|
23
29
|
```bash
|
|
24
30
|
npm install -g metame-cli && metame
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
---
|
|
28
34
|
|
|
29
|
-
> ### 🚀 v1.4.
|
|
35
|
+
> ### 🚀 v1.4.18 — Multi-User ACL + Session Context Preview
|
|
30
36
|
>
|
|
31
|
-
>
|
|
32
|
-
> - **
|
|
33
|
-
> - **
|
|
34
|
-
> - **
|
|
35
|
-
> - **Unix Socket IPC
|
|
37
|
+
> - **Multi-user permission system**: role-based ACL (admin / member / stranger) — share your bots with teammates without giving them full access. Manage users with `/user` commands.
|
|
38
|
+
> - **Session context preview**: `/resume` and `/sessions` now show the last message snippet so you know exactly what to pick up.
|
|
39
|
+
> - **Team Task protocol**: multi-agent task board for cross-agent collaboration. Agents can dispatch and track tasks across workspaces.
|
|
40
|
+
> - **Layered Memory Architecture**: three-layer memory (long-term facts, session summaries, session index) — all automatic.
|
|
41
|
+
> - **Unix Socket IPC**: dispatch latency <100ms.
|
|
36
42
|
>
|
|
37
43
|
> Zero configuration. It just works.
|
|
38
44
|
|
|
@@ -64,7 +70,7 @@ Claude: ✏️ Edit: api/login.ts
|
|
|
64
70
|
✅ Fixed. 3 tests passing.
|
|
65
71
|
```
|
|
66
72
|
|
|
67
|
-
Start on your laptop, continue on the train. `/stop` to interrupt, `/undo` to rollback, `/sh ls` for raw shell access when everything else breaks.
|
|
73
|
+
Start on your laptop, continue on the train. `/stop` to interrupt, `/undo` to rollback, `/mac check` for macOS automation diagnostics, and `/sh ls` for raw shell access when everything else breaks.
|
|
68
74
|
|
|
69
75
|
### 3. Layered Memory That Works While You Sleep
|
|
70
76
|
|
|
@@ -124,7 +130,8 @@ projects:
|
|
|
124
130
|
heartbeat_tasks:
|
|
125
131
|
- name: "daily-draft"
|
|
126
132
|
prompt: "Research top AI news and write an article"
|
|
127
|
-
|
|
133
|
+
at: "09:30"
|
|
134
|
+
days: "weekdays"
|
|
128
135
|
model: "sonnet"
|
|
129
136
|
notify: true
|
|
130
137
|
|
|
@@ -132,7 +139,7 @@ heartbeat:
|
|
|
132
139
|
tasks:
|
|
133
140
|
- name: "morning-brief"
|
|
134
141
|
prompt: "Summarize my git activity from yesterday"
|
|
135
|
-
|
|
142
|
+
at: "09:00"
|
|
136
143
|
notify: true
|
|
137
144
|
```
|
|
138
145
|
|
|
@@ -150,7 +157,21 @@ Chain skills into multi-step workflows — research → write → publish — fu
|
|
|
150
157
|
prompt: "Publish it"
|
|
151
158
|
```
|
|
152
159
|
|
|
153
|
-
Task options
|
|
160
|
+
**Task options:**
|
|
161
|
+
|
|
162
|
+
| Option | Description |
|
|
163
|
+
|--------|-------------|
|
|
164
|
+
| `at` | Fixed-time trigger, e.g. `"09:30"` (local time) |
|
|
165
|
+
| `days` | Day filter, e.g. `"weekdays"`, `[mon, wed, fri]` |
|
|
166
|
+
| `interval` | Interval trigger, e.g. `"4h"`, `"30m"` |
|
|
167
|
+
| `require_idle` | Skip if you're active; retry on next heartbeat tick |
|
|
168
|
+
| `precondition` | Shell guard — skip task if command returns non-zero (zero tokens consumed) |
|
|
169
|
+
| `notify` | Push result to phone when done |
|
|
170
|
+
| `model` | Override model, e.g. `"sonnet"`, `"haiku"` |
|
|
171
|
+
| `cwd` | Working directory for the task |
|
|
172
|
+
| `timeout` | Max run time |
|
|
173
|
+
|
|
174
|
+
> **Scheduled tasks require system registration.** Run `metame daemon install-launchd` and tasks fire on schedule even with the screen locked or the lid closed — as long as the Mac is on.
|
|
154
175
|
|
|
155
176
|
### 5. Skills That Evolve Themselves
|
|
156
177
|
|
|
@@ -172,6 +193,13 @@ Task fails → skill-scout finds a skill → installs → retries → succeeds
|
|
|
172
193
|
|
|
173
194
|
## Quick Start
|
|
174
195
|
|
|
196
|
+
**macOS / Linux / Windows WSL:**
|
|
197
|
+
```bash
|
|
198
|
+
curl -fsSL https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.sh | bash
|
|
199
|
+
```
|
|
200
|
+
> Same command everywhere. The script detects your OS and uses Homebrew (macOS) or apt/dnf/pacman (Linux/WSL) to install Node.js automatically.
|
|
201
|
+
|
|
202
|
+
**Already have Node.js ≥ 18:**
|
|
175
203
|
```bash
|
|
176
204
|
npm install -g metame-cli && metame
|
|
177
205
|
```
|
|
@@ -183,7 +211,35 @@ npm install -g metame-cli && metame
|
|
|
183
211
|
| 1. Install & profile | `metame` | First run: cognitive interview → builds `~/.claude_profile.yaml` |
|
|
184
212
|
| 2. Connect phone | Follow the setup wizard | Bot token + app credentials → `~/.metame/daemon.yaml` |
|
|
185
213
|
| 3. Start daemon | `metame start` | Background daemon launches, bot goes online |
|
|
186
|
-
| 4.
|
|
214
|
+
| 4. Register with system | macOS: `metame daemon install-launchd` · WSL/Linux: see below | Always-on, crash recovery |
|
|
215
|
+
|
|
216
|
+
> **What does system registration mean?**
|
|
217
|
+
> Once registered, MetaMe runs in the background automatically — screen locked, lid closed, woken from sleep — as long as the machine is on. Scheduled tasks fire on time. No terminal window needed.
|
|
218
|
+
|
|
219
|
+
**WSL2 / Linux — register with systemd:**
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
cat > ~/.config/systemd/user/metame.service << 'EOF'
|
|
223
|
+
[Unit]
|
|
224
|
+
Description=MetaMe Daemon
|
|
225
|
+
After=network.target
|
|
226
|
+
|
|
227
|
+
[Service]
|
|
228
|
+
ExecStart=/usr/bin/env metame start
|
|
229
|
+
Restart=on-failure
|
|
230
|
+
RestartSec=5
|
|
231
|
+
|
|
232
|
+
[Install]
|
|
233
|
+
WantedBy=default.target
|
|
234
|
+
EOF
|
|
235
|
+
|
|
236
|
+
systemctl --user enable metame
|
|
237
|
+
systemctl --user start metame
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
> WSL2 requires systemd enabled first: add `[boot]\nsystemd=true` to `/etc/wsl.conf`, then restart WSL.
|
|
241
|
+
|
|
242
|
+
> **WSL limitation:** `/mac` commands (macOS AppleScript/JXA automation) are not available.
|
|
187
243
|
|
|
188
244
|
**Create your first Agent:**
|
|
189
245
|
|
|
@@ -208,15 +264,46 @@ npm install -g metame-cli && metame
|
|
|
208
264
|
| **Browser Automation** | Built-in Playwright MCP. Browser control out of the box for every user. |
|
|
209
265
|
| **Provider Relay** | Route through any Anthropic-compatible API. Use GPT-4, DeepSeek, Gemini — zero config file mutation. |
|
|
210
266
|
| **Metacognition** | Detects behavioral patterns (decision style, comfort zones, goal drift) and injects mirror observations. Zero extra API cost. |
|
|
211
|
-
| **
|
|
267
|
+
| **Multi-User ACL** | Role-based permission system (admin / member / stranger). Share bots with teammates safely. Dynamic user management via `/user` commands with hot-reload config. |
|
|
268
|
+
| **Team Task** | Multi-agent task board for cross-agent collaboration. Agents can create, assign, and track tasks across workspaces. N-agent session scoping for parallel team workflows. |
|
|
269
|
+
| **Emergency Tools** | `/doctor` diagnostics, `/mac` macOS control helpers, `/sh` raw shell, `/fix` config restore, `/undo` git-based rollback. |
|
|
212
270
|
|
|
213
271
|
## Defining Your Agents
|
|
214
272
|
|
|
215
|
-
|
|
273
|
+
MetaMe's design philosophy: **one folder = one agent.**
|
|
274
|
+
|
|
275
|
+
Give an agent a directory, drop a `CLAUDE.md` inside describing its role, and you're done. The folder is the agent — it can be a code project, a blog repo, any workspace you already have.
|
|
276
|
+
|
|
277
|
+
### Option 1: Just say it (fastest)
|
|
278
|
+
|
|
279
|
+
No commands needed. Tell the bot what you want in plain language — MetaMe understands intent and acts:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
You: Create an agent for this group, directory ~/projects/assistant
|
|
283
|
+
Bot: ✅ Agent created and bound
|
|
284
|
+
Name: assistant
|
|
285
|
+
Dir: ~/projects/assistant
|
|
286
|
+
|
|
287
|
+
You: Change this agent's role to: a writing and content creation assistant
|
|
288
|
+
Bot: ✅ Role definition updated in CLAUDE.md
|
|
289
|
+
|
|
290
|
+
You: Bind an agent to ~/AGI/MyProject
|
|
291
|
+
Bot: ✅ Agent bound
|
|
292
|
+
Name: MyProject
|
|
293
|
+
Dir: ~/AGI/MyProject
|
|
294
|
+
|
|
295
|
+
You: List all agents
|
|
296
|
+
Bot: 📋 Agent list
|
|
297
|
+
🤖 assistant ◀ current
|
|
298
|
+
Dir: ~/projects/assistant
|
|
299
|
+
...
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Supported intents: create, bind, unbind, edit role, list — just say it naturally.
|
|
216
303
|
|
|
217
|
-
###
|
|
304
|
+
### Option 2: Wizard commands
|
|
218
305
|
|
|
219
|
-
|
|
306
|
+
Use `/agent` commands in any Telegram/Feishu group:
|
|
220
307
|
|
|
221
308
|
| Command | What it does |
|
|
222
309
|
|---------|-------------|
|
|
@@ -226,21 +313,7 @@ The easiest way. Open any Telegram/Feishu group and use the `/agent` wizard:
|
|
|
226
313
|
| `/agent edit` | Update the current agent's role description (rewrites its `CLAUDE.md` section). |
|
|
227
314
|
| `/agent reset` | Remove the current agent's role section. |
|
|
228
315
|
|
|
229
|
-
|
|
230
|
-
```
|
|
231
|
-
You: /agent new
|
|
232
|
-
Bot: Please select a working directory:
|
|
233
|
-
📁 ~/AGI 📁 ~/projects 📁 ~/Desktop
|
|
234
|
-
You: ~/AGI/MyProject/NewDir
|
|
235
|
-
Bot: ✅ 已新建目录:~/AGI/MyProject/NewDir
|
|
236
|
-
What should we name this agent?
|
|
237
|
-
You: 小美
|
|
238
|
-
Bot: Describe 小美's role and responsibilities:
|
|
239
|
-
You: Personal assistant. Manages my calendar, drafts messages, and tracks todos.
|
|
240
|
-
Bot: ✅ Agent「小美」created. CLAUDE.md updated with role definition.
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
You can tap a button to pick an existing directory, or type any path directly in chat. If the path doesn't exist, it's created automatically. All entry points (`/agent new` wizard and `/agent bind`) validate that the directory is real before saving.
|
|
316
|
+
You can tap a button to pick an existing directory, or type any path directly in chat. If the path doesn't exist, it's created automatically.
|
|
244
317
|
|
|
245
318
|
### From config file (for power users)
|
|
246
319
|
|
|
@@ -263,7 +336,8 @@ projects:
|
|
|
263
336
|
heartbeat_tasks:
|
|
264
337
|
- name: "daily-review"
|
|
265
338
|
prompt: "Review yesterday's commits and flag any issues"
|
|
266
|
-
|
|
339
|
+
at: "20:30"
|
|
340
|
+
days: [mon, tue, wed, thu, fri]
|
|
267
341
|
notify: true
|
|
268
342
|
|
|
269
343
|
feishu:
|
|
@@ -294,10 +368,20 @@ All agents share your cognitive profile (`~/.claude_profile.yaml`) — they all
|
|
|
294
368
|
| `/list` | Browse & download project files |
|
|
295
369
|
| `/model` | Switch model (sonnet/opus/haiku) |
|
|
296
370
|
| `/agent bind <name> [dir]` | Register group as dedicated agent |
|
|
371
|
+
| `/mac` | macOS control helper: permissions check/open + AppleScript/JXA execution |
|
|
297
372
|
| `/sh <cmd>` | Raw shell — bypasses Claude |
|
|
298
373
|
| `/memory` | Memory stats: fact count, session tags, DB size |
|
|
299
374
|
| `/memory <keyword>` | Search long-term facts by keyword |
|
|
300
375
|
| `/doctor` | Interactive diagnostics |
|
|
376
|
+
| `/user add <open_id>` | Add a user (admin only) |
|
|
377
|
+
| `/user role <open_id> <admin\|member>` | Set user role |
|
|
378
|
+
| `/user list` | List all configured users |
|
|
379
|
+
| `/user remove <open_id>` | Remove a user |
|
|
380
|
+
| `/sessions` | Browse recent sessions with last message preview |
|
|
381
|
+
| `/teamtask create <agent> <goal>` | Create a cross-agent collaboration task |
|
|
382
|
+
| `/teamtask` | List recent TeamTasks (last 10) |
|
|
383
|
+
| `/teamtask <task_id>` | View task detail |
|
|
384
|
+
| `/teamtask resume <task_id>` | Resume a task |
|
|
301
385
|
|
|
302
386
|
## How It Works
|
|
303
387
|
|
package/index.js
CHANGED
|
@@ -30,7 +30,7 @@ if (!fs.existsSync(METAME_DIR)) {
|
|
|
30
30
|
// Auto-deploy bundled scripts to ~/.metame/
|
|
31
31
|
// IMPORTANT: daemon.yaml is USER CONFIG — never overwrite it. Only daemon-default.yaml (template) is synced.
|
|
32
32
|
const scriptsDir = path.join(__dirname, 'scripts');
|
|
33
|
-
const BUNDLED_BASE_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.js', 'daemon.js', 'telegram-adapter.js', 'feishu-adapter.js', 'daemon-default.yaml', 'providers.js', 'session-analytics.js', 'resolve-yaml.js', 'utils.js', 'skill-evolution.js', 'memory.js', 'memory-extract.js', 'qmd-client.js', 'session-summarize.js'];
|
|
33
|
+
const BUNDLED_BASE_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.js', 'daemon.js', 'telegram-adapter.js', 'feishu-adapter.js', 'daemon-default.yaml', 'providers.js', 'session-analytics.js', 'resolve-yaml.js', 'utils.js', 'skill-evolution.js', 'memory.js', 'memory-extract.js', 'memory-search.js', 'qmd-client.js', 'session-summarize.js', 'check-macos-control-capabilities.sh', 'usage-classifier.js', 'task-board.js'];
|
|
34
34
|
const DAEMON_MODULE_SCRIPTS = (() => {
|
|
35
35
|
try {
|
|
36
36
|
return fs.readdirSync(scriptsDir).filter((f) => /^daemon-[\w-]+\.js$/.test(f));
|
|
@@ -1755,15 +1755,22 @@ if (!isKnownUser) {
|
|
|
1755
1755
|
// RAG: inject relevant facts based on current project (desktop-side equivalent of daemon RAG)
|
|
1756
1756
|
try {
|
|
1757
1757
|
const memory = require(path.join(__dirname, 'scripts', 'memory.js'));
|
|
1758
|
-
|
|
1759
|
-
|
|
1758
|
+
const { projectScopeFromCwd } = require(path.join(__dirname, 'scripts', 'utils.js'));
|
|
1759
|
+
// Keep cwd basename as authoritative project filter for legacy rows (scope IS NULL).
|
|
1760
|
+
const cwdProject = path.basename(process.cwd());
|
|
1761
|
+
let repoProject = cwdProject;
|
|
1760
1762
|
try {
|
|
1761
1763
|
const { execSync } = require('child_process');
|
|
1762
1764
|
const remote = execSync('git remote get-url origin 2>/dev/null || true', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
1763
|
-
if (remote)
|
|
1765
|
+
if (remote) repoProject = path.basename(remote, '.git');
|
|
1764
1766
|
} catch { /* not a git repo, use dirname */ }
|
|
1765
1767
|
|
|
1766
|
-
const
|
|
1768
|
+
const factQuery = repoProject === cwdProject ? cwdProject : `${repoProject} ${cwdProject}`;
|
|
1769
|
+
const facts = memory.searchFacts(factQuery, {
|
|
1770
|
+
limit: 5,
|
|
1771
|
+
project: cwdProject || undefined,
|
|
1772
|
+
scope: projectScopeFromCwd(process.cwd()) || undefined,
|
|
1773
|
+
});
|
|
1767
1774
|
if (facts.length > 0) {
|
|
1768
1775
|
const factBlock = facts.map(f => `- [${f.relation}] ${f.value}`).join('\n');
|
|
1769
1776
|
launchArgs.push(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metame-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.19",
|
|
4
4
|
"description": "The Cognitive Profile Layer for Claude Code. Knows how you think, not just what you said.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"test": "node --test scripts/*.test.js",
|
|
15
15
|
"start": "node index.js",
|
|
16
|
-
"sync:plugin": "cp scripts/schema.js scripts/pending-traits.js scripts/signal-capture.js scripts/distill.js scripts/daemon.js scripts/daemon-agent-commands.js scripts/daemon-session-commands.js scripts/daemon-admin-commands.js scripts/daemon-exec-commands.js scripts/daemon-ops-commands.js scripts/daemon-session-store.js scripts/daemon-checkpoints.js scripts/daemon-bridges.js scripts/daemon-file-browser.js scripts/daemon-runtime-lifecycle.js scripts/daemon-notify.js scripts/daemon-claude-engine.js scripts/daemon-command-router.js scripts/daemon-agent-tools.js scripts/daemon-task-scheduler.js scripts/telegram-adapter.js scripts/feishu-adapter.js scripts/daemon-default.yaml scripts/providers.js scripts/utils.js scripts/resolve-yaml.js scripts/memory.js scripts/memory-extract.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/skill-evolution.js plugin/scripts/ && echo '✅ Plugin scripts synced'",
|
|
16
|
+
"sync:plugin": "cp scripts/schema.js scripts/pending-traits.js scripts/signal-capture.js scripts/distill.js scripts/daemon.js scripts/daemon-agent-commands.js scripts/daemon-session-commands.js scripts/daemon-admin-commands.js scripts/daemon-exec-commands.js scripts/daemon-ops-commands.js scripts/daemon-session-store.js scripts/daemon-checkpoints.js scripts/daemon-bridges.js scripts/daemon-file-browser.js scripts/daemon-runtime-lifecycle.js scripts/daemon-notify.js scripts/daemon-claude-engine.js scripts/daemon-command-router.js scripts/daemon-user-acl.js scripts/daemon-agent-tools.js scripts/daemon-task-scheduler.js scripts/daemon-task-envelope.js scripts/task-board.js scripts/telegram-adapter.js scripts/feishu-adapter.js scripts/daemon-default.yaml scripts/providers.js scripts/utils.js scripts/usage-classifier.js scripts/resolve-yaml.js scripts/memory.js scripts/memory-extract.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/skill-evolution.js scripts/check-macos-control-capabilities.sh plugin/scripts/ && echo '✅ Plugin scripts synced'",
|
|
17
17
|
"restart:daemon": "node index.js stop 2>/dev/null; sleep 1; node index.js start 2>/dev/null || echo '⚠️ Daemon not running or restart failed'",
|
|
18
18
|
"precommit": "npm run sync:plugin && npm run restart:daemon"
|
|
19
19
|
},
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -u
|
|
3
|
+
set -o pipefail
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
WARN=0
|
|
8
|
+
|
|
9
|
+
print_line() {
|
|
10
|
+
printf '%s\n' "$1"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
run_check() {
|
|
14
|
+
local name="$1"
|
|
15
|
+
local mode="$2"
|
|
16
|
+
local cmd="$3"
|
|
17
|
+
|
|
18
|
+
local output
|
|
19
|
+
output="$(bash -o pipefail -lc "$cmd" 2>&1)"
|
|
20
|
+
local code=$?
|
|
21
|
+
|
|
22
|
+
if [ "$mode" = "pass_on_zero" ]; then
|
|
23
|
+
if [ $code -eq 0 ]; then
|
|
24
|
+
PASS=$((PASS + 1))
|
|
25
|
+
print_line "[PASS] $name"
|
|
26
|
+
[ -n "$output" ] && print_line " $output"
|
|
27
|
+
else
|
|
28
|
+
FAIL=$((FAIL + 1))
|
|
29
|
+
print_line "[FAIL] $name"
|
|
30
|
+
[ -n "$output" ] && print_line " $output"
|
|
31
|
+
fi
|
|
32
|
+
return
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
if [ "$mode" = "warn_on_nonzero" ]; then
|
|
36
|
+
if [ $code -eq 0 ]; then
|
|
37
|
+
PASS=$((PASS + 1))
|
|
38
|
+
print_line "[PASS] $name"
|
|
39
|
+
[ -n "$output" ] && print_line " $output"
|
|
40
|
+
else
|
|
41
|
+
WARN=$((WARN + 1))
|
|
42
|
+
print_line "[WARN] $name"
|
|
43
|
+
[ -n "$output" ] && print_line " $output"
|
|
44
|
+
fi
|
|
45
|
+
return
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
FAIL=$((FAIL + 1))
|
|
49
|
+
print_line "[FAIL] $name"
|
|
50
|
+
print_line " invalid mode: $mode"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
print_line "MetaMe macOS control capability check"
|
|
54
|
+
print_line "Timestamp: $(date '+%Y-%m-%d %H:%M:%S %z')"
|
|
55
|
+
print_line ""
|
|
56
|
+
|
|
57
|
+
run_check "osascript binary available" "pass_on_zero" "which osascript"
|
|
58
|
+
run_check "AppleScript baseline" "pass_on_zero" "osascript -e 'return \"ok\"'"
|
|
59
|
+
run_check "Finder automation" "pass_on_zero" "osascript -e 'tell application \"Finder\" to get name of startup disk'"
|
|
60
|
+
run_check "System Events accessibility" "pass_on_zero" "osascript -e 'tell application \"System Events\" to get UI elements enabled'"
|
|
61
|
+
run_check "GUI app launch/control (Calculator)" "pass_on_zero" "open -a Calculator >/dev/null 2>&1; sleep 1; osascript -e 'tell application \"System Events\" to tell process \"Calculator\" to return {frontmost, (count of windows)}'; osascript -e 'tell application \"Calculator\" to quit' >/dev/null 2>&1"
|
|
62
|
+
|
|
63
|
+
SHOT_PATH="/tmp/metame_gui_test_$$.png"
|
|
64
|
+
run_check "Screenshot capability (screencapture)" "pass_on_zero" "screencapture -x '$SHOT_PATH' && ls -lh '$SHOT_PATH'"
|
|
65
|
+
rm -f "$SHOT_PATH" >/dev/null 2>&1
|
|
66
|
+
|
|
67
|
+
run_check "Full Disk probe: read ~/Library/Mail" "warn_on_nonzero" "ls '$HOME/Library/Mail' | head -n 3"
|
|
68
|
+
run_check "Full Disk probe: query Safari History.db" "warn_on_nonzero" "sqlite3 '$HOME/Library/Safari/History.db' 'select count(*) from history_items;'"
|
|
69
|
+
|
|
70
|
+
print_line ""
|
|
71
|
+
print_line "Summary: pass=$PASS warn=$WARN fail=$FAIL"
|
|
72
|
+
|
|
73
|
+
if [ $FAIL -gt 0 ]; then
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
exit 0
|