@askexenow/exe-os 0.8.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 +139 -0
- package/dist/bin/backfill-responses.js +1912 -0
- package/dist/bin/backfill-vectors.js +1642 -0
- package/dist/bin/cleanup-stale-review-tasks.js +1339 -0
- package/dist/bin/cli.js +18800 -0
- package/dist/bin/exe-agent.js +1858 -0
- package/dist/bin/exe-assign.js +1957 -0
- package/dist/bin/exe-boot.js +6460 -0
- package/dist/bin/exe-call.js +197 -0
- package/dist/bin/exe-cloud.js +850 -0
- package/dist/bin/exe-dispatch.js +1146 -0
- package/dist/bin/exe-doctor.js +1657 -0
- package/dist/bin/exe-export-behaviors.js +1494 -0
- package/dist/bin/exe-forget.js +1627 -0
- package/dist/bin/exe-gateway.js +7732 -0
- package/dist/bin/exe-healthcheck.js +207 -0
- package/dist/bin/exe-heartbeat.js +1647 -0
- package/dist/bin/exe-kill.js +1479 -0
- package/dist/bin/exe-launch-agent.js +1704 -0
- package/dist/bin/exe-link.js +192 -0
- package/dist/bin/exe-new-employee.js +852 -0
- package/dist/bin/exe-pending-messages.js +1446 -0
- package/dist/bin/exe-pending-notifications.js +1321 -0
- package/dist/bin/exe-pending-reviews.js +1468 -0
- package/dist/bin/exe-repo-drift.js +95 -0
- package/dist/bin/exe-review.js +1590 -0
- package/dist/bin/exe-search.js +2651 -0
- package/dist/bin/exe-session-cleanup.js +3173 -0
- package/dist/bin/exe-settings.js +354 -0
- package/dist/bin/exe-status.js +1532 -0
- package/dist/bin/exe-team.js +1324 -0
- package/dist/bin/git-sweep.js +2185 -0
- package/dist/bin/graph-backfill.js +1968 -0
- package/dist/bin/graph-export.js +1604 -0
- package/dist/bin/install.js +656 -0
- package/dist/bin/list-providers.js +140 -0
- package/dist/bin/scan-tasks.js +1820 -0
- package/dist/bin/setup.js +951 -0
- package/dist/bin/shard-migrate.js +1494 -0
- package/dist/bin/update.js +95 -0
- package/dist/bin/wiki-sync.js +1514 -0
- package/dist/gateway/index.js +8848 -0
- package/dist/hooks/bug-report-worker.js +2743 -0
- package/dist/hooks/commit-complete.js +2108 -0
- package/dist/hooks/error-recall.js +2861 -0
- package/dist/hooks/exe-heartbeat-hook.js +232 -0
- package/dist/hooks/ingest-worker.js +4793 -0
- package/dist/hooks/ingest.js +684 -0
- package/dist/hooks/instructions-loaded.js +1880 -0
- package/dist/hooks/notification.js +1726 -0
- package/dist/hooks/post-compact.js +1751 -0
- package/dist/hooks/pre-compact.js +1746 -0
- package/dist/hooks/pre-tool-use.js +2191 -0
- package/dist/hooks/prompt-ingest-worker.js +2126 -0
- package/dist/hooks/prompt-submit.js +4693 -0
- package/dist/hooks/response-ingest-worker.js +1936 -0
- package/dist/hooks/session-end.js +1752 -0
- package/dist/hooks/session-start.js +2795 -0
- package/dist/hooks/stop.js +1835 -0
- package/dist/hooks/subagent-stop.js +1726 -0
- package/dist/hooks/summary-worker.js +2661 -0
- package/dist/index.js +11834 -0
- package/dist/lib/cloud-sync.js +495 -0
- package/dist/lib/config.js +222 -0
- package/dist/lib/consolidation.js +476 -0
- package/dist/lib/crypto.js +51 -0
- package/dist/lib/database.js +730 -0
- package/dist/lib/device-registry.js +900 -0
- package/dist/lib/embedder.js +632 -0
- package/dist/lib/employee-templates.js +543 -0
- package/dist/lib/employees.js +177 -0
- package/dist/lib/error-detector.js +156 -0
- package/dist/lib/exe-daemon-client.js +451 -0
- package/dist/lib/exe-daemon.js +8285 -0
- package/dist/lib/file-grep.js +199 -0
- package/dist/lib/hybrid-search.js +1819 -0
- package/dist/lib/identity-templates.js +320 -0
- package/dist/lib/identity.js +223 -0
- package/dist/lib/keychain.js +145 -0
- package/dist/lib/license.js +377 -0
- package/dist/lib/messaging.js +1376 -0
- package/dist/lib/reminders.js +63 -0
- package/dist/lib/schedules.js +1396 -0
- package/dist/lib/session-registry.js +52 -0
- package/dist/lib/skill-learning.js +477 -0
- package/dist/lib/status-brief.js +235 -0
- package/dist/lib/store.js +1551 -0
- package/dist/lib/task-router.js +62 -0
- package/dist/lib/tasks.js +2456 -0
- package/dist/lib/tmux-routing.js +2836 -0
- package/dist/lib/tmux-status.js +261 -0
- package/dist/lib/tmux-transport.js +83 -0
- package/dist/lib/transport.js +128 -0
- package/dist/lib/ws-auth.js +19 -0
- package/dist/lib/ws-client.js +160 -0
- package/dist/mcp/server.js +10538 -0
- package/dist/mcp/tools/complete-reminder.js +67 -0
- package/dist/mcp/tools/create-reminder.js +52 -0
- package/dist/mcp/tools/create-task.js +1853 -0
- package/dist/mcp/tools/deactivate-behavior.js +263 -0
- package/dist/mcp/tools/list-reminders.js +62 -0
- package/dist/mcp/tools/list-tasks.js +463 -0
- package/dist/mcp/tools/send-message.js +1382 -0
- package/dist/mcp/tools/update-task.js +1692 -0
- package/dist/runtime/index.js +6809 -0
- package/dist/tui/App.js +17479 -0
- package/package.json +104 -0
- package/src/commands/exe/assign.md +17 -0
- package/src/commands/exe/build-adv.md +381 -0
- package/src/commands/exe/call.md +133 -0
- package/src/commands/exe/cloud.md +17 -0
- package/src/commands/exe/employee-heartbeat.md +44 -0
- package/src/commands/exe/forget.md +15 -0
- package/src/commands/exe/heartbeat.md +92 -0
- package/src/commands/exe/intercom.md +81 -0
- package/src/commands/exe/kill.md +34 -0
- package/src/commands/exe/launch.md +52 -0
- package/src/commands/exe/link.md +17 -0
- package/src/commands/exe/logs.md +22 -0
- package/src/commands/exe/new-employee.md +12 -0
- package/src/commands/exe/review.md +14 -0
- package/src/commands/exe/schedule.md +108 -0
- package/src/commands/exe/search.md +13 -0
- package/src/commands/exe/sessions.md +25 -0
- package/src/commands/exe/settings.md +13 -0
- package/src/commands/exe/setup.md +171 -0
- package/src/commands/exe/status.md +15 -0
- package/src/commands/exe/team.md +11 -0
- package/src/commands/exe/update.md +11 -0
- package/src/commands/exe.md +181 -0
package/package.json
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@askexenow/exe-os",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
|
|
5
|
+
"license": "CC-BY-NC-4.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/AskExe/exe-os.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://askexe.com",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude-code",
|
|
14
|
+
"claude",
|
|
15
|
+
"mcp",
|
|
16
|
+
"memory",
|
|
17
|
+
"ai-agents",
|
|
18
|
+
"multi-agent",
|
|
19
|
+
"embeddings",
|
|
20
|
+
"vector-search",
|
|
21
|
+
"libsql",
|
|
22
|
+
"turso",
|
|
23
|
+
"rag"
|
|
24
|
+
],
|
|
25
|
+
"bin": {
|
|
26
|
+
"exe-os": "./dist/bin/cli.js",
|
|
27
|
+
"exe-os-install": "./dist/bin/install.js",
|
|
28
|
+
"exe-agent": "./dist/bin/exe-agent.js",
|
|
29
|
+
"exe-os-backfill-responses": "./dist/bin/backfill-responses.js",
|
|
30
|
+
"exe-os-repo-drift": "./dist/bin/exe-repo-drift.js",
|
|
31
|
+
"exe-kill": "./dist/bin/exe-kill.js",
|
|
32
|
+
"exe-os-list-providers": "./dist/bin/list-providers.js",
|
|
33
|
+
"exe-export-behaviors": "./dist/bin/exe-export-behaviors.js",
|
|
34
|
+
"exe-launch-agent": "./dist/bin/exe-launch-agent.js",
|
|
35
|
+
"exe": "./dist/bin/exe-launch-agent.js",
|
|
36
|
+
"yoshi": "./dist/bin/exe-launch-agent.js",
|
|
37
|
+
"tom": "./dist/bin/exe-launch-agent.js",
|
|
38
|
+
"mari": "./dist/bin/exe-launch-agent.js",
|
|
39
|
+
"sasha": "./dist/bin/exe-launch-agent.js",
|
|
40
|
+
"exe-opencode": "./dist/bin/exe-launch-agent.js",
|
|
41
|
+
"yoshi-opencode": "./dist/bin/exe-launch-agent.js",
|
|
42
|
+
"tom-opencode": "./dist/bin/exe-launch-agent.js",
|
|
43
|
+
"mari-opencode": "./dist/bin/exe-launch-agent.js",
|
|
44
|
+
"sasha-opencode": "./dist/bin/exe-launch-agent.js"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"src/commands",
|
|
49
|
+
"package.json",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
],
|
|
52
|
+
"exports": {
|
|
53
|
+
".": "./dist/index.js",
|
|
54
|
+
"./runtime": "./dist/runtime/index.js",
|
|
55
|
+
"./gateway": "./dist/gateway/index.js",
|
|
56
|
+
"./lib/*": "./dist/lib/*.js",
|
|
57
|
+
"./hooks/*": "./dist/hooks/*.js",
|
|
58
|
+
"./mcp/server": "./dist/mcp/server.js"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=18.0.0"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"test:watch": "vitest",
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"build": "tsup",
|
|
68
|
+
"deploy": "tsup && npm install -g . && node dist/bin/install.js --global",
|
|
69
|
+
"postinstall": "node dist/bin/install.js --commands-only 2>/dev/null || true",
|
|
70
|
+
"prepublishOnly": "npm run typecheck && npm run build && npx vitest run --exclude 'tests/tui/**' --exclude 'tests/lib/tmux-routing.test.ts' --exclude 'tests/lib/intercom-routing.test.ts'"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@anthropic-ai/sdk": "^0.80.0",
|
|
74
|
+
"@discordjs/voice": "^0.19.2",
|
|
75
|
+
"@libsql/client": "^0.14.0",
|
|
76
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
77
|
+
"@slack/bolt": "^4.7.0",
|
|
78
|
+
"@slack/web-api": "^7.15.1",
|
|
79
|
+
"@types/react": "^19.2.14",
|
|
80
|
+
"@types/ws": "^8.18.1",
|
|
81
|
+
"@whiskeysockets/baileys": "^7.0.0-rc.9",
|
|
82
|
+
"bip39": "^3.1.0",
|
|
83
|
+
"discord.js": "^14.26.3",
|
|
84
|
+
"grammy": "^1.42.0",
|
|
85
|
+
"ink": "^6.8.0",
|
|
86
|
+
"ink-text-input": "^6.0.0",
|
|
87
|
+
"jose": "^6.2.2",
|
|
88
|
+
"keytar": "^7.9.0",
|
|
89
|
+
"node-llama-cpp": "^3.18.0",
|
|
90
|
+
"nodemailer": "^8.0.5",
|
|
91
|
+
"openai": "^6.33.0",
|
|
92
|
+
"react": "^19.2.4",
|
|
93
|
+
"ws": "^8.20.0",
|
|
94
|
+
"zod": "^4.3.6"
|
|
95
|
+
},
|
|
96
|
+
"devDependencies": {
|
|
97
|
+
"@types/node": "^22.0.0",
|
|
98
|
+
"@types/nodemailer": "^8.0.0",
|
|
99
|
+
"tsup": "^8.5.1",
|
|
100
|
+
"tsx": "^4.0.0",
|
|
101
|
+
"typescript": "^5.0.0",
|
|
102
|
+
"vitest": "^3.0.0"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Assign a task to a specialist (auto-route or direct)
|
|
3
|
+
allowed-tools: Bash
|
|
4
|
+
argument-hint: [employee-name] "<task description>"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Assign a task to the best specialist or a named employee.
|
|
8
|
+
|
|
9
|
+
Auto-route (finds best match):
|
|
10
|
+
```bash
|
|
11
|
+
node "$(npm root -g)/exe-os/dist/bin/exe-assign.js" "debug the auth timeout"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Direct assign:
|
|
15
|
+
```bash
|
|
16
|
+
node "$(npm root -g)/exe-os/dist/bin/exe-assign.js" yoshi "debug the auth timeout"
|
|
17
|
+
```
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Multi-agent parallel development — spec, test, decompose, implement in parallel, integrate
|
|
3
|
+
allowed-tools: Bash, Read, Edit, Write, Glob, Grep, Agent, recall_my_memory, ask_team_memory, store_memory, create_task, update_task, list_tasks
|
|
4
|
+
argument-hint: [--auto] [--agents N] [--skip-spec] [--skip-tests] "<feature description>"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Multi-agent parallel development workflow. Yoshi specs and writes tests, spawns sub-agents to implement in parallel, then integrates and commits.
|
|
8
|
+
|
|
9
|
+
**FULLY AUTONOMOUS.** Each phase has a mechanical checkpoint that verifies completion and self-heals on failure. Never ask the user for help — fix it yourself.
|
|
10
|
+
|
|
11
|
+
## Checkpoint protocol (mandatory for every phase)
|
|
12
|
+
|
|
13
|
+
After completing each phase, run the checkpoint. The pattern:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
1. Run VERIFY command (bash — returns pass/fail)
|
|
17
|
+
2. If PASS → proceed to next phase
|
|
18
|
+
3. If FAIL → run FIX action (the action is defined per-phase)
|
|
19
|
+
4. Re-run VERIFY
|
|
20
|
+
5. Repeat up to 3 times
|
|
21
|
+
6. After 3 failures → log error with store_memory, continue to next phase
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**NEVER ask the user.** Fix it yourself or log and move on.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Parse arguments
|
|
29
|
+
|
|
30
|
+
Extract flags and feature description from $ARGUMENTS:
|
|
31
|
+
|
|
32
|
+
- `--auto` — skip approval checkpoint before spawning agents (default: ask)
|
|
33
|
+
- `--agents N` — max parallel sub-agents (default: 3)
|
|
34
|
+
- `--skip-spec` — reuse existing spec from `exe/output/{slug}/spec.md`
|
|
35
|
+
- `--skip-tests` — reuse existing tests (dangerous)
|
|
36
|
+
- Everything else is the feature description
|
|
37
|
+
|
|
38
|
+
Derive `{slug}` by kebab-casing the feature description (e.g., "Add rate limiting" → "add-rate-limiting").
|
|
39
|
+
|
|
40
|
+
Write build state tracker:
|
|
41
|
+
```bash
|
|
42
|
+
mkdir -p exe/output/{slug}
|
|
43
|
+
echo '{"phase":0,"slug":"{slug}","startedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","feature":"{feature}"}' > exe/output/{slug}/build-state.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Phase 1: Spec (sequential)
|
|
49
|
+
|
|
50
|
+
**Skip if `--skip-spec` and `exe/output/{slug}/spec.md` exists.**
|
|
51
|
+
|
|
52
|
+
1. Read `exe/ARCHITECTURE.md` for system context
|
|
53
|
+
2. Use `recall_my_memory` to search for related past work on this feature area
|
|
54
|
+
3. Identify:
|
|
55
|
+
- Which existing files need modification (read them)
|
|
56
|
+
- What new files need creation
|
|
57
|
+
- API contracts, data types, interfaces
|
|
58
|
+
- Constraints from ARCHITECTURE.md invariants
|
|
59
|
+
- Test framework in use (check for vitest.config, jest.config, etc.)
|
|
60
|
+
4. Write the spec to `exe/output/{slug}/spec.md`:
|
|
61
|
+
|
|
62
|
+
```markdown
|
|
63
|
+
# Feature: {feature description}
|
|
64
|
+
|
|
65
|
+
## Summary
|
|
66
|
+
{1-2 sentence overview}
|
|
67
|
+
|
|
68
|
+
## Files to modify
|
|
69
|
+
{list of existing files with what changes}
|
|
70
|
+
|
|
71
|
+
## Files to create
|
|
72
|
+
{list of new files with purpose}
|
|
73
|
+
|
|
74
|
+
## API / Interface contracts
|
|
75
|
+
{types, function signatures, data shapes}
|
|
76
|
+
|
|
77
|
+
## Constraints
|
|
78
|
+
{from ARCHITECTURE.md, invariants that must not be violated}
|
|
79
|
+
|
|
80
|
+
## Test framework
|
|
81
|
+
{vitest/jest/other — detected from config}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Checkpoint 1: Spec exists
|
|
85
|
+
|
|
86
|
+
**VERIFY:**
|
|
87
|
+
```bash
|
|
88
|
+
[ -f "exe/output/{slug}/spec.md" ] && grep -q "## Files to" "exe/output/{slug}/spec.md" && echo "CHECKPOINT_PASS" || echo "CHECKPOINT_FAIL"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**FIX:** Write the spec file. You have all the context from steps 1-3. Write it now.
|
|
92
|
+
|
|
93
|
+
**Update state:**
|
|
94
|
+
```bash
|
|
95
|
+
node -e "const f='exe/output/{slug}/build-state.json'; const s=JSON.parse(require('fs').readFileSync(f,'utf8')); s.phase=1; s.specFile='exe/output/{slug}/spec.md'; require('fs').writeFileSync(f,JSON.stringify(s))"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Phase 2: Test (sequential)
|
|
101
|
+
|
|
102
|
+
**Skip if `--skip-tests`.**
|
|
103
|
+
|
|
104
|
+
1. Read the spec from Phase 1
|
|
105
|
+
2. Write failing tests that define the acceptance criteria:
|
|
106
|
+
- One test file per logical module/concern
|
|
107
|
+
- Tests must import from the expected source paths (even if files don't exist yet)
|
|
108
|
+
- Tests must be runnable and RED (failing because implementation doesn't exist)
|
|
109
|
+
- Group tests by which subtask will make them pass
|
|
110
|
+
3. Write test files to the codebase (e.g., `tests/lib/{feature}.test.ts`)
|
|
111
|
+
4. Create a test manifest at `exe/output/{slug}/test-manifest.md`:
|
|
112
|
+
|
|
113
|
+
```markdown
|
|
114
|
+
# Test Manifest
|
|
115
|
+
|
|
116
|
+
## Group 1: {module name}
|
|
117
|
+
- File: tests/lib/{file}.test.ts
|
|
118
|
+
- Tests: {count}
|
|
119
|
+
- Covers: {what this group validates}
|
|
120
|
+
|
|
121
|
+
## Group 2: ...
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Checkpoint 2: Tests exist and fail (red phase)
|
|
125
|
+
|
|
126
|
+
**VERIFY:**
|
|
127
|
+
```bash
|
|
128
|
+
TEST_FILES=$(grep -oP '(?<=File: ).*\.test\.ts' "exe/output/{slug}/test-manifest.md" 2>/dev/null)
|
|
129
|
+
if [ -z "$TEST_FILES" ]; then echo "CHECKPOINT_FAIL:no_manifest"; exit 0; fi
|
|
130
|
+
ALL_EXIST=true
|
|
131
|
+
for f in $TEST_FILES; do [ -f "$f" ] || ALL_EXIST=false; done
|
|
132
|
+
if [ "$ALL_EXIST" = false ]; then echo "CHECKPOINT_FAIL:missing_test_files"; exit 0; fi
|
|
133
|
+
# Tests should FAIL (red phase — no implementation yet). Passing tests means they're too weak.
|
|
134
|
+
npx vitest run $TEST_FILES --reporter=verbose 2>&1 | tail -5
|
|
135
|
+
RESULT=$(npx vitest run $TEST_FILES 2>&1 | tail -1)
|
|
136
|
+
echo "$RESULT" | grep -q "fail" && echo "CHECKPOINT_PASS:tests_failing_as_expected" || echo "CHECKPOINT_WARN:tests_not_failing"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**FIX:** If no manifest: write it. If missing test files: write them. If tests pass unexpectedly: the tests are too weak — add assertions that test the actual new behavior.
|
|
140
|
+
|
|
141
|
+
**Update state:**
|
|
142
|
+
```bash
|
|
143
|
+
node -e "const f='exe/output/{slug}/build-state.json'; const s=JSON.parse(require('fs').readFileSync(f,'utf8')); s.phase=2; s.testManifest='exe/output/{slug}/test-manifest.md'; require('fs').writeFileSync(f,JSON.stringify(s))"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Phase 3: Decompose (sequential)
|
|
149
|
+
|
|
150
|
+
1. Read the spec and test manifest
|
|
151
|
+
2. Break implementation into N independent subtasks where each subtask:
|
|
152
|
+
- Has a clear, bounded scope (1-3 files)
|
|
153
|
+
- Is responsible for making specific test group(s) pass
|
|
154
|
+
- Lists exact files to create/modify
|
|
155
|
+
- Includes relevant architecture context (copy the relevant section, don't reference)
|
|
156
|
+
- Declares `blocked_by` if it depends on another subtask's output
|
|
157
|
+
3. Create subtasks via `create_task` MCP with `parent_task_id` set to the build-adv parent task:
|
|
158
|
+
|
|
159
|
+
For each subtask, call create_task with:
|
|
160
|
+
- `title`: "build-adv/{slug}: {subtask title}"
|
|
161
|
+
- `assigned_to`: "tom" (or next available agent)
|
|
162
|
+
- `project_name`: current project
|
|
163
|
+
- `priority`: "p1"
|
|
164
|
+
- `context`: Full subtask spec including test files to make pass, files to modify, constraints
|
|
165
|
+
- `parent_task_id`: the parent task ID (if this build-adv was triggered from a task)
|
|
166
|
+
- `blocked_by`: subtask ID of dependency (if any)
|
|
167
|
+
|
|
168
|
+
4. Write subtask specs to `exe/output/{slug}/subtask-{N}.md` as reference copies
|
|
169
|
+
|
|
170
|
+
5. Determine the execution waves:
|
|
171
|
+
- Wave 1: all subtasks with no dependencies
|
|
172
|
+
- Wave 2: subtasks unblocked after wave 1
|
|
173
|
+
- Wave N: remaining
|
|
174
|
+
|
|
175
|
+
**If `--auto` is NOT set:** Present the subtask breakdown and wave plan to the user. Ask: "Proceed with implementation? (y/n)". Wait for approval.
|
|
176
|
+
|
|
177
|
+
**If `--auto` IS set:** Proceed immediately.
|
|
178
|
+
|
|
179
|
+
### Checkpoint 3: Subtasks exist in DB
|
|
180
|
+
|
|
181
|
+
**VERIFY:**
|
|
182
|
+
```bash
|
|
183
|
+
SUBTASK_COUNT=$(node -e "
|
|
184
|
+
const {getClient}=require(require('path').join(require('child_process').execSync('npm root -g',{encoding:'utf8'}).trim(),'exe-os/dist/lib/turso.js'));
|
|
185
|
+
const {initStore}=require(require('path').join(require('child_process').execSync('npm root -g',{encoding:'utf8'}).trim(),'exe-os/dist/lib/store.js'));
|
|
186
|
+
(async()=>{await initStore();const c=getClient();const r=await c.execute({sql:\"SELECT COUNT(*) as cnt FROM tasks WHERE title LIKE 'build-adv/{slug}%' AND status != 'done'\",args:[]});console.log(Number(r.rows[0]?.cnt??0))})()
|
|
187
|
+
" 2>/dev/null)
|
|
188
|
+
[ "$SUBTASK_COUNT" -gt 0 ] 2>/dev/null && echo "CHECKPOINT_PASS:${SUBTASK_COUNT}_subtasks" || echo "CHECKPOINT_FAIL:no_subtasks_in_db"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**FIX:** Run `create_task` MCP for each subtask from the decomposition. You have the specs from step 2.
|
|
192
|
+
|
|
193
|
+
**Update state:**
|
|
194
|
+
```bash
|
|
195
|
+
node -e "const f='exe/output/{slug}/build-state.json'; const s=JSON.parse(require('fs').readFileSync(f,'utf8')); s.phase=3; s.subtaskCount=${SUBTASK_COUNT:-0}; require('fs').writeFileSync(f,JSON.stringify(s))"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Phase 4: Parallel Implementation (sub-agents)
|
|
201
|
+
|
|
202
|
+
For each wave, spawn sub-agents in parallel (respecting `--agents N` limit):
|
|
203
|
+
|
|
204
|
+
1. Read each subtask spec
|
|
205
|
+
2. Read the test file(s) the subtask must make pass
|
|
206
|
+
3. Read any source files the subtask needs to modify
|
|
207
|
+
4. Launch Agent for each subtask in the wave:
|
|
208
|
+
|
|
209
|
+
Use Agent tool with these parameters:
|
|
210
|
+
- `subagent_type`: "general-purpose"
|
|
211
|
+
- `description`: "Implement: {subtask title}"
|
|
212
|
+
- `prompt`: Include the following in the prompt:
|
|
213
|
+
- The subtask spec (full content)
|
|
214
|
+
- The test file contents (full content)
|
|
215
|
+
- The current content of any files to modify
|
|
216
|
+
- Clear rules: make tests pass, don't modify tests, don't touch other files, report changes and test results
|
|
217
|
+
|
|
218
|
+
**Launch all agents in the same wave as parallel tool calls in a single message.**
|
|
219
|
+
|
|
220
|
+
5. Collect results from each agent
|
|
221
|
+
6. After wave completes, check if next wave's dependencies are met, launch next wave
|
|
222
|
+
|
|
223
|
+
### Checkpoint 4: Sub-agents completed work
|
|
224
|
+
|
|
225
|
+
**VERIFY:**
|
|
226
|
+
```bash
|
|
227
|
+
# Check git diff shows changes from sub-agents
|
|
228
|
+
CHANGED=$(git diff --stat HEAD 2>/dev/null | tail -1)
|
|
229
|
+
[ -n "$CHANGED" ] && echo "CHECKPOINT_PASS:$CHANGED" || echo "CHECKPOINT_FAIL:no_changes"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**FIX:** If no changes detected, re-launch failed agents. Read their error output and provide more context in the prompt.
|
|
233
|
+
|
|
234
|
+
**Update state:**
|
|
235
|
+
```bash
|
|
236
|
+
node -e "const f='exe/output/{slug}/build-state.json'; const s=JSON.parse(require('fs').readFileSync(f,'utf8')); s.phase=4; require('fs').writeFileSync(f,JSON.stringify(s))"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Print after each wave: `Wave {W} complete: {N}/{N} subtasks succeeded`
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Phase 5: Integration (sequential)
|
|
244
|
+
|
|
245
|
+
1. Review what each sub-agent changed (git diff or read modified files)
|
|
246
|
+
2. Run the full test suite:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
npx vitest run --reporter=verbose 2>&1 | tail -40
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
3. Run typecheck:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npx tsc --noEmit 2>&1 | head -40
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Checkpoint 5: Tests pass and typecheck clean
|
|
259
|
+
|
|
260
|
+
**VERIFY:**
|
|
261
|
+
```bash
|
|
262
|
+
npx tsc --noEmit 2>&1 && npx vitest run 2>&1 | tail -3
|
|
263
|
+
TSC_EXIT=$?
|
|
264
|
+
VITEST_RESULT=$(npx vitest run 2>&1 | tail -1)
|
|
265
|
+
echo "$VITEST_RESULT" | grep -q "passed" && [ $TSC_EXIT -eq 0 ] && echo "CHECKPOINT_PASS" || echo "CHECKPOINT_FAIL"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**FIX:** Read the test failures and type errors. Fix the code — resolve integration conflicts, missing imports, type mismatches. Do NOT modify test files. Re-run verification.
|
|
269
|
+
|
|
270
|
+
**Update state:**
|
|
271
|
+
```bash
|
|
272
|
+
node -e "const f='exe/output/{slug}/build-state.json'; const s=JSON.parse(require('fs').readFileSync(f,'utf8')); s.phase=5; s.testsPass=true; require('fs').writeFileSync(f,JSON.stringify(s))"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Phase 6: Commit
|
|
278
|
+
|
|
279
|
+
1. Stage all changed files:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
git add -u && git add .
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
2. Commit with message: `feat: {feature description}`
|
|
286
|
+
|
|
287
|
+
### Checkpoint 6: Git clean after commit
|
|
288
|
+
|
|
289
|
+
**VERIFY:**
|
|
290
|
+
```bash
|
|
291
|
+
git status --porcelain | head -5
|
|
292
|
+
[ -z "$(git status --porcelain)" ] && echo "CHECKPOINT_PASS" || echo "CHECKPOINT_FAIL"
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**FIX:** Stage and commit any remaining files: `git add -u && git add . && git commit -m "feat: {feature} (remaining files)"`
|
|
296
|
+
|
|
297
|
+
**Update state:**
|
|
298
|
+
```bash
|
|
299
|
+
COMMIT_HASH=$(git rev-parse --short HEAD)
|
|
300
|
+
node -e "const f='exe/output/{slug}/build-state.json'; const s=JSON.parse(require('fs').readFileSync(f,'utf8')); s.phase=6; s.commitHash='${COMMIT_HASH}'; require('fs').writeFileSync(f,JSON.stringify(s))"
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Phase 7: Close
|
|
306
|
+
|
|
307
|
+
1. Use `store_memory` to record:
|
|
308
|
+
- What was built
|
|
309
|
+
- Key decisions made during spec
|
|
310
|
+
- Subtask breakdown pattern (useful for future decomposition)
|
|
311
|
+
- Any integration issues encountered
|
|
312
|
+
|
|
313
|
+
2. If system structure changed, update `exe/ARCHITECTURE.md`
|
|
314
|
+
|
|
315
|
+
3. Mark subtasks as done via `update_task` MCP
|
|
316
|
+
|
|
317
|
+
### Checkpoint 7: All subtasks done in DB
|
|
318
|
+
|
|
319
|
+
**VERIFY:**
|
|
320
|
+
```bash
|
|
321
|
+
OPEN_COUNT=$(node -e "
|
|
322
|
+
const {getClient}=require(require('path').join(require('child_process').execSync('npm root -g',{encoding:'utf8'}).trim(),'exe-os/dist/lib/turso.js'));
|
|
323
|
+
const {initStore}=require(require('path').join(require('child_process').execSync('npm root -g',{encoding:'utf8'}).trim(),'exe-os/dist/lib/store.js'));
|
|
324
|
+
(async()=>{await initStore();const c=getClient();const r=await c.execute({sql:\"SELECT COUNT(*) as cnt FROM tasks WHERE title LIKE 'build-adv/{slug}%' AND status NOT IN ('done','cancelled')\",args:[]});console.log(Number(r.rows[0]?.cnt??0))})()
|
|
325
|
+
" 2>/dev/null)
|
|
326
|
+
[ "$OPEN_COUNT" -eq 0 ] 2>/dev/null && echo "CHECKPOINT_PASS" || echo "CHECKPOINT_FAIL:${OPEN_COUNT}_subtasks_still_open"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**FIX:** Call `update_task(done)` for each remaining open subtask with a result summary.
|
|
330
|
+
|
|
331
|
+
**Update state:**
|
|
332
|
+
```bash
|
|
333
|
+
node -e "const f='exe/output/{slug}/build-state.json'; const s=JSON.parse(require('fs').readFileSync(f,'utf8')); s.phase=7; s.completedAt=new Date().toISOString(); require('fs').writeFileSync(f,JSON.stringify(s))"
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Phase 8: Summary
|
|
339
|
+
|
|
340
|
+
Print final summary:
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
## /exe-build-adv Complete
|
|
344
|
+
|
|
345
|
+
Feature: {description}
|
|
346
|
+
Phases: spec -> test -> decompose -> {N} subtasks in {W} waves -> integrate -> commit
|
|
347
|
+
Tests: {pass}/{total} pass
|
|
348
|
+
Files changed: {count}
|
|
349
|
+
Commit: {hash}
|
|
350
|
+
|
|
351
|
+
Checkpoints: {passed}/{total} passed, {failed} self-healed, {skipped} skipped
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Build status
|
|
357
|
+
|
|
358
|
+
At any point, check the current state of a build-adv run:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
cat exe/output/{slug}/build-state.json 2>/dev/null | node -e "
|
|
362
|
+
const s=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
363
|
+
const phases=['parse','spec','test','decompose','implement','integrate','commit','close'];
|
|
364
|
+
console.log('Build: '+s.feature);
|
|
365
|
+
console.log('Phase: '+s.phase+'/7 ('+phases[s.phase]+')');
|
|
366
|
+
console.log('Started: '+s.startedAt);
|
|
367
|
+
if(s.completedAt) console.log('Completed: '+s.completedAt);
|
|
368
|
+
if(s.commitHash) console.log('Commit: '+s.commitHash);
|
|
369
|
+
if(s.subtaskCount) console.log('Subtasks: '+s.subtaskCount);
|
|
370
|
+
if(s.testsPass) console.log('Tests: passing');
|
|
371
|
+
"
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Error handling
|
|
377
|
+
|
|
378
|
+
- If daemon/embed fails during recall: continue without memory context
|
|
379
|
+
- If a sub-agent times out: note it, proceed with remaining agents, attempt manual fix in Phase 5
|
|
380
|
+
- After 3 checkpoint failures: log error with `store_memory`, continue to next phase — never block on user input
|
|
381
|
+
- Never silently skip a failing test — report it explicitly
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Launch an employee session with isolated config and memory tracking
|
|
3
|
+
allowed-tools: Bash, Read, Edit, Write, Glob, Grep, recall_my_memory, ask_team_memory, get_session_context, store_memory
|
|
4
|
+
argument-hint: [employee-name]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Load the named employee and become them for this session, or launch them in a new tmux window.
|
|
8
|
+
|
|
9
|
+
## 0. tmux gate (mandatory)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Robust tmux detection — handles su/sudo login shells (env vars stripped)
|
|
13
|
+
([ -n "$TMUX" ] || [ -n "$TMUX_PANE" ] || [[ "$TERM" == tmux* ]] || [[ "$TERM" == screen* ]] || tmux display-message -p '#{session_name}' >/dev/null 2>&1) && echo "TMUX_OK" || echo "TMUX_MISSING"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If `TMUX_MISSING`, show this message EXACTLY and STOP — do not launch any employee:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
═══════════════════════════════════════════════
|
|
20
|
+
tmux is required for exe-os multi-agent.
|
|
21
|
+
|
|
22
|
+
Start tmux first, then launch claude:
|
|
23
|
+
|
|
24
|
+
tmux new-session -s work
|
|
25
|
+
claude
|
|
26
|
+
/exe
|
|
27
|
+
|
|
28
|
+
Why: exe-os uses tmux for instant cross-session
|
|
29
|
+
communication between employees. Without it,
|
|
30
|
+
task dispatch, parallel execution, and agent
|
|
31
|
+
coordination don't work.
|
|
32
|
+
|
|
33
|
+
Install tmux:
|
|
34
|
+
macOS: brew install tmux
|
|
35
|
+
Linux: apt install tmux
|
|
36
|
+
|
|
37
|
+
New to tmux? https://tmuxcheatsheet.com
|
|
38
|
+
═══════════════════════════════════════════════
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 0b. Validate employee
|
|
42
|
+
|
|
43
|
+
Load the employee roster and find the employee:
|
|
44
|
+
```bash
|
|
45
|
+
cat ~/.exe-os/exe-employees.json 2>/dev/null || echo "NO_ROSTER"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If the employee name from $ARGUMENTS is not found in the roster, tell the user:
|
|
49
|
+
> Employee not found. Run `/exe-team` to see available employees, or `/exe-new-employee <name>` to create one.
|
|
50
|
+
|
|
51
|
+
## 0c. Session routing — launch vs become
|
|
52
|
+
|
|
53
|
+
Check the current tmux session name to decide if we're in an employee session or the exe session:
|
|
54
|
+
```bash
|
|
55
|
+
NAME="$ARGUMENTS"
|
|
56
|
+
CURRENT_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
|
|
57
|
+
echo "CURRENT_SESSION=${CURRENT_SESSION} TARGET=${NAME}"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Employee sessions are named `{name}-{exeSession}` (e.g., `yoshi-exe1`) or `{name}{N}-{exeSession}` for multi-instance (e.g., `tom2-exe1`).
|
|
61
|
+
|
|
62
|
+
**If CURRENT_SESSION matches `{NAME}-` or `{NAME}{digit}-`** (auto-launched into employee session, e.g. `yoshi-exe1` or `tom2-exe1`):
|
|
63
|
+
- Continue below to become the employee.
|
|
64
|
+
|
|
65
|
+
**Otherwise** (user is calling from their exe session):
|
|
66
|
+
- Ensure the employee is running (check → intercom or spawn) and STOP:
|
|
67
|
+
```bash
|
|
68
|
+
NAME="$ARGUMENTS"
|
|
69
|
+
EXE_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
|
|
70
|
+
RESULT=$(node -e "const {ensureEmployee}=require('$(npm root -g)/exe-os/dist/lib/tmux-routing.js'); const r=ensureEmployee('${NAME}','${EXE_SESSION}','$(pwd)'); console.log(JSON.stringify(r));")
|
|
71
|
+
echo "$RESULT"
|
|
72
|
+
```
|
|
73
|
+
Report the result and STOP HERE. Do not continue to step 1.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
If found, read their system prompt from the roster. Then:
|
|
78
|
+
|
|
79
|
+
1. Write an active-agent marker so all hooks tag memories correctly, then set environment:
|
|
80
|
+
```bash
|
|
81
|
+
NAME="$ARGUMENTS"
|
|
82
|
+
ROLE="$(NAME="$ARGUMENTS" node -e "const e = JSON.parse(require('fs').readFileSync(require('os').homedir()+'/.exe-os/exe-employees.json','utf8')); const emp = e.find(x=>x.name===process.env.NAME); console.log(emp?.role||'specialist')")"
|
|
83
|
+
# Walk process tree to find Claude Code PID (same logic as session-key.ts)
|
|
84
|
+
KEY=$$
|
|
85
|
+
while [ "$KEY" -gt 1 ] 2>/dev/null; do
|
|
86
|
+
CMD=$(ps -p $KEY -o comm= 2>/dev/null)
|
|
87
|
+
case "$CMD" in *claude*) break ;; esac
|
|
88
|
+
KEY=$(ps -p $KEY -o ppid= 2>/dev/null | tr -d ' ')
|
|
89
|
+
done
|
|
90
|
+
mkdir -p ~/.exe-os/session-cache
|
|
91
|
+
NAME="$NAME" ROLE="$ROLE" KEY="$KEY" node -e "require('fs').writeFileSync(
|
|
92
|
+
require('os').homedir()+'/.exe-os/session-cache/active-agent-'+process.env.KEY+'.json',
|
|
93
|
+
JSON.stringify({agentId:process.env.NAME, agentRole:process.env.ROLE, startedAt:new Date().toISOString()})
|
|
94
|
+
)" 2>/dev/null
|
|
95
|
+
echo "Agent marker written: ${NAME} (${ROLE}) [key=${KEY}]"
|
|
96
|
+
export AGENT_ID="${NAME}"
|
|
97
|
+
export AGENT_ROLE="${ROLE}"
|
|
98
|
+
# Register parent exe from session name (deterministic routing)
|
|
99
|
+
# Employee session is named {name}-{exeSession}, e.g. yoshi-exe1
|
|
100
|
+
# parseParentExe extracts the exe session name: yoshi-exe1 -> exe1
|
|
101
|
+
# dispatch-info tells us who actually dispatched us (may be yoshi, not exe)
|
|
102
|
+
MY_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
|
|
103
|
+
DISPATCH_INFO="$HOME/.exe-os/session-cache/dispatch-info-${MY_SESSION}.json"
|
|
104
|
+
DISPATCHED_BY=$(node -e "try{const d=JSON.parse(require('fs').readFileSync('$DISPATCH_INFO','utf8'));console.log(d.dispatchedBy||'')}catch{console.log('')}" 2>/dev/null)
|
|
105
|
+
node -e "const {parseParentExe,registerParentExe}=require('$(npm root -g)/exe-os/dist/lib/tmux-routing.js'); const p=parseParentExe('$MY_SESSION','$NAME'); if(p) registerParentExe('$KEY',p,'$DISPATCHED_BY'||undefined);"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
2. Ensure exe folder structure exists and scan for open tasks:
|
|
109
|
+
```bash
|
|
110
|
+
mkdir -p "exe/$ARGUMENTS" exe/output exe/research
|
|
111
|
+
# Ensure exe/ is gitignored (task files are private)
|
|
112
|
+
if [ -f .gitignore ]; then
|
|
113
|
+
grep -q '^/exe/$' .gitignore 2>/dev/null || echo -e '\n# Employee task assignments (private)\n/exe/' >> .gitignore
|
|
114
|
+
else
|
|
115
|
+
echo -e '# Employee task assignments (private)\n/exe/' > .gitignore
|
|
116
|
+
fi
|
|
117
|
+
node "$(npm root -g)/exe-os/dist/bin/scan-tasks.js" "exe/$ARGUMENTS" --format=mandatory 2>/dev/null
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
3. Check memories for context from exe:
|
|
121
|
+
Use `recall_my_memory` to check what you've done before in this project.
|
|
122
|
+
Use `ask_team_memory` for "exe" to see if exe left any context or instructions.
|
|
123
|
+
|
|
124
|
+
4. **Adopt the employee's identity.** Read and follow their system prompt from the roster. You ARE this employee for the rest of the conversation. Stay in character.
|
|
125
|
+
|
|
126
|
+
5. If the scan output contains "MANDATORY" or "Continue working on":
|
|
127
|
+
- Read the task file referenced in the output
|
|
128
|
+
- Begin working immediately — do NOT greet the user, do NOT ask what to do
|
|
129
|
+
- If "Continue working on": resume the in-progress task
|
|
130
|
+
- If "MANDATORY": start the highest-priority open task
|
|
131
|
+
|
|
132
|
+
6. If the scan output is empty (no open tasks):
|
|
133
|
+
> [name] online. No open tasks. What do you need?
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Set up cloud sync — link device encryption key and configure Exe Cloud
|
|
3
|
+
allowed-tools: Bash
|
|
4
|
+
argument-hint: [sync|status]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
One command to link a device and enable cloud sync.
|
|
8
|
+
|
|
9
|
+
- `/exe-cloud` — full interactive setup (import encryption key + configure cloud API)
|
|
10
|
+
- `/exe-cloud sync` — export mnemonic + cloud config for pasting on a new device
|
|
11
|
+
- `/exe-cloud status` — show current sync status and key fingerprint
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
node "$(npm root -g)/exe-os/dist/bin/exe-cloud.js" $ARGUMENTS
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Note:** `/exe-link` is deprecated. Use `/exe-cloud` instead.
|