@skrr-ai/cli 0.1.23 → 0.1.25

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.
Files changed (85) hide show
  1. package/bin/run.js +54 -1
  2. package/dist/base-command.d.ts +4 -4
  3. package/dist/base-command.js +81 -6
  4. package/dist/commands/harnesses/leases/list.js +1 -1
  5. package/dist/commands/harnesses/products.js +1 -0
  6. package/dist/commands/machines/dedicated/create.d.ts +18 -0
  7. package/dist/commands/machines/dedicated/create.js +90 -0
  8. package/dist/commands/machines/dedicated/destroy.d.ts +14 -0
  9. package/dist/commands/machines/dedicated/destroy.js +56 -0
  10. package/dist/commands/machines/dedicated/grow.d.ts +13 -0
  11. package/dist/commands/machines/dedicated/grow.js +46 -0
  12. package/dist/commands/machines/dedicated/health-check.d.ts +12 -0
  13. package/dist/commands/machines/dedicated/health-check.js +42 -0
  14. package/dist/commands/machines/dedicated/incident-close.d.ts +12 -0
  15. package/dist/commands/machines/dedicated/incident-close.js +42 -0
  16. package/dist/commands/machines/dedicated/index.d.ts +6 -0
  17. package/dist/commands/machines/dedicated/index.js +39 -0
  18. package/dist/commands/machines/dedicated/list.d.ts +11 -0
  19. package/dist/commands/machines/dedicated/list.js +58 -0
  20. package/dist/commands/machines/dedicated/restart.d.ts +12 -0
  21. package/dist/commands/machines/dedicated/restart.js +42 -0
  22. package/dist/commands/machines/dedicated/show.d.ts +12 -0
  23. package/dist/commands/machines/dedicated/show.js +58 -0
  24. package/dist/commands/machines/dedicated/snapshot.d.ts +12 -0
  25. package/dist/commands/machines/dedicated/snapshot.js +42 -0
  26. package/dist/commands/machines/dedicated/start.d.ts +12 -0
  27. package/dist/commands/machines/dedicated/start.js +42 -0
  28. package/dist/commands/machines/dedicated/stop.d.ts +12 -0
  29. package/dist/commands/machines/dedicated/stop.js +42 -0
  30. package/dist/commands/machines/hosted/index.js +4 -1
  31. package/dist/commands/spaces/auto-executor/preview.d.ts +8 -0
  32. package/dist/commands/spaces/auto-executor/preview.js +41 -0
  33. package/dist/commands/spaces/task-rollups.d.ts +14 -0
  34. package/dist/commands/spaces/task-rollups.js +66 -0
  35. package/dist/commands/spaces/update.js +16 -12
  36. package/dist/commands/spaces/work-sync/reconcile.d.ts +38 -0
  37. package/dist/commands/spaces/work-sync/reconcile.js +90 -0
  38. package/dist/commands/spaces/work-sync/telemetry.d.ts +29 -0
  39. package/dist/commands/spaces/work-sync/telemetry.js +118 -0
  40. package/dist/commands/tasks/activity.js +5 -1
  41. package/dist/commands/tasks/assign.js +1 -1
  42. package/dist/commands/tasks/complete.d.ts +5 -1
  43. package/dist/commands/tasks/complete.js +17 -3
  44. package/dist/commands/tasks/create.d.ts +1 -0
  45. package/dist/commands/tasks/create.js +17 -4
  46. package/dist/commands/tasks/deliverable/add.js +22 -4
  47. package/dist/commands/tasks/deliverable/list.js +23 -2
  48. package/dist/commands/tasks/events/append.js +31 -3
  49. package/dist/commands/tasks/expectations/assess.d.ts +1 -1
  50. package/dist/commands/tasks/expectations/assess.js +33 -7
  51. package/dist/commands/tasks/expectations.js +110 -10
  52. package/dist/commands/tasks/output.d.ts +0 -1
  53. package/dist/commands/tasks/output.js +21 -1
  54. package/dist/commands/tasks/report.js +3 -1
  55. package/dist/commands/tasks/result/show.js +20 -2
  56. package/dist/commands/tasks/resume/save.js +11 -1
  57. package/dist/commands/tasks/self-schedule.js +7 -4
  58. package/dist/commands/tasks/show.js +19 -0
  59. package/dist/commands/tasks/timeline.js +9 -1
  60. package/dist/commands/tasks/updates/add.js +23 -2
  61. package/dist/lib/api-fetch.js +9 -1
  62. package/dist/lib/assignee-resolver.d.ts +7 -6
  63. package/dist/lib/daemonBroker.d.ts +20 -0
  64. package/dist/lib/daemonBroker.js +58 -17
  65. package/dist/lib/dedicated-machines.d.ts +66 -0
  66. package/dist/lib/dedicated-machines.js +161 -0
  67. package/dist/lib/harnesses.d.ts +5 -0
  68. package/dist/lib/harnesses.js +5 -2
  69. package/dist/lib/idempotency-scope.d.ts +10 -0
  70. package/dist/lib/idempotency-scope.js +16 -0
  71. package/dist/lib/node-adapter.d.ts +1 -1
  72. package/dist/lib/node-adapter.js +12 -10
  73. package/dist/lib/task-legacy.d.ts +24 -0
  74. package/dist/lib/task-legacy.js +48 -0
  75. package/dist/lib/tasks.d.ts +32 -0
  76. package/dist/lib/tasks.js +47 -1
  77. package/dist/lib/triggers.js +7 -0
  78. package/dist/lib/work-sync.d.ts +105 -0
  79. package/dist/lib/work-sync.js +29 -0
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +71 -21
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +71 -21
  82. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  83. package/dist/node_modules/@skrr-ai/data-provider/index.js +2141 -2061
  84. package/oclif.manifest.json +15661 -14585
  85. package/package.json +1 -1
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../../base-command");
5
+ const format_1 = require("../../../lib/format");
6
+ const dedicated_machines_1 = require("../../../lib/dedicated-machines");
7
+ class DedicatedMachinesList extends base_command_1.BaseCommand {
8
+ static description = 'List Dedicated Runtime leases';
9
+ static examples = [
10
+ '<%= config.bin %> machines dedicated list',
11
+ '<%= config.bin %> machines dedicated list --json',
12
+ ];
13
+ static flags = {
14
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
15
+ state: core_1.Flags.string({ description: 'Filter by lease state' }),
16
+ limit: core_1.Flags.integer({ description: 'Max leases to return', default: 50 }),
17
+ };
18
+ async run() {
19
+ this.requireAuth();
20
+ const { flags } = await this.parse(DedicatedMachinesList);
21
+ let response;
22
+ try {
23
+ response = await (0, dedicated_machines_1.listDedicatedRuntimes)({
24
+ limit: flags.limit,
25
+ state: flags.state,
26
+ });
27
+ }
28
+ catch (err) {
29
+ const message = (0, dedicated_machines_1.formatDedicatedRuntimeApiError)(err);
30
+ if (message)
31
+ this.error(message, { exit: 1 });
32
+ this.handleApiError(err);
33
+ }
34
+ if (flags.json) {
35
+ this.log(JSON.stringify(response, null, 2));
36
+ return;
37
+ }
38
+ const leases = response?.leases ?? [];
39
+ if (leases.length === 0) {
40
+ this.log('No Dedicated Runtime leases.');
41
+ return;
42
+ }
43
+ (0, format_1.renderTable)(leases.map((lease) => ({
44
+ id: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseId)(lease)),
45
+ status: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseState)(lease)),
46
+ name: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseDisplayName)(lease)),
47
+ retention: (0, format_1.formatCell)(lease.storage?.retentionClass || '-'),
48
+ backup: (0, format_1.formatCell)(lease.storage?.lastBackupAt || '-'),
49
+ })), [
50
+ { key: 'id', header: 'ID' },
51
+ { key: 'status', header: 'STATUS' },
52
+ { key: 'name', header: 'NAME', maxWidth: 28 },
53
+ { key: 'retention', header: 'RETENTION', maxWidth: 14 },
54
+ { key: 'backup', header: 'LAST BACKUP', maxWidth: 24 },
55
+ ], (line) => this.log(line));
56
+ }
57
+ }
58
+ exports.default = DedicatedMachinesList;
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ export default class DedicatedMachinesRestart extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../../base-command");
5
+ const dedicated_machines_1 = require("../../../lib/dedicated-machines");
6
+ class DedicatedMachinesRestart extends base_command_1.BaseCommand {
7
+ static description = 'Restart a Dedicated Runtime lease';
8
+ static examples = [
9
+ '<%= config.bin %> machines dedicated restart <lease-id>',
10
+ '<%= config.bin %> machines dedicated restart <lease-id> --json',
11
+ ];
12
+ static args = {
13
+ lease: core_1.Args.string({
14
+ description: 'Dedicated Runtime lease id',
15
+ required: true,
16
+ ignoreStdin: true,
17
+ }),
18
+ };
19
+ static flags = {
20
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
21
+ };
22
+ async run() {
23
+ this.requireAuth();
24
+ const { args, flags } = await this.parse(DedicatedMachinesRestart);
25
+ let response;
26
+ try {
27
+ response = await (0, dedicated_machines_1.performDedicatedLeaseAction)(args.lease, 'restart');
28
+ }
29
+ catch (err) {
30
+ const message = (0, dedicated_machines_1.formatDedicatedRuntimeApiError)(err);
31
+ if (message)
32
+ this.error(message, { exit: 1 });
33
+ this.handleApiError(err);
34
+ }
35
+ if (flags.json) {
36
+ this.log(JSON.stringify(response, null, 2));
37
+ return;
38
+ }
39
+ this.log(`Dedicated Runtime ${(0, dedicated_machines_1.dedicatedLeaseId)(response?.lease)} ${(0, dedicated_machines_1.dedicatedLeaseState)(response?.lease)}.`);
40
+ }
41
+ }
42
+ exports.default = DedicatedMachinesRestart;
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ export default class DedicatedMachinesShow extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../../base-command");
5
+ const dedicated_machines_1 = require("../../../lib/dedicated-machines");
6
+ class DedicatedMachinesShow extends base_command_1.BaseCommand {
7
+ static description = 'Show one Dedicated Runtime lease';
8
+ static examples = [
9
+ '<%= config.bin %> machines dedicated show <lease-id>',
10
+ '<%= config.bin %> machines dedicated show <lease-id> --json',
11
+ ];
12
+ static args = {
13
+ lease: core_1.Args.string({
14
+ ignoreStdin: true,
15
+ description: 'Dedicated Runtime lease id',
16
+ required: true,
17
+ }),
18
+ };
19
+ static flags = { json: core_1.Flags.boolean({ description: 'Output as JSON' }) };
20
+ async run() {
21
+ this.requireAuth();
22
+ const { args, flags } = await this.parse(DedicatedMachinesShow);
23
+ let response;
24
+ try {
25
+ response = await (0, dedicated_machines_1.getDedicatedRuntime)(args.lease);
26
+ }
27
+ catch (err) {
28
+ const message = (0, dedicated_machines_1.formatDedicatedRuntimeApiError)(err);
29
+ if (message)
30
+ this.error(message, { exit: 1 });
31
+ this.handleApiError(err);
32
+ }
33
+ const lease = response?.lease;
34
+ if (!lease) {
35
+ this.error('Dedicated Runtime lease not found.', { exit: 1 });
36
+ }
37
+ if (flags.json) {
38
+ this.log(JSON.stringify(response, null, 2));
39
+ return;
40
+ }
41
+ this.log((0, dedicated_machines_1.dedicatedLeaseDisplayName)(lease));
42
+ this.log(` id ${(0, dedicated_machines_1.dedicatedLeaseId)(lease)}`);
43
+ this.log(` status ${(0, dedicated_machines_1.dedicatedLeaseState)(lease)}`);
44
+ if (lease.storage?.retentionClass) {
45
+ this.log(` retention ${lease.storage.retentionClass}`);
46
+ }
47
+ if (lease.storage?.backupProfile) {
48
+ this.log(` backup ${lease.storage.backupProfile}`);
49
+ }
50
+ if (lease.storage?.lastBackupAt) {
51
+ this.log(` last backup ${lease.storage.lastBackupAt}`);
52
+ }
53
+ if (lease.billing?.monthlySpendLimitCents != null) {
54
+ this.log(` monthly cap ${lease.billing.monthlySpendLimitCents} cents`);
55
+ }
56
+ }
57
+ }
58
+ exports.default = DedicatedMachinesShow;
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ export default class DedicatedMachinesSnapshot extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../../base-command");
5
+ const dedicated_machines_1 = require("../../../lib/dedicated-machines");
6
+ class DedicatedMachinesSnapshot extends base_command_1.BaseCommand {
7
+ static description = 'Snapshot a Dedicated Runtime lease workspace';
8
+ static examples = [
9
+ '<%= config.bin %> machines dedicated snapshot <lease-id>',
10
+ '<%= config.bin %> machines dedicated snapshot <lease-id> --json',
11
+ ];
12
+ static args = {
13
+ lease: core_1.Args.string({
14
+ description: 'Dedicated Runtime lease id',
15
+ required: true,
16
+ ignoreStdin: true,
17
+ }),
18
+ };
19
+ static flags = {
20
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
21
+ };
22
+ async run() {
23
+ this.requireAuth();
24
+ const { args, flags } = await this.parse(DedicatedMachinesSnapshot);
25
+ let response;
26
+ try {
27
+ response = await (0, dedicated_machines_1.performDedicatedLeaseAction)(args.lease, 'snapshot');
28
+ }
29
+ catch (err) {
30
+ const message = (0, dedicated_machines_1.formatDedicatedRuntimeApiError)(err);
31
+ if (message)
32
+ this.error(message, { exit: 1 });
33
+ this.handleApiError(err);
34
+ }
35
+ if (flags.json) {
36
+ this.log(JSON.stringify(response, null, 2));
37
+ return;
38
+ }
39
+ this.log(`Dedicated Runtime ${(0, dedicated_machines_1.dedicatedLeaseId)(response?.lease)} ${(0, dedicated_machines_1.dedicatedLeaseState)(response?.lease)}.`);
40
+ }
41
+ }
42
+ exports.default = DedicatedMachinesSnapshot;
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ export default class DedicatedMachinesStart extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../../base-command");
5
+ const dedicated_machines_1 = require("../../../lib/dedicated-machines");
6
+ class DedicatedMachinesStart extends base_command_1.BaseCommand {
7
+ static description = 'Start a Dedicated Runtime lease';
8
+ static examples = [
9
+ '<%= config.bin %> machines dedicated start <lease-id>',
10
+ '<%= config.bin %> machines dedicated start <lease-id> --json',
11
+ ];
12
+ static args = {
13
+ lease: core_1.Args.string({
14
+ description: 'Dedicated Runtime lease id',
15
+ required: true,
16
+ ignoreStdin: true,
17
+ }),
18
+ };
19
+ static flags = {
20
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
21
+ };
22
+ async run() {
23
+ this.requireAuth();
24
+ const { args, flags } = await this.parse(DedicatedMachinesStart);
25
+ let response;
26
+ try {
27
+ response = await (0, dedicated_machines_1.performDedicatedLeaseAction)(args.lease, 'start');
28
+ }
29
+ catch (err) {
30
+ const message = (0, dedicated_machines_1.formatDedicatedRuntimeApiError)(err);
31
+ if (message)
32
+ this.error(message, { exit: 1 });
33
+ this.handleApiError(err);
34
+ }
35
+ if (flags.json) {
36
+ this.log(JSON.stringify(response, null, 2));
37
+ return;
38
+ }
39
+ this.log(`Dedicated Runtime ${(0, dedicated_machines_1.dedicatedLeaseId)(response?.lease)} ${(0, dedicated_machines_1.dedicatedLeaseState)(response?.lease)}.`);
40
+ }
41
+ }
42
+ exports.default = DedicatedMachinesStart;
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ export default class DedicatedMachinesStop extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../../base-command");
5
+ const dedicated_machines_1 = require("../../../lib/dedicated-machines");
6
+ class DedicatedMachinesStop extends base_command_1.BaseCommand {
7
+ static description = 'Stop a Dedicated Runtime lease';
8
+ static examples = [
9
+ '<%= config.bin %> machines dedicated stop <lease-id>',
10
+ '<%= config.bin %> machines dedicated stop <lease-id> --json',
11
+ ];
12
+ static args = {
13
+ lease: core_1.Args.string({
14
+ description: 'Dedicated Runtime lease id',
15
+ required: true,
16
+ ignoreStdin: true,
17
+ }),
18
+ };
19
+ static flags = {
20
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
21
+ };
22
+ async run() {
23
+ this.requireAuth();
24
+ const { args, flags } = await this.parse(DedicatedMachinesStop);
25
+ let response;
26
+ try {
27
+ response = await (0, dedicated_machines_1.performDedicatedLeaseAction)(args.lease, 'stop');
28
+ }
29
+ catch (err) {
30
+ const message = (0, dedicated_machines_1.formatDedicatedRuntimeApiError)(err);
31
+ if (message)
32
+ this.error(message, { exit: 1 });
33
+ this.handleApiError(err);
34
+ }
35
+ if (flags.json) {
36
+ this.log(JSON.stringify(response, null, 2));
37
+ return;
38
+ }
39
+ this.log(`Dedicated Runtime ${(0, dedicated_machines_1.dedicatedLeaseId)(response?.lease)} ${(0, dedicated_machines_1.dedicatedLeaseState)(response?.lease)}.`);
40
+ }
41
+ }
42
+ exports.default = DedicatedMachinesStop;
@@ -2,7 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  class Machines extends core_1.Command {
5
- static description = 'Manage skrr Hosted Machine workspaces';
5
+ // Paired with `machines dedicated`, and the pairing is the point: this one
6
+ // resumes, that one restarts. Stated here so the choice can be made without
7
+ // opening either topic.
8
+ static description = 'A managed workspace that resumes — files, memory and running processes survive pause';
6
9
  static examples = [
7
10
  '<%= config.bin %> machines hosted start --name scratch --wait',
8
11
  '<%= config.bin %> machines hosted list',
@@ -1,3 +1,4 @@
1
+ import { type SpaceAutoExecutorRefillState } from '@skrr-ai/data-provider';
1
2
  import { BaseCommand } from '../../../base-command';
2
3
  export default class SpacesAutoExecutorPreview extends BaseCommand {
3
4
  static description: string;
@@ -10,3 +11,10 @@ export default class SpacesAutoExecutorPreview extends BaseCommand {
10
11
  };
11
12
  run(): Promise<void>;
12
13
  }
14
+ /**
15
+ * The refill latch in words: whether start work is owed, why it last woke, and
16
+ * what each recent task-pool change did. This is how "the board is not picking
17
+ * up work" becomes a question with an answer. `undefined` is a server that does
18
+ * not report it; `null` is one that could not read it, and says so.
19
+ */
20
+ export declare function refillLines(refill: SpaceAutoExecutorRefillState | null | undefined): string[];
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.refillLines = refillLines;
3
4
  const core_1 = require("@oclif/core");
4
5
  const data_provider_1 = require("@skrr-ai/data-provider");
5
6
  const base_command_1 = require("../../../base-command");
@@ -49,6 +50,8 @@ class SpacesAutoExecutorPreview extends base_command_1.BaseCommand {
49
50
  if (capacity.budget && !capacity.budget.unlimited) {
50
51
  this.log(`Budget: ${capacity.budget.consumedStarts}/${capacity.budget.maxStarts} starts used this round`);
51
52
  }
53
+ for (const line of refillLines(result.refill))
54
+ this.log(line);
52
55
  const { actions, summary } = plan;
53
56
  this.log('');
54
57
  this.log(`Board: ${summary.readyCount} ready · ${summary.unassignedCount} unassigned · ` +
@@ -70,6 +73,44 @@ class SpacesAutoExecutorPreview extends base_command_1.BaseCommand {
70
73
  }
71
74
  }
72
75
  exports.default = SpacesAutoExecutorPreview;
76
+ /**
77
+ * The refill latch in words: whether start work is owed, why it last woke, and
78
+ * what each recent task-pool change did. This is how "the board is not picking
79
+ * up work" becomes a question with an answer. `undefined` is a server that does
80
+ * not report it; `null` is one that could not read it, and says so.
81
+ */
82
+ function refillLines(refill) {
83
+ if (refill === undefined)
84
+ return [];
85
+ if (refill === null)
86
+ return ['Refill: state unavailable (the server could not read it)'];
87
+ const owed = refill.requestedGeneration - refill.processedGeneration;
88
+ const lines = [
89
+ `Refill: ${refill.pending
90
+ ? `${owed} wake${owed === 1 ? '' : 's'} owed (generation ${refill.processedGeneration} → ${refill.requestedGeneration})`
91
+ : `caught up (generation ${refill.processedGeneration})`}${refill.lastReason ? ` · last: ${refill.lastReason}` : ''}`,
92
+ ];
93
+ if (refill.notBefore)
94
+ lines.push(` deferred until ${refill.notBefore}`);
95
+ if (refill.waitingOnAgentIds.length) {
96
+ lines.push(` waiting on agent capacity: ${refill.waitingOnAgentIds.join(', ')}`);
97
+ }
98
+ const wakes = refill.taskPool.recentWakes;
99
+ if (wakes.length) {
100
+ lines.push('');
101
+ lines.push(`Recent task-pool changes (${wakes.length}):`);
102
+ for (const wake of wakes) {
103
+ const effect = wake.refillRelevant === true
104
+ ? 'can start work'
105
+ : wake.refillRelevant === false
106
+ ? 'nothing to start'
107
+ : 'unclassified';
108
+ const outcome = wake.status === 'processed' ? (wake.decision ?? 'processed') : wake.status;
109
+ lines.push(` - #${wake.generation} ${wake.mutation} · ${effect} · ${outcome}`);
110
+ }
111
+ }
112
+ return lines;
113
+ }
73
114
  function taskLine(task) {
74
115
  return `${task.identifier ? `${task.identifier} ` : ''}${task.title}`;
75
116
  }
@@ -0,0 +1,14 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * `skrr spaces task-rollups` — task-status counts for every Space you can view,
4
+ * in one request: the same rollup the Spaces sidebar row reads, counted by the
5
+ * project-rollup rules (`done` is done only; `open` excludes cancelled).
6
+ */
7
+ export default class SpacesTaskRollups extends BaseCommand {
8
+ static description: string;
9
+ static examples: string[];
10
+ static flags: {
11
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../base-command");
6
+ /**
7
+ * `skrr spaces task-rollups` — task-status counts for every Space you can view,
8
+ * in one request: the same rollup the Spaces sidebar row reads, counted by the
9
+ * project-rollup rules (`done` is done only; `open` excludes cancelled).
10
+ */
11
+ class SpacesTaskRollups extends base_command_1.BaseCommand {
12
+ static description = 'Show task-status counts for every space you can view';
13
+ static examples = [
14
+ '<%= config.bin %> spaces task-rollups',
15
+ '<%= config.bin %> spaces task-rollups --json',
16
+ ];
17
+ static flags = {
18
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
19
+ };
20
+ async run() {
21
+ this.requireAuth();
22
+ const { flags } = await this.parse(SpacesTaskRollups);
23
+ let response;
24
+ // Titles are best-effort: the rollup carries only ids, and a failed title
25
+ // lookup must not hide counts the rollup did return.
26
+ let titles = new Map();
27
+ try {
28
+ const [rollups, spaces] = await Promise.all([
29
+ data_provider_1.dataService.listSpaceTaskRollups(),
30
+ data_provider_1.dataService.listSpaces({ includeArchived: true, limit: 500 }).catch(() => null),
31
+ ]);
32
+ response = rollups;
33
+ const rows = (spaces?.data ||
34
+ []);
35
+ titles = new Map(rows.map((space) => [String(space.id), space.title || '']));
36
+ }
37
+ catch (err) {
38
+ this.handleApiError(err);
39
+ return;
40
+ }
41
+ if (flags.json) {
42
+ this.log(JSON.stringify(response, null, 2));
43
+ return;
44
+ }
45
+ if (response.degraded) {
46
+ // A partial walk is not the whole picture; say so before the numbers.
47
+ this.log(`Degraded: ${(response.warnings || []).join('; ') || 'some spaces were not counted'}`);
48
+ }
49
+ const rows = [...(response.rollups || [])].sort((a, b) => b.tasks.open - a.tasks.open);
50
+ if (rows.length === 0) {
51
+ this.log('No spaces with tasks.');
52
+ return;
53
+ }
54
+ const label = (spaceId) => {
55
+ const title = titles.get(spaceId);
56
+ const text = title ? `${title} (${spaceId.slice(0, 8)})` : spaceId;
57
+ return text.length > 48 ? `${text.slice(0, 45)}...` : text;
58
+ };
59
+ this.log(`${'SPACE'.padEnd(48)} ${'OPEN'.padStart(5)} ${'DONE'.padStart(5)} ${'BLOCKED'.padStart(7)} ${'TOTAL'.padStart(5)}`);
60
+ for (const { spaceId, tasks } of rows) {
61
+ this.log(`${label(spaceId).padEnd(48)} ${String(tasks.open).padStart(5)} ${String(tasks.done).padStart(5)} ` +
62
+ `${String(tasks.blocked).padStart(7)} ${String(tasks.total).padStart(5)}`);
63
+ }
64
+ }
65
+ }
66
+ exports.default = SpacesTaskRollups;
@@ -76,7 +76,9 @@ class SpacesUpdate extends base_command_1.BaseCommand {
76
76
  description: tasks_1.EXPECT_FLAG_HELP +
77
77
  ' This is the space DEFAULT, and it is where the contract actually gets set: nobody ' +
78
78
  'declares one per task, but a space default is configured once and then holds. A ' +
79
- 'per-task --expect wins. Merged into deliveryPolicy without clobbering its other fields.',
79
+ 'per-task --expect wins. Merged into deliveryPolicy without clobbering its other ' +
80
+ 'fields. Replaces the whole list; pass "clear" to remove the default so new tasks ' +
81
+ 'inherit nothing.',
80
82
  }),
81
83
  'delivery-mode': core_1.Flags.string({
82
84
  options: ['none', 'branch', 'pr', 'pr_automerge', 'direct'],
@@ -132,21 +134,23 @@ class SpacesUpdate extends base_command_1.BaseCommand {
132
134
  // Parse before any write: a bad spec must fail here, not reach the server
133
135
  // as a silently-dropped field. A space default nobody is enforcing is worse
134
136
  // than none, because every task under it then shows a contract that isn't real.
135
- let defaultExpectations;
136
- if (flags['default-expect'] !== undefined && flags['default-expect'].length > 0) {
137
- const parsed = (0, tasks_1.parseExpectations)(flags['default-expect']);
138
- if (!parsed.ok) {
139
- this.error(parsed.error, { exit: 1 });
140
- }
141
- else {
142
- defaultExpectations = parsed.value;
143
- }
137
+ const resolvedDefaults = (0, tasks_1.resolveDefaultExpectations)(flags['default-expect']);
138
+ if (!resolvedDefaults.ok) {
139
+ this.error(resolvedDefaults.error, { exit: 1 });
144
140
  }
141
+ const defaultExpectations = resolvedDefaults.ok ? resolvedDefaults.value : undefined;
145
142
  const isolation = flags['worktree-isolation'];
146
143
  const deliveryMode = flags['delivery-mode'];
147
144
  const integrationBranch = flags['integration-branch'];
148
145
  const autoMergeTier = flags['auto-merge-tier'];
149
- if (isolation || defaultExpectations || deliveryMode || integrationBranch || autoMergeTier) {
146
+ // `defaultExpectations === null` is a real instruction (remove the default), so
147
+ // it must reach the policy merge — a truthiness test dropped it silently and
148
+ // made `clear` a no-op that printed success.
149
+ if (isolation ||
150
+ defaultExpectations !== undefined ||
151
+ deliveryMode ||
152
+ integrationBranch ||
153
+ autoMergeTier) {
150
154
  let basePolicy = {};
151
155
  if (payload.deliveryPolicy && typeof payload.deliveryPolicy === 'object') {
152
156
  basePolicy = { ...payload.deliveryPolicy };
@@ -169,7 +173,7 @@ class SpacesUpdate extends base_command_1.BaseCommand {
169
173
  ...(deliveryMode ? { mode: deliveryMode } : {}),
170
174
  ...(integrationBranch ? { integrationBranch } : {}),
171
175
  ...(autoMergeTier ? { autoMergeTrustTier: autoMergeTier } : {}),
172
- ...(defaultExpectations ? { defaultExpectations } : {}),
176
+ ...(defaultExpectations !== undefined ? { defaultExpectations } : {}),
173
177
  };
174
178
  }
175
179
  // Goal links are NOT part of the PATCH body — the server rejects a PATCH
@@ -0,0 +1,38 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ /**
3
+ * `skrr spaces work-sync reconcile <space>` — answer the divergence question
4
+ * without a browser, and repair what it finds.
5
+ *
6
+ * Root `CLAUDE.md` records that two Work Sync exit criteria were knowingly
7
+ * overridden, and that "an unexplained board divergence is a live hypothesis, not
8
+ * an impossibility, and is the first thing to suspect." `work-sync telemetry`
9
+ * reports whether the browser's convergence detector has ever RUN. This answers
10
+ * the underlying question directly: it compares every published projection entry
11
+ * against authoritative task truth server-side, and `upserts` is the count that
12
+ * disagreed.
13
+ *
14
+ * That matters because the detector only runs where a board is open. A Space
15
+ * nobody has visited since the detector shipped reports zero comparisons forever,
16
+ * which is indistinguishable from health in the telemetry and is not health. This
17
+ * command needs no board, no browser, and no visitor.
18
+ *
19
+ * IT WRITES, and the help says so plainly rather than burying it. The endpoint is
20
+ * editor-gated because it republishes projection rows that clients will see. It
21
+ * never mutates task truth — the projection is derived, and a republished card is
22
+ * a corrected copy of a Task nobody touched — and on a healthy scope it finds
23
+ * nothing and changes nothing, so the verification and the repair are the same
24
+ * call. There is deliberately no dry-run flag to add: a read-only variant would be
25
+ * a second implementation of the comparison, which is how the two answers start
26
+ * disagreeing.
27
+ */
28
+ export default class SpacesWorkSyncReconcile extends BaseCommand {
29
+ static description: string;
30
+ static examples: string[];
31
+ static args: {
32
+ space: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
33
+ };
34
+ static flags: {
35
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
36
+ };
37
+ run(): Promise<void>;
38
+ }