@kitman-sh/cli 0.7.5 → 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-a44szh8v.js → chunk-2npvts4c.js} +315 -4
- package/dist/{chunk-0pre510a.js → chunk-64et31rh.js} +142 -12
- package/dist/{chunk-tk0gy9wq.js → chunk-8m4wzjt8.js} +16 -426
- package/dist/chunk-ad124cbc.js +132 -0
- package/dist/{chunk-sq2brwmh.js → chunk-dbtnt79b.js} +1 -1
- package/dist/{chunk-hpfcwnd9.js → chunk-djtdn35f.js} +18 -9
- package/dist/{chunk-1f7qe00x.js → chunk-fbb0fdnj.js} +18 -7
- package/dist/{chunk-p2wk2v39.js → chunk-fpyy4rpd.js} +8501 -6615
- package/dist/{chunk-64cz9grx.js → chunk-m1086bf9.js} +989 -386
- package/dist/{chunk-vz3v0f4j.js → chunk-m6yymqfr.js} +481 -138
- package/dist/{chunk-xnzxpert.js → chunk-nmgr9w6v.js} +3 -3
- package/dist/index.js +1294 -1423
- package/package.json +3 -1
- package/dist/chunk-4gc7b9qt.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,103 +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
|
-
|
|
137
|
-
ContextPackModes,
|
|
138
|
-
DefaultContextPackMode,
|
|
139
|
-
MemoryStatuses,
|
|
140
|
-
MemoryScopes,
|
|
141
|
-
MaxMemoryListLimit,
|
|
96
|
+
redactDurableSurface,
|
|
97
|
+
AgentHarnessIds,
|
|
142
98
|
RepoBindingProviders,
|
|
143
99
|
RepoBindingCredentialProviderTypes,
|
|
144
|
-
|
|
145
|
-
|
|
100
|
+
ContextPackModes,
|
|
101
|
+
DefaultContextPackMode,
|
|
102
|
+
ProposalStatuses,
|
|
103
|
+
ProposalTargetTypes,
|
|
146
104
|
RunDisplayStates,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
105
|
+
defaultRunnerCapabilities,
|
|
106
|
+
isSyncKitHashValid,
|
|
107
|
+
isSyncKitSafe,
|
|
108
|
+
resolveSyncKit,
|
|
151
109
|
KitKinds,
|
|
152
110
|
KitInstallationScopes,
|
|
153
111
|
KitCreate,
|
|
154
112
|
KitUpdate,
|
|
155
|
-
|
|
113
|
+
MemoryStatuses,
|
|
114
|
+
MemoryScopes,
|
|
115
|
+
MaxMemoryListLimit,
|
|
116
|
+
DecisionStatuses,
|
|
117
|
+
FixFlowRecoveryActions,
|
|
118
|
+
KitmanAgentCapabilityNames,
|
|
119
|
+
isRunFileRefusalReason,
|
|
120
|
+
runFileRefusalWords,
|
|
156
121
|
repositoryKey,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
resolveSyncKit,
|
|
122
|
+
createId,
|
|
123
|
+
digestSha256,
|
|
124
|
+
sha256Hasher,
|
|
161
125
|
decodeJsonString,
|
|
162
126
|
decodeJsonStringOption,
|
|
163
127
|
encodeJsonStringSync,
|
|
@@ -165,6 +129,17 @@ import {
|
|
|
165
129
|
encodeUnknownJsonStringSync,
|
|
166
130
|
option2,
|
|
167
131
|
String3,
|
|
132
|
+
raw,
|
|
133
|
+
make9,
|
|
134
|
+
get2,
|
|
135
|
+
put,
|
|
136
|
+
HttpClient,
|
|
137
|
+
execute,
|
|
138
|
+
Fetch,
|
|
139
|
+
layer2,
|
|
140
|
+
runDisplayLabels,
|
|
141
|
+
KitmanClientError,
|
|
142
|
+
KitmanEffectClient,
|
|
168
143
|
CliError,
|
|
169
144
|
DefaultApiUrl,
|
|
170
145
|
resolveConfig,
|
|
@@ -183,15 +158,12 @@ import {
|
|
|
183
158
|
removeAllSessions,
|
|
184
159
|
agentCredentialForApi,
|
|
185
160
|
storeAgentCredential,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
layer4,
|
|
193
|
-
KitmanEffectClient,
|
|
194
|
-
digestSha256,
|
|
161
|
+
removeAgentCredential,
|
|
162
|
+
normalizeApiUrl,
|
|
163
|
+
isStoredAgentCredential,
|
|
164
|
+
RpcClientError,
|
|
165
|
+
RunnerHeaders,
|
|
166
|
+
RunnerRpcError,
|
|
195
167
|
parseCallbackUrl,
|
|
196
168
|
revokeToken,
|
|
197
169
|
toStoredSession,
|
|
@@ -202,941 +174,286 @@ import {
|
|
|
202
174
|
refreshSession,
|
|
203
175
|
createAgentAuthBearerToken,
|
|
204
176
|
createAgentAuthSignedFetch,
|
|
205
|
-
RunnerHeaders,
|
|
206
177
|
setupMcpAgentCredential,
|
|
207
178
|
generateAgentKeyPair,
|
|
179
|
+
CommandFailed2,
|
|
208
180
|
runCommand2,
|
|
209
181
|
detectHarnesses2,
|
|
210
182
|
herdrRunnerCapabilities2
|
|
211
|
-
} from "./chunk-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
// node_modules/.bun/@effect+platform-node-shared@4.0.4+b8cbb99ebc524863/node_modules/@effect/platform-node-shared/dist/NodeSink.js
|
|
229
|
-
var fromWritable = (options) => fromChannel(mapDone(fromWritableChannel(options), (_) => [_]));
|
|
230
|
-
var fromWritableChannel = (options) => fromTransform((pull) => {
|
|
231
|
-
const writable = options.evaluate();
|
|
232
|
-
return succeed5(pullIntoWritable({
|
|
233
|
-
...options,
|
|
234
|
-
writable,
|
|
235
|
-
pull
|
|
236
|
-
}));
|
|
237
|
-
});
|
|
238
|
-
var pullIntoWritable = (options) => options.pull.pipe(flatMap2((chunk) => {
|
|
239
|
-
let i = 0;
|
|
240
|
-
return callback((resume) => {
|
|
241
|
-
let cancelled = false;
|
|
242
|
-
const loop = () => {
|
|
243
|
-
for (;i < chunk.length; ) {
|
|
244
|
-
if (cancelled) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
const success = options.writable.write(chunk[i++], options.encoding);
|
|
248
|
-
if (!success) {
|
|
249
|
-
if (!cancelled) {
|
|
250
|
-
options.writable.once("drain", loop);
|
|
251
|
-
}
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (!cancelled) {
|
|
256
|
-
resume(void_2);
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
loop();
|
|
260
|
-
return sync(() => {
|
|
261
|
-
cancelled = true;
|
|
262
|
-
options.writable.off("drain", loop);
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
}), forever({
|
|
266
|
-
disableYield: true
|
|
267
|
-
}), options.endOnDone !== false ? catchDone((_) => {
|
|
268
|
-
if ("closed" in options.writable && options.writable.closed) {
|
|
269
|
-
return done(_);
|
|
270
|
-
}
|
|
271
|
-
return callback((resume) => {
|
|
272
|
-
const onFinish = () => resume(done(_));
|
|
273
|
-
options.writable.once("finish", onFinish);
|
|
274
|
-
options.writable.end();
|
|
275
|
-
return sync(() => {
|
|
276
|
-
options.writable.off("finish", onFinish);
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
}) : identity, raceFirst(callback((resume) => {
|
|
280
|
-
const onError = (error) => resume(fail4(options.onError(error)));
|
|
281
|
-
options.writable.once("error", onError);
|
|
282
|
-
return sync(() => {
|
|
283
|
-
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);
|
|
284
198
|
});
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
chunkSize: options.chunkSize,
|
|
294
|
-
closeOnDone: options.closeOnDone
|
|
295
|
-
}));
|
|
296
|
-
var readableToPullUnsafe = (options) => {
|
|
297
|
-
const readable = options.readable;
|
|
298
|
-
const closeOnDone = options.closeOnDone ?? true;
|
|
299
|
-
const exit = options.exit ?? make8(undefined);
|
|
300
|
-
const latch = options.latch ?? makeUnsafe3(false);
|
|
301
|
-
function onReadable() {
|
|
302
|
-
latch.openUnsafe();
|
|
303
|
-
}
|
|
304
|
-
function onError(error) {
|
|
305
|
-
exit.current = fail2(options.onError(error));
|
|
306
|
-
latch.openUnsafe();
|
|
307
|
-
}
|
|
308
|
-
function onEnd() {
|
|
309
|
-
exit.current = fail2(Done());
|
|
310
|
-
latch.openUnsafe();
|
|
311
|
-
}
|
|
312
|
-
readable.on("readable", onReadable);
|
|
313
|
-
readable.once("error", onError);
|
|
314
|
-
readable.once("end", onEnd);
|
|
315
|
-
const pull = suspend(function loop() {
|
|
316
|
-
let item = options.readable.read(options.chunkSize);
|
|
317
|
-
if (item === null) {
|
|
318
|
-
if (exit.current) {
|
|
319
|
-
return exit.current;
|
|
320
|
-
}
|
|
321
|
-
if (readable.readableEnded) {
|
|
322
|
-
return fail4(Done());
|
|
323
|
-
}
|
|
324
|
-
latch.closeUnsafe();
|
|
325
|
-
return flatMap2(latch.await, loop);
|
|
326
|
-
}
|
|
327
|
-
const chunk = of(item);
|
|
328
|
-
while (true) {
|
|
329
|
-
item = options.readable.read(options.chunkSize);
|
|
330
|
-
if (item === null)
|
|
331
|
-
break;
|
|
332
|
-
chunk.push(item);
|
|
333
|
-
}
|
|
334
|
-
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 };
|
|
335
207
|
});
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
};
|
|
345
|
-
var defaultOnError = (error) => new UnknownError(error);
|
|
346
|
-
|
|
347
|
-
// node_modules/.bun/@effect+platform-node-shared@4.0.4+b8cbb99ebc524863/node_modules/@effect/platform-node-shared/dist/NodeChildProcessSpawner.js
|
|
348
|
-
var toError = (error) => error instanceof globalThis.Error ? error : new globalThis.Error(String(error));
|
|
349
|
-
var toPlatformError = (method, error, command) => {
|
|
350
|
-
const {
|
|
351
|
-
commands
|
|
352
|
-
} = flattenCommand(command);
|
|
353
|
-
const commandStr = commands.reduce((acc, curr) => {
|
|
354
|
-
const cmd = `${curr.command} ${curr.args.join(" ")}`;
|
|
355
|
-
return acc.length === 0 ? cmd : `${acc} | ${cmd}`;
|
|
356
|
-
}, "");
|
|
357
|
-
return handleErrnoException("ChildProcess", method)(error, [commandStr]);
|
|
358
|
-
};
|
|
359
|
-
var processGroupGraceMillis = 1000;
|
|
360
|
-
var processGroupPollIntervalMillis = 10;
|
|
361
|
-
var handleInputErrors = (writable) => {
|
|
362
|
-
const onError = () => {};
|
|
363
|
-
writable.on("error", onError);
|
|
364
|
-
writable.once("close", () => writable.off("error", onError));
|
|
365
|
-
};
|
|
366
|
-
var isProcessAlive = (childProcess, exitSignal) => {
|
|
367
|
-
if (!isDoneUnsafe(exitSignal)) {
|
|
368
|
-
return true;
|
|
369
|
-
}
|
|
370
|
-
if (globalThis.process.platform === "win32") {
|
|
371
|
-
return false;
|
|
372
|
-
}
|
|
373
|
-
try {
|
|
374
|
-
globalThis.process.kill(-childProcess.pid, 0);
|
|
375
|
-
return true;
|
|
376
|
-
} catch {
|
|
377
|
-
return false;
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
var taskkill = (childProcess, onExit = () => {}) => {
|
|
381
|
-
if (childProcess.exitCode !== null || childProcess.signalCode !== null) {
|
|
382
|
-
onExit(null);
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
NodeChildProcess.execFile("taskkill", ["/pid", String(childProcess.pid), "/T", "/F"], {
|
|
386
|
-
windowsHide: true
|
|
387
|
-
}, onExit);
|
|
388
|
-
};
|
|
389
|
-
var make = /* @__PURE__ */ gen(function* () {
|
|
390
|
-
const fs = yield* FileSystem;
|
|
391
|
-
const path = yield* Path;
|
|
392
|
-
const resolveWorkingDirectory = fnUntraced(function* (options) {
|
|
393
|
-
if (isUndefined(options.cwd))
|
|
394
|
-
return;
|
|
395
|
-
yield* fs.access(options.cwd);
|
|
396
|
-
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")));
|
|
397
216
|
});
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
...defaultConfig,
|
|
418
|
-
stream: options.stdin
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
if (isStream(options.stdin)) {
|
|
422
|
-
return {
|
|
423
|
-
...defaultConfig,
|
|
424
|
-
stream: options.stdin
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
return {
|
|
428
|
-
stream: options.stdin.stream,
|
|
429
|
-
encoding: options.stdin.encoding ?? defaultConfig.encoding,
|
|
430
|
-
endOnDone: options.stdin.endOnDone ?? defaultConfig.endOnDone
|
|
431
|
-
};
|
|
432
|
-
};
|
|
433
|
-
const resolveOutputOption = (options, streamName) => {
|
|
434
|
-
const option = options[streamName];
|
|
435
|
-
if (isUndefined(option)) {
|
|
436
|
-
return {
|
|
437
|
-
stream: "pipe"
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
if (typeof option === "string") {
|
|
441
|
-
return {
|
|
442
|
-
stream: option
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
if (isSink(option)) {
|
|
446
|
-
return {
|
|
447
|
-
stream: option
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
return {
|
|
451
|
-
stream: option.stream
|
|
452
|
-
};
|
|
453
|
-
};
|
|
454
|
-
const resolveAdditionalFds = (options) => {
|
|
455
|
-
if (isUndefined(options.additionalFds)) {
|
|
456
|
-
return [];
|
|
457
|
-
}
|
|
458
|
-
const result = [];
|
|
459
|
-
for (const [name, config] of Object.entries(options.additionalFds)) {
|
|
460
|
-
const fd = parseFdName(name);
|
|
461
|
-
if (isNotUndefined(fd)) {
|
|
462
|
-
result.push({
|
|
463
|
-
fd,
|
|
464
|
-
config
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
return result.sort((a, b) => a.fd - b.fd);
|
|
469
|
-
};
|
|
470
|
-
const buildStdioArray = (stdinConfig, stdoutConfig, stderrConfig, additionalFds) => {
|
|
471
|
-
const stdio = [inputToStdioOption(stdinConfig.stream), outputToStdioOption(stdoutConfig.stream), outputToStdioOption(stderrConfig.stream)];
|
|
472
|
-
if (additionalFds.length === 0) {
|
|
473
|
-
return stdio;
|
|
474
|
-
}
|
|
475
|
-
const maxFd = additionalFds.reduce((max, {
|
|
476
|
-
fd
|
|
477
|
-
}) => Math.max(max, fd), 2);
|
|
478
|
-
for (let i = 3;i <= maxFd; i++) {
|
|
479
|
-
stdio[i] = "ignore";
|
|
480
|
-
}
|
|
481
|
-
for (const {
|
|
482
|
-
fd
|
|
483
|
-
} of additionalFds) {
|
|
484
|
-
stdio[fd] = "pipe";
|
|
485
|
-
}
|
|
486
|
-
return stdio;
|
|
487
|
-
};
|
|
488
|
-
const setupAdditionalFds = fnUntraced(function* (command, childProcess, additionalFds) {
|
|
489
|
-
if (additionalFds.length === 0) {
|
|
490
|
-
return {
|
|
491
|
-
getInputFd: () => drain,
|
|
492
|
-
getOutputFd: () => empty2
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
const inputSinks = new Map;
|
|
496
|
-
const outputStreams = new Map;
|
|
497
|
-
for (const {
|
|
498
|
-
config,
|
|
499
|
-
fd
|
|
500
|
-
} of additionalFds) {
|
|
501
|
-
const nodeStream = childProcess.stdio[fd];
|
|
502
|
-
switch (config.type) {
|
|
503
|
-
case "input": {
|
|
504
|
-
let sink = drain;
|
|
505
|
-
if (nodeStream && "write" in nodeStream) {
|
|
506
|
-
handleInputErrors(nodeStream);
|
|
507
|
-
sink = fromWritable({
|
|
508
|
-
evaluate: () => nodeStream,
|
|
509
|
-
onError: (error) => toPlatformError(`fromWritable(fd${fd})`, toError(error), command)
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
if (config.stream) {
|
|
513
|
-
yield* forkScoped(run(config.stream, sink));
|
|
514
|
-
}
|
|
515
|
-
inputSinks.set(fd, sink);
|
|
516
|
-
break;
|
|
517
|
-
}
|
|
518
|
-
case "output": {
|
|
519
|
-
let stream = empty2;
|
|
520
|
-
if (nodeStream && "read" in nodeStream) {
|
|
521
|
-
const passThrough = new PassThrough;
|
|
522
|
-
nodeStream.on("error", (error) => passThrough.destroy(error));
|
|
523
|
-
nodeStream.pipe(passThrough);
|
|
524
|
-
stream = fromReadable({
|
|
525
|
-
evaluate: () => passThrough,
|
|
526
|
-
onError: (error) => toPlatformError(`fromReadable(fd${fd})`, toError(error), command)
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
if (config.sink) {
|
|
530
|
-
stream = transduce(stream, config.sink);
|
|
531
|
-
}
|
|
532
|
-
outputStreams.set(fd, stream);
|
|
533
|
-
break;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
return {
|
|
538
|
-
getInputFd: (fd) => inputSinks.get(fd) ?? drain,
|
|
539
|
-
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
|
|
540
236
|
};
|
|
237
|
+
yield* storeAgentCredential(input.apiUrl, herdrRunnerCredentialName(input.runner.id), credential, input.authPath);
|
|
238
|
+
return credential;
|
|
541
239
|
});
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
encoding: config.encoding
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
if (isStream(config.stream)) {
|
|
554
|
-
return as(forkScoped(run(config.stream, sink)), sink);
|
|
555
|
-
}
|
|
556
|
-
return succeed5(sink);
|
|
557
|
-
});
|
|
558
|
-
const setupChildOutputStreams = (command, childProcess, stdoutConfig, stderrConfig) => {
|
|
559
|
-
let stdout = childProcess.stdout ? (() => {
|
|
560
|
-
const passThrough = new PassThrough;
|
|
561
|
-
childProcess.stdout.on("error", (error) => passThrough.destroy(error));
|
|
562
|
-
childProcess.stdout.pipe(passThrough);
|
|
563
|
-
return fromReadable({
|
|
564
|
-
evaluate: () => passThrough,
|
|
565
|
-
onError: (error) => toPlatformError("fromReadable(stdout)", toError(error), command)
|
|
566
|
-
});
|
|
567
|
-
})() : empty2;
|
|
568
|
-
let stderr = childProcess.stderr ? (() => {
|
|
569
|
-
const passThrough = new PassThrough;
|
|
570
|
-
childProcess.stderr.on("error", (error) => passThrough.destroy(error));
|
|
571
|
-
childProcess.stderr.pipe(passThrough);
|
|
572
|
-
return fromReadable({
|
|
573
|
-
evaluate: () => passThrough,
|
|
574
|
-
onError: (error) => toPlatformError("fromReadable(stderr)", toError(error), command)
|
|
575
|
-
});
|
|
576
|
-
})() : empty2;
|
|
577
|
-
if (isSink(stdoutConfig.stream)) {
|
|
578
|
-
stdout = transduce(stdout, stdoutConfig.stream);
|
|
579
|
-
}
|
|
580
|
-
if (isSink(stderrConfig.stream)) {
|
|
581
|
-
stderr = transduce(stderr, stderrConfig.stream);
|
|
582
|
-
}
|
|
583
|
-
const all = merge2(stdout, stderr);
|
|
584
|
-
return {
|
|
585
|
-
stdout,
|
|
586
|
-
stderr,
|
|
587
|
-
all
|
|
588
|
-
};
|
|
589
|
-
};
|
|
590
|
-
const spawn2 = (command, spawnOptions) => callback((resume) => {
|
|
591
|
-
const deferred = makeUnsafe();
|
|
592
|
-
const handle = NodeChildProcess.spawn(command.command, command.args, spawnOptions);
|
|
593
|
-
handle.on("error", (error) => {
|
|
594
|
-
resume(fail4(toPlatformError("spawn", error, command)));
|
|
595
|
-
});
|
|
596
|
-
handle.on("exit", (...args) => {
|
|
597
|
-
doneUnsafe(deferred, succeed2(args));
|
|
598
|
-
});
|
|
599
|
-
handle.on("spawn", () => {
|
|
600
|
-
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)
|
|
601
248
|
});
|
|
602
|
-
|
|
603
|
-
|
|
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" }
|
|
604
258
|
});
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
taskkill(childProcess, (error) => {
|
|
610
|
-
if (error) {
|
|
611
|
-
resume(fail4(toPlatformError("kill", toError(error), command)));
|
|
612
|
-
} else {
|
|
613
|
-
resume(void_2);
|
|
614
|
-
}
|
|
615
|
-
});
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
return try_({
|
|
619
|
-
try: () => {
|
|
620
|
-
globalThis.process.kill(-childProcess.pid, signal);
|
|
621
|
-
},
|
|
622
|
-
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"]
|
|
623
263
|
});
|
|
624
|
-
|
|
625
|
-
const killProcessGroupOnExit = (childProcess, signal) => {
|
|
626
|
-
if (globalThis.process.platform === "win32") {
|
|
627
|
-
taskkill(childProcess);
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
try {
|
|
631
|
-
globalThis.process.kill(-childProcess.pid, signal);
|
|
632
|
-
} catch {}
|
|
633
|
-
};
|
|
634
|
-
const killProcess = (command, childProcess, signal) => suspend(() => {
|
|
635
|
-
const killed = childProcess.kill(signal);
|
|
636
|
-
if (!killed) {
|
|
637
|
-
const error = new globalThis.Error("Failed to kill child process");
|
|
638
|
-
return fail4(toPlatformError("kill", error, command));
|
|
639
|
-
}
|
|
640
|
-
return void_2;
|
|
641
|
-
});
|
|
642
|
-
const awaitProcessExit = (childProcess, exitSignal, timeoutMillis) => callback((resume) => {
|
|
643
|
-
const deadline = Date.now() + timeoutMillis;
|
|
644
|
-
let timer;
|
|
645
|
-
const stop = () => {
|
|
646
|
-
clearTimeout(timer);
|
|
647
|
-
childProcess.removeListener("exit", poll);
|
|
648
|
-
};
|
|
649
|
-
const poll = () => {
|
|
650
|
-
clearTimeout(timer);
|
|
651
|
-
if (Date.now() >= deadline || !isProcessAlive(childProcess, exitSignal)) {
|
|
652
|
-
stop();
|
|
653
|
-
resume(void_2);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
timer = setTimeout(poll, processGroupPollIntervalMillis);
|
|
657
|
-
};
|
|
658
|
-
childProcess.on("exit", poll);
|
|
659
|
-
poll();
|
|
660
|
-
return sync(stop);
|
|
661
|
-
});
|
|
662
|
-
const terminateProcessGroup = fnUntraced(function* (command, childProcess, exitSignal, options) {
|
|
663
|
-
const signalGroup = (signal) => killProcessGroup(command, childProcess, signal).pipe(catch_(() => killProcess(command, childProcess, signal)));
|
|
664
|
-
yield* signalGroup(options?.killSignal ?? "SIGTERM");
|
|
665
|
-
if (isUndefined(options?.forceKillAfter)) {
|
|
666
|
-
yield* awaitProcessExit(childProcess, exitSignal, processGroupGraceMillis);
|
|
667
|
-
} else {
|
|
668
|
-
yield* awaitProcessExit(childProcess, exitSignal, toMillis(options.forceKillAfter));
|
|
669
|
-
if (isProcessAlive(childProcess, exitSignal)) {
|
|
670
|
-
yield* signalGroup("SIGKILL");
|
|
671
|
-
yield* awaitProcessExit(childProcess, exitSignal, processGroupGraceMillis);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
yield* _await(exitSignal);
|
|
675
|
-
});
|
|
676
|
-
const getSourceStream = (handle, from) => {
|
|
677
|
-
const fromOption = from ?? "stdout";
|
|
678
|
-
switch (fromOption) {
|
|
679
|
-
case "stdout":
|
|
680
|
-
return handle.stdout;
|
|
681
|
-
case "stderr":
|
|
682
|
-
return handle.stderr;
|
|
683
|
-
case "all":
|
|
684
|
-
return handle.all;
|
|
685
|
-
default: {
|
|
686
|
-
const fd = parseFdName(fromOption);
|
|
687
|
-
if (isNotUndefined(fd)) {
|
|
688
|
-
return handle.getOutputFd(fd);
|
|
689
|
-
}
|
|
690
|
-
return handle.stdout;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
};
|
|
694
|
-
const spawnCommand = fnUntraced(function* (cmd) {
|
|
695
|
-
switch (cmd._tag) {
|
|
696
|
-
case "StandardCommand": {
|
|
697
|
-
const stdinConfig = resolveStdinOption(cmd.options);
|
|
698
|
-
const stdoutConfig = resolveOutputOption(cmd.options, "stdout");
|
|
699
|
-
const stderrConfig = resolveOutputOption(cmd.options, "stderr");
|
|
700
|
-
const resolvedAdditionalFds = resolveAdditionalFds(cmd.options);
|
|
701
|
-
let isReferenced = true;
|
|
702
|
-
const cwd = yield* resolveWorkingDirectory(cmd.options);
|
|
703
|
-
const env = resolveEnvironment(cmd.options);
|
|
704
|
-
const stdio = buildStdioArray(stdinConfig, stdoutConfig, stderrConfig, resolvedAdditionalFds);
|
|
705
|
-
const [childProcess, exitSignal] = yield* acquireRelease(spawn2(cmd, buildSpawnOptions(cmd.options, {
|
|
706
|
-
cwd,
|
|
707
|
-
env,
|
|
708
|
-
stdio
|
|
709
|
-
}, process.platform)), fnUntraced(function* ([childProcess, exitSignal]) {
|
|
710
|
-
const exited = yield* isDone(exitSignal);
|
|
711
|
-
if (exited) {
|
|
712
|
-
const [code] = yield* _await(exitSignal);
|
|
713
|
-
if (code !== 0 && isNotNull(code)) {
|
|
714
|
-
yield* ignore(terminateProcessGroup(cmd, childProcess, exitSignal, cmd.options));
|
|
715
|
-
} else if (isReferenced && process.platform !== "win32" && cmd.options.detached !== false) {
|
|
716
|
-
yield* ignore(terminateProcessGroup(cmd, childProcess, exitSignal, cmd.options));
|
|
717
|
-
}
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
if (!isReferenced) {
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
yield* ignore(terminateProcessGroup(cmd, childProcess, exitSignal, cmd.options));
|
|
724
|
-
}));
|
|
725
|
-
const pid = ProcessId(childProcess.pid);
|
|
726
|
-
childProcess.on("exit", (code) => {
|
|
727
|
-
if (code !== 0 && isNotNull(code)) {
|
|
728
|
-
killProcessGroupOnExit(childProcess, cmd.options.killSignal ?? "SIGTERM");
|
|
729
|
-
}
|
|
730
|
-
});
|
|
731
|
-
const reref = sync(() => {
|
|
732
|
-
if (!isReferenced) {
|
|
733
|
-
childProcess.ref();
|
|
734
|
-
isReferenced = true;
|
|
735
|
-
}
|
|
736
|
-
});
|
|
737
|
-
const unref = sync(() => {
|
|
738
|
-
if (isReferenced) {
|
|
739
|
-
childProcess.unref();
|
|
740
|
-
isReferenced = false;
|
|
741
|
-
}
|
|
742
|
-
return reref;
|
|
743
|
-
});
|
|
744
|
-
const stdin = yield* setupChildStdin(cmd, childProcess, stdinConfig);
|
|
745
|
-
const {
|
|
746
|
-
all,
|
|
747
|
-
stderr,
|
|
748
|
-
stdout
|
|
749
|
-
} = setupChildOutputStreams(cmd, childProcess, stdoutConfig, stderrConfig);
|
|
750
|
-
const {
|
|
751
|
-
getInputFd,
|
|
752
|
-
getOutputFd
|
|
753
|
-
} = yield* setupAdditionalFds(cmd, childProcess, resolvedAdditionalFds);
|
|
754
|
-
const isRunning = map3(isDone(exitSignal), (done) => !done);
|
|
755
|
-
const exitCode = flatMap2(_await(exitSignal), ([code, signal]) => {
|
|
756
|
-
if (isNotNull(code)) {
|
|
757
|
-
return succeed5(ExitCode(code));
|
|
758
|
-
}
|
|
759
|
-
const error = new globalThis.Error(`Process interrupted due to receipt of signal: '${signal}'`);
|
|
760
|
-
return fail4(toPlatformError("exitCode", error, cmd));
|
|
761
|
-
});
|
|
762
|
-
const kill = (options) => terminateProcessGroup(cmd, childProcess, exitSignal, options);
|
|
763
|
-
return makeHandle({
|
|
764
|
-
pid,
|
|
765
|
-
exitCode,
|
|
766
|
-
isRunning,
|
|
767
|
-
kill,
|
|
768
|
-
stdin,
|
|
769
|
-
stdout,
|
|
770
|
-
stderr,
|
|
771
|
-
all,
|
|
772
|
-
getInputFd,
|
|
773
|
-
getOutputFd,
|
|
774
|
-
unref
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
case "PipedCommand": {
|
|
778
|
-
const {
|
|
779
|
-
commands,
|
|
780
|
-
pipeOptions
|
|
781
|
-
} = flattenCommand(cmd);
|
|
782
|
-
const [root, ...pipeline] = commands;
|
|
783
|
-
const handles = [yield* spawnCommand(root)];
|
|
784
|
-
for (let i = 0;i < pipeline.length; i++) {
|
|
785
|
-
const command = pipeline[i];
|
|
786
|
-
const options = pipeOptions[i] ?? {};
|
|
787
|
-
const stdinConfig = resolveStdinOption(command.options);
|
|
788
|
-
const sourceStream = unwrap(succeed5(getSourceStream(handles[handles.length - 1], options.from)));
|
|
789
|
-
const toOption = options.to ?? "stdin";
|
|
790
|
-
if (toOption === "stdin") {
|
|
791
|
-
handles.push(yield* spawnCommand(make13(command.command, command.args, {
|
|
792
|
-
...command.options,
|
|
793
|
-
stdin: {
|
|
794
|
-
...stdinConfig,
|
|
795
|
-
stream: sourceStream
|
|
796
|
-
}
|
|
797
|
-
})));
|
|
798
|
-
} else {
|
|
799
|
-
const fd = parseFdName(toOption);
|
|
800
|
-
if (isNotUndefined(fd)) {
|
|
801
|
-
const fdName2 = fdName(fd);
|
|
802
|
-
const existingFds = command.options.additionalFds ?? {};
|
|
803
|
-
handles.push(yield* spawnCommand(make13(command.command, command.args, {
|
|
804
|
-
...command.options,
|
|
805
|
-
additionalFds: {
|
|
806
|
-
...existingFds,
|
|
807
|
-
[fdName2]: {
|
|
808
|
-
type: "input",
|
|
809
|
-
stream: sourceStream
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
})));
|
|
813
|
-
} else {
|
|
814
|
-
handles.push(yield* spawnCommand(make13(command.command, command.args, {
|
|
815
|
-
...command.options,
|
|
816
|
-
stdin: {
|
|
817
|
-
...stdinConfig,
|
|
818
|
-
stream: sourceStream
|
|
819
|
-
}
|
|
820
|
-
})));
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
const handle = handles[handles.length - 1];
|
|
825
|
-
const kill = (options) => forEach([...handles].reverse(), (handle) => ignore(handle.kill(options)), {
|
|
826
|
-
discard: true
|
|
827
|
-
});
|
|
828
|
-
const unref = gen(function* () {
|
|
829
|
-
const rerefs = [];
|
|
830
|
-
for (const handle of handles) {
|
|
831
|
-
rerefs.push(yield* handle.unref);
|
|
832
|
-
}
|
|
833
|
-
return forEach([...rerefs].reverse(), (reref) => reref, {
|
|
834
|
-
discard: true
|
|
835
|
-
});
|
|
836
|
-
});
|
|
837
|
-
return makeHandle({
|
|
838
|
-
pid: handle.pid,
|
|
839
|
-
exitCode: handle.exitCode,
|
|
840
|
-
isRunning: handle.isRunning,
|
|
841
|
-
kill,
|
|
842
|
-
stdin: handles[0].stdin,
|
|
843
|
-
stdout: handle.stdout,
|
|
844
|
-
stderr: handle.stderr,
|
|
845
|
-
all: handle.all,
|
|
846
|
-
getInputFd: handle.getInputFd,
|
|
847
|
-
getOutputFd: handle.getOutputFd,
|
|
848
|
-
unref
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
}
|
|
264
|
+
return { client, rpcBearer };
|
|
852
265
|
});
|
|
853
|
-
|
|
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
|
|
854
279
|
});
|
|
855
|
-
var
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
const flatten = (cmd) => {
|
|
860
|
-
switch (cmd._tag) {
|
|
861
|
-
case "StandardCommand": {
|
|
862
|
-
commands.push(cmd);
|
|
863
|
-
break;
|
|
864
|
-
}
|
|
865
|
-
case "PipedCommand": {
|
|
866
|
-
flatten(cmd.left);
|
|
867
|
-
pipeOptions.push(cmd.options);
|
|
868
|
-
flatten(cmd.right);
|
|
869
|
-
break;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
};
|
|
873
|
-
flatten(command);
|
|
874
|
-
if (commands.length === 0) {
|
|
875
|
-
throw new Error("flattenCommand produced empty commands array");
|
|
876
|
-
}
|
|
877
|
-
const [first, ...rest] = commands;
|
|
878
|
-
const nonEmptyCommands = [first, ...rest];
|
|
879
|
-
return {
|
|
880
|
-
commands: nonEmptyCommands,
|
|
881
|
-
pipeOptions
|
|
882
|
-
};
|
|
280
|
+
var credentialRefused = {
|
|
281
|
+
code: "credential_refused",
|
|
282
|
+
message: "Kitman refused this Runner's credential.",
|
|
283
|
+
fix: pairFix
|
|
883
284
|
};
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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.`
|
|
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;
|
|
894
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")]);
|
|
895
309
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
method: "stdout",
|
|
907
|
-
_tag: "Unknown",
|
|
908
|
-
cause
|
|
909
|
-
}),
|
|
910
|
-
endOnDone: options?.endOnDone ?? false
|
|
911
|
-
}),
|
|
912
|
-
stderr: (options) => fromWritable({
|
|
913
|
-
evaluate: () => process.stderr,
|
|
914
|
-
onError: (cause) => systemError({
|
|
915
|
-
module: "Stdio",
|
|
916
|
-
method: "stderr",
|
|
917
|
-
_tag: "Unknown",
|
|
918
|
-
cause
|
|
919
|
-
}),
|
|
920
|
-
endOnDone: options?.endOnDone ?? false
|
|
921
|
-
}),
|
|
922
|
-
stdin: /* @__PURE__ */ fromReadable({
|
|
923
|
-
evaluate: () => process.stdin,
|
|
924
|
-
onError: (cause) => systemError({
|
|
925
|
-
module: "Stdio",
|
|
926
|
-
method: "stdin",
|
|
927
|
-
_tag: "Unknown",
|
|
928
|
-
cause
|
|
929
|
-
}),
|
|
930
|
-
closeOnDone: false
|
|
931
|
-
})
|
|
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
|
+
});
|
|
932
320
|
}));
|
|
933
321
|
|
|
934
|
-
//
|
|
935
|
-
var
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
var TypeId2 = "~effect/Terminal";
|
|
939
|
-
var QuitErrorTypeId = "~effect/Terminal/QuitError";
|
|
940
|
-
|
|
941
|
-
class QuitError extends (/* @__PURE__ */ Error2("QuitError")({
|
|
942
|
-
_tag: /* @__PURE__ */ tag("QuitError")
|
|
943
|
-
})) {
|
|
944
|
-
[QuitErrorTypeId] = QuitErrorTypeId;
|
|
945
|
-
}
|
|
946
|
-
var Terminal = /* @__PURE__ */ Service("effect/Terminal");
|
|
947
|
-
var make3 = (impl) => Terminal.of({
|
|
948
|
-
...impl,
|
|
949
|
-
[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 }))
|
|
950
326
|
});
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
yield* addFinalizer2(() => sync(() => stdin.off("end", onStdinEnd)));
|
|
968
|
-
const rlRef = yield* make10({
|
|
969
|
-
acquire: acquireRelease(sync(() => {
|
|
970
|
-
const rl = readline.createInterface({
|
|
971
|
-
input: stdin,
|
|
972
|
-
escapeCodeTimeout: 50
|
|
973
|
-
});
|
|
974
|
-
const onLine = (line) => offerUnsafe(lines, line);
|
|
975
|
-
const onClose = () => {
|
|
976
|
-
readlineActive = false;
|
|
977
|
-
endUnsafe(lines);
|
|
978
|
-
};
|
|
979
|
-
readlineActive = true;
|
|
980
|
-
readline.emitKeypressEvents(stdin, rl);
|
|
981
|
-
rl.on("line", onLine);
|
|
982
|
-
rl.once("close", onClose);
|
|
983
|
-
if (stdin.isTTY) {
|
|
984
|
-
stdin.setRawMode(true);
|
|
985
|
-
}
|
|
986
|
-
return {
|
|
987
|
-
rl,
|
|
988
|
-
onClose,
|
|
989
|
-
onLine
|
|
990
|
-
};
|
|
991
|
-
}), ({
|
|
992
|
-
rl,
|
|
993
|
-
onClose,
|
|
994
|
-
onLine
|
|
995
|
-
}) => sync(() => {
|
|
996
|
-
readlineActive = false;
|
|
997
|
-
rl.off("line", onLine);
|
|
998
|
-
rl.off("close", onClose);
|
|
999
|
-
if (stdin.isTTY) {
|
|
1000
|
-
stdin.setRawMode(false);
|
|
1001
|
-
}
|
|
1002
|
-
rl.close();
|
|
1003
|
-
if (inputEnded) {
|
|
1004
|
-
endUnsafe(lines);
|
|
1005
|
-
}
|
|
1006
|
-
})),
|
|
1007
|
-
idleTimeToLive: "10 millis"
|
|
1008
|
-
});
|
|
1009
|
-
const columns = sync(() => stdout.columns ?? 0);
|
|
1010
|
-
const rows = sync(() => stdout.rows ?? 0);
|
|
1011
|
-
const readInput = gen(function* () {
|
|
1012
|
-
const queue = yield* make9();
|
|
1013
|
-
const handleKeypress = (s, k) => {
|
|
1014
|
-
const userInput = {
|
|
1015
|
-
input: fromUndefinedOr(s),
|
|
1016
|
-
key: {
|
|
1017
|
-
name: k.name ?? "",
|
|
1018
|
-
ctrl: !!k.ctrl,
|
|
1019
|
-
meta: !!k.meta,
|
|
1020
|
-
shift: !!k.shift
|
|
1021
|
-
}
|
|
1022
|
-
};
|
|
1023
|
-
offerUnsafe(queue, userInput);
|
|
1024
|
-
if (shouldQuit(userInput)) {
|
|
1025
|
-
endUnsafe(queue);
|
|
1026
|
-
}
|
|
1027
|
-
};
|
|
1028
|
-
const keepAlive = setInterval(() => {}, 2147483647);
|
|
1029
|
-
const handleEnd = () => {
|
|
1030
|
-
clearInterval(keepAlive);
|
|
1031
|
-
endUnsafe(queue);
|
|
1032
|
-
};
|
|
1033
|
-
yield* addFinalizer2(() => sync(() => {
|
|
1034
|
-
clearInterval(keepAlive);
|
|
1035
|
-
stdin.off("keypress", handleKeypress);
|
|
1036
|
-
stdin.off("end", handleEnd);
|
|
1037
|
-
}));
|
|
1038
|
-
stdin.on("keypress", handleKeypress);
|
|
1039
|
-
if (inputEnded) {
|
|
1040
|
-
handleEnd();
|
|
1041
|
-
} else {
|
|
1042
|
-
yield* get2(rlRef);
|
|
1043
|
-
stdin.once("end", handleEnd);
|
|
1044
|
-
}
|
|
1045
|
-
return queue;
|
|
1046
|
-
});
|
|
1047
|
-
const readLine = suspend(() => poll(lines).pipe(flatMap2(match({
|
|
1048
|
-
onNone: () => scoped(andThen(get2(rlRef), take(lines))),
|
|
1049
|
-
onSome: succeed5
|
|
1050
|
-
})), mapError(() => new QuitError({}))));
|
|
1051
|
-
const display = (prompt2) => uninterruptible(callback((resume) => {
|
|
1052
|
-
stdout.write(prompt2, (err) => isNullish(err) ? resume(void_2) : resume(fail4(badArgument({
|
|
1053
|
-
module: "Terminal",
|
|
1054
|
-
method: "display",
|
|
1055
|
-
description: "Failed to write prompt to stdout",
|
|
1056
|
-
cause: err
|
|
1057
|
-
}))));
|
|
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;
|
|
1058
343
|
}));
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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)}` })
|
|
1065
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);
|
|
400
|
+
});
|
|
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 };
|
|
1066
407
|
});
|
|
1067
|
-
var layer8 = /* @__PURE__ */ effect(Terminal, /* @__PURE__ */ make4(defaultShouldQuit));
|
|
1068
|
-
function defaultShouldQuit(input) {
|
|
1069
|
-
const key = input.key;
|
|
1070
|
-
return key.name === "escape" || key.ctrl && (key.name === "c" || key.name === "d");
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
// node_modules/.bun/@effect+platform-bun@4.0.4+b8cbb99ebc524863/node_modules/@effect/platform-bun/dist/BunTerminal.js
|
|
1074
|
-
var layer9 = layer8;
|
|
1075
408
|
|
|
1076
|
-
//
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
+
}
|
|
1081
414
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
yield*
|
|
1101
|
-
publicKey: credential.publicKeyJwk
|
|
1102
|
-
}).pipe(mapError(() => new CliError({ message: "herdr_runner_credential_unavailable" })));
|
|
1103
|
-
return credential;
|
|
415
|
+
|
|
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 });
|
|
1104
434
|
});
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
apiUrl: input.apiUrl,
|
|
1121
|
-
fetch: runnerFetch,
|
|
1122
|
-
session: { accessToken: "runner-agent-auth" }
|
|
1123
|
-
});
|
|
1124
|
-
const rpcBearer = createAgentAuthBearerToken({
|
|
1125
|
-
credential,
|
|
1126
|
-
audience: new URL("/v1/runners/rpc", input.apiUrl).toString(),
|
|
1127
|
-
capabilities: ["kitman_api"]
|
|
1128
|
-
});
|
|
1129
|
-
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)));
|
|
1130
450
|
});
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
publicKeyJwk: keyPair.publicKeyJwk
|
|
1138
|
-
})));
|
|
1139
|
-
}
|
|
451
|
+
return {
|
|
452
|
+
claiming: () => claiming,
|
|
453
|
+
problems: () => problems,
|
|
454
|
+
update
|
|
455
|
+
};
|
|
456
|
+
};
|
|
1140
457
|
|
|
1141
458
|
// apps/cli/src/harness-sync.ts
|
|
1142
459
|
function buildHarnessSyncPlan(input) {
|
|
@@ -1145,11 +462,11 @@ function buildHarnessSyncPlan(input) {
|
|
|
1145
462
|
version: 1,
|
|
1146
463
|
apiUrl: input.apiUrl,
|
|
1147
464
|
workspaceId: input.workspaceId,
|
|
1148
|
-
...input.
|
|
465
|
+
...input.teamId === undefined ? undefined : { teamId: input.teamId },
|
|
1149
466
|
harnesses: harnesses.map((harness) => ({ harness, configScope: "user", managedBy: "kitman" })),
|
|
1150
467
|
sources: [
|
|
1151
468
|
{ kind: "workspace", id: input.workspaceId },
|
|
1152
|
-
...input.
|
|
469
|
+
...input.teamId === undefined ? [] : [{ kind: "team", id: input.teamId }],
|
|
1153
470
|
{ kind: "user-preferences", id: "default" }
|
|
1154
471
|
],
|
|
1155
472
|
materialization: (input.materializationPlans ?? []).map(renderMaterializationPlan),
|
|
@@ -1232,7 +549,7 @@ function hashIdentity(value) {
|
|
|
1232
549
|
|
|
1233
550
|
// apps/cli/src/assets.ts
|
|
1234
551
|
function send(request, failure) {
|
|
1235
|
-
return execute(request).pipe(mapError((error) => failure(error.cause ?? error)), provide3(
|
|
552
|
+
return execute(request).pipe(mapError((error) => failure(error.cause ?? error)), provide3(layer2));
|
|
1236
553
|
}
|
|
1237
554
|
function putPresignedAsset(upload, bytes) {
|
|
1238
555
|
return gen(function* () {
|
|
@@ -1245,6 +562,30 @@ function putPresignedAsset(upload, bytes) {
|
|
|
1245
562
|
return yield* new CliError({ message: `Asset upload failed with ${response.status}` });
|
|
1246
563
|
});
|
|
1247
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
|
+
}
|
|
1248
589
|
|
|
1249
590
|
// apps/cli/src/output.ts
|
|
1250
591
|
function printJson(value) {
|
|
@@ -1382,6 +723,162 @@ function declaredChecks(manifest, rootFiles) {
|
|
|
1382
723
|
// apps/cli/src/init.ts
|
|
1383
724
|
import { hostname } from "os";
|
|
1384
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
|
+
|
|
1385
882
|
// apps/cli/src/repo-binding-setup.ts
|
|
1386
883
|
var Git = Reference("kitman/cli/Git", {
|
|
1387
884
|
defaultValue: () => (args) => runCommand2("git", args).pipe(map3((stdout) => stdout.trim()))
|
|
@@ -1494,7 +991,17 @@ function stripPathSuffix(value) {
|
|
|
1494
991
|
|
|
1495
992
|
// apps/cli/src/init.ts
|
|
1496
993
|
var ServiceManager = Reference("kitman/cli/ServiceManager", {
|
|
1497
|
-
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
|
+
})
|
|
1498
1005
|
});
|
|
1499
1006
|
var Login = Reference("kitman/cli/Login", {
|
|
1500
1007
|
defaultValue: () => browserSession
|
|
@@ -1536,52 +1043,103 @@ function initCommand(args) {
|
|
|
1536
1043
|
return yield* new CliError({ message: "No Workspace to use" });
|
|
1537
1044
|
yield* writeUserConfig({ ...user, apiUrl, activeWorkspaceId: workspace.id });
|
|
1538
1045
|
yield* step("Workspace", current === undefined ? "set_up" : "already", workspace.name);
|
|
1539
|
-
|
|
1540
|
-
yield*
|
|
1541
|
-
yield* step("Project", project.outcome, project.name);
|
|
1542
|
-
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;
|
|
1543
1048
|
const bound = bindings.find((binding) => matchesRemote(binding, remoteUrl));
|
|
1544
|
-
const binding = bound ?? (yield* client.createRepoBinding(
|
|
1545
|
-
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}`);
|
|
1546
1051
|
yield* runnerStep(client, { apiUrl, session, workspaceId: workspace.id, userId: me.userId }, step);
|
|
1547
1052
|
const syncPath = yield* userHarnessSyncPath;
|
|
1548
1053
|
const before = yield* readText(syncPath);
|
|
1549
1054
|
yield* harnessSync(["sync"]);
|
|
1550
1055
|
const after = yield* readText(syncPath);
|
|
1551
1056
|
yield* step("Harness", before === undefined ? "set_up" : before === after ? "already" : "repaired", "synced");
|
|
1552
|
-
yield* verificationStep(client,
|
|
1057
|
+
yield* verificationStep(client, workspace.id, binding, confirm, step);
|
|
1553
1058
|
const changed = steps.filter((entry) => entry.outcome === "set_up" || entry.outcome === "repaired");
|
|
1554
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>".`);
|
|
1555
1060
|
});
|
|
1556
1061
|
}
|
|
1557
1062
|
function pairCommand(args) {
|
|
1558
1063
|
return gen(function* () {
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1561
|
-
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));
|
|
1562
1066
|
const flags = parseInitFlags(rest);
|
|
1563
1067
|
const apiUrl = yield* resolveApiUrl(flags.apiUrl);
|
|
1564
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;
|
|
1565
1075
|
const anonymous = new KitmanEffectClient({ baseUrl: apiUrl, fetch: yield* Fetch });
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
...credentials.refreshToken === undefined ? undefined : { refresh_token: credentials.refreshToken },
|
|
1570
|
-
...credentials.expiresIn === undefined ? undefined : { expires_in: credentials.expiresIn }
|
|
1076
|
+
const redeemed = yield* anonymous.redeemPairingCode({
|
|
1077
|
+
code,
|
|
1078
|
+
publicKey: keys.publicKeyJwk
|
|
1571
1079
|
});
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
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);
|
|
1583
1097
|
});
|
|
1584
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
|
+
});
|
|
1585
1143
|
var parseInitFlags = (args) => {
|
|
1586
1144
|
const flags = { yes: false };
|
|
1587
1145
|
for (let index = 0;index < args.length; index += 1) {
|
|
@@ -1606,54 +1164,59 @@ var pickWorkspace = (workspaces, ask) => gen(function* () {
|
|
|
1606
1164
|
return workspaces.find((workspace) => workspace.slug === answer || workspace.name === answer) ?? first;
|
|
1607
1165
|
});
|
|
1608
1166
|
var matchesRemote = (binding, remoteUrl) => binding.status === "active" && repositoryKey(binding.repositoryUrl ?? `https://${binding.host}/${binding.owner}/${binding.name}`) === repositoryKey(remoteUrl);
|
|
1609
|
-
var projectFor = (client, workspaceId, remoteUrl, repositoryName, configured) => gen(function* () {
|
|
1610
|
-
const projects = (yield* client.listProjects(workspaceId)).items;
|
|
1611
|
-
const known = projects.find((project) => project.id === configured);
|
|
1612
|
-
if (known !== undefined)
|
|
1613
|
-
return { id: known.id, name: known.name, outcome: "already" };
|
|
1614
|
-
const boundHere = yield* findFirst(projects, (project) => client.listRepoBindings(project.id).pipe(map3((bindings) => bindings.items.some((binding) => matchesRemote(binding, remoteUrl))), orElseSucceed(() => false)));
|
|
1615
|
-
const found = getOrUndefined(boundHere) ?? projects.find((project) => project.slug === repositoryName || project.name === repositoryName);
|
|
1616
|
-
if (found === undefined) {
|
|
1617
|
-
const created = yield* client.createProject(workspaceId, { name: repositoryName });
|
|
1618
|
-
return { id: created.id, name: created.name, outcome: "set_up" };
|
|
1619
|
-
}
|
|
1620
|
-
return { id: found.id, name: found.name, outcome: "set_up" };
|
|
1621
|
-
});
|
|
1622
1167
|
var runnerStep = (client, input, step) => gen(function* () {
|
|
1623
1168
|
const name = yield* machineName;
|
|
1624
1169
|
const runners = (yield* client.listRunners(input.workspaceId)).items;
|
|
1625
|
-
const
|
|
1626
|
-
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, {
|
|
1627
1173
|
name,
|
|
1628
1174
|
capabilities: herdrRunnerCapabilities2({ harnesses: yield* detectHarnesses2 }),
|
|
1629
1175
|
labels: ["kitman-runner", "kitman-loop", "herdr"]
|
|
1630
1176
|
})).runner;
|
|
1631
|
-
const
|
|
1632
|
-
if (
|
|
1633
|
-
yield*
|
|
1634
|
-
yield* step("Runner",
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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);
|
|
1642
1194
|
if (service === "unsupported")
|
|
1643
|
-
yield* step("Runner service", "skipped", `start it yourself with kitman runner start ${
|
|
1644
|
-
|
|
1645
|
-
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");
|
|
1646
1201
|
});
|
|
1202
|
+
var credentialNote = { missing: ", credential restored", refused: ", credential re-keyed", accepted: "" };
|
|
1647
1203
|
var machineName = map3(option2(String3("KITMAN_RUNNER_NAME")), (name) => getOrElse(name, () => hostname()));
|
|
1648
|
-
var
|
|
1649
|
-
const
|
|
1650
|
-
|
|
1651
|
-
|
|
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
|
+
});
|
|
1652
1215
|
var readText = (path) => flatMap2(FileSystem, (fs) => fs.readFileString(path)).pipe(orElseSucceed(() => {
|
|
1653
1216
|
return;
|
|
1654
1217
|
}));
|
|
1655
|
-
var verificationStep = (client,
|
|
1656
|
-
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;
|
|
1657
1220
|
const suggested = (yield* scriptChecks).filter((check) => !approved.some((entry) => entry.name === check.name));
|
|
1658
1221
|
if (suggested.length === 0)
|
|
1659
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(", "));
|
|
@@ -1662,7 +1225,7 @@ var verificationStep = (client, projectId, binding, confirm, step) => gen(functi
|
|
|
1662
1225
|
if (!(yield* confirm("Approve these verifications as evidence for every Run", true)))
|
|
1663
1226
|
return yield* step("Verifications", "skipped", "not approved; run kitman init again or kitman verify add");
|
|
1664
1227
|
for (const check of suggested)
|
|
1665
|
-
yield* client.approveVerification(
|
|
1228
|
+
yield* client.approveVerification(workspaceId, binding.id, check);
|
|
1666
1229
|
yield* step("Verifications", "set_up", suggested.map((check) => check.name).join(", "));
|
|
1667
1230
|
});
|
|
1668
1231
|
var scriptChecks = gen(function* () {
|
|
@@ -1684,13 +1247,15 @@ var systemdUnit = (spec) => [
|
|
|
1684
1247
|
`Environment="PATH=${spec.path}"`,
|
|
1685
1248
|
"Restart=on-failure",
|
|
1686
1249
|
"RestartSec=5",
|
|
1250
|
+
"RestartSteps=10",
|
|
1251
|
+
"RestartMaxDelaySec=300",
|
|
1687
1252
|
"",
|
|
1688
1253
|
"[Install]",
|
|
1689
1254
|
"WantedBy=default.target",
|
|
1690
1255
|
""
|
|
1691
1256
|
].join(`
|
|
1692
1257
|
`);
|
|
1693
|
-
var launchdPlist = (label, spec) => [
|
|
1258
|
+
var launchdPlist = (label, spec, log) => [
|
|
1694
1259
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
1695
1260
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
1696
1261
|
'<plist version="1.0"><dict>',
|
|
@@ -1698,8 +1263,10 @@ var launchdPlist = (label, spec) => [
|
|
|
1698
1263
|
`<key>ProgramArguments</key><array>${spec.command.map((word) => `<string>${escapeXml(word)}</string>`).join("")}</array>`,
|
|
1699
1264
|
`<key>WorkingDirectory</key><string>${escapeXml(spec.workingDirectory)}</string>`,
|
|
1700
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>`,
|
|
1701
1268
|
"<key>RunAtLoad</key><true/>",
|
|
1702
|
-
"<key>KeepAlive</key><
|
|
1269
|
+
"<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>",
|
|
1703
1270
|
"</dict></plist>",
|
|
1704
1271
|
""
|
|
1705
1272
|
].join(`
|
|
@@ -1707,6 +1274,44 @@ var launchdPlist = (label, spec) => [
|
|
|
1707
1274
|
var escapeXml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1708
1275
|
var runnerServiceName = (runnerId) => `kitman-runner-${runnerId}`;
|
|
1709
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
|
+
});
|
|
1710
1315
|
var launchdDomain = gen(function* () {
|
|
1711
1316
|
const manager = yield* ServiceManager;
|
|
1712
1317
|
return `gui/${(yield* manager.run("id", ["-u"])).trim()}`;
|
|
@@ -1726,7 +1331,6 @@ function runnerService(spec) {
|
|
|
1726
1331
|
const fs = yield* FileSystem;
|
|
1727
1332
|
const path = yield* Path;
|
|
1728
1333
|
const manager = yield* ServiceManager;
|
|
1729
|
-
const home = yield* String3("HOME");
|
|
1730
1334
|
const define = (file, content) => gen(function* () {
|
|
1731
1335
|
if ((yield* readText(file)) === content)
|
|
1732
1336
|
return false;
|
|
@@ -1736,7 +1340,7 @@ function runnerService(spec) {
|
|
|
1736
1340
|
});
|
|
1737
1341
|
if (manager.platform === "linux") {
|
|
1738
1342
|
const unit = `${spec.name}.service`;
|
|
1739
|
-
const changed = yield* define(
|
|
1343
|
+
const changed = yield* define(yield* runnerServiceFile(manager.platform, spec.name), systemdUnit(spec));
|
|
1740
1344
|
if (!changed && (yield* runnerServiceState(spec.name)) === "systemd: active")
|
|
1741
1345
|
return "running";
|
|
1742
1346
|
yield* manager.run("systemctl", ["--user", "daemon-reload"]);
|
|
@@ -1746,8 +1350,10 @@ function runnerService(spec) {
|
|
|
1746
1350
|
}
|
|
1747
1351
|
if (manager.platform === "darwin") {
|
|
1748
1352
|
const label = launchdLabel(spec.name);
|
|
1749
|
-
const file =
|
|
1750
|
-
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));
|
|
1751
1357
|
if (!changed && (yield* runnerServiceState(spec.name)) === "launchd: running")
|
|
1752
1358
|
return "running";
|
|
1753
1359
|
const domain = yield* launchdDomain;
|
|
@@ -1788,7 +1394,7 @@ var package_default = {
|
|
|
1788
1394
|
};
|
|
1789
1395
|
|
|
1790
1396
|
// apps/cli/src/scaffold.ts
|
|
1791
|
-
var cliVersion = "0.
|
|
1397
|
+
var cliVersion = "0.8.0";
|
|
1792
1398
|
var packageJson = (name, scaffold) => `${JSON.stringify({
|
|
1793
1399
|
name,
|
|
1794
1400
|
private: true,
|
|
@@ -1898,7 +1504,7 @@ var readme = (name, scaffold) => [
|
|
|
1898
1504
|
"1. `bun install`, then `bun test`.",
|
|
1899
1505
|
"2. `bunx kitman deploy --plan` shows what deploying would change.",
|
|
1900
1506
|
"3. Push this repository to GitLab, then let its CI deploy the default branch: a Workspace owner or admin runs",
|
|
1901
|
-
" `bunx kitman deploy bind --branch main` (add `--
|
|
1507
|
+
" `bunx kitman deploy bind --branch main` (add `--team <team>` to deploy to one Team).",
|
|
1902
1508
|
"4. Merge requests print the plan, and merging deploys. `.gitlab-ci.yml` names the Workspace to deploy to.",
|
|
1903
1509
|
""
|
|
1904
1510
|
].join(`
|
|
@@ -1908,7 +1514,7 @@ var scaffoldPackage = (directory, scaffold = {}) => gen(function* () {
|
|
|
1908
1514
|
const path = yield* Path;
|
|
1909
1515
|
const root = path.resolve(directory);
|
|
1910
1516
|
if (scaffold.fork !== undefined) {
|
|
1911
|
-
const { shippedLoops } = yield* promise(() => import("./chunk-
|
|
1517
|
+
const { shippedLoops } = yield* promise(() => import("./chunk-64et31rh.js"));
|
|
1912
1518
|
if (!(scaffold.fork in shippedLoops))
|
|
1913
1519
|
return yield* new CliError({
|
|
1914
1520
|
message: `Kitman ships no Loop ${scaffold.fork}; fork one of ${Object.keys(shippedLoops).join(", ")}`
|
|
@@ -1940,30 +1546,38 @@ var scaffoldPackage = (directory, scaffold = {}) => gen(function* () {
|
|
|
1940
1546
|
// apps/cli/src/commands.ts
|
|
1941
1547
|
var ClientId = "kitman-cli";
|
|
1942
1548
|
var Usage = [
|
|
1943
|
-
"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]",
|
|
1944
1550
|
"",
|
|
1945
1551
|
"Set up, in a repository (run it again to repair what is missing):",
|
|
1946
1552
|
" kitman init [--yes] [--api-url <url>]",
|
|
1947
|
-
" 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)",
|
|
1948
1556
|
"",
|
|
1949
1557
|
"Runs:",
|
|
1950
|
-
' kitman run start <loop> "<words>" [--input <json>] [--
|
|
1558
|
+
' kitman run start <loop> "<words>" [--input <json>] [--team <team>] [--type <task type>] [--constraint <text>]...',
|
|
1951
1559
|
" kitman run show <run>",
|
|
1952
1560
|
" kitman run intent <run> [--set-goal <text>] [--add-constraint <text>] [--remove-constraint <id>] [--corrections on|off]",
|
|
1953
1561
|
" kitman run drift <run> [--dismiss <constraint> --reason <text>]",
|
|
1954
|
-
" 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>]",
|
|
1955
1565
|
" kitman run wait <run> [--until attention|finished]",
|
|
1956
1566
|
' kitman run answer <run> "<option or words>" [--question <name>]',
|
|
1957
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>]",
|
|
1958
1570
|
" kitman run stop <run>",
|
|
1959
1571
|
" kitman run resume <run>",
|
|
1960
1572
|
" kitman run close <run> --complete|--abandoned [--reason <text>]",
|
|
1961
1573
|
" kitman run actions <run>",
|
|
1962
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)",
|
|
1963
1577
|
" kitman inbox [--unread|--snoozed|--done]",
|
|
1964
|
-
" kitman inbox my-teams [--
|
|
1965
|
-
" kitman inbox read|unread|done|undone|unsnooze <task:id|run:id|proposal:id>...",
|
|
1966
|
-
" 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>",
|
|
1967
1581
|
" kitman inbox unsubscribe <task>...",
|
|
1968
1582
|
"",
|
|
1969
1583
|
"",
|
|
@@ -1985,24 +1599,37 @@ var Usage = [
|
|
|
1985
1599
|
' kitman constraint add "<constraint>"',
|
|
1986
1600
|
" kitman constraint remove <constraint>",
|
|
1987
1601
|
"",
|
|
1988
|
-
"Secrets, for the Workspace or with --
|
|
1989
|
-
" <command> | kitman secret set <name> [--
|
|
1990
|
-
" kitman secret delete <name> [--
|
|
1991
|
-
" 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",
|
|
1992
1607
|
"",
|
|
1993
|
-
"
|
|
1994
|
-
" kitman
|
|
1995
|
-
|
|
1996
|
-
" kitman
|
|
1997
|
-
"
|
|
1998
|
-
"
|
|
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>",
|
|
1612
|
+
"",
|
|
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)",
|
|
1999
1626
|
"",
|
|
2000
1627
|
"Every run, loop, agent, verify, constraint, secret, deploy, dev and package command takes --json."
|
|
2001
1628
|
].join(`
|
|
2002
1629
|
`);
|
|
2003
1630
|
var KitCreateValidator = guard(KitCreate);
|
|
2004
1631
|
var KitUpdateValidator = guard(KitUpdate);
|
|
2005
|
-
var agentRuntime = promise(() => import("./chunk-
|
|
1632
|
+
var agentRuntime = promise(() => import("./chunk-m6yymqfr.js"));
|
|
2006
1633
|
var TuiLauncher = Reference("kitman/cli/TuiLauncher", {
|
|
2007
1634
|
defaultValue: () => gen(function* () {
|
|
2008
1635
|
const tui = yield* tryPromise({
|
|
@@ -2024,6 +1651,8 @@ function runEffect(args) {
|
|
|
2024
1651
|
yield* log(Usage);
|
|
2025
1652
|
return;
|
|
2026
1653
|
}
|
|
1654
|
+
if (args[0] === "--version")
|
|
1655
|
+
return yield* log(kitmanVersion ?? "dev");
|
|
2027
1656
|
const [command, ...rest] = args;
|
|
2028
1657
|
if (command === "init" && rest[0] === "--package")
|
|
2029
1658
|
return yield* scaffoldPackage(rest[1] ?? ".");
|
|
@@ -2041,8 +1670,10 @@ function runEffect(args) {
|
|
|
2041
1670
|
return yield* configCommand(args);
|
|
2042
1671
|
if (command === "workspace")
|
|
2043
1672
|
return yield* workspaceCommand(args);
|
|
2044
|
-
if (command === "
|
|
2045
|
-
return yield*
|
|
1673
|
+
if (command === "team")
|
|
1674
|
+
return yield* teamCommand(args);
|
|
1675
|
+
if (command === "repo")
|
|
1676
|
+
return yield* repoCommand(args);
|
|
2046
1677
|
if (command === "runner")
|
|
2047
1678
|
return yield* runnerCommand(args);
|
|
2048
1679
|
if (command === "mcp")
|
|
@@ -2065,6 +1696,8 @@ function runEffect(args) {
|
|
|
2065
1696
|
return yield* agentCommand(args);
|
|
2066
1697
|
if (command === "run")
|
|
2067
1698
|
return yield* runCommand(args);
|
|
1699
|
+
if (command === "transcript")
|
|
1700
|
+
return yield* transcriptCommand(["transcript", "read", ...rest]);
|
|
2068
1701
|
if (command === "inbox")
|
|
2069
1702
|
return yield* inboxCommand(args);
|
|
2070
1703
|
if (command === "verify")
|
|
@@ -2100,6 +1733,10 @@ var isInteractiveTty = gen(function* () {
|
|
|
2100
1733
|
});
|
|
2101
1734
|
var tuiModulePath = "./tui/index";
|
|
2102
1735
|
var solidTransformModulePath = "@opentui/solid/preload";
|
|
1736
|
+
var renamedOptions = new Map([
|
|
1737
|
+
["--project", "--team"],
|
|
1738
|
+
["--project-access", "--team-access"]
|
|
1739
|
+
]);
|
|
2103
1740
|
function parseCommand(args) {
|
|
2104
1741
|
const group = args[0];
|
|
2105
1742
|
const action = args[1];
|
|
@@ -2180,18 +1817,16 @@ function parseCommand(args) {
|
|
|
2180
1817
|
if (flags.closure !== undefined && flags.closure !== kind)
|
|
2181
1818
|
throw new Error("run close takes one of --complete or --abandoned");
|
|
2182
1819
|
flags.closure = kind;
|
|
2183
|
-
} else if (arg === "--
|
|
2184
|
-
flags.
|
|
2185
|
-
else if (arg === "--runner") {
|
|
1820
|
+
} else if (arg === "--team" && group === "sync")
|
|
1821
|
+
flags.teamScope = true;
|
|
1822
|
+
else if (arg === "--runner" || arg === "--credential-file") {
|
|
2186
1823
|
const value = args[++index];
|
|
2187
1824
|
if (!value)
|
|
2188
1825
|
throw new Error(`${arg} requires a value`);
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
throw new Error("--agent-identity requires a value");
|
|
2194
|
-
flags.agentIdentityId = value;
|
|
1826
|
+
if (arg === "--runner")
|
|
1827
|
+
flags.runnerId = value;
|
|
1828
|
+
else
|
|
1829
|
+
flags.credentialFile = value;
|
|
2195
1830
|
} else if (arg === "--iterations") {
|
|
2196
1831
|
if (group === "runner" && action === "start")
|
|
2197
1832
|
throw new Error("runner start accepts only a Runner identity");
|
|
@@ -2199,6 +1834,11 @@ function parseCommand(args) {
|
|
|
2199
1834
|
if (!value)
|
|
2200
1835
|
throw new Error("--iterations requires a value");
|
|
2201
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);
|
|
2202
1842
|
} else if (arg === "--poll-interval-ms") {
|
|
2203
1843
|
if (group === "runner" && action === "start")
|
|
2204
1844
|
throw new Error("runner start accepts only a Runner identity");
|
|
@@ -2269,7 +1909,7 @@ function parseCommand(args) {
|
|
|
2269
1909
|
if (!value)
|
|
2270
1910
|
throw new Error("--capabilities requires a value");
|
|
2271
1911
|
flags.capabilities = validateAgentCapabilities(value);
|
|
2272
|
-
} 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") {
|
|
2273
1913
|
const value = args[++index];
|
|
2274
1914
|
if (!value)
|
|
2275
1915
|
throw new Error(`${arg} requires a value`);
|
|
@@ -2295,14 +1935,12 @@ function parseCommand(args) {
|
|
|
2295
1935
|
flags.filename = value;
|
|
2296
1936
|
else if (arg === "--session")
|
|
2297
1937
|
flags.sessionId = value;
|
|
2298
|
-
else if (arg === "--
|
|
2299
|
-
flags.
|
|
1938
|
+
else if (arg === "--team")
|
|
1939
|
+
flags.team = value;
|
|
2300
1940
|
else if (arg === "--workspace")
|
|
2301
1941
|
flags.workspace = value;
|
|
2302
1942
|
else if (arg === "--branch")
|
|
2303
1943
|
flags.branch = value;
|
|
2304
|
-
else if (arg === "--team")
|
|
2305
|
-
flags.team = value;
|
|
2306
1944
|
else if (arg === "--type")
|
|
2307
1945
|
flags.taskType = value;
|
|
2308
1946
|
else if (arg === "--status")
|
|
@@ -2317,7 +1955,7 @@ function parseCommand(args) {
|
|
|
2317
1955
|
flags.dismiss = value;
|
|
2318
1956
|
else if (arg === "--action")
|
|
2319
1957
|
flags.fixFlowAction = validateFixFlowAction(value);
|
|
2320
|
-
else if (arg === "--step-key")
|
|
1958
|
+
else if (arg === "--step-key" || arg === "--step")
|
|
2321
1959
|
flags.stepKey = value;
|
|
2322
1960
|
else if (arg === "--target-type")
|
|
2323
1961
|
flags.targetType = validateProposalTargetType(value);
|
|
@@ -2325,10 +1963,10 @@ function parseCommand(args) {
|
|
|
2325
1963
|
flags.scopeType = value;
|
|
2326
1964
|
else if (arg === "--scope-id")
|
|
2327
1965
|
flags.scopeId = value;
|
|
2328
|
-
else if (arg === "--
|
|
1966
|
+
else if (arg === "--team-access") {
|
|
2329
1967
|
if (value !== "all" && value !== "selected")
|
|
2330
|
-
throw new Error("--
|
|
2331
|
-
flags.
|
|
1968
|
+
throw new Error("--team-access must be all or selected");
|
|
1969
|
+
flags.teamAccess = value;
|
|
2332
1970
|
} else if (arg === "--provider")
|
|
2333
1971
|
flags.provider = validateRepoBindingProvider(value);
|
|
2334
1972
|
else if (arg === "--task")
|
|
@@ -2353,6 +1991,8 @@ function parseCommand(args) {
|
|
|
2353
1991
|
flags.runId = value;
|
|
2354
1992
|
else if (arg === "--run-id")
|
|
2355
1993
|
flags.runId = value;
|
|
1994
|
+
else if (arg === "--out")
|
|
1995
|
+
flags.out = value;
|
|
2356
1996
|
else if (arg === "--replica")
|
|
2357
1997
|
flags.replicaId = value;
|
|
2358
1998
|
else if (arg === "--work-unit-kind")
|
|
@@ -2369,9 +2009,13 @@ function parseCommand(args) {
|
|
|
2369
2009
|
flags.expectedBranch = value;
|
|
2370
2010
|
else if (arg === "--expected-commit-sha")
|
|
2371
2011
|
flags.expectedCommitSha = value;
|
|
2012
|
+
else if (arg === "--to")
|
|
2013
|
+
flags.to = value;
|
|
2372
2014
|
else
|
|
2373
2015
|
flags.credentialRefs = [...flags.credentialRefs ?? [], parseRepoBindingCredentialRef(value)];
|
|
2374
|
-
} 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("--"))
|
|
2375
2019
|
throw new Error(`Unknown option: ${arg}`);
|
|
2376
2020
|
else if (arg)
|
|
2377
2021
|
values.push(arg);
|
|
@@ -2387,7 +2031,7 @@ function syncCommand(args) {
|
|
|
2387
2031
|
materializationPlans,
|
|
2388
2032
|
configPlans,
|
|
2389
2033
|
userConfigPlans,
|
|
2390
|
-
|
|
2034
|
+
teamConfigPlans,
|
|
2391
2035
|
syncKits,
|
|
2392
2036
|
outputPath,
|
|
2393
2037
|
applied: shouldApply
|
|
@@ -2404,8 +2048,8 @@ function syncCommand(args) {
|
|
|
2404
2048
|
else {
|
|
2405
2049
|
yield* log(`Harness sync ${shouldApply ? "applied" : "preview"}.`);
|
|
2406
2050
|
yield* log(`Workspace: ${plan.workspaceId}`);
|
|
2407
|
-
if (plan.
|
|
2408
|
-
yield* log(`
|
|
2051
|
+
if (plan.teamId)
|
|
2052
|
+
yield* log(`Team: ${plan.teamId}`);
|
|
2409
2053
|
yield* log(`Harnesses: ${plan.harnesses.map((item) => item.harness).join(", ")}`);
|
|
2410
2054
|
yield* log(`Secrets: ${plan.secrets}`);
|
|
2411
2055
|
yield* log(`Plan path: ${outputPath}`);
|
|
@@ -2417,43 +2061,45 @@ function syncCommand(args) {
|
|
|
2417
2061
|
for (const configPlan of userConfigPlans) {
|
|
2418
2062
|
yield* log(`${configPlan.harness} user writes: ${configPlan.operations.map((operation) => operation.path).join(", ") || "none"}`);
|
|
2419
2063
|
}
|
|
2420
|
-
for (const configPlan of
|
|
2421
|
-
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"}`);
|
|
2422
2066
|
}
|
|
2423
2067
|
}
|
|
2424
2068
|
});
|
|
2425
2069
|
}
|
|
2426
|
-
function harnessSync(args) {
|
|
2070
|
+
function harnessSync(args, runnerClient) {
|
|
2427
2071
|
const command = parseSyncCommand(args);
|
|
2428
2072
|
return gen(function* () {
|
|
2429
2073
|
const { applyAgentEnvironmentPlans, composeAgentEnvironmentSyncPlan, resolveAgentEnvironmentFromSources } = yield* agentRuntime;
|
|
2430
2074
|
const { config } = yield* resolveCommandConfig(command.flags);
|
|
2431
2075
|
if (!config.activeWorkspaceId)
|
|
2432
2076
|
return yield* new CliError({ message: "No active workspace. Run kitman workspace use <workspace>." });
|
|
2433
|
-
if (command.flags.
|
|
2077
|
+
if (command.flags.teamScope && !config.activeTeamId)
|
|
2434
2078
|
return yield* new CliError({
|
|
2435
|
-
message: "No active
|
|
2079
|
+
message: "No active team. Run kitman team use <team> before team sync."
|
|
2436
2080
|
});
|
|
2437
|
-
const
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
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
|
+
}));
|
|
2442
2088
|
const outputPath = yield* userHarnessSyncPath;
|
|
2443
2089
|
const workspaceId = config.activeWorkspaceId;
|
|
2444
2090
|
const selectedHarnesses = syncTargetHarnesses(command, config);
|
|
2445
2091
|
const workspaceKits = yield* client.listKits(workspaceId).pipe(catchIf((error) => error.status === 404, () => succeed5({ items: Array() })));
|
|
2446
|
-
const
|
|
2092
|
+
const teamKits = activeTeamId === undefined ? { items: Array() } : yield* client.listTeamKits(activeTeamId, { workspaceId });
|
|
2447
2093
|
const userPreferences = config.userAgentPreferences;
|
|
2448
2094
|
const syncKits = selectedHarnesses.map((harness) => resolveSyncKit({
|
|
2449
2095
|
defaultHarness: harness,
|
|
2450
2096
|
activeKits: syncKitActiveKits({
|
|
2451
2097
|
workspaceId,
|
|
2452
|
-
...
|
|
2098
|
+
...activeTeamId === undefined ? undefined : { teamId: activeTeamId },
|
|
2453
2099
|
...userPreferences === undefined ? undefined : { userPreferences },
|
|
2454
2100
|
harnesses: [harness],
|
|
2455
2101
|
workspaceKits: workspaceKits.items,
|
|
2456
|
-
|
|
2102
|
+
teamKits: teamKits.items
|
|
2457
2103
|
})
|
|
2458
2104
|
}));
|
|
2459
2105
|
const blockedSyncKit = syncKits.find((syncKit) => syncKit.status === "blocked");
|
|
@@ -2461,13 +2107,13 @@ function harnessSync(args) {
|
|
|
2461
2107
|
return yield* new CliError({ message: `Sync Kit is blocked: ${blockedSyncKit.blockers.join("; ")}` });
|
|
2462
2108
|
const fragments = syncFragments({
|
|
2463
2109
|
workspaceId,
|
|
2464
|
-
...
|
|
2110
|
+
...activeTeamId === undefined ? undefined : { teamId: activeTeamId },
|
|
2465
2111
|
userId: "default",
|
|
2466
2112
|
workspaceKits: workspaceKits.items,
|
|
2467
|
-
|
|
2113
|
+
teamKits: teamKits.items
|
|
2468
2114
|
});
|
|
2469
|
-
const syncProfile = syncProfileForCommand(command, config, workspaceId,
|
|
2470
|
-
const
|
|
2115
|
+
const syncProfile = syncProfileForCommand(command, config, workspaceId, activeTeamId);
|
|
2116
|
+
const teamContext = activeTeamId === undefined ? undefined : { workspaceId, teamId: activeTeamId };
|
|
2471
2117
|
const environments = selectedHarnesses.map((harness) => {
|
|
2472
2118
|
const environment = resolveAgentEnvironmentFromSources({
|
|
2473
2119
|
userId: "default",
|
|
@@ -2475,27 +2121,27 @@ function harnessSync(args) {
|
|
|
2475
2121
|
...userPreferences === undefined ? undefined : { userPreferences },
|
|
2476
2122
|
syncProfile,
|
|
2477
2123
|
fragments,
|
|
2478
|
-
...
|
|
2124
|
+
...teamContext === undefined ? undefined : { teamContext }
|
|
2479
2125
|
});
|
|
2480
2126
|
return {
|
|
2481
2127
|
...environment,
|
|
2482
2128
|
warnings: [
|
|
2483
2129
|
...environment.warnings,
|
|
2484
|
-
...(
|
|
2130
|
+
...(teamKits.policyOptOuts ?? []).map((optOut) => `Workspace-default Kit ${optOut.kitId} revision ${optOut.policyRevision} is opted out for this Team.`)
|
|
2485
2131
|
]
|
|
2486
2132
|
};
|
|
2487
2133
|
});
|
|
2488
|
-
const
|
|
2489
|
-
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()) ?? {});
|
|
2490
2136
|
const environmentPlan = composeAgentEnvironmentSyncPlan(targetPlans);
|
|
2491
2137
|
const materializationPlans = targetPlans.map((target) => target.skills);
|
|
2492
|
-
const userConfigPlans = command.flags.
|
|
2493
|
-
const
|
|
2494
|
-
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];
|
|
2495
2141
|
const plan = buildHarnessSyncPlan({
|
|
2496
2142
|
apiUrl: config.apiUrl,
|
|
2497
2143
|
workspaceId,
|
|
2498
|
-
...
|
|
2144
|
+
...activeTeamId === undefined ? undefined : { teamId: activeTeamId },
|
|
2499
2145
|
harnesses: selectedHarnesses,
|
|
2500
2146
|
outputPath,
|
|
2501
2147
|
materializationPlans,
|
|
@@ -2513,7 +2159,7 @@ function harnessSync(args) {
|
|
|
2513
2159
|
materializationPlans: resultPlan.targets.map((target) => target.skills),
|
|
2514
2160
|
configPlans,
|
|
2515
2161
|
userConfigPlans,
|
|
2516
|
-
|
|
2162
|
+
teamConfigPlans,
|
|
2517
2163
|
syncKits,
|
|
2518
2164
|
outputPath,
|
|
2519
2165
|
applied
|
|
@@ -2546,12 +2192,12 @@ function syncTargetHarnesses(command, config) {
|
|
|
2546
2192
|
return [config.userAgentPreferences.defaultHarness];
|
|
2547
2193
|
return AgentHarnessIds;
|
|
2548
2194
|
}
|
|
2549
|
-
function syncProfileForCommand(command, config, workspaceId,
|
|
2195
|
+
function syncProfileForCommand(command, config, workspaceId, teamId) {
|
|
2550
2196
|
const harnesses = [...syncTargetHarnesses(command, config)];
|
|
2551
2197
|
const existing = config.agentSyncProfile;
|
|
2552
2198
|
const sources = existing?.sources ?? [
|
|
2553
2199
|
{ kind: "personal_kit_defaults", enabled: true },
|
|
2554
|
-
...
|
|
2200
|
+
...teamId === undefined ? [] : [{ kind: "team_kits", teamId, enabled: true }]
|
|
2555
2201
|
];
|
|
2556
2202
|
return {
|
|
2557
2203
|
id: existing?.id ?? "default",
|
|
@@ -2564,7 +2210,7 @@ function syncProfileForCommand(command, config, workspaceId, projectId) {
|
|
|
2564
2210
|
}
|
|
2565
2211
|
function syncKitActiveKits(input) {
|
|
2566
2212
|
const legacyKit = (kit, scope, index) => {
|
|
2567
|
-
const active = scope === "
|
|
2213
|
+
const active = scope === "team" ? true : isInstallDefaultKit(kit) || input.harnesses.some((harness) => kitEnabledForHarness(input.userPreferences, harness, kit.id));
|
|
2568
2214
|
const metadata = kit.metadata ?? {};
|
|
2569
2215
|
const version = isString2(metadata.version) && metadata.version.length > 0 ? metadata.version : "legacy";
|
|
2570
2216
|
return {
|
|
@@ -2573,11 +2219,11 @@ function syncKitActiveKits(input) {
|
|
|
2573
2219
|
kitRef: `kit:${kit.slug}`,
|
|
2574
2220
|
version,
|
|
2575
2221
|
sha256: digestSha256(encodeUnknownJsonStringSync(kit), "hex"),
|
|
2576
|
-
precedence: scope === "
|
|
2222
|
+
precedence: scope === "team" ? 20 + index : 10 + index
|
|
2577
2223
|
},
|
|
2578
2224
|
activation: {
|
|
2579
2225
|
activationId: `legacy:${kit.id}:${scope}`,
|
|
2580
|
-
activationType: scope === "
|
|
2226
|
+
activationType: scope === "team" ? "team_attachment" : "personal_default"
|
|
2581
2227
|
},
|
|
2582
2228
|
enabled: active,
|
|
2583
2229
|
gates: kit.agentEnvironment === undefined ? [] : [
|
|
@@ -2591,7 +2237,7 @@ function syncKitActiveKits(input) {
|
|
|
2591
2237
|
};
|
|
2592
2238
|
return [
|
|
2593
2239
|
...input.workspaceKits.map((kit, index) => legacyKit(kit, "workspace", index)),
|
|
2594
|
-
...input.
|
|
2240
|
+
...input.teamId === undefined ? [] : input.teamKits.map((kit, index) => legacyKit(kit, "team", index))
|
|
2595
2241
|
];
|
|
2596
2242
|
}
|
|
2597
2243
|
function syncFragments(input) {
|
|
@@ -2612,27 +2258,27 @@ function syncFragments(input) {
|
|
|
2612
2258
|
fragment: kit.agentEnvironment
|
|
2613
2259
|
});
|
|
2614
2260
|
}
|
|
2615
|
-
for (const kit of input.
|
|
2616
|
-
if (kit.agentEnvironment === undefined || input.
|
|
2261
|
+
for (const kit of input.teamKits) {
|
|
2262
|
+
if (kit.agentEnvironment === undefined || input.teamId === undefined)
|
|
2617
2263
|
continue;
|
|
2618
2264
|
fragments.push({
|
|
2619
|
-
sourceKind: "
|
|
2265
|
+
sourceKind: "team_kit",
|
|
2620
2266
|
workspaceId: input.workspaceId,
|
|
2621
|
-
|
|
2267
|
+
teamId: input.teamId,
|
|
2622
2268
|
source: {
|
|
2623
|
-
kind: "
|
|
2269
|
+
kind: "team_kit",
|
|
2624
2270
|
kitId: kit.id,
|
|
2625
2271
|
label: kit.name,
|
|
2626
|
-
...
|
|
2627
|
-
...kitSourceMetadata(kit, "
|
|
2272
|
+
...teamKitPolicyMetadata(kit),
|
|
2273
|
+
...kitSourceMetadata(kit, "team_attachment")
|
|
2628
2274
|
},
|
|
2629
2275
|
fragment: kit.agentEnvironment
|
|
2630
2276
|
});
|
|
2631
2277
|
}
|
|
2632
2278
|
return fragments;
|
|
2633
2279
|
}
|
|
2634
|
-
function
|
|
2635
|
-
const attachment = kit.
|
|
2280
|
+
function teamKitPolicyMetadata(kit) {
|
|
2281
|
+
const attachment = kit.teamAttachment;
|
|
2636
2282
|
if (attachment === undefined)
|
|
2637
2283
|
return {};
|
|
2638
2284
|
return {
|
|
@@ -2657,14 +2303,14 @@ function isInstallDefaultKit(kit) {
|
|
|
2657
2303
|
const rollout = isObject(marketplace) && marketplace !== null && "rollout" in marketplace ? marketplace.rollout : undefined;
|
|
2658
2304
|
return isObject(rollout) && rollout !== null && "installDefault" in rollout && rollout.installDefault === true;
|
|
2659
2305
|
}
|
|
2660
|
-
function previewEnvironmentPlans(environments, command,
|
|
2306
|
+
function previewEnvironmentPlans(environments, command, teamRoot, mcpCredentialName, syncKits = [], userConfig = {}) {
|
|
2661
2307
|
const previews = forEach(environments, (environment, index) => gen(function* () {
|
|
2662
|
-
const rootDir = command.flags.
|
|
2308
|
+
const rootDir = command.flags.teamScope ? teamRoot : getOrUndefined(yield* option2(String3("HOME")));
|
|
2663
2309
|
const { previewAgentEnvironmentPlan } = yield* agentRuntime;
|
|
2664
2310
|
return yield* previewAgentEnvironmentPlan({
|
|
2665
2311
|
environment,
|
|
2666
2312
|
...syncKits[index] === undefined ? undefined : { syncKit: syncKits[index] },
|
|
2667
|
-
scope: command.flags.
|
|
2313
|
+
scope: command.flags.teamScope ? "project" : "user",
|
|
2668
2314
|
...rootDir === undefined ? undefined : { rootDir },
|
|
2669
2315
|
...command.flags.noInstall === undefined ? undefined : { noInstall: command.flags.noInstall },
|
|
2670
2316
|
...userConfig.approvedSkillSources === undefined ? undefined : { approvedSkillSources: userConfig.approvedSkillSources },
|
|
@@ -2811,14 +2457,14 @@ function mixedKitState(kit, harnesses, preferences) {
|
|
|
2811
2457
|
return "disabled";
|
|
2812
2458
|
return "mixed";
|
|
2813
2459
|
}
|
|
2814
|
-
var
|
|
2460
|
+
var resolveTeamWriteRoot = gen(function* () {
|
|
2815
2461
|
const fs = yield* FileSystem;
|
|
2816
2462
|
const path = yield* Path;
|
|
2817
2463
|
const cwd = path.resolve(".");
|
|
2818
2464
|
if ((yield* fs.exists(path.join(cwd, ".git"))) || (yield* fs.exists(path.join(cwd, ".kitman/config.json"))))
|
|
2819
2465
|
return cwd;
|
|
2820
2466
|
return yield* new CliError({
|
|
2821
|
-
message: "
|
|
2467
|
+
message: "Team sync requires a repo root or local team config in the current working directory."
|
|
2822
2468
|
});
|
|
2823
2469
|
});
|
|
2824
2470
|
function parseHarnesses(values) {
|
|
@@ -2945,7 +2591,7 @@ function whoami(args) {
|
|
|
2945
2591
|
user: "userId" in me ? me.userId : me.actorId,
|
|
2946
2592
|
apiUrl,
|
|
2947
2593
|
activeWorkspace: me.activeWorkspaceId ?? config.activeWorkspaceId ?? me.workspaceId ?? null,
|
|
2948
|
-
|
|
2594
|
+
activeTeam: config.activeTeamId ?? null,
|
|
2949
2595
|
workspaceRole: me.workspaceRole ?? null,
|
|
2950
2596
|
credentialType: me.credentialType,
|
|
2951
2597
|
tokenExpires: session.expiresAt ?? null
|
|
@@ -2957,7 +2603,7 @@ function whoami(args) {
|
|
|
2957
2603
|
yield* log(`User: ${output.user}`);
|
|
2958
2604
|
yield* log(`API URL: ${output.apiUrl}`);
|
|
2959
2605
|
yield* log(`Active workspace: ${output.activeWorkspace ?? "none"}`);
|
|
2960
|
-
yield* log(`Active
|
|
2606
|
+
yield* log(`Active team: ${output.activeTeam ?? "none"}`);
|
|
2961
2607
|
yield* log(`Workspace role: ${output.workspaceRole ?? "unknown"}`);
|
|
2962
2608
|
yield* log(`Credential type: ${output.credentialType}`);
|
|
2963
2609
|
yield* log(`Token expires: ${output.tokenExpires ?? "unknown"}`);
|
|
@@ -2973,7 +2619,7 @@ function configCommand(args) {
|
|
|
2973
2619
|
const output = {
|
|
2974
2620
|
apiUrl: normalizeResolvedApiUrl(config.apiUrl),
|
|
2975
2621
|
activeWorkspace: config.activeWorkspaceId ?? null,
|
|
2976
|
-
|
|
2622
|
+
activeTeam: config.activeTeamId ?? null,
|
|
2977
2623
|
sources: resolved.sources,
|
|
2978
2624
|
userConfigPath: resolved.userConfigPath,
|
|
2979
2625
|
...resolved.localConfigPath ? { localConfigPath: resolved.localConfigPath } : undefined
|
|
@@ -2987,8 +2633,8 @@ function configCommand(args) {
|
|
|
2987
2633
|
"API URL": output.apiUrl,
|
|
2988
2634
|
"Active workspace": output.activeWorkspace ?? "none",
|
|
2989
2635
|
"Active workspace source": output.sources.activeWorkspace,
|
|
2990
|
-
"Active
|
|
2991
|
-
"Active
|
|
2636
|
+
"Active team": output.activeTeam ?? "none",
|
|
2637
|
+
"Active team source": output.sources.activeTeam,
|
|
2992
2638
|
"API URL source": output.sources.apiUrl,
|
|
2993
2639
|
"User config path": output.userConfigPath,
|
|
2994
2640
|
...output.localConfigPath ? { "Local config path": output.localConfigPath } : undefined
|
|
@@ -3033,52 +2679,80 @@ function workspaceCommand(args) {
|
|
|
3033
2679
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
3034
2680
|
});
|
|
3035
2681
|
}
|
|
3036
|
-
function
|
|
2682
|
+
function teamCommand(args) {
|
|
3037
2683
|
return gen(function* () {
|
|
3038
2684
|
const command = parseCommand(args);
|
|
3039
2685
|
const apiUrl = yield* resolveApiUrl(command.flags.apiUrl);
|
|
3040
|
-
const
|
|
3041
|
-
const
|
|
3042
|
-
if (command.action === "list")
|
|
3043
|
-
|
|
3044
|
-
const projects = yield* client.listProjects(workspaceId);
|
|
3045
|
-
return yield* printRows(projects.items.map(projectRow), command.flags.json);
|
|
3046
|
-
}
|
|
3047
|
-
if (command.action === "create") {
|
|
3048
|
-
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
3049
|
-
const name = requiredArg(command, "project name");
|
|
3050
|
-
const project = yield* client.createProject(workspaceId, { name });
|
|
3051
|
-
yield* writeActiveProject(apiUrl, project.workspaceId, project.id);
|
|
3052
|
-
return yield* printRows([projectRow(project)], command.flags.json);
|
|
3053
|
-
}
|
|
3054
|
-
if (command.action === "show") {
|
|
3055
|
-
const projectRef = requiredArg(command, "project");
|
|
3056
|
-
const workspaceId = yield* optionalActiveWorkspaceId(command.flags);
|
|
3057
|
-
const project = yield* client.getProject(projectRef, workspaceId ? { workspaceId } : undefined);
|
|
3058
|
-
return yield* printRows([projectRow(project)], command.flags.json);
|
|
3059
|
-
}
|
|
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);
|
|
3060
2690
|
if (command.action === "use") {
|
|
3061
|
-
const
|
|
3062
|
-
|
|
3063
|
-
const project = yield* client.getProject(projectRef, workspaceId ? { workspaceId } : undefined);
|
|
3064
|
-
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);
|
|
3065
2693
|
if (command.flags.json)
|
|
3066
|
-
return yield* printRows([
|
|
3067
|
-
yield* log(`Active
|
|
2694
|
+
return yield* printRows([teamRow(team)], true);
|
|
2695
|
+
yield* log(`Active team: ${team.name}`);
|
|
3068
2696
|
return;
|
|
3069
2697
|
}
|
|
3070
|
-
if (command.action === "
|
|
3071
|
-
const
|
|
3072
|
-
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);
|
|
3073
2740
|
if (command.flags.json)
|
|
3074
2741
|
return yield* printJson(repoBindings);
|
|
3075
|
-
return yield* printRows(repoBindings.items.map(repoBindingRow), false);
|
|
2742
|
+
return yield* printRows(repoBindings.items.map((binding) => ({ ...repoBindingRow(binding), team: ownerOf(binding.teamId) })), false);
|
|
3076
2743
|
}
|
|
3077
|
-
if (command.action === "add
|
|
3078
|
-
const
|
|
2744
|
+
if (command.action === "add") {
|
|
2745
|
+
const team = command.flags.team === undefined ? undefined : yield* teamIn(teams, command.flags.team);
|
|
3079
2746
|
const input = command.flags.fromCurrentRepo === true ? yield* repoBindingInputFromCurrentRepo : repoBindingInput2(command);
|
|
3080
|
-
const repoBinding = yield* client.createRepoBinding(
|
|
3081
|
-
|
|
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);
|
|
3082
2756
|
}
|
|
3083
2757
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
3084
2758
|
});
|
|
@@ -3109,7 +2783,30 @@ function runnerCommand(args) {
|
|
|
3109
2783
|
yield* log(`Runner bootstrap prepared for ${output.profile}; no canonical state was created.`);
|
|
3110
2784
|
return;
|
|
3111
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
|
+
}
|
|
3112
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
|
+
}
|
|
3113
2810
|
const session = yield* requiredSessionForApi(apiUrl);
|
|
3114
2811
|
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
3115
2812
|
const client = yield* clientFor(apiUrl, session);
|
|
@@ -3149,21 +2846,21 @@ function runnerCommand(args) {
|
|
|
3149
2846
|
return yield* new CliError({ message: "runner grant requires add, list, or remove and --runner <runner-id>" });
|
|
3150
2847
|
const runner = yield* resolveRunnerForCommand(client, workspaceId, runnerRef);
|
|
3151
2848
|
if (grantAction === "list") {
|
|
3152
|
-
const response = yield* client.
|
|
2849
|
+
const response = yield* client.listRunnerTeamGrants(runner.id);
|
|
3153
2850
|
return yield* printRows(response.items.map((grant) => ({
|
|
3154
2851
|
id: grant.id,
|
|
3155
2852
|
runnerId: grant.runnerId,
|
|
3156
2853
|
workspaceId: grant.workspaceId,
|
|
3157
|
-
|
|
2854
|
+
teamId: grant.teamId,
|
|
3158
2855
|
createdAt: grant.createdAt
|
|
3159
2856
|
})), command.flags.json);
|
|
3160
2857
|
}
|
|
3161
2858
|
if (grantAction === "add" || grantAction === "remove") {
|
|
3162
|
-
const
|
|
3163
|
-
if (!
|
|
3164
|
-
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>` });
|
|
3165
2862
|
if (grantAction === "add") {
|
|
3166
|
-
const response = yield* client.
|
|
2863
|
+
const response = yield* client.addRunnerTeamGrant(runner.id, { teamId });
|
|
3167
2864
|
if (command.flags.json)
|
|
3168
2865
|
return yield* printJson(response);
|
|
3169
2866
|
return yield* printRows([
|
|
@@ -3171,15 +2868,15 @@ function runnerCommand(args) {
|
|
|
3171
2868
|
id: response.id,
|
|
3172
2869
|
runnerId: response.runnerId,
|
|
3173
2870
|
workspaceId: response.workspaceId,
|
|
3174
|
-
|
|
2871
|
+
teamId: response.teamId,
|
|
3175
2872
|
status: "granted"
|
|
3176
2873
|
}
|
|
3177
2874
|
], false);
|
|
3178
2875
|
}
|
|
3179
|
-
const response = yield* client.
|
|
2876
|
+
const response = yield* client.removeRunnerTeamGrant(runner.id, teamId);
|
|
3180
2877
|
if (command.flags.json)
|
|
3181
2878
|
return yield* printJson(response);
|
|
3182
|
-
return yield* printRows([{ runnerId: runner.id,
|
|
2879
|
+
return yield* printRows([{ runnerId: runner.id, teamId, status: response.removed ? "removed" : "not_found" }], false);
|
|
3183
2880
|
}
|
|
3184
2881
|
return yield* new CliError({ message: `Unknown runner grant action: ${grantAction}` });
|
|
3185
2882
|
}
|
|
@@ -3192,9 +2889,8 @@ function runnerCommand(args) {
|
|
|
3192
2889
|
const ownership = runnerOwnershipFromCommand(command, false);
|
|
3193
2890
|
const response = yield* client.registerRunner(workspaceId, {
|
|
3194
2891
|
name,
|
|
3195
|
-
...command.flags.agentIdentityId === undefined ? undefined : { agentIdentityId: command.flags.agentIdentityId },
|
|
3196
2892
|
...ownership === undefined ? undefined : { ownership },
|
|
3197
|
-
...command.flags.
|
|
2893
|
+
...command.flags.team === undefined ? undefined : { teamId: command.flags.team },
|
|
3198
2894
|
capabilities: herdr ? (yield* agentRuntime).herdrRunnerCapabilities({ harnesses: yield* (yield* agentRuntime).detectHarnesses }) : loopRunnerCapabilities(),
|
|
3199
2895
|
labels: herdr ? ["kitman-runner", "kitman-loop", "herdr"] : ["kitman-runner", "kitman-loop"]
|
|
3200
2896
|
});
|
|
@@ -3218,26 +2914,11 @@ function runnerCommand(args) {
|
|
|
3218
2914
|
const runnerRef = command.flags.runnerId ?? requiredArg(command, "Runner");
|
|
3219
2915
|
const runner = yield* resolveRunnerForCommand(client, workspaceId, runnerRef);
|
|
3220
2916
|
const response = yield* client.heartbeatRunner(runner.id, {
|
|
3221
|
-
healthStatus: "healthy",
|
|
3222
2917
|
capabilities: runner.capabilities,
|
|
3223
2918
|
labels: runner.labels
|
|
3224
2919
|
});
|
|
3225
2920
|
return yield* printRows([runnerRow(response)], command.flags.json);
|
|
3226
2921
|
}
|
|
3227
|
-
if (command.action === "start") {
|
|
3228
|
-
if (command.flags.backend !== undefined)
|
|
3229
|
-
return yield* new CliError({ message: "runner start accepts only a Runner identity" });
|
|
3230
|
-
const runnerRef = command.flags.runnerId ?? requiredArg(command, "Runner");
|
|
3231
|
-
const runner = yield* resolveRunnerForCommand(client, workspaceId, runnerRef);
|
|
3232
|
-
return yield* startCanonicalHerdrRunnerCommand({
|
|
3233
|
-
apiUrl,
|
|
3234
|
-
accessToken: session.accessToken,
|
|
3235
|
-
workspaceId,
|
|
3236
|
-
...command.flags.project === undefined ? undefined : { projectId: command.flags.project },
|
|
3237
|
-
runner,
|
|
3238
|
-
client
|
|
3239
|
-
});
|
|
3240
|
-
}
|
|
3241
2922
|
if (command.action === "doctor") {
|
|
3242
2923
|
const runners = yield* client.listRunners(workspaceId);
|
|
3243
2924
|
if (command.flags.runnerId === undefined && command.args.length === 0 && command.flags.backend === undefined)
|
|
@@ -3261,6 +2942,16 @@ function runnerCommand(args) {
|
|
|
3261
2942
|
var SandboxHostProbe = Reference("kitman/cli/SandboxHostProbe", {
|
|
3262
2943
|
defaultValue: () => flatMap2(agentRuntime, (runtime) => runtime.commandHostProbe)
|
|
3263
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
|
+
});
|
|
3264
2955
|
var runnerSettings = gen(function* () {
|
|
3265
2956
|
const optional = (name) => String3(name).pipe(option2, map3((value) => getOrUndefined(value)?.trim()));
|
|
3266
2957
|
const concurrency = Number(yield* optional("KITMAN_RUNNER_CONCURRENCY")) || 1;
|
|
@@ -3315,27 +3006,40 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3315
3006
|
superviseReplica,
|
|
3316
3007
|
verificationExecutor
|
|
3317
3008
|
} = yield* agentRuntime;
|
|
3318
|
-
const
|
|
3009
|
+
const openRunnerRpc = yield* OpenRunnerRpc;
|
|
3319
3010
|
const settings = yield* runnerSettings;
|
|
3320
3011
|
const { concurrency, fullAccess, sshAgentAvailable } = settings;
|
|
3321
|
-
const
|
|
3322
|
-
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 }));
|
|
3323
3022
|
const sandbox = personal ? settings.sandbox : host.backends.includes("docker") ? "docker" : "srt";
|
|
3324
|
-
if (personal && !host.backends.includes(sandbox))
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
});
|
|
3328
|
-
|
|
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);
|
|
3329
3030
|
const harnesses = yield* detectHarnesses;
|
|
3330
3031
|
if (harnesses.harnesses.length > 0)
|
|
3331
3032
|
yield* harnessSync([
|
|
3332
3033
|
"sync",
|
|
3333
3034
|
...harnesses.harnesses.map((harness) => harness === "claude" ? "claude-code" : harness),
|
|
3334
3035
|
"--workspace",
|
|
3335
|
-
|
|
3036
|
+
runner.workspaceId,
|
|
3336
3037
|
"--api-url",
|
|
3337
3038
|
input.apiUrl
|
|
3338
|
-
]).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}`)));
|
|
3339
3043
|
const advertisedCapabilities = repoBinding === undefined ? herdrRunnerCapabilities({ harnesses, fullAccess, sandbox: host, shared: !personal }) : herdrRunnerCapabilitiesForRepoBinding(repoBinding, {
|
|
3340
3044
|
harnesses,
|
|
3341
3045
|
fullAccess,
|
|
@@ -3348,23 +3052,16 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3348
3052
|
resourceLimits: { ...advertisedCapabilities.resourceLimits, maxConcurrentRuns: concurrency }
|
|
3349
3053
|
};
|
|
3350
3054
|
assertHerdrRunnerCapability(capabilities);
|
|
3351
|
-
const credential = yield* ensureHerdrRunnerCredential({
|
|
3352
|
-
apiUrl: input.apiUrl,
|
|
3353
|
-
accessToken: input.accessToken,
|
|
3354
|
-
runner: input.runner
|
|
3355
|
-
});
|
|
3356
|
-
const controlPlane = yield* createHerdrRunnerClient({ apiUrl: input.apiUrl, runnerId: input.runner.id, credential });
|
|
3357
|
-
const fs = yield* FileSystem;
|
|
3358
3055
|
const path = yield* Path;
|
|
3359
3056
|
const runnerRootDir = yield* herdrRunnerRootDir;
|
|
3360
3057
|
yield* fs.makeDirectory(path.join(runnerRootDir, ".kitman", "worktrees"), { recursive: true });
|
|
3361
3058
|
yield* fs.makeDirectory(bundleCacheDir, { recursive: true });
|
|
3362
|
-
const replica = yield* controlPlane.client.registerRunnerReplica(input.
|
|
3059
|
+
const replica = yield* startupStep("register the replica", "30 seconds", stopOnRefusal(controlPlane.client.registerRunnerReplica(input.runnerId, {
|
|
3363
3060
|
name: `replica-${createId()}`,
|
|
3364
3061
|
capacity: { maxConcurrentRuns: concurrency },
|
|
3365
3062
|
telemetry: { activeWorkUnits: 0 }
|
|
3366
|
-
});
|
|
3367
|
-
const backend = herdrExecutorSurface({ runnerId: input.
|
|
3063
|
+
})));
|
|
3064
|
+
const backend = herdrExecutorSurface({ runnerId: input.runnerId, session: personal ? "default" : "own" });
|
|
3368
3065
|
const uid = process.getuid?.();
|
|
3369
3066
|
const gid = process.getgid?.();
|
|
3370
3067
|
const docker = host.backends.includes("docker") && settings.dockerImage !== undefined ? {
|
|
@@ -3375,41 +3072,46 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3375
3072
|
const root = herdrAgentBackendLayer({
|
|
3376
3073
|
client: () => backend.executionClient,
|
|
3377
3074
|
sandboxBackends: host.backends,
|
|
3378
|
-
...docker === undefined ? undefined : { docker: { ...docker, runnerId: input.
|
|
3379
|
-
}).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));
|
|
3380
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
|
+
});
|
|
3381
3085
|
return yield* gen(function* () {
|
|
3382
3086
|
const agentBackend = yield* AgentBackend;
|
|
3383
3087
|
const spawner = yield* ChildProcessSpawner;
|
|
3384
|
-
yield* controlPlane.client.heartbeatRunner(input.runner.id, {
|
|
3385
|
-
healthStatus: "healthy",
|
|
3386
|
-
capabilities,
|
|
3387
|
-
labels: input.runner.labels
|
|
3388
|
-
});
|
|
3389
|
-
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, {
|
|
3390
3089
|
healthStatus: "healthy",
|
|
3391
3090
|
drainState: "accepting",
|
|
3392
3091
|
capacity: { maxConcurrentRuns: concurrency },
|
|
3393
3092
|
telemetry: { activeWorkUnits: 0 }
|
|
3394
|
-
});
|
|
3093
|
+
})))));
|
|
3395
3094
|
return yield* scoped(gen(function* () {
|
|
3396
|
-
const rpc = yield*
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
});
|
|
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([])))));
|
|
3401
3100
|
const signals = yield* makeReplicaSignals;
|
|
3402
3101
|
const bundles = yield* kitmanBundles({
|
|
3403
3102
|
fetch: (digest) => rpc.bundle(digest).pipe(map3((found) => found.bundle))
|
|
3404
3103
|
});
|
|
3405
3104
|
const checkouts = runCheckouts(path.join(runnerRootDir, ".kitman"));
|
|
3406
3105
|
const platform = yield* context();
|
|
3106
|
+
const updated = yield* make5();
|
|
3107
|
+
const scope2 = yield* scope;
|
|
3407
3108
|
yield* listenForSignals({
|
|
3408
3109
|
rpc,
|
|
3409
3110
|
runnerReplicaId: replica.id,
|
|
3410
3111
|
signals,
|
|
3411
3112
|
checkouts,
|
|
3412
|
-
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)
|
|
3413
3115
|
}).pipe(forkScoped);
|
|
3414
3116
|
const authStore = yield* userAuthPath;
|
|
3415
3117
|
const runnerState = {
|
|
@@ -3420,7 +3122,7 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3420
3122
|
};
|
|
3421
3123
|
const sharedSandbox = personal ? undefined : {
|
|
3422
3124
|
runner: runnerState,
|
|
3423
|
-
...docker !== undefined ? { backend: "docker", docker, runnerId: input.
|
|
3125
|
+
...docker !== undefined ? { backend: "docker", docker, runnerId: input.runnerId } : { backend: host.backends.includes("srt") ? "srt" : undefined }
|
|
3424
3126
|
};
|
|
3425
3127
|
const runPackageProgram = programProcessExecutor({
|
|
3426
3128
|
bundles,
|
|
@@ -3435,12 +3137,14 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3435
3137
|
preflight: (mcp) => kitmanMcpPreflight(mcp).pipe(provideService(ChildProcessSpawner, spawner))
|
|
3436
3138
|
});
|
|
3437
3139
|
const heartbeatOf = (work) => rpc.heartbeat({ workUnitId: work.id, lease: leaseOf(work) });
|
|
3140
|
+
const storage = yield* provide3(service(HttpClient), layer2);
|
|
3438
3141
|
const runAgentStep = agentStepExecutor({
|
|
3439
3142
|
rootDir: path.join(runnerRootDir, ".kitman"),
|
|
3440
3143
|
heartbeat: heartbeatOf,
|
|
3441
|
-
checkpoints: rpcStepCheckpoints(rpc, signals),
|
|
3442
|
-
|
|
3443
|
-
|
|
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,
|
|
3444
3148
|
sharedRunner: !personal,
|
|
3445
3149
|
attempt: runnerStepAttemptFromAgentStep(work),
|
|
3446
3150
|
workspace: { path: worktree },
|
|
@@ -3456,38 +3160,49 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3456
3160
|
settled: heartbeatOf(work).pipe(map3((state) => state.settled || state.outputSubmitted === true)),
|
|
3457
3161
|
steering: {
|
|
3458
3162
|
...signals.steers(work.id),
|
|
3459
|
-
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))))
|
|
3460
3164
|
}
|
|
3461
3165
|
}).pipe(catch_((refusal) => succeed5({ status: "failed", reason: refusal.reason, summary: refusal.summary })), map3(agentStepRunOutcome))
|
|
3462
3166
|
});
|
|
3463
3167
|
const runVerification = verificationExecutor({
|
|
3464
3168
|
rootDir: path.join(runnerRootDir, ".kitman"),
|
|
3465
3169
|
known: rpcKnownEvidence(rpc),
|
|
3170
|
+
runFiles: { client: rpc, storage },
|
|
3466
3171
|
...sharedSandbox === undefined ? undefined : { sandbox: { ...sharedSandbox, apiUrl: input.apiUrl } }
|
|
3467
3172
|
});
|
|
3468
3173
|
yield* acquireRelease(backend.start(), () => backend.stop());
|
|
3469
3174
|
const control = (capacitySlot) => ({
|
|
3470
|
-
claim: () =>
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
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(),
|
|
3478
3193
|
capabilities,
|
|
3479
|
-
labels:
|
|
3480
|
-
}).pipe(flatMap2(() => controlPlane.client.heartbeatRunnerReplica(input.
|
|
3194
|
+
labels: runner.labels
|
|
3195
|
+
}).pipe(flatMap2(() => controlPlane.client.heartbeatRunnerReplica(input.runnerId, replica.id, {
|
|
3481
3196
|
healthStatus: "healthy",
|
|
3482
|
-
drainState: "accepting",
|
|
3197
|
+
drainState: updater.claiming() ? "accepting" : "draining",
|
|
3483
3198
|
capacity: { maxConcurrentRuns: concurrency },
|
|
3484
3199
|
telemetry: { activeWorkUnits: activeSlots.size }
|
|
3485
3200
|
}).pipe(asVoid))),
|
|
3486
3201
|
heartbeatWork: (workUnitId, lease) => rpc.heartbeat({ workUnitId, lease }),
|
|
3487
|
-
complete: (completion) => rpc.complete(completion)
|
|
3488
|
-
release: (workUnitId, lease) => rpc.release({ workUnitId, lease })
|
|
3202
|
+
complete: (completion) => rpc.complete(completion),
|
|
3203
|
+
release: (workUnitId, lease) => rpc.release({ workUnitId, lease })
|
|
3489
3204
|
});
|
|
3490
|
-
|
|
3205
|
+
const supervised = superviseReplica({
|
|
3491
3206
|
slots: Array.from({ length: concurrency }, (_, index) => `slot-${index + 1}`),
|
|
3492
3207
|
control,
|
|
3493
3208
|
executor: {
|
|
@@ -3510,9 +3225,11 @@ function startCanonicalHerdrRunnerCommand(input) {
|
|
|
3510
3225
|
},
|
|
3511
3226
|
signals
|
|
3512
3227
|
});
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
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}`)));
|
|
3516
3233
|
}
|
|
3517
3234
|
function checkHerdrRunnerReadiness(runner) {
|
|
3518
3235
|
const notReady = (code) => ({ backend: "herdr", status: "not_ready", code, runnerId: runner.id });
|
|
@@ -3542,7 +3259,7 @@ function mcpCommand(args) {
|
|
|
3542
3259
|
if (command.action === "run") {
|
|
3543
3260
|
if (command.args.length > 0)
|
|
3544
3261
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
3545
|
-
const { runMcpServerCommand } = yield* promise(() => import("./chunk-
|
|
3262
|
+
const { runMcpServerCommand } = yield* promise(() => import("./chunk-m1086bf9.js"));
|
|
3546
3263
|
return yield* runMcpServerCommand({ flags: command.flags });
|
|
3547
3264
|
}
|
|
3548
3265
|
if (command.action !== "setup")
|
|
@@ -3741,7 +3458,7 @@ function kitCommand(args) {
|
|
|
3741
3458
|
const source = requiredArg(command, "kit source");
|
|
3742
3459
|
const version = command.args[1];
|
|
3743
3460
|
const scopeType = kitInstallScope(command.flags.scopeType);
|
|
3744
|
-
const scopeId = command.flags.scopeId ?? (scopeType === "
|
|
3461
|
+
const scopeId = command.flags.scopeId ?? (scopeType === "team" ? yield* activeTeamId(command.flags) : undefined);
|
|
3745
3462
|
if ((scopeType === "user" || scopeType === "team") && scopeId === undefined)
|
|
3746
3463
|
return yield* new CliError({ message: `kit install --scope-type ${scopeType} requires --scope-id` });
|
|
3747
3464
|
const install = yield* client.installKit(workspaceId, {
|
|
@@ -3778,32 +3495,57 @@ function kitCommand(args) {
|
|
|
3778
3495
|
return;
|
|
3779
3496
|
}
|
|
3780
3497
|
if (command.action === "attach") {
|
|
3781
|
-
const
|
|
3498
|
+
const teamRef = requiredArg(command, "team");
|
|
3782
3499
|
const kitRef = command.args[1];
|
|
3783
3500
|
if (!kitRef)
|
|
3784
3501
|
return yield* new CliError({ message: "kit attach requires kit" });
|
|
3785
3502
|
const workspaceId = yield* optionalActiveWorkspaceId(command.flags);
|
|
3786
|
-
const result = yield* client.attachKit(
|
|
3503
|
+
const result = yield* client.attachKit(teamRef, kitRef, workspaceId ? { workspaceId } : undefined);
|
|
3787
3504
|
if (command.flags.json)
|
|
3788
|
-
return yield* printRows([{
|
|
3789
|
-
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}`);
|
|
3790
3507
|
return;
|
|
3791
3508
|
}
|
|
3792
3509
|
if (command.action === "detach") {
|
|
3793
|
-
const
|
|
3510
|
+
const teamRef = requiredArg(command, "team");
|
|
3794
3511
|
const kitRef = command.args[1];
|
|
3795
3512
|
if (!kitRef)
|
|
3796
3513
|
return yield* new CliError({ message: "kit detach requires kit" });
|
|
3797
3514
|
const workspaceId = yield* optionalActiveWorkspaceId(command.flags);
|
|
3798
|
-
const result = yield* client.detachKit(
|
|
3515
|
+
const result = yield* client.detachKit(teamRef, kitRef, workspaceId ? { workspaceId } : undefined);
|
|
3799
3516
|
if (command.flags.json)
|
|
3800
|
-
return yield* printRows([{
|
|
3801
|
-
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}`);
|
|
3802
3519
|
return;
|
|
3803
3520
|
}
|
|
3804
3521
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
3805
3522
|
});
|
|
3806
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
|
+
}
|
|
3807
3549
|
function taskCommand(args) {
|
|
3808
3550
|
return gen(function* () {
|
|
3809
3551
|
const command = parseCommand(args);
|
|
@@ -3811,16 +3553,18 @@ function taskCommand(args) {
|
|
|
3811
3553
|
const session = yield* requiredSessionForApi(apiUrl);
|
|
3812
3554
|
const client = yield* clientFor(apiUrl, session);
|
|
3813
3555
|
if (command.action === "list") {
|
|
3814
|
-
const
|
|
3556
|
+
const teamId = yield* activeTeamId(command.flags);
|
|
3815
3557
|
const tasks = [];
|
|
3816
|
-
let page = yield* client.listTasks(
|
|
3558
|
+
let page = yield* client.listTasks(teamId);
|
|
3817
3559
|
tasks.push(...page.items);
|
|
3818
3560
|
while (command.flags.status !== undefined && page.nextCursor !== undefined) {
|
|
3819
|
-
page = yield* client.listTasks(
|
|
3561
|
+
page = yield* client.listTasks(teamId, { cursor: page.nextCursor, limit: 100 });
|
|
3820
3562
|
tasks.push(...page.items);
|
|
3821
3563
|
}
|
|
3822
3564
|
return yield* printRows(tasks.flatMap((task) => inState(task, command.flags.status) ? [taskRow(task)] : []), command.flags.json);
|
|
3823
3565
|
}
|
|
3566
|
+
if (command.action === "assign")
|
|
3567
|
+
return yield* assignTask(client, command);
|
|
3824
3568
|
if (command.action === "show") {
|
|
3825
3569
|
const taskRef = requiredArg(command, "task");
|
|
3826
3570
|
const task = yield* client.getTask(taskRef);
|
|
@@ -3975,6 +3719,11 @@ function memoryCommand(args) {
|
|
|
3975
3719
|
yield* log(`Memory ${memoryTransitionPastTense(command.action)}: ${response.memory.id}`);
|
|
3976
3720
|
return;
|
|
3977
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
|
+
}
|
|
3978
3727
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
3979
3728
|
});
|
|
3980
3729
|
}
|
|
@@ -3985,8 +3734,8 @@ function proposalCommand(args) {
|
|
|
3985
3734
|
const session = yield* requiredSessionForApi(apiUrl);
|
|
3986
3735
|
const client = yield* clientFor(apiUrl, session);
|
|
3987
3736
|
if (command.action === "list") {
|
|
3988
|
-
const
|
|
3989
|
-
const response = yield* client.listProposals(
|
|
3737
|
+
const teamId = command.flags.team ?? (yield* activeTeamId(command.flags));
|
|
3738
|
+
const response = yield* client.listProposals(teamId, {
|
|
3990
3739
|
...command.flags.status === undefined ? undefined : { status: validateProposalStatus(command.flags.status) },
|
|
3991
3740
|
...command.flags.targetType === undefined ? undefined : { targetType: command.flags.targetType },
|
|
3992
3741
|
...command.flags.limit === undefined ? undefined : { limit: command.flags.limit }
|
|
@@ -4024,8 +3773,8 @@ function decisionCommand(args) {
|
|
|
4024
3773
|
const session = yield* requiredSessionForApi(apiUrl);
|
|
4025
3774
|
const client = yield* clientFor(apiUrl, session);
|
|
4026
3775
|
if (command.action === "list") {
|
|
4027
|
-
const
|
|
4028
|
-
const response = yield* client.listDecisions("
|
|
3776
|
+
const teamId = command.flags.team ?? (yield* activeTeamId(command.flags));
|
|
3777
|
+
const response = yield* client.listDecisions("team", teamId, {
|
|
4029
3778
|
status: command.flags.status === undefined ? "active" : validateDecisionStatus(command.flags.status),
|
|
4030
3779
|
...command.flags.limit === undefined ? undefined : { limit: command.flags.limit }
|
|
4031
3780
|
});
|
|
@@ -4036,6 +3785,11 @@ function decisionCommand(args) {
|
|
|
4036
3785
|
const decision = yield* client.getDecision(decisionId);
|
|
4037
3786
|
return yield* printRows([decisionRow(decision)], command.flags.json);
|
|
4038
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
|
+
}
|
|
4039
3793
|
return yield* new CliError({ message: `Unknown command: ${args.join(" ")}` });
|
|
4040
3794
|
});
|
|
4041
3795
|
}
|
|
@@ -4071,18 +3825,18 @@ function programLoopCommand(command) {
|
|
|
4071
3825
|
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4072
3826
|
const print = (value, lines) => command.flags.json ? printJson(value) : forEach(lines(value), (line) => log(line), { discard: true });
|
|
4073
3827
|
if (command.action === "list") {
|
|
4074
|
-
const
|
|
4075
|
-
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 });
|
|
4076
3830
|
return yield* print(listed, (value) => value.items.flatMap(programLoopLines));
|
|
4077
3831
|
}
|
|
4078
3832
|
if (command.action === "starts") {
|
|
4079
|
-
const started = yield* client.listAutomaticStarts(yield*
|
|
3833
|
+
const started = yield* client.listAutomaticStarts(yield* activeTeamId(command.flags), command.flags.limit === undefined ? {} : { limit: command.flags.limit });
|
|
4080
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}`}`));
|
|
4081
3835
|
}
|
|
4082
3836
|
if (command.action === "show") {
|
|
4083
3837
|
const loop = requiredArg(command, "a Loop");
|
|
4084
|
-
const
|
|
4085
|
-
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 });
|
|
4086
3840
|
return yield* print(shown, (value) => value.resolved === undefined ? [`${loop} resolves to nothing here`] : programLoopLines(value.resolved));
|
|
4087
3841
|
}
|
|
4088
3842
|
return yield* new CliError({ message: `Unknown loop command: ${command.action}` });
|
|
@@ -4108,8 +3862,8 @@ function agentCommand(args) {
|
|
|
4108
3862
|
resolvedField(agent, "effort")
|
|
4109
3863
|
].filter((field) => field !== "").join(" ");
|
|
4110
3864
|
if (command.action === "list") {
|
|
4111
|
-
const
|
|
4112
|
-
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 });
|
|
4113
3867
|
return yield* print(listed, (value) => value.items.map(agentLine));
|
|
4114
3868
|
}
|
|
4115
3869
|
return yield* new CliError({ message: `Unknown agent command: ${command.action}. Use kitman agent list.` });
|
|
@@ -4132,21 +3886,21 @@ function loopCatalogCommand(args) {
|
|
|
4132
3886
|
const command = parseCommand(args);
|
|
4133
3887
|
const apiUrl = yield* resolveApiUrl(command.flags.apiUrl);
|
|
4134
3888
|
const client = yield* loopClientFor(apiUrl);
|
|
4135
|
-
const
|
|
3889
|
+
const teamId = yield* activeTeamId(command.flags);
|
|
4136
3890
|
if (command.action === "list" || command.action === "catalog") {
|
|
4137
|
-
const response = yield* client.requestJson("listLoops", `/loops${loopListQuery(
|
|
3891
|
+
const response = yield* client.requestJson("listLoops", `/loops${loopListQuery(teamId, command.flags.limit)}`);
|
|
4138
3892
|
return yield* printOperationalResult(command, response);
|
|
4139
3893
|
}
|
|
4140
3894
|
const loopRef = requiredArg(command, "Loop");
|
|
4141
3895
|
if (command.action === "show") {
|
|
4142
|
-
const response = yield* client.requestJson("getLoop", `/loops/${encodeURIComponent(loopRef)}?
|
|
3896
|
+
const response = yield* client.requestJson("getLoop", `/loops/${encodeURIComponent(loopRef)}?teamId=${encodeURIComponent(teamId)}`);
|
|
4143
3897
|
return yield* printOperationalResult(command, response);
|
|
4144
3898
|
}
|
|
4145
3899
|
if (command.action === "validate" || command.action === "test") {
|
|
4146
3900
|
const input = yield* operationalInput(command, `loop ${command.action}`);
|
|
4147
3901
|
const response = yield* client.requestJson(`loop${capitalize(command.action)}`, `/loops/${encodeURIComponent(loopRef)}/${command.action}`, {
|
|
4148
3902
|
method: "POST",
|
|
4149
|
-
body: {
|
|
3903
|
+
body: { teamId, loopRef, ...input }
|
|
4150
3904
|
});
|
|
4151
3905
|
return yield* printOperationalResult(command, response);
|
|
4152
3906
|
}
|
|
@@ -4164,9 +3918,9 @@ var launchContext = (flags) => gen(function* () {
|
|
|
4164
3918
|
const read = (args) => git(args).pipe(orElseSucceed(() => ""));
|
|
4165
3919
|
const remoteUrl = yield* read(["remote", "get-url", "origin"]);
|
|
4166
3920
|
const branch = yield* read(["branch", "--show-current"]);
|
|
4167
|
-
const
|
|
3921
|
+
const teamId = yield* optionalActiveTeamId(flags);
|
|
4168
3922
|
return {
|
|
4169
|
-
...
|
|
3923
|
+
...teamId === undefined ? undefined : { teamId },
|
|
4170
3924
|
...remoteUrl === "" ? undefined : { remoteUrl },
|
|
4171
3925
|
...branch === "" ? undefined : { branch }
|
|
4172
3926
|
};
|
|
@@ -4297,12 +4051,14 @@ var inboxItemLine = (item) => {
|
|
|
4297
4051
|
`${item.subject.kind}:${item.subject.id}`
|
|
4298
4052
|
].join(" ");
|
|
4299
4053
|
};
|
|
4300
|
-
var inboxSubjectKinds = ["task", "run", "proposal"];
|
|
4054
|
+
var inboxSubjectKinds = ["task", "run", "proposal", "memory", "project_update", "runner"];
|
|
4301
4055
|
var inboxSubjectOf = (ref) => {
|
|
4302
4056
|
const separator = ref.indexOf(":");
|
|
4303
4057
|
const kind = inboxSubjectKinds.find((known) => known === ref.slice(0, separator));
|
|
4304
4058
|
const id = ref.slice(separator + 1);
|
|
4305
|
-
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
|
+
}));
|
|
4306
4062
|
};
|
|
4307
4063
|
var inboxActions = new Set([
|
|
4308
4064
|
"list",
|
|
@@ -4322,7 +4078,7 @@ function inboxCommand(args) {
|
|
|
4322
4078
|
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4323
4079
|
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
4324
4080
|
if (command.action === "my-teams") {
|
|
4325
|
-
const view = yield* client.showMyTeams(workspaceId, command.flags.
|
|
4081
|
+
const view = yield* client.showMyTeams(workspaceId, command.flags.team === undefined ? {} : { teamId: command.flags.team });
|
|
4326
4082
|
if (command.flags.json)
|
|
4327
4083
|
return yield* printJson(view);
|
|
4328
4084
|
return yield* forEach(myTeamsSections.flatMap(([section, title]) => [
|
|
@@ -4365,6 +4121,93 @@ function inboxCommand(args) {
|
|
|
4365
4121
|
return yield* command.flags.json ? printJson(counts) : log(`${counts.items} in your Inbox, ${counts.unread} unread`);
|
|
4366
4122
|
});
|
|
4367
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
|
+
});
|
|
4368
4211
|
function runCommand(args) {
|
|
4369
4212
|
return gen(function* () {
|
|
4370
4213
|
const command = parseCommand(args);
|
|
@@ -4396,17 +4239,35 @@ function runCommand(args) {
|
|
|
4396
4239
|
]);
|
|
4397
4240
|
}
|
|
4398
4241
|
if (command.action === "list") {
|
|
4399
|
-
const {
|
|
4242
|
+
const { team, loop, states, limit } = command.flags;
|
|
4400
4243
|
const listed = yield* client.listRuns(yield* activeWorkspaceId(command.flags), {
|
|
4401
|
-
...
|
|
4244
|
+
...team === undefined ? undefined : { teamId: team },
|
|
4402
4245
|
...loop === undefined ? undefined : { loop },
|
|
4403
4246
|
...states === undefined ? undefined : { states },
|
|
4404
4247
|
...limit === undefined ? undefined : { limit }
|
|
4405
4248
|
});
|
|
4406
4249
|
return yield* print(listed, (value) => value.items.length === 0 ? ["No Runs"] : value.items.map(runListLine));
|
|
4407
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}`]);
|
|
4266
|
+
}
|
|
4408
4267
|
const runId = requiredArg(command, "a Run");
|
|
4409
4268
|
switch (command.action) {
|
|
4269
|
+
case "files":
|
|
4270
|
+
return yield* print(yield* client.listRunFiles(runId, command.flags.stepKey === undefined ? {} : { step: command.flags.stepKey }), runFileLines);
|
|
4410
4271
|
case "show": {
|
|
4411
4272
|
const shown = {
|
|
4412
4273
|
...yield* client.showRun(runId),
|
|
@@ -4461,6 +4322,11 @@ function runCommand(args) {
|
|
|
4461
4322
|
});
|
|
4462
4323
|
return yield* print(answered, (question) => [`Answered ${question.stepKey}: ${value}`]);
|
|
4463
4324
|
}
|
|
4325
|
+
case "follow":
|
|
4326
|
+
return yield* followTranscripts(client, runId, {
|
|
4327
|
+
json: command.flags.json === true,
|
|
4328
|
+
pollIntervalMs: command.flags.pollIntervalMs ?? 1000
|
|
4329
|
+
});
|
|
4464
4330
|
case "steer": {
|
|
4465
4331
|
const text = command.args.slice(1).join(" ");
|
|
4466
4332
|
if (text === "")
|
|
@@ -4506,38 +4372,56 @@ function runCommand(args) {
|
|
|
4506
4372
|
}
|
|
4507
4373
|
});
|
|
4508
4374
|
}
|
|
4509
|
-
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* () {
|
|
4510
4395
|
const remote = yield* (yield* Git)(["remote", "get-url", "origin"]).pipe(orElseSucceed(() => ""));
|
|
4511
4396
|
if (remote === "")
|
|
4512
4397
|
return yield* new CliError({ message: "Run this inside a git checkout with an origin" });
|
|
4513
|
-
const
|
|
4514
|
-
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));
|
|
4515
4399
|
if (binding === undefined)
|
|
4516
|
-
return yield* new CliError({ message: `No repository of the
|
|
4400
|
+
return yield* new CliError({ message: `No repository of the Workspace matches ${remote.trim()}` });
|
|
4517
4401
|
return binding;
|
|
4518
4402
|
});
|
|
4519
4403
|
function verifyCommand(args) {
|
|
4520
4404
|
return gen(function* () {
|
|
4521
4405
|
const command = parseCommand(args.length === 1 ? [...args, "list"] : args);
|
|
4522
4406
|
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4523
|
-
const
|
|
4524
|
-
const binding = yield* currentRepoBinding(client,
|
|
4407
|
+
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
4408
|
+
const binding = yield* currentRepoBinding(client, workspaceId);
|
|
4525
4409
|
const print = (value, lines) => command.flags.json ? printJson(value) : forEach(lines(value), (line) => log(line), { discard: true });
|
|
4526
4410
|
switch (command.action) {
|
|
4527
4411
|
case "list":
|
|
4528
|
-
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})`}`));
|
|
4529
4413
|
case "add": {
|
|
4530
4414
|
const [name, ...words] = command.args;
|
|
4531
4415
|
if (name === undefined || words.length === 0)
|
|
4532
4416
|
return yield* new CliError({ message: "verify add requires a name and a command" });
|
|
4533
|
-
return yield* print(yield* client.approveVerification(
|
|
4417
|
+
return yield* print(yield* client.approveVerification(workspaceId, binding.id, {
|
|
4534
4418
|
name,
|
|
4535
4419
|
command: words.join(" "),
|
|
4536
4420
|
...command.flags.workingDirectory === undefined ? undefined : { workingDirectory: command.flags.workingDirectory }
|
|
4537
4421
|
}), (check) => [`Approved ${check.name}: ${check.command}`]);
|
|
4538
4422
|
}
|
|
4539
4423
|
case "remove":
|
|
4540
|
-
return yield* print(yield* client.removeVerification(
|
|
4424
|
+
return yield* print(yield* client.removeVerification(workspaceId, binding.id, requiredArg(command, "a verification")), (removed) => [`Removed ${removed.removed}`]);
|
|
4541
4425
|
default:
|
|
4542
4426
|
return yield* new CliError({ message: `Unknown verify command: ${command.action}` });
|
|
4543
4427
|
}
|
|
@@ -4549,13 +4433,13 @@ function secretCommand(args) {
|
|
|
4549
4433
|
const command = parseCommand(args.length === 1 ? [...args, "list"] : args);
|
|
4550
4434
|
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4551
4435
|
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
4552
|
-
const
|
|
4553
|
-
const scope =
|
|
4554
|
-
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}`;
|
|
4555
4439
|
const print = (value, lines) => command.flags.json ? printJson(value) : forEach(lines(value), (line) => log(line), { discard: true });
|
|
4556
4440
|
switch (command.action) {
|
|
4557
4441
|
case "list":
|
|
4558
|
-
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}`));
|
|
4559
4443
|
case "set": {
|
|
4560
4444
|
const name = requiredArg(command, "a Secret name");
|
|
4561
4445
|
if (command.args.length > 1)
|
|
@@ -4571,13 +4455,17 @@ function secretCommand(args) {
|
|
|
4571
4455
|
}
|
|
4572
4456
|
case "delete":
|
|
4573
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
|
+
}
|
|
4574
4462
|
default:
|
|
4575
4463
|
return yield* new CliError({ message: `Unknown secret command: ${command.action}` });
|
|
4576
4464
|
}
|
|
4577
4465
|
});
|
|
4578
4466
|
}
|
|
4579
4467
|
var PackageBuilder = Reference("kitman/cli/PackageBuilder", {
|
|
4580
|
-
defaultValue: () => (directory) => flatMap2(promise(() => import("./chunk-
|
|
4468
|
+
defaultValue: () => (directory) => flatMap2(promise(() => import("./chunk-djtdn35f.js")), (build) => build.buildPackage(directory))
|
|
4581
4469
|
});
|
|
4582
4470
|
var optionalEnv = (name) => String3(name).pipe(option2, map3(getOrUndefined));
|
|
4583
4471
|
var ciIdToken = (apiUrl) => gen(function* () {
|
|
@@ -4591,7 +4479,7 @@ var ciIdToken = (apiUrl) => gen(function* () {
|
|
|
4591
4479
|
const url = new URL(requestUrl);
|
|
4592
4480
|
const refused = (reason) => new CliError({ message: `GitHub Actions issued no ID token: ${reason}` });
|
|
4593
4481
|
url.searchParams.set("audience", apiUrl.replace(/\/+$/, ""));
|
|
4594
|
-
const response = yield* send(
|
|
4482
|
+
const response = yield* send(get2(url, { headers: { authorization: `Bearer ${requestToken}` } }), (cause) => refused(String(cause)));
|
|
4595
4483
|
if (response.status < 200 || response.status >= 300)
|
|
4596
4484
|
return yield* refused(`${response.status}; does the job have id-token: write?`);
|
|
4597
4485
|
const body = yield* response.text.pipe(mapError((error) => refused(error.message)));
|
|
@@ -4604,12 +4492,12 @@ var packageScope = (command, options = {}) => gen(function* () {
|
|
|
4604
4492
|
const token = options.ci === true ? yield* ciIdToken(apiUrl) : undefined;
|
|
4605
4493
|
const client = token === undefined ? yield* loopClientFor(apiUrl) : yield* createSdkClient({ apiUrl, session: { accessToken: token }, clientId: ClientId });
|
|
4606
4494
|
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
4607
|
-
const
|
|
4495
|
+
const teamId = command.flags.team === undefined || token !== undefined ? command.flags.team : (yield* teamNamed(client, workspaceId, command.flags.team)).id;
|
|
4608
4496
|
return {
|
|
4609
4497
|
client,
|
|
4610
4498
|
workspaceId,
|
|
4611
|
-
scope:
|
|
4612
|
-
where:
|
|
4499
|
+
scope: teamId === undefined ? {} : { teamId },
|
|
4500
|
+
where: teamId !== undefined ? `Team ${command.flags.team}` : token === undefined ? "the Workspace" : "the Deploy Binding's layer"
|
|
4613
4501
|
};
|
|
4614
4502
|
});
|
|
4615
4503
|
var changeLines = (resource, changes) => [
|
|
@@ -4618,9 +4506,9 @@ var changeLines = (resource, changes) => [
|
|
|
4618
4506
|
...changes.removed.map((id) => ` - ${resource} ${id}`)
|
|
4619
4507
|
];
|
|
4620
4508
|
var scheduleProblems = {
|
|
4621
|
-
missing: "names no
|
|
4622
|
-
not_allowed: "names a
|
|
4623
|
-
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"
|
|
4624
4512
|
};
|
|
4625
4513
|
var removedKitRecords = (shown, kind) => ({
|
|
4626
4514
|
created: [],
|
|
@@ -4637,9 +4525,9 @@ var conflictLine = (conflict) => {
|
|
|
4637
4525
|
switch (conflict.kind) {
|
|
4638
4526
|
case "id_taken":
|
|
4639
4527
|
return ` conflict: ${conflict.resource} ${conflict.id} is declared by Package ${conflict.package} in this layer`;
|
|
4640
|
-
case "
|
|
4641
|
-
return ` conflict: a
|
|
4642
|
-
case "
|
|
4528
|
+
case "not_for_team":
|
|
4529
|
+
return ` conflict: a Team Package cannot declare ${conflict.declaration}`;
|
|
4530
|
+
case "schedule_team":
|
|
4643
4531
|
return ` conflict: schedule ${conflict.key} ${scheduleProblems[conflict.problem]}`;
|
|
4644
4532
|
case "loop_unresolved":
|
|
4645
4533
|
return ` conflict: schedule ${conflict.key} starts Loop ${conflict.loop}, which will not resolve after this deploy`;
|
|
@@ -4649,8 +4537,8 @@ var conflictLine = (conflict) => {
|
|
|
4649
4537
|
return ` conflict: kit ${conflict.key} ${kitSourceProblems[conflict.problem]}`;
|
|
4650
4538
|
case "kit_policy_disabled":
|
|
4651
4539
|
return ` conflict: kit assignment ${conflict.key} sets Workspace policy, which this Workspace's rollout does not allow`;
|
|
4652
|
-
case "
|
|
4653
|
-
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`;
|
|
4654
4542
|
}
|
|
4655
4543
|
};
|
|
4656
4544
|
var driftValue = (drift, side) => encodeJsonStringSync(toJsonValue(drift[side]));
|
|
@@ -4767,19 +4655,6 @@ function devCommand(args) {
|
|
|
4767
4655
|
return yield* new CliError({ message: `Run ${settled.run.id} ${settled.reason}` });
|
|
4768
4656
|
});
|
|
4769
4657
|
}
|
|
4770
|
-
var workspaceRepoBinding = (client, workspaceId, projectId) => gen(function* () {
|
|
4771
|
-
const remote = yield* (yield* Git)(["remote", "get-url", "origin"]).pipe(orElseSucceed(() => ""));
|
|
4772
|
-
if (remote === "")
|
|
4773
|
-
return yield* new CliError({ message: "Run kitman deploy bind inside a git checkout with an origin" });
|
|
4774
|
-
const projects = (yield* client.listProjects(workspaceId)).items.map((project) => project.id);
|
|
4775
|
-
const ordered = projectId === undefined ? projects : [projectId, ...projects.filter((id) => id !== projectId)];
|
|
4776
|
-
for (const id of ordered) {
|
|
4777
|
-
const binding = (yield* client.listRepoBindings(id)).items.find((candidate) => repositoryKey(candidate.repositoryUrl ?? `https://${candidate.host}/${candidate.owner}/${candidate.name}`) === repositoryKey(remote));
|
|
4778
|
-
if (binding !== undefined)
|
|
4779
|
-
return binding;
|
|
4780
|
-
}
|
|
4781
|
-
return yield* new CliError({ message: `No repository of the Workspace matches ${remote.trim()}` });
|
|
4782
|
-
});
|
|
4783
4658
|
function deployBindingCommand(args) {
|
|
4784
4659
|
return gen(function* () {
|
|
4785
4660
|
const command = parseCommand(args);
|
|
@@ -4787,12 +4662,12 @@ function deployBindingCommand(args) {
|
|
|
4787
4662
|
const print = (value, lines) => command.flags.json ? printJson(value) : forEach(lines(value), (line) => log(line), { discard: true });
|
|
4788
4663
|
switch (command.action) {
|
|
4789
4664
|
case "bindings":
|
|
4790
|
-
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}`));
|
|
4791
4666
|
case "bind": {
|
|
4792
4667
|
const branch = command.flags.branch;
|
|
4793
4668
|
if (branch === undefined)
|
|
4794
4669
|
return yield* new CliError({ message: "deploy bind requires --branch <branch>" });
|
|
4795
|
-
const repoBinding = yield*
|
|
4670
|
+
const repoBinding = yield* currentRepoBinding(client, workspaceId);
|
|
4796
4671
|
const built = yield* (yield* PackageBuilder)(".");
|
|
4797
4672
|
return yield* print(yield* client.createDeployBinding(workspaceId, {
|
|
4798
4673
|
repoBindingId: repoBinding.id,
|
|
@@ -4868,22 +4743,22 @@ function constraintCommand(args) {
|
|
|
4868
4743
|
return gen(function* () {
|
|
4869
4744
|
const command = parseCommand(args.length === 1 ? [...args, "list"] : args);
|
|
4870
4745
|
const client = yield* loopClientFor(yield* resolveApiUrl(command.flags.apiUrl));
|
|
4871
|
-
const
|
|
4872
|
-
const binding = yield* currentRepoBinding(client,
|
|
4746
|
+
const workspaceId = yield* activeWorkspaceId(command.flags);
|
|
4747
|
+
const binding = yield* currentRepoBinding(client, workspaceId);
|
|
4873
4748
|
const print = (value, lines) => command.flags.json ? printJson(value) : forEach(lines(value), (line) => log(line), { discard: true });
|
|
4874
4749
|
switch (command.action) {
|
|
4875
4750
|
case "list":
|
|
4876
|
-
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}`));
|
|
4877
4752
|
case "add": {
|
|
4878
4753
|
const text = command.args.join(" ");
|
|
4879
4754
|
if (text.trim() === "")
|
|
4880
4755
|
return yield* new CliError({ message: "constraint add requires a constraint" });
|
|
4881
|
-
return yield* print(yield* client.addStandingConstraint(
|
|
4756
|
+
return yield* print(yield* client.addStandingConstraint(workspaceId, binding.id, text), (added) => [
|
|
4882
4757
|
`Added ${added.id}: ${added.text}`
|
|
4883
4758
|
]);
|
|
4884
4759
|
}
|
|
4885
4760
|
case "remove":
|
|
4886
|
-
return yield* print(yield* client.removeStandingConstraint(
|
|
4761
|
+
return yield* print(yield* client.removeStandingConstraint(workspaceId, binding.id, requiredArg(command, "a constraint")), (removed) => [`Removed ${removed.removed}`]);
|
|
4887
4762
|
default:
|
|
4888
4763
|
return yield* new CliError({ message: `Unknown constraint command: ${command.action}` });
|
|
4889
4764
|
}
|
|
@@ -4915,10 +4790,10 @@ function scheduleCommand(args) {
|
|
|
4915
4790
|
const apiUrl = yield* resolveApiUrl(command.flags.apiUrl);
|
|
4916
4791
|
const client = yield* loopClientFor(apiUrl);
|
|
4917
4792
|
const operational = createOperationalCliClient(client);
|
|
4918
|
-
const
|
|
4793
|
+
const teamId = yield* optionalActiveTeamId(command.flags);
|
|
4919
4794
|
const response = yield* operational.publishSchedule(scheduleId, {
|
|
4920
4795
|
workspaceId: yield* activeWorkspaceId(command.flags),
|
|
4921
|
-
...
|
|
4796
|
+
...teamId === undefined ? undefined : { teamId },
|
|
4922
4797
|
...yield* operationalInput(command, "schedule")
|
|
4923
4798
|
}, yield* idempotencyKey(command.flags.idempotencyKey, "schedule-publish"));
|
|
4924
4799
|
return yield* printOperationalResult(command, response);
|
|
@@ -5036,8 +4911,8 @@ function readString(value, keys) {
|
|
|
5036
4911
|
function readNumber(value, keys) {
|
|
5037
4912
|
return fieldValues(value, keys).find(isNumber);
|
|
5038
4913
|
}
|
|
5039
|
-
function loopListQuery(
|
|
5040
|
-
const query = new URLSearchParams({
|
|
4914
|
+
function loopListQuery(teamId, limit) {
|
|
4915
|
+
const query = new URLSearchParams({ teamId });
|
|
5041
4916
|
if (limit !== undefined)
|
|
5042
4917
|
query.set("limit", String(limit));
|
|
5043
4918
|
return `?${query.toString()}`;
|
|
@@ -5062,30 +4937,30 @@ function runnerOwnershipFromCommand(command, required) {
|
|
|
5062
4937
|
if (scopeType === undefined) {
|
|
5063
4938
|
if (required)
|
|
5064
4939
|
throw new Error("runner rescope requires --scope-type");
|
|
5065
|
-
if (command.flags.scopeId !== undefined || command.flags.
|
|
5066
|
-
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");
|
|
5067
4942
|
return;
|
|
5068
4943
|
}
|
|
5069
4944
|
if (scopeType === "workspace") {
|
|
5070
4945
|
if (command.flags.scopeId !== undefined)
|
|
5071
|
-
throw new Error("workspace runner scope does not accept --scope-id; use --
|
|
5072
|
-
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" };
|
|
5073
4948
|
}
|
|
5074
|
-
if (scopeType === "
|
|
5075
|
-
const
|
|
5076
|
-
if (!
|
|
5077
|
-
throw new Error("
|
|
5078
|
-
if (command.flags.
|
|
5079
|
-
throw new Error("
|
|
5080
|
-
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 };
|
|
5081
4956
|
}
|
|
5082
4957
|
if (scopeType === "personal") {
|
|
5083
4958
|
const ownerUserId = command.flags.scopeId;
|
|
5084
4959
|
if (!ownerUserId)
|
|
5085
4960
|
throw new Error("personal runner scope requires --scope-id <owner-user-id>");
|
|
5086
|
-
return { kind: "personal", ownerUserId,
|
|
4961
|
+
return { kind: "personal", ownerUserId, teamAccess: command.flags.teamAccess ?? "all" };
|
|
5087
4962
|
}
|
|
5088
|
-
throw new Error("--scope-type must be workspace,
|
|
4963
|
+
throw new Error("--scope-type must be workspace, team, or personal");
|
|
5089
4964
|
}
|
|
5090
4965
|
function loopRunnerCapabilities() {
|
|
5091
4966
|
const capabilities = defaultRunnerCapabilities();
|
|
@@ -5146,7 +5021,7 @@ function resolveCommandConfig(flags = {}) {
|
|
|
5146
5021
|
const localConfig = yield* readUserConfig(localConfigPathValue);
|
|
5147
5022
|
const configFlags = {
|
|
5148
5023
|
...flags.apiUrl ? { apiUrl: flags.apiUrl } : undefined,
|
|
5149
|
-
...flags.
|
|
5024
|
+
...flags.team ? { team: flags.team } : undefined,
|
|
5150
5025
|
...flags.workspace ? { workspace: flags.workspace } : undefined
|
|
5151
5026
|
};
|
|
5152
5027
|
const envApiUrl = yield* apiUrlFromEnv;
|
|
@@ -5169,10 +5044,10 @@ function resolveCommandConfig(flags = {}) {
|
|
|
5169
5044
|
["localConfig", localConfig?.activeWorkspaceId],
|
|
5170
5045
|
["userConfig", userConfig?.activeWorkspaceId]
|
|
5171
5046
|
], "none"),
|
|
5172
|
-
|
|
5173
|
-
["flag", flags.
|
|
5174
|
-
["localConfig", localConfig?.
|
|
5175
|
-
["userConfig", userConfig?.
|
|
5047
|
+
activeTeam: firstConfigSource([
|
|
5048
|
+
["flag", flags.team],
|
|
5049
|
+
["localConfig", localConfig?.activeTeamId],
|
|
5050
|
+
["userConfig", userConfig?.activeTeamId]
|
|
5176
5051
|
], "none")
|
|
5177
5052
|
},
|
|
5178
5053
|
userConfigPath: yield* userConfigPath,
|
|
@@ -5197,36 +5072,36 @@ function optionalActiveWorkspaceId(flags) {
|
|
|
5197
5072
|
return config.activeWorkspaceId;
|
|
5198
5073
|
});
|
|
5199
5074
|
}
|
|
5200
|
-
function
|
|
5075
|
+
function activeTeamId(flags) {
|
|
5201
5076
|
return gen(function* () {
|
|
5202
5077
|
const { config } = yield* resolveCommandConfig(flags);
|
|
5203
|
-
if (!config.
|
|
5078
|
+
if (!config.activeTeamId) {
|
|
5204
5079
|
return yield* new CliError({
|
|
5205
|
-
message: "No active
|
|
5080
|
+
message: "No active Team. Run kitman team use <team> or pass --team <team>."
|
|
5206
5081
|
});
|
|
5207
5082
|
}
|
|
5208
|
-
return config.
|
|
5083
|
+
return config.activeTeamId;
|
|
5209
5084
|
});
|
|
5210
5085
|
}
|
|
5211
|
-
function
|
|
5086
|
+
function optionalActiveTeamId(flags) {
|
|
5212
5087
|
return gen(function* () {
|
|
5213
5088
|
const { config } = yield* resolveCommandConfig(flags);
|
|
5214
|
-
return config.
|
|
5089
|
+
return config.activeTeamId;
|
|
5215
5090
|
});
|
|
5216
5091
|
}
|
|
5217
5092
|
function writeActiveWorkspace(apiUrl, workspaceId, local) {
|
|
5218
5093
|
return gen(function* () {
|
|
5219
5094
|
const path = local ? yield* localConfigPath : undefined;
|
|
5220
5095
|
const existing = yield* readUserConfig(path);
|
|
5221
|
-
const {
|
|
5096
|
+
const { activeTeamId: _activeTeamId, ...rest } = existing ?? {};
|
|
5222
5097
|
yield* writeUserConfig({ ...rest, apiUrl, activeWorkspaceId: workspaceId }, path);
|
|
5223
5098
|
});
|
|
5224
5099
|
}
|
|
5225
|
-
function
|
|
5100
|
+
function writeActiveTeam(apiUrl, workspaceId, teamId, local) {
|
|
5226
5101
|
return gen(function* () {
|
|
5227
5102
|
const path = local ? yield* localConfigPath : undefined;
|
|
5228
5103
|
const existing = yield* readUserConfig(path);
|
|
5229
|
-
yield* writeUserConfig({ ...existing, apiUrl, activeWorkspaceId: workspaceId,
|
|
5104
|
+
yield* writeUserConfig({ ...existing, apiUrl, activeWorkspaceId: workspaceId, activeTeamId: teamId }, path);
|
|
5230
5105
|
});
|
|
5231
5106
|
}
|
|
5232
5107
|
function resolveWorkspace(client, workspaceRef) {
|
|
@@ -5244,14 +5119,8 @@ function requiredArg(command, name) {
|
|
|
5244
5119
|
throw new Error(`${command.group} ${command.action} requires ${name}`);
|
|
5245
5120
|
return value;
|
|
5246
5121
|
}
|
|
5247
|
-
function
|
|
5248
|
-
return {
|
|
5249
|
-
id: project.id,
|
|
5250
|
-
name: project.name,
|
|
5251
|
-
slug: project.slug,
|
|
5252
|
-
open: project.openTaskCount,
|
|
5253
|
-
blocked: project.blockedTaskCount
|
|
5254
|
-
};
|
|
5122
|
+
function teamRow(team) {
|
|
5123
|
+
return { id: team.id, key: team.key, name: team.name, members: team.memberCount };
|
|
5255
5124
|
}
|
|
5256
5125
|
function kitRow(kit) {
|
|
5257
5126
|
return { id: kit.id, name: kit.name, slug: kit.slug, kind: kit.kind, description: kit.description };
|
|
@@ -5338,18 +5207,19 @@ function repoBindingCredentialRefLabel(ref) {
|
|
|
5338
5207
|
}
|
|
5339
5208
|
function runnerRow(runner) {
|
|
5340
5209
|
const ownership = runner.ownership;
|
|
5341
|
-
let
|
|
5342
|
-
if (ownership.kind === "
|
|
5343
|
-
|
|
5344
|
-
else if (ownership.
|
|
5345
|
-
|
|
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";
|
|
5346
5215
|
else
|
|
5347
|
-
|
|
5216
|
+
effectiveTeamAccess = ownership.kind === "workspace" ? "workspace_all" : `personal:${ownership.ownerUserId}`;
|
|
5348
5217
|
return {
|
|
5349
5218
|
id: runner.id,
|
|
5350
5219
|
name: runner.name,
|
|
5351
5220
|
status: runner.status,
|
|
5352
|
-
|
|
5221
|
+
state: runner.state.kind,
|
|
5222
|
+
credentialStatus: runner.credentialStatus,
|
|
5353
5223
|
agentIdentityId: runner.agentIdentityId,
|
|
5354
5224
|
harnesses: runner.capabilities.harnesses.join(","),
|
|
5355
5225
|
defaultHarness: runner.capabilities.defaultHarness ?? "",
|
|
@@ -5358,10 +5228,10 @@ function runnerRow(runner) {
|
|
|
5358
5228
|
sandboxes: runner.capabilities.sandboxBackends.join(","),
|
|
5359
5229
|
labels: runner.labels.join(","),
|
|
5360
5230
|
ownership: ownership.kind,
|
|
5361
|
-
|
|
5231
|
+
teamAccess: ownership.kind === "team" ? undefined : ownership.teamAccess,
|
|
5362
5232
|
ownerUserId: ownership.kind === "personal" ? ownership.ownerUserId : undefined,
|
|
5363
|
-
|
|
5364
|
-
|
|
5233
|
+
scopedTeamId: ownership.kind === "team" ? ownership.teamId : undefined,
|
|
5234
|
+
effectiveTeamAccess,
|
|
5365
5235
|
managementBoundary: "workspace_operator_only",
|
|
5366
5236
|
schedulingBoundary: "scope_and_identity_gated",
|
|
5367
5237
|
lastHeartbeatAt: runner.lastHeartbeatAt
|
|
@@ -5384,7 +5254,8 @@ function runnerDoctorRow(runner) {
|
|
|
5384
5254
|
id: runner.id,
|
|
5385
5255
|
name: runner.name,
|
|
5386
5256
|
status: runner.status,
|
|
5387
|
-
|
|
5257
|
+
state: runner.state.kind,
|
|
5258
|
+
credentialStatus: runner.credentialStatus,
|
|
5388
5259
|
runs: runner.capabilities.runs?.supported === true,
|
|
5389
5260
|
harnesses: runner.capabilities.harnesses.join(","),
|
|
5390
5261
|
defaultHarness: runner.capabilities.defaultHarness ?? "",
|
|
@@ -5448,8 +5319,8 @@ function sessionUpdateRow(update) {
|
|
|
5448
5319
|
function dependencyTaskLabel(task) {
|
|
5449
5320
|
if (task.redacted) {
|
|
5450
5321
|
const taskId = task.taskId ?? "hidden-task";
|
|
5451
|
-
const
|
|
5452
|
-
return `[redacted] ${taskId}${
|
|
5322
|
+
const team = task.teamId === undefined ? "" : ` (${task.teamId})`;
|
|
5323
|
+
return `[redacted] ${taskId}${team}`;
|
|
5453
5324
|
}
|
|
5454
5325
|
return `${task.taskKey} ${task.title} [${task.state.name}]`;
|
|
5455
5326
|
}
|
|
@@ -5477,7 +5348,7 @@ function downloadPresignedAsset(download, outputPath) {
|
|
|
5477
5348
|
return gen(function* () {
|
|
5478
5349
|
const fs = yield* FileSystem;
|
|
5479
5350
|
const failed = (cause) => new CliError({ message: `Asset download failed: ${String(cause)}` });
|
|
5480
|
-
const response = yield* send(
|
|
5351
|
+
const response = yield* send(make9(download.method)(download.url), failed);
|
|
5481
5352
|
if (response.status < 200 || response.status >= 300)
|
|
5482
5353
|
return yield* new CliError({ message: `Asset download failed with ${response.status}` });
|
|
5483
5354
|
const body = yield* response.arrayBuffer.pipe(mapError((error) => failed(error.message)));
|
|
@@ -5634,15 +5505,15 @@ function parseRepoBindingCredentialRef(value) {
|
|
|
5634
5505
|
}
|
|
5635
5506
|
function repoBindingInput2(command) {
|
|
5636
5507
|
if (!command.flags.provider)
|
|
5637
|
-
throw new Error("
|
|
5508
|
+
throw new Error("repo add requires --provider");
|
|
5638
5509
|
if (!command.flags.host)
|
|
5639
|
-
throw new Error("
|
|
5510
|
+
throw new Error("repo add requires --host");
|
|
5640
5511
|
if (!command.flags.owner)
|
|
5641
|
-
throw new Error("
|
|
5512
|
+
throw new Error("repo add requires --owner");
|
|
5642
5513
|
if (!command.flags.name)
|
|
5643
|
-
throw new Error("
|
|
5514
|
+
throw new Error("repo add requires --name");
|
|
5644
5515
|
if (!command.flags.defaultBranch)
|
|
5645
|
-
throw new Error("
|
|
5516
|
+
throw new Error("repo add requires --default-branch");
|
|
5646
5517
|
return {
|
|
5647
5518
|
provider: command.flags.provider,
|
|
5648
5519
|
host: command.flags.host,
|
|
@@ -5869,5 +5740,5 @@ function cliErrorJson(error) {
|
|
|
5869
5740
|
var program = runEffect(args).pipe(tapCause((cause) => {
|
|
5870
5741
|
const error2 = squash(cause);
|
|
5871
5742
|
return args.includes("--json") ? printJson(cliErrorJson(error2)) : error(error2 instanceof Error ? error2.message : String(error2));
|
|
5872
|
-
}), provide3(
|
|
5743
|
+
}), provide3(layer));
|
|
5873
5744
|
runMain(program, { disableErrorReporting: true });
|