@skrr-ai/cli 0.1.126 → 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 (123) 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/agents/goals/list.d.ts +2 -2
  5. package/dist/commands/agents/goals/list.js +2 -2
  6. package/dist/commands/commitments/action-history.js +1 -1
  7. package/dist/commands/commitments/cases.js +3 -3
  8. package/dist/commands/commitments/check-now.d.ts +10 -2
  9. package/dist/commands/commitments/check-now.js +53 -3
  10. package/dist/commands/commitments/create.d.ts +6 -14
  11. package/dist/commands/commitments/create.js +61 -6
  12. package/dist/commands/commitments/doctor.js +3 -1
  13. package/dist/commands/commitments/effective-policy.d.ts +11 -1
  14. package/dist/commands/commitments/effective-policy.js +84 -4
  15. package/dist/commands/commitments/hypotheses.js +12 -12
  16. package/dist/commands/commitments/next-tick.js +5 -0
  17. package/dist/commands/commitments/observations.js +18 -17
  18. package/dist/commands/commitments/pack/list.js +2 -1
  19. package/dist/commands/commitments/pack/plan.d.ts +6 -0
  20. package/dist/commands/commitments/pack/plan.js +19 -0
  21. package/dist/commands/commitments/preflight.d.ts +19 -0
  22. package/dist/commands/commitments/preflight.js +108 -10
  23. package/dist/commands/commitments/trigger-status.js +8 -0
  24. package/dist/commands/commitments/update.d.ts +1 -0
  25. package/dist/commands/commitments/update.js +45 -4
  26. package/dist/commands/commitments/wake-capabilities.js +13 -0
  27. package/dist/commands/compass/archive.js +4 -0
  28. package/dist/commands/compass/commitments.js +15 -1
  29. package/dist/commands/compass/create.js +11 -3
  30. package/dist/commands/compass/effective-policy.d.ts +1 -0
  31. package/dist/commands/compass/effective-policy.js +8 -7
  32. package/dist/commands/compass/revert.d.ts +8 -0
  33. package/dist/commands/compass/revert.js +25 -1
  34. package/dist/commands/compass/revisions.d.ts +2 -0
  35. package/dist/commands/compass/revisions.js +18 -2
  36. package/dist/commands/compass/tactic.d.ts +5 -3
  37. package/dist/commands/compass/tactic.js +8 -7
  38. package/dist/commands/compass/update.js +19 -3
  39. package/dist/commands/followups/act.js +1 -1
  40. package/dist/commands/followups/cancel.js +13 -0
  41. package/dist/commands/followups/observability.js +20 -1
  42. package/dist/commands/followups/plans/create.js +12 -1
  43. package/dist/commands/followups/show.js +13 -0
  44. package/dist/commands/followups/watch.js +1 -1
  45. package/dist/commands/goals/create.d.ts +1 -0
  46. package/dist/commands/goals/create.js +4 -1
  47. package/dist/commands/goals/planner-config.d.ts +11 -0
  48. package/dist/commands/goals/planner-config.js +16 -0
  49. package/dist/commands/goals/revisions.js +3 -19
  50. package/dist/commands/machines/update.d.ts +5 -3
  51. package/dist/commands/machines/update.js +70 -9
  52. package/dist/commands/northstars/create.js +3 -1
  53. package/dist/commands/northstars/governance/configure.d.ts +4 -0
  54. package/dist/commands/northstars/governance/configure.js +75 -9
  55. package/dist/commands/northstars/intents/decide.d.ts +6 -0
  56. package/dist/commands/northstars/intents/decide.js +69 -15
  57. package/dist/commands/northstars/intents/list.d.ts +2 -1
  58. package/dist/commands/northstars/intents/list.js +15 -5
  59. package/dist/commands/northstars/intents.d.ts +26 -0
  60. package/dist/commands/northstars/intents.js +49 -0
  61. package/dist/commands/northstars/list.d.ts +1 -0
  62. package/dist/commands/northstars/list.js +24 -3
  63. package/dist/commands/northstars/recommendations/list.d.ts +1 -1
  64. package/dist/commands/northstars/recommendations/list.js +3 -3
  65. package/dist/commands/northstars/recommendations.d.ts +19 -0
  66. package/dist/commands/northstars/recommendations.js +42 -0
  67. package/dist/commands/northstars/review-now.d.ts +1 -0
  68. package/dist/commands/northstars/review-now.js +10 -4
  69. package/dist/commands/northstars/reviews.d.ts +1 -0
  70. package/dist/commands/northstars/reviews.js +19 -3
  71. package/dist/commands/northstars/update.js +8 -1
  72. package/dist/commands/proposals/list.js +11 -2
  73. package/dist/commands/proposals/show.js +9 -1
  74. package/dist/commands/spaces/auto-executor/preview.d.ts +14 -1
  75. package/dist/commands/spaces/auto-executor/preview.js +35 -1
  76. package/dist/commands/spaces/auto-executor/run.d.ts +17 -0
  77. package/dist/commands/spaces/auto-executor/run.js +60 -2
  78. package/dist/commands/spaces/files/upload.js +1 -2
  79. package/dist/commands/spaces/index.js +23 -0
  80. package/dist/commands/spaces/revisions.js +3 -2
  81. package/dist/commands/tasks/revisions.js +3 -2
  82. package/dist/commands/triggers/fire-detail.js +2 -0
  83. package/dist/generated/agent-guides.json +69 -69
  84. package/dist/lib/agentic-stream.d.ts +9 -0
  85. package/dist/lib/agentic-stream.js +14 -0
  86. package/dist/lib/command-miss.js +11 -2
  87. package/dist/lib/commitment-attention-copy.d.ts +96 -0
  88. package/dist/lib/commitment-attention-copy.js +209 -0
  89. package/dist/lib/commitment-loop-status.d.ts +6 -0
  90. package/dist/lib/commitment-loop-status.js +16 -6
  91. package/dist/lib/commitments.d.ts +88 -0
  92. package/dist/lib/commitments.js +188 -0
  93. package/dist/lib/compass-body-help.d.ts +20 -0
  94. package/dist/lib/compass-body-help.js +61 -0
  95. package/dist/lib/compass-refusals.d.ts +35 -0
  96. package/dist/lib/compass-refusals.js +50 -0
  97. package/dist/lib/compasses.d.ts +80 -4
  98. package/dist/lib/compasses.js +232 -12
  99. package/dist/lib/followups.d.ts +22 -1
  100. package/dist/lib/followups.js +21 -1
  101. package/dist/lib/format.d.ts +11 -0
  102. package/dist/lib/format.js +29 -0
  103. package/dist/lib/initiatives.d.ts +9 -2
  104. package/dist/lib/initiatives.js +13 -2
  105. package/dist/lib/machines.d.ts +14 -1
  106. package/dist/lib/machines.js +24 -0
  107. package/dist/lib/northstars.d.ts +158 -24
  108. package/dist/lib/northstars.js +538 -26
  109. package/dist/lib/proposal-decision-command.js +21 -1
  110. package/dist/lib/proposals.d.ts +26 -1
  111. package/dist/lib/proposals.js +163 -0
  112. package/dist/lib/rate-limit-retry.d.ts +5 -0
  113. package/dist/lib/rate-limit-retry.js +19 -0
  114. package/dist/lib/revision-actor.d.ts +2 -0
  115. package/dist/lib/revision-actor.js +16 -0
  116. package/dist/lib/roles.d.ts +1 -1
  117. package/dist/lib/space-charter.js +9 -0
  118. package/dist/lib/task-asset-upload.js +1 -2
  119. package/dist/lib/triggers.d.ts +33 -1
  120. package/dist/lib/triggers.js +77 -3
  121. package/dist/node_modules/@skrr-ai/data-provider/index.js +7537 -7474
  122. package/oclif.manifest.json +357 -34
  123. package/package.json +4 -4
@@ -359,8 +359,17 @@ function nearestCommands(id, all, limit = 3) {
359
359
  // A third of the length keeps this to genuine near-misses; without a bound
360
360
  // every miss suggests three unrelated commands, which is noise wearing the
361
361
  // shape of help.
362
- .filter(({ d }) => d <= Math.max(2, Math.floor(id.length / 3)))
363
- .sort((x, y) => x.d - y.d)
362
+ // A command NESTED under what was typed (`northstars:intents` -> `northstars:intents:list`)
363
+ // is the correction the reader was reaching for, whatever its edit distance, so it is
364
+ // neither bound-filtered nor ranked by distance. Distance alone kept `intents:decide` out
365
+ // and put shorter siblings ahead of `intents:list`, so `northstars intents <id>` was
366
+ // answered with "Did you mean: northstars create, northstars delete, northstars intents
367
+ // list?" and the first guess a reader acts on was a destructive one.
368
+ .filter(({ d, candidate }) => candidate.startsWith(`${id}:`) || d <= Math.max(2, Math.floor(id.length / 3)))
369
+ .sort((x, y) => {
370
+ const nested = (c) => (c.startsWith(`${id}:`) ? 0 : 1);
371
+ return nested(x.candidate) - nested(y.candidate) || x.d - y.d;
372
+ })
364
373
  .slice(0, limit)
365
374
  .map(({ candidate }) => candidate));
366
375
  }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The attention settings of a Commitment, said in words.
3
+ *
4
+ * `show`, `effective-policy` and `preflight` all print these, from this one
5
+ * file, so the three cannot phrase a setting three ways. A value is described
6
+ * from the RAW stored setting and never through a coercion: `null` is "no
7
+ * limit" and `0` is "muted", and `Number(null)` is `0`, which is exactly how a
8
+ * cap that was never set would read as a mute (the server documents the same
9
+ * trap at `policySchema.maxUserNudgesPerDay` in api/server/routes/commitments.js).
10
+ */
11
+ /** What the owner can type for `--max-nudges`, as `--help` prints it. */
12
+ export declare const MAX_NUDGES_FLAG_HELP: string;
13
+ /**
14
+ * Parse `--max-nudges`: a whole number, or the word `unlimited` (stored as `null`).
15
+ * The server accepts 0 up to MAX_SAFE_INTEGER, so there is no 0..20 ceiling here.
16
+ */
17
+ export declare function parseMaxNudgesFlag(raw: string | undefined): number | null | undefined;
18
+ /** `null` and an unset cap are "no daily limit"; `0` is a mute; N is "at most N a day". */
19
+ export declare function describeNudgeCap(value: unknown): string;
20
+ /** The least time the platform leaves between two nudges to you. */
21
+ export declare function describeNudgeGap(ms: unknown): string;
22
+ /** The back-off for a person who is not answering. The server's default count is 2. */
23
+ export declare function describeNoReplyRule(noReply: {
24
+ enabled?: boolean;
25
+ afterNudges?: number;
26
+ } | null | undefined): string;
27
+ export type NudgeSettings = {
28
+ /** `null` = unlimited, `0` = muted. `undefined` = never set, which is unlimited. */
29
+ maxUserNudgesPerDay?: number | null;
30
+ minUserNudgeIntervalMs?: number | null;
31
+ noReply?: {
32
+ enabled?: boolean;
33
+ afterNudges?: number;
34
+ } | null;
35
+ };
36
+ /** Read the three settings out of a stored `policy`. */
37
+ export declare function nudgeSettingsFromPolicy(policy: Record<string, any>): NudgeSettings;
38
+ /**
39
+ * The lines that say how much a Commitment may interrupt its owner. A muted one
40
+ * prints the mute alone: a gap and a back-off on a Commitment that never speaks
41
+ * would only suggest it does. `withNoReply: false` leaves the back-off out when the
42
+ * stored rule could not be read, rather than claim it is off.
43
+ */
44
+ export declare function nudgeSettingLines(settings: NudgeSettings, { withNoReply }?: {
45
+ withNoReply?: boolean;
46
+ }): string[];
47
+ /** What a time of day is, in the words a person can type. */
48
+ export declare const CLOCK_TIME_HINT = "use HH:MM, 00:00-23:59, for example 22:00";
49
+ /**
50
+ * How to set quiet hours, for the `--from-json` help of `create` and `update`. There is no flag for
51
+ * them, so without this sentence a user had to know the `policy.quietHours` key from outside the CLI
52
+ * (OSK-21123).
53
+ */
54
+ export declare const QUIET_HOURS_HELP: string;
55
+ /**
56
+ * What `policy.executionBudget.allowedEventSources` means, for the same help. An empty list is the
57
+ * unrestricted case, which is the opposite of what `[]` suggests (OSK-21113).
58
+ */
59
+ export declare const EVENT_SOURCES_HELP: string;
60
+ /**
61
+ * The allowlist in words. An empty or missing list means every source is allowed
62
+ * (`sourceAllowed` in api/server/services/EventBus/commitmentWakeCapabilities.js and the check in
63
+ * WakeIntentResolver.js both treat it so); the stored form of "never set" is the same `[]`.
64
+ */
65
+ export declare function describeEventSources(allowed: unknown): string;
66
+ /**
67
+ * The problem with the quiet-hours times in a `policy` about to be sent, or null. The server
68
+ * answered a bad time with its raw pattern (`must match pattern /^\d{1,2}:\d{2}$/`), which says
69
+ * nothing a person can type, and accepted `25:00` (a window that then never applies). Checked
70
+ * here, so the request never leaves with either.
71
+ */
72
+ export declare function quietHoursInputProblem(policy: unknown): string | null;
73
+ type QuietHoursPolicy = {
74
+ enabled?: boolean;
75
+ start?: string;
76
+ end?: string;
77
+ timezone?: string;
78
+ } | null;
79
+ /**
80
+ * The Commitment's own quiet-hours window in words. Without one of its own a Commitment uses its
81
+ * Agent's Compass window (`scheduleIssues.js` documents the same rule), so "none set" says where
82
+ * else a window may come from rather than claiming there is no quiet time at all.
83
+ */
84
+ export declare function describeQuietHours(quietHours: QuietHoursPolicy | undefined): string;
85
+ /**
86
+ * When a held message is released, for the preflight line "Held until your quiet hours end".
87
+ * The report's `quietHours` carries the end as typed, the zone, and the exact instant (`endsAt`,
88
+ * daylight saving included); null when it carries no instant, so the caller keeps its generic
89
+ * sentence rather than inventing a time.
90
+ */
91
+ export declare function describeQuietHoursEnd(quietHours: {
92
+ end?: string;
93
+ endsAt?: string | null;
94
+ timezone?: string;
95
+ } | null | undefined): string | null;
96
+ export {};
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ /**
3
+ * The attention settings of a Commitment, said in words.
4
+ *
5
+ * `show`, `effective-policy` and `preflight` all print these, from this one
6
+ * file, so the three cannot phrase a setting three ways. A value is described
7
+ * from the RAW stored setting and never through a coercion: `null` is "no
8
+ * limit" and `0` is "muted", and `Number(null)` is `0`, which is exactly how a
9
+ * cap that was never set would read as a mute (the server documents the same
10
+ * trap at `policySchema.maxUserNudgesPerDay` in api/server/routes/commitments.js).
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.EVENT_SOURCES_HELP = exports.QUIET_HOURS_HELP = exports.CLOCK_TIME_HINT = exports.MAX_NUDGES_FLAG_HELP = void 0;
14
+ exports.parseMaxNudgesFlag = parseMaxNudgesFlag;
15
+ exports.describeNudgeCap = describeNudgeCap;
16
+ exports.describeNudgeGap = describeNudgeGap;
17
+ exports.describeNoReplyRule = describeNoReplyRule;
18
+ exports.nudgeSettingsFromPolicy = nudgeSettingsFromPolicy;
19
+ exports.nudgeSettingLines = nudgeSettingLines;
20
+ exports.describeEventSources = describeEventSources;
21
+ exports.quietHoursInputProblem = quietHoursInputProblem;
22
+ exports.describeQuietHours = describeQuietHours;
23
+ exports.describeQuietHoursEnd = describeQuietHoursEnd;
24
+ /** What the owner can type for `--max-nudges`, as `--help` prints it. */
25
+ exports.MAX_NUDGES_FLAG_HELP = 'Daily limit on messages that interrupt you: a whole number from 0 up (0 mutes it: ' +
26
+ 'it never contacts you) or "unlimited" to remove the limit. Left unset it is unlimited';
27
+ /**
28
+ * Parse `--max-nudges`: a whole number, or the word `unlimited` (stored as `null`).
29
+ * The server accepts 0 up to MAX_SAFE_INTEGER, so there is no 0..20 ceiling here.
30
+ */
31
+ function parseMaxNudgesFlag(raw) {
32
+ if (raw === undefined)
33
+ return undefined;
34
+ const value = raw.trim().toLowerCase();
35
+ if (value === 'unlimited')
36
+ return null;
37
+ if (/^\d+$/.test(value)) {
38
+ const parsed = Number(value);
39
+ if (Number.isSafeInteger(parsed))
40
+ return parsed;
41
+ }
42
+ throw new Error(`--max-nudges must be a whole number (0 mutes it) or the word "unlimited"; got "${raw}".`);
43
+ }
44
+ /** `null` and an unset cap are "no daily limit"; `0` is a mute; N is "at most N a day". */
45
+ function describeNudgeCap(value) {
46
+ if (value === null || value === undefined)
47
+ return 'no daily limit';
48
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
49
+ return `unreadable (${String(value)})`;
50
+ }
51
+ if (value <= 0)
52
+ return 'muted: it never contacts you';
53
+ return `at most ${value} a day`;
54
+ }
55
+ function durationWords(ms) {
56
+ if (ms < 60_000)
57
+ return `${Math.max(1, Math.round(ms / 1000))} s`;
58
+ const totalMinutes = Math.round(ms / 60_000);
59
+ const days = Math.floor(totalMinutes / 1440);
60
+ const hours = Math.floor((totalMinutes % 1440) / 60);
61
+ const minutes = totalMinutes % 60;
62
+ return [days ? `${days} d` : '', hours ? `${hours} h` : '', minutes ? `${minutes} min` : '']
63
+ .filter(Boolean)
64
+ .join(' ');
65
+ }
66
+ /** The least time the platform leaves between two nudges to you. */
67
+ function describeNudgeGap(ms) {
68
+ if (ms === null || ms === undefined || ms === 0)
69
+ return 'no minimum gap between nudges';
70
+ if (typeof ms !== 'number' || !Number.isFinite(ms) || ms < 0) {
71
+ return `unreadable (${String(ms)})`;
72
+ }
73
+ return `at least ${durationWords(ms)} between nudges`;
74
+ }
75
+ /** The back-off for a person who is not answering. The server's default count is 2. */
76
+ function describeNoReplyRule(noReply) {
77
+ if (noReply?.enabled !== true)
78
+ return 'off: it keeps nudging whether or not you answer';
79
+ const after = Number.isInteger(noReply.afterNudges) ? noReply.afterNudges : 2;
80
+ return `after ${after} unanswered nudge${after === 1 ? '' : 's'} it waits until you reply`;
81
+ }
82
+ /** Read the three settings out of a stored `policy`. */
83
+ function nudgeSettingsFromPolicy(policy) {
84
+ return {
85
+ maxUserNudgesPerDay: policy.maxUserNudgesPerDay,
86
+ minUserNudgeIntervalMs: policy.minUserNudgeIntervalMs,
87
+ noReply: policy.attention?.noReply,
88
+ };
89
+ }
90
+ /**
91
+ * The lines that say how much a Commitment may interrupt its owner. A muted one
92
+ * prints the mute alone: a gap and a back-off on a Commitment that never speaks
93
+ * would only suggest it does. `withNoReply: false` leaves the back-off out when the
94
+ * stored rule could not be read, rather than claim it is off.
95
+ */
96
+ function nudgeSettingLines(settings, { withNoReply = true } = {}) {
97
+ const lines = [`Nudges: ${describeNudgeCap(settings.maxUserNudgesPerDay)}`];
98
+ const muted = typeof settings.maxUserNudgesPerDay === 'number' && settings.maxUserNudgesPerDay <= 0;
99
+ if (muted)
100
+ return lines;
101
+ lines.push(`Min gap: ${describeNudgeGap(settings.minUserNudgeIntervalMs)}`);
102
+ if (withNoReply)
103
+ lines.push(`No reply: ${describeNoReplyRule(settings.noReply)}`);
104
+ return lines;
105
+ }
106
+ const CLOCK = /^(\d{1,2}):(\d{2})$/;
107
+ /** What a time of day is, in the words a person can type. */
108
+ exports.CLOCK_TIME_HINT = 'use HH:MM, 00:00-23:59, for example 22:00';
109
+ /**
110
+ * How to set quiet hours, for the `--from-json` help of `create` and `update`. There is no flag for
111
+ * them, so without this sentence a user had to know the `policy.quietHours` key from outside the CLI
112
+ * (OSK-21123).
113
+ */
114
+ exports.QUIET_HOURS_HELP = 'Quiet hours (no messages to you) go in the JSON as policy.quietHours: ' +
115
+ '{"enabled":true,"start":"22:00","end":"07:00","timezone":"America/Los_Angeles"}. ' +
116
+ 'Times are 24-hour HH:MM (00:00-23:59); the window may cross midnight and its end is not ' +
117
+ 'inside it; timezone is an IANA name, and when omitted it is UTC, not your local zone.';
118
+ /**
119
+ * What `policy.executionBudget.allowedEventSources` means, for the same help. An empty list is the
120
+ * unrestricted case, which is the opposite of what `[]` suggests (OSK-21113).
121
+ */
122
+ exports.EVENT_SOURCES_HELP = 'policy.executionBudget.allowedEventSources limits which event sources may wake the Commitment: ' +
123
+ 'an empty or missing list allows every source (it is not "none"); name sources to restrict it.';
124
+ /**
125
+ * The allowlist in words. An empty or missing list means every source is allowed
126
+ * (`sourceAllowed` in api/server/services/EventBus/commitmentWakeCapabilities.js and the check in
127
+ * WakeIntentResolver.js both treat it so); the stored form of "never set" is the same `[]`.
128
+ */
129
+ function describeEventSources(allowed) {
130
+ return Array.isArray(allowed) && allowed.length
131
+ ? `limited to: ${allowed.map(String).join(', ')}`
132
+ : 'all allowed (the list is empty)';
133
+ }
134
+ function validClock(value) {
135
+ if (typeof value !== 'string')
136
+ return false;
137
+ const match = CLOCK.exec(value);
138
+ return Boolean(match) && Number(match[1]) <= 23 && Number(match[2]) <= 59;
139
+ }
140
+ /**
141
+ * The problem with the quiet-hours times in a `policy` about to be sent, or null. The server
142
+ * answered a bad time with its raw pattern (`must match pattern /^\d{1,2}:\d{2}$/`), which says
143
+ * nothing a person can type, and accepted `25:00` (a window that then never applies). Checked
144
+ * here, so the request never leaves with either.
145
+ */
146
+ function quietHoursInputProblem(policy) {
147
+ const quietHours = policy?.quietHours;
148
+ if (!quietHours || typeof quietHours !== 'object')
149
+ return null;
150
+ for (const key of ['start', 'end']) {
151
+ const value = quietHours[key];
152
+ if (value !== undefined && !validClock(value)) {
153
+ return `policy.quietHours.${key} ${JSON.stringify(value)} is not a time of day: ${exports.CLOCK_TIME_HINT}.`;
154
+ }
155
+ }
156
+ return null;
157
+ }
158
+ /** `9:00` and `09:00` are the same time to the server; show the zero-padded form. */
159
+ function clockText(value) {
160
+ const match = CLOCK.exec(String(value ?? '').trim());
161
+ return match ? `${match[1].padStart(2, '0')}:${match[2]}` : String(value ?? '');
162
+ }
163
+ /** An omitted timezone is UTC, and no text surface said so (OSK-21122). */
164
+ const NO_TIMEZONE_NOTE = 'UTC (no timezone set, UTC is used)';
165
+ /**
166
+ * The Commitment's own quiet-hours window in words. Without one of its own a Commitment uses its
167
+ * Agent's Compass window (`scheduleIssues.js` documents the same rule), so "none set" says where
168
+ * else a window may come from rather than claiming there is no quiet time at all.
169
+ */
170
+ function describeQuietHours(quietHours) {
171
+ if (!quietHours) {
172
+ return "none set on this Commitment (a window in its Agent's Compass applies, if it has one)";
173
+ }
174
+ const zone = quietHours.timezone ? quietHours.timezone : NO_TIMEZONE_NOTE;
175
+ const window = quietHours.start && quietHours.end
176
+ ? `${clockText(quietHours.start)}-${clockText(quietHours.end)} ${zone}`
177
+ : null;
178
+ if (quietHours.enabled !== true) {
179
+ return window ? `off (${window} is saved but switched off)` : 'off';
180
+ }
181
+ if (!window)
182
+ return 'on, but start and end are not both set, so no quiet window applies';
183
+ if (clockText(quietHours.start) === clockText(quietHours.end)) {
184
+ return `${window}: start and end are the same time, so the window is empty and nothing is held`;
185
+ }
186
+ return window;
187
+ }
188
+ /** `2026-11-01T15:00:00.000Z` as `2026-11-01 15:00 UTC`. */
189
+ function utcText(iso) {
190
+ const parsed = typeof iso === 'string' ? new Date(iso) : null;
191
+ if (!parsed || Number.isNaN(parsed.getTime()))
192
+ return null;
193
+ return `${parsed.toISOString().slice(0, 16).replace('T', ' ')} UTC`;
194
+ }
195
+ /**
196
+ * When a held message is released, for the preflight line "Held until your quiet hours end".
197
+ * The report's `quietHours` carries the end as typed, the zone, and the exact instant (`endsAt`,
198
+ * daylight saving included); null when it carries no instant, so the caller keeps its generic
199
+ * sentence rather than inventing a time.
200
+ */
201
+ function describeQuietHoursEnd(quietHours) {
202
+ const utc = utcText(quietHours?.endsAt);
203
+ if (!utc)
204
+ return null;
205
+ const zone = quietHours?.timezone || 'UTC';
206
+ if (zone === 'UTC' || !quietHours?.end)
207
+ return `Held until your quiet hours end: ${utc}.`;
208
+ return `Held until your quiet hours end: ${clockText(quietHours.end)} ${zone}, ${utc}.`;
209
+ }
@@ -10,6 +10,12 @@
10
10
  */
11
11
  import { type CommitmentLoopStatus, type CommitmentRoster } from '@skrr-ai/data-provider';
12
12
  export declare function loopStateLabel(state: unknown): string;
13
+ /**
14
+ * What an empty "next wakes" list means. "Nothing is scheduled" is true only of an unarmed loop: a held
15
+ * wake IS scheduled and is being refused, and the line above it says "armed" (OSK-21261). Unknown stays
16
+ * "could not be read" because cannot-see is not none.
17
+ */
18
+ export declare function noNextWakeText(status: Pick<CommitmentLoopStatus, 'loopState' | 'loopReason'>): string;
13
19
  /** Roster table: one row per Commitment. */
14
20
  export declare function formatCommitmentRoster(roster: Partial<CommitmentRoster>): string[];
15
21
  /**
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loopStateLabel = loopStateLabel;
4
+ exports.noNextWakeText = noNextWakeText;
4
5
  exports.formatCommitmentRoster = formatCommitmentRoster;
5
6
  exports.formatLoopSummary = formatLoopSummary;
6
7
  exports.formatCommitmentLoopStatus = formatCommitmentLoopStatus;
@@ -64,6 +65,19 @@ function spend(status) {
64
65
  // A partial ledger is a lower bound; never print it as the whole answer.
65
66
  return status.cost?.spendKnown ? amount : `≥ ${amount} (some runs unpriced)`;
66
67
  }
68
+ /**
69
+ * What an empty "next wakes" list means. "Nothing is scheduled" is true only of an unarmed loop: a held
70
+ * wake IS scheduled and is being refused, and the line above it says "armed" (OSK-21261). Unknown stays
71
+ * "could not be read" because cannot-see is not none.
72
+ */
73
+ function noNextWakeText(status) {
74
+ if (status.loopState === 'unknown')
75
+ return 'could not be read';
76
+ if (status.loopReason === 'wake_control_blocked' || status.loopReason === 'wake_admission_blocked') {
77
+ return 'none while its wakes are held (see the loop line)';
78
+ }
79
+ return 'none — nothing is scheduled to wake it';
80
+ }
67
81
  function nextWake(status) {
68
82
  const wake = status.nextWakes?.[0];
69
83
  return wake ? `${when(wake.at)} ${wake.kind}` : '-';
@@ -136,9 +150,7 @@ function formatLoopSummary(status) {
136
150
  const wake = status.nextWakes?.[0];
137
151
  lines.push(`next wake: ${wake
138
152
  ? `${when(wake.at)} ${wake.kind}`
139
- : status.loopState === 'unknown'
140
- ? 'could not be read'
141
- : 'none — nothing is scheduled to wake it'}`);
153
+ : noNextWakeText(status)}`);
142
154
  if (status.lastStop) {
143
155
  const stop = status.lastStop;
144
156
  lines.push(`last stop: ${stop.question}${stop.reason ? ` — ${stop.reason}` : ''}${stop.detail ? `: ${stop.detail}` : ''}`);
@@ -189,9 +201,7 @@ function formatCommitmentLoopStatus(status) {
189
201
  const wakes = Array.isArray(status.nextWakes) ? status.nextWakes : [];
190
202
  lines.push(' Next wakes:');
191
203
  if (!wakes.length) {
192
- lines.push(status.loopState === 'unknown'
193
- ? ' (could not be read)'
194
- : ' none — nothing is scheduled to wake it');
204
+ lines.push(status.loopState === 'unknown' ? ' (could not be read)' : ` ${noNextWakeText(status)}`);
195
205
  }
196
206
  for (const wake of wakes) {
197
207
  lines.push(` ${when(wake.at)} ${wake.kind}${wake.reason ? ` (${wake.reason})` : ''}`);
@@ -60,6 +60,12 @@ export type CommitmentPreflightReport = {
60
60
  id: string;
61
61
  label: string;
62
62
  verdict: 'pass' | 'indeterminate' | 'block';
63
+ /**
64
+ * Whether this gate can stop a Run (`blocking`) or only informs (`advisory`). An advisory gate
65
+ * can still say `block` (a Pack that watches its Goal through a link, not a watched source,
66
+ * has "no observation source"); it stops nothing and never feeds the verdict.
67
+ */
68
+ admission?: 'blocking' | 'advisory';
63
69
  /** Short machine code for why a gate did NOT pass. */
64
70
  reason?: string | null;
65
71
  /**
@@ -96,7 +102,29 @@ export type CommitmentPreflightReport = {
96
102
  retired?: string[];
97
103
  retiredReason?: string;
98
104
  } & Record<string, unknown>;
105
+ /**
106
+ * Whether Resume / `create --activate` would be accepted, answered by the server's one activation
107
+ * rule (`CommitmentActivation`). Absent from an older server. A gate can be advisory for a run and
108
+ * still refuse activation, so this is the answer, not the gate list.
109
+ */
110
+ activation?: {
111
+ canActivate?: boolean;
112
+ blockers?: Array<{
113
+ gate: string;
114
+ label?: string;
115
+ reason?: string;
116
+ hint?: string;
117
+ }>;
118
+ } | null;
99
119
  };
120
+ /**
121
+ * The lines preflight prints when the server says activation would be refused (OSK-21258). The gate
122
+ * list marks an advisory gate `info (advisory: does not stop a run)`, which is true of a run and false
123
+ * of activation: `resume` refused the same gate with a 409 while preflight read as if nothing stopped
124
+ * it. The server owns which gates refuse; this lays out its answer and never re-derives it. Nothing
125
+ * for an allowed activation or an older server that does not send the block.
126
+ */
127
+ export declare function activationRefusalLines(activation: CommitmentPreflightReport['activation']): string[];
100
128
  /**
101
129
  * The lines every Commitment read surface prints for "whose permissions does a
102
130
  * run use?" — `show`, `effective-policy` and `preflight` share it so the three
@@ -161,6 +189,8 @@ export type CommitmentPackPreflightResponse = {
161
189
  export type CommitmentPackDefinition = {
162
190
  id: string;
163
191
  version: string;
192
+ /** The Pack's name as the server sends it. `title` is not a field of the catalogue (OSK-21110). */
193
+ label?: string;
164
194
  title?: string;
165
195
  description?: string;
166
196
  [key: string]: unknown;
@@ -179,6 +209,17 @@ export type CommitmentPackPlanResponse = {
179
209
  agentId: string;
180
210
  holder?: 'space_lead' | 'explicit';
181
211
  };
212
+ /**
213
+ * What the plan cannot fix and `pack preflight` would report later: today only a holder whose
214
+ * runtime can never activate this loop. Beside the plan, outside its digest.
215
+ */
216
+ readiness?: Array<{
217
+ gate: string;
218
+ label: string;
219
+ verdict: 'block';
220
+ reason: string;
221
+ hint?: string;
222
+ }>;
182
223
  [key: string]: unknown;
183
224
  };
184
225
  export type CommitmentPackBindResponse = {
@@ -719,6 +760,21 @@ export declare function readCommitmentAgentDirective({ directive, file, }: {
719
760
  directive?: string;
720
761
  file?: string;
721
762
  }): Promise<string | undefined>;
763
+ /**
764
+ * The top-level keys of a `--from-json` body that `create`/`preflight` will not send. A key within two edits
765
+ * of a real one is a `typo`; the rest are `ignored`. A server-owned dump field is neither.
766
+ */
767
+ export declare function unrecognizedCreateKeys(src: Record<string, unknown>): {
768
+ typos: Array<{
769
+ key: string;
770
+ suggestion: string;
771
+ }>;
772
+ ignored: string[];
773
+ };
774
+ /** The keys of a `--from-json` body that are neither sent nor server-owned and not a typo: to be warned about. */
775
+ export declare function ignoredCreateKeys(input: Record<string, unknown>): string[];
776
+ /** The one sentence `create` and `preflight` print for them. */
777
+ export declare function ignoredCreateKeysWarning(keys: string[]): string | null;
722
778
  /**
723
779
  * Normalize a `--from-json` body into something the create endpoint accepts.
724
780
  *
@@ -951,6 +1007,22 @@ export declare function commitmentWriteWarnings(response: unknown, fallback: {
951
1007
  /** One `Warning:` line per authoring warning, after the write's own output. */
952
1008
  export declare function renderCommitmentWriteWarnings(warnings: CommitmentAuthoringWarning[], warn: (line: string) => void): void;
953
1009
  export declare function describeCommitmentRevision(revision: CommitmentRevisionReceipt | undefined): string;
1010
+ /**
1011
+ * A commitment's priority, as one line: `high · weight 70 · from northstar`.
1012
+ *
1013
+ * Linking a commitment to a northstar rewrites its priority (`source: northstar`) and a governor
1014
+ * reprioritization changes it again, so the place a person looks to confirm either is `show`, which
1015
+ * printed everything but this and sent them to `--json`. An unset priority says so rather than
1016
+ * printing nothing.
1017
+ */
1018
+ export declare function describeCommitmentPriority(priority: CommitmentView['priority']): string;
1019
+ /**
1020
+ * The `Compass:` line of a bound commitment. A commitment names its compass (`compassId`) but the
1021
+ * read does not carry that compass's STATUS, and an archived one governs nothing (the commitment
1022
+ * runs on defaults), so the id alone could be read as "governed by it". Only what is known is
1023
+ * stated: the id, and the command that says whether it is active (OSK-21242).
1024
+ */
1025
+ export declare function describeBoundCompass(commitment: Pick<CommitmentView, 'compassId' | 'agentId'>): string;
954
1026
  export declare function renderCommitmentSummary(commitment: CommitmentView, log: (line: string) => void,
955
1027
  /**
956
1028
  * The most recent check, when the caller has one. Optional because pause /
@@ -1011,10 +1083,26 @@ export declare function hypothesisRefusalLines(run: {
1011
1083
  count: number;
1012
1084
  }>;
1013
1085
  }): string[];
1086
+ /**
1087
+ * A wake the Agent asked for and the platform did not arm, in the server's words.
1088
+ * The only record used to be `ledger --json`; the cycle read `advanced` (OSK-21132).
1089
+ */
1090
+ export declare function wakeOutcomeLines(run: {
1091
+ wake?: {
1092
+ status?: string;
1093
+ reason?: string;
1094
+ detail?: string;
1095
+ fix?: string;
1096
+ };
1097
+ }): string[];
1014
1098
  export declare function formatCommitmentExplanation(explanation: Partial<CommitmentExplanationResponse>): string[];
1015
1099
  /**
1016
1100
  * Print the continuation hint for a paged `object:'list'` response — the line
1017
1101
  * that makes history beyond the newest page reachable instead of invisible.
1102
+ *
1103
+ * Text mode ONLY. Under `--json` stdout must be one JSON document, and the same
1104
+ * facts are already in it as `has_more` / `next_cursor`: a caller that prints
1105
+ * this after the JSON makes `| json.load` fail (OSK-21118).
1018
1106
  */
1019
1107
  export declare function renderListPageHint(page: {
1020
1108
  has_more?: boolean;