workbite 0.1.19 → 0.1.20
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 +183 -164
- package/bin/workbite.js +13 -13
- package/lib/agent.js +240 -240
- package/lib/archive.js +334 -334
- package/lib/boot-provenance.js +54 -54
- package/lib/cli.js +334 -324
- package/lib/common.js +68 -68
- package/lib/diagnostics.js +238 -238
- package/lib/download.js +53 -53
- package/lib/install.js +274 -274
- package/lib/mcp-task-context.js +188 -0
- package/lib/mcp.js +671 -612
- package/lib/private-state.js +116 -116
- package/lib/runtime.js +775 -775
- package/lib/startup-lock.js +71 -71
- package/lib/transfer.js +113 -113
- package/lib/update.js +86 -0
- package/package.json +35 -30
- package/release.json +24 -24
- package/skills/workbite-native/SKILL.md +126 -98
- package/skills/workbite-native/agents/openai.yaml +4 -4
- package/skills/workbite-native/references/commands.md +213 -213
- package/skills/workbite-native/references/task-fields.md +208 -208
- package/skills/workbite-native/references/transfer.md +204 -204
package/lib/cli.js
CHANGED
|
@@ -1,324 +1,334 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const path = require('node:path');
|
|
3
|
-
const { fail, readSmall } = require('./common');
|
|
4
|
-
const { locations, releaseSpec, ensureInstalled, stageReporter, packageVersion } = require('./install');
|
|
5
|
-
const { openSuite, FRONTENDS } = require('./runtime');
|
|
6
|
-
const { bindAgent, executeAgent, agentCapabilities, WRITE_COMMANDS, MAX_PACKET_BYTES } = require('./agent');
|
|
7
|
-
const transfer = require('./transfer');
|
|
8
|
-
|
|
9
|
-
const HELP = `WorkBite ${packageVersion} - Work Stack organizes the work. StackBite gets it moving.
|
|
10
|
-
|
|
11
|
-
Usage:
|
|
12
|
-
workbite install download, verify and install the pinned native suite
|
|
13
|
-
workbite
|
|
14
|
-
workbite
|
|
15
|
-
workbite
|
|
16
|
-
workbite
|
|
17
|
-
workbite
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
workbite agent
|
|
40
|
-
workbite agent
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
workbite agent
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
--
|
|
52
|
-
--
|
|
53
|
-
--
|
|
54
|
-
|
|
55
|
-
bundles
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
workbite
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (argument === '--
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
options.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
if (name
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
else
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
result = await
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (
|
|
310
|
-
return require('./
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const { fail, readSmall } = require('./common');
|
|
4
|
+
const { locations, releaseSpec, ensureInstalled, stageReporter, packageVersion } = require('./install');
|
|
5
|
+
const { openSuite, FRONTENDS } = require('./runtime');
|
|
6
|
+
const { bindAgent, executeAgent, agentCapabilities, WRITE_COMMANDS, MAX_PACKET_BYTES } = require('./agent');
|
|
7
|
+
const transfer = require('./transfer');
|
|
8
|
+
|
|
9
|
+
const HELP = `WorkBite ${packageVersion} - Work Stack organizes the work. StackBite gets it moving.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
workbite install download, verify and install the pinned native suite
|
|
13
|
+
workbite update update the global npm launcher and stage its verified suite
|
|
14
|
+
workbite check verify the installed suite (no download, no processes)
|
|
15
|
+
workbite status [--json] inspect startup records without starting anything
|
|
16
|
+
workbite doctor [--json] explain startup evidence and its limits (read-only)
|
|
17
|
+
workbite recover --dry-run [--json] print a recovery plan only; never changes state
|
|
18
|
+
workbite open --workspace ABS [--init] [--no-ui] [--frontend typescript|rust]
|
|
19
|
+
start or reuse this workspace's Work Stack owner and the
|
|
20
|
+
StackBite backend, then run the Work Stack terminal UI
|
|
21
|
+
workbite [open] [--no-ui] [--frontend typescript|rust]
|
|
22
|
+
reopen the last workspace WorkBite opened successfully
|
|
23
|
+
workbite agent [--workspace ABS --workspace-uid UUID] COMMAND [--input-file FILE | --stdin] [flags]
|
|
24
|
+
curated Work Stack agent commands against the running,
|
|
25
|
+
WorkBite-owned owner (never starts one): capabilities, status,
|
|
26
|
+
tasks list|create|update, context, checkpoint, operation-status,
|
|
27
|
+
export, import, import-status, import-rollback
|
|
28
|
+
workbite mcp [--workspace ABS --workspace-uid UUID]
|
|
29
|
+
WorkBite MCP server on stdio for the same agent commands
|
|
30
|
+
workbite --version | --help
|
|
31
|
+
|
|
32
|
+
agent:
|
|
33
|
+
Reads use the last workspace WorkBite opened unless --workspace and --workspace-uid name one.
|
|
34
|
+
Writes (tasks create, tasks update, checkpoint) always need --workspace ABS --workspace-uid UUID
|
|
35
|
+
and a packet from --input-file FILE or --stdin (UTF-8, at most 32768 bytes). An uncertain write
|
|
36
|
+
is never replayed; check it with agent operation-status --intent-id ID.
|
|
37
|
+
|
|
38
|
+
Task transfer (archive at most 1 MiB; uses the running owner, never starts one):
|
|
39
|
+
workbite agent export --output tasks.json [--include-history] [--redact]
|
|
40
|
+
workbite agent import --input-file tasks.json --mode new --dry-run
|
|
41
|
+
workbite agent --workspace ABS --workspace-uid UUID import --input-file tasks.json --mode new
|
|
42
|
+
--intent-id ID --plan-digest sha256:HEX --expected-snapshot-digest sha256:HEX [--id-map map.json]
|
|
43
|
+
workbite agent import-status --intent-id ID
|
|
44
|
+
workbite agent --workspace ABS --workspace-uid UUID import-rollback --intent-id ID --import-id ID
|
|
45
|
+
--expected-snapshot-digest sha256:HEX
|
|
46
|
+
merge is also supported. --preserve-ids is explicit. Output files must be new local files.
|
|
47
|
+
Import unknowns require import-status with the SAME intent; no automatic replay or Task status changes.
|
|
48
|
+
Full workspace relocation uses official backup/restore.
|
|
49
|
+
|
|
50
|
+
open:
|
|
51
|
+
--workspace ABS absolute workspace folder; always wins over the remembered one
|
|
52
|
+
--init create a new Work Stack workspace in a new or empty folder (needs --workspace)
|
|
53
|
+
--no-ui start or reuse the backends, print one JSON summary and exit; does not prove UI works
|
|
54
|
+
--frontend NAME typescript (default): the familiar Work Stack UI, with same-window 9 Execution
|
|
55
|
+
on execution bundles; rust: the explicit alternative unified UI (execution
|
|
56
|
+
bundles only). Chosen per open, never saved.
|
|
57
|
+
|
|
58
|
+
update:
|
|
59
|
+
Uses official npm latest; global installs only (local/npx copies are never replaced).
|
|
60
|
+
Does not switch or stop running owners, Seats or Phone. Inspect workbite status
|
|
61
|
+
--json and workbite doctor --json; ask your operator for handover before opening
|
|
62
|
+
the new runtime. Closing the UI leaves backends and agents running.
|
|
63
|
+
|
|
64
|
+
First time, name the workspace once:
|
|
65
|
+
workbite open --workspace /abs/path/new-folder --init
|
|
66
|
+
workbite open --workspace /abs/path/existing-workspace
|
|
67
|
+
Afterwards just run:
|
|
68
|
+
workbite
|
|
69
|
+
|
|
70
|
+
The Work Stack owner and StackBite backend keep running after the UI exits, so
|
|
71
|
+
agents continue. WorkBite never auto-updates, installs dependencies, approves
|
|
72
|
+
provider trust or stops processes it cannot prove are its own.
|
|
73
|
+
|
|
74
|
+
Locations (override with absolute WORKBITE_HOME / WORKBITE_STATE_DIR):
|
|
75
|
+
Windows %LOCALAPPDATA%\\WorkBite\\app\\<version> %LOCALAPPDATA%\\WorkBite\\state
|
|
76
|
+
Linux $XDG_DATA_HOME/workbite/app/<version> $XDG_STATE_HOME/workbite
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
// Native flags each curated agent command accepts (value-taking unless listed in FLAGS_WITHOUT_VALUE).
|
|
80
|
+
const AGENT_FLAGS = Object.freeze({
|
|
81
|
+
capabilities: ['--command', '--section'],
|
|
82
|
+
status: ['--workspace-revision'],
|
|
83
|
+
'tasks list': ['--status', '--limit', '--offset'],
|
|
84
|
+
'tasks create': ['--intent-id'],
|
|
85
|
+
'tasks update': ['--intent-id', '--task', '--expected-revision'],
|
|
86
|
+
context: ['--task', '--view', '--format', '--source', '--fingerprint', '--if-fingerprint'],
|
|
87
|
+
checkpoint: ['--intent-id'],
|
|
88
|
+
'operation-status': ['--intent-id'],
|
|
89
|
+
export: ['--include-history', '--redact'],
|
|
90
|
+
import: ['--mode', '--dry-run', '--preserve-ids', '--intent-id', '--plan-digest', '--expected-snapshot-digest'],
|
|
91
|
+
'import-status': ['--intent-id'],
|
|
92
|
+
'import-rollback': ['--intent-id', '--import-id', '--expected-snapshot-digest'],
|
|
93
|
+
});
|
|
94
|
+
const FLAGS_WITHOUT_VALUE = new Set(['--workspace-revision', '--fingerprint', '--include-history', '--redact', '--dry-run', '--preserve-ids']);
|
|
95
|
+
const REPEATABLE = new Set(['--source']);
|
|
96
|
+
const PACKET_COMMANDS = new Set(['tasks create', 'tasks update', 'checkpoint', 'import']);
|
|
97
|
+
|
|
98
|
+
function parseBinding(options, argument, next) {
|
|
99
|
+
if (argument === '--workspace' && options.workspace === undefined && next !== undefined && !next.startsWith('--')) {
|
|
100
|
+
if (!path.isAbsolute(next)) fail('--workspace must be an absolute path');
|
|
101
|
+
options.workspace = next; return true;
|
|
102
|
+
}
|
|
103
|
+
if (argument === '--workspace-uid' && options.workspaceUid === undefined && next !== undefined && !next.startsWith('--')) {
|
|
104
|
+
options.workspaceUid = next; return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
function pairedBinding(options) {
|
|
109
|
+
if ((options.workspace === undefined) !== (options.workspaceUid === undefined)) {
|
|
110
|
+
fail('--workspace and --workspace-uid must be given together');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseAgent(rest) {
|
|
115
|
+
// The binding pair may come before the command words, after them, or split around them;
|
|
116
|
+
// each flag is still accepted once, with its value, and nothing else may precede the words.
|
|
117
|
+
const options = { command: 'agent' };
|
|
118
|
+
while (rest.length && parseBinding(options, rest[0], rest[1])) rest.splice(0, 2);
|
|
119
|
+
if (rest.length && rest[0].startsWith('-')) {
|
|
120
|
+
fail(`Unknown or repeated agent option before the command: ${String(rest[0]).slice(0, 64)}. See workbite --help.`);
|
|
121
|
+
}
|
|
122
|
+
const words = [];
|
|
123
|
+
while (rest.length && !rest[0].startsWith('-')) words.push(rest.shift());
|
|
124
|
+
const name = words.join(' ');
|
|
125
|
+
if (!Object.hasOwn(AGENT_FLAGS, name)) {
|
|
126
|
+
fail(`Unknown agent command: ${name.slice(0, 64) || '(none)'}. Supported: ${Object.keys(AGENT_FLAGS).join(', ')}.`);
|
|
127
|
+
}
|
|
128
|
+
Object.assign(options, { name, native: ['agent', ...words] });
|
|
129
|
+
for (let index = 0; index < rest.length; index++) {
|
|
130
|
+
const argument = rest[index], next = rest[index + 1];
|
|
131
|
+
if (parseBinding(options, argument, next)) { index++; continue; }
|
|
132
|
+
if (((argument === '--output' && name === 'export' && options.output === undefined)
|
|
133
|
+
|| (argument === '--id-map' && name === 'import' && options.idMap === undefined))
|
|
134
|
+
&& next !== undefined && !next.startsWith('--')) {
|
|
135
|
+
options[argument === '--output' ? 'output' : 'idMap'] = next; index++; continue;
|
|
136
|
+
}
|
|
137
|
+
if (argument === '--input-file' && options.inputFile === undefined && !options.stdin && next !== undefined
|
|
138
|
+
&& !next.startsWith('--')) { options.inputFile = next; index++; continue; }
|
|
139
|
+
if (argument === '--stdin' && !options.stdin && options.inputFile === undefined) { options.stdin = true; continue; }
|
|
140
|
+
if (!AGENT_FLAGS[name].includes(argument) || (!REPEATABLE.has(argument) && options.native.includes(argument))) {
|
|
141
|
+
fail(`Unknown or repeated agent ${name} option: ${String(argument).slice(0, 64)}. See workbite --help.`);
|
|
142
|
+
}
|
|
143
|
+
options.native.push(argument);
|
|
144
|
+
if (FLAGS_WITHOUT_VALUE.has(argument)) continue;
|
|
145
|
+
if (next === undefined || next.startsWith('--')) fail(`${argument} needs a value`);
|
|
146
|
+
options.native.push(next); index++;
|
|
147
|
+
}
|
|
148
|
+
pairedBinding(options);
|
|
149
|
+
const packet = options.stdin || options.inputFile !== undefined;
|
|
150
|
+
if (PACKET_COMMANDS.has(name) !== packet) {
|
|
151
|
+
fail(PACKET_COMMANDS.has(name) ? `agent ${name} needs a packet from --input-file FILE or --stdin`
|
|
152
|
+
: `agent ${name} does not take --input-file or --stdin`);
|
|
153
|
+
}
|
|
154
|
+
const value = flag => options.native[options.native.indexOf(flag) + 1];
|
|
155
|
+
const has = flag => options.native.includes(flag);
|
|
156
|
+
const dryRun = name === 'import' && has('--dry-run');
|
|
157
|
+
if (name === 'import') {
|
|
158
|
+
if (options.inputFile === undefined) fail('agent import requires --input-file FILE');
|
|
159
|
+
if (!has('--mode') || !['new', 'merge'].includes(value('--mode'))) fail('--mode must be new or merge');
|
|
160
|
+
const proofs = ['--intent-id', '--plan-digest', '--expected-snapshot-digest'];
|
|
161
|
+
if (dryRun && (proofs.some(has) || options.idMap !== undefined)) fail('Import preview does not accept write identity, proofs or --id-map');
|
|
162
|
+
if (!dryRun && proofs.some(flag => !has(flag))) fail('Import apply requires --intent-id, --plan-digest and --expected-snapshot-digest from preview');
|
|
163
|
+
}
|
|
164
|
+
if (['import-status', 'import-rollback'].includes(name) && !has('--intent-id')) fail(`agent ${name} requires --intent-id`);
|
|
165
|
+
if (name === 'import-rollback' && (!has('--import-id') || !has('--expected-snapshot-digest'))) fail('Import rollback requires --import-id and --expected-snapshot-digest');
|
|
166
|
+
for (const proof of ['--plan-digest', '--expected-snapshot-digest']) {
|
|
167
|
+
if (has(proof) && !transfer.DIGEST.test(value(proof))) fail(`${proof} must be sha256: followed by 64 lowercase hex digits`);
|
|
168
|
+
}
|
|
169
|
+
if (has('--intent-id') && name.startsWith('import') && !/^[A-Za-z0-9._:-]{8,128}$/.test(value('--intent-id'))) fail('Invalid transfer --intent-id');
|
|
170
|
+
if (WRITE_COMMANDS.includes(name) && !dryRun && options.workspace === undefined) {
|
|
171
|
+
fail(`agent ${name} changes Work Stack; name the workspace with --workspace ABS --workspace-uid UUID`);
|
|
172
|
+
}
|
|
173
|
+
if (packet) options.native.push('--stdin');
|
|
174
|
+
return options;
|
|
175
|
+
}
|
|
176
|
+
function parseMcp(rest) {
|
|
177
|
+
const options = { command: 'mcp' };
|
|
178
|
+
for (let index = 0; index < rest.length; index++) {
|
|
179
|
+
if (!parseBinding(options, rest[index], rest[index + 1])) {
|
|
180
|
+
fail(`Unknown or repeated mcp option: ${String(rest[index]).slice(0, 64)}. See workbite --help.`);
|
|
181
|
+
}
|
|
182
|
+
index++;
|
|
183
|
+
}
|
|
184
|
+
pairedBinding(options);
|
|
185
|
+
return options;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function parseArguments(argv) {
|
|
189
|
+
if (argv.length === 1 && ['--help', '-h', 'help'].includes(argv[0])) return { command: 'help' };
|
|
190
|
+
if (argv.length === 1 && ['--version', '-v', 'version'].includes(argv[0])) return { command: 'version' };
|
|
191
|
+
// Bare `workbite` (or only open options) means `workbite open`.
|
|
192
|
+
const [command, ...rest] = argv.length && !argv[0].startsWith('--') ? argv : ['open', ...argv];
|
|
193
|
+
if (['install', 'check', 'update'].includes(command) && !rest.length) return { command };
|
|
194
|
+
if (['status', 'doctor', 'recover'].includes(command)) {
|
|
195
|
+
const options = { command, json: false, dryRun: false };
|
|
196
|
+
for (const argument of rest) {
|
|
197
|
+
if (argument === '--json' && !options.json) options.json = true;
|
|
198
|
+
else if (command === 'recover' && argument === '--dry-run' && !options.dryRun) options.dryRun = true;
|
|
199
|
+
else fail(`Unsupported or repeated ${command} option. See workbite --help.`);
|
|
200
|
+
}
|
|
201
|
+
if (command === 'recover' && !options.dryRun) fail('recover requires --dry-run; automatic repair is not supported.');
|
|
202
|
+
return options;
|
|
203
|
+
}
|
|
204
|
+
if (command === 'agent') return parseAgent([...rest]);
|
|
205
|
+
if (command === 'mcp') return parseMcp(rest);
|
|
206
|
+
if (command === 'open') {
|
|
207
|
+
const options = { command, init: false, ui: true };
|
|
208
|
+
for (let index = 0; index < rest.length; index++) {
|
|
209
|
+
const argument = rest[index];
|
|
210
|
+
if (argument === '--init' && !options.init) options.init = true;
|
|
211
|
+
else if (argument === '--no-ui' && options.ui) options.ui = false;
|
|
212
|
+
else if (argument === '--workspace' && options.workspace === undefined && rest[index + 1] !== undefined
|
|
213
|
+
&& !rest[index + 1].startsWith('--')) options.workspace = rest[++index];
|
|
214
|
+
else if (argument === '--frontend' && options.frontend === undefined) {
|
|
215
|
+
const name = rest[++index];
|
|
216
|
+
if (!FRONTENDS.includes(name)) fail(`--frontend must be one of: ${FRONTENDS.join(', ')}`);
|
|
217
|
+
options.frontend = name;
|
|
218
|
+
}
|
|
219
|
+
else fail(`Unknown or repeated open option: ${String(argument).slice(0, 64)}. See workbite --help.`);
|
|
220
|
+
}
|
|
221
|
+
// Without --workspace the remembered workspace is reopened; --init never picks a folder by itself.
|
|
222
|
+
if (options.workspace === undefined) {
|
|
223
|
+
if (options.init) fail('--init needs --workspace ABS naming the new or empty folder');
|
|
224
|
+
return options;
|
|
225
|
+
}
|
|
226
|
+
if (!path.isAbsolute(options.workspace)) fail('--workspace must be an absolute path');
|
|
227
|
+
return options;
|
|
228
|
+
}
|
|
229
|
+
fail(`Unknown command: ${String(command).slice(0, 64)}. See workbite --help.`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// A packet is bounded UTF-8 text handed to the native client as stdin; it never enters argv.
|
|
233
|
+
function packetText(bytes) {
|
|
234
|
+
if (bytes.length > MAX_PACKET_BYTES) fail(`The agent packet exceeds ${MAX_PACKET_BYTES} bytes`);
|
|
235
|
+
try { return new TextDecoder('utf-8', { fatal: true }).decode(bytes); } catch { fail('The agent packet is not valid UTF-8'); }
|
|
236
|
+
}
|
|
237
|
+
async function readPacket(parsed, stdin) {
|
|
238
|
+
if (parsed.inputFile !== undefined) {
|
|
239
|
+
let bytes;
|
|
240
|
+
try { bytes = await readSmall(path.resolve(parsed.inputFile), MAX_PACKET_BYTES); } catch (error) {
|
|
241
|
+
fail(/oversized/.test(error.message) ? `The agent packet file is not a regular file of at most ${MAX_PACKET_BYTES} bytes`
|
|
242
|
+
: 'The agent packet file could not be read');
|
|
243
|
+
}
|
|
244
|
+
return packetText(bytes);
|
|
245
|
+
}
|
|
246
|
+
if (!parsed.stdin) return null;
|
|
247
|
+
const chunks = [];
|
|
248
|
+
let size = 0;
|
|
249
|
+
for await (const chunk of stdin) {
|
|
250
|
+
size += chunk.length;
|
|
251
|
+
if (size > MAX_PACKET_BYTES) fail(`The agent packet exceeds ${MAX_PACKET_BYTES} bytes`);
|
|
252
|
+
chunks.push(Buffer.from(chunk));
|
|
253
|
+
}
|
|
254
|
+
return packetText(Buffer.concat(chunks));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function runAgent(parsed, { installed, stateRoot, environment, commands, timeouts, stdout, stderr, stdin }) {
|
|
258
|
+
let result, binding = null;
|
|
259
|
+
if (parsed.name === 'capabilities' && parsed.workspace === undefined) {
|
|
260
|
+
result = await agentCapabilities({ installed, commands, environment, argv: parsed.native, timeouts });
|
|
261
|
+
} else {
|
|
262
|
+
binding = await bindAgent({ installed, stateRoot, environment, workspace: parsed.workspace,
|
|
263
|
+
workspaceUid: parsed.workspaceUid, commands, timeouts });
|
|
264
|
+
if (parsed.output !== undefined) await transfer.outputPath(parsed.output);
|
|
265
|
+
if (parsed.idMap !== undefined) await transfer.outputPath(parsed.idMap);
|
|
266
|
+
const packet = parsed.name === 'import' ? await transfer.readArchive(parsed.inputFile) : await readPacket(parsed, stdin || process.stdin);
|
|
267
|
+
result = await executeAgent(binding, parsed.native, { stdin: packet });
|
|
268
|
+
}
|
|
269
|
+
let output = result.stdout;
|
|
270
|
+
if (result.code === 0 && (parsed.output !== undefined || parsed.idMap !== undefined)) {
|
|
271
|
+
try {
|
|
272
|
+
const intentIndex = parsed.native.indexOf('--intent-id');
|
|
273
|
+
const data = transfer.resultData(result, parsed.name, binding.workspaceUid,
|
|
274
|
+
intentIndex === -1 ? undefined : parsed.native[intentIndex + 1]);
|
|
275
|
+
if (parsed.output !== undefined) await transfer.writePrivate(parsed.output, transfer.archive(data, binding.workspaceUid));
|
|
276
|
+
if (parsed.idMap !== undefined) await transfer.writePrivate(parsed.idMap, transfer.idMap(data.id_map));
|
|
277
|
+
} catch (error) {
|
|
278
|
+
// Apply may already be committed: preserve its receipt and never replay for a local file failure.
|
|
279
|
+
if (output) stdout.write(output);
|
|
280
|
+
stderr.write(`error: transfer file output failed: ${error.message}. Native operation was not replayed; retain this response and use import-status for any write.\n`);
|
|
281
|
+
return { code: 1, signal: null };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (parsed.name === 'status' && result.code === 0) {
|
|
285
|
+
try {
|
|
286
|
+
const document = JSON.parse(output);
|
|
287
|
+
if (document !== null && typeof document === 'object' && !Array.isArray(document)) {
|
|
288
|
+
document.workbite = { schema: 'workbite.agent.binding.v1', workspace: binding.workspace,
|
|
289
|
+
workspace_uid: binding.workspaceUid, endpoint: binding.endpoint, state: binding.stateRoot };
|
|
290
|
+
output = JSON.stringify(document) + '\n';
|
|
291
|
+
}
|
|
292
|
+
} catch { /* not JSON: passed through unchanged */ }
|
|
293
|
+
}
|
|
294
|
+
if (output) stdout.write(output);
|
|
295
|
+
if (result.stderr) stderr.write(result.stderr);
|
|
296
|
+
return { code: result.code, signal: null };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function main(argv = process.argv.slice(2), options = {}) {
|
|
300
|
+
const stdout = options.stdout || process.stdout, stderr = options.stderr || process.stderr;
|
|
301
|
+
const parsed = parseArguments(argv);
|
|
302
|
+
if (parsed.command === 'help') { stdout.write(HELP); return { code: 0, signal: null }; }
|
|
303
|
+
if (parsed.command === 'version') { stdout.write(`workbite ${packageVersion}\n`); return { code: 0, signal: null }; }
|
|
304
|
+
const environment = options.environment || process.env;
|
|
305
|
+
const platform = options.platform || process.platform;
|
|
306
|
+
if (parsed.command === 'update') {
|
|
307
|
+
return require('./update').runUpdate({ ...options, environment, platform, stdout, stderr });
|
|
308
|
+
}
|
|
309
|
+
if (['status', 'doctor', 'recover'].includes(parsed.command)) {
|
|
310
|
+
return require('./diagnostics').runDiagnostics(parsed, { ...options, environment, platform, stdout });
|
|
311
|
+
}
|
|
312
|
+
const spec = releaseSpec(options.manifest || require('../release.json'), platform, options.arch || process.arch, options);
|
|
313
|
+
const where = locations(environment, platform);
|
|
314
|
+
// agent and mcp only use an already installed, verified suite: nothing is downloaded or started.
|
|
315
|
+
const installed = await ensureInstalled(spec, { ...options, apps: options.apps || where.apps,
|
|
316
|
+
checkOnly: ['check', 'agent', 'mcp'].includes(parsed.command), progress: options.progress || stageReporter(stderr) });
|
|
317
|
+
const stateRoot = options.stateRoot || where.state;
|
|
318
|
+
if (parsed.command === 'agent') return runAgent(parsed, { ...options, installed, stateRoot, environment, stdout, stderr });
|
|
319
|
+
if (parsed.command === 'mcp') {
|
|
320
|
+
return require('./mcp').runMcp({ installed, stateRoot, environment, workspace: parsed.workspace,
|
|
321
|
+
workspaceUid: parsed.workspaceUid, commands: options.commands, timeouts: options.timeouts,
|
|
322
|
+
stdin: options.stdin || process.stdin, stdout, stderr });
|
|
323
|
+
}
|
|
324
|
+
if (parsed.command === 'install' || parsed.command === 'check') {
|
|
325
|
+
stdout.write(JSON.stringify({ schema: 'workbite.install.v1', version: spec.version, target: spec.target,
|
|
326
|
+
home: installed.home, state: stateRoot }) + '\n');
|
|
327
|
+
return { code: 0, signal: null };
|
|
328
|
+
}
|
|
329
|
+
return openSuite({ installed, commands: options.commands, workspace: parsed.workspace, init: parsed.init, ui: parsed.ui,
|
|
330
|
+
frontend: parsed.frontend, stateRoot, environment, spawnImpl: options.spawnImpl, execImpl: options.execImpl, stdout, stderr, timeouts: options.timeouts,
|
|
331
|
+
mobileBootstrap: options.mobileBootstrap });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = { main, parseArguments, HELP };
|