@kitman-sh/cli 0.7.4 → 0.8.0

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