@kitman-sh/cli 0.7.4 → 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/dist/{chunk-h12rxyy9.js → chunk-2npvts4c.js} +315 -4
- package/dist/{chunk-jwtkec1w.js → chunk-64et31rh.js} +142 -12
- package/dist/{chunk-q6hk9ceb.js → chunk-8m4wzjt8.js} +16 -426
- package/dist/chunk-ad124cbc.js +132 -0
- package/dist/{chunk-20nwe9zd.js → chunk-dbtnt79b.js} +1 -1
- package/dist/{chunk-gew1a5p6.js → chunk-djtdn35f.js} +18 -9
- package/dist/{chunk-j2psmzr4.js → chunk-fbb0fdnj.js} +18 -7
- package/dist/{chunk-jd09xfx0.js → chunk-fpyy4rpd.js} +7624 -5641
- package/dist/{chunk-120n71cv.js → chunk-m1086bf9.js} +995 -389
- package/dist/{chunk-s92k78ef.js → chunk-m6yymqfr.js} +481 -138
- package/dist/{chunk-5cc3fb7y.js → chunk-nmgr9w6v.js} +3 -3
- package/dist/index.js +1330 -1438
- package/package.json +3 -1
- package/dist/chunk-7f20xrc0.js +0 -74
package/dist/index.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
import {
|
|
4
|
-
identity,
|
|
5
|
-
isUndefined,
|
|
6
|
-
isNotUndefined,
|
|
7
|
-
isNotNull,
|
|
8
|
-
isNullish,
|
|
9
4
|
toJsonValue,
|
|
10
5
|
isOneOf,
|
|
11
6
|
toJsonObject,
|
|
@@ -14,34 +9,19 @@ import {
|
|
|
14
9
|
isObject,
|
|
15
10
|
isJsonObject,
|
|
16
11
|
isString2,
|
|
17
|
-
match,
|
|
18
12
|
getOrElse,
|
|
19
|
-
fromUndefinedOr,
|
|
20
13
|
getOrUndefined,
|
|
21
|
-
Service,
|
|
22
14
|
Reference,
|
|
23
|
-
of,
|
|
24
|
-
toMillis,
|
|
25
15
|
TaggedError,
|
|
26
16
|
squash,
|
|
27
|
-
|
|
28
|
-
done,
|
|
29
|
-
UnknownError,
|
|
30
|
-
succeed2,
|
|
31
|
-
fail2,
|
|
32
|
-
makeUnsafe,
|
|
17
|
+
make5,
|
|
33
18
|
_await,
|
|
34
|
-
|
|
35
|
-
isDoneUnsafe,
|
|
36
|
-
doneUnsafe,
|
|
37
|
-
addFinalizer,
|
|
38
|
-
succeed4,
|
|
39
|
-
effect,
|
|
40
|
-
mergeAll,
|
|
19
|
+
fail3,
|
|
41
20
|
provide2,
|
|
42
21
|
provideMerge,
|
|
43
|
-
|
|
44
|
-
|
|
22
|
+
min,
|
|
23
|
+
exponential,
|
|
24
|
+
spaced,
|
|
45
25
|
forEach,
|
|
46
26
|
promise,
|
|
47
27
|
tryPromise,
|
|
@@ -50,7 +30,6 @@ import {
|
|
|
50
30
|
sync,
|
|
51
31
|
void_2,
|
|
52
32
|
undefined_,
|
|
53
|
-
callback,
|
|
54
33
|
gen,
|
|
55
34
|
fail4,
|
|
56
35
|
try_,
|
|
@@ -61,102 +40,88 @@ import {
|
|
|
61
40
|
as,
|
|
62
41
|
asVoid,
|
|
63
42
|
catch_,
|
|
43
|
+
catchTag,
|
|
64
44
|
catchIf,
|
|
65
45
|
mapError,
|
|
46
|
+
tapError,
|
|
66
47
|
tapCause,
|
|
48
|
+
retry,
|
|
67
49
|
ignore,
|
|
68
50
|
orElseSucceed,
|
|
51
|
+
timeoutOrElse,
|
|
69
52
|
sleep,
|
|
70
53
|
raceFirst,
|
|
71
54
|
filterOrFail,
|
|
72
55
|
context,
|
|
73
56
|
provide3,
|
|
74
57
|
provideContext,
|
|
58
|
+
service,
|
|
75
59
|
provideService,
|
|
60
|
+
scope,
|
|
76
61
|
scoped,
|
|
77
62
|
acquireRelease,
|
|
78
|
-
addFinalizer2,
|
|
79
63
|
ensuring,
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
onError,
|
|
65
|
+
forkIn,
|
|
82
66
|
forkScoped,
|
|
83
|
-
fnUntraced,
|
|
84
67
|
logWarning,
|
|
85
68
|
logError,
|
|
86
69
|
runMain,
|
|
87
|
-
systemError,
|
|
88
|
-
badArgument,
|
|
89
|
-
makeUnsafe3,
|
|
90
|
-
make8,
|
|
91
|
-
make9,
|
|
92
|
-
offerUnsafe,
|
|
93
|
-
endUnsafe,
|
|
94
70
|
take,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
mapDone,
|
|
98
|
-
isSink,
|
|
99
|
-
fromChannel,
|
|
100
|
-
drain,
|
|
101
|
-
make10,
|
|
102
|
-
get2,
|
|
103
|
-
isStream,
|
|
104
|
-
fromChannel2,
|
|
105
|
-
empty2,
|
|
106
|
-
unwrap,
|
|
107
|
-
merge2,
|
|
108
|
-
transduce,
|
|
71
|
+
tap2,
|
|
72
|
+
mapError2,
|
|
109
73
|
decodeText,
|
|
110
74
|
run,
|
|
111
75
|
mkString,
|
|
112
76
|
FileSystem,
|
|
113
77
|
Path,
|
|
114
78
|
encode,
|
|
115
|
-
ExitCode,
|
|
116
|
-
ProcessId,
|
|
117
|
-
makeHandle,
|
|
118
|
-
make12,
|
|
119
79
|
ChildProcessSpawner,
|
|
120
|
-
|
|
121
|
-
parseFdName,
|
|
122
|
-
fdName,
|
|
123
|
-
handleErrnoException,
|
|
80
|
+
make8,
|
|
124
81
|
Crypto,
|
|
82
|
+
Stdio,
|
|
83
|
+
is,
|
|
84
|
+
decodeUnknownEffect,
|
|
85
|
+
String2,
|
|
86
|
+
Struct,
|
|
87
|
+
ArraySchema,
|
|
88
|
+
isPattern,
|
|
89
|
+
Terminal,
|
|
125
90
|
layer,
|
|
126
|
-
layer2,
|
|
127
|
-
layer3,
|
|
128
|
-
tag,
|
|
129
|
-
Error2,
|
|
130
91
|
error,
|
|
131
92
|
log,
|
|
132
93
|
guard,
|
|
133
94
|
toIso,
|
|
134
95
|
currentDate,
|
|
135
|
-
|
|
136
|
-
|
|
96
|
+
redactDurableSurface,
|
|
97
|
+
AgentHarnessIds,
|
|
98
|
+
RepoBindingProviders,
|
|
99
|
+
RepoBindingCredentialProviderTypes,
|
|
137
100
|
ContextPackModes,
|
|
138
101
|
DefaultContextPackMode,
|
|
102
|
+
ProposalStatuses,
|
|
103
|
+
ProposalTargetTypes,
|
|
104
|
+
RunDisplayStates,
|
|
105
|
+
defaultRunnerCapabilities,
|
|
106
|
+
isSyncKitHashValid,
|
|
107
|
+
isSyncKitSafe,
|
|
108
|
+
resolveSyncKit,
|
|
109
|
+
KitKinds,
|
|
110
|
+
KitInstallationScopes,
|
|
111
|
+
KitCreate,
|
|
112
|
+
KitUpdate,
|
|
139
113
|
MemoryStatuses,
|
|
140
114
|
MemoryScopes,
|
|
141
115
|
MaxMemoryListLimit,
|
|
142
|
-
RepoBindingProviders,
|
|
143
|
-
RepoBindingCredentialProviderTypes,
|
|
144
|
-
AgentHarnessIds,
|
|
145
|
-
redactDurableSurface,
|
|
146
|
-
RunStatuses,
|
|
147
|
-
KitmanAgentCapabilityNames,
|
|
148
116
|
DecisionStatuses,
|
|
149
117
|
FixFlowRecoveryActions,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
KitCreate,
|
|
154
|
-
KitUpdate,
|
|
118
|
+
KitmanAgentCapabilityNames,
|
|
119
|
+
isRunFileRefusalReason,
|
|
120
|
+
runFileRefusalWords,
|
|
155
121
|
repositoryKey,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
resolveSyncKit,
|
|
122
|
+
createId,
|
|
123
|
+
digestSha256,
|
|
124
|
+
sha256Hasher,
|
|
160
125
|
decodeJsonString,
|
|
161
126
|
decodeJsonStringOption,
|
|
162
127
|
encodeJsonStringSync,
|
|
@@ -164,6 +129,17 @@ import {
|
|
|
164
129
|
encodeUnknownJsonStringSync,
|
|
165
130
|
option2,
|
|
166
131
|
String3,
|
|
132
|
+
raw,
|
|
133
|
+
make9,
|
|
134
|
+
get2,
|
|
135
|
+
put,
|
|
136
|
+
HttpClient,
|
|
137
|
+
execute,
|
|
138
|
+
Fetch,
|
|
139
|
+
layer2,
|
|
140
|
+
runDisplayLabels,
|
|
141
|
+
KitmanClientError,
|
|
142
|
+
KitmanEffectClient,
|
|
167
143
|
CliError,
|
|
168
144
|
DefaultApiUrl,
|
|
169
145
|
resolveConfig,
|
|
@@ -182,15 +158,12 @@ import {
|
|
|
182
158
|
removeAllSessions,
|
|
183
159
|
agentCredentialForApi,
|
|
184
160
|
storeAgentCredential,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
layer4,
|
|
192
|
-
KitmanEffectClient,
|
|
193
|
-
digestSha256,
|
|
161
|
+
removeAgentCredential,
|
|
162
|
+
normalizeApiUrl,
|
|
163
|
+
isStoredAgentCredential,
|
|
164
|
+
RpcClientError,
|
|
165
|
+
RunnerHeaders,
|
|
166
|
+
RunnerRpcError,
|
|
194
167
|
parseCallbackUrl,
|
|
195
168
|
revokeToken,
|
|
196
169
|
toStoredSession,
|
|
@@ -201,941 +174,286 @@ import {
|
|
|
201
174
|
refreshSession,
|
|
202
175
|
createAgentAuthBearerToken,
|
|
203
176
|
createAgentAuthSignedFetch,
|
|
204
|
-
RunnerHeaders,
|
|
205
177
|
setupMcpAgentCredential,
|
|
206
178
|
generateAgentKeyPair,
|
|
179
|
+
CommandFailed2,
|
|
207
180
|
runCommand2,
|
|
208
181
|
detectHarnesses2,
|
|
209
182
|
herdrRunnerCapabilities2
|
|
210
|
-
} from "./chunk-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
// node_modules/.bun/@effect+platform-node-shared@4.0.4+b8cbb99ebc524863/node_modules/@effect/platform-node-shared/dist/NodeSink.js
|
|
228
|
-
var fromWritable = (options) => fromChannel(mapDone(fromWritableChannel(options), (_) => [_]));
|
|
229
|
-
var fromWritableChannel = (options) => fromTransform((pull) => {
|
|
230
|
-
const writable = options.evaluate();
|
|
231
|
-
return succeed5(pullIntoWritable({
|
|
232
|
-
...options,
|
|
233
|
-
writable,
|
|
234
|
-
pull
|
|
235
|
-
}));
|
|
236
|
-
});
|
|
237
|
-
var pullIntoWritable = (options) => options.pull.pipe(flatMap2((chunk) => {
|
|
238
|
-
let i = 0;
|
|
239
|
-
return callback((resume) => {
|
|
240
|
-
let cancelled = false;
|
|
241
|
-
const loop = () => {
|
|
242
|
-
for (;i < chunk.length; ) {
|
|
243
|
-
if (cancelled) {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const success = options.writable.write(chunk[i++], options.encoding);
|
|
247
|
-
if (!success) {
|
|
248
|
-
if (!cancelled) {
|
|
249
|
-
options.writable.once("drain", loop);
|
|
250
|
-
}
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (!cancelled) {
|
|
255
|
-
resume(void_2);
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
loop();
|
|
259
|
-
return sync(() => {
|
|
260
|
-
cancelled = true;
|
|
261
|
-
options.writable.off("drain", loop);
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
}), forever({
|
|
265
|
-
disableYield: true
|
|
266
|
-
}), options.endOnDone !== false ? catchDone((_) => {
|
|
267
|
-
if ("closed" in options.writable && options.writable.closed) {
|
|
268
|
-
return done(_);
|
|
269
|
-
}
|
|
270
|
-
return callback((resume) => {
|
|
271
|
-
const onFinish = () => resume(done(_));
|
|
272
|
-
options.writable.once("finish", onFinish);
|
|
273
|
-
options.writable.end();
|
|
274
|
-
return sync(() => {
|
|
275
|
-
options.writable.off("finish", onFinish);
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
}) : identity, raceFirst(callback((resume) => {
|
|
279
|
-
const onError = (error) => resume(fail4(options.onError(error)));
|
|
280
|
-
options.writable.once("error", onError);
|
|
281
|
-
return sync(() => {
|
|
282
|
-
options.writable.off("error", onError);
|
|
183
|
+
} from "./chunk-fpyy4rpd.js";
|
|
184
|
+
// apps/cli/src/herdr-runner-credential.ts
|
|
185
|
+
function herdrRunnerCredentialName(runnerId) {
|
|
186
|
+
return `herdr-runner:${runnerId}`;
|
|
187
|
+
}
|
|
188
|
+
function storedHerdrRunnerCredential(apiUrl, runnerId) {
|
|
189
|
+
return map3(readAuthStore(), (auth) => agentCredentialForApi(auth, apiUrl, herdrRunnerCredentialName(runnerId)));
|
|
190
|
+
}
|
|
191
|
+
function writeRunnerCredentialFile(path, file) {
|
|
192
|
+
return gen(function* () {
|
|
193
|
+
const fs = yield* FileSystem;
|
|
194
|
+
yield* fs.makeDirectory((yield* Path).dirname(path), { recursive: true, mode: 448 });
|
|
195
|
+
yield* fs.writeFileString(path, `${encodeJsonStringSync(file)}
|
|
196
|
+
`, { mode: 384 });
|
|
197
|
+
yield* fs.chmod(path, 384);
|
|
283
198
|
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
chunkSize: options.chunkSize,
|
|
293
|
-
closeOnDone: options.closeOnDone
|
|
294
|
-
}));
|
|
295
|
-
var readableToPullUnsafe = (options) => {
|
|
296
|
-
const readable = options.readable;
|
|
297
|
-
const closeOnDone = options.closeOnDone ?? true;
|
|
298
|
-
const exit = options.exit ?? make8(undefined);
|
|
299
|
-
const latch = options.latch ?? makeUnsafe3(false);
|
|
300
|
-
function onReadable() {
|
|
301
|
-
latch.openUnsafe();
|
|
302
|
-
}
|
|
303
|
-
function onError(error) {
|
|
304
|
-
exit.current = fail2(options.onError(error));
|
|
305
|
-
latch.openUnsafe();
|
|
306
|
-
}
|
|
307
|
-
function onEnd() {
|
|
308
|
-
exit.current = fail2(Done());
|
|
309
|
-
latch.openUnsafe();
|
|
310
|
-
}
|
|
311
|
-
readable.on("readable", onReadable);
|
|
312
|
-
readable.once("error", onError);
|
|
313
|
-
readable.once("end", onEnd);
|
|
314
|
-
const pull = suspend(function loop() {
|
|
315
|
-
let item = options.readable.read(options.chunkSize);
|
|
316
|
-
if (item === null) {
|
|
317
|
-
if (exit.current) {
|
|
318
|
-
return exit.current;
|
|
319
|
-
}
|
|
320
|
-
if (readable.readableEnded) {
|
|
321
|
-
return fail4(Done());
|
|
322
|
-
}
|
|
323
|
-
latch.closeUnsafe();
|
|
324
|
-
return flatMap2(latch.await, loop);
|
|
325
|
-
}
|
|
326
|
-
const chunk = of(item);
|
|
327
|
-
while (true) {
|
|
328
|
-
item = options.readable.read(options.chunkSize);
|
|
329
|
-
if (item === null)
|
|
330
|
-
break;
|
|
331
|
-
chunk.push(item);
|
|
332
|
-
}
|
|
333
|
-
return succeed5(chunk);
|
|
199
|
+
}
|
|
200
|
+
function readRunnerCredentialFile(path) {
|
|
201
|
+
return gen(function* () {
|
|
202
|
+
const text = yield* flatMap2(FileSystem, (fs) => fs.readFileString(path));
|
|
203
|
+
const file = yield* decodeJsonString(text);
|
|
204
|
+
if (!isJsonObject(file) || !isStoredAgentCredential(file) || !isString2(file.apiUrl) || !isString2(file.runnerId) || file.hostId === undefined)
|
|
205
|
+
return yield* new CliError({ message: `${path} holds no Runner credential; pair again with --credential-file` });
|
|
206
|
+
return { apiUrl: file.apiUrl, runnerId: file.runnerId, credential: file };
|
|
334
207
|
});
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
};
|
|
344
|
-
var defaultOnError = (error) => new UnknownError(error);
|
|
345
|
-
|
|
346
|
-
// node_modules/.bun/@effect+platform-node-shared@4.0.4+b8cbb99ebc524863/node_modules/@effect/platform-node-shared/dist/NodeChildProcessSpawner.js
|
|
347
|
-
var toError = (error) => error instanceof globalThis.Error ? error : new globalThis.Error(String(error));
|
|
348
|
-
var toPlatformError = (method, error, command) => {
|
|
349
|
-
const {
|
|
350
|
-
commands
|
|
351
|
-
} = flattenCommand(command);
|
|
352
|
-
const commandStr = commands.reduce((acc, curr) => {
|
|
353
|
-
const cmd = `${curr.command} ${curr.args.join(" ")}`;
|
|
354
|
-
return acc.length === 0 ? cmd : `${acc} | ${cmd}`;
|
|
355
|
-
}, "");
|
|
356
|
-
return handleErrnoException("ChildProcess", method)(error, [commandStr]);
|
|
357
|
-
};
|
|
358
|
-
var processGroupGraceMillis = 1000;
|
|
359
|
-
var processGroupPollIntervalMillis = 10;
|
|
360
|
-
var handleInputErrors = (writable) => {
|
|
361
|
-
const onError = () => {};
|
|
362
|
-
writable.on("error", onError);
|
|
363
|
-
writable.once("close", () => writable.off("error", onError));
|
|
364
|
-
};
|
|
365
|
-
var isProcessAlive = (childProcess, exitSignal) => {
|
|
366
|
-
if (!isDoneUnsafe(exitSignal)) {
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
if (globalThis.process.platform === "win32") {
|
|
370
|
-
return false;
|
|
371
|
-
}
|
|
372
|
-
try {
|
|
373
|
-
globalThis.process.kill(-childProcess.pid, 0);
|
|
374
|
-
return true;
|
|
375
|
-
} catch {
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
var taskkill = (childProcess, onExit = () => {}) => {
|
|
380
|
-
if (childProcess.exitCode !== null || childProcess.signalCode !== null) {
|
|
381
|
-
onExit(null);
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
NodeChildProcess.execFile("taskkill", ["/pid", String(childProcess.pid), "/T", "/F"], {
|
|
385
|
-
windowsHide: true
|
|
386
|
-
}, onExit);
|
|
387
|
-
};
|
|
388
|
-
var make = /* @__PURE__ */ gen(function* () {
|
|
389
|
-
const fs = yield* FileSystem;
|
|
390
|
-
const path = yield* Path;
|
|
391
|
-
const resolveWorkingDirectory = fnUntraced(function* (options) {
|
|
392
|
-
if (isUndefined(options.cwd))
|
|
393
|
-
return;
|
|
394
|
-
yield* fs.access(options.cwd);
|
|
395
|
-
return path.resolve(options.cwd);
|
|
208
|
+
}
|
|
209
|
+
function herdrRunnerCredentialCheck(apiUrl, runnerId) {
|
|
210
|
+
return gen(function* () {
|
|
211
|
+
const credential = yield* storedHerdrRunnerCredential(apiUrl, runnerId);
|
|
212
|
+
if (credential === undefined)
|
|
213
|
+
return "missing";
|
|
214
|
+
const { client } = yield* createHerdrRunnerClient({ apiUrl, runnerId, credential });
|
|
215
|
+
return yield* client.heartbeatRunner(runnerId, {}).pipe(as("accepted"), catchIf((error) => error.status === 401 || error.status === 403, () => succeed5("refused")));
|
|
396
216
|
});
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
...defaultConfig,
|
|
417
|
-
stream: options.stdin
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
if (isStream(options.stdin)) {
|
|
421
|
-
return {
|
|
422
|
-
...defaultConfig,
|
|
423
|
-
stream: options.stdin
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
return {
|
|
427
|
-
stream: options.stdin.stream,
|
|
428
|
-
encoding: options.stdin.encoding ?? defaultConfig.encoding,
|
|
429
|
-
endOnDone: options.stdin.endOnDone ?? defaultConfig.endOnDone
|
|
430
|
-
};
|
|
431
|
-
};
|
|
432
|
-
const resolveOutputOption = (options, streamName) => {
|
|
433
|
-
const option = options[streamName];
|
|
434
|
-
if (isUndefined(option)) {
|
|
435
|
-
return {
|
|
436
|
-
stream: "pipe"
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
if (typeof option === "string") {
|
|
440
|
-
return {
|
|
441
|
-
stream: option
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
if (isSink(option)) {
|
|
445
|
-
return {
|
|
446
|
-
stream: option
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
return {
|
|
450
|
-
stream: option.stream
|
|
451
|
-
};
|
|
452
|
-
};
|
|
453
|
-
const resolveAdditionalFds = (options) => {
|
|
454
|
-
if (isUndefined(options.additionalFds)) {
|
|
455
|
-
return [];
|
|
456
|
-
}
|
|
457
|
-
const result = [];
|
|
458
|
-
for (const [name, config] of Object.entries(options.additionalFds)) {
|
|
459
|
-
const fd = parseFdName(name);
|
|
460
|
-
if (isNotUndefined(fd)) {
|
|
461
|
-
result.push({
|
|
462
|
-
fd,
|
|
463
|
-
config
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return result.sort((a, b) => a.fd - b.fd);
|
|
468
|
-
};
|
|
469
|
-
const buildStdioArray = (stdinConfig, stdoutConfig, stderrConfig, additionalFds) => {
|
|
470
|
-
const stdio = [inputToStdioOption(stdinConfig.stream), outputToStdioOption(stdoutConfig.stream), outputToStdioOption(stderrConfig.stream)];
|
|
471
|
-
if (additionalFds.length === 0) {
|
|
472
|
-
return stdio;
|
|
473
|
-
}
|
|
474
|
-
const maxFd = additionalFds.reduce((max, {
|
|
475
|
-
fd
|
|
476
|
-
}) => Math.max(max, fd), 2);
|
|
477
|
-
for (let i = 3;i <= maxFd; i++) {
|
|
478
|
-
stdio[i] = "ignore";
|
|
479
|
-
}
|
|
480
|
-
for (const {
|
|
481
|
-
fd
|
|
482
|
-
} of additionalFds) {
|
|
483
|
-
stdio[fd] = "pipe";
|
|
484
|
-
}
|
|
485
|
-
return stdio;
|
|
486
|
-
};
|
|
487
|
-
const setupAdditionalFds = fnUntraced(function* (command, childProcess, additionalFds) {
|
|
488
|
-
if (additionalFds.length === 0) {
|
|
489
|
-
return {
|
|
490
|
-
getInputFd: () => drain,
|
|
491
|
-
getOutputFd: () => empty2
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
const inputSinks = new Map;
|
|
495
|
-
const outputStreams = new Map;
|
|
496
|
-
for (const {
|
|
497
|
-
config,
|
|
498
|
-
fd
|
|
499
|
-
} of additionalFds) {
|
|
500
|
-
const nodeStream = childProcess.stdio[fd];
|
|
501
|
-
switch (config.type) {
|
|
502
|
-
case "input": {
|
|
503
|
-
let sink = drain;
|
|
504
|
-
if (nodeStream && "write" in nodeStream) {
|
|
505
|
-
handleInputErrors(nodeStream);
|
|
506
|
-
sink = fromWritable({
|
|
507
|
-
evaluate: () => nodeStream,
|
|
508
|
-
onError: (error) => toPlatformError(`fromWritable(fd${fd})`, toError(error), command)
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
if (config.stream) {
|
|
512
|
-
yield* forkScoped(run(config.stream, sink));
|
|
513
|
-
}
|
|
514
|
-
inputSinks.set(fd, sink);
|
|
515
|
-
break;
|
|
516
|
-
}
|
|
517
|
-
case "output": {
|
|
518
|
-
let stream = empty2;
|
|
519
|
-
if (nodeStream && "read" in nodeStream) {
|
|
520
|
-
const passThrough = new PassThrough;
|
|
521
|
-
nodeStream.on("error", (error) => passThrough.destroy(error));
|
|
522
|
-
nodeStream.pipe(passThrough);
|
|
523
|
-
stream = fromReadable({
|
|
524
|
-
evaluate: () => passThrough,
|
|
525
|
-
onError: (error) => toPlatformError(`fromReadable(fd${fd})`, toError(error), command)
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
if (config.sink) {
|
|
529
|
-
stream = transduce(stream, config.sink);
|
|
530
|
-
}
|
|
531
|
-
outputStreams.set(fd, stream);
|
|
532
|
-
break;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
return {
|
|
537
|
-
getInputFd: (fd) => inputSinks.get(fd) ?? drain,
|
|
538
|
-
getOutputFd: (fd) => outputStreams.get(fd) ?? empty2
|
|
217
|
+
}
|
|
218
|
+
function provisionHerdrRunnerCredential(input) {
|
|
219
|
+
return gen(function* () {
|
|
220
|
+
const keyPair = yield* generateAgentKeyPair;
|
|
221
|
+
const client = yield* createSdkClient({
|
|
222
|
+
apiUrl: input.apiUrl,
|
|
223
|
+
session: { accessToken: input.accessToken },
|
|
224
|
+
...input.fetch === undefined ? undefined : { fetch: input.fetch }
|
|
225
|
+
});
|
|
226
|
+
const provisioned = yield* client.provisionRunnerCredential(input.runner.id, {
|
|
227
|
+
publicKey: keyPair.publicKeyJwk
|
|
228
|
+
}).pipe(mapError(() => new CliError({ message: "herdr_runner_credential_unavailable" })));
|
|
229
|
+
if (provisioned.credentialHostId === undefined)
|
|
230
|
+
return yield* new CliError({ message: "herdr_runner_credential_unavailable" });
|
|
231
|
+
const credential = {
|
|
232
|
+
agentId: provisioned.agentIdentityId,
|
|
233
|
+
hostId: provisioned.credentialHostId,
|
|
234
|
+
privateKeyJwk: keyPair.privateKeyJwk,
|
|
235
|
+
publicKeyJwk: keyPair.publicKeyJwk
|
|
539
236
|
};
|
|
237
|
+
yield* storeAgentCredential(input.apiUrl, herdrRunnerCredentialName(input.runner.id), credential, input.authPath);
|
|
238
|
+
return credential;
|
|
540
239
|
});
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
encoding: config.encoding
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
if (isStream(config.stream)) {
|
|
553
|
-
return as(forkScoped(run(config.stream, sink)), sink);
|
|
554
|
-
}
|
|
555
|
-
return succeed5(sink);
|
|
556
|
-
});
|
|
557
|
-
const setupChildOutputStreams = (command, childProcess, stdoutConfig, stderrConfig) => {
|
|
558
|
-
let stdout = childProcess.stdout ? (() => {
|
|
559
|
-
const passThrough = new PassThrough;
|
|
560
|
-
childProcess.stdout.on("error", (error) => passThrough.destroy(error));
|
|
561
|
-
childProcess.stdout.pipe(passThrough);
|
|
562
|
-
return fromReadable({
|
|
563
|
-
evaluate: () => passThrough,
|
|
564
|
-
onError: (error) => toPlatformError("fromReadable(stdout)", toError(error), command)
|
|
565
|
-
});
|
|
566
|
-
})() : empty2;
|
|
567
|
-
let stderr = childProcess.stderr ? (() => {
|
|
568
|
-
const passThrough = new PassThrough;
|
|
569
|
-
childProcess.stderr.on("error", (error) => passThrough.destroy(error));
|
|
570
|
-
childProcess.stderr.pipe(passThrough);
|
|
571
|
-
return fromReadable({
|
|
572
|
-
evaluate: () => passThrough,
|
|
573
|
-
onError: (error) => toPlatformError("fromReadable(stderr)", toError(error), command)
|
|
574
|
-
});
|
|
575
|
-
})() : empty2;
|
|
576
|
-
if (isSink(stdoutConfig.stream)) {
|
|
577
|
-
stdout = transduce(stdout, stdoutConfig.stream);
|
|
578
|
-
}
|
|
579
|
-
if (isSink(stderrConfig.stream)) {
|
|
580
|
-
stderr = transduce(stderr, stderrConfig.stream);
|
|
581
|
-
}
|
|
582
|
-
const all = merge2(stdout, stderr);
|
|
583
|
-
return {
|
|
584
|
-
stdout,
|
|
585
|
-
stderr,
|
|
586
|
-
all
|
|
587
|
-
};
|
|
588
|
-
};
|
|
589
|
-
const spawn2 = (command, spawnOptions) => callback((resume) => {
|
|
590
|
-
const deferred = makeUnsafe();
|
|
591
|
-
const handle = NodeChildProcess.spawn(command.command, command.args, spawnOptions);
|
|
592
|
-
handle.on("error", (error) => {
|
|
593
|
-
resume(fail4(toPlatformError("spawn", error, command)));
|
|
594
|
-
});
|
|
595
|
-
handle.on("exit", (...args) => {
|
|
596
|
-
doneUnsafe(deferred, succeed2(args));
|
|
597
|
-
});
|
|
598
|
-
handle.on("spawn", () => {
|
|
599
|
-
resume(succeed5([handle, deferred]));
|
|
240
|
+
}
|
|
241
|
+
function createHerdrRunnerClient(input) {
|
|
242
|
+
return gen(function* () {
|
|
243
|
+
const credential = input.credential;
|
|
244
|
+
const signedFetch = createAgentAuthSignedFetch({
|
|
245
|
+
apiUrl: input.apiUrl,
|
|
246
|
+
credential,
|
|
247
|
+
fetch: input.fetch ?? (yield* Fetch)
|
|
600
248
|
});
|
|
601
|
-
|
|
602
|
-
|
|
249
|
+
const runnerFetch = (requestInput, init) => {
|
|
250
|
+
const request = new Request(requestInput, init);
|
|
251
|
+
request.headers.set(RunnerHeaders.runnerId, input.runnerId);
|
|
252
|
+
return signedFetch(request);
|
|
253
|
+
};
|
|
254
|
+
const client = yield* createSdkClient({
|
|
255
|
+
apiUrl: input.apiUrl,
|
|
256
|
+
fetch: runnerFetch,
|
|
257
|
+
session: { accessToken: "runner-agent-auth" }
|
|
603
258
|
});
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
taskkill(childProcess, (error) => {
|
|
609
|
-
if (error) {
|
|
610
|
-
resume(fail4(toPlatformError("kill", toError(error), command)));
|
|
611
|
-
} else {
|
|
612
|
-
resume(void_2);
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
return try_({
|
|
618
|
-
try: () => {
|
|
619
|
-
globalThis.process.kill(-childProcess.pid, signal);
|
|
620
|
-
},
|
|
621
|
-
catch: (error) => toPlatformError("kill", toError(error), command)
|
|
259
|
+
const rpcBearer = createAgentAuthBearerToken({
|
|
260
|
+
credential,
|
|
261
|
+
audience: new URL("/v1/runners/rpc", input.apiUrl).toString(),
|
|
262
|
+
capabilities: ["kitman_api"]
|
|
622
263
|
});
|
|
623
|
-
|
|
624
|
-
const killProcessGroupOnExit = (childProcess, signal) => {
|
|
625
|
-
if (globalThis.process.platform === "win32") {
|
|
626
|
-
taskkill(childProcess);
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
try {
|
|
630
|
-
globalThis.process.kill(-childProcess.pid, signal);
|
|
631
|
-
} catch {}
|
|
632
|
-
};
|
|
633
|
-
const killProcess = (command, childProcess, signal) => suspend(() => {
|
|
634
|
-
const killed = childProcess.kill(signal);
|
|
635
|
-
if (!killed) {
|
|
636
|
-
const error = new globalThis.Error("Failed to kill child process");
|
|
637
|
-
return fail4(toPlatformError("kill", error, command));
|
|
638
|
-
}
|
|
639
|
-
return void_2;
|
|
640
|
-
});
|
|
641
|
-
const awaitProcessExit = (childProcess, exitSignal, timeoutMillis) => callback((resume) => {
|
|
642
|
-
const deadline = Date.now() + timeoutMillis;
|
|
643
|
-
let timer;
|
|
644
|
-
const stop = () => {
|
|
645
|
-
clearTimeout(timer);
|
|
646
|
-
childProcess.removeListener("exit", poll);
|
|
647
|
-
};
|
|
648
|
-
const poll = () => {
|
|
649
|
-
clearTimeout(timer);
|
|
650
|
-
if (Date.now() >= deadline || !isProcessAlive(childProcess, exitSignal)) {
|
|
651
|
-
stop();
|
|
652
|
-
resume(void_2);
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
timer = setTimeout(poll, processGroupPollIntervalMillis);
|
|
656
|
-
};
|
|
657
|
-
childProcess.on("exit", poll);
|
|
658
|
-
poll();
|
|
659
|
-
return sync(stop);
|
|
660
|
-
});
|
|
661
|
-
const terminateProcessGroup = fnUntraced(function* (command, childProcess, exitSignal, options) {
|
|
662
|
-
const signalGroup = (signal) => killProcessGroup(command, childProcess, signal).pipe(catch_(() => killProcess(command, childProcess, signal)));
|
|
663
|
-
yield* signalGroup(options?.killSignal ?? "SIGTERM");
|
|
664
|
-
if (isUndefined(options?.forceKillAfter)) {
|
|
665
|
-
yield* awaitProcessExit(childProcess, exitSignal, processGroupGraceMillis);
|
|
666
|
-
} else {
|
|
667
|
-
yield* awaitProcessExit(childProcess, exitSignal, toMillis(options.forceKillAfter));
|
|
668
|
-
if (isProcessAlive(childProcess, exitSignal)) {
|
|
669
|
-
yield* signalGroup("SIGKILL");
|
|
670
|
-
yield* awaitProcessExit(childProcess, exitSignal, processGroupGraceMillis);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
yield* _await(exitSignal);
|
|
674
|
-
});
|
|
675
|
-
const getSourceStream = (handle, from) => {
|
|
676
|
-
const fromOption = from ?? "stdout";
|
|
677
|
-
switch (fromOption) {
|
|
678
|
-
case "stdout":
|
|
679
|
-
return handle.stdout;
|
|
680
|
-
case "stderr":
|
|
681
|
-
return handle.stderr;
|
|
682
|
-
case "all":
|
|
683
|
-
return handle.all;
|
|
684
|
-
default: {
|
|
685
|
-
const fd = parseFdName(fromOption);
|
|
686
|
-
if (isNotUndefined(fd)) {
|
|
687
|
-
return handle.getOutputFd(fd);
|
|
688
|
-
}
|
|
689
|
-
return handle.stdout;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
const spawnCommand = fnUntraced(function* (cmd) {
|
|
694
|
-
switch (cmd._tag) {
|
|
695
|
-
case "StandardCommand": {
|
|
696
|
-
const stdinConfig = resolveStdinOption(cmd.options);
|
|
697
|
-
const stdoutConfig = resolveOutputOption(cmd.options, "stdout");
|
|
698
|
-
const stderrConfig = resolveOutputOption(cmd.options, "stderr");
|
|
699
|
-
const resolvedAdditionalFds = resolveAdditionalFds(cmd.options);
|
|
700
|
-
let isReferenced = true;
|
|
701
|
-
const cwd = yield* resolveWorkingDirectory(cmd.options);
|
|
702
|
-
const env = resolveEnvironment(cmd.options);
|
|
703
|
-
const stdio = buildStdioArray(stdinConfig, stdoutConfig, stderrConfig, resolvedAdditionalFds);
|
|
704
|
-
const [childProcess, exitSignal] = yield* acquireRelease(spawn2(cmd, buildSpawnOptions(cmd.options, {
|
|
705
|
-
cwd,
|
|
706
|
-
env,
|
|
707
|
-
stdio
|
|
708
|
-
}, process.platform)), fnUntraced(function* ([childProcess, exitSignal]) {
|
|
709
|
-
const exited = yield* isDone(exitSignal);
|
|
710
|
-
if (exited) {
|
|
711
|
-
const [code] = yield* _await(exitSignal);
|
|
712
|
-
if (code !== 0 && isNotNull(code)) {
|
|
713
|
-
yield* ignore(terminateProcessGroup(cmd, childProcess, exitSignal, cmd.options));
|
|
714
|
-
} else if (isReferenced && process.platform !== "win32" && cmd.options.detached !== false) {
|
|
715
|
-
yield* ignore(terminateProcessGroup(cmd, childProcess, exitSignal, cmd.options));
|
|
716
|
-
}
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
if (!isReferenced) {
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
yield* ignore(terminateProcessGroup(cmd, childProcess, exitSignal, cmd.options));
|
|
723
|
-
}));
|
|
724
|
-
const pid = ProcessId(childProcess.pid);
|
|
725
|
-
childProcess.on("exit", (code) => {
|
|
726
|
-
if (code !== 0 && isNotNull(code)) {
|
|
727
|
-
killProcessGroupOnExit(childProcess, cmd.options.killSignal ?? "SIGTERM");
|
|
728
|
-
}
|
|
729
|
-
});
|
|
730
|
-
const reref = sync(() => {
|
|
731
|
-
if (!isReferenced) {
|
|
732
|
-
childProcess.ref();
|
|
733
|
-
isReferenced = true;
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
const unref = sync(() => {
|
|
737
|
-
if (isReferenced) {
|
|
738
|
-
childProcess.unref();
|
|
739
|
-
isReferenced = false;
|
|
740
|
-
}
|
|
741
|
-
return reref;
|
|
742
|
-
});
|
|
743
|
-
const stdin = yield* setupChildStdin(cmd, childProcess, stdinConfig);
|
|
744
|
-
const {
|
|
745
|
-
all,
|
|
746
|
-
stderr,
|
|
747
|
-
stdout
|
|
748
|
-
} = setupChildOutputStreams(cmd, childProcess, stdoutConfig, stderrConfig);
|
|
749
|
-
const {
|
|
750
|
-
getInputFd,
|
|
751
|
-
getOutputFd
|
|
752
|
-
} = yield* setupAdditionalFds(cmd, childProcess, resolvedAdditionalFds);
|
|
753
|
-
const isRunning = map3(isDone(exitSignal), (done) => !done);
|
|
754
|
-
const exitCode = flatMap2(_await(exitSignal), ([code, signal]) => {
|
|
755
|
-
if (isNotNull(code)) {
|
|
756
|
-
return succeed5(ExitCode(code));
|
|
757
|
-
}
|
|
758
|
-
const error = new globalThis.Error(`Process interrupted due to receipt of signal: '${signal}'`);
|
|
759
|
-
return fail4(toPlatformError("exitCode", error, cmd));
|
|
760
|
-
});
|
|
761
|
-
const kill = (options) => terminateProcessGroup(cmd, childProcess, exitSignal, options);
|
|
762
|
-
return makeHandle({
|
|
763
|
-
pid,
|
|
764
|
-
exitCode,
|
|
765
|
-
isRunning,
|
|
766
|
-
kill,
|
|
767
|
-
stdin,
|
|
768
|
-
stdout,
|
|
769
|
-
stderr,
|
|
770
|
-
all,
|
|
771
|
-
getInputFd,
|
|
772
|
-
getOutputFd,
|
|
773
|
-
unref
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
case "PipedCommand": {
|
|
777
|
-
const {
|
|
778
|
-
commands,
|
|
779
|
-
pipeOptions
|
|
780
|
-
} = flattenCommand(cmd);
|
|
781
|
-
const [root, ...pipeline] = commands;
|
|
782
|
-
const handles = [yield* spawnCommand(root)];
|
|
783
|
-
for (let i = 0;i < pipeline.length; i++) {
|
|
784
|
-
const command = pipeline[i];
|
|
785
|
-
const options = pipeOptions[i] ?? {};
|
|
786
|
-
const stdinConfig = resolveStdinOption(command.options);
|
|
787
|
-
const sourceStream = unwrap(succeed5(getSourceStream(handles[handles.length - 1], options.from)));
|
|
788
|
-
const toOption = options.to ?? "stdin";
|
|
789
|
-
if (toOption === "stdin") {
|
|
790
|
-
handles.push(yield* spawnCommand(make13(command.command, command.args, {
|
|
791
|
-
...command.options,
|
|
792
|
-
stdin: {
|
|
793
|
-
...stdinConfig,
|
|
794
|
-
stream: sourceStream
|
|
795
|
-
}
|
|
796
|
-
})));
|
|
797
|
-
} else {
|
|
798
|
-
const fd = parseFdName(toOption);
|
|
799
|
-
if (isNotUndefined(fd)) {
|
|
800
|
-
const fdName2 = fdName(fd);
|
|
801
|
-
const existingFds = command.options.additionalFds ?? {};
|
|
802
|
-
handles.push(yield* spawnCommand(make13(command.command, command.args, {
|
|
803
|
-
...command.options,
|
|
804
|
-
additionalFds: {
|
|
805
|
-
...existingFds,
|
|
806
|
-
[fdName2]: {
|
|
807
|
-
type: "input",
|
|
808
|
-
stream: sourceStream
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
})));
|
|
812
|
-
} else {
|
|
813
|
-
handles.push(yield* spawnCommand(make13(command.command, command.args, {
|
|
814
|
-
...command.options,
|
|
815
|
-
stdin: {
|
|
816
|
-
...stdinConfig,
|
|
817
|
-
stream: sourceStream
|
|
818
|
-
}
|
|
819
|
-
})));
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
const handle = handles[handles.length - 1];
|
|
824
|
-
const kill = (options) => forEach([...handles].reverse(), (handle) => ignore(handle.kill(options)), {
|
|
825
|
-
discard: true
|
|
826
|
-
});
|
|
827
|
-
const unref = gen(function* () {
|
|
828
|
-
const rerefs = [];
|
|
829
|
-
for (const handle of handles) {
|
|
830
|
-
rerefs.push(yield* handle.unref);
|
|
831
|
-
}
|
|
832
|
-
return forEach([...rerefs].reverse(), (reref) => reref, {
|
|
833
|
-
discard: true
|
|
834
|
-
});
|
|
835
|
-
});
|
|
836
|
-
return makeHandle({
|
|
837
|
-
pid: handle.pid,
|
|
838
|
-
exitCode: handle.exitCode,
|
|
839
|
-
isRunning: handle.isRunning,
|
|
840
|
-
kill,
|
|
841
|
-
stdin: handles[0].stdin,
|
|
842
|
-
stdout: handle.stdout,
|
|
843
|
-
stderr: handle.stderr,
|
|
844
|
-
all: handle.all,
|
|
845
|
-
getInputFd: handle.getInputFd,
|
|
846
|
-
getOutputFd: handle.getOutputFd,
|
|
847
|
-
unref
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
}
|
|
264
|
+
return { client, rpcBearer };
|
|
851
265
|
});
|
|
852
|
-
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// apps/cli/src/kitman-version.ts
|
|
269
|
+
var kitmanVersion = undefined;
|
|
270
|
+
|
|
271
|
+
// apps/cli/src/runner-startup.ts
|
|
272
|
+
class RunnerStopped extends TaggedError("RunnerStopped") {
|
|
273
|
+
}
|
|
274
|
+
var pairFix = "Run `kitman pair` on this machine. Not signed in there? Mint a Pairing Code on the Runners page and run the command it shows.";
|
|
275
|
+
var credentialMissing = (runnerId) => ({
|
|
276
|
+
code: "credential_missing",
|
|
277
|
+
message: `This machine holds no credential for Runner ${runnerId}.`,
|
|
278
|
+
fix: pairFix
|
|
853
279
|
});
|
|
854
|
-
var
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
const flatten = (cmd) => {
|
|
859
|
-
switch (cmd._tag) {
|
|
860
|
-
case "StandardCommand": {
|
|
861
|
-
commands.push(cmd);
|
|
862
|
-
break;
|
|
863
|
-
}
|
|
864
|
-
case "PipedCommand": {
|
|
865
|
-
flatten(cmd.left);
|
|
866
|
-
pipeOptions.push(cmd.options);
|
|
867
|
-
flatten(cmd.right);
|
|
868
|
-
break;
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
};
|
|
872
|
-
flatten(command);
|
|
873
|
-
if (commands.length === 0) {
|
|
874
|
-
throw new Error("flattenCommand produced empty commands array");
|
|
875
|
-
}
|
|
876
|
-
const [first, ...rest] = commands;
|
|
877
|
-
const nonEmptyCommands = [first, ...rest];
|
|
878
|
-
return {
|
|
879
|
-
commands: nonEmptyCommands,
|
|
880
|
-
pipeOptions
|
|
881
|
-
};
|
|
280
|
+
var credentialRefused = {
|
|
281
|
+
code: "credential_refused",
|
|
282
|
+
message: "Kitman refused this Runner's credential.",
|
|
283
|
+
fix: pairFix
|
|
882
284
|
};
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
285
|
+
var runnerRemoved = {
|
|
286
|
+
code: "runner_removed",
|
|
287
|
+
message: "This Runner no longer exists in Kitman.",
|
|
288
|
+
fix: "Run `kitman pair` to set it up again, or `kitman runner remove` to clean up this machine."
|
|
289
|
+
};
|
|
290
|
+
var sandboxInstall = {
|
|
291
|
+
srt: process.platform === "darwin" ? "Install what it needs with `brew install ripgrep`" : "Install what it needs with `sudo apt install bubblewrap socat ripgrep` (or your distribution\u2019s packages)",
|
|
292
|
+
docker: "Install and start Docker (https://docs.docker.com/engine/install/), and set KITMAN_RUNNER_DOCKER_IMAGE to an image pinned by digest"
|
|
293
|
+
};
|
|
294
|
+
var sandboxMissing = (sandbox, missing) => ({
|
|
295
|
+
code: "sandbox_missing",
|
|
296
|
+
message: `\`${sandbox}\` is not available on this host: ${missing.join("; ")}.`.slice(0, 500),
|
|
297
|
+
fix: `${sandboxInstall[sandbox]}, or unset KITMAN_RUNNER_SANDBOX to run agents on the host.`
|
|
893
298
|
});
|
|
299
|
+
var stopOnRefusal = (call) => mapError(call, (error) => {
|
|
300
|
+
if (!(error instanceof KitmanClientError) && !is(RunnerRpcError)(error))
|
|
301
|
+
return error;
|
|
302
|
+
if (error.status === 401 || error.status === 403)
|
|
303
|
+
return new RunnerStopped({ problem: credentialRefused });
|
|
304
|
+
return error.status === 404 ? new RunnerStopped({ problem: runnerRemoved }) : error;
|
|
305
|
+
});
|
|
306
|
+
var stopWhenRefused = (replica) => mapError(replica, (error) => error.kind === "unauthorized" ? new RunnerStopped({ problem: credentialRefused }) : error);
|
|
307
|
+
var unreachable = (error) => error instanceof KitmanClientError && (error.status === undefined || error.status >= 500) || is(RunnerRpcError)(error) && error.status >= 500 || is(RpcClientError)(error);
|
|
308
|
+
var backoff = min([exponential("1 second"), spaced("1 minute")]);
|
|
894
309
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
method: "stdout",
|
|
906
|
-
_tag: "Unknown",
|
|
907
|
-
cause
|
|
908
|
-
}),
|
|
909
|
-
endOnDone: options?.endOnDone ?? false
|
|
910
|
-
}),
|
|
911
|
-
stderr: (options) => fromWritable({
|
|
912
|
-
evaluate: () => process.stderr,
|
|
913
|
-
onError: (cause) => systemError({
|
|
914
|
-
module: "Stdio",
|
|
915
|
-
method: "stderr",
|
|
916
|
-
_tag: "Unknown",
|
|
917
|
-
cause
|
|
918
|
-
}),
|
|
919
|
-
endOnDone: options?.endOnDone ?? false
|
|
920
|
-
}),
|
|
921
|
-
stdin: /* @__PURE__ */ fromReadable({
|
|
922
|
-
evaluate: () => process.stdin,
|
|
923
|
-
onError: (cause) => systemError({
|
|
924
|
-
module: "Stdio",
|
|
925
|
-
method: "stdin",
|
|
926
|
-
_tag: "Unknown",
|
|
927
|
-
cause
|
|
928
|
-
}),
|
|
929
|
-
closeOnDone: false
|
|
930
|
-
})
|
|
310
|
+
class StepTimedOut extends TaggedError("StepTimedOut") {
|
|
311
|
+
}
|
|
312
|
+
var startupStep = (step, bound, attempt) => attempt.pipe(timeoutOrElse({ duration: bound, orElse: () => fail4(new StepTimedOut) }), tapError((error2) => unreachable(error2) ? error(`Runner start: ${step} failed, trying again: ${error2.message}`) : void_2), retry({ schedule: backoff, while: unreachable }), mapError((error) => {
|
|
313
|
+
if (error instanceof RunnerStopped)
|
|
314
|
+
return error;
|
|
315
|
+
if (error instanceof StepTimedOut)
|
|
316
|
+
return new CliError({ message: `Runner start: ${step} did not finish within ${bound}` });
|
|
317
|
+
return new CliError({
|
|
318
|
+
message: `Runner start: ${step} failed: ${error instanceof Error ? error.message : String(error)}`
|
|
319
|
+
});
|
|
931
320
|
}));
|
|
932
321
|
|
|
933
|
-
//
|
|
934
|
-
var
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
var TypeId2 = "~effect/Terminal";
|
|
938
|
-
var QuitErrorTypeId = "~effect/Terminal/QuitError";
|
|
939
|
-
|
|
940
|
-
class QuitError extends (/* @__PURE__ */ Error2("QuitError")({
|
|
941
|
-
_tag: /* @__PURE__ */ tag("QuitError")
|
|
942
|
-
})) {
|
|
943
|
-
[QuitErrorTypeId] = QuitErrorTypeId;
|
|
944
|
-
}
|
|
945
|
-
var Terminal = /* @__PURE__ */ Service("effect/Terminal");
|
|
946
|
-
var make3 = (impl) => Terminal.of({
|
|
947
|
-
...impl,
|
|
948
|
-
[TypeId2]: TypeId2
|
|
322
|
+
// apps/cli/src/managed-install.ts
|
|
323
|
+
var RunnerReleaseManifest = Struct({
|
|
324
|
+
version: String2.check(isPattern(/^[\w.+-]+$/u)),
|
|
325
|
+
platforms: ArraySchema(Struct({ os: String2, architecture: String2, sha256: String2, url: String2 }))
|
|
949
326
|
});
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
yield* addFinalizer2(() => sync(() => stdin.off("end", onStdinEnd)));
|
|
967
|
-
const rlRef = yield* make10({
|
|
968
|
-
acquire: acquireRelease(sync(() => {
|
|
969
|
-
const rl = readline.createInterface({
|
|
970
|
-
input: stdin,
|
|
971
|
-
escapeCodeTimeout: 50
|
|
972
|
-
});
|
|
973
|
-
const onLine = (line) => offerUnsafe(lines, line);
|
|
974
|
-
const onClose = () => {
|
|
975
|
-
readlineActive = false;
|
|
976
|
-
endUnsafe(lines);
|
|
977
|
-
};
|
|
978
|
-
readlineActive = true;
|
|
979
|
-
readline.emitKeypressEvents(stdin, rl);
|
|
980
|
-
rl.on("line", onLine);
|
|
981
|
-
rl.once("close", onClose);
|
|
982
|
-
if (stdin.isTTY) {
|
|
983
|
-
stdin.setRawMode(true);
|
|
984
|
-
}
|
|
985
|
-
return {
|
|
986
|
-
rl,
|
|
987
|
-
onClose,
|
|
988
|
-
onLine
|
|
989
|
-
};
|
|
990
|
-
}), ({
|
|
991
|
-
rl,
|
|
992
|
-
onClose,
|
|
993
|
-
onLine
|
|
994
|
-
}) => sync(() => {
|
|
995
|
-
readlineActive = false;
|
|
996
|
-
rl.off("line", onLine);
|
|
997
|
-
rl.off("close", onClose);
|
|
998
|
-
if (stdin.isTTY) {
|
|
999
|
-
stdin.setRawMode(false);
|
|
1000
|
-
}
|
|
1001
|
-
rl.close();
|
|
1002
|
-
if (inputEnded) {
|
|
1003
|
-
endUnsafe(lines);
|
|
1004
|
-
}
|
|
1005
|
-
})),
|
|
1006
|
-
idleTimeToLive: "10 millis"
|
|
1007
|
-
});
|
|
1008
|
-
const columns = sync(() => stdout.columns ?? 0);
|
|
1009
|
-
const rows = sync(() => stdout.rows ?? 0);
|
|
1010
|
-
const readInput = gen(function* () {
|
|
1011
|
-
const queue = yield* make9();
|
|
1012
|
-
const handleKeypress = (s, k) => {
|
|
1013
|
-
const userInput = {
|
|
1014
|
-
input: fromUndefinedOr(s),
|
|
1015
|
-
key: {
|
|
1016
|
-
name: k.name ?? "",
|
|
1017
|
-
ctrl: !!k.ctrl,
|
|
1018
|
-
meta: !!k.meta,
|
|
1019
|
-
shift: !!k.shift
|
|
1020
|
-
}
|
|
1021
|
-
};
|
|
1022
|
-
offerUnsafe(queue, userInput);
|
|
1023
|
-
if (shouldQuit(userInput)) {
|
|
1024
|
-
endUnsafe(queue);
|
|
1025
|
-
}
|
|
1026
|
-
};
|
|
1027
|
-
const keepAlive = setInterval(() => {}, 2147483647);
|
|
1028
|
-
const handleEnd = () => {
|
|
1029
|
-
clearInterval(keepAlive);
|
|
1030
|
-
endUnsafe(queue);
|
|
1031
|
-
};
|
|
1032
|
-
yield* addFinalizer2(() => sync(() => {
|
|
1033
|
-
clearInterval(keepAlive);
|
|
1034
|
-
stdin.off("keypress", handleKeypress);
|
|
1035
|
-
stdin.off("end", handleEnd);
|
|
1036
|
-
}));
|
|
1037
|
-
stdin.on("keypress", handleKeypress);
|
|
1038
|
-
if (inputEnded) {
|
|
1039
|
-
handleEnd();
|
|
1040
|
-
} else {
|
|
1041
|
-
yield* get2(rlRef);
|
|
1042
|
-
stdin.once("end", handleEnd);
|
|
1043
|
-
}
|
|
1044
|
-
return queue;
|
|
1045
|
-
});
|
|
1046
|
-
const readLine = suspend(() => poll(lines).pipe(flatMap2(match({
|
|
1047
|
-
onNone: () => scoped(andThen(get2(rlRef), take(lines))),
|
|
1048
|
-
onSome: succeed5
|
|
1049
|
-
})), mapError(() => new QuitError({}))));
|
|
1050
|
-
const display = (prompt2) => uninterruptible(callback((resume) => {
|
|
1051
|
-
stdout.write(prompt2, (err) => isNullish(err) ? resume(void_2) : resume(fail4(badArgument({
|
|
1052
|
-
module: "Terminal",
|
|
1053
|
-
method: "display",
|
|
1054
|
-
description: "Failed to write prompt to stdout",
|
|
1055
|
-
cause: err
|
|
1056
|
-
}))));
|
|
327
|
+
var versionsDirectory = (platform) => gen(function* () {
|
|
328
|
+
const path = yield* Path;
|
|
329
|
+
const home = yield* String3("HOME");
|
|
330
|
+
const xdgData = getOrUndefined(yield* option2(String3("XDG_DATA_HOME")));
|
|
331
|
+
const data = platform === "darwin" ? path.join(home, "Library", "Application Support") : xdgData ?? path.join(home, ".local", "share");
|
|
332
|
+
return path.join(data, "kitman", "versions");
|
|
333
|
+
});
|
|
334
|
+
var managedKitmanPath = gen(function* () {
|
|
335
|
+
const path = yield* Path;
|
|
336
|
+
return path.join(yield* String3("HOME"), ".local", "bin", "kitman");
|
|
337
|
+
});
|
|
338
|
+
var installedKitmanVersion = gen(function* () {
|
|
339
|
+
const fs = yield* FileSystem;
|
|
340
|
+
const path = yield* Path;
|
|
341
|
+
return yield* fs.readLink(yield* managedKitmanPath).pipe(map3((target) => path.basename(path.dirname(target))), orElseSucceed(() => {
|
|
342
|
+
return;
|
|
1057
343
|
}));
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
344
|
+
});
|
|
345
|
+
var runningManagedInstall = (platform) => gen(function* () {
|
|
346
|
+
if (process.argv[1]?.startsWith("/$bunfs/") !== true)
|
|
347
|
+
return false;
|
|
348
|
+
const fs = yield* FileSystem;
|
|
349
|
+
const versions = yield* versionsDirectory(platform);
|
|
350
|
+
const executable = yield* fs.realPath(process.execPath).pipe(orElseSucceed(() => process.execPath));
|
|
351
|
+
return executable.startsWith(`${versions}/`);
|
|
352
|
+
});
|
|
353
|
+
var downloadManagedKitman = (apiUrl, platform) => gen(function* () {
|
|
354
|
+
const fs = yield* FileSystem;
|
|
355
|
+
const path = yield* Path;
|
|
356
|
+
const fetch = yield* Fetch;
|
|
357
|
+
const get = (url) => tryPromise({
|
|
358
|
+
try: () => fetch(url),
|
|
359
|
+
catch: (error) => new CliError({ message: `Fetching ${url} failed: ${String(error)}` })
|
|
1064
360
|
});
|
|
361
|
+
const answer = yield* get(new URL("/v1/runner-release", apiUrl).toString());
|
|
362
|
+
if (answer.status === 404)
|
|
363
|
+
return;
|
|
364
|
+
if (!answer.ok)
|
|
365
|
+
return yield* new CliError({ message: `${apiUrl} answered ${answer.status} for its kitman release` });
|
|
366
|
+
const manifest = yield* tryPromise(() => answer.json()).pipe(flatMap2(decodeUnknownEffect(RunnerReleaseManifest)), mapError(() => new CliError({ message: `${apiUrl} served a kitman release this kitman cannot read` })));
|
|
367
|
+
const os = platform === "darwin" ? "darwin" : "linux";
|
|
368
|
+
const architecture = process.arch === "x64" ? "amd64" : process.arch;
|
|
369
|
+
const release = manifest.platforms.find((entry) => entry.os === os && entry.architecture === architecture && entry.sha256 !== "");
|
|
370
|
+
if (release === undefined)
|
|
371
|
+
return yield* new CliError({ message: `${apiUrl} serves no kitman for ${os}/${architecture}` });
|
|
372
|
+
const versions = yield* versionsDirectory(platform);
|
|
373
|
+
const kitman = path.join(versions, manifest.version, "kitman");
|
|
374
|
+
const installed = !(yield* fs.exists(kitman));
|
|
375
|
+
if (installed) {
|
|
376
|
+
const binary = yield* get(release.url).pipe(filterOrFail((downloaded) => downloaded.ok, (downloaded) => new CliError({ message: `Downloading kitman ${manifest.version} answered ${downloaded.status}` })), flatMap2((downloaded) => tryPromise({
|
|
377
|
+
try: () => downloaded.arrayBuffer(),
|
|
378
|
+
catch: (error) => new CliError({ message: `Downloading kitman ${manifest.version} failed: ${String(error)}` })
|
|
379
|
+
})), map3((bytes) => new Uint8Array(bytes)));
|
|
380
|
+
if (digestSha256(binary, "hex") !== release.sha256)
|
|
381
|
+
return yield* new CliError({
|
|
382
|
+
message: `The downloaded kitman ${manifest.version} for ${os}/${architecture} does not match its sha256; nothing was installed`
|
|
383
|
+
});
|
|
384
|
+
const download = path.join(versions, `.kitman-${manifest.version}.download`);
|
|
385
|
+
yield* fs.makeDirectory(path.dirname(kitman), { recursive: true });
|
|
386
|
+
yield* fs.writeFile(download, binary, { mode: 493 });
|
|
387
|
+
yield* fs.rename(download, kitman);
|
|
388
|
+
}
|
|
389
|
+
return { version: manifest.version, kitman, installed };
|
|
390
|
+
});
|
|
391
|
+
var linkManagedKitman = (kitman) => gen(function* () {
|
|
392
|
+
const fs = yield* FileSystem;
|
|
393
|
+
const path = yield* Path;
|
|
394
|
+
const link = yield* managedKitmanPath;
|
|
395
|
+
const nextLink = `${link}.next`;
|
|
396
|
+
yield* fs.makeDirectory(path.dirname(link), { recursive: true });
|
|
397
|
+
yield* ignore(fs.remove(nextLink));
|
|
398
|
+
yield* fs.symlink(kitman, nextLink);
|
|
399
|
+
yield* fs.rename(nextLink, link);
|
|
1065
400
|
});
|
|
1066
|
-
var
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
401
|
+
var installManagedKitman = (apiUrl, platform) => gen(function* () {
|
|
402
|
+
const downloaded = yield* downloadManagedKitman(apiUrl, platform);
|
|
403
|
+
if (downloaded === undefined)
|
|
404
|
+
return;
|
|
405
|
+
yield* linkManagedKitman(downloaded.kitman);
|
|
406
|
+
return { version: downloaded.version, installed: downloaded.installed };
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// apps/cli/src/runner-update.ts
|
|
410
|
+
class RunnerUpdated extends TaggedError("RunnerUpdated") {
|
|
411
|
+
get message() {
|
|
412
|
+
return `Updated to kitman ${this.version}; exiting so the Runner service restarts on it`;
|
|
413
|
+
}
|
|
1070
414
|
}
|
|
1071
415
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
if (credential.agentId !== input.runner.agentIdentityId || credential.hostId !== hostId)
|
|
1091
|
-
return yield* new CliError({ message: "herdr_runner_credential_mismatch" });
|
|
1092
|
-
if (existing === undefined)
|
|
1093
|
-
yield* storeAgentCredential(input.apiUrl, credentialName, credential, input.authPath);
|
|
1094
|
-
const client = yield* createSdkClient({
|
|
1095
|
-
apiUrl: input.apiUrl,
|
|
1096
|
-
session: { accessToken: input.accessToken },
|
|
1097
|
-
...input.fetch === undefined ? undefined : { fetch: input.fetch }
|
|
1098
|
-
});
|
|
1099
|
-
yield* client.provisionRunnerCredential(input.runner.id, {
|
|
1100
|
-
publicKey: credential.publicKeyJwk
|
|
1101
|
-
}).pipe(mapError(() => new CliError({ message: "herdr_runner_credential_unavailable" })));
|
|
1102
|
-
return credential;
|
|
416
|
+
class UpdateStepFailed extends TaggedError("UpdateStepFailed") {
|
|
417
|
+
}
|
|
418
|
+
var step = (name) => (effect) => mapError(effect, (error) => new UpdateStepFailed({ step: name, reason: error.message }));
|
|
419
|
+
var runnerUpdater = (input) => {
|
|
420
|
+
let claiming = true;
|
|
421
|
+
let updating = false;
|
|
422
|
+
let problems = [];
|
|
423
|
+
const failed = new Set;
|
|
424
|
+
const attempt = gen(function* () {
|
|
425
|
+
const downloaded = yield* downloadManagedKitman(input.apiUrl, input.platform).pipe(filterOrFail((found) => found !== undefined, () => new UpdateStepFailed({ step: "download", reason: `${input.apiUrl} serves no kitman` })), step("download"));
|
|
426
|
+
const reported = (yield* runCommand2(downloaded.kitman, ["--version"], { timeout: "30 seconds" }).pipe(step("start"))).trim();
|
|
427
|
+
if (reported !== downloaded.version)
|
|
428
|
+
return yield* new UpdateStepFailed({ step: "start", reason: `it reported version ${reported || "none"}` });
|
|
429
|
+
claiming = false;
|
|
430
|
+
while ((yield* input.inFlight) > 0)
|
|
431
|
+
yield* sleep("1 second");
|
|
432
|
+
yield* linkManagedKitman(downloaded.kitman).pipe(step("switch"));
|
|
433
|
+
return yield* new RunnerUpdated({ version: downloaded.version });
|
|
1103
434
|
});
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
apiUrl: input.apiUrl,
|
|
1120
|
-
fetch: runnerFetch,
|
|
1121
|
-
session: { accessToken: "runner-agent-auth" }
|
|
1122
|
-
});
|
|
1123
|
-
const rpcBearer = createAgentAuthBearerToken({
|
|
1124
|
-
credential,
|
|
1125
|
-
audience: new URL("/v1/runners/rpc", input.apiUrl).toString(),
|
|
1126
|
-
capabilities: ["kitman_api"]
|
|
1127
|
-
});
|
|
1128
|
-
return { client, rpcBearer };
|
|
435
|
+
const update = (version) => suspend(() => {
|
|
436
|
+
if (updating || failed.has(version))
|
|
437
|
+
return void_2;
|
|
438
|
+
updating = true;
|
|
439
|
+
return attempt.pipe(catchTag("UpdateStepFailed", ({ step: failedStep, reason }) => sync(() => {
|
|
440
|
+
failed.add(version);
|
|
441
|
+
claiming = true;
|
|
442
|
+
problems = [
|
|
443
|
+
{
|
|
444
|
+
code: "update_failed",
|
|
445
|
+
message: `Updating kitman to ${version} failed at the ${failedStep} step: ${reason}`.slice(0, 500),
|
|
446
|
+
fix: `It keeps running its current version. Check that this machine reaches ${input.apiUrl}, then run \`kitman runner restart ${input.runnerId}\` here to try again.`.slice(0, 500)
|
|
447
|
+
}
|
|
448
|
+
];
|
|
449
|
+
})), ensuring(sync(() => updating = false)));
|
|
1129
450
|
});
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
publicKeyJwk: keyPair.publicKeyJwk
|
|
1137
|
-
})));
|
|
1138
|
-
}
|
|
451
|
+
return {
|
|
452
|
+
claiming: () => claiming,
|
|
453
|
+
problems: () => problems,
|
|
454
|
+
update
|
|
455
|
+
};
|
|
456
|
+
};
|
|
1139
457
|
|
|
1140
458
|
// apps/cli/src/harness-sync.ts
|
|
1141
459
|
function buildHarnessSyncPlan(input) {
|
|
@@ -1144,11 +462,11 @@ function buildHarnessSyncPlan(input) {
|
|
|
1144
462
|
version: 1,
|
|
1145
463
|
apiUrl: input.apiUrl,
|
|
1146
464
|
workspaceId: input.workspaceId,
|
|
1147
|
-
...input.
|
|
465
|
+
...input.teamId === undefined ? undefined : { teamId: input.teamId },
|
|
1148
466
|
harnesses: harnesses.map((harness) => ({ harness, configScope: "user", managedBy: "kitman" })),
|
|
1149
467
|
sources: [
|
|
1150
468
|
{ kind: "workspace", id: input.workspaceId },
|
|
1151
|
-
...input.
|
|
469
|
+
...input.teamId === undefined ? [] : [{ kind: "team", id: input.teamId }],
|
|
1152
470
|
{ kind: "user-preferences", id: "default" }
|
|
1153
471
|
],
|
|
1154
472
|
materialization: (input.materializationPlans ?? []).map(renderMaterializationPlan),
|
|
@@ -1231,7 +549,7 @@ function hashIdentity(value) {
|
|
|
1231
549
|
|
|
1232
550
|
// apps/cli/src/assets.ts
|
|
1233
551
|
function send(request, failure) {
|
|
1234
|
-
return execute(request).pipe(mapError((error) => failure(error.cause ?? error)), provide3(
|
|
552
|
+
return execute(request).pipe(mapError((error) => failure(error.cause ?? error)), provide3(layer2));
|
|
1235
553
|
}
|
|
1236
554
|
function putPresignedAsset(upload, bytes) {
|
|
1237
555
|
return gen(function* () {
|
|
@@ -1244,6 +562,30 @@ function putPresignedAsset(upload, bytes) {
|
|
|
1244
562
|
return yield* new CliError({ message: `Asset upload failed with ${response.status}` });
|
|
1245
563
|
});
|
|
1246
564
|
}
|
|
565
|
+
var localFileName = (name, fallback) => {
|
|
566
|
+
const last = name.split(/[\\/]/u).at(-1) ?? "";
|
|
567
|
+
return last === "" || last === "." || last === ".." ? fallback : last;
|
|
568
|
+
};
|
|
569
|
+
function downloadVerified(url, path, sha256) {
|
|
570
|
+
return gen(function* () {
|
|
571
|
+
const fs = yield* FileSystem;
|
|
572
|
+
const failed = (cause) => new CliError({ message: `Download failed: ${String(cause)}` });
|
|
573
|
+
const response = yield* send(get2(url), failed);
|
|
574
|
+
if (response.status < 200 || response.status >= 300)
|
|
575
|
+
return yield* new CliError({ message: `Download failed with ${response.status}` });
|
|
576
|
+
const partial = `${path}.partial`;
|
|
577
|
+
const hash = sha256Hasher();
|
|
578
|
+
yield* response.stream.pipe(tap2((chunk) => sync(() => hash.update(chunk))), mapError2((error) => failed(error.message)), run(fs.sink(partial)), onError(() => ignore(fs.remove(partial))));
|
|
579
|
+
const actual = hash.digest("hex");
|
|
580
|
+
if (actual !== sha256) {
|
|
581
|
+
yield* fs.remove(partial);
|
|
582
|
+
return yield* new CliError({
|
|
583
|
+
message: `The download's SHA-256 is ${actual}, not the ${sha256} Kitman recorded; nothing was kept`
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
yield* fs.rename(partial, path);
|
|
587
|
+
});
|
|
588
|
+
}
|
|
1247
589
|
|
|
1248
590
|
// apps/cli/src/output.ts
|
|
1249
591
|
function printJson(value) {
|
|
@@ -1381,6 +723,162 @@ function declaredChecks(manifest, rootFiles) {
|
|
|
1381
723
|
// apps/cli/src/init.ts
|
|
1382
724
|
import { hostname } from "os";
|
|
1383
725
|
|
|
726
|
+
// apps/cli/src/machine-runners.ts
|
|
727
|
+
var serviceFilePattern = (platform) => platform === "darwin" ? /^dev\.kitman\.kitman-runner-(.+)\.plist$/u : /^kitman-runner-(.+)\.service$/u;
|
|
728
|
+
var unescapeXml = (text) => text.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
729
|
+
var serviceCommand = (platform, definition) => {
|
|
730
|
+
if (platform === "darwin") {
|
|
731
|
+
const programArguments = /<key>ProgramArguments<\/key>\s*<array>(.*?)<\/array>/su.exec(definition)?.[1] ?? "";
|
|
732
|
+
return Array.from(programArguments.matchAll(/<string>(.*?)<\/string>/gsu), ([, word = ""]) => unescapeXml(word));
|
|
733
|
+
}
|
|
734
|
+
const execStart = /^ExecStart=(.*)$/mu.exec(definition)?.[1] ?? "";
|
|
735
|
+
return Array.from(execStart.matchAll(/"([^"]*)"|(\S+)/gu), ([, quoted, word]) => quoted ?? word ?? "");
|
|
736
|
+
};
|
|
737
|
+
var machineRunnerServices = gen(function* () {
|
|
738
|
+
const fs = yield* FileSystem;
|
|
739
|
+
const path = yield* Path;
|
|
740
|
+
const { platform } = yield* ServiceManager;
|
|
741
|
+
const directory = path.dirname(yield* runnerServiceFile(platform, runnerServiceName("")));
|
|
742
|
+
const pattern = serviceFilePattern(platform);
|
|
743
|
+
const files = yield* fs.readDirectory(directory).pipe(orElseSucceed(() => []));
|
|
744
|
+
return yield* forEach(files.flatMap((file) => {
|
|
745
|
+
const runnerId = pattern.exec(file)?.[1];
|
|
746
|
+
return runnerId === undefined ? [] : [{ runnerId, file: path.join(directory, file) }];
|
|
747
|
+
}), ({ runnerId, file }) => map3(fs.readFileString(file), (definition) => ({
|
|
748
|
+
runnerId,
|
|
749
|
+
command: serviceCommand(platform, definition)
|
|
750
|
+
})));
|
|
751
|
+
});
|
|
752
|
+
var serviceApiUrl = (service, fallback) => {
|
|
753
|
+
const index = service.command.indexOf("--api-url");
|
|
754
|
+
return normalizeApiUrl((index < 0 ? undefined : service.command[index + 1]) ?? fallback);
|
|
755
|
+
};
|
|
756
|
+
var ownersRunners = (apiUrl) => gen(function* () {
|
|
757
|
+
const session = yield* sessionForApiWithRefresh(apiUrl);
|
|
758
|
+
if (session === undefined)
|
|
759
|
+
return;
|
|
760
|
+
const client = yield* clientFor(apiUrl, session);
|
|
761
|
+
const workspaces = (yield* client.listWorkspaces()).items;
|
|
762
|
+
const lists = yield* forEach(workspaces, (workspace) => map3(client.listRunners(workspace.id), (list) => list.items));
|
|
763
|
+
return new Map(lists.flat().map((runner) => [runner.id, runner]));
|
|
764
|
+
}).pipe(orElseSucceed(() => {
|
|
765
|
+
return;
|
|
766
|
+
}));
|
|
767
|
+
var runnerAsItself = (apiUrl, runnerId) => gen(function* () {
|
|
768
|
+
const credential = yield* storedHerdrRunnerCredential(apiUrl, runnerId);
|
|
769
|
+
if (credential === undefined)
|
|
770
|
+
return;
|
|
771
|
+
const { client } = yield* createHerdrRunnerClient({ apiUrl, runnerId, credential });
|
|
772
|
+
return yield* client.getRunner(runnerId).pipe(catchIf((error) => error instanceof KitmanClientError && [401, 403, 404].includes(error.status ?? 0), () => succeed5("gone")));
|
|
773
|
+
}).pipe(orElseSucceed(() => {
|
|
774
|
+
return;
|
|
775
|
+
}));
|
|
776
|
+
var removeAsItself = (apiUrl, runnerId) => gen(function* () {
|
|
777
|
+
const credential = yield* storedHerdrRunnerCredential(apiUrl, runnerId);
|
|
778
|
+
if (credential === undefined)
|
|
779
|
+
return;
|
|
780
|
+
const { client } = yield* createHerdrRunnerClient({ apiUrl, runnerId, credential });
|
|
781
|
+
return yield* client.removeRunner(runnerId).pipe(as("removed"), catchIf((error) => error.status === 404, () => succeed5("removed")), catchIf((error) => error.status === 401 || error.status === 403, () => succeed5("refused")));
|
|
782
|
+
});
|
|
783
|
+
var adoptRunnerServices = (apiUrl) => gen(function* () {
|
|
784
|
+
const fs = yield* FileSystem;
|
|
785
|
+
const services = yield* machineRunnerServices;
|
|
786
|
+
const managed = yield* managedKitmanPath;
|
|
787
|
+
const installed = yield* fs.exists(managed);
|
|
788
|
+
const known = new Map;
|
|
789
|
+
for (const service of services) {
|
|
790
|
+
const url = serviceApiUrl(service, apiUrl);
|
|
791
|
+
if (!known.has(url))
|
|
792
|
+
known.set(url, yield* ownersRunners(url));
|
|
793
|
+
}
|
|
794
|
+
const everyListRead = Array.from(known.values()).every((runners) => runners !== undefined);
|
|
795
|
+
for (const service of services) {
|
|
796
|
+
const url = serviceApiUrl(service, apiUrl);
|
|
797
|
+
const runners = known.get(url);
|
|
798
|
+
const name = runnerServiceName(service.runnerId);
|
|
799
|
+
if (everyListRead && runners?.has(service.runnerId) === false) {
|
|
800
|
+
yield* removeRunnerService(name);
|
|
801
|
+
yield* removeAgentCredential(url, `herdr-runner:${service.runnerId}`);
|
|
802
|
+
yield* log(`Runner service: removed ${name} (Kitman no longer has Runner ${service.runnerId})`);
|
|
803
|
+
} else if (installed && service.command[0] !== managed && (yield* storedHerdrRunnerCredential(url, service.runnerId)) !== undefined) {
|
|
804
|
+
yield* runnerService(yield* managedRunnerService(service.runnerId, url));
|
|
805
|
+
yield* log(`Runner service: moved ${name} onto ${managed}`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return known.get(normalizeApiUrl(apiUrl)) ?? (yield* ownersRunners(apiUrl));
|
|
809
|
+
});
|
|
810
|
+
var stateText = (state) => {
|
|
811
|
+
if (state.kind === "needs_attention")
|
|
812
|
+
return "needs attention";
|
|
813
|
+
return state.kind === "offline" && state.since !== undefined ? `offline since ${state.since}` : state.kind;
|
|
814
|
+
};
|
|
815
|
+
var chosenService = (services, runnerId) => {
|
|
816
|
+
if (runnerId !== undefined)
|
|
817
|
+
return succeed5(services.find((service) => service.runnerId === runnerId));
|
|
818
|
+
const [only, ...others] = services;
|
|
819
|
+
if (only === undefined)
|
|
820
|
+
return fail4(new CliError({ message: "No Runner service runs on this machine" }));
|
|
821
|
+
if (others.length > 0)
|
|
822
|
+
return fail4(new CliError({
|
|
823
|
+
message: `This machine runs several Runners; choose one with ${services.map((service) => `--runner ${service.runnerId}`).join(" or ")}`
|
|
824
|
+
}));
|
|
825
|
+
return succeed5(only);
|
|
826
|
+
};
|
|
827
|
+
var serviceFor = (runnerId) => gen(function* () {
|
|
828
|
+
const service = yield* chosenService(yield* machineRunnerServices, runnerId);
|
|
829
|
+
if (service === undefined)
|
|
830
|
+
return yield* new CliError({ message: `No service for Runner ${runnerId} runs on this machine` });
|
|
831
|
+
return service;
|
|
832
|
+
});
|
|
833
|
+
var machineRunnerCommand = (action, apiUrl, options) => gen(function* () {
|
|
834
|
+
if (action === "logs")
|
|
835
|
+
return yield* showRunnerServiceLog(runnerServiceName((yield* serviceFor(options.runnerId)).runnerId), options.follow);
|
|
836
|
+
if (action === "restart") {
|
|
837
|
+
const { runnerId } = yield* serviceFor(options.runnerId);
|
|
838
|
+
yield* restartRunnerService(runnerServiceName(runnerId));
|
|
839
|
+
return yield* log(`Restarted Runner ${runnerId}`);
|
|
840
|
+
}
|
|
841
|
+
if (action === "remove") {
|
|
842
|
+
const service = yield* chosenService(yield* machineRunnerServices, options.runnerId);
|
|
843
|
+
const runnerId = options.runnerId ?? service?.runnerId;
|
|
844
|
+
if (runnerId === undefined)
|
|
845
|
+
return yield* new CliError({ message: "runner remove requires --runner <runner-id>" });
|
|
846
|
+
const url = service === undefined ? normalizeApiUrl(apiUrl) : serviceApiUrl(service, apiUrl);
|
|
847
|
+
const session = yield* sessionForApiWithRefresh(url);
|
|
848
|
+
const removal = session === undefined ? yield* removeAsItself(url, runnerId) : yield* (yield* clientFor(url, session)).removeRunner(runnerId).pipe(as("removed"), catchIf((error) => error instanceof KitmanClientError && error.status === 404, () => succeed5("removed")));
|
|
849
|
+
if (removal === undefined)
|
|
850
|
+
return yield* new CliError({
|
|
851
|
+
message: `This machine holds no credential for Runner ${runnerId}, and nobody is signed in to ${url} here to remove it. Remove it on the Runners page, or sign in with \`kitman login --api-url ${url}\`, then run this again.`
|
|
852
|
+
});
|
|
853
|
+
if (service !== undefined)
|
|
854
|
+
yield* removeRunnerService(runnerServiceName(runnerId));
|
|
855
|
+
yield* removeAgentCredential(url, `herdr-runner:${runnerId}`);
|
|
856
|
+
if (removal === "refused")
|
|
857
|
+
return yield* log(`Removed Runner ${runnerId} from this machine. Kitman refused this machine's credential for it, so it may still exist: if the Runners page lists it, remove it there.`);
|
|
858
|
+
return yield* log(`Removed Runner ${runnerId} from Kitman and this machine`);
|
|
859
|
+
}
|
|
860
|
+
const runners = yield* adoptRunnerServices(apiUrl);
|
|
861
|
+
const services = yield* machineRunnerServices;
|
|
862
|
+
if (services.length === 0)
|
|
863
|
+
return yield* log("No Runner service runs on this machine. Pair one with the command the Runners page shows.");
|
|
864
|
+
const version = yield* installedKitmanVersion;
|
|
865
|
+
for (const service of services) {
|
|
866
|
+
const { runnerId } = service;
|
|
867
|
+
const shown = runners?.get(runnerId) ?? (yield* runnerAsItself(serviceApiUrl(service, apiUrl), runnerId));
|
|
868
|
+
const runner = shown === "gone" ? undefined : shown;
|
|
869
|
+
const problems = runner?.state.kind === "needs_attention" ? runner.state.problems : [];
|
|
870
|
+
const state = shown === "gone" ? "Kitman refuses its credential; run `kitman pair` here, or `kitman runner remove` to clean up" : runner === undefined ? "unknown; Kitman could not be asked" : stateText(runner.state);
|
|
871
|
+
yield* log([
|
|
872
|
+
`${runner?.name ?? "Runner"} (${runnerId})`,
|
|
873
|
+
` state: ${state}`,
|
|
874
|
+
...problems.flatMap((problem) => [` ${problem.code}: ${problem.message}`, ` fix: ${problem.fix}`]),
|
|
875
|
+
` kitman: ${version ?? "not installed"}`,
|
|
876
|
+
` service: ${(yield* runnerServiceState(runnerServiceName(runnerId))) ?? "unsupported"}`
|
|
877
|
+
].join(`
|
|
878
|
+
`));
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
1384
882
|
// apps/cli/src/repo-binding-setup.ts
|
|
1385
883
|
var Git = Reference("kitman/cli/Git", {
|
|
1386
884
|
defaultValue: () => (args) => runCommand2("git", args).pipe(map3((stdout) => stdout.trim()))
|
|
@@ -1493,7 +991,17 @@ function stripPathSuffix(value) {
|
|
|
1493
991
|
|
|
1494
992
|
// apps/cli/src/init.ts
|
|
1495
993
|
var ServiceManager = Reference("kitman/cli/ServiceManager", {
|
|
1496
|
-
defaultValue: () => ({
|
|
994
|
+
defaultValue: () => ({
|
|
995
|
+
platform: process.platform,
|
|
996
|
+
run: (command, args) => runCommand2(command, args),
|
|
997
|
+
attach: (command, args) => gen(function* () {
|
|
998
|
+
const spawner = yield* ChildProcessSpawner;
|
|
999
|
+
const handle = yield* spawner.spawn(make8(command, args, { stdin: "inherit", stdout: "inherit", stderr: "inherit" }));
|
|
1000
|
+
const exitCode = yield* handle.exitCode;
|
|
1001
|
+
if (exitCode !== 0)
|
|
1002
|
+
return yield* new CommandFailed2({ command, reason: `exit code ${exitCode}` });
|
|
1003
|
+
}).pipe(scoped)
|
|
1004
|
+
})
|
|
1497
1005
|
});
|
|
1498
1006
|
var Login = Reference("kitman/cli/Login", {
|
|
1499
1007
|
defaultValue: () => browserSession
|
|
@@ -1535,52 +1043,103 @@ function initCommand(args) {
|
|
|
1535
1043
|
return yield* new CliError({ message: "No Workspace to use" });
|
|
1536
1044
|
yield* writeUserConfig({ ...user, apiUrl, activeWorkspaceId: workspace.id });
|
|
1537
1045
|
yield* step("Workspace", current === undefined ? "set_up" : "already", workspace.name);
|
|
1538
|
-
|
|
1539
|
-
yield*
|
|
1540
|
-
yield* step("Project", project.outcome, project.name);
|
|
1541
|
-
const bindings = (yield* client.listRepoBindings(project.id)).items;
|
|
1046
|
+
yield* writeUserConfig({ ...local, apiUrl, activeWorkspaceId: workspace.id }, yield* localConfigPath);
|
|
1047
|
+
const bindings = (yield* client.listRepoBindings(workspace.id)).items;
|
|
1542
1048
|
const bound = bindings.find((binding) => matchesRemote(binding, remoteUrl));
|
|
1543
|
-
const binding = bound ?? (yield* client.createRepoBinding(
|
|
1544
|
-
yield* step("Repository", bound === undefined ?
|
|
1049
|
+
const binding = bound ?? (yield* client.createRepoBinding(workspace.id, yield* repoBindingInputFromCurrentRepo));
|
|
1050
|
+
yield* step("Repository", bound === undefined ? current === undefined ? "set_up" : "repaired" : "already", `${remote.owner}/${remote.name}`);
|
|
1545
1051
|
yield* runnerStep(client, { apiUrl, session, workspaceId: workspace.id, userId: me.userId }, step);
|
|
1546
1052
|
const syncPath = yield* userHarnessSyncPath;
|
|
1547
1053
|
const before = yield* readText(syncPath);
|
|
1548
1054
|
yield* harnessSync(["sync"]);
|
|
1549
1055
|
const after = yield* readText(syncPath);
|
|
1550
1056
|
yield* step("Harness", before === undefined ? "set_up" : before === after ? "already" : "repaired", "synced");
|
|
1551
|
-
yield* verificationStep(client,
|
|
1057
|
+
yield* verificationStep(client, workspace.id, binding, confirm, step);
|
|
1552
1058
|
const changed = steps.filter((entry) => entry.outcome === "set_up" || entry.outcome === "repaired");
|
|
1553
1059
|
yield* log(changed.length === 0 ? "Nothing to change: Kitman is already set up here." : `Set up ${changed.map((entry) => entry.name).join(", ")}. Start work with kitman run start build "<what to build>".`);
|
|
1554
1060
|
});
|
|
1555
1061
|
}
|
|
1556
1062
|
function pairCommand(args) {
|
|
1557
1063
|
return gen(function* () {
|
|
1558
|
-
const
|
|
1559
|
-
|
|
1560
|
-
return yield* new CliError({ message: "Usage: kitman pair <pairing code> [--api-url <url>]" });
|
|
1064
|
+
const code = args[0]?.startsWith("-") === false ? args[0] : undefined;
|
|
1065
|
+
const { credentialFile, rest } = credentialFileFlag(code === undefined ? args : args.slice(1));
|
|
1561
1066
|
const flags = parseInitFlags(rest);
|
|
1562
1067
|
const apiUrl = yield* resolveApiUrl(flags.apiUrl);
|
|
1563
1068
|
const step = (name, outcome, detail) => log(`${name}: ${outcomeLabel[outcome]} (${detail})`);
|
|
1069
|
+
if (code === undefined) {
|
|
1070
|
+
if (credentialFile !== undefined)
|
|
1071
|
+
return yield* new CliError({ message: "--credential-file needs a Pairing Code minted for a Runner" });
|
|
1072
|
+
return yield* pairAsOwner(apiUrl, yield* storedPairing(apiUrl), "already", step);
|
|
1073
|
+
}
|
|
1074
|
+
const keys = yield* generateAgentKeyPair;
|
|
1564
1075
|
const anonymous = new KitmanEffectClient({ baseUrl: apiUrl, fetch: yield* Fetch });
|
|
1565
|
-
const
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
...credentials.refreshToken === undefined ? undefined : { refresh_token: credentials.refreshToken },
|
|
1569
|
-
...credentials.expiresIn === undefined ? undefined : { expires_in: credentials.expiresIn }
|
|
1076
|
+
const redeemed = yield* anonymous.redeemPairingCode({
|
|
1077
|
+
code,
|
|
1078
|
+
publicKey: keys.publicKeyJwk
|
|
1570
1079
|
});
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1080
|
+
if ("runnerId" in redeemed) {
|
|
1081
|
+
const credential = { agentId: redeemed.agentIdentityId, hostId: redeemed.credentialHostId, ...keys };
|
|
1082
|
+
if (credentialFile !== undefined) {
|
|
1083
|
+
yield* writeRunnerCredentialFile(credentialFile, { apiUrl, runnerId: redeemed.runnerId, ...credential });
|
|
1084
|
+
return yield* step("Runner credential", "set_up", `${credentialFile}; start each replica with kitman runner start --credential-file ${credentialFile}`);
|
|
1085
|
+
}
|
|
1086
|
+
yield* storeAgentCredential(apiUrl, herdrRunnerCredentialName(redeemed.runnerId), credential);
|
|
1087
|
+
yield* writeUserConfig({ ...yield* readUserConfig(), apiUrl });
|
|
1088
|
+
yield* step("Runner", "set_up", redeemed.runnerId);
|
|
1089
|
+
return yield* runnerServiceStep(apiUrl, redeemed.runnerId, { fresh: true, keyed: true }, step);
|
|
1090
|
+
}
|
|
1091
|
+
const pairing = yield* storeRedeemedSession(apiUrl, redeemed);
|
|
1092
|
+
if (credentialFile !== undefined)
|
|
1093
|
+
return yield* new CliError({
|
|
1094
|
+
message: "This Pairing Code signed you in here and pairs no Runner, so there is no Runner credential to write. Run kitman pair to pair this machine as you, or mint a code with Pair a shared Runner."
|
|
1095
|
+
});
|
|
1096
|
+
yield* pairAsOwner(apiUrl, pairing, "set_up", step);
|
|
1582
1097
|
});
|
|
1583
1098
|
}
|
|
1099
|
+
var pairAsOwner = (apiUrl, pairing, login, step) => gen(function* () {
|
|
1100
|
+
yield* step("Login", login, apiUrl);
|
|
1101
|
+
yield* runnerStep(yield* clientFor(apiUrl, pairing.session), { apiUrl, ...pairing }, step);
|
|
1102
|
+
yield* adoptRunnerServices(apiUrl);
|
|
1103
|
+
const detected = yield* detectHarnesses2;
|
|
1104
|
+
if (detected.harnesses.length === 0)
|
|
1105
|
+
return yield* step("Harness", "skipped", `none logged in: ${detected.unavailable.map((harness) => harness.missing.join(", ")).join("; ")}`);
|
|
1106
|
+
const harnesses = detected.harnesses.map((harness) => harness === "claude" ? "claude-code" : harness);
|
|
1107
|
+
yield* harnessSync(["sync", ...harnesses]);
|
|
1108
|
+
yield* step("Harness", "set_up", `${harnesses.join(", ")} synced`);
|
|
1109
|
+
});
|
|
1110
|
+
var credentialFileFlag = (args) => {
|
|
1111
|
+
const at = args.indexOf("--credential-file");
|
|
1112
|
+
if (at === -1)
|
|
1113
|
+
return { credentialFile: undefined, rest: args };
|
|
1114
|
+
const credentialFile = args[at + 1];
|
|
1115
|
+
if (!credentialFile)
|
|
1116
|
+
throw new Error("--credential-file requires a path");
|
|
1117
|
+
return { credentialFile, rest: [...args.slice(0, at), ...args.slice(at + 2)] };
|
|
1118
|
+
};
|
|
1119
|
+
var storeRedeemedSession = (apiUrl, credentials) => gen(function* () {
|
|
1120
|
+
const session = toStoredSession({
|
|
1121
|
+
access_token: credentials.accessToken,
|
|
1122
|
+
...credentials.refreshToken === undefined ? undefined : { refresh_token: credentials.refreshToken },
|
|
1123
|
+
...credentials.expiresIn === undefined ? undefined : { expires_in: credentials.expiresIn }
|
|
1124
|
+
});
|
|
1125
|
+
yield* storeSession(apiUrl, session);
|
|
1126
|
+
yield* writeUserConfig({ ...yield* readUserConfig(), apiUrl, activeWorkspaceId: credentials.workspaceId });
|
|
1127
|
+
return { session, workspaceId: credentials.workspaceId, userId: credentials.userId };
|
|
1128
|
+
});
|
|
1129
|
+
var storedPairing = (apiUrl) => gen(function* () {
|
|
1130
|
+
const session = yield* sessionForApiWithRefresh(apiUrl);
|
|
1131
|
+
if (session === undefined)
|
|
1132
|
+
return yield* new CliError({
|
|
1133
|
+
message: `kitman pair needs a Pairing Code: nobody is signed in to ${apiUrl} here. Copy the pairing command from the Runners page in Kitman; it carries a new code.`
|
|
1134
|
+
});
|
|
1135
|
+
const me = yield* (yield* clientFor(apiUrl, session)).whoami();
|
|
1136
|
+
if (me.actorType !== "human")
|
|
1137
|
+
return yield* new CliError({ message: "kitman pair needs a person signed in" });
|
|
1138
|
+
const workspaceId = (yield* readUserConfig())?.activeWorkspaceId ?? me.activeWorkspaceId ?? me.workspaceId;
|
|
1139
|
+
if (workspaceId === undefined)
|
|
1140
|
+
return yield* new CliError({ message: "No active Workspace. Run kitman workspace use <workspace>." });
|
|
1141
|
+
return { session, workspaceId, userId: me.userId };
|
|
1142
|
+
});
|
|
1584
1143
|
var parseInitFlags = (args) => {
|
|
1585
1144
|
const flags = { yes: false };
|
|
1586
1145
|
for (let index = 0;index < args.length; index += 1) {
|
|
@@ -1605,54 +1164,59 @@ var pickWorkspace = (workspaces, ask) => gen(function* () {
|
|
|
1605
1164
|
return workspaces.find((workspace) => workspace.slug === answer || workspace.name === answer) ?? first;
|
|
1606
1165
|
});
|
|
1607
1166
|
var matchesRemote = (binding, remoteUrl) => binding.status === "active" && repositoryKey(binding.repositoryUrl ?? `https://${binding.host}/${binding.owner}/${binding.name}`) === repositoryKey(remoteUrl);
|
|
1608
|
-
var projectFor = (client, workspaceId, remoteUrl, repositoryName, configured) => gen(function* () {
|
|
1609
|
-
const projects = (yield* client.listProjects(workspaceId)).items;
|
|
1610
|
-
const known = projects.find((project) => project.id === configured);
|
|
1611
|
-
if (known !== undefined)
|
|
1612
|
-
return { id: known.id, name: known.name, outcome: "already" };
|
|
1613
|
-
const boundHere = yield* findFirst(projects, (project) => client.listRepoBindings(project.id).pipe(map3((bindings) => bindings.items.some((binding) => matchesRemote(binding, remoteUrl))), orElseSucceed(() => false)));
|
|
1614
|
-
const found = getOrUndefined(boundHere) ?? projects.find((project) => project.slug === repositoryName || project.name === repositoryName);
|
|
1615
|
-
if (found === undefined) {
|
|
1616
|
-
const created = yield* client.createProject(workspaceId, { name: repositoryName });
|
|
1617
|
-
return { id: created.id, name: created.name, outcome: "set_up" };
|
|
1618
|
-
}
|
|
1619
|
-
return { id: found.id, name: found.name, outcome: "set_up" };
|
|
1620
|
-
});
|
|
1621
1167
|
var runnerStep = (client, input, step) => gen(function* () {
|
|
1622
1168
|
const name = yield* machineName;
|
|
1623
1169
|
const runners = (yield* client.listRunners(input.workspaceId)).items;
|
|
1624
|
-
const
|
|
1625
|
-
const
|
|
1170
|
+
const listed = runners.find((runner) => runner.ownership.kind === "personal" && runner.ownership.ownerUserId === input.userId && runner.name === name && runner.status === "active");
|
|
1171
|
+
const check = listed === undefined ? undefined : yield* herdrRunnerCredentialCheck(input.apiUrl, listed.id);
|
|
1172
|
+
const runner = listed ?? (yield* client.registerRunner(input.workspaceId, {
|
|
1626
1173
|
name,
|
|
1627
1174
|
capabilities: herdrRunnerCapabilities2({ harnesses: yield* detectHarnesses2 }),
|
|
1628
1175
|
labels: ["kitman-runner", "kitman-loop", "herdr"]
|
|
1629
1176
|
})).runner;
|
|
1630
|
-
const
|
|
1631
|
-
if (
|
|
1632
|
-
yield*
|
|
1633
|
-
yield* step("Runner",
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1177
|
+
const keyed = check !== "accepted";
|
|
1178
|
+
if (keyed)
|
|
1179
|
+
yield* provisionHerdrRunnerCredential({ apiUrl: input.apiUrl, accessToken: input.session.accessToken, runner });
|
|
1180
|
+
yield* step("Runner", check === undefined ? "set_up" : keyed ? "repaired" : "already", `${name}${check === undefined ? "" : credentialNote[check]}`);
|
|
1181
|
+
yield* runnerServiceStep(input.apiUrl, runner.id, { fresh: listed === undefined, keyed }, step);
|
|
1182
|
+
});
|
|
1183
|
+
var runnerServiceStep = (apiUrl, runnerId, change, step) => gen(function* () {
|
|
1184
|
+
const manager = yield* ServiceManager;
|
|
1185
|
+
if (manager.platform === "linux" || manager.platform === "darwin") {
|
|
1186
|
+
const managed = (yield* runningManagedInstall(manager.platform)) ? "running" : yield* installManagedKitman(apiUrl, manager.platform);
|
|
1187
|
+
if (managed === undefined)
|
|
1188
|
+
return yield* step("Runner service", "skipped", `${apiUrl} serves no kitman to install; start it yourself with kitman runner start --runner ${runnerId}`);
|
|
1189
|
+
if (managed !== "running")
|
|
1190
|
+
yield* step("kitman", managed.installed ? "set_up" : "already", managed.version);
|
|
1191
|
+
}
|
|
1192
|
+
const spec = yield* managedRunnerService(runnerId, apiUrl);
|
|
1193
|
+
const service = yield* runnerService(spec);
|
|
1641
1194
|
if (service === "unsupported")
|
|
1642
|
-
yield* step("Runner service", "skipped", `start it yourself with kitman runner start ${
|
|
1643
|
-
|
|
1644
|
-
yield* step("Runner service",
|
|
1195
|
+
return yield* step("Runner service", "skipped", `start it yourself with kitman runner start ${runnerId}`);
|
|
1196
|
+
if (service === "running" && !change.keyed)
|
|
1197
|
+
return yield* step("Runner service", "already", "running");
|
|
1198
|
+
if (service === "running")
|
|
1199
|
+
yield* restartRunnerService(spec.name);
|
|
1200
|
+
yield* step("Runner service", change.fresh ? "set_up" : "repaired", "started");
|
|
1645
1201
|
});
|
|
1202
|
+
var credentialNote = { missing: ", credential restored", refused: ", credential re-keyed", accepted: "" };
|
|
1646
1203
|
var machineName = map3(option2(String3("KITMAN_RUNNER_NAME")), (name) => getOrElse(name, () => hostname()));
|
|
1647
|
-
var
|
|
1648
|
-
const
|
|
1649
|
-
|
|
1650
|
-
|
|
1204
|
+
var managedRunnerService = (runnerId, apiUrl) => gen(function* () {
|
|
1205
|
+
const kitman = yield* managedKitmanPath;
|
|
1206
|
+
const bin = (yield* Path).dirname(kitman);
|
|
1207
|
+
const path = getOrElse(yield* option2(String3("PATH")), () => "/usr/local/bin:/usr/bin:/bin");
|
|
1208
|
+
return {
|
|
1209
|
+
name: runnerServiceName(runnerId),
|
|
1210
|
+
command: [kitman, "runner", "start", "--runner", runnerId, "--api-url", apiUrl],
|
|
1211
|
+
workingDirectory: yield* String3("HOME"),
|
|
1212
|
+
path: [bin, ...path.split(":").filter((entry) => entry !== bin)].join(":")
|
|
1213
|
+
};
|
|
1214
|
+
});
|
|
1651
1215
|
var readText = (path) => flatMap2(FileSystem, (fs) => fs.readFileString(path)).pipe(orElseSucceed(() => {
|
|
1652
1216
|
return;
|
|
1653
1217
|
}));
|
|
1654
|
-
var verificationStep = (client,
|
|
1655
|
-
const approved = (yield* client.listApprovedVerifications(
|
|
1218
|
+
var verificationStep = (client, workspaceId, binding, confirm, step) => gen(function* () {
|
|
1219
|
+
const approved = (yield* client.listApprovedVerifications(workspaceId, binding.id)).items;
|
|
1656
1220
|
const suggested = (yield* scriptChecks).filter((check) => !approved.some((entry) => entry.name === check.name));
|
|
1657
1221
|
if (suggested.length === 0)
|
|
1658
1222
|
return yield* step("Verifications", approved.length === 0 ? "skipped" : "already", approved.length === 0 ? "no check scripts found; add one with kitman verify add" : approved.map((check) => check.name).join(", "));
|
|
@@ -1661,7 +1225,7 @@ var verificationStep = (client, projectId, binding, confirm, step) => gen(functi
|
|
|
1661
1225
|
if (!(yield* confirm("Approve these verifications as evidence for every Run", true)))
|
|
1662
1226
|
return yield* step("Verifications", "skipped", "not approved; run kitman init again or kitman verify add");
|
|
1663
1227
|
for (const check of suggested)
|
|
1664
|
-
yield* client.approveVerification(
|
|
1228
|
+
yield* client.approveVerification(workspaceId, binding.id, check);
|
|
1665
1229
|
yield* step("Verifications", "set_up", suggested.map((check) => check.name).join(", "));
|
|
1666
1230
|
});
|
|
1667
1231
|
var scriptChecks = gen(function* () {
|
|
@@ -1683,13 +1247,15 @@ var systemdUnit = (spec) => [
|
|
|
1683
1247
|
`Environment="PATH=${spec.path}"`,
|
|
1684
1248
|
"Restart=on-failure",
|
|
1685
1249
|
"RestartSec=5",
|
|
1250
|
+
"RestartSteps=10",
|
|
1251
|
+
"RestartMaxDelaySec=300",
|
|
1686
1252
|
"",
|
|
1687
1253
|
"[Install]",
|
|
1688
1254
|
"WantedBy=default.target",
|
|
1689
1255
|
""
|
|
1690
1256
|
].join(`
|
|
1691
1257
|
`);
|
|
1692
|
-
var launchdPlist = (label, spec) => [
|
|
1258
|
+
var launchdPlist = (label, spec, log) => [
|
|
1693
1259
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
1694
1260
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
1695
1261
|
'<plist version="1.0"><dict>',
|
|
@@ -1697,8 +1263,10 @@ var launchdPlist = (label, spec) => [
|
|
|
1697
1263
|
`<key>ProgramArguments</key><array>${spec.command.map((word) => `<string>${escapeXml(word)}</string>`).join("")}</array>`,
|
|
1698
1264
|
`<key>WorkingDirectory</key><string>${escapeXml(spec.workingDirectory)}</string>`,
|
|
1699
1265
|
`<key>EnvironmentVariables</key><dict><key>PATH</key><string>${escapeXml(spec.path)}</string></dict>`,
|
|
1266
|
+
`<key>StandardOutPath</key><string>${escapeXml(log)}</string>`,
|
|
1267
|
+
`<key>StandardErrorPath</key><string>${escapeXml(log)}</string>`,
|
|
1700
1268
|
"<key>RunAtLoad</key><true/>",
|
|
1701
|
-
"<key>KeepAlive</key><
|
|
1269
|
+
"<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>",
|
|
1702
1270
|
"</dict></plist>",
|
|
1703
1271
|
""
|
|
1704
1272
|
].join(`
|
|
@@ -1706,6 +1274,44 @@ var launchdPlist = (label, spec) => [
|
|
|
1706
1274
|
var escapeXml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1707
1275
|
var runnerServiceName = (runnerId) => `kitman-runner-${runnerId}`;
|
|
1708
1276
|
var launchdLabel = (name) => `dev.kitman.${name}`;
|
|
1277
|
+
var runnerServiceFile = (platform, name) => gen(function* () {
|
|
1278
|
+
const path = yield* Path;
|
|
1279
|
+
const home = yield* String3("HOME");
|
|
1280
|
+
return platform === "darwin" ? path.join(home, "Library", "LaunchAgents", `${launchdLabel(name)}.plist`) : path.join(home, ".config", "systemd", "user", `${name}.service`);
|
|
1281
|
+
});
|
|
1282
|
+
var runnerServiceLog = (name) => gen(function* () {
|
|
1283
|
+
const path = yield* Path;
|
|
1284
|
+
return path.join(yield* String3("HOME"), "Library", "Logs", "kitman", `${name}.log`);
|
|
1285
|
+
});
|
|
1286
|
+
var restartRunnerService = (name) => gen(function* () {
|
|
1287
|
+
const manager = yield* ServiceManager;
|
|
1288
|
+
if (manager.platform === "darwin")
|
|
1289
|
+
return yield* manager.run("launchctl", ["kickstart", "-k", `${yield* launchdDomain}/${launchdLabel(name)}`]);
|
|
1290
|
+
return yield* manager.run("systemctl", ["--user", "restart", `${name}.service`]);
|
|
1291
|
+
});
|
|
1292
|
+
var showRunnerServiceLog = (name, follow) => gen(function* () {
|
|
1293
|
+
const manager = yield* ServiceManager;
|
|
1294
|
+
if (manager.platform === "darwin")
|
|
1295
|
+
return yield* manager.attach("tail", ["-n", "200", ...follow ? ["-f"] : [], yield* runnerServiceLog(name)]);
|
|
1296
|
+
return yield* manager.attach("journalctl", [
|
|
1297
|
+
"--user",
|
|
1298
|
+
"--unit",
|
|
1299
|
+
`${name}.service`,
|
|
1300
|
+
...follow ? ["--follow"] : ["--no-pager"]
|
|
1301
|
+
]);
|
|
1302
|
+
});
|
|
1303
|
+
var removeRunnerService = (name) => gen(function* () {
|
|
1304
|
+
const fs = yield* FileSystem;
|
|
1305
|
+
const manager = yield* ServiceManager;
|
|
1306
|
+
const file = yield* runnerServiceFile(manager.platform, name);
|
|
1307
|
+
if (manager.platform === "darwin")
|
|
1308
|
+
yield* ignore(manager.run("launchctl", ["bootout", `${yield* launchdDomain}/${launchdLabel(name)}`]));
|
|
1309
|
+
else
|
|
1310
|
+
yield* ignore(manager.run("systemctl", ["--user", "disable", "--now", `${name}.service`]));
|
|
1311
|
+
yield* fs.remove(file, { force: true });
|
|
1312
|
+
if (manager.platform !== "darwin")
|
|
1313
|
+
yield* ignore(manager.run("systemctl", ["--user", "daemon-reload"]));
|
|
1314
|
+
});
|
|
1709
1315
|
var launchdDomain = gen(function* () {
|
|
1710
1316
|
const manager = yield* ServiceManager;
|
|
1711
1317
|
return `gui/${(yield* manager.run("id", ["-u"])).trim()}`;
|
|
@@ -1725,7 +1331,6 @@ function runnerService(spec) {
|
|
|
1725
1331
|
const fs = yield* FileSystem;
|
|
1726
1332
|
const path = yield* Path;
|
|
1727
1333
|
const manager = yield* ServiceManager;
|
|
1728
|
-
const home = yield* String3("HOME");
|
|
1729
1334
|
const define = (file, content) => gen(function* () {
|
|
1730
1335
|
if ((yield* readText(file)) === content)
|
|
1731
1336
|
return false;
|
|
@@ -1735,7 +1340,7 @@ function runnerService(spec) {
|
|
|
1735
1340
|
});
|
|
1736
1341
|
if (manager.platform === "linux") {
|
|
1737
1342
|
const unit = `${spec.name}.service`;
|
|
1738
|
-
const changed = yield* define(
|
|
1343
|
+
const changed = yield* define(yield* runnerServiceFile(manager.platform, spec.name), systemdUnit(spec));
|
|
1739
1344
|
if (!changed && (yield* runnerServiceState(spec.name)) === "systemd: active")
|
|
1740
1345
|
return "running";
|
|
1741
1346
|
yield* manager.run("systemctl", ["--user", "daemon-reload"]);
|
|
@@ -1745,8 +1350,10 @@ function runnerService(spec) {
|
|
|
1745
1350
|
}
|
|
1746
1351
|
if (manager.platform === "darwin") {
|
|
1747
1352
|
const label = launchdLabel(spec.name);
|
|
1748
|
-
const file =
|
|
1749
|
-
const
|
|
1353
|
+
const file = yield* runnerServiceFile(manager.platform, spec.name);
|
|
1354
|
+
const log = yield* runnerServiceLog(spec.name);
|
|
1355
|
+
yield* fs.makeDirectory(path.dirname(log), { recursive: true });
|
|
1356
|
+
const changed = yield* define(file, launchdPlist(label, spec, log));
|
|
1750
1357
|
if (!changed && (yield* runnerServiceState(spec.name)) === "launchd: running")
|
|
1751
1358
|
return "running";
|
|
1752
1359
|
const domain = yield* launchdDomain;
|
|
@@ -1787,7 +1394,7 @@ var package_default = {
|
|
|
1787
1394
|
};
|
|
1788
1395
|
|
|
1789
1396
|
// apps/cli/src/scaffold.ts
|
|
1790
|
-
var cliVersion = "0.
|
|
1397
|
+
var cliVersion = "0.8.0";
|
|
1791
1398
|
var packageJson = (name, scaffold) => `${JSON.stringify({
|
|
1792
1399
|
name,
|
|
1793
1400
|
private: true,
|
|
@@ -1897,7 +1504,7 @@ var readme = (name, scaffold) => [
|
|
|
1897
1504
|
"1. `bun install`, then `bun test`.",
|
|
1898
1505
|
"2. `bunx kitman deploy --plan` shows what deploying would change.",
|
|
1899
1506
|
"3. Push this repository to GitLab, then let its CI deploy the default branch: a Workspace owner or admin runs",
|
|
1900
|
-
" `bunx kitman deploy bind --branch main` (add `--
|
|
1507
|
+
" `bunx kitman deploy bind --branch main` (add `--team <team>` to deploy to one Team).",
|
|
1901
1508
|
"4. Merge requests print the plan, and merging deploys. `.gitlab-ci.yml` names the Workspace to deploy to.",
|
|
1902
1509
|
""
|
|
1903
1510
|
].join(`
|
|
@@ -1907,7 +1514,7 @@ var scaffoldPackage = (directory, scaffold = {}) => gen(function* () {
|
|
|
1907
1514
|
const path = yield* Path;
|
|
1908
1515
|
const root = path.resolve(directory);
|
|
1909
1516
|
if (scaffold.fork !== undefined) {
|
|
1910
|
-
const { shippedLoops } = yield* promise(() => import("./chunk-
|
|
1517
|
+
const { shippedLoops } = yield* promise(() => import("./chunk-64et31rh.js"));
|
|
1911
1518
|
if (!(scaffold.fork in shippedLoops))
|
|
1912
1519
|
return yield* new CliError({
|
|
1913
1520
|
message: `Kitman ships no Loop ${scaffold.fork}; fork one of ${Object.keys(shippedLoops).join(", ")}`
|
|
@@ -1939,30 +1546,38 @@ var scaffoldPackage = (directory, scaffold = {}) => gen(function* () {
|
|
|
1939
1546
|
// apps/cli/src/commands.ts
|
|
1940
1547
|
var ClientId = "kitman-cli";
|
|
1941
1548
|
var Usage = [
|
|
1942
|
-
"Usage: kitman <init|pair|run|inbox|loop|agent|deploy|package|verify|secret|constraint|mcp|sync|whoami|config|kit|task|memory|proposal|decision|context|schedule|delivery|reconcile|lifecycle|logout> [options]",
|
|
1549
|
+
"Usage: kitman <init|pair|run|inbox|loop|agent|deploy|package|verify|secret|team|repo|constraint|mcp|sync|whoami|config|kit|task|memory|proposal|decision|context|schedule|delivery|reconcile|lifecycle|logout> [options]",
|
|
1943
1550
|
"",
|
|
1944
1551
|
"Set up, in a repository (run it again to repair what is missing):",
|
|
1945
1552
|
" kitman init [--yes] [--api-url <url>]",
|
|
1946
|
-
" kitman pair <pairing code> [--api-url <url>] (what the command the web shows runs)",
|
|
1553
|
+
" kitman pair [<pairing code>] [--api-url <url>] (what the command the web shows runs; no code finishes or re-keys with your session)",
|
|
1554
|
+
" kitman pair <runner pairing code> --credential-file <path> (writes a shared Runner's credential for a container or its replicas to mount)",
|
|
1555
|
+
" kitman runner start --credential-file <path> (or KITMAN_RUNNER_CREDENTIAL_FILE; starts the Runner the file holds)",
|
|
1947
1556
|
"",
|
|
1948
1557
|
"Runs:",
|
|
1949
|
-
' kitman run start <loop> "<words>" [--input <json>] [--
|
|
1558
|
+
' kitman run start <loop> "<words>" [--input <json>] [--team <team>] [--type <task type>] [--constraint <text>]...',
|
|
1950
1559
|
" kitman run show <run>",
|
|
1951
1560
|
" kitman run intent <run> [--set-goal <text>] [--add-constraint <text>] [--remove-constraint <id>] [--corrections on|off]",
|
|
1952
1561
|
" kitman run drift <run> [--dismiss <constraint> --reason <text>]",
|
|
1953
|
-
" kitman run
|
|
1562
|
+
" kitman run files <run> [--step <step>]",
|
|
1563
|
+
" kitman run file get <run> <id> [--out <path>]",
|
|
1564
|
+
" kitman run list [--team <id>] [--loop <loop>] [--state <state>[,<state>\u2026]] [--limit <n>]",
|
|
1954
1565
|
" kitman run wait <run> [--until attention|finished]",
|
|
1955
1566
|
' kitman run answer <run> "<option or words>" [--question <name>]',
|
|
1956
1567
|
' kitman run steer <run> "<note for the agent>"',
|
|
1568
|
+
" kitman run follow <run> (prints its agents' Transcripts as they grow, until the Run settles)",
|
|
1569
|
+
" kitman transcript <run> [--step <step>] [--attempt <n>]",
|
|
1957
1570
|
" kitman run stop <run>",
|
|
1958
1571
|
" kitman run resume <run>",
|
|
1959
1572
|
" kitman run close <run> --complete|--abandoned [--reason <text>]",
|
|
1960
1573
|
" kitman run actions <run>",
|
|
1961
1574
|
" kitman run action <run> <action>",
|
|
1575
|
+
" kitman task assign <task> --to <person> (by email, name or id)",
|
|
1576
|
+
" kitman task assign <task> --loop <loop> (starts a Run of the Loop on the Task; you stay its owner)",
|
|
1962
1577
|
" kitman inbox [--unread|--snoozed|--done]",
|
|
1963
|
-
" kitman inbox my-teams [--
|
|
1964
|
-
" kitman inbox read|unread|done|undone|unsnooze <task:id|run:id|proposal:id>...",
|
|
1965
|
-
" kitman inbox snooze <task:id|run:id|proposal:id>... --until <time>",
|
|
1578
|
+
" kitman inbox my-teams [--team <team>]",
|
|
1579
|
+
" kitman inbox read|unread|done|undone|unsnooze <task:id|run:id|proposal:id|project_update:id>...",
|
|
1580
|
+
" kitman inbox snooze <task:id|run:id|proposal:id|project_update:id>... --until <time>",
|
|
1966
1581
|
" kitman inbox unsubscribe <task>...",
|
|
1967
1582
|
"",
|
|
1968
1583
|
"",
|
|
@@ -1984,24 +1599,37 @@ var Usage = [
|
|
|
1984
1599
|
' kitman constraint add "<constraint>"',
|
|
1985
1600
|
" kitman constraint remove <constraint>",
|
|
1986
1601
|
"",
|
|
1987
|
-
"Secrets, for the Workspace or with --
|
|
1988
|
-
" <command> | kitman secret set <name> [--
|
|
1989
|
-
" kitman secret delete <name> [--
|
|
1990
|
-
" kitman secret list [--
|
|
1602
|
+
"Secrets, for the Workspace or with --team for one Team; values are write-only:",
|
|
1603
|
+
" <command> | kitman secret set <name> [--team <team>] (reads the value from stdin)",
|
|
1604
|
+
" kitman secret delete <name> [--team <team>]",
|
|
1605
|
+
" kitman secret list [--team <team>]",
|
|
1606
|
+
" kitman secret move <name> [--team <team>] --to <team>|workspace",
|
|
1607
|
+
"",
|
|
1608
|
+
"A private Team, and all it holds, is seen by its members alone:",
|
|
1609
|
+
" kitman team private <team>",
|
|
1610
|
+
" kitman team open <team>",
|
|
1611
|
+
" kitman team invite <team> <person>",
|
|
1991
1612
|
"",
|
|
1992
|
-
"
|
|
1993
|
-
" kitman
|
|
1994
|
-
|
|
1995
|
-
" kitman
|
|
1996
|
-
" kitman
|
|
1997
|
-
" kitman
|
|
1613
|
+
"Repositories, Memory and Decisions belong to the Workspace or one Team:",
|
|
1614
|
+
" kitman repo list",
|
|
1615
|
+
" kitman repo add [--from-current-repo] [--team <team>]",
|
|
1616
|
+
" kitman repo move <repository> --to <team>|workspace",
|
|
1617
|
+
" kitman memory move <memory> --to <team>|workspace",
|
|
1618
|
+
" kitman decision move <decision> --to <team>|workspace",
|
|
1619
|
+
"",
|
|
1620
|
+
"Packages, built from the current directory and deployed to the Workspace or with --team to one Team:",
|
|
1621
|
+
" kitman deploy [--plan] [--team <team>] (--plan prints what would change; deploying needs a clean tree)",
|
|
1622
|
+
' kitman dev <loop> "<words>" [--input <json>] [--team <team>] (runs the working tree on your personal Runner)',
|
|
1623
|
+
" kitman package list [--team <team>]",
|
|
1624
|
+
" kitman package show <name> [--team <team>]",
|
|
1625
|
+
" kitman package remove <name> [--team <team>] [--yes] (prints the plan; removes only with --yes)",
|
|
1998
1626
|
"",
|
|
1999
1627
|
"Every run, loop, agent, verify, constraint, secret, deploy, dev and package command takes --json."
|
|
2000
1628
|
].join(`
|
|
2001
1629
|
`);
|
|
2002
1630
|
var KitCreateValidator = guard(KitCreate);
|
|
2003
1631
|
var KitUpdateValidator = guard(KitUpdate);
|
|
2004
|
-
var agentRuntime = promise(() => import("./chunk-
|
|
1632
|
+
var agentRuntime = promise(() => import("./chunk-m6yymqfr.js"));
|
|
2005
1633
|
var TuiLauncher = Reference("kitman/cli/TuiLauncher", {
|
|
2006
1634
|
defaultValue: () => gen(function* () {
|
|
2007
1635
|
const tui = yield* tryPromise({
|
|
@@ -2023,6 +1651,8 @@ function runEffect(args) {
|
|
|
2023
1651
|
yield* log(Usage);
|
|
2024
1652
|
return;
|
|
2025
1653
|
}
|
|
1654
|
+
if (args[0] === "--version")
|
|
1655
|
+
return yield* log(kitmanVersion ?? "dev");
|
|
2026
1656
|
const [command, ...rest] = args;
|
|
2027
1657
|
if (command === "init" && rest[0] === "--package")
|
|
2028
1658
|
return yield* scaffoldPackage(rest[1] ?? ".");
|
|
@@ -2040,8 +1670,10 @@ function runEffect(args) {
|
|
|
2040
1670
|
return yield* configCommand(args);
|
|
2041
1671
|
if (command === "workspace")
|
|
2042
1672
|
return yield* workspaceCommand(args);
|
|
2043
|
-
if (command === "
|
|
2044
|
-
return yield*
|
|
1673
|
+
if (command === "team")
|
|
1674
|
+
return yield* teamCommand(args);
|
|
1675
|
+
if (command === "repo")
|
|
1676
|
+
return yield* repoCommand(args);
|
|
2045
1677
|
if (command === "runner")
|
|
2046
1678
|
return yield* runnerCommand(args);
|
|
2047
1679
|
if (command === "mcp")
|
|
@@ -2064,6 +1696,8 @@ function runEffect(args) {
|
|
|
2064
1696
|
return yield* agentCommand(args);
|
|
2065
1697
|
if (command === "run")
|
|
2066
1698
|
return yield* runCommand(args);
|
|
1699
|
+
if (command === "transcript")
|
|
1700
|
+
return yield* transcriptCommand(["transcript", "read", ...rest]);
|
|
2067
1701
|
if (command === "inbox")
|
|
2068
1702
|
return yield* inboxCommand(args);
|
|
2069
1703
|
if (command === "verify")
|
|
@@ -2099,6 +1733,10 @@ var isInteractiveTty = gen(function* () {
|
|
|
2099
1733
|
});
|
|
2100
1734
|
var tuiModulePath = "./tui/index";
|
|
2101
1735
|
var solidTransformModulePath = "@opentui/solid/preload";
|
|
1736
|
+
var renamedOptions = new Map([
|
|
1737
|
+
["--project", "--team"],
|
|
1738
|
+
["--project-access", "--team-access"]
|
|
1739
|
+
]);
|
|
2102
1740
|
function parseCommand(args) {
|
|
2103
1741
|
const group = args[0];
|
|
2104
1742
|
const action = args[1];
|
|
@@ -2179,18 +1817,16 @@ function parseCommand(args) {
|
|
|
2179
1817
|
if (flags.closure !== undefined && flags.closure !== kind)
|
|
2180
1818
|
throw new Error("run close takes one of --complete or --abandoned");
|
|
2181
1819
|
flags.closure = kind;
|
|
2182
|
-
} else if (arg === "--
|
|
2183
|
-
flags.
|
|
2184
|
-
else if (arg === "--runner") {
|
|
1820
|
+
} else if (arg === "--team" && group === "sync")
|
|
1821
|
+
flags.teamScope = true;
|
|
1822
|
+
else if (arg === "--runner" || arg === "--credential-file") {
|
|
2185
1823
|
const value = args[++index];
|
|
2186
1824
|
if (!value)
|
|
2187
1825
|
throw new Error(`${arg} requires a value`);
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
throw new Error("--agent-identity requires a value");
|
|
2193
|
-
flags.agentIdentityId = value;
|
|
1826
|
+
if (arg === "--runner")
|
|
1827
|
+
flags.runnerId = value;
|
|
1828
|
+
else
|
|
1829
|
+
flags.credentialFile = value;
|
|
2194
1830
|
} else if (arg === "--iterations") {
|
|
2195
1831
|
if (group === "runner" && action === "start")
|
|
2196
1832
|
throw new Error("runner start accepts only a Runner identity");
|
|
@@ -2198,6 +1834,11 @@ function parseCommand(args) {
|
|
|
2198
1834
|
if (!value)
|
|
2199
1835
|
throw new Error("--iterations requires a value");
|
|
2200
1836
|
flags.iterations = parsePositiveIntegerFlag("--iterations", value);
|
|
1837
|
+
} else if (arg === "--attempt") {
|
|
1838
|
+
const value = args[++index];
|
|
1839
|
+
if (!value)
|
|
1840
|
+
throw new Error("--attempt requires a value");
|
|
1841
|
+
flags.attempt = parsePositiveIntegerFlag("--attempt", value);
|
|
2201
1842
|
} else if (arg === "--poll-interval-ms") {
|
|
2202
1843
|
if (group === "runner" && action === "start")
|
|
2203
1844
|
throw new Error("runner start accepts only a Runner identity");
|
|
@@ -2268,7 +1909,7 @@ function parseCommand(args) {
|
|
|
2268
1909
|
if (!value)
|
|
2269
1910
|
throw new Error("--capabilities requires a value");
|
|
2270
1911
|
flags.capabilities = validateAgentCapabilities(value);
|
|
2271
|
-
} else if (arg === "--name" || arg === "--title" || arg === "--description" || arg === "--summary" || arg === "--rationale" || arg === "--kind" || arg === "--content" || arg === "--source" || arg === "--content-type" || arg === "--filename" || arg === "--session" || arg === "--
|
|
1912
|
+
} else if (arg === "--name" || arg === "--title" || arg === "--description" || arg === "--summary" || arg === "--rationale" || arg === "--kind" || arg === "--content" || arg === "--source" || arg === "--content-type" || arg === "--filename" || arg === "--session" || arg === "--team" || arg === "--workspace" || arg === "--branch" || arg === "--team" || arg === "--type" || arg === "--status" || arg === "--loop" || arg === "--state" || arg === "--reason" || arg === "--dismiss" || arg === "--action" || arg === "--step-key" || arg === "--step" || arg === "--risk" || arg === "--target-type" || arg === "--scope-type" || arg === "--scope-id" || arg === "--team-access" || arg === "--provider" || arg === "--task" || arg === "--input" || arg === "--idempotency-key" || arg === "--key" || arg === "--after" || arg === "--version" || arg === "--alias" || arg === "--profile" || arg === "--event" || arg === "--cascade" || arg === "--run" || arg === "--run-id" || arg === "--out" || arg === "--replica" || arg === "--work-unit-kind" || arg === "--host" || arg === "--owner" || arg === "--repository-url" || arg === "--default-branch" || arg === "--expected-branch" || arg === "--expected-commit-sha" || arg === "--credential" || arg === "--to") {
|
|
2272
1913
|
const value = args[++index];
|
|
2273
1914
|
if (!value)
|
|
2274
1915
|
throw new Error(`${arg} requires a value`);
|
|
@@ -2294,25 +1935,27 @@ function parseCommand(args) {
|
|
|
2294
1935
|
flags.filename = value;
|
|
2295
1936
|
else if (arg === "--session")
|
|
2296
1937
|
flags.sessionId = value;
|
|
2297
|
-
else if (arg === "--
|
|
2298
|
-
flags.
|
|
1938
|
+
else if (arg === "--team")
|
|
1939
|
+
flags.team = value;
|
|
2299
1940
|
else if (arg === "--workspace")
|
|
2300
1941
|
flags.workspace = value;
|
|
2301
1942
|
else if (arg === "--branch")
|
|
2302
1943
|
flags.branch = value;
|
|
2303
|
-
else if (arg === "--team")
|
|
2304
|
-
flags.team = value;
|
|
2305
1944
|
else if (arg === "--type")
|
|
2306
1945
|
flags.taskType = value;
|
|
2307
1946
|
else if (arg === "--status")
|
|
2308
1947
|
flags.status = value;
|
|
1948
|
+
else if (arg === "--loop")
|
|
1949
|
+
flags.loop = value;
|
|
1950
|
+
else if (arg === "--state")
|
|
1951
|
+
flags.states = validateRunDisplayStates(value);
|
|
2309
1952
|
else if (arg === "--reason")
|
|
2310
1953
|
flags.reason = value;
|
|
2311
1954
|
else if (arg === "--dismiss")
|
|
2312
1955
|
flags.dismiss = value;
|
|
2313
1956
|
else if (arg === "--action")
|
|
2314
1957
|
flags.fixFlowAction = validateFixFlowAction(value);
|
|
2315
|
-
else if (arg === "--step-key")
|
|
1958
|
+
else if (arg === "--step-key" || arg === "--step")
|
|
2316
1959
|
flags.stepKey = value;
|
|
2317
1960
|
else if (arg === "--target-type")
|
|
2318
1961
|
flags.targetType = validateProposalTargetType(value);
|
|
@@ -2320,10 +1963,10 @@ function parseCommand(args) {
|
|
|
2320
1963
|
flags.scopeType = value;
|
|
2321
1964
|
else if (arg === "--scope-id")
|
|
2322
1965
|
flags.scopeId = value;
|
|
2323
|
-
else if (arg === "--
|
|
1966
|
+
else if (arg === "--team-access") {
|
|
2324
1967
|
if (value !== "all" && value !== "selected")
|
|
2325
|
-
throw new Error("--
|
|
2326
|
-
flags.
|
|
1968
|
+
throw new Error("--team-access must be all or selected");
|
|
1969
|
+
flags.teamAccess = value;
|
|
2327
1970
|
} else if (arg === "--provider")
|
|
2328
1971
|
flags.provider = validateRepoBindingProvider(value);
|
|
2329
1972
|
else if (arg === "--task")
|
|
@@ -2348,6 +1991,8 @@ function parseCommand(args) {
|
|
|
2348
1991
|
flags.runId = value;
|
|
2349
1992
|
else if (arg === "--run-id")
|
|
2350
1993
|
flags.runId = value;
|
|
1994
|
+
else if (arg === "--out")
|
|
1995
|
+
flags.out = value;
|
|
2351
1996
|
else if (arg === "--replica")
|
|
2352
1997
|
flags.replicaId = value;
|
|
2353
1998
|
else if (arg === "--work-unit-kind")
|
|
@@ -2364,9 +2009,13 @@ function parseCommand(args) {
|
|
|
2364
2009
|
flags.expectedBranch = value;
|
|
2365
2010
|
else if (arg === "--expected-commit-sha")
|
|
2366
2011
|
flags.expectedCommitSha = value;
|
|
2012
|
+
else if (arg === "--to")
|
|
2013
|
+
flags.to = value;
|
|
2367
2014
|
else
|
|
2368
2015
|
flags.credentialRefs = [...flags.credentialRefs ?? [], parseRepoBindingCredentialRef(value)];
|
|
2369
|
-
} else if (arg
|
|
2016
|
+
} else if (arg !== undefined && renamedOptions.has(arg))
|
|
2017
|
+
throw new Error(`${arg} is now ${renamedOptions.get(arg)}`);
|
|
2018
|
+
else if (arg?.startsWith("--"))
|
|
2370
2019
|
throw new Error(`Unknown option: ${arg}`);
|
|
2371
2020
|
else if (arg)
|
|
2372
2021
|
values.push(arg);
|
|
@@ -2382,7 +2031,7 @@ function syncCommand(args) {
|
|
|
2382
2031
|
materializationPlans,
|
|
2383
2032
|
configPlans,
|
|
2384
2033
|
userConfigPlans,
|
|
2385
|
-
|
|
2034
|
+
teamConfigPlans,
|
|
2386
2035
|
syncKits,
|
|
2387
2036
|
outputPath,
|
|
2388
2037
|
applied: shouldApply
|
|
@@ -2399,8 +2048,8 @@ function syncCommand(args) {
|
|
|
2399
2048
|
else {
|
|
2400
2049
|
yield* log(`Harness sync ${shouldApply ? "applied" : "preview"}.`);
|
|
2401
2050
|
yield* log(`Workspace: ${plan.workspaceId}`);
|
|
2402
|
-
if (plan.
|
|
2403
|
-
yield* log(`
|
|
2051
|
+
if (plan.teamId)
|
|
2052
|
+
yield* log(`Team: ${plan.teamId}`);
|
|
2404
2053
|
yield* log(`Harnesses: ${plan.harnesses.map((item) => item.harness).join(", ")}`);
|
|
2405
2054
|
yield* log(`Secrets: ${plan.secrets}`);
|
|
2406
2055
|
yield* log(`Plan path: ${outputPath}`);
|
|
@@ -2412,43 +2061,45 @@ function syncCommand(args) {
|
|
|
2412
2061
|
for (const configPlan of userConfigPlans) {
|
|
2413
2062
|
yield* log(`${configPlan.harness} user writes: ${configPlan.operations.map((operation) => operation.path).join(", ") || "none"}`);
|
|
2414
2063
|
}
|
|
2415
|
-
for (const configPlan of
|
|
2416
|
-
yield* log(`${configPlan.harness}
|
|
2064
|
+
for (const configPlan of teamConfigPlans) {
|
|
2065
|
+
yield* log(`${configPlan.harness} team writes: ${configPlan.operations.map((operation) => operation.path).join(", ") || "none"}`);
|
|
2417
2066
|
}
|
|
2418
2067
|
}
|
|
2419
2068
|
});
|
|
2420
2069
|
}
|
|
2421
|
-
function harnessSync(args) {
|
|
2070
|
+
function harnessSync(args, runnerClient) {
|
|
2422
2071
|
const command = parseSyncCommand(args);
|
|
2423
2072
|
return gen(function* () {
|
|
2424
2073
|
const { applyAgentEnvironmentPlans, composeAgentEnvironmentSyncPlan, resolveAgentEnvironmentFromSources } = yield* agentRuntime;
|
|
2425
2074
|
const { config } = yield* resolveCommandConfig(command.flags);
|
|
2426
2075
|
if (!config.activeWorkspaceId)
|
|
2427
2076
|
return yield* new CliError({ message: "No active workspace. Run kitman workspace use <workspace>." });
|
|
2428
|
-
if (command.flags.
|
|
2077
|
+
if (command.flags.teamScope && !config.activeTeamId)
|
|
2429
2078
|
return yield* new CliError({
|
|
2430
|
-
message: "No active
|
|
2079
|
+
message: "No active team. Run kitman team use <team> before team sync."
|
|
2431
2080
|
});
|
|
2432
|
-
const
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2081
|
+
const activeTeamId = command.flags.teamScope ? config.activeTeamId : undefined;
|
|
2082
|
+
const client = runnerClient ?? (yield* gen(function* () {
|
|
2083
|
+
const session = yield* sessionForApiWithRefresh(config.apiUrl);
|
|
2084
|
+
if (!session)
|
|
2085
|
+
return yield* new CliError({ message: `Not logged in for ${config.apiUrl}. Run kitman login.` });
|
|
2086
|
+
return yield* clientFor(config.apiUrl, session);
|
|
2087
|
+
}));
|
|
2437
2088
|
const outputPath = yield* userHarnessSyncPath;
|
|
2438
2089
|
const workspaceId = config.activeWorkspaceId;
|
|
2439
2090
|
const selectedHarnesses = syncTargetHarnesses(command, config);
|
|
2440
2091
|
const workspaceKits = yield* client.listKits(workspaceId).pipe(catchIf((error) => error.status === 404, () => succeed5({ items: Array() })));
|
|
2441
|
-
const
|
|
2092
|
+
const teamKits = activeTeamId === undefined ? { items: Array() } : yield* client.listTeamKits(activeTeamId, { workspaceId });
|
|
2442
2093
|
const userPreferences = config.userAgentPreferences;
|
|
2443
2094
|
const syncKits = selectedHarnesses.map((harness) => resolveSyncKit({
|
|
2444
2095
|
defaultHarness: harness,
|
|
2445
2096
|
activeKits: syncKitActiveKits({
|
|
2446
2097
|
workspaceId,
|
|
2447
|
-
...
|
|
2098
|
+
...activeTeamId === undefined ? undefined : { teamId: activeTeamId },
|
|
2448
2099
|
...userPreferences === undefined ? undefined : { userPreferences },
|
|
2449
2100
|
harnesses: [harness],
|
|
2450
2101
|
workspaceKits: workspaceKits.items,
|
|
2451
|
-
|
|
2102
|
+
teamKits: teamKits.items
|
|
2452
2103
|
})
|
|
2453
2104
|
}));
|
|
2454
2105
|
const blockedSyncKit = syncKits.find((syncKit) => syncKit.status === "blocked");
|
|
@@ -2456,13 +2107,13 @@ function harnessSync(args) {
|
|
|
2456
2107
|
return yield* new CliError({ message: `Sync Kit is blocked: ${blockedSyncKit.blockers.join("; ")}` });
|
|
2457
2108
|
const fragments = syncFragments({
|
|
2458
2109
|
workspaceId,
|
|
2459
|
-
...
|
|
2110
|
+
...activeTeamId === undefined ? undefined : { teamId: activeTeamId },
|
|
2460
2111
|
userId: "default",
|
|
2461
2112
|
workspaceKits: workspaceKits.items,
|
|
2462
|
-
|
|
2113
|
+
teamKits: teamKits.items
|
|
2463
2114
|
});
|
|
2464
|
-
const syncProfile = syncProfileForCommand(command, config, workspaceId,
|
|
2465
|
-
const
|
|
2115
|
+
const syncProfile = syncProfileForCommand(command, config, workspaceId, activeTeamId);
|
|
2116
|
+
const teamContext = activeTeamId === undefined ? undefined : { workspaceId, teamId: activeTeamId };
|
|
2466
2117
|
const environments = selectedHarnesses.map((harness) => {
|
|
2467
2118
|
const environment = resolveAgentEnvironmentFromSources({
|
|
2468
2119
|
userId: "default",
|
|
@@ -2470,27 +2121,27 @@ function harnessSync(args) {
|
|
|
2470
2121
|
...userPreferences === undefined ? undefined : { userPreferences },
|
|
2471
2122
|
syncProfile,
|
|
2472
2123
|
fragments,
|
|
2473
|
-
...
|
|
2124
|
+
...teamContext === undefined ? undefined : { teamContext }
|
|
2474
2125
|
});
|
|
2475
2126
|
return {
|
|
2476
2127
|
...environment,
|
|
2477
2128
|
warnings: [
|
|
2478
2129
|
...environment.warnings,
|
|
2479
|
-
...(
|
|
2130
|
+
...(teamKits.policyOptOuts ?? []).map((optOut) => `Workspace-default Kit ${optOut.kitId} revision ${optOut.policyRevision} is opted out for this Team.`)
|
|
2480
2131
|
]
|
|
2481
2132
|
};
|
|
2482
2133
|
});
|
|
2483
|
-
const
|
|
2484
|
-
const targetPlans = yield* previewEnvironmentPlans(environments, command,
|
|
2134
|
+
const teamRoot = command.flags.teamScope ? yield* resolveTeamWriteRoot : undefined;
|
|
2135
|
+
const targetPlans = yield* previewEnvironmentPlans(environments, command, teamRoot, config.mcpCredentialName, syncKits, (yield* readUserConfig()) ?? {});
|
|
2485
2136
|
const environmentPlan = composeAgentEnvironmentSyncPlan(targetPlans);
|
|
2486
2137
|
const materializationPlans = targetPlans.map((target) => target.skills);
|
|
2487
|
-
const userConfigPlans = command.flags.
|
|
2488
|
-
const
|
|
2489
|
-
const configPlans = [...userConfigPlans, ...
|
|
2138
|
+
const userConfigPlans = command.flags.teamScope ? [] : targetPlans.map((target) => target.harnessConfig);
|
|
2139
|
+
const teamConfigPlans = teamRoot === undefined ? [] : targetPlans.map((target) => target.harnessConfig);
|
|
2140
|
+
const configPlans = [...userConfigPlans, ...teamConfigPlans];
|
|
2490
2141
|
const plan = buildHarnessSyncPlan({
|
|
2491
2142
|
apiUrl: config.apiUrl,
|
|
2492
2143
|
workspaceId,
|
|
2493
|
-
...
|
|
2144
|
+
...activeTeamId === undefined ? undefined : { teamId: activeTeamId },
|
|
2494
2145
|
harnesses: selectedHarnesses,
|
|
2495
2146
|
outputPath,
|
|
2496
2147
|
materializationPlans,
|
|
@@ -2508,7 +2159,7 @@ function harnessSync(args) {
|
|
|
2508
2159
|
materializationPlans: resultPlan.targets.map((target) => target.skills),
|
|
2509
2160
|
configPlans,
|
|
2510
2161
|
userConfigPlans,
|
|
2511
|
-
|
|
2162
|
+
teamConfigPlans,
|
|
2512
2163
|
syncKits,
|
|
2513
2164
|
outputPath,
|
|
2514
2165
|
applied
|
|
@@ -2541,12 +2192,12 @@ function syncTargetHarnesses(command, config) {
|
|
|
2541
2192
|
return [config.userAgentPreferences.defaultHarness];
|
|
2542
2193
|
return AgentHarnessIds;
|
|
2543
2194
|
}
|
|
2544
|
-
function syncProfileForCommand(command, config, workspaceId,
|
|
2195
|
+
function syncProfileForCommand(command, config, workspaceId, teamId) {
|
|
2545
2196
|
const harnesses = [...syncTargetHarnesses(command, config)];
|
|
2546
2197
|
const existing = config.agentSyncProfile;
|
|
2547
2198
|
const sources = existing?.sources ?? [
|
|
2548
2199
|
{ kind: "personal_kit_defaults", enabled: true },
|
|
2549
|
-
...
|
|
2200
|
+
...teamId === undefined ? [] : [{ kind: "team_kits", teamId, enabled: true }]
|
|
2550
2201
|
];
|
|
2551
2202
|
return {
|
|
2552
2203
|
id: existing?.id ?? "default",
|
|
@@ -2559,7 +2210,7 @@ function syncProfileForCommand(command, config, workspaceId, projectId) {
|
|
|
2559
2210
|
}
|
|
2560
2211
|
function syncKitActiveKits(input) {
|
|
2561
2212
|
const legacyKit = (kit, scope, index) => {
|
|
2562
|
-
const active = scope === "
|
|
2213
|
+
const active = scope === "team" ? true : isInstallDefaultKit(kit) || input.harnesses.some((harness) => kitEnabledForHarness(input.userPreferences, harness, kit.id));
|
|
2563
2214
|
const metadata = kit.metadata ?? {};
|
|
2564
2215
|
const version = isString2(metadata.version) && metadata.version.length > 0 ? metadata.version : "legacy";
|
|
2565
2216
|
return {
|
|
@@ -2568,11 +2219,11 @@ function syncKitActiveKits(input) {
|
|
|
2568
2219
|
kitRef: `kit:${kit.slug}`,
|
|
2569
2220
|
version,
|
|
2570
2221
|
sha256: digestSha256(encodeUnknownJsonStringSync(kit), "hex"),
|
|
2571
|
-
precedence: scope === "
|
|
2222
|
+
precedence: scope === "team" ? 20 + index : 10 + index
|
|
2572
2223
|
},
|
|
2573
2224
|
activation: {
|
|
2574
2225
|
activationId: `legacy:${kit.id}:${scope}`,
|
|
2575
|
-
activationType: scope === "
|
|
2226
|
+
activationType: scope === "team" ? "team_attachment" : "personal_default"
|
|
2576
2227
|
},
|
|
2577
2228
|
enabled: active,
|
|
2578
2229
|
gates: kit.agentEnvironment === undefined ? [] : [
|
|
@@ -2586,7 +2237,7 @@ function syncKitActiveKits(input) {
|
|
|
2586
2237
|
};
|
|
2587
2238
|
return [
|
|
2588
2239
|
...input.workspaceKits.map((kit, index) => legacyKit(kit, "workspace", index)),
|
|
2589
|
-
...input.
|
|
2240
|
+
...input.teamId === undefined ? [] : input.teamKits.map((kit, index) => legacyKit(kit, "team", index))
|
|
2590
2241
|
];
|
|
2591
2242
|
}
|
|
2592
2243
|
function syncFragments(input) {
|
|
@@ -2607,27 +2258,27 @@ function syncFragments(input) {
|
|
|
2607
2258
|
fragment: kit.agentEnvironment
|
|
2608
2259
|
});
|
|
2609
2260
|
}
|
|
2610
|
-
for (const kit of input.
|
|
2611
|
-
if (kit.agentEnvironment === undefined || input.
|
|
2261
|
+
for (const kit of input.teamKits) {
|
|
2262
|
+
if (kit.agentEnvironment === undefined || input.teamId === undefined)
|
|
2612
2263
|
continue;
|
|
2613
2264
|
fragments.push({
|
|
2614
|
-
sourceKind: "
|
|
2265
|
+
sourceKind: "team_kit",
|
|
2615
2266
|
workspaceId: input.workspaceId,
|
|
2616
|
-
|
|
2267
|
+
teamId: input.teamId,
|
|
2617
2268
|
source: {
|
|
2618
|
-
kind: "
|
|
2269
|
+
kind: "team_kit",
|
|
2619
2270
|
kitId: kit.id,
|
|
2620
2271
|
label: kit.name,
|
|
2621
|
-
...
|
|
2622
|
-
...kitSourceMetadata(kit, "
|
|
2272
|
+
...teamKitPolicyMetadata(kit),
|
|
2273
|
+
...kitSourceMetadata(kit, "team_attachment")
|
|
2623
2274
|
},
|
|
2624
2275
|
fragment: kit.agentEnvironment
|
|
2625
2276
|
});
|
|
2626
2277
|
}
|
|
2627
2278
|
return fragments;
|
|
2628
2279
|
}
|
|
2629
|
-
function
|
|
2630
|
-
const attachment = kit.
|
|
2280
|
+
function teamKitPolicyMetadata(kit) {
|
|
2281
|
+
const attachment = kit.teamAttachment;
|
|
2631
2282
|
if (attachment === undefined)
|
|
2632
2283
|
return {};
|
|
2633
2284
|
return {
|
|
@@ -2652,14 +2303,14 @@ function isInstallDefaultKit(kit) {
|
|
|
2652
2303
|
const rollout = isObject(marketplace) && marketplace !== null && "rollout" in marketplace ? marketplace.rollout : undefined;
|
|
2653
2304
|
return isObject(rollout) && rollout !== null && "installDefault" in rollout && rollout.installDefault === true;
|
|
2654
2305
|
}
|
|
2655
|
-
function previewEnvironmentPlans(environments, command,
|
|
2306
|
+
function previewEnvironmentPlans(environments, command, teamRoot, mcpCredentialName, syncKits = [], userConfig = {}) {
|
|
2656
2307
|
const previews = forEach(environments, (environment, index) => gen(function* () {
|
|
2657
|
-
const rootDir = command.flags.
|
|
2308
|
+
const rootDir = command.flags.teamScope ? teamRoot : getOrUndefined(yield* option2(String3("HOME")));
|
|
2658
2309
|
const { previewAgentEnvironmentPlan } = yield* agentRuntime;
|
|
2659
2310
|
return yield* previewAgentEnvironmentPlan({
|
|
2660
2311
|
environment,
|
|
2661
2312
|
...syncKits[index] === undefined ? undefined : { syncKit: syncKits[index] },
|
|
2662
|
-
scope: command.flags.
|
|
2313
|
+
scope: command.flags.teamScope ? "project" : "user",
|
|
2663
2314
|
...rootDir === undefined ? undefined : { rootDir },
|
|
2664
2315
|
...command.flags.noInstall === undefined ? undefined : { noInstall: command.flags.noInstall },
|
|
2665
2316
|
...userConfig.approvedSkillSources === undefined ? undefined : { approvedSkillSources: userConfig.approvedSkillSources },
|
|
@@ -2806,14 +2457,14 @@ function mixedKitState(kit, harnesses, preferences) {
|
|
|
2806
2457
|
return "disabled";
|
|
2807
2458
|
return "mixed";
|
|
2808
2459
|
}
|
|
2809
|
-
var
|
|
2460
|
+
var resolveTeamWriteRoot = gen(function* () {
|
|
2810
2461
|
const fs = yield* FileSystem;
|
|
2811
2462
|
const path = yield* Path;
|
|
2812
2463
|
const cwd = path.resolve(".");
|
|
2813
2464
|
if ((yield* fs.exists(path.join(cwd, ".git"))) || (yield* fs.exists(path.join(cwd, ".kitman/config.json"))))
|
|
2814
2465
|
return cwd;
|
|
2815
2466
|
return yield* new CliError({
|
|
2816
|
-
message: "
|
|
2467
|
+
message: "Team sync requires a repo root or local team config in the current working directory."
|
|
2817
2468
|
});
|
|
2818
2469
|
});
|
|
2819
2470
|
function parseHarnesses(values) {
|
|
@@ -2940,7 +2591,7 @@ function whoami(args) {
|
|
|
2940
2591
|
user: "userId" in me ? me.userId : me.actorId,
|
|
2941
2592
|
apiUrl,
|
|
2942
2593
|
activeWorkspace: me.activeWorkspaceId ?? config.activeWorkspaceId ?? me.workspaceId ?? null,
|
|
2943
|
-
|
|
2594
|
+
activeTeam: config.activeTeamId ?? null,
|
|
2944
2595
|
workspaceRole: me.workspaceRole ?? null,
|
|
2945
2596
|
credentialType: me.credentialType,
|
|
2946
2597
|
tokenExpires: session.expiresAt ?? null
|
|
@@ -2952,7 +2603,7 @@ function whoami(args) {
|
|
|
2952
2603
|
yield* log(`User: ${output.user}`);
|
|
2953
2604
|
yield* log(`API URL: ${output.apiUrl}`);
|
|
2954
2605
|
yield* log(`Active workspace: ${output.activeWorkspace ?? "none"}`);
|
|
2955
|
-
yield* log(`Active
|
|
2606
|
+
yield* log(`Active team: ${output.activeTeam ?? "none"}`);
|
|
2956
2607
|
yield* log(`Workspace role: ${output.workspaceRole ?? "unknown"}`);
|
|
2957
2608
|
yield* log(`Credential type: ${output.credentialType}`);
|
|
2958
2609
|
yield* log(`Token expires: ${output.tokenExpires ?? "unknown"}`);
|
|
@@ -2968,7 +2619,7 @@ function configCommand(args) {
|
|
|
2968
2619
|
const output = {
|
|
2969
2620
|
apiUrl: normalizeResolvedApiUrl(config.apiUrl),
|
|
2970
2621
|
activeWorkspace: config.activeWorkspaceId ?? null,
|
|
2971
|
-
|
|
2622
|
+
activeTeam: config.activeTeamId ?? null,
|
|
2972
2623
|
sources: resolved.sources,
|
|
2973
2624
|
userConfigPath: resolved.userConfigPath,
|
|
2974
2625
|
...resolved.localConfigPath ? { localConfigPath: resolved.localConfigPath } : undefined
|
|
@@ -2982,8 +2633,8 @@ function configCommand(args) {
|
|
|
2982
2633
|
"API URL": output.apiUrl,
|
|
2983
2634
|
"Active workspace": output.activeWorkspace ?? "none",
|
|
2984
2635
|
"Active workspace source": output.sources.activeWorkspace,
|
|
2985
|
-
"Active
|
|
2986
|
-
"Active
|
|
2636
|
+
"Active team": output.activeTeam ?? "none",
|
|
2637
|
+
"Active team source": output.sources.activeTeam,
|
|
2987
2638
|
"API URL source": output.sources.apiUrl,
|
|
2988
2639
|
"User config path": output.userConfigPath,
|
|
2989
2640
|
...output.localConfigPath ? { "Local config path": output.localConfigPath } : undefined
|
|
@@ -3028,52 +2679,80 @@ function workspaceCommand(args) {
|
|
|
3028
2679
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
3029
2680
|
});
|
|
3030
2681
|
}
|
|
3031
|
-
function
|
|
2682
|
+
function teamCommand(args) {
|
|
3032
2683
|
return gen(function* () {
|
|
3033
2684
|
const command = parseCommand(args);
|
|
3034
2685
|
const apiUrl = yield* resolveApiUrl(command.flags.apiUrl);
|
|
3035
|
-
const
|
|
3036
|
-
const
|
|
3037
|
-
if (command.action === "list")
|
|
3038
|
-
|
|
3039
|
-
const projects = yield* client.listProjects(workspaceId);
|
|
3040
|
-
return yield* printRows(projects.items.map(projectRow), command.flags.json);
|
|
3041
|
-
}
|
|
3042
|
-
if (command.action === "create") {
|
|
3043
|
-
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
3044
|
-
const name = requiredArg(command, "project name");
|
|
3045
|
-
const project = yield* client.createProject(workspaceId, { name });
|
|
3046
|
-
yield* writeActiveProject(apiUrl, project.workspaceId, project.id);
|
|
3047
|
-
return yield* printRows([projectRow(project)], command.flags.json);
|
|
3048
|
-
}
|
|
3049
|
-
if (command.action === "show") {
|
|
3050
|
-
const projectRef = requiredArg(command, "project");
|
|
3051
|
-
const workspaceId = yield* optionalActiveWorkspaceId(command.flags);
|
|
3052
|
-
const project = yield* client.getProject(projectRef, workspaceId ? { workspaceId } : undefined);
|
|
3053
|
-
return yield* printRows([projectRow(project)], command.flags.json);
|
|
3054
|
-
}
|
|
2686
|
+
const client = yield* clientFor(apiUrl, yield* requiredSessionForApi(apiUrl));
|
|
2687
|
+
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
2688
|
+
if (command.action === "list")
|
|
2689
|
+
return yield* printRows((yield* client.listTeams(workspaceId)).items.map(teamRow), command.flags.json);
|
|
3055
2690
|
if (command.action === "use") {
|
|
3056
|
-
const
|
|
3057
|
-
|
|
3058
|
-
const project = yield* client.getProject(projectRef, workspaceId ? { workspaceId } : undefined);
|
|
3059
|
-
yield* writeActiveProject(apiUrl, project.workspaceId, project.id, command.flags.local);
|
|
2691
|
+
const team = yield* teamNamed(client, workspaceId, requiredArg(command, "team"));
|
|
2692
|
+
yield* writeActiveTeam(apiUrl, workspaceId, team.id, command.flags.local);
|
|
3060
2693
|
if (command.flags.json)
|
|
3061
|
-
return yield* printRows([
|
|
3062
|
-
yield* log(`Active
|
|
2694
|
+
return yield* printRows([teamRow(team)], true);
|
|
2695
|
+
yield* log(`Active team: ${team.name}`);
|
|
3063
2696
|
return;
|
|
3064
2697
|
}
|
|
3065
|
-
if (command.action === "
|
|
3066
|
-
const
|
|
3067
|
-
const
|
|
2698
|
+
if (command.action === "private" || command.action === "open") {
|
|
2699
|
+
const team = yield* teamNamed(client, workspaceId, requiredArg(command, "team"));
|
|
2700
|
+
const visibility = command.action === "private" ? "private" : "workspace";
|
|
2701
|
+
yield* client.updateTeam(workspaceId, team.id, { visibility });
|
|
2702
|
+
return yield* log(visibility === "private" ? `${team.key} is private: only its members see it` : `${team.key} is open to the Workspace`);
|
|
2703
|
+
}
|
|
2704
|
+
if (command.action === "invite") {
|
|
2705
|
+
const team = yield* teamNamed(client, workspaceId, requiredArg(command, "team"));
|
|
2706
|
+
const userId = command.args[1];
|
|
2707
|
+
if (userId === undefined)
|
|
2708
|
+
return yield* new CliError({ message: "team invite requires <team> <person>" });
|
|
2709
|
+
yield* client.addTeamMember(workspaceId, team.id, { userId });
|
|
2710
|
+
return yield* log(`Invited ${userId} to ${team.key}`);
|
|
2711
|
+
}
|
|
2712
|
+
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
var teamIn = (teams, ref) => {
|
|
2716
|
+
const team = teams.find((candidate) => candidate.id === ref || candidate.key === ref.toUpperCase());
|
|
2717
|
+
return team === undefined ? fail4(new CliError({ message: `No Team ${ref} in the Workspace` })) : succeed5(team);
|
|
2718
|
+
};
|
|
2719
|
+
var teamNamed = (client, workspaceId, ref) => flatMap2(client.listTeams(workspaceId), (teams) => teamIn(teams.items, ref));
|
|
2720
|
+
var ownerTo = (client, workspaceId, command) => gen(function* () {
|
|
2721
|
+
const to = command.flags.to;
|
|
2722
|
+
if (to === undefined)
|
|
2723
|
+
return yield* new CliError({ message: `${command.group} move requires --to <team> or --to workspace` });
|
|
2724
|
+
if (to === "workspace")
|
|
2725
|
+
return { change: { team: null }, label: "the Workspace" };
|
|
2726
|
+
const team = yield* teamNamed(client, workspaceId, to);
|
|
2727
|
+
return { change: { team: team.id }, label: team.key };
|
|
2728
|
+
});
|
|
2729
|
+
var printMoved = (command, moved, label) => command.flags.json ? printJson(moved) : log(`Moved ${moved.id} to ${label}`);
|
|
2730
|
+
function repoCommand(args) {
|
|
2731
|
+
return gen(function* () {
|
|
2732
|
+
const command = parseCommand(args);
|
|
2733
|
+
const apiUrl = yield* resolveApiUrl(command.flags.apiUrl);
|
|
2734
|
+
const client = yield* clientFor(apiUrl, yield* requiredSessionForApi(apiUrl));
|
|
2735
|
+
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
2736
|
+
const teams = (yield* client.listTeams(workspaceId)).items;
|
|
2737
|
+
const ownerOf = (teamId) => teams.find((team) => team.id === teamId)?.key ?? "Workspace";
|
|
2738
|
+
if (command.action === "list") {
|
|
2739
|
+
const repoBindings = yield* client.listRepoBindings(workspaceId);
|
|
3068
2740
|
if (command.flags.json)
|
|
3069
2741
|
return yield* printJson(repoBindings);
|
|
3070
|
-
return yield* printRows(repoBindings.items.map(repoBindingRow), false);
|
|
2742
|
+
return yield* printRows(repoBindings.items.map((binding) => ({ ...repoBindingRow(binding), team: ownerOf(binding.teamId) })), false);
|
|
3071
2743
|
}
|
|
3072
|
-
if (command.action === "add
|
|
3073
|
-
const
|
|
2744
|
+
if (command.action === "add") {
|
|
2745
|
+
const team = command.flags.team === undefined ? undefined : yield* teamIn(teams, command.flags.team);
|
|
3074
2746
|
const input = command.flags.fromCurrentRepo === true ? yield* repoBindingInputFromCurrentRepo : repoBindingInput2(command);
|
|
3075
|
-
const repoBinding = yield* client.createRepoBinding(
|
|
3076
|
-
|
|
2747
|
+
const repoBinding = yield* client.createRepoBinding(workspaceId, {
|
|
2748
|
+
...input,
|
|
2749
|
+
...team === undefined ? undefined : { teamId: team.id }
|
|
2750
|
+
});
|
|
2751
|
+
return yield* printRows([{ ...repoBindingRow(repoBinding), team: ownerOf(repoBinding.teamId) }], command.flags.json);
|
|
2752
|
+
}
|
|
2753
|
+
if (command.action === "move") {
|
|
2754
|
+
const owner = yield* ownerTo(client, workspaceId, command);
|
|
2755
|
+
return yield* printMoved(command, yield* client.moveRepoBinding(workspaceId, requiredArg(command, "a repository"), owner.change), owner.label);
|
|
3077
2756
|
}
|
|
3078
2757
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
3079
2758
|
});
|
|
@@ -3104,7 +2783,30 @@ function runnerCommand(args) {
|
|
|
3104
2783
|
yield* log(`Runner bootstrap prepared for ${output.profile}; no canonical state was created.`);
|
|
3105
2784
|
return;
|
|
3106
2785
|
}
|
|
2786
|
+
if (command.action === "start") {
|
|
2787
|
+
if (command.flags.backend !== undefined)
|
|
2788
|
+
return yield* new CliError({ message: "runner start accepts only a Runner identity" });
|
|
2789
|
+
const teamId = command.flags.team === undefined ? undefined : { teamId: command.flags.team };
|
|
2790
|
+
const credentialFile = command.flags.credentialFile ?? getOrUndefined(yield* option2(String3("KITMAN_RUNNER_CREDENTIAL_FILE")));
|
|
2791
|
+
if (credentialFile !== undefined)
|
|
2792
|
+
return yield* startCanonicalHerdrRunnerCommand({
|
|
2793
|
+
...yield* readRunnerCredentialFile(credentialFile),
|
|
2794
|
+
...teamId
|
|
2795
|
+
});
|
|
2796
|
+
return yield* startCanonicalHerdrRunnerCommand({
|
|
2797
|
+
apiUrl: yield* resolveApiUrl(command.flags.apiUrl),
|
|
2798
|
+
runnerId: command.flags.runnerId ?? requiredArg(command, "Runner"),
|
|
2799
|
+
...teamId
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
3107
2802
|
const apiUrl = yield* resolveApiUrl(command.flags.apiUrl);
|
|
2803
|
+
if (command.action === "status" || command.action === "logs" || command.action === "restart" || command.action === "remove") {
|
|
2804
|
+
const runnerId = command.flags.runnerId ?? command.args[0];
|
|
2805
|
+
return yield* machineRunnerCommand(command.action, apiUrl, {
|
|
2806
|
+
follow: command.flags.follow === true,
|
|
2807
|
+
...runnerId === undefined ? undefined : { runnerId }
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
3108
2810
|
const session = yield* requiredSessionForApi(apiUrl);
|
|
3109
2811
|
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
3110
2812
|
const client = yield* clientFor(apiUrl, session);
|
|
@@ -3144,21 +2846,21 @@ function runnerCommand(args) {
|
|
|
3144
2846
|
return yield* new CliError({ message: "runner grant requires add, list, or remove and --runner <runner-id>" });
|
|
3145
2847
|
const runner = yield* resolveRunnerForCommand(client, workspaceId, runnerRef);
|
|
3146
2848
|
if (grantAction === "list") {
|
|
3147
|
-
const response = yield* client.
|
|
2849
|
+
const response = yield* client.listRunnerTeamGrants(runner.id);
|
|
3148
2850
|
return yield* printRows(response.items.map((grant) => ({
|
|
3149
2851
|
id: grant.id,
|
|
3150
2852
|
runnerId: grant.runnerId,
|
|
3151
2853
|
workspaceId: grant.workspaceId,
|
|
3152
|
-
|
|
2854
|
+
teamId: grant.teamId,
|
|
3153
2855
|
createdAt: grant.createdAt
|
|
3154
2856
|
})), command.flags.json);
|
|
3155
2857
|
}
|
|
3156
2858
|
if (grantAction === "add" || grantAction === "remove") {
|
|
3157
|
-
const
|
|
3158
|
-
if (!
|
|
3159
|
-
return yield* new CliError({ message: `runner grant ${grantAction} requires --
|
|
2859
|
+
const teamId = command.flags.team ?? command.flags.scopeId ?? command.args[2];
|
|
2860
|
+
if (!teamId)
|
|
2861
|
+
return yield* new CliError({ message: `runner grant ${grantAction} requires --team <team-id>` });
|
|
3160
2862
|
if (grantAction === "add") {
|
|
3161
|
-
const response = yield* client.
|
|
2863
|
+
const response = yield* client.addRunnerTeamGrant(runner.id, { teamId });
|
|
3162
2864
|
if (command.flags.json)
|
|
3163
2865
|
return yield* printJson(response);
|
|
3164
2866
|
return yield* printRows([
|
|
@@ -3166,15 +2868,15 @@ function runnerCommand(args) {
|
|
|
3166
2868
|
id: response.id,
|
|
3167
2869
|
runnerId: response.runnerId,
|
|
3168
2870
|
workspaceId: response.workspaceId,
|
|
3169
|
-
|
|
2871
|
+
teamId: response.teamId,
|
|
3170
2872
|
status: "granted"
|
|
3171
2873
|
}
|
|
3172
2874
|
], false);
|
|
3173
2875
|
}
|
|
3174
|
-
const response = yield* client.
|
|
2876
|
+
const response = yield* client.removeRunnerTeamGrant(runner.id, teamId);
|
|
3175
2877
|
if (command.flags.json)
|
|
3176
2878
|
return yield* printJson(response);
|
|
3177
|
-
return yield* printRows([{ runnerId: runner.id,
|
|
2879
|
+
return yield* printRows([{ runnerId: runner.id, teamId, status: response.removed ? "removed" : "not_found" }], false);
|
|
3178
2880
|
}
|
|
3179
2881
|
return yield* new CliError({ message: `Unknown runner grant action: ${grantAction}` });
|
|
3180
2882
|
}
|
|
@@ -3187,9 +2889,8 @@ function runnerCommand(args) {
|
|
|
3187
2889
|
const ownership = runnerOwnershipFromCommand(command, false);
|
|
3188
2890
|
const response = yield* client.registerRunner(workspaceId, {
|
|
3189
2891
|
name,
|
|
3190
|
-
...command.flags.agentIdentityId === undefined ? undefined : { agentIdentityId: command.flags.agentIdentityId },
|
|
3191
2892
|
...ownership === undefined ? undefined : { ownership },
|
|
3192
|
-
...command.flags.
|
|
2893
|
+
...command.flags.team === undefined ? undefined : { teamId: command.flags.team },
|
|
3193
2894
|
capabilities: herdr ? (yield* agentRuntime).herdrRunnerCapabilities({ harnesses: yield* (yield* agentRuntime).detectHarnesses }) : loopRunnerCapabilities(),
|
|
3194
2895
|
labels: herdr ? ["kitman-runner", "kitman-loop", "herdr"] : ["kitman-runner", "kitman-loop"]
|
|
3195
2896
|
});
|
|
@@ -3213,26 +2914,11 @@ function runnerCommand(args) {
|
|
|
3213
2914
|
const runnerRef = command.flags.runnerId ?? requiredArg(command, "Runner");
|
|
3214
2915
|
const runner = yield* resolveRunnerForCommand(client, workspaceId, runnerRef);
|
|
3215
2916
|
const response = yield* client.heartbeatRunner(runner.id, {
|
|
3216
|
-
healthStatus: "healthy",
|
|
3217
2917
|
capabilities: runner.capabilities,
|
|
3218
2918
|
labels: runner.labels
|
|
3219
2919
|
});
|
|
3220
2920
|
return yield* printRows([runnerRow(response)], command.flags.json);
|
|
3221
2921
|
}
|
|
3222
|
-
if (command.action === "start") {
|
|
3223
|
-
if (command.flags.backend !== undefined)
|
|
3224
|
-
return yield* new CliError({ message: "runner start accepts only a Runner identity" });
|
|
3225
|
-
const runnerRef = command.flags.runnerId ?? requiredArg(command, "Runner");
|
|
3226
|
-
const runner = yield* resolveRunnerForCommand(client, workspaceId, runnerRef);
|
|
3227
|
-
return yield* startCanonicalHerdrRunnerCommand({
|
|
3228
|
-
apiUrl,
|
|
3229
|
-
accessToken: session.accessToken,
|
|
3230
|
-
workspaceId,
|
|
3231
|
-
...command.flags.project === undefined ? undefined : { projectId: command.flags.project },
|
|
3232
|
-
runner,
|
|
3233
|
-
client
|
|
3234
|
-
});
|
|
3235
|
-
}
|
|
3236
2922
|
if (command.action === "doctor") {
|
|
3237
2923
|
const runners = yield* client.listRunners(workspaceId);
|
|
3238
2924
|
if (command.flags.runnerId === undefined && command.args.length === 0 && command.flags.backend === undefined)
|
|
@@ -3256,6 +2942,16 @@ function runnerCommand(args) {
|
|
|
3256
2942
|
var SandboxHostProbe = Reference("kitman/cli/SandboxHostProbe", {
|
|
3257
2943
|
defaultValue: () => flatMap2(agentRuntime, (runtime) => runtime.commandHostProbe)
|
|
3258
2944
|
});
|
|
2945
|
+
var OpenRunnerRpc = Reference("kitman/cli/OpenRunnerRpc", {
|
|
2946
|
+
defaultValue: () => (options) => gen(function* () {
|
|
2947
|
+
const { makeRunnerRpcClient, runnerRpcUrl, runnerWebSocketProtocol } = yield* promise(() => import("./chunk-nmgr9w6v.js"));
|
|
2948
|
+
return yield* makeRunnerRpcClient({
|
|
2949
|
+
runnerId: options.runnerId,
|
|
2950
|
+
bearer: options.bearer,
|
|
2951
|
+
protocol: runnerWebSocketProtocol(runnerRpcUrl(options.apiUrl))
|
|
2952
|
+
});
|
|
2953
|
+
})
|
|
2954
|
+
});
|
|
3259
2955
|
var runnerSettings = gen(function* () {
|
|
3260
2956
|
const optional = (name) => String3(name).pipe(option2, map3((value) => getOrUndefined(value)?.trim()));
|
|
3261
2957
|
const concurrency = Number(yield* optional("KITMAN_RUNNER_CONCURRENCY")) || 1;
|
|
@@ -3310,27 +3006,40 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3310
3006
|
superviseReplica,
|
|
3311
3007
|
verificationExecutor
|
|
3312
3008
|
} = yield* agentRuntime;
|
|
3313
|
-
const
|
|
3009
|
+
const openRunnerRpc = yield* OpenRunnerRpc;
|
|
3314
3010
|
const settings = yield* runnerSettings;
|
|
3315
3011
|
const { concurrency, fullAccess, sshAgentAvailable } = settings;
|
|
3316
|
-
const
|
|
3317
|
-
const
|
|
3012
|
+
const fs = yield* FileSystem;
|
|
3013
|
+
const container = input.credential !== undefined || (yield* fs.exists("/.dockerenv").pipe(orElseSucceed(() => false))) || (yield* fs.exists("/run/.containerenv").pipe(orElseSucceed(() => false)));
|
|
3014
|
+
const reported = { ...kitmanVersion === undefined ? undefined : { version: kitmanVersion }, container };
|
|
3015
|
+
const credential = input.credential ?? (yield* startupStep("load the Runner credential", "10 seconds", storedHerdrRunnerCredential(input.apiUrl, input.runnerId)));
|
|
3016
|
+
if (credential === undefined)
|
|
3017
|
+
return yield* new RunnerStopped({ problem: credentialMissing(input.runnerId) });
|
|
3018
|
+
const controlPlane = yield* createHerdrRunnerClient({ apiUrl: input.apiUrl, runnerId: input.runnerId, credential });
|
|
3019
|
+
const runner = yield* startupStep("first heartbeat", "30 seconds", stopOnRefusal(controlPlane.client.heartbeatRunner(input.runnerId, reported)));
|
|
3020
|
+
const personal = runner.ownership.kind === "personal";
|
|
3021
|
+
const host = yield* startupStep("check the sandbox", "30 seconds", probeSandboxHost(yield* yield* SandboxHostProbe, undefined, settings.dockerImage === undefined ? {} : { dockerImage: settings.dockerImage }));
|
|
3318
3022
|
const sandbox = personal ? settings.sandbox : host.backends.includes("docker") ? "docker" : "srt";
|
|
3319
|
-
if (personal && !host.backends.includes(sandbox))
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
});
|
|
3323
|
-
|
|
3023
|
+
if (personal && sandbox !== "host" && !host.backends.includes(sandbox)) {
|
|
3024
|
+
const problem = sandboxMissing(sandbox, host.unavailable.find((entry) => entry.backend === sandbox)?.missing ?? ["unsupported"]);
|
|
3025
|
+
yield* startupStep("report the Runner Problem", "30 seconds", stopOnRefusal(controlPlane.client.heartbeatRunner(input.runnerId, { ...reported, problems: [problem] })));
|
|
3026
|
+
return yield* new RunnerStopped({ problem });
|
|
3027
|
+
}
|
|
3028
|
+
const teamId = input.teamId;
|
|
3029
|
+
const repoBinding = teamId === undefined ? undefined : (yield* startupStep("read the repo binding", "30 seconds", controlPlane.client.listRepoBindings(runner.workspaceId))).items.find((binding) => binding.status === "active" && binding.teamId === teamId);
|
|
3324
3030
|
const harnesses = yield* detectHarnesses;
|
|
3325
3031
|
if (harnesses.harnesses.length > 0)
|
|
3326
3032
|
yield* harnessSync([
|
|
3327
3033
|
"sync",
|
|
3328
3034
|
...harnesses.harnesses.map((harness) => harness === "claude" ? "claude-code" : harness),
|
|
3329
3035
|
"--workspace",
|
|
3330
|
-
|
|
3036
|
+
runner.workspaceId,
|
|
3331
3037
|
"--api-url",
|
|
3332
3038
|
input.apiUrl
|
|
3333
|
-
]).pipe(
|
|
3039
|
+
], controlPlane.client).pipe(timeoutOrElse({
|
|
3040
|
+
duration: "60 seconds",
|
|
3041
|
+
orElse: () => fail4(new CliError({ message: "harness sync did not finish within 60 seconds" }))
|
|
3042
|
+
}), catch_((error2) => error(`Harness sync failed; the Runner starts on the previous one: ${error2.message}`)));
|
|
3334
3043
|
const advertisedCapabilities = repoBinding === undefined ? herdrRunnerCapabilities({ harnesses, fullAccess, sandbox: host, shared: !personal }) : herdrRunnerCapabilitiesForRepoBinding(repoBinding, {
|
|
3335
3044
|
harnesses,
|
|
3336
3045
|
fullAccess,
|
|
@@ -3343,23 +3052,16 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3343
3052
|
resourceLimits: { ...advertisedCapabilities.resourceLimits, maxConcurrentRuns: concurrency }
|
|
3344
3053
|
};
|
|
3345
3054
|
assertHerdrRunnerCapability(capabilities);
|
|
3346
|
-
const credential = yield* ensureHerdrRunnerCredential({
|
|
3347
|
-
apiUrl: input.apiUrl,
|
|
3348
|
-
accessToken: input.accessToken,
|
|
3349
|
-
runner: input.runner
|
|
3350
|
-
});
|
|
3351
|
-
const controlPlane = yield* createHerdrRunnerClient({ apiUrl: input.apiUrl, runnerId: input.runner.id, credential });
|
|
3352
|
-
const fs = yield* FileSystem;
|
|
3353
3055
|
const path = yield* Path;
|
|
3354
3056
|
const runnerRootDir = yield* herdrRunnerRootDir;
|
|
3355
3057
|
yield* fs.makeDirectory(path.join(runnerRootDir, ".kitman", "worktrees"), { recursive: true });
|
|
3356
3058
|
yield* fs.makeDirectory(bundleCacheDir, { recursive: true });
|
|
3357
|
-
const replica = yield* controlPlane.client.registerRunnerReplica(input.
|
|
3059
|
+
const replica = yield* startupStep("register the replica", "30 seconds", stopOnRefusal(controlPlane.client.registerRunnerReplica(input.runnerId, {
|
|
3358
3060
|
name: `replica-${createId()}`,
|
|
3359
3061
|
capacity: { maxConcurrentRuns: concurrency },
|
|
3360
3062
|
telemetry: { activeWorkUnits: 0 }
|
|
3361
|
-
});
|
|
3362
|
-
const backend = herdrExecutorSurface({ runnerId: input.
|
|
3063
|
+
})));
|
|
3064
|
+
const backend = herdrExecutorSurface({ runnerId: input.runnerId, session: personal ? "default" : "own" });
|
|
3363
3065
|
const uid = process.getuid?.();
|
|
3364
3066
|
const gid = process.getgid?.();
|
|
3365
3067
|
const docker = host.backends.includes("docker") && settings.dockerImage !== undefined ? {
|
|
@@ -3370,41 +3072,46 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3370
3072
|
const root = herdrAgentBackendLayer({
|
|
3371
3073
|
client: () => backend.executionClient,
|
|
3372
3074
|
sandboxBackends: host.backends,
|
|
3373
|
-
...docker === undefined ? undefined : { docker: { ...docker, runnerId: input.
|
|
3374
|
-
}).pipe(provide2(herdrHarnessLayer({ client: () => backend.executionClient })), provide2(
|
|
3075
|
+
...docker === undefined ? undefined : { docker: { ...docker, runnerId: input.runnerId } }
|
|
3076
|
+
}).pipe(provide2(herdrHarnessLayer({ client: () => backend.executionClient })), provide2(layer));
|
|
3375
3077
|
const activeSlots = new Set;
|
|
3078
|
+
let claimsInFlight = 0;
|
|
3079
|
+
const updater = runnerUpdater({
|
|
3080
|
+
apiUrl: input.apiUrl,
|
|
3081
|
+
runnerId: input.runnerId,
|
|
3082
|
+
platform: process.platform,
|
|
3083
|
+
inFlight: sync(() => activeSlots.size + claimsInFlight)
|
|
3084
|
+
});
|
|
3376
3085
|
return yield* gen(function* () {
|
|
3377
3086
|
const agentBackend = yield* AgentBackend;
|
|
3378
3087
|
const spawner = yield* ChildProcessSpawner;
|
|
3379
|
-
yield* controlPlane.client.heartbeatRunner(input.runner.id, {
|
|
3380
|
-
healthStatus: "healthy",
|
|
3381
|
-
capabilities,
|
|
3382
|
-
labels: input.runner.labels
|
|
3383
|
-
});
|
|
3384
|
-
yield* controlPlane.client.heartbeatRunnerReplica(input.runner.id, replica.id, {
|
|
3088
|
+
yield* startupStep("heartbeat", "30 seconds", stopOnRefusal(controlPlane.client.heartbeatRunner(input.runnerId, { ...reported, problems: [], capabilities, labels: runner.labels }).pipe(andThen(controlPlane.client.heartbeatRunnerReplica(input.runnerId, replica.id, {
|
|
3385
3089
|
healthStatus: "healthy",
|
|
3386
3090
|
drainState: "accepting",
|
|
3387
3091
|
capacity: { maxConcurrentRuns: concurrency },
|
|
3388
3092
|
telemetry: { activeWorkUnits: 0 }
|
|
3389
|
-
});
|
|
3093
|
+
})))));
|
|
3390
3094
|
return yield* scoped(gen(function* () {
|
|
3391
|
-
const rpc = yield*
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
});
|
|
3095
|
+
const rpc = yield* startupStep("open the Runner RPC", "30 seconds", stopOnRefusal(openRunnerRpc({
|
|
3096
|
+
apiUrl: input.apiUrl,
|
|
3097
|
+
runnerId: input.runnerId,
|
|
3098
|
+
bearer: controlPlane.rpcBearer.pipe(provideService(Crypto, yield* Crypto))
|
|
3099
|
+
}).pipe(tap((opened) => opened.settledRuns([])))));
|
|
3396
3100
|
const signals = yield* makeReplicaSignals;
|
|
3397
3101
|
const bundles = yield* kitmanBundles({
|
|
3398
3102
|
fetch: (digest) => rpc.bundle(digest).pipe(map3((found) => found.bundle))
|
|
3399
3103
|
});
|
|
3400
3104
|
const checkouts = runCheckouts(path.join(runnerRootDir, ".kitman"));
|
|
3401
3105
|
const platform = yield* context();
|
|
3106
|
+
const updated = yield* make5();
|
|
3107
|
+
const scope2 = yield* scope;
|
|
3402
3108
|
yield* listenForSignals({
|
|
3403
3109
|
rpc,
|
|
3404
3110
|
runnerReplicaId: replica.id,
|
|
3405
3111
|
signals,
|
|
3406
3112
|
checkouts,
|
|
3407
|
-
reconnectAfter: "2 seconds"
|
|
3113
|
+
reconnectAfter: "2 seconds",
|
|
3114
|
+
update: (version) => container ? void_2 : updater.update(version).pipe(catch_((error) => fail3(updated, error)), provideContext(platform), forkIn(scope2), asVoid)
|
|
3408
3115
|
}).pipe(forkScoped);
|
|
3409
3116
|
const authStore = yield* userAuthPath;
|
|
3410
3117
|
const runnerState = {
|
|
@@ -3415,7 +3122,7 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3415
3122
|
};
|
|
3416
3123
|
const sharedSandbox = personal ? undefined : {
|
|
3417
3124
|
runner: runnerState,
|
|
3418
|
-
...docker !== undefined ? { backend: "docker", docker, runnerId: input.
|
|
3125
|
+
...docker !== undefined ? { backend: "docker", docker, runnerId: input.runnerId } : { backend: host.backends.includes("srt") ? "srt" : undefined }
|
|
3419
3126
|
};
|
|
3420
3127
|
const runPackageProgram = programProcessExecutor({
|
|
3421
3128
|
bundles,
|
|
@@ -3430,12 +3137,14 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3430
3137
|
preflight: (mcp) => kitmanMcpPreflight(mcp).pipe(provideService(ChildProcessSpawner, spawner))
|
|
3431
3138
|
});
|
|
3432
3139
|
const heartbeatOf = (work) => rpc.heartbeat({ workUnitId: work.id, lease: leaseOf(work) });
|
|
3140
|
+
const storage = yield* provide3(service(HttpClient), layer2);
|
|
3433
3141
|
const runAgentStep = agentStepExecutor({
|
|
3434
3142
|
rootDir: path.join(runnerRootDir, ".kitman"),
|
|
3435
3143
|
heartbeat: heartbeatOf,
|
|
3436
|
-
checkpoints: rpcStepCheckpoints(rpc, signals),
|
|
3437
|
-
|
|
3438
|
-
|
|
3144
|
+
checkpoints: rpcStepCheckpoints(rpc, signals, storage),
|
|
3145
|
+
transcripts: (work, entries) => rpc.appendTranscript({ workUnitId: work.id, entries }),
|
|
3146
|
+
runAgent: (work, worktree, session, steered) => agentBackend.execute({
|
|
3147
|
+
runnerId: input.runnerId,
|
|
3439
3148
|
sharedRunner: !personal,
|
|
3440
3149
|
attempt: runnerStepAttemptFromAgentStep(work),
|
|
3441
3150
|
workspace: { path: worktree },
|
|
@@ -3451,38 +3160,49 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3451
3160
|
settled: heartbeatOf(work).pipe(map3((state) => state.settled || state.outputSubmitted === true)),
|
|
3452
3161
|
steering: {
|
|
3453
3162
|
...signals.steers(work.id),
|
|
3454
|
-
report: (note, delivered) => rpc.reportSteer({ workUnitId: work.id, messageId: note.messageId, delivered }).pipe(catch_((error) => logWarning("runner_steer_report_failed", error.message)))
|
|
3163
|
+
report: (note, delivered) => andThen(delivered ? steered(note) : void_2, rpc.reportSteer({ workUnitId: work.id, messageId: note.messageId, delivered }).pipe(catch_((error) => logWarning("runner_steer_report_failed", error.message))))
|
|
3455
3164
|
}
|
|
3456
3165
|
}).pipe(catch_((refusal) => succeed5({ status: "failed", reason: refusal.reason, summary: refusal.summary })), map3(agentStepRunOutcome))
|
|
3457
3166
|
});
|
|
3458
3167
|
const runVerification = verificationExecutor({
|
|
3459
3168
|
rootDir: path.join(runnerRootDir, ".kitman"),
|
|
3460
3169
|
known: rpcKnownEvidence(rpc),
|
|
3170
|
+
runFiles: { client: rpc, storage },
|
|
3461
3171
|
...sharedSandbox === undefined ? undefined : { sandbox: { ...sharedSandbox, apiUrl: input.apiUrl } }
|
|
3462
3172
|
});
|
|
3463
3173
|
yield* acquireRelease(backend.start(), () => backend.stop());
|
|
3464
3174
|
const control = (capacitySlot) => ({
|
|
3465
|
-
claim: () =>
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3175
|
+
claim: () => suspend(() => {
|
|
3176
|
+
if (!updater.claiming())
|
|
3177
|
+
return succeed5({});
|
|
3178
|
+
claimsInFlight += 1;
|
|
3179
|
+
return rpc.claim({
|
|
3180
|
+
...input.teamId === undefined ? undefined : { teamId: input.teamId },
|
|
3181
|
+
runnerReplicaId: replica.id,
|
|
3182
|
+
capacitySlot,
|
|
3183
|
+
idempotencyKey: `${replica.id}:${createId()}`
|
|
3184
|
+
}).pipe(tap((result) => sync(() => {
|
|
3185
|
+
if (result.workUnit !== undefined)
|
|
3186
|
+
activeSlots.add(capacitySlot);
|
|
3187
|
+
})), ensuring(sync(() => claimsInFlight -= 1)));
|
|
3188
|
+
}),
|
|
3189
|
+
finished: () => sync(() => activeSlots.delete(capacitySlot)),
|
|
3190
|
+
heartbeatReplica: () => controlPlane.client.heartbeatRunner(input.runnerId, {
|
|
3191
|
+
...reported,
|
|
3192
|
+
problems: updater.problems(),
|
|
3473
3193
|
capabilities,
|
|
3474
|
-
labels:
|
|
3475
|
-
}).pipe(flatMap2(() => controlPlane.client.heartbeatRunnerReplica(input.
|
|
3194
|
+
labels: runner.labels
|
|
3195
|
+
}).pipe(flatMap2(() => controlPlane.client.heartbeatRunnerReplica(input.runnerId, replica.id, {
|
|
3476
3196
|
healthStatus: "healthy",
|
|
3477
|
-
drainState: "accepting",
|
|
3197
|
+
drainState: updater.claiming() ? "accepting" : "draining",
|
|
3478
3198
|
capacity: { maxConcurrentRuns: concurrency },
|
|
3479
3199
|
telemetry: { activeWorkUnits: activeSlots.size }
|
|
3480
3200
|
}).pipe(asVoid))),
|
|
3481
3201
|
heartbeatWork: (workUnitId, lease) => rpc.heartbeat({ workUnitId, lease }),
|
|
3482
|
-
complete: (completion) => rpc.complete(completion)
|
|
3483
|
-
release: (workUnitId, lease) => rpc.release({ workUnitId, lease })
|
|
3202
|
+
complete: (completion) => rpc.complete(completion),
|
|
3203
|
+
release: (workUnitId, lease) => rpc.release({ workUnitId, lease })
|
|
3484
3204
|
});
|
|
3485
|
-
|
|
3205
|
+
const supervised = superviseReplica({
|
|
3486
3206
|
slots: Array.from({ length: concurrency }, (_, index) => `slot-${index + 1}`),
|
|
3487
3207
|
control,
|
|
3488
3208
|
executor: {
|
|
@@ -3505,9 +3225,11 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3505
3225
|
},
|
|
3506
3226
|
signals
|
|
3507
3227
|
});
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3228
|
+
return yield* raceFirst(stopWhenRefused(supervised), _await(updated));
|
|
3229
|
+
}));
|
|
3230
|
+
}).pipe(ensuring(ignore(controlPlane.client.stopRunnerReplica(input.runnerId, replica.id))), provide3(provideMerge(root, layer)));
|
|
3231
|
+
}).pipe(catchTag("RunnerStopped", ({ problem }) => error(`Runner stopped (${problem.code}): ${problem.message}
|
|
3232
|
+
Fix: ${problem.fix}`)));
|
|
3511
3233
|
}
|
|
3512
3234
|
function checkHerdrRunnerReadiness(runner) {
|
|
3513
3235
|
const notReady = (code) => ({ backend: "herdr", status: "not_ready", code, runnerId: runner.id });
|
|
@@ -3537,7 +3259,7 @@ function mcpCommand(args) {
|
|
|
3537
3259
|
if (command.action === "run") {
|
|
3538
3260
|
if (command.args.length > 0)
|
|
3539
3261
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
3540
|
-
const { runMcpServerCommand } = yield* promise(() => import("./chunk-
|
|
3262
|
+
const { runMcpServerCommand } = yield* promise(() => import("./chunk-m1086bf9.js"));
|
|
3541
3263
|
return yield* runMcpServerCommand({ flags: command.flags });
|
|
3542
3264
|
}
|
|
3543
3265
|
if (command.action !== "setup")
|
|
@@ -3736,7 +3458,7 @@ function kitCommand(args) {
|
|
|
3736
3458
|
const source = requiredArg(command, "kit source");
|
|
3737
3459
|
const version = command.args[1];
|
|
3738
3460
|
const scopeType = kitInstallScope(command.flags.scopeType);
|
|
3739
|
-
const scopeId = command.flags.scopeId ?? (scopeType === "
|
|
3461
|
+
const scopeId = command.flags.scopeId ?? (scopeType === "team" ? yield* activeTeamId(command.flags) : undefined);
|
|
3740
3462
|
if ((scopeType === "user" || scopeType === "team") && scopeId === undefined)
|
|
3741
3463
|
return yield* new CliError({ message: `kit install --scope-type ${scopeType} requires --scope-id` });
|
|
3742
3464
|
const install = yield* client.installKit(workspaceId, {
|
|
@@ -3773,32 +3495,57 @@ function kitCommand(args) {
|
|
|
3773
3495
|
return;
|
|
3774
3496
|
}
|
|
3775
3497
|
if (command.action === "attach") {
|
|
3776
|
-
const
|
|
3498
|
+
const teamRef = requiredArg(command, "team");
|
|
3777
3499
|
const kitRef = command.args[1];
|
|
3778
3500
|
if (!kitRef)
|
|
3779
3501
|
return yield* new CliError({ message: "kit attach requires kit" });
|
|
3780
3502
|
const workspaceId = yield* optionalActiveWorkspaceId(command.flags);
|
|
3781
|
-
const result = yield* client.attachKit(
|
|
3503
|
+
const result = yield* client.attachKit(teamRef, kitRef, workspaceId ? { workspaceId } : undefined);
|
|
3782
3504
|
if (command.flags.json)
|
|
3783
|
-
return yield* printRows([{
|
|
3784
|
-
yield* log(`Kit attached: ${result.kitId} to ${result.
|
|
3505
|
+
return yield* printRows([{ teamId: result.teamId, kitId: result.kitId, status: result.status }], true);
|
|
3506
|
+
yield* log(`Kit attached: ${result.kitId} to ${result.teamId}`);
|
|
3785
3507
|
return;
|
|
3786
3508
|
}
|
|
3787
3509
|
if (command.action === "detach") {
|
|
3788
|
-
const
|
|
3510
|
+
const teamRef = requiredArg(command, "team");
|
|
3789
3511
|
const kitRef = command.args[1];
|
|
3790
3512
|
if (!kitRef)
|
|
3791
3513
|
return yield* new CliError({ message: "kit detach requires kit" });
|
|
3792
3514
|
const workspaceId = yield* optionalActiveWorkspaceId(command.flags);
|
|
3793
|
-
const result = yield* client.detachKit(
|
|
3515
|
+
const result = yield* client.detachKit(teamRef, kitRef, workspaceId ? { workspaceId } : undefined);
|
|
3794
3516
|
if (command.flags.json)
|
|
3795
|
-
return yield* printRows([{
|
|
3796
|
-
yield* log(`Kit detached: ${result.kitId} from ${result.
|
|
3517
|
+
return yield* printRows([{ teamId: result.teamId, kitId: result.kitId, status: result.status }], true);
|
|
3518
|
+
yield* log(`Kit detached: ${result.kitId} from ${result.teamId}`);
|
|
3797
3519
|
return;
|
|
3798
3520
|
}
|
|
3799
3521
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
3800
3522
|
});
|
|
3801
3523
|
}
|
|
3524
|
+
function assignTask(client, command) {
|
|
3525
|
+
return gen(function* () {
|
|
3526
|
+
const { to, loop, json } = command.flags;
|
|
3527
|
+
if (to === undefined === (loop === undefined))
|
|
3528
|
+
return yield* new CliError({ message: "task assign takes one of --to <person> or --loop <loop>" });
|
|
3529
|
+
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
3530
|
+
const task = yield* client.getTeamTask(workspaceId, requiredArg(command, "task"));
|
|
3531
|
+
if (loop !== undefined) {
|
|
3532
|
+
const loops = yield* client.listProgramLoops(workspaceId, { teamId: task.team.id });
|
|
3533
|
+
if (!loops.items.some((candidate) => candidate.id === loop))
|
|
3534
|
+
return yield* new CliError({
|
|
3535
|
+
message: `${task.key}'s Team has no Loop ${loop}. Its Loops: ${loops.items.map((candidate) => candidate.id).join(", ") || "none"}`
|
|
3536
|
+
});
|
|
3537
|
+
const started = yield* client.assignTaskLoop(workspaceId, task.id, { loop });
|
|
3538
|
+
return yield* json ? printJson(started) : log(`Started Run ${started.run.id} of ${loop} for ${task.key}`);
|
|
3539
|
+
}
|
|
3540
|
+
const team = yield* client.getTeam(workspaceId, task.team.id);
|
|
3541
|
+
const named = (to ?? "").toLowerCase();
|
|
3542
|
+
const person = [...team.members, ...team.nonMembers].find((candidate) => candidate.id === to || candidate.email.toLowerCase() === named || candidate.name.toLowerCase() === named);
|
|
3543
|
+
if (person === undefined)
|
|
3544
|
+
return yield* new CliError({ message: `${to} is not a member of this Workspace` });
|
|
3545
|
+
const assigned = yield* client.assignTeamTask(workspaceId, task.id, { assigneeId: person.id });
|
|
3546
|
+
return yield* json ? printJson(assigned) : log(`Assigned ${task.key} to ${person.name}`);
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3802
3549
|
function taskCommand(args) {
|
|
3803
3550
|
return gen(function* () {
|
|
3804
3551
|
const command = parseCommand(args);
|
|
@@ -3806,16 +3553,18 @@ function taskCommand(args) {
|
|
|
3806
3553
|
const session = yield* requiredSessionForApi(apiUrl);
|
|
3807
3554
|
const client = yield* clientFor(apiUrl, session);
|
|
3808
3555
|
if (command.action === "list") {
|
|
3809
|
-
const
|
|
3556
|
+
const teamId = yield* activeTeamId(command.flags);
|
|
3810
3557
|
const tasks = [];
|
|
3811
|
-
let page = yield* client.listTasks(
|
|
3558
|
+
let page = yield* client.listTasks(teamId);
|
|
3812
3559
|
tasks.push(...page.items);
|
|
3813
3560
|
while (command.flags.status !== undefined && page.nextCursor !== undefined) {
|
|
3814
|
-
page = yield* client.listTasks(
|
|
3561
|
+
page = yield* client.listTasks(teamId, { cursor: page.nextCursor, limit: 100 });
|
|
3815
3562
|
tasks.push(...page.items);
|
|
3816
3563
|
}
|
|
3817
3564
|
return yield* printRows(tasks.flatMap((task) => inState(task, command.flags.status) ? [taskRow(task)] : []), command.flags.json);
|
|
3818
3565
|
}
|
|
3566
|
+
if (command.action === "assign")
|
|
3567
|
+
return yield* assignTask(client, command);
|
|
3819
3568
|
if (command.action === "show") {
|
|
3820
3569
|
const taskRef = requiredArg(command, "task");
|
|
3821
3570
|
const task = yield* client.getTask(taskRef);
|
|
@@ -3970,6 +3719,11 @@ function memoryCommand(args) {
|
|
|
3970
3719
|
yield* log(`Memory ${memoryTransitionPastTense(command.action)}: ${response.memory.id}`);
|
|
3971
3720
|
return;
|
|
3972
3721
|
}
|
|
3722
|
+
if (command.action === "move") {
|
|
3723
|
+
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
3724
|
+
const owner = yield* ownerTo(client, workspaceId, command);
|
|
3725
|
+
return yield* printMoved(command, yield* client.moveMemory(workspaceId, requiredArg(command, "memory"), owner.change), owner.label);
|
|
3726
|
+
}
|
|
3973
3727
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
3974
3728
|
});
|
|
3975
3729
|
}
|
|
@@ -3980,8 +3734,8 @@ function proposalCommand(args) {
|
|
|
3980
3734
|
const session = yield* requiredSessionForApi(apiUrl);
|
|
3981
3735
|
const client = yield* clientFor(apiUrl, session);
|
|
3982
3736
|
if (command.action === "list") {
|
|
3983
|
-
const
|
|
3984
|
-
const response = yield* client.listProposals(
|
|
3737
|
+
const teamId = command.flags.team ?? (yield* activeTeamId(command.flags));
|
|
3738
|
+
const response = yield* client.listProposals(teamId, {
|
|
3985
3739
|
...command.flags.status === undefined ? undefined : { status: validateProposalStatus(command.flags.status) },
|
|
3986
3740
|
...command.flags.targetType === undefined ? undefined : { targetType: command.flags.targetType },
|
|
3987
3741
|
...command.flags.limit === undefined ? undefined : { limit: command.flags.limit }
|
|
@@ -4019,8 +3773,8 @@ function decisionCommand(args) {
|
|
|
4019
3773
|
const session = yield* requiredSessionForApi(apiUrl);
|
|
4020
3774
|
const client = yield* clientFor(apiUrl, session);
|
|
4021
3775
|
if (command.action === "list") {
|
|
4022
|
-
const
|
|
4023
|
-
const response = yield* client.listDecisions("
|
|
3776
|
+
const teamId = command.flags.team ?? (yield* activeTeamId(command.flags));
|
|
3777
|
+
const response = yield* client.listDecisions("team", teamId, {
|
|
4024
3778
|
status: command.flags.status === undefined ? "active" : validateDecisionStatus(command.flags.status),
|
|
4025
3779
|
...command.flags.limit === undefined ? undefined : { limit: command.flags.limit }
|
|
4026
3780
|
});
|
|
@@ -4031,6 +3785,11 @@ function decisionCommand(args) {
|
|
|
4031
3785
|
const decision = yield* client.getDecision(decisionId);
|
|
4032
3786
|
return yield* printRows([decisionRow(decision)], command.flags.json);
|
|
4033
3787
|
}
|
|
3788
|
+
if (command.action === "move") {
|
|
3789
|
+
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
3790
|
+
const owner = yield* ownerTo(client, workspaceId, command);
|
|
3791
|
+
return yield* printMoved(command, yield* client.moveDecision(workspaceId, requiredArg(command, "decision"), owner.change), owner.label);
|
|
3792
|
+
}
|
|
4034
3793
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
4035
3794
|
});
|
|
4036
3795
|
}
|
|
@@ -4066,18 +3825,18 @@ function programLoopCommand(command) {
|
|
|
4066
3825
|
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4067
3826
|
const print = (value, lines) => command.flags.json ? printJson(value) : forEach(lines(value), (line) => log(line), { discard: true });
|
|
4068
3827
|
if (command.action === "list") {
|
|
4069
|
-
const
|
|
4070
|
-
const listed = yield* client.listProgramLoops(yield* activeWorkspaceId(command.flags),
|
|
3828
|
+
const teamId = yield* optionalActiveTeamId(command.flags);
|
|
3829
|
+
const listed = yield* client.listProgramLoops(yield* activeWorkspaceId(command.flags), teamId === undefined ? {} : { teamId });
|
|
4071
3830
|
return yield* print(listed, (value) => value.items.flatMap(programLoopLines));
|
|
4072
3831
|
}
|
|
4073
3832
|
if (command.action === "starts") {
|
|
4074
|
-
const started = yield* client.listAutomaticStarts(yield*
|
|
3833
|
+
const started = yield* client.listAutomaticStarts(yield* activeTeamId(command.flags), command.flags.limit === undefined ? {} : { limit: command.flags.limit });
|
|
4075
3834
|
return yield* print(started, (value) => value.items.length === 0 ? ["No Event has started a Loop here yet"] : value.items.map((start) => `${start.at} ${start.eventType} -> ${start.loop}: ${start.runId === undefined ? `not started: ${start.rejection ?? "unknown"}` : `Run ${start.runId}`}`));
|
|
4076
3835
|
}
|
|
4077
3836
|
if (command.action === "show") {
|
|
4078
3837
|
const loop = requiredArg(command, "a Loop");
|
|
4079
|
-
const
|
|
4080
|
-
const shown = yield* client.showProgramLoop(yield* activeWorkspaceId(command.flags), loop,
|
|
3838
|
+
const teamId = yield* optionalActiveTeamId(command.flags);
|
|
3839
|
+
const shown = yield* client.showProgramLoop(yield* activeWorkspaceId(command.flags), loop, teamId === undefined ? {} : { teamId });
|
|
4081
3840
|
return yield* print(shown, (value) => value.resolved === undefined ? [`${loop} resolves to nothing here`] : programLoopLines(value.resolved));
|
|
4082
3841
|
}
|
|
4083
3842
|
return yield* new CliError({ message: `Unknown loop command: ${command.action}` });
|
|
@@ -4103,8 +3862,8 @@ function agentCommand(args) {
|
|
|
4103
3862
|
resolvedField(agent, "effort")
|
|
4104
3863
|
].filter((field) => field !== "").join(" ");
|
|
4105
3864
|
if (command.action === "list") {
|
|
4106
|
-
const
|
|
4107
|
-
const listed = yield* client.listProgramAgents(yield* activeWorkspaceId(command.flags),
|
|
3865
|
+
const teamId = yield* optionalActiveTeamId(command.flags);
|
|
3866
|
+
const listed = yield* client.listProgramAgents(yield* activeWorkspaceId(command.flags), teamId === undefined ? {} : { teamId });
|
|
4108
3867
|
return yield* print(listed, (value) => value.items.map(agentLine));
|
|
4109
3868
|
}
|
|
4110
3869
|
return yield* new CliError({ message: `Unknown agent command: ${command.action}. Use kitman agent list.` });
|
|
@@ -4127,21 +3886,21 @@ function loopCatalogCommand(args) {
|
|
|
4127
3886
|
const command = parseCommand(args);
|
|
4128
3887
|
const apiUrl = yield* resolveApiUrl(command.flags.apiUrl);
|
|
4129
3888
|
const client = yield* loopClientFor(apiUrl);
|
|
4130
|
-
const
|
|
3889
|
+
const teamId = yield* activeTeamId(command.flags);
|
|
4131
3890
|
if (command.action === "list" || command.action === "catalog") {
|
|
4132
|
-
const response = yield* client.requestJson("listLoops", `/loops${loopListQuery(
|
|
3891
|
+
const response = yield* client.requestJson("listLoops", `/loops${loopListQuery(teamId, command.flags.limit)}`);
|
|
4133
3892
|
return yield* printOperationalResult(command, response);
|
|
4134
3893
|
}
|
|
4135
3894
|
const loopRef = requiredArg(command, "Loop");
|
|
4136
3895
|
if (command.action === "show") {
|
|
4137
|
-
const response = yield* client.requestJson("getLoop", `/loops/${encodeURIComponent(loopRef)}?
|
|
3896
|
+
const response = yield* client.requestJson("getLoop", `/loops/${encodeURIComponent(loopRef)}?teamId=${encodeURIComponent(teamId)}`);
|
|
4138
3897
|
return yield* printOperationalResult(command, response);
|
|
4139
3898
|
}
|
|
4140
3899
|
if (command.action === "validate" || command.action === "test") {
|
|
4141
3900
|
const input = yield* operationalInput(command, `loop ${command.action}`);
|
|
4142
3901
|
const response = yield* client.requestJson(`loop${capitalize(command.action)}`, `/loops/${encodeURIComponent(loopRef)}/${command.action}`, {
|
|
4143
3902
|
method: "POST",
|
|
4144
|
-
body: {
|
|
3903
|
+
body: { teamId, loopRef, ...input }
|
|
4145
3904
|
});
|
|
4146
3905
|
return yield* printOperationalResult(command, response);
|
|
4147
3906
|
}
|
|
@@ -4159,9 +3918,9 @@ var launchContext = (flags) => gen(function* () {
|
|
|
4159
3918
|
const read = (args) => git(args).pipe(orElseSucceed(() => ""));
|
|
4160
3919
|
const remoteUrl = yield* read(["remote", "get-url", "origin"]);
|
|
4161
3920
|
const branch = yield* read(["branch", "--show-current"]);
|
|
4162
|
-
const
|
|
3921
|
+
const teamId = yield* optionalActiveTeamId(flags);
|
|
4163
3922
|
return {
|
|
4164
|
-
...
|
|
3923
|
+
...teamId === undefined ? undefined : { teamId },
|
|
4165
3924
|
...remoteUrl === "" ? undefined : { remoteUrl },
|
|
4166
3925
|
...branch === "" ? undefined : { branch }
|
|
4167
3926
|
};
|
|
@@ -4217,8 +3976,28 @@ var driftLines = (drift) => [
|
|
|
4217
3976
|
...drift.reports.length === 0 ? ["No Drift Reports", ...judgementLines(drift)] : shownDriftLines(drift),
|
|
4218
3977
|
...drift.boundaries.flatMap((boundary) => boundary.uncheckedReason === undefined ? [] : [`Unchecked at ${boundary.stepKey}: ${boundary.uncheckedReason}`])
|
|
4219
3978
|
];
|
|
3979
|
+
var graphNodeText = (node) => `${node.name} ${node.kind} ${runDisplayLabels[node.status]}${node.attempts.length > 1 ? `, ${node.attempts.length} attempts` : ""}`;
|
|
3980
|
+
var displayGlyphs = {
|
|
3981
|
+
queued: "\u25CB",
|
|
3982
|
+
running: "\u25D0",
|
|
3983
|
+
needs_you: "?",
|
|
3984
|
+
blocked: "\u25B2",
|
|
3985
|
+
failed: "\u2717",
|
|
3986
|
+
stopped: "\u25A0",
|
|
3987
|
+
succeeded: "\u2713",
|
|
3988
|
+
partial: "\u25D1"
|
|
3989
|
+
};
|
|
3990
|
+
var runListLine = (item) => [
|
|
3991
|
+
`${runDisplayLabels[item.display.state]}${item.display.retried ? " \xB7 Retried" : ""}`,
|
|
3992
|
+
item.miniGraph.map((state) => displayGlyphs[state]).join(""),
|
|
3993
|
+
item.task === undefined ? item.loop.title : `${item.task.key} ${item.task.title}`,
|
|
3994
|
+
item.display.summary ?? "",
|
|
3995
|
+
item.run.id
|
|
3996
|
+
].filter((part) => part !== "").join(" ");
|
|
4220
3997
|
var runShowLines = (shown) => [
|
|
4221
|
-
`Run ${shown.run.id}: ${shown.
|
|
3998
|
+
`Run ${shown.run.id}: ${runDisplayLabels[shown.display.state]}${shown.display.retried ? " \xB7 Retried" : ""}`,
|
|
3999
|
+
...shown.display.summary === undefined ? [] : [`Now: ${shown.display.summary}`],
|
|
4000
|
+
...shown.graph.columns.map((column, index) => `${index + 1}. ${column.map(graphNodeText).join(" \xB7 ")}`),
|
|
4222
4001
|
...shown.closures.map((closure, index) => index === 0 ? closureLine(closure) : `Earlier: ${closureLine(closure)}`),
|
|
4223
4002
|
...alignmentLines(shown.run.alignment),
|
|
4224
4003
|
...shown.run.loopDigest === undefined ? [] : [`Loop from the ${shown.run.loopLayer ?? "shipped"} layer, digest ${shown.run.loopDigest}`],
|
|
@@ -4251,7 +4030,7 @@ var myTeamsCardLine = (card) => {
|
|
|
4251
4030
|
...card.questions.map((question) => `asks: ${question.prompt}`),
|
|
4252
4031
|
...card.changeRequests.map((changeRequest) => `review ${changeRequest.url}`),
|
|
4253
4032
|
...card.proposals.map((proposal) => `decide: ${proposal.title}`),
|
|
4254
|
-
...
|
|
4033
|
+
...latest === undefined ? [] : [runDisplayLabels[latest.display.state]]
|
|
4255
4034
|
].join(" \xB7 ");
|
|
4256
4035
|
};
|
|
4257
4036
|
var inboxActionLabel = {
|
|
@@ -4272,12 +4051,14 @@ var inboxItemLine = (item) => {
|
|
|
4272
4051
|
`${item.subject.kind}:${item.subject.id}`
|
|
4273
4052
|
].join(" ");
|
|
4274
4053
|
};
|
|
4275
|
-
var inboxSubjectKinds = ["task", "run", "proposal"];
|
|
4054
|
+
var inboxSubjectKinds = ["task", "run", "proposal", "memory", "project_update", "runner"];
|
|
4276
4055
|
var inboxSubjectOf = (ref) => {
|
|
4277
4056
|
const separator = ref.indexOf(":");
|
|
4278
4057
|
const kind = inboxSubjectKinds.find((known) => known === ref.slice(0, separator));
|
|
4279
4058
|
const id = ref.slice(separator + 1);
|
|
4280
|
-
return kind !== undefined && id !== "" ? succeed5({ kind, id }) : fail4(new CliError({
|
|
4059
|
+
return kind !== undefined && id !== "" ? succeed5({ kind, id }) : fail4(new CliError({
|
|
4060
|
+
message: `${ref} is not an Inbox item; name it as task:<id>, run:<id>, proposal:<id> or project_update:<id>`
|
|
4061
|
+
}));
|
|
4281
4062
|
};
|
|
4282
4063
|
var inboxActions = new Set([
|
|
4283
4064
|
"list",
|
|
@@ -4297,7 +4078,7 @@ function inboxCommand(args) {
|
|
|
4297
4078
|
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4298
4079
|
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
4299
4080
|
if (command.action === "my-teams") {
|
|
4300
|
-
const view = yield* client.showMyTeams(workspaceId, command.flags.
|
|
4081
|
+
const view = yield* client.showMyTeams(workspaceId, command.flags.team === undefined ? {} : { teamId: command.flags.team });
|
|
4301
4082
|
if (command.flags.json)
|
|
4302
4083
|
return yield* printJson(view);
|
|
4303
4084
|
return yield* forEach(myTeamsSections.flatMap(([section, title]) => [
|
|
@@ -4340,6 +4121,93 @@ function inboxCommand(args) {
|
|
|
4340
4121
|
return yield* command.flags.json ? printJson(counts) : log(`${counts.items} in your Inbox, ${counts.unread} unread`);
|
|
4341
4122
|
});
|
|
4342
4123
|
}
|
|
4124
|
+
var transcriptEventLine = (event) => {
|
|
4125
|
+
switch (event.kind) {
|
|
4126
|
+
case "message":
|
|
4127
|
+
return `${event.role}: ${event.text}`;
|
|
4128
|
+
case "tool_call":
|
|
4129
|
+
return `tool ${event.name} ${encodeJsonStringSync(event.arguments)}`;
|
|
4130
|
+
case "tool_result":
|
|
4131
|
+
return `${event.isError === true ? "tool error" : "tool result"}: ${event.output}`;
|
|
4132
|
+
case "file":
|
|
4133
|
+
return `${event.change} ${event.path}`;
|
|
4134
|
+
case "question":
|
|
4135
|
+
case "answer":
|
|
4136
|
+
case "steer":
|
|
4137
|
+
return `${event.kind}: ${event.text}`;
|
|
4138
|
+
case "lifecycle":
|
|
4139
|
+
return `session ${event.state}`;
|
|
4140
|
+
}
|
|
4141
|
+
};
|
|
4142
|
+
var transcriptLine = (entry) => `${entry.sequence} ${transcriptEventLine(entry.event)}`;
|
|
4143
|
+
var transcriptAfter = (client, runId, stepAttemptId, after) => gen(function* () {
|
|
4144
|
+
const entries = [];
|
|
4145
|
+
for (let cursor = after;cursor !== undefined; ) {
|
|
4146
|
+
const page = yield* client.readTranscript(runId, stepAttemptId, cursor === 0 ? {} : { after: cursor });
|
|
4147
|
+
entries.push(...page.items);
|
|
4148
|
+
cursor = page.next;
|
|
4149
|
+
}
|
|
4150
|
+
return entries;
|
|
4151
|
+
});
|
|
4152
|
+
var askedAttempts = (attempts, step, attempt) => {
|
|
4153
|
+
const latest = new Map;
|
|
4154
|
+
for (const candidate of attempts)
|
|
4155
|
+
if ((step === undefined || candidate.stepKey === step) && (attempt === undefined || candidate.attempt === attempt) && candidate.attempt >= (latest.get(candidate.stepKey)?.attempt ?? 0))
|
|
4156
|
+
latest.set(candidate.stepKey, candidate);
|
|
4157
|
+
return [...latest.values()].sort((left, right) => left.sequence - right.sequence);
|
|
4158
|
+
};
|
|
4159
|
+
function transcriptCommand(args) {
|
|
4160
|
+
return gen(function* () {
|
|
4161
|
+
const command = parseCommand(args);
|
|
4162
|
+
const runId = requiredArg(command, "a Run");
|
|
4163
|
+
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4164
|
+
const { stepKey, attempt } = command.flags;
|
|
4165
|
+
const asked = askedAttempts((yield* client.listStepAttempts(runId)).items, stepKey, attempt);
|
|
4166
|
+
if (asked.length === 0)
|
|
4167
|
+
return yield* new CliError({
|
|
4168
|
+
message: `Run ${runId} has no ${stepKey === undefined ? "Step attempts" : `attempt of ${stepKey}`}${attempt === undefined ? "" : ` numbered ${attempt}`}`
|
|
4169
|
+
});
|
|
4170
|
+
const items = yield* forEach(asked, (found) => map3(transcriptAfter(client, runId, found.id, 0), (entries) => ({
|
|
4171
|
+
stepKey: found.stepKey,
|
|
4172
|
+
attempt: found.attempt,
|
|
4173
|
+
stepAttemptId: found.id,
|
|
4174
|
+
entries
|
|
4175
|
+
})));
|
|
4176
|
+
if (command.flags.json)
|
|
4177
|
+
return yield* log(encodeJsonStringSync({ items }));
|
|
4178
|
+
for (const item of items) {
|
|
4179
|
+
yield* log(`Step ${item.stepKey}, attempt ${item.attempt}`);
|
|
4180
|
+
yield* forEach(item.entries, (entry) => log(transcriptLine(entry)), { discard: true });
|
|
4181
|
+
}
|
|
4182
|
+
});
|
|
4183
|
+
}
|
|
4184
|
+
var settledRunStatuses = new Set(["completed", "failed", "canceled", "expired"]);
|
|
4185
|
+
var followTranscripts = (client, runId, options) => gen(function* () {
|
|
4186
|
+
const read = new Map;
|
|
4187
|
+
const refused = new Set;
|
|
4188
|
+
let status;
|
|
4189
|
+
const show = (value, line) => log(options.json ? encodeJsonStringSync(value) : line);
|
|
4190
|
+
for (;; ) {
|
|
4191
|
+
const run = yield* client.getRun(runId);
|
|
4192
|
+
if (run.status !== status) {
|
|
4193
|
+
status = run.status;
|
|
4194
|
+
yield* show({ run: { id: run.id, status: run.status } }, `Run ${run.id}: ${run.status}`);
|
|
4195
|
+
}
|
|
4196
|
+
for (const attempt of (yield* client.listStepAttempts(runId)).items) {
|
|
4197
|
+
if (refused.has(attempt.id))
|
|
4198
|
+
continue;
|
|
4199
|
+
const step = { stepKey: attempt.stepKey, attempt: attempt.attempt };
|
|
4200
|
+
const entries = yield* transcriptAfter(client, runId, attempt.id, read.get(attempt.id) ?? 0).pipe(catchIf((error) => error instanceof KitmanClientError && error.status === 403, (error) => as(andThen(sync(() => refused.add(attempt.id)), show({ ...step, stepAttemptId: attempt.id, refused: error.responseMessage ?? error.message }, `${attempt.stepKey} #${attempt.attempt}: ${error.responseMessage ?? error.message}`)), [])));
|
|
4201
|
+
for (const entry of entries) {
|
|
4202
|
+
read.set(attempt.id, entry.sequence);
|
|
4203
|
+
yield* show({ ...step, ...entry }, `${attempt.stepKey} #${attempt.attempt} ${transcriptLine(entry)}`);
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
if (settledRunStatuses.has(run.status))
|
|
4207
|
+
return;
|
|
4208
|
+
yield* sleep(options.pollIntervalMs);
|
|
4209
|
+
}
|
|
4210
|
+
});
|
|
4343
4211
|
function runCommand(args) {
|
|
4344
4212
|
return gen(function* () {
|
|
4345
4213
|
const command = parseCommand(args);
|
|
@@ -4371,22 +4239,35 @@ function runCommand(args) {
|
|
|
4371
4239
|
]);
|
|
4372
4240
|
}
|
|
4373
4241
|
if (command.action === "list") {
|
|
4374
|
-
const
|
|
4375
|
-
|
|
4376
|
-
...
|
|
4242
|
+
const { team, loop, states, limit } = command.flags;
|
|
4243
|
+
const listed = yield* client.listRuns(yield* activeWorkspaceId(command.flags), {
|
|
4244
|
+
...team === undefined ? undefined : { teamId: team },
|
|
4245
|
+
...loop === undefined ? undefined : { loop },
|
|
4246
|
+
...states === undefined ? undefined : { states },
|
|
4247
|
+
...limit === undefined ? undefined : { limit }
|
|
4377
4248
|
});
|
|
4378
|
-
return yield* print(listed, (value) => [
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4249
|
+
return yield* print(listed, (value) => value.items.length === 0 ? ["No Runs"] : value.items.map(runListLine));
|
|
4250
|
+
}
|
|
4251
|
+
if (command.action === "file") {
|
|
4252
|
+
const [verb, fileRunId, fileId] = command.args;
|
|
4253
|
+
if (verb !== "get" || fileRunId === undefined || fileId === undefined)
|
|
4254
|
+
return yield* new CliError({ message: "Usage: kitman run file get <run> <id> [--out path]" });
|
|
4255
|
+
const listed = yield* client.listRunFiles(fileRunId);
|
|
4256
|
+
const file = "files" in listed ? listed.files.find((candidate) => candidate.id === fileId) : undefined;
|
|
4257
|
+
if (file === undefined)
|
|
4258
|
+
return yield* new CliError({ message: `Run ${fileRunId} has no file ${fileId} you can read` });
|
|
4259
|
+
const { urls } = yield* client.runFileUrls(fileRunId, { ids: [fileId], use: "download" });
|
|
4260
|
+
const url = urls.find((candidate) => candidate.id === fileId)?.url;
|
|
4261
|
+
if (url === undefined)
|
|
4262
|
+
return yield* new CliError({ message: `Kitman gave no link to file ${fileId}` });
|
|
4263
|
+
const out = command.flags.out ?? localFileName(file.name, file.id);
|
|
4264
|
+
yield* downloadVerified(url, out, file.sha256);
|
|
4265
|
+
return yield* print({ id: file.id, path: out }, (value) => [`Wrote ${value.id} to ${value.path}`]);
|
|
4387
4266
|
}
|
|
4388
4267
|
const runId = requiredArg(command, "a Run");
|
|
4389
4268
|
switch (command.action) {
|
|
4269
|
+
case "files":
|
|
4270
|
+
return yield* print(yield* client.listRunFiles(runId, command.flags.stepKey === undefined ? {} : { step: command.flags.stepKey }), runFileLines);
|
|
4390
4271
|
case "show": {
|
|
4391
4272
|
const shown = {
|
|
4392
4273
|
...yield* client.showRun(runId),
|
|
@@ -4441,6 +4322,11 @@ function runCommand(args) {
|
|
|
4441
4322
|
});
|
|
4442
4323
|
return yield* print(answered, (question) => [`Answered ${question.stepKey}: ${value}`]);
|
|
4443
4324
|
}
|
|
4325
|
+
case "follow":
|
|
4326
|
+
return yield* followTranscripts(client, runId, {
|
|
4327
|
+
json: command.flags.json === true,
|
|
4328
|
+
pollIntervalMs: command.flags.pollIntervalMs ?? 1000
|
|
4329
|
+
});
|
|
4444
4330
|
case "steer": {
|
|
4445
4331
|
const text = command.args.slice(1).join(" ");
|
|
4446
4332
|
if (text === "")
|
|
@@ -4486,38 +4372,56 @@ function runCommand(args) {
|
|
|
4486
4372
|
}
|
|
4487
4373
|
});
|
|
4488
4374
|
}
|
|
4489
|
-
var
|
|
4375
|
+
var byteSize = (bytes) => {
|
|
4376
|
+
const units = ["B", "KiB", "MiB", "GiB"];
|
|
4377
|
+
const exponent = Math.min(units.length - 1, Math.floor(Math.log(Math.max(bytes, 1)) / Math.log(1024)));
|
|
4378
|
+
return exponent === 0 ? `${bytes} B` : `${(bytes / 1024 ** exponent).toFixed(1)} ${units[exponent]}`;
|
|
4379
|
+
};
|
|
4380
|
+
var runFileLines = (listed) => {
|
|
4381
|
+
if (!("files" in listed))
|
|
4382
|
+
return [`${listed.count} files; reading them needs access to the content of the Run's repositories`];
|
|
4383
|
+
if (listed.files.length === 0)
|
|
4384
|
+
return ["No files"];
|
|
4385
|
+
return listed.files.map((file) => [
|
|
4386
|
+
file.id,
|
|
4387
|
+
file.name,
|
|
4388
|
+
file.madeBy,
|
|
4389
|
+
file.kind,
|
|
4390
|
+
byteSize(file.byteSize),
|
|
4391
|
+
file.reason === undefined ? file.status : `${file.status}: ${file.status === "refused" && isRunFileRefusalReason(file.reason) ? runFileRefusalWords({ ...file, reason: file.reason }) : file.reason}`
|
|
4392
|
+
].join(" "));
|
|
4393
|
+
};
|
|
4394
|
+
var currentRepoBinding = (client, workspaceId) => gen(function* () {
|
|
4490
4395
|
const remote = yield* (yield* Git)(["remote", "get-url", "origin"]).pipe(orElseSucceed(() => ""));
|
|
4491
4396
|
if (remote === "")
|
|
4492
4397
|
return yield* new CliError({ message: "Run this inside a git checkout with an origin" });
|
|
4493
|
-
const
|
|
4494
|
-
const binding = bindings.items.find((candidate) => repositoryKey(candidate.repositoryUrl ?? `https://${candidate.host}/${candidate.owner}/${candidate.name}`) === repositoryKey(remote));
|
|
4398
|
+
const binding = (yield* client.listRepoBindings(workspaceId)).items.find((candidate) => candidate.status === "active" && repositoryKey(candidate.repositoryUrl ?? `https://${candidate.host}/${candidate.owner}/${candidate.name}`) === repositoryKey(remote));
|
|
4495
4399
|
if (binding === undefined)
|
|
4496
|
-
return yield* new CliError({ message: `No repository of the
|
|
4400
|
+
return yield* new CliError({ message: `No repository of the Workspace matches ${remote.trim()}` });
|
|
4497
4401
|
return binding;
|
|
4498
4402
|
});
|
|
4499
4403
|
function verifyCommand(args) {
|
|
4500
4404
|
return gen(function* () {
|
|
4501
4405
|
const command = parseCommand(args.length === 1 ? [...args, "list"] : args);
|
|
4502
4406
|
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4503
|
-
const
|
|
4504
|
-
const binding = yield* currentRepoBinding(client,
|
|
4407
|
+
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
4408
|
+
const binding = yield* currentRepoBinding(client, workspaceId);
|
|
4505
4409
|
const print = (value, lines) => command.flags.json ? printJson(value) : forEach(lines(value), (line) => log(line), { discard: true });
|
|
4506
4410
|
switch (command.action) {
|
|
4507
4411
|
case "list":
|
|
4508
|
-
return yield* print(yield* client.listApprovedVerifications(
|
|
4412
|
+
return yield* print(yield* client.listApprovedVerifications(workspaceId, binding.id), (listed) => listed.items.length === 0 ? [`No approved verifications for ${binding.owner}/${binding.name}`] : listed.items.map((check) => `${check.name}: ${check.command}${check.workingDirectory === undefined ? "" : ` (in ${check.workingDirectory})`}`));
|
|
4509
4413
|
case "add": {
|
|
4510
4414
|
const [name, ...words] = command.args;
|
|
4511
4415
|
if (name === undefined || words.length === 0)
|
|
4512
4416
|
return yield* new CliError({ message: "verify add requires a name and a command" });
|
|
4513
|
-
return yield* print(yield* client.approveVerification(
|
|
4417
|
+
return yield* print(yield* client.approveVerification(workspaceId, binding.id, {
|
|
4514
4418
|
name,
|
|
4515
4419
|
command: words.join(" "),
|
|
4516
4420
|
...command.flags.workingDirectory === undefined ? undefined : { workingDirectory: command.flags.workingDirectory }
|
|
4517
4421
|
}), (check) => [`Approved ${check.name}: ${check.command}`]);
|
|
4518
4422
|
}
|
|
4519
4423
|
case "remove":
|
|
4520
|
-
return yield* print(yield* client.removeVerification(
|
|
4424
|
+
return yield* print(yield* client.removeVerification(workspaceId, binding.id, requiredArg(command, "a verification")), (removed) => [`Removed ${removed.removed}`]);
|
|
4521
4425
|
default:
|
|
4522
4426
|
return yield* new CliError({ message: `Unknown verify command: ${command.action}` });
|
|
4523
4427
|
}
|
|
@@ -4529,13 +4433,13 @@ function secretCommand(args) {
|
|
|
4529
4433
|
const command = parseCommand(args.length === 1 ? [...args, "list"] : args);
|
|
4530
4434
|
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4531
4435
|
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
4532
|
-
const
|
|
4533
|
-
const scope =
|
|
4534
|
-
const where =
|
|
4436
|
+
const teamId = command.flags.team === undefined ? undefined : (yield* teamNamed(client, workspaceId, command.flags.team)).id;
|
|
4437
|
+
const scope = teamId === undefined ? {} : { teamId };
|
|
4438
|
+
const where = teamId === undefined ? "the Workspace" : `Team ${command.flags.team}`;
|
|
4535
4439
|
const print = (value, lines) => command.flags.json ? printJson(value) : forEach(lines(value), (line) => log(line), { discard: true });
|
|
4536
4440
|
switch (command.action) {
|
|
4537
4441
|
case "list":
|
|
4538
|
-
return yield* print(yield* client.listSecrets(workspaceId, scope), (listed) => listed.items.length === 0 ? [`No Secrets in ${where}`] : listed.items.map((secret) => `${secret.name} ${secret.scope === "workspace" ? "workspace" : `
|
|
4442
|
+
return yield* print(yield* client.listSecrets(workspaceId, scope), (listed) => listed.items.length === 0 ? [`No Secrets in ${where}`] : listed.items.map((secret) => `${secret.name} ${secret.scope === "workspace" ? "workspace" : `team ${secret.teamId}`} set by ${secret.setByActorId} at ${secret.setAt}`));
|
|
4539
4443
|
case "set": {
|
|
4540
4444
|
const name = requiredArg(command, "a Secret name");
|
|
4541
4445
|
if (command.args.length > 1)
|
|
@@ -4551,13 +4455,17 @@ function secretCommand(args) {
|
|
|
4551
4455
|
}
|
|
4552
4456
|
case "delete":
|
|
4553
4457
|
return yield* print(yield* client.deleteSecret(workspaceId, requiredArg(command, "a Secret name"), scope), (removed) => [`Deleted ${removed.removed} from ${where}`]);
|
|
4458
|
+
case "move": {
|
|
4459
|
+
const owner = yield* ownerTo(client, workspaceId, command);
|
|
4460
|
+
return yield* printMoved(command, yield* client.moveSecret(workspaceId, requiredArg(command, "a Secret name"), owner.change, scope), owner.label);
|
|
4461
|
+
}
|
|
4554
4462
|
default:
|
|
4555
4463
|
return yield* new CliError({ message: `Unknown secret command: ${command.action}` });
|
|
4556
4464
|
}
|
|
4557
4465
|
});
|
|
4558
4466
|
}
|
|
4559
4467
|
var PackageBuilder = Reference("kitman/cli/PackageBuilder", {
|
|
4560
|
-
defaultValue: () => (directory) => flatMap2(promise(() => import("./chunk-
|
|
4468
|
+
defaultValue: () => (directory) => flatMap2(promise(() => import("./chunk-djtdn35f.js")), (build) => build.buildPackage(directory))
|
|
4561
4469
|
});
|
|
4562
4470
|
var optionalEnv = (name) => String3(name).pipe(option2, map3(getOrUndefined));
|
|
4563
4471
|
var ciIdToken = (apiUrl) => gen(function* () {
|
|
@@ -4571,7 +4479,7 @@ var ciIdToken = (apiUrl) => gen(function* () {
|
|
|
4571
4479
|
const url = new URL(requestUrl);
|
|
4572
4480
|
const refused = (reason) => new CliError({ message: `GitHub Actions issued no ID token: ${reason}` });
|
|
4573
4481
|
url.searchParams.set("audience", apiUrl.replace(/\/+$/, ""));
|
|
4574
|
-
const response = yield* send(
|
|
4482
|
+
const response = yield* send(get2(url, { headers: { authorization: `Bearer ${requestToken}` } }), (cause) => refused(String(cause)));
|
|
4575
4483
|
if (response.status < 200 || response.status >= 300)
|
|
4576
4484
|
return yield* refused(`${response.status}; does the job have id-token: write?`);
|
|
4577
4485
|
const body = yield* response.text.pipe(mapError((error) => refused(error.message)));
|
|
@@ -4584,12 +4492,12 @@ var packageScope = (command, options = {}) => gen(function* () {
|
|
|
4584
4492
|
const token = options.ci === true ? yield* ciIdToken(apiUrl) : undefined;
|
|
4585
4493
|
const client = token === undefined ? yield* loopClientFor(apiUrl) : yield* createSdkClient({ apiUrl, session: { accessToken: token }, clientId: ClientId });
|
|
4586
4494
|
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
4587
|
-
const
|
|
4495
|
+
const teamId = command.flags.team === undefined || token !== undefined ? command.flags.team : (yield* teamNamed(client, workspaceId, command.flags.team)).id;
|
|
4588
4496
|
return {
|
|
4589
4497
|
client,
|
|
4590
4498
|
workspaceId,
|
|
4591
|
-
scope:
|
|
4592
|
-
where:
|
|
4499
|
+
scope: teamId === undefined ? {} : { teamId },
|
|
4500
|
+
where: teamId !== undefined ? `Team ${command.flags.team}` : token === undefined ? "the Workspace" : "the Deploy Binding's layer"
|
|
4593
4501
|
};
|
|
4594
4502
|
});
|
|
4595
4503
|
var changeLines = (resource, changes) => [
|
|
@@ -4598,9 +4506,9 @@ var changeLines = (resource, changes) => [
|
|
|
4598
4506
|
...changes.removed.map((id) => ` - ${resource} ${id}`)
|
|
4599
4507
|
];
|
|
4600
4508
|
var scheduleProblems = {
|
|
4601
|
-
missing: "names no
|
|
4602
|
-
not_allowed: "names a
|
|
4603
|
-
unknown: "names a
|
|
4509
|
+
missing: "names no Team, which a Workspace Package must",
|
|
4510
|
+
not_allowed: "names a Team, though a Team Package's Schedules run in its own",
|
|
4511
|
+
unknown: "names a Team this Workspace does not have"
|
|
4604
4512
|
};
|
|
4605
4513
|
var removedKitRecords = (shown, kind) => ({
|
|
4606
4514
|
created: [],
|
|
@@ -4617,9 +4525,9 @@ var conflictLine = (conflict) => {
|
|
|
4617
4525
|
switch (conflict.kind) {
|
|
4618
4526
|
case "id_taken":
|
|
4619
4527
|
return ` conflict: ${conflict.resource} ${conflict.id} is declared by Package ${conflict.package} in this layer`;
|
|
4620
|
-
case "
|
|
4621
|
-
return ` conflict: a
|
|
4622
|
-
case "
|
|
4528
|
+
case "not_for_team":
|
|
4529
|
+
return ` conflict: a Team Package cannot declare ${conflict.declaration}`;
|
|
4530
|
+
case "schedule_team":
|
|
4623
4531
|
return ` conflict: schedule ${conflict.key} ${scheduleProblems[conflict.problem]}`;
|
|
4624
4532
|
case "loop_unresolved":
|
|
4625
4533
|
return ` conflict: schedule ${conflict.key} starts Loop ${conflict.loop}, which will not resolve after this deploy`;
|
|
@@ -4629,8 +4537,8 @@ var conflictLine = (conflict) => {
|
|
|
4629
4537
|
return ` conflict: kit ${conflict.key} ${kitSourceProblems[conflict.problem]}`;
|
|
4630
4538
|
case "kit_policy_disabled":
|
|
4631
4539
|
return ` conflict: kit assignment ${conflict.key} sets Workspace policy, which this Workspace's rollout does not allow`;
|
|
4632
|
-
case "
|
|
4633
|
-
return ` conflict: kit assignment ${conflict.key} names
|
|
4540
|
+
case "kit_team_unknown":
|
|
4541
|
+
return ` conflict: kit assignment ${conflict.key} names Team ${conflict.team}, which this Workspace does not have`;
|
|
4634
4542
|
}
|
|
4635
4543
|
};
|
|
4636
4544
|
var driftValue = (drift, side) => encodeJsonStringSync(toJsonValue(drift[side]));
|
|
@@ -4747,19 +4655,6 @@ function devCommand(args) {
|
|
|
4747
4655
|
return yield* new CliError({ message: `Run ${settled.run.id} ${settled.reason}` });
|
|
4748
4656
|
});
|
|
4749
4657
|
}
|
|
4750
|
-
var workspaceRepoBinding = (client, workspaceId, projectId) => gen(function* () {
|
|
4751
|
-
const remote = yield* (yield* Git)(["remote", "get-url", "origin"]).pipe(orElseSucceed(() => ""));
|
|
4752
|
-
if (remote === "")
|
|
4753
|
-
return yield* new CliError({ message: "Run kitman deploy bind inside a git checkout with an origin" });
|
|
4754
|
-
const projects = (yield* client.listProjects(workspaceId)).items.map((project) => project.id);
|
|
4755
|
-
const ordered = projectId === undefined ? projects : [projectId, ...projects.filter((id) => id !== projectId)];
|
|
4756
|
-
for (const id of ordered) {
|
|
4757
|
-
const binding = (yield* client.listRepoBindings(id)).items.find((candidate) => repositoryKey(candidate.repositoryUrl ?? `https://${candidate.host}/${candidate.owner}/${candidate.name}`) === repositoryKey(remote));
|
|
4758
|
-
if (binding !== undefined)
|
|
4759
|
-
return binding;
|
|
4760
|
-
}
|
|
4761
|
-
return yield* new CliError({ message: `No repository of the Workspace matches ${remote.trim()}` });
|
|
4762
|
-
});
|
|
4763
4658
|
function deployBindingCommand(args) {
|
|
4764
4659
|
return gen(function* () {
|
|
4765
4660
|
const command = parseCommand(args);
|
|
@@ -4767,12 +4662,12 @@ function deployBindingCommand(args) {
|
|
|
4767
4662
|
const print = (value, lines) => command.flags.json ? printJson(value) : forEach(lines(value), (line) => log(line), { discard: true });
|
|
4768
4663
|
switch (command.action) {
|
|
4769
4664
|
case "bindings":
|
|
4770
|
-
return yield* print(yield* client.listDeployBindings(workspaceId), (listed) => listed.items.length === 0 ? ["No Deploy Bindings in the Workspace"] : listed.items.map((binding) => `${binding.id} ${binding.packageName} ${binding.layer === "workspace" ? "workspace" : `
|
|
4665
|
+
return yield* print(yield* client.listDeployBindings(workspaceId), (listed) => listed.items.length === 0 ? ["No Deploy Bindings in the Workspace"] : listed.items.map((binding) => `${binding.id} ${binding.packageName} ${binding.layer === "workspace" ? "workspace" : `team ${binding.teamId}`} ${binding.repository} ${binding.branch} by ${binding.createdByUserId}`));
|
|
4771
4666
|
case "bind": {
|
|
4772
4667
|
const branch = command.flags.branch;
|
|
4773
4668
|
if (branch === undefined)
|
|
4774
4669
|
return yield* new CliError({ message: "deploy bind requires --branch <branch>" });
|
|
4775
|
-
const repoBinding = yield*
|
|
4670
|
+
const repoBinding = yield* currentRepoBinding(client, workspaceId);
|
|
4776
4671
|
const built = yield* (yield* PackageBuilder)(".");
|
|
4777
4672
|
return yield* print(yield* client.createDeployBinding(workspaceId, {
|
|
4778
4673
|
repoBindingId: repoBinding.id,
|
|
@@ -4848,31 +4743,32 @@ function constraintCommand(args) {
|
|
|
4848
4743
|
return gen(function* () {
|
|
4849
4744
|
const command = parseCommand(args.length === 1 ? [...args, "list"] : args);
|
|
4850
4745
|
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4851
|
-
const
|
|
4852
|
-
const binding = yield* currentRepoBinding(client,
|
|
4746
|
+
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
4747
|
+
const binding = yield* currentRepoBinding(client, workspaceId);
|
|
4853
4748
|
const print = (value, lines) => command.flags.json ? printJson(value) : forEach(lines(value), (line) => log(line), { discard: true });
|
|
4854
4749
|
switch (command.action) {
|
|
4855
4750
|
case "list":
|
|
4856
|
-
return yield* print(yield* client.listStandingConstraints(
|
|
4751
|
+
return yield* print(yield* client.listStandingConstraints(workspaceId, binding.id), (listed) => listed.items.length === 0 ? [`No standing constraints for ${binding.owner}/${binding.name}`] : listed.items.map((constraint) => `${constraint.id}: ${constraint.text}`));
|
|
4857
4752
|
case "add": {
|
|
4858
4753
|
const text = command.args.join(" ");
|
|
4859
4754
|
if (text.trim() === "")
|
|
4860
4755
|
return yield* new CliError({ message: "constraint add requires a constraint" });
|
|
4861
|
-
return yield* print(yield* client.addStandingConstraint(
|
|
4756
|
+
return yield* print(yield* client.addStandingConstraint(workspaceId, binding.id, text), (added) => [
|
|
4862
4757
|
`Added ${added.id}: ${added.text}`
|
|
4863
4758
|
]);
|
|
4864
4759
|
}
|
|
4865
4760
|
case "remove":
|
|
4866
|
-
return yield* print(yield* client.removeStandingConstraint(
|
|
4761
|
+
return yield* print(yield* client.removeStandingConstraint(workspaceId, binding.id, requiredArg(command, "a constraint")), (removed) => [`Removed ${removed.removed}`]);
|
|
4867
4762
|
default:
|
|
4868
4763
|
return yield* new CliError({ message: `Unknown constraint command: ${command.action}` });
|
|
4869
4764
|
}
|
|
4870
4765
|
});
|
|
4871
4766
|
}
|
|
4872
|
-
var
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4767
|
+
var validateRunDisplayStates = (value) => {
|
|
4768
|
+
const states = value.split(",").flatMap((named) => RunDisplayStates.filter((known) => known === named));
|
|
4769
|
+
if (states.length !== value.split(",").length)
|
|
4770
|
+
throw new Error(`--state must be one or more of: ${RunDisplayStates.join(", ")}`);
|
|
4771
|
+
return states;
|
|
4876
4772
|
};
|
|
4877
4773
|
function triggerCommand(args) {
|
|
4878
4774
|
return gen(function* () {
|
|
@@ -4894,10 +4790,10 @@ function scheduleCommand(args) {
|
|
|
4894
4790
|
const apiUrl = yield* resolveApiUrl(command.flags.apiUrl);
|
|
4895
4791
|
const client = yield* loopClientFor(apiUrl);
|
|
4896
4792
|
const operational = createOperationalCliClient(client);
|
|
4897
|
-
const
|
|
4793
|
+
const teamId = yield* optionalActiveTeamId(command.flags);
|
|
4898
4794
|
const response = yield* operational.publishSchedule(scheduleId, {
|
|
4899
4795
|
workspaceId: yield* activeWorkspaceId(command.flags),
|
|
4900
|
-
...
|
|
4796
|
+
...teamId === undefined ? undefined : { teamId },
|
|
4901
4797
|
...yield* operationalInput(command, "schedule")
|
|
4902
4798
|
}, yield* idempotencyKey(command.flags.idempotencyKey, "schedule-publish"));
|
|
4903
4799
|
return yield* printOperationalResult(command, response);
|
|
@@ -5015,8 +4911,8 @@ function readString(value, keys) {
|
|
|
5015
4911
|
function readNumber(value, keys) {
|
|
5016
4912
|
return fieldValues(value, keys).find(isNumber);
|
|
5017
4913
|
}
|
|
5018
|
-
function loopListQuery(
|
|
5019
|
-
const query = new URLSearchParams({
|
|
4914
|
+
function loopListQuery(teamId, limit) {
|
|
4915
|
+
const query = new URLSearchParams({ teamId });
|
|
5020
4916
|
if (limit !== undefined)
|
|
5021
4917
|
query.set("limit", String(limit));
|
|
5022
4918
|
return `?${query.toString()}`;
|
|
@@ -5041,30 +4937,30 @@ function runnerOwnershipFromCommand(command, required) {
|
|
|
5041
4937
|
if (scopeType === undefined) {
|
|
5042
4938
|
if (required)
|
|
5043
4939
|
throw new Error("runner rescope requires --scope-type");
|
|
5044
|
-
if (command.flags.scopeId !== undefined || command.flags.
|
|
5045
|
-
throw new Error("--scope-id and --
|
|
4940
|
+
if (command.flags.scopeId !== undefined || command.flags.teamAccess !== undefined)
|
|
4941
|
+
throw new Error("--scope-id and --team-access require --scope-type");
|
|
5046
4942
|
return;
|
|
5047
4943
|
}
|
|
5048
4944
|
if (scopeType === "workspace") {
|
|
5049
4945
|
if (command.flags.scopeId !== undefined)
|
|
5050
|
-
throw new Error("workspace runner scope does not accept --scope-id; use --
|
|
5051
|
-
return { kind: "workspace",
|
|
4946
|
+
throw new Error("workspace runner scope does not accept --scope-id; use --team-access selected and grants");
|
|
4947
|
+
return { kind: "workspace", teamAccess: command.flags.teamAccess ?? "all" };
|
|
5052
4948
|
}
|
|
5053
|
-
if (scopeType === "
|
|
5054
|
-
const
|
|
5055
|
-
if (!
|
|
5056
|
-
throw new Error("
|
|
5057
|
-
if (command.flags.
|
|
5058
|
-
throw new Error("
|
|
5059
|
-
return { kind: "
|
|
4949
|
+
if (scopeType === "team") {
|
|
4950
|
+
const teamId = command.flags.scopeId ?? command.flags.team;
|
|
4951
|
+
if (!teamId)
|
|
4952
|
+
throw new Error("team runner scope requires --scope-id <team-id>");
|
|
4953
|
+
if (command.flags.teamAccess !== undefined)
|
|
4954
|
+
throw new Error("team runner scope does not accept --team-access");
|
|
4955
|
+
return { kind: "team", teamId };
|
|
5060
4956
|
}
|
|
5061
4957
|
if (scopeType === "personal") {
|
|
5062
4958
|
const ownerUserId = command.flags.scopeId;
|
|
5063
4959
|
if (!ownerUserId)
|
|
5064
4960
|
throw new Error("personal runner scope requires --scope-id <owner-user-id>");
|
|
5065
|
-
return { kind: "personal", ownerUserId,
|
|
4961
|
+
return { kind: "personal", ownerUserId, teamAccess: command.flags.teamAccess ?? "all" };
|
|
5066
4962
|
}
|
|
5067
|
-
throw new Error("--scope-type must be workspace,
|
|
4963
|
+
throw new Error("--scope-type must be workspace, team, or personal");
|
|
5068
4964
|
}
|
|
5069
4965
|
function loopRunnerCapabilities() {
|
|
5070
4966
|
const capabilities = defaultRunnerCapabilities();
|
|
@@ -5125,7 +5021,7 @@ function resolveCommandConfig(flags = {}) {
|
|
|
5125
5021
|
const localConfig = yield* readUserConfig(localConfigPathValue);
|
|
5126
5022
|
const configFlags = {
|
|
5127
5023
|
...flags.apiUrl ? { apiUrl: flags.apiUrl } : undefined,
|
|
5128
|
-
...flags.
|
|
5024
|
+
...flags.team ? { team: flags.team } : undefined,
|
|
5129
5025
|
...flags.workspace ? { workspace: flags.workspace } : undefined
|
|
5130
5026
|
};
|
|
5131
5027
|
const envApiUrl = yield* apiUrlFromEnv;
|
|
@@ -5148,10 +5044,10 @@ function resolveCommandConfig(flags = {}) {
|
|
|
5148
5044
|
["localConfig", localConfig?.activeWorkspaceId],
|
|
5149
5045
|
["userConfig", userConfig?.activeWorkspaceId]
|
|
5150
5046
|
], "none"),
|
|
5151
|
-
|
|
5152
|
-
["flag", flags.
|
|
5153
|
-
["localConfig", localConfig?.
|
|
5154
|
-
["userConfig", userConfig?.
|
|
5047
|
+
activeTeam: firstConfigSource([
|
|
5048
|
+
["flag", flags.team],
|
|
5049
|
+
["localConfig", localConfig?.activeTeamId],
|
|
5050
|
+
["userConfig", userConfig?.activeTeamId]
|
|
5155
5051
|
], "none")
|
|
5156
5052
|
},
|
|
5157
5053
|
userConfigPath: yield* userConfigPath,
|
|
@@ -5176,36 +5072,36 @@ function optionalActiveWorkspaceId(flags) {
|
|
|
5176
5072
|
return config.activeWorkspaceId;
|
|
5177
5073
|
});
|
|
5178
5074
|
}
|
|
5179
|
-
function
|
|
5075
|
+
function activeTeamId(flags) {
|
|
5180
5076
|
return gen(function* () {
|
|
5181
5077
|
const { config } = yield* resolveCommandConfig(flags);
|
|
5182
|
-
if (!config.
|
|
5078
|
+
if (!config.activeTeamId) {
|
|
5183
5079
|
return yield* new CliError({
|
|
5184
|
-
message: "No active
|
|
5080
|
+
message: "No active Team. Run kitman team use <team> or pass --team <team>."
|
|
5185
5081
|
});
|
|
5186
5082
|
}
|
|
5187
|
-
return config.
|
|
5083
|
+
return config.activeTeamId;
|
|
5188
5084
|
});
|
|
5189
5085
|
}
|
|
5190
|
-
function
|
|
5086
|
+
function optionalActiveTeamId(flags) {
|
|
5191
5087
|
return gen(function* () {
|
|
5192
5088
|
const { config } = yield* resolveCommandConfig(flags);
|
|
5193
|
-
return config.
|
|
5089
|
+
return config.activeTeamId;
|
|
5194
5090
|
});
|
|
5195
5091
|
}
|
|
5196
5092
|
function writeActiveWorkspace(apiUrl, workspaceId, local) {
|
|
5197
5093
|
return gen(function* () {
|
|
5198
5094
|
const path = local ? yield* localConfigPath : undefined;
|
|
5199
5095
|
const existing = yield* readUserConfig(path);
|
|
5200
|
-
const {
|
|
5096
|
+
const { activeTeamId: _activeTeamId, ...rest } = existing ?? {};
|
|
5201
5097
|
yield* writeUserConfig({ ...rest, apiUrl, activeWorkspaceId: workspaceId }, path);
|
|
5202
5098
|
});
|
|
5203
5099
|
}
|
|
5204
|
-
function
|
|
5100
|
+
function writeActiveTeam(apiUrl, workspaceId, teamId, local) {
|
|
5205
5101
|
return gen(function* () {
|
|
5206
5102
|
const path = local ? yield* localConfigPath : undefined;
|
|
5207
5103
|
const existing = yield* readUserConfig(path);
|
|
5208
|
-
yield* writeUserConfig({ ...existing, apiUrl, activeWorkspaceId: workspaceId,
|
|
5104
|
+
yield* writeUserConfig({ ...existing, apiUrl, activeWorkspaceId: workspaceId, activeTeamId: teamId }, path);
|
|
5209
5105
|
});
|
|
5210
5106
|
}
|
|
5211
5107
|
function resolveWorkspace(client, workspaceRef) {
|
|
@@ -5223,14 +5119,8 @@ function requiredArg(command, name) {
|
|
|
5223
5119
|
throw new Error(`${command.group} ${command.action} requires ${name}`);
|
|
5224
5120
|
return value;
|
|
5225
5121
|
}
|
|
5226
|
-
function
|
|
5227
|
-
return {
|
|
5228
|
-
id: project.id,
|
|
5229
|
-
name: project.name,
|
|
5230
|
-
slug: project.slug,
|
|
5231
|
-
open: project.openTaskCount,
|
|
5232
|
-
blocked: project.blockedTaskCount
|
|
5233
|
-
};
|
|
5122
|
+
function teamRow(team) {
|
|
5123
|
+
return { id: team.id, key: team.key, name: team.name, members: team.memberCount };
|
|
5234
5124
|
}
|
|
5235
5125
|
function kitRow(kit) {
|
|
5236
5126
|
return { id: kit.id, name: kit.name, slug: kit.slug, kind: kit.kind, description: kit.description };
|
|
@@ -5317,18 +5207,19 @@ function repoBindingCredentialRefLabel(ref) {
|
|
|
5317
5207
|
}
|
|
5318
5208
|
function runnerRow(runner) {
|
|
5319
5209
|
const ownership = runner.ownership;
|
|
5320
|
-
let
|
|
5321
|
-
if (ownership.kind === "
|
|
5322
|
-
|
|
5323
|
-
else if (ownership.
|
|
5324
|
-
|
|
5210
|
+
let effectiveTeamAccess;
|
|
5211
|
+
if (ownership.kind === "team")
|
|
5212
|
+
effectiveTeamAccess = `team:${ownership.teamId}`;
|
|
5213
|
+
else if (ownership.teamAccess === "selected")
|
|
5214
|
+
effectiveTeamAccess = runner.teamGrantIds.join(",") || "selected_none";
|
|
5325
5215
|
else
|
|
5326
|
-
|
|
5216
|
+
effectiveTeamAccess = ownership.kind === "workspace" ? "workspace_all" : `personal:${ownership.ownerUserId}`;
|
|
5327
5217
|
return {
|
|
5328
5218
|
id: runner.id,
|
|
5329
5219
|
name: runner.name,
|
|
5330
5220
|
status: runner.status,
|
|
5331
|
-
|
|
5221
|
+
state: runner.state.kind,
|
|
5222
|
+
credentialStatus: runner.credentialStatus,
|
|
5332
5223
|
agentIdentityId: runner.agentIdentityId,
|
|
5333
5224
|
harnesses: runner.capabilities.harnesses.join(","),
|
|
5334
5225
|
defaultHarness: runner.capabilities.defaultHarness ?? "",
|
|
@@ -5337,10 +5228,10 @@ function runnerRow(runner) {
|
|
|
5337
5228
|
sandboxes: runner.capabilities.sandboxBackends.join(","),
|
|
5338
5229
|
labels: runner.labels.join(","),
|
|
5339
5230
|
ownership: ownership.kind,
|
|
5340
|
-
|
|
5231
|
+
teamAccess: ownership.kind === "team" ? undefined : ownership.teamAccess,
|
|
5341
5232
|
ownerUserId: ownership.kind === "personal" ? ownership.ownerUserId : undefined,
|
|
5342
|
-
|
|
5343
|
-
|
|
5233
|
+
scopedTeamId: ownership.kind === "team" ? ownership.teamId : undefined,
|
|
5234
|
+
effectiveTeamAccess,
|
|
5344
5235
|
managementBoundary: "workspace_operator_only",
|
|
5345
5236
|
schedulingBoundary: "scope_and_identity_gated",
|
|
5346
5237
|
lastHeartbeatAt: runner.lastHeartbeatAt
|
|
@@ -5363,7 +5254,8 @@ function runnerDoctorRow(runner) {
|
|
|
5363
5254
|
id: runner.id,
|
|
5364
5255
|
name: runner.name,
|
|
5365
5256
|
status: runner.status,
|
|
5366
|
-
|
|
5257
|
+
state: runner.state.kind,
|
|
5258
|
+
credentialStatus: runner.credentialStatus,
|
|
5367
5259
|
runs: runner.capabilities.runs?.supported === true,
|
|
5368
5260
|
harnesses: runner.capabilities.harnesses.join(","),
|
|
5369
5261
|
defaultHarness: runner.capabilities.defaultHarness ?? "",
|
|
@@ -5427,8 +5319,8 @@ function sessionUpdateRow(update) {
|
|
|
5427
5319
|
function dependencyTaskLabel(task) {
|
|
5428
5320
|
if (task.redacted) {
|
|
5429
5321
|
const taskId = task.taskId ?? "hidden-task";
|
|
5430
|
-
const
|
|
5431
|
-
return `[redacted] ${taskId}${
|
|
5322
|
+
const team = task.teamId === undefined ? "" : ` (${task.teamId})`;
|
|
5323
|
+
return `[redacted] ${taskId}${team}`;
|
|
5432
5324
|
}
|
|
5433
5325
|
return `${task.taskKey} ${task.title} [${task.state.name}]`;
|
|
5434
5326
|
}
|
|
@@ -5456,7 +5348,7 @@ function downloadPresignedAsset(download, outputPath) {
|
|
|
5456
5348
|
return gen(function* () {
|
|
5457
5349
|
const fs = yield* FileSystem;
|
|
5458
5350
|
const failed = (cause) => new CliError({ message: `Asset download failed: ${String(cause)}` });
|
|
5459
|
-
const response = yield* send(
|
|
5351
|
+
const response = yield* send(make9(download.method)(download.url), failed);
|
|
5460
5352
|
if (response.status < 200 || response.status >= 300)
|
|
5461
5353
|
return yield* new CliError({ message: `Asset download failed with ${response.status}` });
|
|
5462
5354
|
const body = yield* response.arrayBuffer.pipe(mapError((error) => failed(error.message)));
|
|
@@ -5613,15 +5505,15 @@ function parseRepoBindingCredentialRef(value) {
|
|
|
5613
5505
|
}
|
|
5614
5506
|
function repoBindingInput2(command) {
|
|
5615
5507
|
if (!command.flags.provider)
|
|
5616
|
-
throw new Error("
|
|
5508
|
+
throw new Error("repo add requires --provider");
|
|
5617
5509
|
if (!command.flags.host)
|
|
5618
|
-
throw new Error("
|
|
5510
|
+
throw new Error("repo add requires --host");
|
|
5619
5511
|
if (!command.flags.owner)
|
|
5620
|
-
throw new Error("
|
|
5512
|
+
throw new Error("repo add requires --owner");
|
|
5621
5513
|
if (!command.flags.name)
|
|
5622
|
-
throw new Error("
|
|
5514
|
+
throw new Error("repo add requires --name");
|
|
5623
5515
|
if (!command.flags.defaultBranch)
|
|
5624
|
-
throw new Error("
|
|
5516
|
+
throw new Error("repo add requires --default-branch");
|
|
5625
5517
|
return {
|
|
5626
5518
|
provider: command.flags.provider,
|
|
5627
5519
|
host: command.flags.host,
|
|
@@ -5848,5 +5740,5 @@ function cliErrorJson(error) {
|
|
|
5848
5740
|
var program = runEffect(args).pipe(tapCause((cause) => {
|
|
5849
5741
|
const error2 = squash(cause);
|
|
5850
5742
|
return args.includes("--json") ? printJson(cliErrorJson(error2)) : error(error2 instanceof Error ? error2.message : String(error2));
|
|
5851
|
-
}), provide3(
|
|
5743
|
+
}), provide3(layer));
|
|
5852
5744
|
runMain(program, { disableErrorReporting: true });
|