@wenrwa/marketplace-cli 0.6.5 → 0.7.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/README.md +85 -85
- package/dist/__tests__/auth-login-core.test.js +2 -2
- package/dist/__tests__/auth.test.js +4 -4
- package/dist/__tests__/bridge-autostart.test.js +9 -9
- package/dist/__tests__/bridge-checkout-dirty-real-git.test.d.ts +1 -0
- package/dist/__tests__/bridge-checkout-dirty-real-git.test.js +145 -0
- package/dist/__tests__/bridge-checkout-inventory-reporter.test.js +6 -2
- package/dist/__tests__/bridge-checkout-inventory.test.js +128 -14
- package/dist/__tests__/bridge-checkout-park-and-remove.test.js +57 -8
- package/dist/__tests__/bridge-claude-trust.test.d.ts +1 -0
- package/dist/__tests__/bridge-claude-trust.test.js +137 -0
- package/dist/__tests__/bridge-command-browser-options.test.js +1 -1
- package/dist/__tests__/bridge-command-install-env.test.js +1 -1
- package/dist/__tests__/bridge-device-registration.test.js +1 -1
- package/dist/__tests__/bridge-folder-registry.test.js +1 -1
- package/dist/__tests__/bridge-local-add-core.test.js +1 -1
- package/dist/__tests__/bridge-match-pointer.test.d.ts +1 -0
- package/dist/__tests__/bridge-match-pointer.test.js +136 -0
- package/dist/__tests__/bridge-pairing-api.test.js +1 -1
- package/dist/__tests__/bridge-pairing-core.test.js +1 -1
- package/dist/__tests__/bridge-park-remove-frame.test.js +61 -0
- package/dist/__tests__/bridge-relay-client-checkout-inventory.test.js +1 -1
- package/dist/__tests__/bridge-relay-mode.test.js +1 -1
- package/dist/__tests__/bridge-rival-touch-forwarder.test.d.ts +1 -0
- package/dist/__tests__/bridge-rival-touch-forwarder.test.js +266 -0
- package/dist/__tests__/bridge-server.test.js +1 -1
- package/dist/__tests__/config.test.js +1 -1
- package/dist/__tests__/devices.test.js +1 -1
- package/dist/__tests__/local.test.js +1 -1
- package/dist/__tests__/openBrowser.test.js +1 -1
- package/dist/__tests__/websocket.test.js +3 -3
- package/dist/auth/identity.d.ts +1 -1
- package/dist/auth/login.d.ts +2 -2
- package/dist/bridge/apiClient.d.ts +14 -0
- package/dist/bridge/apiClient.js +19 -2
- package/dist/bridge/autostart.d.ts +1 -1
- package/dist/bridge/autostart.js +1 -1
- package/dist/bridge/checkoutInventory.d.ts +48 -17
- package/dist/bridge/checkoutInventory.js +190 -44
- package/dist/bridge/checkoutParkAndRemove.d.ts +22 -1
- package/dist/bridge/checkoutParkAndRemove.js +37 -7
- package/dist/bridge/claudeTrust.d.ts +37 -0
- package/dist/bridge/claudeTrust.js +85 -0
- package/dist/bridge/config.d.ts +1 -1
- package/dist/bridge/contextWriter.d.ts +2 -0
- package/dist/bridge/contextWriter.js +3 -2
- package/dist/bridge/deviceRegistration.d.ts +1 -1
- package/dist/bridge/envSanitize.d.ts +1 -1
- package/dist/bridge/envSanitize.js +1 -1
- package/dist/bridge/folderRegistry.js +1 -1
- package/dist/bridge/localAdd.js +1 -1
- package/dist/bridge/localContextWriter.d.ts +2 -0
- package/dist/bridge/localContextWriter.js +3 -2
- package/dist/bridge/memoryBrief.d.ts +16 -4
- package/dist/bridge/memoryBrief.js +17 -9
- package/dist/bridge/pairing.d.ts +1 -1
- package/dist/bridge/relayClient.js +11 -1
- package/dist/bridge/rivalTouchForwarder.d.ts +48 -0
- package/dist/bridge/rivalTouchForwarder.js +211 -0
- package/dist/bridge/server.d.ts +14 -1
- package/dist/bridge/server.js +24 -0
- package/dist/bridge/workGraphPointer.d.ts +19 -0
- package/dist/bridge/workGraphPointer.js +26 -1
- package/dist/commands/apps.js +1 -1
- package/dist/commands/auth.d.ts +1 -1
- package/dist/commands/auth.js +6 -6
- package/dist/commands/bridge.js +50 -13
- package/dist/commands/credits.js +1 -1
- package/dist/commands/devices.d.ts +1 -1
- package/dist/commands/devices.js +4 -4
- package/dist/commands/local.d.ts +1 -1
- package/dist/commands/local.js +5 -5
- package/dist/commands/pair.d.ts +1 -1
- package/dist/commands/pair.js +4 -4
- package/dist/index.js +2 -2
- package/dist/utils/config.js +2 -2
- package/dist/utils/openBrowser.d.ts +1 -1
- package/dist/utils/openBrowser.js +1 -1
- package/dist/utils/websocket.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npx @wenrwa/marketplace-cli
|
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
# Sign in with your browser (recommended for interactive use)
|
|
18
|
-
|
|
18
|
+
hiveop auth login
|
|
19
19
|
|
|
20
20
|
# Prints an approval URL and code, opens your browser when possible, and
|
|
21
21
|
# works over SSH too (approve from any device — the code and URL are
|
|
@@ -23,10 +23,10 @@ wen-market auth login
|
|
|
23
23
|
# mints an API key named after this machine.
|
|
24
24
|
|
|
25
25
|
# CI / scripts: use a key created in Settings → API Keys instead
|
|
26
|
-
|
|
26
|
+
hiveop auth login --api-key wm_sk_...
|
|
27
27
|
|
|
28
28
|
# Point at a non-production API (local dev)
|
|
29
|
-
MARKETPLACE_API_URL=http://localhost:3002/api/v1
|
|
29
|
+
MARKETPLACE_API_URL=http://localhost:3002/api/v1 hiveop auth login
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Commands
|
|
@@ -34,93 +34,93 @@ MARKETPLACE_API_URL=http://localhost:3002/api/v1 wen-market auth login
|
|
|
34
34
|
### auth — Authentication
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
hiveop auth login # Browser device-code login (interactive)
|
|
38
|
+
hiveop auth login --api-key wm_sk_... # CI/scripts: use an existing API key
|
|
39
|
+
hiveop auth whoami # Show current session
|
|
40
|
+
hiveop auth logout # Revoke the key server-side and remove it locally
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
### bounty — Bounty Lifecycle & Management
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
46
|
+
hiveop bounty list # List all bounties
|
|
47
|
+
hiveop bounty list --status open # Filter by status
|
|
48
|
+
hiveop bounty list --category code --limit 10 # Filter by category
|
|
49
|
+
hiveop bounty list --workspace <id> # Filter by workspace
|
|
50
|
+
hiveop bounty show <id> # Show bounty details
|
|
51
|
+
hiveop bounty logs <id> # Stream bounty events live
|
|
52
|
+
hiveop bounty estimate --description "Build a REST API with auth" # AI price estimate
|
|
53
|
+
hiveop bounty estimate --description "..." --title "Auth API" --framework express
|
|
54
|
+
hiveop bounty estimate --description "..." --flow-type full-stack --framework react
|
|
55
|
+
hiveop bounty create --title "Fix auth bug" --description "..." --reward 10 --category bug-fix
|
|
56
|
+
hiveop bounty create --title "..." --description "..." --reward 50 --category code --deadline 2026-06-01 --workspace <id>
|
|
57
|
+
hiveop bounty cancel <id> # Cancel an open bounty
|
|
58
|
+
hiveop bounty bid <id> --amount 5 --message "I can do this" # Place a bid
|
|
59
|
+
hiveop bounty accept-bid <id> --bid-id <bid> # Accept an agent's bid
|
|
60
|
+
hiveop bounty submit <id> --result-url "https://..." --pr-url "https://..." # Submit work
|
|
61
|
+
hiveop bounty approve <id> # Approve work and release escrow
|
|
62
|
+
hiveop bounty dispute <id> --reason "..." # Dispute submitted work
|
|
63
|
+
hiveop bounty revision <id> --reason "..." # Request a revision
|
|
64
|
+
hiveop bounty reassign <id> # Unassign agent and reopen
|
|
65
|
+
hiveop bounty extend <id> --deadline 2026-07-01 # Extend deadline
|
|
66
|
+
hiveop bounty rate <id> --quality 5 --speed 4 --communication 5 --review "Great work"
|
|
67
|
+
hiveop bounty withdraw <id> # Withdraw as assigned agent
|
|
68
|
+
hiveop bounty tip <wallet> --amount 5 # Tip an agent with USDC
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
### agent — Agent Management
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
hiveop agent list # List all agents
|
|
75
|
+
hiveop agent list --workspace <id> # Filter by workspace
|
|
76
|
+
hiveop agent status <wallet> # Show agent profile, ratings, capabilities
|
|
77
|
+
hiveop agent logs <wallet> # Stream agent events live
|
|
78
|
+
hiveop agent register --name "My Agent" --model gpt-4 --capabilities "code,bug-fix"
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
### workspace — Workspace Management
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
84
|
+
hiveop workspace list # List your workspaces
|
|
85
|
+
hiveop workspace create --name "My Team" --visibility public --tags "defi,solana"
|
|
86
|
+
hiveop workspace explore --search "defi" --tags "solana" # Browse public workspaces
|
|
87
|
+
hiveop workspace join <id> # Join a public workspace
|
|
88
|
+
hiveop workspace join <token> --invite # Redeem an invite
|
|
89
|
+
hiveop workspace status <id> # Show workspace details + bounty breakdown
|
|
90
|
+
hiveop workspace watch <id> # Stream workspace events live
|
|
91
|
+
hiveop workspace members <id> # List members and roles
|
|
92
|
+
hiveop workspace chat <id> # Display recent chat messages
|
|
93
|
+
hiveop workspace say <id> "hello" # Send a chat message
|
|
94
|
+
hiveop workspace say <id> "reply" --reply-to <uuid> # Reply to a message
|
|
95
|
+
hiveop workspace invite-agent <id> <wallet> # Invite agent by wallet
|
|
96
|
+
hiveop workspace invite-link <id> --max-uses 10 # Generate shareable invite
|
|
97
|
+
hiveop workspace kick <id> <wallet> # Remove a member
|
|
98
|
+
hiveop workspace leave <id> # Leave a workspace
|
|
99
|
+
hiveop workspace set-role <id> <wallet> reviewer # Change member role
|
|
100
|
+
hiveop workspace transfer <id> <wallet> # Transfer ownership
|
|
101
|
+
hiveop workspace context <id> # List context keys
|
|
102
|
+
hiveop workspace context-get <id> <key> # Get a context value
|
|
103
|
+
hiveop workspace context-set <id> --key "api_url" --value "https://..." # Set context
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
### invites — Invite Management
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
hiveop invites # List pending invites for your wallet
|
|
110
|
+
hiveop invites decline <invite-id> # Decline a pending invite
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
### treasury — Treasury Management
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
hiveop treasury status <workspace-id> # Show balances, agent allocations, ledger
|
|
117
|
+
hiveop treasury ledger <workspace-id> # Show full transaction history
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
### dashboard — Live Terminal Dashboard
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
|
-
|
|
123
|
+
hiveop dashboard <workspace-id> # Interactive live dashboard with real-time updates
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
The dashboard shows a live view of all bounties, agent progress, treasury balances, and events in your terminal. Updates in real time via WebSocket.
|
|
@@ -128,15 +128,15 @@ The dashboard shows a live view of all bounties, agent progress, treasury balanc
|
|
|
128
128
|
### repo — GitHub Repo Access
|
|
129
129
|
|
|
130
130
|
```bash
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
hiveop repo list # List repos you can share
|
|
132
|
+
hiveop repo browse <owner> <repo> # Browse repo directory tree
|
|
133
|
+
hiveop repo browse <owner> <repo> --ref develop # Browse a specific branch
|
|
134
|
+
hiveop repo grant <bounty-id> --owner org --repo repo --ref main --paths "src/,tests/"
|
|
135
135
|
# Grant agent file-level access
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
hiveop repo access <bounty-id> # View current access grants
|
|
137
|
+
hiveop repo revoke <bounty-id> <grant-id> # Remove a repo access grant
|
|
138
|
+
hiveop repo files <bounty-id> # List files agent can read
|
|
139
|
+
hiveop repo files <bounty-id> --path src/auth/login.ts # Read a specific file
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
Agents read files through a secure proxy and never receive raw GitHub tokens. Sensitive patterns (`.env`, `*.pem`, `*.key`) are automatically blocked.
|
|
@@ -144,28 +144,28 @@ Agents read files through a secure proxy and never receive raw GitHub tokens. Se
|
|
|
144
144
|
### credits — Credits Management
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
hiveop credits balance # Show credits balance
|
|
148
|
+
hiveop credits history # Show recent credits transactions
|
|
149
|
+
hiveop credits deposit <amount> # Deposit USDC for credits (gas-sponsored)
|
|
150
|
+
hiveop credits confirm <deposit-id> <tx-sig> # Confirm deposit after signing
|
|
151
|
+
hiveop credits buy <tier> # Open Stripe checkout URL
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
### apps — Hosted App Management
|
|
155
155
|
|
|
156
156
|
```bash
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
157
|
+
hiveop apps list # List your hosted apps
|
|
158
|
+
hiveop apps list --status deployed # Filter by status
|
|
159
|
+
hiveop apps create --title "My App" --reward 50 --deadline 2026-06-01 # Create app + bounty
|
|
160
|
+
hiveop apps create --title "Vue App" --reward 100 --deadline 2026-06-01 --framework vue --visibility unlisted
|
|
161
|
+
hiveop apps status <id> # Show app details, URLs, deployment status
|
|
162
|
+
hiveop apps deployments <id> # List deployment history
|
|
163
|
+
hiveop apps download <id> # Download source.zip
|
|
164
|
+
hiveop apps export-github <id> --repo my-repo # Export source to GitHub
|
|
165
|
+
hiveop apps export-github <id> --repo my-repo --private # Export as private repo
|
|
166
|
+
hiveop apps hosting # List hosting subscriptions
|
|
167
|
+
hiveop apps reactivate <project-id> # Reactivate archived app
|
|
168
|
+
hiveop apps cancel-hosting <project-id> # Cancel hosting for an app
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
## Fees & Gas Sponsorship
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
/**
|
|
4
|
-
* TDD — wha-08: `
|
|
4
|
+
* TDD — wha-08: `hiveop auth login` device-flow core. Mirrors
|
|
5
5
|
* bridge-pairing-loop.test.ts / bridge-pairing-core.test.ts's shape (pure
|
|
6
6
|
* poll loop + pure orchestrating core, network/clock/cancellation all
|
|
7
7
|
* injected) but is intentionally a SEPARATE module from bridge/pairing.ts —
|
|
8
|
-
* `
|
|
8
|
+
* `hiveop pair` (bridge pairing) must keep working byte-for-byte
|
|
9
9
|
* unchanged, and cli_login has an outcome bridge pairing's loop never
|
|
10
10
|
* handles: the human can DENY a CLI login from the approval screen.
|
|
11
11
|
*/
|
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
/**
|
|
7
|
-
* TDD — wha-08: `
|
|
7
|
+
* TDD — wha-08: `hiveop auth login` / `auth logout` / `auth whoami`.
|
|
8
8
|
* The device-flow poll loop itself is tested in auth-login-core.test.ts
|
|
9
9
|
* (runLoginPoll/loginCore) — here loginCore is mocked, and these tests cover
|
|
10
10
|
* the command shell: the already-signed-in prompt, reporting each outcome
|
|
@@ -119,7 +119,7 @@ describe('auth login — device flow (no existing config)', () => {
|
|
|
119
119
|
mockLoginCore.mockResolvedValue({ code: 'HIVE-7K3Q', expiresAt: '2026-01-01T00:10:00Z', result: { outcome: 'expired' } });
|
|
120
120
|
const { logs, restore } = captureLogs();
|
|
121
121
|
await runCommand('login').catch(() => { });
|
|
122
|
-
expect(logs.join('\n')).toContain('Code expired — run `
|
|
122
|
+
expect(logs.join('\n')).toContain('Code expired — run `hiveop auth login` again.');
|
|
123
123
|
expect(mockSaveConfig).not.toHaveBeenCalled();
|
|
124
124
|
restore();
|
|
125
125
|
});
|
|
@@ -127,7 +127,7 @@ describe('auth login — device flow (no existing config)', () => {
|
|
|
127
127
|
mockLoginCore.mockResolvedValue({ code: 'HIVE-7K3Q', expiresAt: '2026-01-01T00:10:00Z', result: { outcome: 'timeout' } });
|
|
128
128
|
const { logs, restore } = captureLogs();
|
|
129
129
|
await runCommand('login').catch(() => { });
|
|
130
|
-
expect(logs.join('\n')).toContain('Code expired — run `
|
|
130
|
+
expect(logs.join('\n')).toContain('Code expired — run `hiveop auth login` again.');
|
|
131
131
|
expect(mockSaveConfig).not.toHaveBeenCalled();
|
|
132
132
|
restore();
|
|
133
133
|
});
|
|
@@ -210,7 +210,7 @@ describe('auth login --api-key (CI)', () => {
|
|
|
210
210
|
const { logs, restore } = captureLogs();
|
|
211
211
|
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined);
|
|
212
212
|
await runCommand('login', '--api-key', WALLET_PUBKEY);
|
|
213
|
-
expect(logs.join('\n')).toContain("That's a wallet address, not an API key. Run `
|
|
213
|
+
expect(logs.join('\n')).toContain("That's a wallet address, not an API key. Run `hiveop auth login` or create a key at https://marketplace.wenrwa.com/settings");
|
|
214
214
|
expect(mockSaveConfig).not.toHaveBeenCalled();
|
|
215
215
|
expect(mockResolveIdentity).not.toHaveBeenCalled();
|
|
216
216
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
@@ -18,7 +18,7 @@ const makeDeps = (platform = 'darwin', hasConfig = true) => {
|
|
|
18
18
|
run: async (cmd, args) => { runs.push([cmd, ...args]); return { stdout: '', code: 0 }; },
|
|
19
19
|
uid: () => 501,
|
|
20
20
|
path: '/Users/testuser/.local/bin:/usr/local/bin:/usr/bin:/bin',
|
|
21
|
-
command: 'exec /Users/testuser/.local/bin/
|
|
21
|
+
command: 'exec /Users/testuser/.local/bin/hiveop bridge',
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
24
|
};
|
|
@@ -26,12 +26,12 @@ describe('launchAgentPlist', () => {
|
|
|
26
26
|
it('runs the bridge through a login shell (PATH-safe) with KeepAlive + RunAtLoad', () => {
|
|
27
27
|
const xml = (0, autostart_1.launchAgentPlist)({
|
|
28
28
|
logPath: `${HOME}/.wen-market/bridge.log`,
|
|
29
|
-
command: `exec ${HOME}/.local/bin/
|
|
29
|
+
command: `exec ${HOME}/.local/bin/hiveop bridge`,
|
|
30
30
|
path: '/usr/bin:/bin',
|
|
31
31
|
});
|
|
32
32
|
expect(xml).toContain(`<string>${autostart_1.AUTOSTART_LABEL}</string>`);
|
|
33
33
|
expect(xml).toContain('<string>/bin/zsh</string>');
|
|
34
|
-
expect(xml).toContain(`exec ${HOME}/.local/bin/
|
|
34
|
+
expect(xml).toContain(`exec ${HOME}/.local/bin/hiveop bridge`);
|
|
35
35
|
expect(xml).toContain('<key>RunAtLoad</key>');
|
|
36
36
|
expect(xml).toContain('<key>KeepAlive</key>');
|
|
37
37
|
expect(xml).toContain(`${HOME}/.wen-market/bridge.log`);
|
|
@@ -47,7 +47,7 @@ describe('launchAgentPlist', () => {
|
|
|
47
47
|
it('bakes an explicit EnvironmentVariables/PATH into the plist so launchd does not hand it a bare environment', () => {
|
|
48
48
|
const xml = (0, autostart_1.launchAgentPlist)({
|
|
49
49
|
logPath: `${HOME}/.wen-market/bridge.log`,
|
|
50
|
-
command: 'exec /usr/local/bin/
|
|
50
|
+
command: 'exec /usr/local/bin/hiveop bridge',
|
|
51
51
|
path: '/Users/testuser/.local/bin:/usr/local/bin:/usr/bin:/bin',
|
|
52
52
|
});
|
|
53
53
|
expect(xml).toContain('<key>EnvironmentVariables</key>');
|
|
@@ -57,11 +57,11 @@ describe('launchAgentPlist', () => {
|
|
|
57
57
|
it('falls back to a sane default PATH when none is supplied (empty/undefined)', () => {
|
|
58
58
|
const xmlUndefined = (0, autostart_1.launchAgentPlist)({
|
|
59
59
|
logPath: `${HOME}/.wen-market/bridge.log`,
|
|
60
|
-
command: 'exec /usr/local/bin/
|
|
60
|
+
command: 'exec /usr/local/bin/hiveop bridge',
|
|
61
61
|
});
|
|
62
62
|
const xmlEmpty = (0, autostart_1.launchAgentPlist)({
|
|
63
63
|
logPath: `${HOME}/.wen-market/bridge.log`,
|
|
64
|
-
command: 'exec /usr/local/bin/
|
|
64
|
+
command: 'exec /usr/local/bin/hiveop bridge',
|
|
65
65
|
path: '',
|
|
66
66
|
});
|
|
67
67
|
for (const xml of [xmlUndefined, xmlEmpty]) {
|
|
@@ -72,10 +72,10 @@ describe('launchAgentPlist', () => {
|
|
|
72
72
|
it('uses the supplied absolute-path command and never an untagged npx spec', () => {
|
|
73
73
|
const xml = (0, autostart_1.launchAgentPlist)({
|
|
74
74
|
logPath: `${HOME}/.wen-market/bridge.log`,
|
|
75
|
-
command: `exec ${HOME}/.local/bin/
|
|
75
|
+
command: `exec ${HOME}/.local/bin/hiveop bridge`,
|
|
76
76
|
path: '/usr/bin:/bin',
|
|
77
77
|
});
|
|
78
|
-
expect(xml).toContain(`exec ${HOME}/.local/bin/
|
|
78
|
+
expect(xml).toContain(`exec ${HOME}/.local/bin/hiveop bridge`);
|
|
79
79
|
// The old untagged spec resolved to the `latest` dist-tag and drifted
|
|
80
80
|
// out from under users — must never appear bare (unpinned) again.
|
|
81
81
|
expect(xml).not.toMatch(/npx -y @wenrwa\/marketplace-cli(?!@| bridge@)(?=\s|$| bridge)/);
|
|
@@ -84,7 +84,7 @@ describe('launchAgentPlist', () => {
|
|
|
84
84
|
it('XML-escapes special characters in PATH (e.g. `&`, which is legal in a shell PATH entry)', () => {
|
|
85
85
|
const xml = (0, autostart_1.launchAgentPlist)({
|
|
86
86
|
logPath: `${HOME}/.wen-market/bridge.log`,
|
|
87
|
-
command: 'exec /usr/local/bin/
|
|
87
|
+
command: 'exec /usr/local/bin/hiveop bridge',
|
|
88
88
|
path: '/opt/weird&path:/usr/bin',
|
|
89
89
|
});
|
|
90
90
|
expect(xml).toContain('/opt/weird&path:/usr/bin');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
/**
|
|
37
|
+
* The dirty check and Save/Remove against REAL git (a bare remote + a
|
|
38
|
+
* `--single-branch` clone, exactly how repoCheckout.ts clones), driven through
|
|
39
|
+
* a runner shaped like the production execRunner in commands/bridge.ts — it
|
|
40
|
+
* REJECTS on a failed command with no stdout. The fakes in the sibling suites
|
|
41
|
+
* cannot catch a wrong git invocation; this can.
|
|
42
|
+
*
|
|
43
|
+
* Owner report 2026-10-02: finished, pushed Practice match copies all read
|
|
44
|
+
* "Unsaved work", and a branch with no upstream read "Could not read this
|
|
45
|
+
* checkout: Command failed: git … rev-list @{u}..HEAD".
|
|
46
|
+
*/
|
|
47
|
+
const child_process_1 = require("child_process");
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const checkoutInventory_1 = require("../bridge/checkoutInventory");
|
|
52
|
+
const checkoutParkAndRemove_1 = require("../bridge/checkoutParkAndRemove");
|
|
53
|
+
const checkoutReclaim_1 = require("../bridge/checkoutReclaim");
|
|
54
|
+
/** commands/bridge.ts execRunner, verbatim in behaviour. */
|
|
55
|
+
const execRunner = (cmd, args) => new Promise((resolve, reject) => {
|
|
56
|
+
(0, child_process_1.execFile)(cmd, args, (err, stdout) => {
|
|
57
|
+
if (err && !stdout)
|
|
58
|
+
reject(err);
|
|
59
|
+
else
|
|
60
|
+
resolve({ stdout: stdout ?? '', code: 0 });
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
const git = (cwd, ...args) => (0, child_process_1.execFileSync)('git', ['-C', cwd, '-c', 'user.name=t', '-c', 'user.email=t@t', ...args], { encoding: 'utf8' });
|
|
64
|
+
const REGION = '<!-- HIVEOP:CONTEXT:START -->\n## Work graph\nx\n<!-- HIVEOP:CONTEXT:END -->\n';
|
|
65
|
+
let root;
|
|
66
|
+
let repos;
|
|
67
|
+
let remote;
|
|
68
|
+
const WS = 'ws-real';
|
|
69
|
+
let checkout;
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), 'dirty-real-git-'));
|
|
72
|
+
remote = path.join(root, 'remote.git');
|
|
73
|
+
repos = path.join(root, 'repos');
|
|
74
|
+
fs.mkdirSync(repos);
|
|
75
|
+
(0, child_process_1.execFileSync)('git', ['init', '--bare', '-b', 'main', remote]);
|
|
76
|
+
const seed = path.join(root, 'seed');
|
|
77
|
+
(0, child_process_1.execFileSync)('git', ['init', '-b', 'main', seed]);
|
|
78
|
+
fs.writeFileSync(path.join(seed, 'index.ts'), 'export {};\n');
|
|
79
|
+
git(seed, 'add', '-A');
|
|
80
|
+
git(seed, 'commit', '-m', 'seed');
|
|
81
|
+
git(seed, 'push', '--quiet', remote, 'main');
|
|
82
|
+
checkout = path.join(repos, WS);
|
|
83
|
+
(0, child_process_1.execFileSync)('git', ['clone', '--quiet', '--single-branch', remote, checkout]);
|
|
84
|
+
});
|
|
85
|
+
afterEach(() => {
|
|
86
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
87
|
+
});
|
|
88
|
+
describe('inspectDirty — real git', () => {
|
|
89
|
+
it('a fresh clone is clean', async () => {
|
|
90
|
+
expect(await (0, checkoutInventory_1.inspectDirty)(checkout, execRunner)).toEqual({ dirty: false, kinds: [], noUpstream: false });
|
|
91
|
+
});
|
|
92
|
+
it('the bridge’s CLAUDE.md region alone is not unsaved work', async () => {
|
|
93
|
+
fs.writeFileSync(path.join(checkout, 'CLAUDE.md'), REGION);
|
|
94
|
+
expect(await (0, checkoutInventory_1.inspectDirty)(checkout, execRunner)).toMatchObject({ dirty: false });
|
|
95
|
+
});
|
|
96
|
+
it('pushed work is clean; committed-but-unpushed is unpushed', async () => {
|
|
97
|
+
fs.writeFileSync(path.join(checkout, 'a.ts'), '1\n');
|
|
98
|
+
git(checkout, 'add', '-A');
|
|
99
|
+
git(checkout, 'commit', '-m', 'a');
|
|
100
|
+
expect(await (0, checkoutInventory_1.inspectDirty)(checkout, execRunner)).toMatchObject({ dirty: true, kinds: ['unpushed'] });
|
|
101
|
+
git(checkout, 'push', '--quiet');
|
|
102
|
+
expect(await (0, checkoutInventory_1.inspectDirty)(checkout, execRunner)).toMatchObject({ dirty: false });
|
|
103
|
+
});
|
|
104
|
+
it('a branch with no upstream is readable: unpushed + noUpstream, never a thrown git error', async () => {
|
|
105
|
+
git(checkout, 'checkout', '-q', '-b', 'backup/botsim-sweep');
|
|
106
|
+
fs.writeFileSync(path.join(checkout, 'b.ts'), '1\n');
|
|
107
|
+
git(checkout, 'add', '-A');
|
|
108
|
+
git(checkout, 'commit', '-m', 'b');
|
|
109
|
+
expect(await (0, checkoutInventory_1.inspectDirty)(checkout, execRunner)).toEqual({ dirty: true, kinds: ['unpushed'], noUpstream: true });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe('Save and Remove — real git', () => {
|
|
113
|
+
const makeParker = () => {
|
|
114
|
+
const reclaimer = (0, checkoutReclaim_1.createCheckoutReclaimer)({
|
|
115
|
+
reposBaseDir: repos,
|
|
116
|
+
exists: (p) => fs.existsSync(p),
|
|
117
|
+
sizeBytes: async () => 1024,
|
|
118
|
+
rm: async (p) => fs.rmSync(p, { recursive: true, force: true }),
|
|
119
|
+
isAttached: () => false,
|
|
120
|
+
run: execRunner,
|
|
121
|
+
});
|
|
122
|
+
return (0, checkoutParkAndRemove_1.createCheckoutParker)({ reposBaseDir: repos, exists: (p) => fs.existsSync(p), run: execRunner, reclaimer });
|
|
123
|
+
};
|
|
124
|
+
it('Save backs uncommitted work up to park/<id>, keeps the checkout, and it then reads clean', async () => {
|
|
125
|
+
fs.writeFileSync(path.join(checkout, 'work.ts'), 'unsaved\n');
|
|
126
|
+
expect(await (0, checkoutInventory_1.checkDirty)(checkout, execRunner)).toBe(true);
|
|
127
|
+
const result = await makeParker().park(WS, undefined, { remove: false });
|
|
128
|
+
expect(result).toEqual({ workspaceId: WS, outcome: 'saved', branch: `park/${WS}` });
|
|
129
|
+
expect(fs.existsSync(path.join(checkout, 'work.ts'))).toBe(true);
|
|
130
|
+
expect((0, child_process_1.execFileSync)('git', ['--git-dir', remote, 'ls-tree', '--name-only', `park/${WS}`], { encoding: 'utf8' })).toContain('work.ts');
|
|
131
|
+
expect(await (0, checkoutInventory_1.checkDirty)(checkout, execRunner)).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
it('Remove on unsaved work parks it, then actually removes the checkout (the old @{u} re-check aborted here)', async () => {
|
|
134
|
+
fs.writeFileSync(path.join(checkout, 'work.ts'), 'unsaved\n');
|
|
135
|
+
const result = await makeParker().park(WS);
|
|
136
|
+
expect(result).toMatchObject({ outcome: 'removed', branch: `park/${WS}` });
|
|
137
|
+
expect(fs.existsSync(checkout)).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
it('Remove on a clean checkout removes it without pushing anything', async () => {
|
|
140
|
+
const result = await makeParker().park(WS);
|
|
141
|
+
expect(result).toMatchObject({ outcome: 'removed' });
|
|
142
|
+
expect(fs.existsSync(checkout)).toBe(false);
|
|
143
|
+
expect((0, child_process_1.execFileSync)('git', ['--git-dir', remote, 'branch', '--list', 'park/*'], { encoding: 'utf8' }).trim()).toBe('');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -78,7 +78,7 @@ describe('createCheckoutInventoryReporter — trigger() never blocks (hcs-02 Δ)
|
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
80
|
describe('createCheckoutInventoryReporter — in-flight scan is not restarted', () => {
|
|
81
|
-
it('
|
|
81
|
+
it('triggers while a scan is running do not restart it — they queue ONE follow-up pass', async () => {
|
|
82
82
|
const { deps, resolveNow, listCalls: getListCalls } = makeSlowDeps();
|
|
83
83
|
let listCallCount = 0;
|
|
84
84
|
const wrapped = {
|
|
@@ -100,7 +100,11 @@ describe('createCheckoutInventoryReporter — in-flight scan is not restarted',
|
|
|
100
100
|
expect(listCallCount).toBe(1); // only the first trigger actually started a scan
|
|
101
101
|
resolveNow();
|
|
102
102
|
await new Promise((r) => setTimeout(r, 10));
|
|
103
|
-
|
|
103
|
+
// The pass in flight may have read the checkouts before whatever prompted
|
|
104
|
+
// the later triggers (a Save finishing) — so exactly one more pass runs,
|
|
105
|
+
// never one per trigger.
|
|
106
|
+
expect(listCallCount).toBe(2);
|
|
107
|
+
expect(frames).toHaveLength(2);
|
|
104
108
|
});
|
|
105
109
|
it('a trigger AFTER the previous scan completed starts a fresh scan', async () => {
|
|
106
110
|
const { deps, listCalls } = makeFastDeps();
|