@skrr-ai/cli 0.1.127 → 0.1.128

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 (113) hide show
  1. package/dist/base-command.d.ts +23 -0
  2. package/dist/base-command.js +72 -0
  3. package/dist/commands/agents/chat.js +7 -0
  4. package/dist/commands/commitments/action-history.js +1 -1
  5. package/dist/commands/commitments/cases.js +3 -3
  6. package/dist/commands/commitments/check-now.d.ts +10 -2
  7. package/dist/commands/commitments/check-now.js +53 -3
  8. package/dist/commands/commitments/create.d.ts +6 -14
  9. package/dist/commands/commitments/create.js +61 -6
  10. package/dist/commands/commitments/doctor.js +3 -1
  11. package/dist/commands/commitments/effective-policy.d.ts +11 -1
  12. package/dist/commands/commitments/effective-policy.js +84 -4
  13. package/dist/commands/commitments/hypotheses.js +12 -12
  14. package/dist/commands/commitments/next-tick.js +5 -0
  15. package/dist/commands/commitments/observations.js +18 -17
  16. package/dist/commands/commitments/pack/list.js +2 -1
  17. package/dist/commands/commitments/pack/plan.d.ts +6 -0
  18. package/dist/commands/commitments/pack/plan.js +19 -0
  19. package/dist/commands/commitments/preflight.d.ts +19 -0
  20. package/dist/commands/commitments/preflight.js +108 -10
  21. package/dist/commands/commitments/trigger-status.js +8 -0
  22. package/dist/commands/commitments/update.d.ts +1 -0
  23. package/dist/commands/commitments/update.js +45 -4
  24. package/dist/commands/commitments/wake-capabilities.js +13 -0
  25. package/dist/commands/compass/archive.js +4 -0
  26. package/dist/commands/compass/commitments.js +15 -1
  27. package/dist/commands/compass/create.js +11 -3
  28. package/dist/commands/compass/effective-policy.d.ts +1 -0
  29. package/dist/commands/compass/effective-policy.js +8 -7
  30. package/dist/commands/compass/revert.d.ts +8 -0
  31. package/dist/commands/compass/revert.js +25 -1
  32. package/dist/commands/compass/revisions.d.ts +2 -0
  33. package/dist/commands/compass/revisions.js +18 -2
  34. package/dist/commands/compass/tactic.d.ts +5 -3
  35. package/dist/commands/compass/tactic.js +8 -7
  36. package/dist/commands/compass/update.js +19 -3
  37. package/dist/commands/followups/act.js +1 -1
  38. package/dist/commands/followups/cancel.js +13 -0
  39. package/dist/commands/followups/observability.js +20 -1
  40. package/dist/commands/followups/plans/create.js +12 -1
  41. package/dist/commands/followups/show.js +13 -0
  42. package/dist/commands/followups/watch.js +1 -1
  43. package/dist/commands/goals/planner-config.d.ts +11 -0
  44. package/dist/commands/goals/planner-config.js +16 -0
  45. package/dist/commands/machines/update.d.ts +5 -3
  46. package/dist/commands/machines/update.js +70 -9
  47. package/dist/commands/northstars/create.js +3 -1
  48. package/dist/commands/northstars/governance/configure.d.ts +4 -0
  49. package/dist/commands/northstars/governance/configure.js +75 -9
  50. package/dist/commands/northstars/intents/decide.d.ts +6 -0
  51. package/dist/commands/northstars/intents/decide.js +69 -15
  52. package/dist/commands/northstars/intents/list.d.ts +2 -1
  53. package/dist/commands/northstars/intents/list.js +15 -5
  54. package/dist/commands/northstars/intents.d.ts +26 -0
  55. package/dist/commands/northstars/intents.js +49 -0
  56. package/dist/commands/northstars/list.d.ts +1 -0
  57. package/dist/commands/northstars/list.js +24 -3
  58. package/dist/commands/northstars/recommendations/list.d.ts +1 -1
  59. package/dist/commands/northstars/recommendations/list.js +3 -3
  60. package/dist/commands/northstars/recommendations.d.ts +19 -0
  61. package/dist/commands/northstars/recommendations.js +42 -0
  62. package/dist/commands/northstars/review-now.d.ts +1 -0
  63. package/dist/commands/northstars/review-now.js +10 -4
  64. package/dist/commands/northstars/reviews.d.ts +1 -0
  65. package/dist/commands/northstars/reviews.js +19 -3
  66. package/dist/commands/northstars/update.js +8 -1
  67. package/dist/commands/proposals/list.js +11 -2
  68. package/dist/commands/proposals/show.js +9 -1
  69. package/dist/commands/spaces/auto-executor/preview.d.ts +14 -1
  70. package/dist/commands/spaces/auto-executor/preview.js +35 -1
  71. package/dist/commands/spaces/auto-executor/run.d.ts +17 -0
  72. package/dist/commands/spaces/auto-executor/run.js +60 -2
  73. package/dist/commands/spaces/files/upload.js +1 -2
  74. package/dist/commands/spaces/index.js +23 -0
  75. package/dist/generated/agent-guides.json +69 -69
  76. package/dist/lib/agentic-stream.d.ts +9 -0
  77. package/dist/lib/agentic-stream.js +14 -0
  78. package/dist/lib/command-miss.js +11 -2
  79. package/dist/lib/commitment-attention-copy.d.ts +96 -0
  80. package/dist/lib/commitment-attention-copy.js +209 -0
  81. package/dist/lib/commitment-loop-status.d.ts +6 -0
  82. package/dist/lib/commitment-loop-status.js +16 -6
  83. package/dist/lib/commitments.d.ts +88 -0
  84. package/dist/lib/commitments.js +188 -0
  85. package/dist/lib/compass-body-help.d.ts +20 -0
  86. package/dist/lib/compass-body-help.js +61 -0
  87. package/dist/lib/compass-refusals.d.ts +35 -0
  88. package/dist/lib/compass-refusals.js +50 -0
  89. package/dist/lib/compasses.d.ts +80 -4
  90. package/dist/lib/compasses.js +232 -12
  91. package/dist/lib/followups.d.ts +22 -1
  92. package/dist/lib/followups.js +21 -1
  93. package/dist/lib/format.d.ts +11 -0
  94. package/dist/lib/format.js +29 -0
  95. package/dist/lib/initiatives.d.ts +9 -2
  96. package/dist/lib/initiatives.js +13 -2
  97. package/dist/lib/machines.d.ts +14 -1
  98. package/dist/lib/machines.js +24 -0
  99. package/dist/lib/northstars.d.ts +158 -24
  100. package/dist/lib/northstars.js +538 -26
  101. package/dist/lib/proposal-decision-command.js +21 -1
  102. package/dist/lib/proposals.d.ts +26 -1
  103. package/dist/lib/proposals.js +163 -0
  104. package/dist/lib/rate-limit-retry.d.ts +5 -0
  105. package/dist/lib/rate-limit-retry.js +19 -0
  106. package/dist/lib/roles.d.ts +1 -1
  107. package/dist/lib/space-charter.js +9 -0
  108. package/dist/lib/task-asset-upload.js +1 -2
  109. package/dist/lib/triggers.d.ts +1 -1
  110. package/dist/lib/triggers.js +11 -3
  111. package/dist/node_modules/@skrr-ai/data-provider/index.js +6504 -6493
  112. package/oclif.manifest.json +350 -34
  113. package/package.json +4 -4
@@ -1,22 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_path_1 = require("node:path");
6
+ const data_provider_1 = require("@skrr-ai/data-provider");
4
7
  const base_command_1 = require("../../base-command");
8
+ const agent_visual_refs_1 = require("../../lib/agent-visual-refs");
5
9
  const machines_1 = require("../../lib/machines");
6
10
  /**
7
- * Rename / describe / tag a machine.
11
+ * Rename / describe / tag a machine, and choose its icon.
8
12
  *
9
13
  * `preferences` is the ONLY user-editable part of a machine — identities,
10
14
  * capabilities and lifecycle stay authoritative in the daemon, harness and
11
- * lease records this aggregate reads. The name set here is yours: it does not
12
- * rename the machine for anyone else.
15
+ * lease records this aggregate reads. The name and icon set here are yours: they
16
+ * do not rename or re-draw the machine for anyone else.
13
17
  */
14
18
  class MachinesUpdate extends base_command_1.BaseCommand {
15
- static description = 'Rename a machine, or set its description and tags';
19
+ static description = 'Rename a machine, set its description, tags and icon';
16
20
  static examples = [
17
21
  '<%= config.bin %> machines update 03548116 --name "Work laptop"',
18
22
  '<%= config.bin %> machines update 03548116 --tag personal --tag macbook',
19
23
  '<%= config.bin %> machines update 03548116 --clear-tags',
24
+ '<%= config.bin %> machines update 03548116 --icon laptop',
25
+ '<%= config.bin %> machines update 03548116 --icon none',
26
+ '<%= config.bin %> machines update 03548116 --icon-file ./mac.png',
20
27
  ];
21
28
  static args = {
22
29
  machine: core_1.Args.string({
@@ -37,6 +44,15 @@ class MachinesUpdate extends base_command_1.BaseCommand {
37
44
  description: 'Remove all tags',
38
45
  exclusive: ['tag'],
39
46
  }),
47
+ icon: core_1.Flags.string({
48
+ description: `Icon for this machine: ${data_provider_1.MACHINE_ICON_PRESET_IDS.join(', ')}. ` +
49
+ '"none" goes back to the default.',
50
+ exclusive: ['icon-file'],
51
+ }),
52
+ 'icon-file': core_1.Flags.string({
53
+ description: 'Use your own image as the icon (.png, .jpg, .jpeg, .webp or .gif). It is resized to a square.',
54
+ exclusive: ['icon'],
55
+ }),
40
56
  };
41
57
  async run() {
42
58
  this.requireAuth();
@@ -50,17 +66,61 @@ class MachinesUpdate extends base_command_1.BaseCommand {
50
66
  patch.tags = [];
51
67
  else if (flags.tag)
52
68
  patch.tags = flags.tag;
53
- if (Object.keys(patch).length === 0) {
69
+ if (flags.icon !== undefined) {
70
+ try {
71
+ patch.icon = (0, machines_1.parseMachineIconFlag)(flags.icon);
72
+ }
73
+ catch (err) {
74
+ this.error(err.message, { exit: 1 });
75
+ }
76
+ }
77
+ // Everything that can be refused without a request is refused before one is
78
+ // sent, so a typo'd path never leaves the other changes half applied.
79
+ let upload;
80
+ const iconFile = flags['icon-file'];
81
+ if (iconFile !== undefined) {
82
+ const type = (0, agent_visual_refs_1.imageUploadMimeType)(iconFile);
83
+ if (!type) {
84
+ this.error(`--icon-file must be a .png, .jpg, .jpeg, .webp or .gif image (got ${(0, node_path_1.basename)(iconFile)}).`, { exit: 1 });
85
+ }
86
+ let bytes;
87
+ try {
88
+ bytes = await (0, promises_1.readFile)(iconFile);
89
+ }
90
+ catch (err) {
91
+ this.error(`--icon-file: cannot read ${iconFile} (${err.message}).`, {
92
+ exit: 1,
93
+ });
94
+ }
95
+ upload = { bytes, type, filename: (0, node_path_1.basename)(iconFile) };
96
+ }
97
+ const hasPatch = Object.keys(patch).length > 0;
98
+ if (!hasPatch && !upload) {
54
99
  // Sending an empty patch would return the machine unchanged and read as
55
100
  // success, which is the wrong answer to "I asked you to change something".
56
- this.error('Nothing to update. Pass --name, --description, --tag, or --clear-tags.', {
57
- exit: 1,
58
- });
101
+ this.error('Nothing to update. Pass --name, --description, --tag, --clear-tags, --icon, or --icon-file.', { exit: 1 });
59
102
  }
60
103
  let machine;
61
104
  try {
62
105
  const resolved = (0, machines_1.resolveMachineRef)(await (0, machines_1.listMachines)(), args.machine);
63
- machine = await (0, machines_1.updateMachine)(resolved.id, patch);
106
+ if (hasPatch)
107
+ machine = await (0, machines_1.updateMachine)(resolved.id, patch);
108
+ if (upload) {
109
+ const form = new FormData();
110
+ // Typed, or the route refuses it as application/octet-stream (OSK-9841).
111
+ form.append('file', new Blob([new Uint8Array(upload.bytes)], { type: upload.type }), upload.filename);
112
+ try {
113
+ await data_provider_1.dataService.uploadMachineIcon(resolved.id, form);
114
+ }
115
+ catch (err) {
116
+ if (hasPatch)
117
+ this.warn('The other changes were saved, but the icon was not uploaded.');
118
+ throw err;
119
+ }
120
+ // The upload answers only the icon; read the machine back so what is
121
+ // printed is what is stored.
122
+ machine = await (0, machines_1.getMachine)(resolved.id);
123
+ }
64
124
  }
65
125
  catch (err) {
66
126
  this.handleApiError(err);
@@ -77,6 +137,7 @@ class MachinesUpdate extends base_command_1.BaseCommand {
77
137
  this.log(` description ${machine.preferences.description}`);
78
138
  }
79
139
  this.log(` tags ${machine.preferences?.tags?.join(', ') || '-'}`);
140
+ this.log(` icon ${(0, machines_1.describeMachineIcon)(machine.preferences?.icon)}`);
80
141
  }
81
142
  }
82
143
  exports.default = MachinesUpdate;
@@ -31,7 +31,7 @@ class NorthstarsCreate extends base_command_1.BaseCommand {
31
31
  min: 0,
32
32
  }),
33
33
  'max-active': core_1.Flags.integer({
34
- description: 'Soft cap on simultaneously active commitments',
34
+ description: 'Soft cap on simultaneously active commitments (0 = no cap)',
35
35
  min: 0,
36
36
  }),
37
37
  goal: core_1.Flags.string({
@@ -98,6 +98,8 @@ class NorthstarsCreate extends base_command_1.BaseCommand {
98
98
  }
99
99
  this.log(`Created ${created.id} — ${created.title} (${created.status})`);
100
100
  this.log(`URL: ${created.url ?? '-'}`);
101
+ if (flags['max-active'] === 0)
102
+ this.log('Attention max active 0 (no cap)');
101
103
  this.log(flags['review-interval-hours'] !== undefined
102
104
  ? `Review runs every ${flags['review-interval-hours']}h.`
103
105
  : 'Review is manual — set --review-interval-hours or run `skrr northstars review-now`.');
@@ -22,6 +22,7 @@ export default class NorthstarsGovernanceConfigure extends BaseCommand {
22
22
  allow: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
23
  'require-approval-for': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
24
  protect: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
+ 'clear-protected': import("@oclif/core/interfaces").BooleanFlag<boolean>;
25
26
  'max-intents-per-review': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
26
27
  'max-auto-per-review': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
28
  'max-auto-per-day': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -30,8 +31,11 @@ export default class NorthstarsGovernanceConfigure extends BaseCommand {
30
31
  'max-interval-hours': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
31
32
  'max-relative-change': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
32
33
  'max-observation-runs-per-day': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
34
+ 'clear-max-observation-runs-per-day': import("@oclif/core/interfaces").BooleanFlag<boolean>;
33
35
  'max-runs-per-day': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
36
+ 'clear-max-runs-per-day': import("@oclif/core/interfaces").BooleanFlag<boolean>;
34
37
  'max-concurrent-runs': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
38
+ 'clear-max-concurrent-runs': import("@oclif/core/interfaces").BooleanFlag<boolean>;
35
39
  'critical-reserve': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
36
40
  };
37
41
  run(): Promise<void>;
@@ -19,6 +19,8 @@ class NorthstarsGovernanceConfigure extends base_command_1.BaseCommand {
19
19
  '<%= config.bin %> northstars governance configure nst_abc123 --execution approval',
20
20
  '<%= config.bin %> northstars governance configure nst_abc123 --execution bounded_auto --allow request_commitment_check --max-auto-per-day 3',
21
21
  '<%= config.bin %> northstars governance configure nst_abc123 --protect cmt_1 --protect cmt_2',
22
+ '<%= config.bin %> northstars governance configure nst_abc123 --clear-protected',
23
+ '<%= config.bin %> northstars governance configure nst_abc123 --clear-max-runs-per-day',
22
24
  ];
23
25
  static args = {
24
26
  northstarId: core_1.Args.string({ ignoreStdin: true, description: 'Northstar ID', required: true }),
@@ -30,7 +32,7 @@ class NorthstarsGovernanceConfigure extends base_command_1.BaseCommand {
30
32
  options: [...northstars_1.NORTHSTAR_ANALYSIS_MODES],
31
33
  }),
32
34
  execution: core_1.Flags.string({
33
- description: 'How much write authority a review outcome carries',
35
+ description: "What happens to management intents: advisory records them (none is applied), approval asks first, bounded_auto applies a narrow set. In every mode a review still writes each linked active commitment's allocation (cadence band, run share); it never pauses, resumes, reprioritizes or checks in advisory",
34
36
  options: [...northstars_1.NORTHSTAR_EXECUTION_MODES],
35
37
  }),
36
38
  'reviewer-agent': core_1.Flags.string({
@@ -48,8 +50,13 @@ class NorthstarsGovernanceConfigure extends base_command_1.BaseCommand {
48
50
  multiple: true,
49
51
  }),
50
52
  protect: core_1.Flags.string({
51
- description: 'Commitment the governor may never touch (repeatable; replaces the current list)',
53
+ description: 'Commitment the governor may never touch (repeatable; replaces the current list; --clear-protected empties it)',
52
54
  multiple: true,
55
+ exclusive: ['clear-protected'],
56
+ }),
57
+ 'clear-protected': core_1.Flags.boolean({
58
+ description: 'Remove every protected commitment (the list becomes empty)',
59
+ exclusive: ['protect'],
53
60
  }),
54
61
  'max-intents-per-review': core_1.Flags.integer({
55
62
  description: 'Cap on intents per review',
@@ -88,16 +95,31 @@ class NorthstarsGovernanceConfigure extends base_command_1.BaseCommand {
88
95
  description: 'Opt-in portfolio observation runs per UTC day',
89
96
  min: 0,
90
97
  max: 10000,
98
+ exclusive: ['clear-max-observation-runs-per-day'],
99
+ }),
100
+ 'clear-max-observation-runs-per-day': core_1.Flags.boolean({
101
+ description: 'Un-set the observation ceiling (0 means never look; un-set means no ceiling)',
102
+ exclusive: ['max-observation-runs-per-day'],
91
103
  }),
92
104
  'max-runs-per-day': core_1.Flags.integer({
93
105
  description: 'Portfolio-wide action run budget (execution, NOT attention)',
94
106
  min: 0,
95
107
  max: 10000,
108
+ exclusive: ['clear-max-runs-per-day'],
109
+ }),
110
+ 'clear-max-runs-per-day': core_1.Flags.boolean({
111
+ description: 'Un-set the action run budget (0 is a setting, not un-set)',
112
+ exclusive: ['max-runs-per-day'],
96
113
  }),
97
114
  'max-concurrent-runs': core_1.Flags.integer({
98
- description: 'Portfolio concurrency cap',
115
+ description: 'Portfolio concurrency cap (recorded, not yet enforced: nothing limits concurrent runs yet)',
99
116
  min: 0,
100
117
  max: 1000,
118
+ exclusive: ['clear-max-concurrent-runs'],
119
+ }),
120
+ 'clear-max-concurrent-runs': core_1.Flags.boolean({
121
+ description: 'Un-set the concurrency cap',
122
+ exclusive: ['max-concurrent-runs'],
101
123
  }),
102
124
  'critical-reserve': core_1.Flags.string({
103
125
  description: 'Fraction of the execution budget held for critical commitments (0..1)',
@@ -119,8 +141,17 @@ class NorthstarsGovernanceConfigure extends base_command_1.BaseCommand {
119
141
  body.allowedIntentKinds = flags.allow;
120
142
  if (flags['require-approval-for'])
121
143
  body.requireApprovalFor = flags['require-approval-for'];
122
- if (flags.protect)
144
+ if (flags['clear-protected']) {
145
+ body.protectedCommitmentIds = [];
146
+ }
147
+ else if (flags.protect) {
148
+ // An empty value used to reach the server and come back as
149
+ // `protectedCommitmentIds.0: Too small`, a REST field name nobody typed.
150
+ if (flags.protect.some((id) => id.trim() === '')) {
151
+ this.error('--protect needs a commitment id. To remove every protected commitment, pass --clear-protected.');
152
+ }
123
153
  body.protectedCommitmentIds = flags.protect;
154
+ }
124
155
  if (flags['max-intents-per-review'] !== undefined) {
125
156
  body.maxIntentsPerReview = flags['max-intents-per-review'];
126
157
  }
@@ -150,12 +181,21 @@ class NorthstarsGovernanceConfigure extends base_command_1.BaseCommand {
150
181
  if (Object.keys(cadencePolicy).length)
151
182
  body.cadencePolicy = cadencePolicy;
152
183
  const executionBudget = {};
153
- if (flags['max-observation-runs-per-day'] !== undefined)
184
+ // `null` is how the API un-sets a budget; 0 is a setting and is never read as "un-set".
185
+ if (flags['clear-max-observation-runs-per-day'])
186
+ executionBudget.maxObservationRunsPerDay = null;
187
+ else if (flags['max-observation-runs-per-day'] !== undefined)
154
188
  executionBudget.maxObservationRunsPerDay = flags['max-observation-runs-per-day'];
155
- if (flags['max-runs-per-day'] !== undefined) {
189
+ if (flags['clear-max-runs-per-day']) {
190
+ executionBudget.maxRunsPerDay = null;
191
+ }
192
+ else if (flags['max-runs-per-day'] !== undefined) {
156
193
  executionBudget.maxRunsPerDay = flags['max-runs-per-day'];
157
194
  }
158
- if (flags['max-concurrent-runs'] !== undefined) {
195
+ if (flags['clear-max-concurrent-runs']) {
196
+ executionBudget.maxConcurrentRuns = null;
197
+ }
198
+ else if (flags['max-concurrent-runs'] !== undefined) {
159
199
  executionBudget.maxConcurrentRuns = flags['max-concurrent-runs'];
160
200
  }
161
201
  if (flags['critical-reserve'] !== undefined) {
@@ -170,12 +210,16 @@ class NorthstarsGovernanceConfigure extends base_command_1.BaseCommand {
170
210
  if (Object.keys(body).length === 0) {
171
211
  this.error('Nothing to configure — pass at least one flag.');
172
212
  }
213
+ let written;
173
214
  try {
174
- await northstars_1.northstarApi.configureGovernance(args.northstarId, body);
215
+ written = await northstars_1.northstarApi.configureGovernance(args.northstarId, body);
175
216
  }
176
217
  catch (err) {
177
218
  this.handleApiError(err);
178
219
  }
220
+ // The write response says what the server did to the linked commitments; the status
221
+ // re-read below does not (OSK-21245).
222
+ const refresh = (0, northstars_1.readAllocationRefresh)(written);
179
223
  // Re-read the STATUS rather than echoing the write response: the effective
180
224
  // policy is what the server resolved, and readiness (is the reviewer even
181
225
  // online?) is the part a configure command must not leave the user guessing.
@@ -187,10 +231,32 @@ class NorthstarsGovernanceConfigure extends base_command_1.BaseCommand {
187
231
  this.handleApiError(err);
188
232
  }
189
233
  if (flags.json) {
190
- this.log(JSON.stringify(status, null, 2));
234
+ this.log(JSON.stringify(refresh
235
+ ? {
236
+ ...status,
237
+ allocationsRefreshed: refresh.refreshed,
238
+ allocationsPending: refresh.pending,
239
+ allocationsRefreshFailed: refresh.failed,
240
+ }
241
+ : status, null, 2));
191
242
  return;
192
243
  }
193
244
  (0, northstars_1.renderGovernance)(status, (line) => this.log(line));
245
+ const summary = (0, northstars_1.describeAllocationRefresh)(refresh, {
246
+ northstarId: args.northstarId,
247
+ inputsSent: northstars_1.ALLOCATION_INPUT_FLAGS.some((flag) => flags[flag] !== undefined && flags[flag] !== false),
248
+ bin: this.config?.bin,
249
+ });
250
+ if (summary.lines.length)
251
+ this.log('');
252
+ for (const line of summary.lines)
253
+ this.log(line);
254
+ for (const warning of summary.warnings)
255
+ this.warn(warning);
256
+ if (flags['max-concurrent-runs'] !== undefined) {
257
+ this.log('');
258
+ this.log('Note: --max-concurrent-runs is recorded but not yet enforced. Nothing limits concurrent runs across this northstar; each commitment keeps its own concurrency limit.');
259
+ }
194
260
  }
195
261
  }
196
262
  exports.default = NorthstarsGovernanceConfigure;
@@ -7,6 +7,12 @@ import { BaseCommand } from '../../../base-command';
7
7
  * hours later against a portfolio that has since changed is invalidated rather
8
8
  * than forced through. That is the point of approving an INTENT rather than
9
9
  * queueing a command.
10
+ *
11
+ * ONE exit rule: the command exits 0 only when the thing asked for now holds
12
+ * (an approval left the intent `applied`, a rejection left it `rejected`).
13
+ * Retrying a decision that already took effect is a replay and also exits 0;
14
+ * everything else exits 1 with a sentence, and `--json` keeps the raw code plus
15
+ * `reasonText`.
10
16
  */
11
17
  export default class NorthstarsIntentsDecide extends BaseCommand {
12
18
  static description: string;
@@ -11,6 +11,12 @@ const northstars_1 = require("../../../lib/northstars");
11
11
  * hours later against a portfolio that has since changed is invalidated rather
12
12
  * than forced through. That is the point of approving an INTENT rather than
13
13
  * queueing a command.
14
+ *
15
+ * ONE exit rule: the command exits 0 only when the thing asked for now holds
16
+ * (an approval left the intent `applied`, a rejection left it `rejected`).
17
+ * Retrying a decision that already took effect is a replay and also exits 0;
18
+ * everything else exits 1 with a sentence, and `--json` keeps the raw code plus
19
+ * `reasonText`.
14
20
  */
15
21
  class NorthstarsIntentsDecide extends base_command_1.BaseCommand {
16
22
  static description = 'Approve or reject a pending northstar management intent';
@@ -40,32 +46,80 @@ class NorthstarsIntentsDecide extends base_command_1.BaseCommand {
40
46
  if (!flags.approve && !flags.reject) {
41
47
  this.error('Pass --approve or --reject.');
42
48
  }
49
+ const ctx = {
50
+ northstarId: args.northstarId,
51
+ intentId: args.intentId,
52
+ decision: flags.approve ? 'approve' : 'reject',
53
+ bin: this.config.bin,
54
+ };
43
55
  let result;
44
56
  try {
45
57
  result = await northstars_1.northstarApi.decideIntent(args.northstarId, args.intentId, {
46
- decision: flags.approve ? 'approve' : 'reject',
58
+ decision: ctx.decision,
47
59
  ...(flags.review ? { reviewId: flags.review } : {}),
48
60
  });
49
61
  }
50
62
  catch (err) {
63
+ // The server refuses a decision with a bare code (`northstar_version_drift`,
64
+ // `intent_not_pending`). Say what it means, and keep the code.
65
+ const status = err?.status;
66
+ const { body } = (0, base_command_1.apiRefusal)(err);
67
+ if ((status === 404 || status === 409) && body && typeof body.reason === 'string') {
68
+ const { code, text } = (0, northstars_1.describeDecisionRefusal)(body, ctx);
69
+ // A retry of a decision that already took effect (the first response was
70
+ // lost, or a hedge raced itself) leaves the state that was asked for.
71
+ const verdict = typeof body.verdict === 'string' ? body.verdict : undefined;
72
+ const alreadyHolds = code === 'intent_not_pending' &&
73
+ ((ctx.decision === 'approve' && verdict === 'applied') ||
74
+ (ctx.decision === 'reject' && verdict === 'rejected'));
75
+ if (alreadyHolds) {
76
+ if (flags.json) {
77
+ this.log(JSON.stringify({
78
+ ok: true,
79
+ verdict,
80
+ replayed: true,
81
+ ...(body.decidedAt ? { decidedAt: body.decidedAt } : {}),
82
+ ...(body.decidedByYou !== undefined ? { decidedByYou: body.decidedByYou } : {}),
83
+ reasonText: text,
84
+ }, null, 2));
85
+ }
86
+ else {
87
+ this.log(`${args.intentId} → ${verdict}`);
88
+ this.log(text);
89
+ }
90
+ return;
91
+ }
92
+ this.failWithCliError({
93
+ message: text,
94
+ code,
95
+ status,
96
+ exit: 1,
97
+ retryable: false,
98
+ details: { ...body, reasonText: text },
99
+ });
100
+ }
51
101
  this.handleApiError(err);
52
102
  }
53
- if (flags.json) {
54
- this.log(JSON.stringify(result, null, 2));
55
- return;
56
- }
57
- const verdict = String(result.verdict ?? 'unknown');
58
- if (result.proposalOnly) {
59
- // A proposal is a decision to act BY HAND. Saying so avoids the reasonable
60
- // assumption that approving it made something happen.
61
- this.log(`Recorded: ${args.intentId} approved as a proposal — no mutation was performed.`);
103
+ const outcome = (0, northstars_1.describeDecisionOutcome)(result, ctx);
104
+ if (outcome.exitCode === 0) {
105
+ if (flags.json) {
106
+ this.log(JSON.stringify({ ...result, ...(outcome.text ? { reasonText: outcome.text } : {}) }, null, 2));
107
+ return;
108
+ }
109
+ this.log(outcome.headline);
110
+ if (outcome.text)
111
+ this.log(outcome.text);
62
112
  return;
63
113
  }
64
- this.log(`${args.intentId} → ${verdict}`);
65
- const applyResult = result.applyResult;
66
- if (applyResult && applyResult.applied !== true && applyResult.reason) {
67
- this.log(`Not applied: ${String(applyResult.reason)}`);
68
- }
114
+ if (!flags.json)
115
+ this.log(outcome.headline);
116
+ this.failWithCliError({
117
+ message: outcome.text ?? outcome.headline,
118
+ code: outcome.code ?? 'intent_not_applied',
119
+ exit: 1,
120
+ retryable: false,
121
+ details: { ...result, reasonText: outcome.text },
122
+ });
69
123
  }
70
124
  }
71
125
  exports.default = NorthstarsIntentsDecide;
@@ -1,6 +1,6 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
2
  /**
3
- * `skrr northstars intents <northstar-id>` — management intents across recent
3
+ * `skrr northstars intents list <northstar-id>` — management intents across recent
4
4
  * reviews, newest first.
5
5
  *
6
6
  * Flattened across reviews on purpose: the question a person asks is "what is
@@ -17,6 +17,7 @@ export default class NorthstarsIntentsList extends BaseCommand {
17
17
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
18
  status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
19
  limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ wide: import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
21
  };
21
22
  run(): Promise<void>;
22
23
  }
@@ -5,7 +5,7 @@ const core_1 = require("@oclif/core");
5
5
  const base_command_1 = require("../../../base-command");
6
6
  const northstars_1 = require("../../../lib/northstars");
7
7
  /**
8
- * `skrr northstars intents <northstar-id>` — management intents across recent
8
+ * `skrr northstars intents list <northstar-id>` — management intents across recent
9
9
  * reviews, newest first.
10
10
  *
11
11
  * Flattened across reviews on purpose: the question a person asks is "what is
@@ -15,9 +15,9 @@ const northstars_1 = require("../../../lib/northstars");
15
15
  class NorthstarsIntentsList extends base_command_1.BaseCommand {
16
16
  static description = 'List northstar management intents (default: everything recent)';
17
17
  static examples = [
18
- '<%= config.bin %> northstars intents nst_abc123',
19
- '<%= config.bin %> northstars intents nst_abc123 --status awaiting_approval',
20
- '<%= config.bin %> northstars intents nst_abc123 --status applied --json',
18
+ '<%= config.bin %> northstars intents list nst_abc123',
19
+ '<%= config.bin %> northstars intents list nst_abc123 --status awaiting_approval',
20
+ '<%= config.bin %> northstars intents list nst_abc123 --status applied --json',
21
21
  ];
22
22
  static args = {
23
23
  northstarId: core_1.Args.string({ ignoreStdin: true, description: 'Northstar ID', required: true }),
@@ -29,6 +29,7 @@ class NorthstarsIntentsList extends base_command_1.BaseCommand {
29
29
  options: [...northstars_1.NORTHSTAR_INTENT_VERDICTS],
30
30
  }),
31
31
  limit: core_1.Flags.integer({ description: 'Max intents (1..500)', min: 1, max: 500 }),
32
+ wide: northstars_1.northstarWideFlag,
32
33
  };
33
34
  async run() {
34
35
  this.requireAuth();
@@ -47,7 +48,16 @@ class NorthstarsIntentsList extends base_command_1.BaseCommand {
47
48
  this.printJsonResult((0, list_output_1.listResult)(response));
48
49
  return;
49
50
  }
50
- (0, northstars_1.renderIntentList)(response.data ?? [], (line) => this.log(line));
51
+ const rows = response.data ?? [];
52
+ (0, northstars_1.renderIntentList)(rows, (line) => this.log(line), { wide: flags.wide });
53
+ const footer = (0, northstars_1.listFooter)({
54
+ shown: rows.length,
55
+ hasMore: response.has_more === true,
56
+ limit: flags.limit ?? 100,
57
+ max: 500,
58
+ });
59
+ if (footer)
60
+ this.log(footer);
51
61
  }
52
62
  }
53
63
  exports.default = NorthstarsIntentsList;
@@ -0,0 +1,26 @@
1
+ import NorthstarsIntentsList from './intents/list';
2
+ /**
3
+ * `skrr northstars intents <northstar-id>` — the topic's default: list the intents.
4
+ *
5
+ * `intents` is a topic (`list`, `decide`), and every sibling read command
6
+ * (`show`, `reviews`) works with just a northstar id. Without this command
7
+ * `northstars intents nst_abc123` was answered with "is not a command" and a
8
+ * did-you-mean that listed `create` and `delete` first, while the help examples
9
+ * printed exactly that form. Listing is read-only, so it is the safe default; the
10
+ * subcommands are untouched (oclif resolves the longest command id first, so
11
+ * `intents decide …` and `intents list …` still reach their own commands).
12
+ *
13
+ * With no northstar id it prints the topic help instead of a bare missing-argument error.
14
+ */
15
+ export default class NorthstarsIntents extends NorthstarsIntentsList {
16
+ static description: string;
17
+ static examples: string[];
18
+ static args: typeof NorthstarsIntentsList.args;
19
+ static flags: {
20
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
21
+ status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ wide: import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
+ };
25
+ run(): Promise<void>;
26
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const core_1 = require("@oclif/core");
7
+ const list_1 = __importDefault(require("./intents/list"));
8
+ /**
9
+ * `skrr northstars intents <northstar-id>` — the topic's default: list the intents.
10
+ *
11
+ * `intents` is a topic (`list`, `decide`), and every sibling read command
12
+ * (`show`, `reviews`) works with just a northstar id. Without this command
13
+ * `northstars intents nst_abc123` was answered with "is not a command" and a
14
+ * did-you-mean that listed `create` and `delete` first, while the help examples
15
+ * printed exactly that form. Listing is read-only, so it is the safe default; the
16
+ * subcommands are untouched (oclif resolves the longest command id first, so
17
+ * `intents decide …` and `intents list …` still reach their own commands).
18
+ *
19
+ * With no northstar id it prints the topic help instead of a bare missing-argument error.
20
+ */
21
+ class NorthstarsIntents extends list_1.default {
22
+ static description = 'List northstar management intents; subcommands: list, decide (`intents <id>` is `intents list <id>`)';
23
+ static examples = [
24
+ '<%= config.bin %> northstars intents nst_abc123',
25
+ '<%= config.bin %> northstars intents nst_abc123 --status awaiting_approval',
26
+ '<%= config.bin %> northstars intents list nst_abc123',
27
+ '<%= config.bin %> northstars intents decide nst_abc123 nsi_xyz --approve',
28
+ ];
29
+ // Own, not inherited: oclif spreads the command class into the parser input, and a static a
30
+ // subclass only inherits is dropped (OSK-15346). The argument is optional so a bare
31
+ // `skrr northstars intents` can show the subcommands rather than fail on a missing id.
32
+ static args = {
33
+ northstarId: core_1.Args.string({
34
+ ignoreStdin: true,
35
+ description: 'Northstar ID (omit to see the subcommands)',
36
+ required: false,
37
+ }),
38
+ };
39
+ static flags = list_1.default.flags;
40
+ async run() {
41
+ const { args } = await this.parse(NorthstarsIntents);
42
+ if (!args.northstarId) {
43
+ await this.config.runCommand('help', ['northstars', 'intents']);
44
+ return;
45
+ }
46
+ await super.run();
47
+ }
48
+ }
49
+ exports.default = NorthstarsIntents;
@@ -13,6 +13,7 @@ export default class NorthstarsList extends BaseCommand {
13
13
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
14
  status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
15
  limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ cursor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
17
  };
17
18
  run(): Promise<void>;
18
19
  }
@@ -21,14 +21,24 @@ class NorthstarsList extends base_command_1.BaseCommand {
21
21
  static flags = {
22
22
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
23
23
  status: core_1.Flags.string({ description: 'Filter by status', options: [...northstars_1.NORTHSTAR_STATUSES] }),
24
- limit: core_1.Flags.integer({ description: 'Max results (1..100)', min: 1, max: 100 }),
24
+ limit: core_1.Flags.integer({ description: 'Max results (1..100, default 50)', min: 1, max: 100 }),
25
+ cursor: core_1.Flags.string({
26
+ description: 'Continue from the next_cursor a previous page returned',
27
+ }),
25
28
  };
26
29
  async run() {
27
30
  this.requireAuth();
28
31
  const { flags } = await this.parse(NorthstarsList);
29
32
  let response;
30
33
  try {
31
- response = await northstars_1.northstarApi.list({ status: flags.status, limit: flags.limit });
34
+ // Cursor paging is always on: it is the only list path that returns a cursor, and the
35
+ // default page is sent explicitly so it stays the 50 this command always listed.
36
+ response = await northstars_1.northstarApi.list({
37
+ status: flags.status,
38
+ limit: flags.limit ?? northstars_1.NORTHSTAR_LIST_DEFAULT_LIMIT,
39
+ pagination: 'cursor',
40
+ cursor: flags.cursor,
41
+ });
32
42
  }
33
43
  catch (err) {
34
44
  this.handleApiError(err);
@@ -37,7 +47,18 @@ class NorthstarsList extends base_command_1.BaseCommand {
37
47
  this.printJsonResult((0, list_output_1.listResult)(response));
38
48
  return;
39
49
  }
40
- (0, northstars_1.renderNorthstarList)(response.data ?? [], (line) => this.log(line));
50
+ const rows = response.data ?? [];
51
+ (0, northstars_1.renderNorthstarList)(rows, (line) => this.log(line));
52
+ const footer = (0, northstars_1.listFooter)({
53
+ shown: rows.length,
54
+ hasMore: response.has_more === true,
55
+ limit: flags.limit ?? northstars_1.NORTHSTAR_LIST_DEFAULT_LIMIT,
56
+ max: 100,
57
+ nextCursor: response.next_cursor,
58
+ cursor: true,
59
+ });
60
+ if (footer)
61
+ this.log(footer);
41
62
  }
42
63
  }
43
64
  exports.default = NorthstarsList;