@skrr-ai/cli 0.1.128 → 0.1.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-command.d.ts +12 -0
- package/dist/base-command.js +65 -0
- package/dist/commands/agenda/trigger/create.js +9 -1
- package/dist/commands/agenda/trigger/list.d.ts +2 -1
- package/dist/commands/agenda/trigger/list.js +55 -6
- package/dist/commands/agents/create.js +3 -1
- package/dist/commands/agents/reflect.d.ts +6 -0
- package/dist/commands/agents/reflect.js +45 -6
- package/dist/commands/agents/schedule-overview.d.ts +17 -0
- package/dist/commands/agents/schedule-overview.js +33 -9
- package/dist/commands/agents/triggers/policy.js +3 -1
- package/dist/commands/browser/persistent-profile.d.ts +22 -0
- package/dist/commands/browser/persistent-profile.js +46 -0
- package/dist/commands/commitments/create.js +3 -1
- package/dist/commands/commitments/effective-policy.js +6 -0
- package/dist/commands/commitments/explain.js +10 -1
- package/dist/commands/commitments/plan.d.ts +14 -0
- package/dist/commands/commitments/plan.js +32 -0
- package/dist/commands/commitments/preflight.d.ts +1 -1
- package/dist/commands/commitments/preflight.js +12 -4
- package/dist/commands/commitments/show.d.ts +9 -0
- package/dist/commands/commitments/show.js +14 -1
- package/dist/commands/compass/revisions.d.ts +6 -2
- package/dist/commands/compass/revisions.js +13 -5
- package/dist/commands/compass/tactic.d.ts +5 -0
- package/dist/commands/compass/tactic.js +14 -0
- package/dist/commands/followups/plans/create.js +1 -1
- package/dist/commands/harnesses/list.js +1 -1
- package/dist/commands/northstars/create.js +6 -1
- package/dist/commands/northstars/governance/configure.js +1 -1
- package/dist/commands/northstars/intents/decide.js +2 -3
- package/dist/commands/northstars/list.js +1 -1
- package/dist/commands/northstars/review-now.js +5 -0
- package/dist/commands/northstars/reviews.js +15 -3
- package/dist/commands/northstars/show.js +1 -1
- package/dist/commands/spaces/auto-executor/runs.d.ts +9 -1
- package/dist/commands/spaces/auto-executor/runs.js +18 -1
- package/dist/commands/spaces/heartbeats/create.js +7 -5
- package/dist/commands/spaces/heartbeats/status.js +10 -0
- package/dist/commands/spaces/heartbeats/update.js +5 -3
- package/dist/commands/store/artifacts/releases.d.ts +20 -0
- package/dist/commands/store/artifacts/releases.js +85 -0
- package/dist/commands/store/library.d.ts +14 -0
- package/dist/commands/store/library.js +71 -0
- package/dist/commands/tasks/archive.js +6 -0
- package/dist/commands/tasks/list.d.ts +48 -0
- package/dist/commands/tasks/list.js +141 -9
- package/dist/commands/tasks/pause-schedule.js +1 -1
- package/dist/commands/tasks/remind.js +23 -11
- package/dist/commands/tasks/resume-schedule.js +4 -1
- package/dist/commands/tasks/retry-schedule.js +6 -3
- package/dist/commands/tasks/schedule.js +35 -8
- package/dist/commands/tasks/self-schedule.d.ts +56 -0
- package/dist/commands/tasks/self-schedule.js +179 -21
- package/dist/commands/tasks/show.js +4 -0
- package/dist/lib/agent-trigger-listing.d.ts +26 -0
- package/dist/lib/agent-trigger-listing.js +63 -0
- package/dist/lib/agentic-stream.js +138 -41
- package/dist/lib/commitment-ledgers.js +3 -1
- package/dist/lib/commitments.d.ts +24 -0
- package/dist/lib/commitments.js +64 -4
- package/dist/lib/compasses.d.ts +2 -0
- package/dist/lib/iso-instant.d.ts +52 -0
- package/dist/lib/iso-instant.js +162 -0
- package/dist/lib/northstars.d.ts +87 -4
- package/dist/lib/northstars.js +226 -94
- package/dist/lib/proposals.js +6 -2
- package/dist/lib/reflection.d.ts +58 -0
- package/dist/lib/reflection.js +115 -6
- package/dist/lib/store-artifact-releases.d.ts +39 -0
- package/dist/lib/store-artifact-releases.js +12 -0
- package/dist/lib/store-library.d.ts +37 -0
- package/dist/lib/store-library.js +12 -0
- package/dist/lib/task-calendar.d.ts +19 -0
- package/dist/lib/task-calendar.js +72 -0
- package/dist/lib/task-operations.js +2 -0
- package/dist/lib/task-recurrence-outcome.js +3 -1
- package/dist/lib/task-recurrence-summary.d.ts +15 -0
- package/dist/lib/task-recurrence-summary.js +228 -0
- package/dist/lib/task-reminder.d.ts +28 -0
- package/dist/lib/task-reminder.js +50 -0
- package/dist/lib/task-schedule-notes.d.ts +46 -0
- package/dist/lib/task-schedule-notes.js +71 -0
- package/dist/lib/triggers.d.ts +31 -2
- package/dist/lib/triggers.js +38 -18
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +11 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +9 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.d.ts +123 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/executorStopReceipt.js +160 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +19 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +11 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +9 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +28 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.d.ts +123 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/executorStopReceipt.js +173 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +6772 -6675
- package/oclif.manifest.json +282 -40
- package/package.json +1 -1
package/dist/lib/reflection.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.REFLECTION_PROMPT = exports.MAX_REFLECTION_CADENCE_MINUTES = exports.MIN_REFLECTION_CADENCE_MINUTES = exports.DEFAULT_REFLECTION_CADENCE_MINUTES = exports.REFLECTION_LABEL = void 0;
|
|
3
|
+
exports.InvalidCadenceError = exports.REFLECTION_PROMPT = exports.MAX_REFLECTION_CADENCE_MINUTES = exports.MIN_REFLECTION_CADENCE_MINUTES = exports.DEFAULT_REFLECTION_CADENCE_MINUTES = exports.REFLECTION_LABEL = void 0;
|
|
4
4
|
exports.parseCadenceToMinutes = parseCadenceToMinutes;
|
|
5
5
|
exports.clampCadenceMinutes = clampCadenceMinutes;
|
|
6
|
+
exports.applyCadenceBounds = applyCadenceBounds;
|
|
7
|
+
exports.describeCadenceClamp = describeCadenceClamp;
|
|
6
8
|
exports.describeCadenceMinutes = describeCadenceMinutes;
|
|
7
9
|
exports.findReflectionTrigger = findReflectionTrigger;
|
|
8
10
|
exports.reflectionCadenceMinutes = reflectionCadenceMinutes;
|
|
11
|
+
exports.reflectionNextRunLabel = reflectionNextRunLabel;
|
|
12
|
+
exports.reflectionAutonomy = reflectionAutonomy;
|
|
13
|
+
exports.reflectionAutonomyNote = reflectionAutonomyNote;
|
|
14
|
+
exports.reflectionAutonomyFields = reflectionAutonomyFields;
|
|
9
15
|
exports.buildReflectionTriggerPayload = buildReflectionTriggerPayload;
|
|
10
16
|
/**
|
|
11
17
|
* reflection.ts — the agent self-reflection ("meta cadence") loop.
|
|
@@ -52,9 +58,42 @@ exports.REFLECTION_PROMPT = `[Self-reflection] Step back and review your own sch
|
|
|
52
58
|
4. Fix what is slipping: reschedule, create, or cancel triggers and tasks so your plan matches reality.
|
|
53
59
|
|
|
54
60
|
Be deliberate and minimal. Every trigger you keep spends the user's compute — keep only what earns its place. If nothing needs changing, do nothing and end the turn.`;
|
|
61
|
+
/**
|
|
62
|
+
* A cadence the caller got wrong: unparseable, empty, or not a positive number of
|
|
63
|
+
* minutes. Mirrors `InvalidCadenceError` in the server copy, minus its 4xx `status`:
|
|
64
|
+
* that field is how an MCP server tells the caller's mistake from its own failure,
|
|
65
|
+
* and on the CLI the same field would make `handleApiError` read this as an HTTP
|
|
66
|
+
* response. Commands catch it and report it as a usage error.
|
|
67
|
+
*/
|
|
68
|
+
class InvalidCadenceError extends Error {
|
|
69
|
+
code = 'INVALID_CADENCE';
|
|
70
|
+
constructor(message) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.name = 'InvalidCadenceError';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.InvalidCadenceError = InvalidCadenceError;
|
|
76
|
+
/**
|
|
77
|
+
* A cadence must be a positive, finite number of minutes. `0` is not "off" and not "the
|
|
78
|
+
* minimum": rounding it up to the floor turned an attempt to switch the loop off into one
|
|
79
|
+
* that wakes the agent 96 times a day (OSK-21493).
|
|
80
|
+
*/
|
|
81
|
+
function requirePositiveMinutes(minutes, raw) {
|
|
82
|
+
if (!Number.isFinite(minutes)) {
|
|
83
|
+
throw new InvalidCadenceError(`That cadence is too large to be a number of minutes. Pick between ${describeCadenceMinutes(exports.MIN_REFLECTION_CADENCE_MINUTES)} and ${describeCadenceMinutes(exports.MAX_REFLECTION_CADENCE_MINUTES)}.`);
|
|
84
|
+
}
|
|
85
|
+
if (minutes <= 0) {
|
|
86
|
+
const shown = raw.length > 24 ? `${raw.slice(0, 24)}…` : raw;
|
|
87
|
+
throw new InvalidCadenceError(`"${shown}" is not a cadence — it cannot mean "off". To pause the loop, disable it instead ` +
|
|
88
|
+
'(skrr agents reflect <agent> --disable, or the self_reflection_disable tool); otherwise ' +
|
|
89
|
+
`pick between ${describeCadenceMinutes(exports.MIN_REFLECTION_CADENCE_MINUTES)} and ${describeCadenceMinutes(exports.MAX_REFLECTION_CADENCE_MINUTES)}.`);
|
|
90
|
+
}
|
|
91
|
+
return minutes;
|
|
92
|
+
}
|
|
55
93
|
/**
|
|
56
94
|
* Parse a human cadence into minutes. Accepts `6h`, `90m`, `2h30m`, `1d`, or a
|
|
57
|
-
* bare number (interpreted as minutes). Throws on
|
|
95
|
+
* bare number (interpreted as minutes). Throws `InvalidCadenceError` on
|
|
96
|
+
* unparseable input.
|
|
58
97
|
*/
|
|
59
98
|
// Longest-first alternation, and CADENCE_FULL is anchored so the WHOLE string
|
|
60
99
|
// must be consumed. Without the anchor, "1 month" matches `1` + `m` and silently
|
|
@@ -67,14 +106,14 @@ function parseCadenceToMinutes(input) {
|
|
|
67
106
|
.trim()
|
|
68
107
|
.toLowerCase();
|
|
69
108
|
if (!raw) {
|
|
70
|
-
throw new
|
|
109
|
+
throw new InvalidCadenceError('Empty cadence. Use a form like "6h", "90m", "2h30m", or "360".');
|
|
71
110
|
}
|
|
72
111
|
// Bare number → minutes.
|
|
73
112
|
if (/^\d+(\.\d+)?$/.test(raw)) {
|
|
74
|
-
return Math.round(Number(raw));
|
|
113
|
+
return requirePositiveMinutes(Math.round(Number(raw)), raw);
|
|
75
114
|
}
|
|
76
115
|
if (!CADENCE_FULL.test(raw)) {
|
|
77
|
-
throw new
|
|
116
|
+
throw new InvalidCadenceError(`Could not parse cadence "${input}". Use minutes/hours/days — e.g. "6h", "90m", "2h30m", "360". ` +
|
|
78
117
|
'(Months and seconds are not units here.)');
|
|
79
118
|
}
|
|
80
119
|
let total = 0;
|
|
@@ -90,12 +129,39 @@ function parseCadenceToMinutes(input) {
|
|
|
90
129
|
else
|
|
91
130
|
total += n;
|
|
92
131
|
}
|
|
93
|
-
return Math.round(total);
|
|
132
|
+
return requirePositiveMinutes(Math.round(total), raw);
|
|
94
133
|
}
|
|
95
134
|
/** Clamp a cadence to the allowed reflection window. */
|
|
96
135
|
function clampCadenceMinutes(minutes) {
|
|
97
136
|
return Math.max(exports.MIN_REFLECTION_CADENCE_MINUTES, Math.min(exports.MAX_REFLECTION_CADENCE_MINUTES, Math.round(minutes)));
|
|
98
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Apply the reflection window to a requested cadence and SAY what happened, so every
|
|
140
|
+
* caller can report "asked 5m, applied 15m" instead of only the clamped number
|
|
141
|
+
* (OSK-21486, OSK-21493, OSK-21602). `bound` names the limit that moved it.
|
|
142
|
+
*/
|
|
143
|
+
function applyCadenceBounds(requestedMinutes) {
|
|
144
|
+
const requested = Math.round(requestedMinutes);
|
|
145
|
+
const minutes = clampCadenceMinutes(requested);
|
|
146
|
+
let bound = null;
|
|
147
|
+
if (minutes > requested)
|
|
148
|
+
bound = 'minimum';
|
|
149
|
+
else if (minutes < requested)
|
|
150
|
+
bound = 'maximum';
|
|
151
|
+
return { minutes, requestedMinutes: requested, clamped: bound !== null, bound };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* One sentence for a clamp ("5m is below the 15m minimum; applied 15m."), or null when
|
|
155
|
+
* the request was inside the window. Shared by the MCP result `note` and the CLI line.
|
|
156
|
+
*/
|
|
157
|
+
function describeCadenceClamp({ minutes, requestedMinutes, bound, }) {
|
|
158
|
+
if (!bound)
|
|
159
|
+
return null;
|
|
160
|
+
const limit = bound === 'minimum' ? exports.MIN_REFLECTION_CADENCE_MINUTES : exports.MAX_REFLECTION_CADENCE_MINUTES;
|
|
161
|
+
const side = bound === 'minimum' ? 'below' : 'above';
|
|
162
|
+
return (`${describeCadenceMinutes(requestedMinutes)} is ${side} the ${describeCadenceMinutes(limit)} ` +
|
|
163
|
+
`${bound}; applied ${describeCadenceMinutes(minutes)}.`);
|
|
164
|
+
}
|
|
99
165
|
/** Render minutes back as a compact human cadence ("6h", "90m", "1d"). */
|
|
100
166
|
function describeCadenceMinutes(minutes) {
|
|
101
167
|
if (minutes > 0 && minutes % (60 * 24) === 0)
|
|
@@ -113,6 +179,49 @@ function reflectionCadenceMinutes(trigger) {
|
|
|
113
179
|
const ms = trigger.schedule?.intervalMs;
|
|
114
180
|
return typeof ms === 'number' && Number.isFinite(ms) ? Math.round(ms / 60_000) : undefined;
|
|
115
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* The loop's next run, for display: its scheduled time while it is enabled, else `-`. A
|
|
184
|
+
* paused loop has no next run, and a loop with nothing scheduled does not have its LAST fire
|
|
185
|
+
* promoted to one (the server copy keeps `nextRunAt` and `lastFiredAt` apart the same way).
|
|
186
|
+
*/
|
|
187
|
+
function reflectionNextRunLabel(trigger) {
|
|
188
|
+
return (0, triggers_1.scheduledRunAt)(trigger) ?? '-';
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Whether the loop's fires run, as the SERVER says it (`GET /api/triggers?include=autonomy`), or
|
|
192
|
+
* null when it did not say. Nothing is derived here: the verdict depends on the agent's
|
|
193
|
+
* initiative mode and on which door created the loop, and a local copy of that judgement is free
|
|
194
|
+
* to disagree with the fire path (OSK-21606). A server that predates the field, or one that could
|
|
195
|
+
* not reach a verdict, sends nothing, and that is UNKNOWN, never `runs`.
|
|
196
|
+
*/
|
|
197
|
+
function reflectionAutonomy(trigger) {
|
|
198
|
+
if (trigger.enabled === false)
|
|
199
|
+
return null;
|
|
200
|
+
const { autonomy, autonomyNote } = trigger;
|
|
201
|
+
if (autonomy !== 'runs' && autonomy !== 'suggests_only')
|
|
202
|
+
return null;
|
|
203
|
+
const note = typeof autonomyNote === 'string' && autonomyNote.trim() ? autonomyNote.trim() : '';
|
|
204
|
+
return { autonomy, ...(note ? { note } : {}) };
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The one plain sentence a text view prints under the loop when its fires will not run for the
|
|
208
|
+
* owner; null when they do, when the server did not say, or when the loop is paused (a paused
|
|
209
|
+
* loop fires nothing, so the gate is not what the reader needs to hear about).
|
|
210
|
+
*/
|
|
211
|
+
function reflectionAutonomyNote(trigger, bin) {
|
|
212
|
+
const verdict = reflectionAutonomy(trigger);
|
|
213
|
+
if (!verdict || verdict.autonomy !== 'suggests_only')
|
|
214
|
+
return null;
|
|
215
|
+
return (verdict.note ??
|
|
216
|
+
`This loop's fires only create an inbox suggestion. Run ${bin} triggers preflight ${trigger.id ?? '<trigger-id>'} to see why.`);
|
|
217
|
+
}
|
|
218
|
+
/** The `--json` fields for the same verdict: absent when unknown, so absence reads as unknown. */
|
|
219
|
+
function reflectionAutonomyFields(trigger) {
|
|
220
|
+
const verdict = reflectionAutonomy(trigger);
|
|
221
|
+
if (!verdict)
|
|
222
|
+
return {};
|
|
223
|
+
return { autonomy: verdict.autonomy, ...(verdict.note ? { note: verdict.note } : {}) };
|
|
224
|
+
}
|
|
116
225
|
/**
|
|
117
226
|
* Build the `POST /api/triggers` body for a fresh self-reflection loop. Uses the
|
|
118
227
|
* proven `scope:agent + kind:schedule + mode:run_only` shape and coalesces so a
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/api/store/artifacts/skill/:entityId/releases` — the seller's own
|
|
3
|
+
* publication history for one skill, newest release first.
|
|
4
|
+
*
|
|
5
|
+
* It answers the question a headless seller has after `store artifacts release`:
|
|
6
|
+
* where did my releases land, and did a curator admit or refuse one (with the
|
|
7
|
+
* note they left)? The source snapshot and its private storage references are
|
|
8
|
+
* never in this projection. One release in full is `store artifacts receipt`.
|
|
9
|
+
*
|
|
10
|
+
* The route is written out at its own call site rather than composed from a
|
|
11
|
+
* builder, like the rest of the Store CLI: the parity checker reads the LITERAL
|
|
12
|
+
* at the call site.
|
|
13
|
+
*/
|
|
14
|
+
export type StoreArtifactReleaseSummary = {
|
|
15
|
+
releaseId: string;
|
|
16
|
+
releaseNumber: number;
|
|
17
|
+
versionLabel?: string;
|
|
18
|
+
status?: string;
|
|
19
|
+
admission?: {
|
|
20
|
+
state?: string;
|
|
21
|
+
review?: {
|
|
22
|
+
note?: string;
|
|
23
|
+
decidedAt?: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
createdAt?: string;
|
|
27
|
+
updatedAt?: string;
|
|
28
|
+
publishedAt?: string;
|
|
29
|
+
};
|
|
30
|
+
export type StoreArtifactReleaseHistory = {
|
|
31
|
+
data: StoreArtifactReleaseSummary[];
|
|
32
|
+
/** Pass as `before` for the next (older) page; null when this is the last. */
|
|
33
|
+
nextBefore: number | null;
|
|
34
|
+
};
|
|
35
|
+
/** `GET /artifacts/skill/:entityId/releases` — `limit` 1-50 (default 10), `before` a release number. */
|
|
36
|
+
export declare function listSkillArtifactReleases(entityId: string, params?: {
|
|
37
|
+
limit?: number;
|
|
38
|
+
before?: number;
|
|
39
|
+
}): Promise<StoreArtifactReleaseHistory>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listSkillArtifactReleases = listSkillArtifactReleases;
|
|
4
|
+
const api_fetch_1 = require("./api-fetch");
|
|
5
|
+
const triggers_1 = require("./triggers");
|
|
6
|
+
/** `GET /artifacts/skill/:entityId/releases` — `limit` 1-50 (default 10), `before` a release number. */
|
|
7
|
+
async function listSkillArtifactReleases(entityId, params = {}) {
|
|
8
|
+
return (0, api_fetch_1.apiFetch)((0, triggers_1.withQuery)(`/api/store/artifacts/skill/${encodeURIComponent(entityId)}/releases`, {
|
|
9
|
+
limit: params.limit,
|
|
10
|
+
before: params.before,
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/api/store/library/items` — the Skill, Action and Service copies the signed-in
|
|
3
|
+
* buyer holds in the active workspace, with whether each copy can still be used.
|
|
4
|
+
*
|
|
5
|
+
* Agent purchases are NOT here: `skrr store installs` reads those. This is the
|
|
6
|
+
* other three artifact kinds, which the web Library page lists and which had no
|
|
7
|
+
* headless read at all.
|
|
8
|
+
*
|
|
9
|
+
* The route is written out at its own call site rather than composed from a
|
|
10
|
+
* builder, like the rest of the Store CLI: the parity checker reads the LITERAL
|
|
11
|
+
* at the call site.
|
|
12
|
+
*/
|
|
13
|
+
/** One bought copy. `copyAvailable` is false once the copy is suspended, revoked or uninstalled. */
|
|
14
|
+
export type StoreLibraryItem = {
|
|
15
|
+
/** The listing or release the buyer bought. */
|
|
16
|
+
sourceId: string;
|
|
17
|
+
/** The buyer's own installed copy; null while fulfillment has not produced one. */
|
|
18
|
+
copyId: string | null;
|
|
19
|
+
name: string | null;
|
|
20
|
+
copyAvailable: boolean;
|
|
21
|
+
/** The Agent the copy was installed onto (Skills and Actions only). */
|
|
22
|
+
targetAgentId?: string | null;
|
|
23
|
+
entitlementState?: string;
|
|
24
|
+
fulfillmentState?: string;
|
|
25
|
+
purchasedAt?: string;
|
|
26
|
+
};
|
|
27
|
+
export type StoreLibraryItems = {
|
|
28
|
+
skills: StoreLibraryItem[];
|
|
29
|
+
actions: StoreLibraryItem[];
|
|
30
|
+
services: StoreLibraryItem[];
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* `GET /library/items`. The workspace is required by the server (400
|
|
34
|
+
* `A workspace is required` without one), so it travels as the explicit
|
|
35
|
+
* header, the way `store installs` sends it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getStoreLibraryItems(workspaceId?: string): Promise<StoreLibraryItems>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getStoreLibraryItems = getStoreLibraryItems;
|
|
4
|
+
const api_fetch_1 = require("./api-fetch");
|
|
5
|
+
/**
|
|
6
|
+
* `GET /library/items`. The workspace is required by the server (400
|
|
7
|
+
* `A workspace is required` without one), so it travels as the explicit
|
|
8
|
+
* header, the way `store installs` sends it.
|
|
9
|
+
*/
|
|
10
|
+
async function getStoreLibraryItems(workspaceId) {
|
|
11
|
+
return (0, api_fetch_1.apiFetch)('/api/store/library/items', workspaceId ? { headers: { 'X-Workspace-Id': workspaceId } } : undefined);
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `skrr tasks list --calendar` tells a person about the answer it printed.
|
|
3
|
+
*
|
|
4
|
+
* The calendar read used to end with "Note: calendar occurrence expansion reached its safety cap."
|
|
5
|
+
* whatever had been cut, and "No calendar occurrences." whatever had been missed (OSK-21382,
|
|
6
|
+
* OSK-21510): no limit, no cutoff, no Task, no remedy, and the server's warning codes were never
|
|
7
|
+
* shown. The server now says which bounds it reached (`truncations`) and which access warnings
|
|
8
|
+
* apply; this words them.
|
|
9
|
+
*/
|
|
10
|
+
import type { TaskCalendarResponse, TaskCalendarTruncation } from '@skrr-ai/data-provider';
|
|
11
|
+
export declare function describeCalendarTruncation(truncation: TaskCalendarTruncation): string;
|
|
12
|
+
export declare function describeCalendarWarning(code: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* The lines to print after a calendar answer: empty when the read was complete. A calendar that
|
|
15
|
+
* was cut short never reads as complete, and every cut says what it was.
|
|
16
|
+
*/
|
|
17
|
+
export declare function calendarNoticeLines(calendar: TaskCalendarResponse): string[];
|
|
18
|
+
/** The window that was actually read, in UTC, and what a date-only range stood for. */
|
|
19
|
+
export declare function calendarWindowLine(calendar: Pick<TaskCalendarResponse, 'from' | 'to'>): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeCalendarTruncation = describeCalendarTruncation;
|
|
4
|
+
exports.describeCalendarWarning = describeCalendarWarning;
|
|
5
|
+
exports.calendarNoticeLines = calendarNoticeLines;
|
|
6
|
+
exports.calendarWindowLine = calendarWindowLine;
|
|
7
|
+
const who = (t) => t.ref || t.taskId || 'A task';
|
|
8
|
+
/**
|
|
9
|
+
* One sentence per bound. A `Record` over the shared vocabulary, so a code added to
|
|
10
|
+
* `TASK_CALENDAR_TRUNCATION_CODES` cannot be forgotten here.
|
|
11
|
+
*/
|
|
12
|
+
const TRUNCATION_TEXT = {
|
|
13
|
+
task_occurrence_limit: (t) => `${who(t)} fires more than ${t.limit} times in this window (the limit is ${t.limit} per task)` +
|
|
14
|
+
`${t.cutoffAt ? `; its fires are listed up to ${t.cutoffAt} and later ones are not shown` : ''}. ` +
|
|
15
|
+
'Narrow the range to see the rest.',
|
|
16
|
+
task_skipped_receipt_limit: (t) => `${who(t)} has more than ${t.limit} skipped-fire receipts in this window; only the latest ${t.limit} are shown.`,
|
|
17
|
+
task_recurrence_walk_limit: (t) => `${who(t)} has a run limit too far from this window to count what remains of it (more than ${t.limit} fires walked), so its fires in this window are not shown. ` +
|
|
18
|
+
'Narrow the range to start closer to its next run.',
|
|
19
|
+
calendar_row_limit: (t) => `More than ${t.limit} rows matched${typeof t.omitted === 'number' ? `; ${t.omitted} are not shown` : ''}` +
|
|
20
|
+
`${t.cutoffAt ? `, and the rows are listed only up to ${t.cutoffAt}` : ''}. ` +
|
|
21
|
+
'Narrow the range, or add --space or --project.',
|
|
22
|
+
task_candidate_limit: (t) => `More than ${t.limit} tasks are candidates for this window and not all were read. Narrow the range, or add --space or --project.`,
|
|
23
|
+
};
|
|
24
|
+
function describeCalendarTruncation(truncation) {
|
|
25
|
+
const words = TRUNCATION_TEXT[truncation.code];
|
|
26
|
+
// A code a newer server sent: say it rather than drop it.
|
|
27
|
+
return words
|
|
28
|
+
? words(truncation)
|
|
29
|
+
: `The server reached a limit (${String(truncation.code)}, ${truncation.limit}) and did not describe it.`;
|
|
30
|
+
}
|
|
31
|
+
const WARNING_TEXT = {
|
|
32
|
+
space_cascade_capped: 'You can reach more spaces than one read covers, so tasks you did not create in the rest are not listed. Narrow with --space.',
|
|
33
|
+
space_access_capped: 'You can reach more spaces than one read covers, so tasks in the rest are not listed. Narrow with --space.',
|
|
34
|
+
};
|
|
35
|
+
function describeCalendarWarning(code) {
|
|
36
|
+
if (WARNING_TEXT[code])
|
|
37
|
+
return WARNING_TEXT[code];
|
|
38
|
+
if (code.endsWith('_partial')) {
|
|
39
|
+
return 'An access lookup failed, so some tasks may be missing. Retry the command.';
|
|
40
|
+
}
|
|
41
|
+
return `The server flagged this answer (${code}); it may be incomplete.`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The lines to print after a calendar answer: empty when the read was complete. A calendar that
|
|
45
|
+
* was cut short never reads as complete, and every cut says what it was.
|
|
46
|
+
*/
|
|
47
|
+
function calendarNoticeLines(calendar) {
|
|
48
|
+
const truncations = calendar.truncations ?? [];
|
|
49
|
+
const warnings = calendar.warnings ?? [];
|
|
50
|
+
const lines = [];
|
|
51
|
+
if (truncations.length > 0) {
|
|
52
|
+
lines.push('The calendar is incomplete:');
|
|
53
|
+
for (const truncation of truncations)
|
|
54
|
+
lines.push(` - ${describeCalendarTruncation(truncation)}`);
|
|
55
|
+
}
|
|
56
|
+
else if (calendar.truncated && warnings.length === 0) {
|
|
57
|
+
// A server that predates `truncations` flagged the answer and said nothing more.
|
|
58
|
+
lines.push('The calendar is incomplete: the server cut it short but did not say which limit it reached. Narrow the range, or add --space.');
|
|
59
|
+
}
|
|
60
|
+
if (warnings.length > 0) {
|
|
61
|
+
if (lines.length === 0)
|
|
62
|
+
lines.push('The calendar may be incomplete:');
|
|
63
|
+
for (const code of warnings) {
|
|
64
|
+
lines.push(` - Warning ${code}: ${describeCalendarWarning(code)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return lines;
|
|
68
|
+
}
|
|
69
|
+
/** The window that was actually read, in UTC, and what a date-only range stood for. */
|
|
70
|
+
function calendarWindowLine(calendar) {
|
|
71
|
+
return `Window (UTC): ${calendar.from} .. ${calendar.to}. A date-only range is a whole UTC day.`;
|
|
72
|
+
}
|
|
@@ -28,6 +28,8 @@ function taskActionCommand(action, taskId, targetId, bin = 'skrr', context = {})
|
|
|
28
28
|
request_approval: `tasks approvals create ${task} --type <review|sign_off|deployment>`,
|
|
29
29
|
resolve_approval: `tasks approvals resolve ${task} ${target} --status <approved|rejected>`,
|
|
30
30
|
resolve_task_hold: `tasks move ${task} --status <status>`,
|
|
31
|
+
resume_schedule: `tasks resume-schedule ${task}`,
|
|
32
|
+
retry_schedule: `tasks retry-schedule ${task} --at <time>`,
|
|
31
33
|
retry_task: `tasks retry ${task}`,
|
|
32
34
|
set_execution_mode: `tasks update ${task} --execution-mode <agent|human>`,
|
|
33
35
|
start_task: `tasks start ${task}`,
|
|
@@ -22,7 +22,9 @@ function recurrenceRearmHint(code, taskRef = '<task-id>', bin = 'skrr') {
|
|
|
22
22
|
}
|
|
23
23
|
const ENDED_BECAUSE = {
|
|
24
24
|
max_runs: 'it reached its maximum number of runs',
|
|
25
|
-
|
|
25
|
+
// The end date is exclusive: it ends the series when it has passed AND when the next slot would
|
|
26
|
+
// land on or after it, so "has passed" would be false for a series ended one day early (OSK-21552).
|
|
27
|
+
end_date: 'its end date leaves no further occurrence',
|
|
26
28
|
no_next_occurrence: 'its pattern has no further occurrence',
|
|
27
29
|
};
|
|
28
30
|
/** One line, or null when the Task does not recur (or an older server did not say). */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type CronReading = {
|
|
2
|
+
/** "at 09:00 on the 13th of the month or on Friday" */
|
|
3
|
+
text: string;
|
|
4
|
+
/** True when both a day of month and a weekday are set: cron fires on EITHER. */
|
|
5
|
+
dayOfMonthOrWeekday: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Plain language for a five-field cron pattern, or `null` when this reader cannot say it faithfully.
|
|
9
|
+
*/
|
|
10
|
+
export declare function describeCronPattern(pattern: string): CronReading | null;
|
|
11
|
+
/**
|
|
12
|
+
* The block `tasks show` prints for a recurring Task: the rule, its zone, a plain reading, when it
|
|
13
|
+
* next fires, how many runs it has used, and when it ends. Empty for a Task that does not recur.
|
|
14
|
+
*/
|
|
15
|
+
export declare function recurrenceSummaryLines(task: Record<string, unknown>): string[];
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeCronPattern = describeCronPattern;
|
|
4
|
+
exports.recurrenceSummaryLines = recurrenceSummaryLines;
|
|
5
|
+
/**
|
|
6
|
+
* What `skrr tasks show` says about a Task that recurs (OSK-21531).
|
|
7
|
+
*
|
|
8
|
+
* The human view printed "it is scheduled for 2026-12-01" and nothing else: no word that the Task
|
|
9
|
+
* recurs, no pattern, no zone. The rule lived only in `--json` (`recurrence.pattern`, `.timezone`,
|
|
10
|
+
* `.runsCompleted`), so the cron semantics that surprise people - a day of month AND a weekday
|
|
11
|
+
* fire on EITHER, not both - were invisible exactly where someone checks what they just set up.
|
|
12
|
+
*
|
|
13
|
+
* `describeCronPattern` reads the five-field form the scheduler runs. It is deliberately modest: a
|
|
14
|
+
* pattern it cannot read plainly (names it does not know, `L`, `?`, six fields) returns `null` and
|
|
15
|
+
* the caller prints the raw pattern alone rather than a confident wrong sentence.
|
|
16
|
+
*/
|
|
17
|
+
const iso_instant_1 = require("./iso-instant");
|
|
18
|
+
const WEEKDAYS = [
|
|
19
|
+
'Sunday',
|
|
20
|
+
'Monday',
|
|
21
|
+
'Tuesday',
|
|
22
|
+
'Wednesday',
|
|
23
|
+
'Thursday',
|
|
24
|
+
'Friday',
|
|
25
|
+
'Saturday',
|
|
26
|
+
];
|
|
27
|
+
const MONTHS = [
|
|
28
|
+
'January',
|
|
29
|
+
'February',
|
|
30
|
+
'March',
|
|
31
|
+
'April',
|
|
32
|
+
'May',
|
|
33
|
+
'June',
|
|
34
|
+
'July',
|
|
35
|
+
'August',
|
|
36
|
+
'September',
|
|
37
|
+
'October',
|
|
38
|
+
'November',
|
|
39
|
+
'December',
|
|
40
|
+
];
|
|
41
|
+
const MINUTE = { min: 0, max: 59 };
|
|
42
|
+
const HOUR = { min: 0, max: 23 };
|
|
43
|
+
const DAY_OF_MONTH = { min: 1, max: 31 };
|
|
44
|
+
const MONTH = {
|
|
45
|
+
min: 1,
|
|
46
|
+
max: 12,
|
|
47
|
+
names: MONTHS.map((m) => m.slice(0, 3).toUpperCase()),
|
|
48
|
+
nameBase: 1,
|
|
49
|
+
};
|
|
50
|
+
const DAY_OF_WEEK = {
|
|
51
|
+
min: 0,
|
|
52
|
+
max: 7,
|
|
53
|
+
names: WEEKDAYS.map((d) => d.slice(0, 3).toUpperCase()),
|
|
54
|
+
nameBase: 0,
|
|
55
|
+
};
|
|
56
|
+
/** The values a field names, or `null` when it is not a form this reader understands. */
|
|
57
|
+
function expandField(raw, domain) {
|
|
58
|
+
const values = new Set();
|
|
59
|
+
for (const part of raw.split(',')) {
|
|
60
|
+
const [rangeText, stepText, extra] = part.split('/');
|
|
61
|
+
if (extra !== undefined || rangeText === undefined || rangeText === '')
|
|
62
|
+
return null;
|
|
63
|
+
const step = stepText === undefined ? 1 : Number(stepText);
|
|
64
|
+
if (!Number.isInteger(step) || step < 1)
|
|
65
|
+
return null;
|
|
66
|
+
const toNumber = (token) => {
|
|
67
|
+
if (/^\d+$/.test(token))
|
|
68
|
+
return Number(token);
|
|
69
|
+
const index = domain.names?.indexOf(token.toUpperCase()) ?? -1;
|
|
70
|
+
return index >= 0 ? index + (domain.nameBase ?? 0) : null;
|
|
71
|
+
};
|
|
72
|
+
let from;
|
|
73
|
+
let to;
|
|
74
|
+
if (rangeText === '*') {
|
|
75
|
+
from = domain.min;
|
|
76
|
+
to = domain === DAY_OF_WEEK ? 6 : domain.max;
|
|
77
|
+
}
|
|
78
|
+
else if (rangeText.includes('-')) {
|
|
79
|
+
const [a, b, more] = rangeText.split('-');
|
|
80
|
+
if (more !== undefined)
|
|
81
|
+
return null;
|
|
82
|
+
from = toNumber(a);
|
|
83
|
+
to = toNumber(b);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
from = toNumber(rangeText);
|
|
87
|
+
// `5/10` runs from 5 to the top of the field.
|
|
88
|
+
to = stepText === undefined ? from : domain === DAY_OF_WEEK ? 6 : domain.max;
|
|
89
|
+
}
|
|
90
|
+
if (from === null || to === null || from > to)
|
|
91
|
+
return null;
|
|
92
|
+
if (from < domain.min || to > domain.max)
|
|
93
|
+
return null;
|
|
94
|
+
for (let value = from; value <= to; value += step)
|
|
95
|
+
values.add(domain === DAY_OF_WEEK && value === 7 ? 0 : value);
|
|
96
|
+
}
|
|
97
|
+
return [...values].sort((a, b) => a - b);
|
|
98
|
+
}
|
|
99
|
+
function ordinal(n) {
|
|
100
|
+
const tail = n % 100;
|
|
101
|
+
if (tail >= 11 && tail <= 13)
|
|
102
|
+
return `${n}th`;
|
|
103
|
+
return `${n}${{ 1: 'st', 2: 'nd', 3: 'rd' }[n % 10] ?? 'th'}`;
|
|
104
|
+
}
|
|
105
|
+
function joinList(items, word = 'and') {
|
|
106
|
+
if (items.length <= 1)
|
|
107
|
+
return items.join('');
|
|
108
|
+
return `${items.slice(0, -1).join(', ')} ${word} ${items[items.length - 1]}`;
|
|
109
|
+
}
|
|
110
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
111
|
+
/** Evenly spaced from the field's start, e.g. 0,15,30,45 -> 15. */
|
|
112
|
+
function evenStep(values, size) {
|
|
113
|
+
if (values.length < 2 || values[0] !== 0)
|
|
114
|
+
return null;
|
|
115
|
+
const step = values[1] - values[0];
|
|
116
|
+
if (step < 2 || size % step !== 0 || values.length !== size / step)
|
|
117
|
+
return null;
|
|
118
|
+
return values.every((v, i) => v === i * step) ? step : null;
|
|
119
|
+
}
|
|
120
|
+
function describeTime(minute, hour) {
|
|
121
|
+
const minutes = expandField(minute, MINUTE);
|
|
122
|
+
const hours = expandField(hour, HOUR);
|
|
123
|
+
if (!minutes || !hours)
|
|
124
|
+
return null;
|
|
125
|
+
const everyHour = hours.length === 24;
|
|
126
|
+
if (everyHour && minutes.length === 60)
|
|
127
|
+
return 'every minute';
|
|
128
|
+
if (everyHour) {
|
|
129
|
+
const step = evenStep(minutes, 60);
|
|
130
|
+
if (step)
|
|
131
|
+
return `every ${step} minutes`;
|
|
132
|
+
if (minutes.length === 1)
|
|
133
|
+
return minutes[0] === 0
|
|
134
|
+
? 'at the start of every hour'
|
|
135
|
+
: `at minute ${minutes[0]} of every hour`;
|
|
136
|
+
return `at minutes ${joinList(minutes.map(String))} of every hour`;
|
|
137
|
+
}
|
|
138
|
+
if (minutes.length === 1 && hours.length <= 6) {
|
|
139
|
+
return `at ${joinList(hours.map((h) => `${pad(h)}:${pad(minutes[0])}`))}`;
|
|
140
|
+
}
|
|
141
|
+
const hourStep = evenStep(hours, 24);
|
|
142
|
+
if (minutes.length === 1 && hourStep) {
|
|
143
|
+
return `every ${hourStep} hours, at minute ${minutes[0]}`;
|
|
144
|
+
}
|
|
145
|
+
return `at minute${minutes.length > 1 ? 's' : ''} ${joinList(minutes.map(String))} past hour${hours.length > 1 ? 's' : ''} ${joinList(hours.map(String))}`;
|
|
146
|
+
}
|
|
147
|
+
function describeWeekdays(values) {
|
|
148
|
+
const key = values.join(',');
|
|
149
|
+
if (key === '1,2,3,4,5')
|
|
150
|
+
return 'Monday to Friday';
|
|
151
|
+
if (key === '0,6')
|
|
152
|
+
return 'Saturday and Sunday';
|
|
153
|
+
return joinList(values.map((d) => WEEKDAYS[d]));
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Plain language for a five-field cron pattern, or `null` when this reader cannot say it faithfully.
|
|
157
|
+
*/
|
|
158
|
+
function describeCronPattern(pattern) {
|
|
159
|
+
const fields = String(pattern ?? '')
|
|
160
|
+
.trim()
|
|
161
|
+
.split(/\s+/);
|
|
162
|
+
if (fields.length !== 5)
|
|
163
|
+
return null;
|
|
164
|
+
const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
|
|
165
|
+
const time = describeTime(minute, hour);
|
|
166
|
+
const months = month === '*' ? [] : expandField(month, MONTH);
|
|
167
|
+
const monthDays = dayOfMonth === '*' ? [] : expandField(dayOfMonth, DAY_OF_MONTH);
|
|
168
|
+
const weekdays = dayOfWeek === '*' ? [] : expandField(dayOfWeek, DAY_OF_WEEK);
|
|
169
|
+
if (!time || !months || !monthDays || !weekdays)
|
|
170
|
+
return null;
|
|
171
|
+
const dayParts = [];
|
|
172
|
+
if (monthDays.length > 0) {
|
|
173
|
+
dayParts.push(`on the ${joinList(monthDays.map(ordinal))} of the month`);
|
|
174
|
+
}
|
|
175
|
+
if (weekdays.length > 0) {
|
|
176
|
+
dayParts.push(`on ${describeWeekdays(weekdays)}`);
|
|
177
|
+
}
|
|
178
|
+
const dayText = dayParts.length === 0 ? 'every day' : dayParts.join(' OR ');
|
|
179
|
+
const monthText = months.length > 0 ? ` in ${joinList(months.map((m) => MONTHS[m - 1]))}` : '';
|
|
180
|
+
return {
|
|
181
|
+
text: `${time}, ${dayText}${monthText}`,
|
|
182
|
+
dayOfMonthOrWeekday: monthDays.length > 0 && weekdays.length > 0,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function instantText(value) {
|
|
186
|
+
if (typeof value !== 'string' && !(value instanceof Date))
|
|
187
|
+
return null;
|
|
188
|
+
const date = new Date(value);
|
|
189
|
+
return Number.isNaN(date.getTime())
|
|
190
|
+
? typeof value === 'string'
|
|
191
|
+
? value
|
|
192
|
+
: null
|
|
193
|
+
: (0, iso_instant_1.formatUtcInstant)(date);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The block `tasks show` prints for a recurring Task: the rule, its zone, a plain reading, when it
|
|
197
|
+
* next fires, how many runs it has used, and when it ends. Empty for a Task that does not recur.
|
|
198
|
+
*/
|
|
199
|
+
function recurrenceSummaryLines(task) {
|
|
200
|
+
const recurrence = task.recurrence;
|
|
201
|
+
const pattern = typeof recurrence?.pattern === 'string' ? recurrence.pattern.trim() : '';
|
|
202
|
+
if (!recurrence || !pattern)
|
|
203
|
+
return [];
|
|
204
|
+
const zone = typeof recurrence.timezone === 'string' && recurrence.timezone.trim()
|
|
205
|
+
? recurrence.timezone.trim()
|
|
206
|
+
: 'UTC';
|
|
207
|
+
const reading = describeCronPattern(pattern);
|
|
208
|
+
const lines = [`Recurs: ${pattern} (${zone})${reading ? ` - ${reading.text}` : ''}`];
|
|
209
|
+
const facts = [];
|
|
210
|
+
if (recurrence.paused === true)
|
|
211
|
+
facts.push('paused');
|
|
212
|
+
const next = instantText(recurrence.nextRunAt ?? task.scheduledFor);
|
|
213
|
+
if (next)
|
|
214
|
+
facts.push(`next ${next}`);
|
|
215
|
+
const used = Number.isFinite(Number(recurrence.runsCompleted))
|
|
216
|
+
? Number(recurrence.runsCompleted)
|
|
217
|
+
: 0;
|
|
218
|
+
const cap = Number(recurrence.maxRuns);
|
|
219
|
+
facts.push(Number.isInteger(cap) && cap > 0 ? `${used} of ${cap} runs` : `${used} runs so far, no cap`);
|
|
220
|
+
const ends = instantText(recurrence.endDate);
|
|
221
|
+
if (ends)
|
|
222
|
+
facts.push(`ends ${ends}`);
|
|
223
|
+
lines.push(` ${facts.join(', ')}`);
|
|
224
|
+
if (reading?.dayOfMonthOrWeekday) {
|
|
225
|
+
lines.push(' Cron note: with both a day of the month and a weekday, it fires when EITHER matches, not only when both do.');
|
|
226
|
+
}
|
|
227
|
+
return lines;
|
|
228
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type ReminderOutcome = {
|
|
2
|
+
/** The Task already had a live reminder with these words, and that row was returned. */
|
|
3
|
+
reused?: boolean;
|
|
4
|
+
/** ...and it was moved to the requested time. */
|
|
5
|
+
moved?: boolean;
|
|
6
|
+
/** Where it was before it moved. */
|
|
7
|
+
previousRunAt?: string;
|
|
8
|
+
};
|
|
9
|
+
export type ReminderTrigger = {
|
|
10
|
+
id?: string;
|
|
11
|
+
once?: {
|
|
12
|
+
runAt?: string;
|
|
13
|
+
};
|
|
14
|
+
reminderOutcome?: ReminderOutcome;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The lines to print after a successful `POST /api/triggers`.
|
|
18
|
+
*
|
|
19
|
+
* `requested` is the instant the user asked for; the stored `once.runAt` wins when the
|
|
20
|
+
* server returned one, because that is the time that will fire.
|
|
21
|
+
*/
|
|
22
|
+
export declare function reminderReceiptLines(input: {
|
|
23
|
+
title: string;
|
|
24
|
+
trigger: ReminderTrigger | null | undefined;
|
|
25
|
+
requested: Date;
|
|
26
|
+
bin?: string;
|
|
27
|
+
timeZone?: string;
|
|
28
|
+
}): string[];
|