@skrr-ai/cli 0.1.104 → 0.1.106

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 (111) hide show
  1. package/dist/base-command.js +6 -2
  2. package/dist/commands/agenda/trigger/list.js +2 -0
  3. package/dist/commands/agents/chat.js +4 -1
  4. package/dist/commands/agents/operating-plan.js +5 -2
  5. package/dist/commands/agents/triggers/create.js +3 -1
  6. package/dist/commands/agents/triggers/fire.js +2 -0
  7. package/dist/commands/agents/triggers/replay.js +2 -0
  8. package/dist/commands/agents/triggers/update.js +12 -4
  9. package/dist/commands/commitments/action-history.js +7 -2
  10. package/dist/commands/commitments/cadence-decisions.js +6 -1
  11. package/dist/commands/commitments/cadence-requests/decide.d.ts +1 -1
  12. package/dist/commands/commitments/cadence-requests/decide.js +1 -1
  13. package/dist/commands/commitments/doctor.js +7 -1
  14. package/dist/commands/commitments/observations.js +7 -1
  15. package/dist/commands/commitments/operating-review.d.ts +15 -0
  16. package/dist/commands/commitments/operating-review.js +23 -2
  17. package/dist/commands/commitments/preflight.d.ts +12 -0
  18. package/dist/commands/commitments/preflight.js +23 -9
  19. package/dist/commands/commitments/reflection.js +6 -1
  20. package/dist/commands/commitments/show.js +3 -1
  21. package/dist/commands/commitments/trigger-status.js +2 -22
  22. package/dist/commands/commitments/wake-capabilities.d.ts +13 -0
  23. package/dist/commands/commitments/wake-capabilities.js +39 -3
  24. package/dist/commands/daemon/logs.d.ts +73 -3
  25. package/dist/commands/daemon/logs.js +304 -17
  26. package/dist/commands/followups/act.js +13 -1
  27. package/dist/commands/followups/checkin.js +11 -2
  28. package/dist/commands/followups/reschedule.js +13 -1
  29. package/dist/commands/proposals/discard.d.ts +1 -1
  30. package/dist/commands/proposals/keep.d.ts +1 -1
  31. package/dist/commands/proposals/list.d.ts +7 -0
  32. package/dist/commands/proposals/list.js +31 -5
  33. package/dist/commands/proposals/show.d.ts +2 -1
  34. package/dist/commands/proposals/show.js +31 -4
  35. package/dist/commands/proposals/snooze.d.ts +1 -1
  36. package/dist/commands/requests/create.js +8 -3
  37. package/dist/commands/spaces/workflows/create.js +3 -3
  38. package/dist/commands/tasks/complete.js +4 -0
  39. package/dist/commands/tasks/create.d.ts +2 -0
  40. package/dist/commands/tasks/create.js +17 -3
  41. package/dist/commands/tasks/deliverable/add.d.ts +2 -0
  42. package/dist/commands/tasks/deliverable/add.js +22 -7
  43. package/dist/commands/tasks/deliverable/list.d.ts +13 -0
  44. package/dist/commands/tasks/deliverable/list.js +34 -1
  45. package/dist/commands/tasks/economics-compare.js +7 -1
  46. package/dist/commands/tasks/expectations.d.ts +10 -0
  47. package/dist/commands/tasks/expectations.js +31 -3
  48. package/dist/commands/tasks/list.d.ts +9 -0
  49. package/dist/commands/tasks/list.js +40 -5
  50. package/dist/commands/tasks/report.js +1 -1
  51. package/dist/commands/tasks/result/show.d.ts +7 -0
  52. package/dist/commands/tasks/result/show.js +32 -1
  53. package/dist/commands/tasks/result/submit.js +1 -1
  54. package/dist/commands/tasks/retry-schedule.js +11 -1
  55. package/dist/commands/tasks/show.d.ts +6 -0
  56. package/dist/commands/tasks/show.js +23 -0
  57. package/dist/commands/tasks/update.d.ts +3 -0
  58. package/dist/commands/tasks/update.js +23 -3
  59. package/dist/commands/triggers/audit.js +6 -2
  60. package/dist/commands/triggers/create.js +3 -1
  61. package/dist/commands/triggers/fire.d.ts +1 -0
  62. package/dist/commands/triggers/fire.js +16 -1
  63. package/dist/commands/triggers/preflight.js +6 -5
  64. package/dist/commands/triggers/replay.js +2 -0
  65. package/dist/commands/triggers/update.js +12 -4
  66. package/dist/commands/workspaces/stewardship.js +5 -1
  67. package/dist/lib/agentic-stream.d.ts +9 -0
  68. package/dist/lib/agentic-stream.js +38 -18
  69. package/dist/lib/commitment-event-wakes.d.ts +8 -0
  70. package/dist/lib/commitment-event-wakes.js +39 -0
  71. package/dist/lib/commitment-ledgers.d.ts +9 -0
  72. package/dist/lib/commitment-ledgers.js +126 -0
  73. package/dist/lib/commitments.d.ts +102 -2
  74. package/dist/lib/commitments.js +208 -8
  75. package/dist/lib/delegated-cli.js +13 -2
  76. package/dist/lib/followups.d.ts +6 -0
  77. package/dist/lib/followups.js +12 -0
  78. package/dist/lib/message-content.d.ts +6 -0
  79. package/dist/lib/message-content.js +24 -0
  80. package/dist/lib/operating-plan-loops.d.ts +8 -0
  81. package/dist/lib/operating-plan-loops.js +32 -0
  82. package/dist/lib/preflight-copy.d.ts +27 -0
  83. package/dist/lib/preflight-copy.js +89 -0
  84. package/dist/lib/proposal-decision-command.d.ts +1 -1
  85. package/dist/lib/proposal-decision-command.js +27 -3
  86. package/dist/lib/proposals.d.ts +17 -0
  87. package/dist/lib/proposals.js +41 -0
  88. package/dist/lib/stewardship-output.js +57 -12
  89. package/dist/lib/task-economics-line.d.ts +30 -0
  90. package/dist/lib/task-economics-line.js +49 -0
  91. package/dist/lib/task-economics.d.ts +15 -0
  92. package/dist/lib/task-economics.js +73 -8
  93. package/dist/lib/task-recurrence-outcome.d.ts +19 -0
  94. package/dist/lib/task-recurrence-outcome.js +30 -0
  95. package/dist/lib/triggers.d.ts +42 -0
  96. package/dist/lib/triggers.js +136 -2
  97. package/dist/lib/widgetTerminalText.d.ts +8 -0
  98. package/dist/lib/widgetTerminalText.js +116 -0
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +33 -0
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +27 -0
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.d.ts +28 -5
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.js +50 -5
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +33 -0
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +26 -0
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.d.ts +28 -5
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.js +50 -5
  107. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  108. package/dist/node_modules/@skrr-ai/data-provider/index.js +7261 -7056
  109. package/docs/v1/operations.md +1 -1
  110. package/oclif.manifest.json +116 -72
  111. package/package.json +1 -1
@@ -9,9 +9,15 @@ const proposals_1 = require("./proposals");
9
9
  * before an old decision would approve content the caller never reviewed. */
10
10
  class ProposalDecisionCommand extends base_command_1.BaseCommand {
11
11
  static decision;
12
+ // The kind is validated in `resolveProposalRef`, not by oclif, so the KIND:ID that `proposals list`
13
+ // prints can be pasted whole (OSK-15371).
12
14
  static args = {
13
- kind: core_1.Args.string({ required: true, options: [...data_provider_1.PROPOSAL_KINDS], ignoreStdin: true }),
14
- id: core_1.Args.string({ required: true, ignoreStdin: true }),
15
+ kind: core_1.Args.string({
16
+ required: true,
17
+ description: 'Proposal kind, or the KIND:ID that `proposals list` prints',
18
+ ignoreStdin: true,
19
+ }),
20
+ id: core_1.Args.string({ required: false, description: 'Proposal id', ignoreStdin: true }),
15
21
  };
16
22
  static flags = {
17
23
  json: core_1.Flags.boolean(),
@@ -45,9 +51,18 @@ class ProposalDecisionCommand extends base_command_1.BaseCommand {
45
51
  if (until > Date.now() + data_provider_1.PROPOSAL_SNOOZE_MAX_DAYS * 86400000)
46
52
  this.error(`--until may be at most ${data_provider_1.PROPOSAL_SNOOZE_MAX_DAYS} days ahead.`);
47
53
  }
54
+ const ref = (0, proposals_1.resolveProposalRef)(args.kind, args.id, this.config?.bin);
55
+ if ('error' in ref) {
56
+ this.failWithCliError({
57
+ message: ref.error,
58
+ code: 'PROPOSAL_REF_INVALID',
59
+ exit: 2,
60
+ retryable: false,
61
+ });
62
+ }
48
63
  let result;
49
64
  try {
50
- result = await proposals_1.proposalApi.decide(args.kind, args.id, {
65
+ result = await proposals_1.proposalApi.decide(ref.kind, ref.id, {
51
66
  decision,
52
67
  expected,
53
68
  reason: flags.reason,
@@ -56,6 +71,15 @@ class ProposalDecisionCommand extends base_command_1.BaseCommand {
56
71
  });
57
72
  }
58
73
  catch (error) {
74
+ if (error?.status === 404) {
75
+ this.failWithCliError({
76
+ message: (0, proposals_1.describeProposalNotFound)(ref.kind, ref.id, this.config?.bin),
77
+ code: 'PROPOSAL_NOT_FOUND',
78
+ status: 404,
79
+ exit: 4,
80
+ retryable: false,
81
+ });
82
+ }
59
83
  this.handleApiError(error);
60
84
  }
61
85
  this.log(flags.json ? JSON.stringify(result, null, 2) : `Proposal ${decision} decision recorded.`);
@@ -6,3 +6,20 @@ export declare const proposalApi: {
6
6
  show(kind: ProposalKind, id: string): Promise<ProposalView>;
7
7
  decide(kind: ProposalKind, id: string, input: ProposalDecisionParams): Promise<ProposalDecisionResponse>;
8
8
  };
9
+ /**
10
+ * The proposal a person pointed at, from what they typed or pasted (OSK-15371, OSK-15354).
11
+ *
12
+ * `proposals list` prints one token, `KIND:ID`, and `show`/`keep`/`discard`/`snooze` wanted two
13
+ * arguments, so pasting the list's own output failed with a message listing six kinds that never
14
+ * said the id belonged in a second argument. Both spellings are accepted now. The kind never
15
+ * contains a colon, so the FIRST one splits (a Northstar intent's id is itself
16
+ * `<review-id>:<intent-id>`), and `KIND KIND:ID` is accepted too.
17
+ */
18
+ export declare function resolveProposalRef(kindArg: string | undefined, idArg: string | undefined, bin?: string): {
19
+ kind: ProposalKind;
20
+ id: string;
21
+ } | {
22
+ error: string;
23
+ };
24
+ /** A 404 here means no such proposal under THIS kind: the id is looked up under the kind given. */
25
+ export declare function describeProposalNotFound(kind: string, id: string, bin?: string): string;
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.proposalApi = void 0;
4
+ exports.resolveProposalRef = resolveProposalRef;
5
+ exports.describeProposalNotFound = describeProposalNotFound;
4
6
  const data_provider_1 = require("@skrr-ai/data-provider");
7
+ const data_provider_2 = require("@skrr-ai/data-provider");
5
8
  const triggers_1 = require("./triggers");
6
9
  const MOUNT = '/api/proposals';
7
10
  /** The unified, read-time proposal inbox. Decisions are routed by the server to
@@ -20,3 +23,41 @@ exports.proposalApi = {
20
23
  });
21
24
  },
22
25
  };
26
+ /**
27
+ * The proposal a person pointed at, from what they typed or pasted (OSK-15371, OSK-15354).
28
+ *
29
+ * `proposals list` prints one token, `KIND:ID`, and `show`/`keep`/`discard`/`snooze` wanted two
30
+ * arguments, so pasting the list's own output failed with a message listing six kinds that never
31
+ * said the id belonged in a second argument. Both spellings are accepted now. The kind never
32
+ * contains a colon, so the FIRST one splits (a Northstar intent's id is itself
33
+ * `<review-id>:<intent-id>`), and `KIND KIND:ID` is accepted too.
34
+ */
35
+ function resolveProposalRef(kindArg, idArg, bin = 'skrr') {
36
+ const kinds = [...data_provider_2.PROPOSAL_KINDS].join(', ');
37
+ const usage = `Pass KIND and ID (\`${bin} proposals show request <id>\`) or the KIND:ID that \`${bin} proposals list\` prints.`;
38
+ let kind = (kindArg ?? '').trim();
39
+ let id = (idArg ?? '').trim();
40
+ if (!id) {
41
+ const colon = kind.indexOf(':');
42
+ if (colon < 0) {
43
+ return { error: `Missing the proposal id. ${usage}` };
44
+ }
45
+ id = kind.slice(colon + 1);
46
+ kind = kind.slice(0, colon);
47
+ }
48
+ else if (id.startsWith(`${kind}:`)) {
49
+ id = id.slice(kind.length + 1);
50
+ }
51
+ if (!data_provider_2.PROPOSAL_KINDS.includes(kind)) {
52
+ return { error: `Unknown proposal kind "${kind}". Kinds: ${kinds}. ${usage}` };
53
+ }
54
+ if (!id) {
55
+ return { error: `Missing the proposal id after "${kind}". ${usage}` };
56
+ }
57
+ return { kind: kind, id };
58
+ }
59
+ /** A 404 here means no such proposal under THIS kind: the id is looked up under the kind given. */
60
+ function describeProposalNotFound(kind, id, bin = 'skrr') {
61
+ return (`No ${kind} proposal ${id} is visible to you. A proposal is looked up under its kind, so an id ` +
62
+ `that exists under another kind is not found here; \`${bin} proposals list\` prints each as KIND:ID.`);
63
+ }
@@ -9,10 +9,26 @@ function printStewardshipCoverage(report, log) {
9
9
  log('Coverage: complete within the bounded sources.');
10
10
  return;
11
11
  }
12
- const unreadable = report.coverage.sources.some((source) => source.status === 'unavailable' || source.truncated);
13
- log(unreadable
14
- ? 'Coverage: incomplete. Unreadable sources do not confirm absence.'
15
- : 'Coverage: incomplete. Some Grants are not attributed to this workspace and are not listed.');
12
+ const unavailable = report.coverage.sources.some((source) => source.status === 'unavailable');
13
+ // A list cut at the row cap is READABLE and partial; calling it "unreadable" sent the reader looking
14
+ // for a permission problem (OSK-15355). `unattributedGrants` has its own line below.
15
+ const cut = report.coverage.sources.some((source) => source.status !== 'unavailable' && source.truncated && source.name !== 'unattributedGrants');
16
+ const unlistedGrants = report.coverage.sources.some((source) => source.unlisted);
17
+ if (unavailable || cut) {
18
+ log(`Coverage: incomplete. ${[
19
+ unavailable ? 'Unreadable sources do not confirm absence.' : null,
20
+ cut
21
+ ? 'Lists cut at the row cap are partial, so a row missing from them is not shown absent; this command has no paging.'
22
+ : null,
23
+ ]
24
+ .filter(Boolean)
25
+ .join(' ')}`);
26
+ }
27
+ else {
28
+ log(unlistedGrants
29
+ ? 'Coverage: incomplete. Some Grants are not attributed to this workspace and are not listed.'
30
+ : 'Coverage: incomplete.');
31
+ }
16
32
  for (const source of report.coverage.sources) {
17
33
  if (source.status === 'unavailable')
18
34
  log(`${source.name}: unavailable`);
@@ -25,6 +41,18 @@ function printStewardshipCoverage(report, log) {
25
41
  }
26
42
  }
27
43
  }
44
+ /** Who holds the Grant, in the same `kind id` words `skrr grants list` uses. */
45
+ function principalText(principal) {
46
+ const value = principal;
47
+ if (!value?.kind)
48
+ return '—';
49
+ const id = value.agentId ?? value.commitmentId;
50
+ return id ? `${value.kind} ${id}` : value.kind;
51
+ }
52
+ /** What the Grant allows: its capability names, or an honest dash when it lists none. */
53
+ function accessText(capabilities) {
54
+ return Array.isArray(capabilities) && capabilities.length ? capabilities.join(', ') : '—';
55
+ }
28
56
  function printWorkspaceGrants(report, log) {
29
57
  printStewardshipCoverage(report, log);
30
58
  printGrantRows(report, log);
@@ -44,16 +72,23 @@ function printGrantRows(report, log) {
44
72
  (0, format_1.renderTable)(report.grants.map((row) => ({
45
73
  id: row.id,
46
74
  label: row.label,
75
+ provider: row.provider || '—',
76
+ principal: principalText(row.principal),
77
+ access: accessText(row.capabilities),
47
78
  owner: row.ownerUserId,
48
79
  active: row.active ? 'yes' : 'no',
49
- expires: row.expiresAt ?? '',
50
- envKeys: row.credential?.envKeys.join(', ') ?? '',
80
+ // No expiry is a fact about the Grant; a blank cell read as "not readable" (OSK-15357).
81
+ expires: row.expiresAt ?? 'never',
82
+ envKeys: row.credential?.envKeys.length ? row.credential.envKeys.join(', ') : '—',
51
83
  referencedBy: row.referencedBy === null
52
84
  ? 'unavailable'
53
85
  : `${row.referencedBy.join(', ')}${row.referencesTruncated ? ' (truncated)' : ''}`,
54
86
  })), [
55
87
  { key: 'id', header: 'ID' },
56
88
  { key: 'label', header: 'LABEL' },
89
+ { key: 'provider', header: 'PROVIDER' },
90
+ { key: 'principal', header: 'HELD BY' },
91
+ { key: 'access', header: 'ACCESS' },
57
92
  { key: 'owner', header: 'OWNER' },
58
93
  { key: 'active', header: 'ACTIVE' },
59
94
  { key: 'expires', header: 'EXPIRES' },
@@ -63,12 +98,22 @@ function printGrantRows(report, log) {
63
98
  }
64
99
  function printWorkspaceStewardship(report, log) {
65
100
  printStewardshipCoverage(report, log);
66
- log(`Membership: ${report.membership.status}${report.membership.role ? ` (${report.membership.role})` : ''}`);
67
- for (const [name, rows] of [
68
- ['Commitments', report.commitments],
69
- ['Approver seats', report.approverSeats],
101
+ log(`Membership: ${report.membership.status}${report.membership.role ? ` (${report.membership.role})` : ''}${
102
+ // "Absent" is all the report knows: a removed member and an id that never existed read alike (OSK-15356).
103
+ report.membership.status === 'absent'
104
+ ? ' — not a current member; this report cannot tell a removed member from an id that never existed'
105
+ : ''}`);
106
+ const isCut = (source) => report.coverage.sources.some((entry) => entry.name === source && entry.truncated);
107
+ const count = (rows, source) => rows === null
108
+ ? 'unavailable'
109
+ : isCut(source)
110
+ ? `${rows.length} shown, cut at the row cap (there are more)`
111
+ : String(rows.length);
112
+ for (const [name, source, rows] of [
113
+ ['Commitments', 'commitments', report.commitments],
114
+ ['Approver seats', 'approverSeats', report.approverSeats],
70
115
  ]) {
71
- log(`${name}: ${rows === null ? 'unavailable' : rows.length}`);
116
+ log(`${name}: ${count(rows, source)}`);
72
117
  if (rows?.length)
73
118
  (0, format_1.renderTable)(rows.map((row) => ({
74
119
  id: row.id,
@@ -82,7 +127,7 @@ function printWorkspaceStewardship(report, log) {
82
127
  { key: 'agentId', header: 'EXECUTOR' },
83
128
  ], log);
84
129
  }
85
- log(`Authored Agents: ${report.agents === null ? 'unavailable' : report.agents.length}`);
130
+ log(`Authored Agents: ${count(report.agents, 'agents')}`);
86
131
  if (report.agents?.length)
87
132
  (0, format_1.renderTable)(report.agents.map((row) => ({ id: row.id, name: row.name ?? '' })), [
88
133
  { key: 'id', header: 'ID' },
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The Task's stored economic claim as one line for `tasks show` (OSK-15363).
3
+ *
4
+ * The detail view printed no savings, effort, risk, basis or confirmer at all: only `--json` carried
5
+ * them, with the confirmer as a raw user id, though `docs/cli/SKRR_TASKS.md` sends a reader to "ordinary
6
+ * task detail" for the claim. A claim is not a measurement, so the basis is part of the line.
7
+ */
8
+ type Economics = {
9
+ savings?: {
10
+ amount?: number;
11
+ currency?: string;
12
+ per?: string;
13
+ };
14
+ effortHours?: number;
15
+ risk?: string;
16
+ confidence?: string;
17
+ rationale?: string;
18
+ basis?: string;
19
+ confirmedBy?: string;
20
+ confirmedAt?: string;
21
+ revision?: number;
22
+ };
23
+ /** Null when the Task carries no claim at all. `confirmerName` is the resolved name, if there is one. */
24
+ export declare function economicsLine(economics: Economics | null | undefined, confirmerName?: string): string | null;
25
+ /**
26
+ * The claim's rationale on its own line, or null. It is kept when a later edit changes the figures, so
27
+ * it is shown beside the basis it now carries rather than left to `--json`.
28
+ */
29
+ export declare function economicsRationaleLine(economics: Economics | null | undefined): string | null;
30
+ export {};
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * The Task's stored economic claim as one line for `tasks show` (OSK-15363).
4
+ *
5
+ * The detail view printed no savings, effort, risk, basis or confirmer at all: only `--json` carried
6
+ * them, with the confirmer as a raw user id, though `docs/cli/SKRR_TASKS.md` sends a reader to "ordinary
7
+ * task detail" for the claim. A claim is not a measurement, so the basis is part of the line.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.economicsLine = economicsLine;
11
+ exports.economicsRationaleLine = economicsRationaleLine;
12
+ const format_1 = require("./format");
13
+ const BASIS = {
14
+ agent_estimate: 'Agent estimate',
15
+ human_confirmed: 'Human confirmed',
16
+ measured: 'Measured',
17
+ };
18
+ /** Null when the Task carries no claim at all. `confirmerName` is the resolved name, if there is one. */
19
+ function economicsLine(economics, confirmerName) {
20
+ if (!economics || typeof economics !== 'object')
21
+ return null;
22
+ const parts = [];
23
+ const savings = economics.savings;
24
+ if (savings && typeof savings.amount === 'number') {
25
+ parts.push(`${savings.currency ?? 'USD'} ${savings.amount}${savings.per ? `/${savings.per}` : ''} saved`);
26
+ }
27
+ if (typeof economics.effortHours === 'number')
28
+ parts.push(`${economics.effortHours}h effort`);
29
+ if (economics.risk)
30
+ parts.push(`risk ${economics.risk}`);
31
+ if (parts.length === 0)
32
+ return null;
33
+ // Part of the claim, and kept across a later re-estimate: a reader must see what the label covers (OSK-15372).
34
+ if (economics.confidence)
35
+ parts.push(`confidence ${economics.confidence}`);
36
+ const basis = economics.basis ? (BASIS[economics.basis] ?? economics.basis) : 'basis not stated';
37
+ const who = economics.confirmedBy ? ` by ${confirmerName || economics.confirmedBy}` : '';
38
+ const when = economics.confirmedAt ? ` on ${String(economics.confirmedAt).slice(0, 10)}` : '';
39
+ const revision = typeof economics.revision === 'number' ? ` (claim revision ${economics.revision})` : '';
40
+ return `${parts.join(' · ')} · ${basis}${who}${when}${revision} — a claim, not a measurement`;
41
+ }
42
+ /**
43
+ * The claim's rationale on its own line, or null. It is kept when a later edit changes the figures, so
44
+ * it is shown beside the basis it now carries rather than left to `--json`.
45
+ */
46
+ function economicsRationaleLine(economics) {
47
+ const rationale = economics?.rationale;
48
+ return typeof rationale === 'string' && rationale.trim() ? (0, format_1.collapseWhitespace)(rationale) : null;
49
+ }
@@ -3,6 +3,8 @@ export declare function economicsFromFlags(flags: {
3
3
  per?: string;
4
4
  effort?: string;
5
5
  risk?: string;
6
+ confidence?: string;
7
+ rationale?: string;
6
8
  }, existing?: unknown): {
7
9
  savings?: {
8
10
  amount: number;
@@ -14,3 +16,16 @@ export declare function economicsFromFlags(flags: {
14
16
  confidence?: "low" | "medium" | "high" | undefined;
15
17
  rationale?: string | undefined;
16
18
  } | undefined;
19
+ /**
20
+ * One readable line for an estimate: `USD 7/month · 4h effort · medium risk · Human confirmed`.
21
+ * `economics-compare` printed the stored object as one minified JSON line (OSK-15368). A dimension
22
+ * the estimate does not carry is named as not set, never skipped.
23
+ */
24
+ export declare function describeEstimate(economics: unknown): string;
25
+ /**
26
+ * The number `--rank economics` sorts by, so the order can be read off the screen (OSK-15374):
27
+ * annualized USD savings per effort hour (floored at half an hour), times the risk factor
28
+ * (low 1, medium 0.6, high 0.25). `—` when savings, effort or risk is missing: such a row is not
29
+ * scored and sorts after every row that is.
30
+ */
31
+ export declare function economicsScoreText(economics: unknown): string;
@@ -1,22 +1,87 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.economicsFromFlags = economicsFromFlags;
4
+ exports.describeEstimate = describeEstimate;
5
+ exports.economicsScoreText = economicsScoreText;
4
6
  const data_provider_1 = require("@skrr-ai/data-provider");
7
+ /**
8
+ * A flag's amount as a number, or an error that names the flag and the value (OSK-15366). A raw
9
+ * Zod issue array named internal paths (`savings.amount`, `effortHours`) and no flag, so a person
10
+ * who typed `--savings 1,000` was shown a JSON dump of `too_small` and `invalid_type`.
11
+ */
12
+ function amountFlag(flag, value) {
13
+ const amount = Number(value);
14
+ if (value.trim() === '' || !Number.isFinite(amount) || amount < 0) {
15
+ const hint = /,/.test(value) ? ' Write it without a thousands separator.' : '';
16
+ throw new Error(`${flag} must be a non-negative number (got '${value}').${hint}`);
17
+ }
18
+ return amount;
19
+ }
20
+ /** Zod's internal path, in the flag a person typed. */
21
+ const FLAG_FOR_PATH = {
22
+ 'savings.amount': '--savings',
23
+ 'savings.per': '--per',
24
+ effortHours: '--effort',
25
+ risk: '--risk',
26
+ confidence: '--confidence',
27
+ rationale: '--rationale',
28
+ };
5
29
  function economicsFromFlags(flags, existing) {
6
- if ([flags.savings, flags.per, flags.effort, flags.risk].every((value) => value === undefined))
30
+ if ([flags.savings, flags.per, flags.effort, flags.risk, flags.confidence, flags.rationale].every((value) => value === undefined))
7
31
  return undefined;
8
32
  if ((flags.savings === undefined) !== (flags.per === undefined))
9
33
  throw new Error('--savings and --per must be supplied together (USD/month, year or once).');
10
- for (const value of [flags.savings, flags.effort])
11
- if (value !== undefined && value.trim() === '')
12
- throw new Error('Economic amounts must be explicit nonnegative numbers.');
34
+ const savings = flags.savings === undefined ? undefined : amountFlag('--savings', flags.savings);
35
+ const effort = flags.effort === undefined ? undefined : amountFlag('--effort', flags.effort);
13
36
  const content = existing && typeof existing === 'object' ? existing : {};
14
- return data_provider_1.ProposalEconomicsInputSchema.parse({
37
+ const parsed = data_provider_1.ProposalEconomicsInputSchema.safeParse({
15
38
  ...content,
16
- ...(flags.savings !== undefined
17
- ? { savings: { amount: Number(flags.savings), currency: 'USD', per: flags.per } }
39
+ ...(savings !== undefined
40
+ ? { savings: { amount: savings, currency: 'USD', per: flags.per } }
18
41
  : {}),
19
- ...(flags.effort !== undefined ? { effortHours: Number(flags.effort) } : {}),
42
+ ...(effort !== undefined ? { effortHours: effort } : {}),
20
43
  ...(flags.risk !== undefined ? { risk: flags.risk } : {}),
44
+ ...(flags.confidence !== undefined ? { confidence: flags.confidence } : {}),
45
+ ...(flags.rationale !== undefined ? { rationale: flags.rationale } : {}),
21
46
  });
47
+ if (parsed.success)
48
+ return parsed.data;
49
+ // One line per refused field, in the flag when there is one, never the raw issue array.
50
+ throw new Error(parsed.error.issues
51
+ .map((issue) => {
52
+ const path = issue.path.join('.');
53
+ return `${FLAG_FOR_PATH[path] ?? `economics.${path || 'value'}`}: ${issue.message}`;
54
+ })
55
+ .join('\n'));
56
+ }
57
+ const BASIS_WORDS = {
58
+ agent_estimate: 'Agent estimate',
59
+ human_confirmed: 'Human confirmed',
60
+ measured: 'Measured',
61
+ };
62
+ /**
63
+ * One readable line for an estimate: `USD 7/month · 4h effort · medium risk · Human confirmed`.
64
+ * `economics-compare` printed the stored object as one minified JSON line (OSK-15368). A dimension
65
+ * the estimate does not carry is named as not set, never skipped.
66
+ */
67
+ function describeEstimate(economics) {
68
+ const value = (economics && typeof economics === 'object' ? economics : {});
69
+ return [
70
+ value.savings
71
+ ? `${value.savings.currency ?? 'USD'} ${value.savings.amount}/${value.savings.per}`
72
+ : 'savings not set',
73
+ value.effortHours !== undefined ? `${value.effortHours}h effort` : 'effort not set',
74
+ value.risk ? `${value.risk} risk` : 'risk not set',
75
+ BASIS_WORDS[value.basis ?? ''] ?? 'basis unknown',
76
+ ].join(' · ');
77
+ }
78
+ /**
79
+ * The number `--rank economics` sorts by, so the order can be read off the screen (OSK-15374):
80
+ * annualized USD savings per effort hour (floored at half an hour), times the risk factor
81
+ * (low 1, medium 0.6, high 0.25). `—` when savings, effort or risk is missing: such a row is not
82
+ * scored and sorts after every row that is.
83
+ */
84
+ function economicsScoreText(economics) {
85
+ const score = (0, data_provider_1.proposalEconomicsRank)(economics).score;
86
+ return score === null || !Number.isFinite(score) ? '—' : String(Math.round(score));
22
87
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * What completing a recurring occurrence did to its series, as a sentence (OSK-15483).
3
+ *
4
+ * `tasks complete` armed the next occurrence (or deliberately did not) and printed nothing either way, so a
5
+ * person could not tell that the series continued, or that the third of three runs had ended it. The server
6
+ * reports `recurrence` on the completion; this words it.
7
+ */
8
+ export type RecurrenceOutcome = {
9
+ state?: string;
10
+ reason?: string;
11
+ successor?: {
12
+ id?: string;
13
+ identifier?: string | null;
14
+ scheduledFor?: string | null;
15
+ };
16
+ armFailure?: unknown;
17
+ };
18
+ /** One line, or null when the Task does not recur (or an older server did not say). */
19
+ export declare function recurrenceOutcomeLine(outcome: RecurrenceOutcome | null | undefined): string | null;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.recurrenceOutcomeLine = recurrenceOutcomeLine;
4
+ const ENDED_BECAUSE = {
5
+ max_runs: 'it reached its maximum number of runs',
6
+ end_date: 'its end date has passed',
7
+ no_next_occurrence: 'its pattern has no further occurrence',
8
+ };
9
+ /** One line, or null when the Task does not recur (or an older server did not say). */
10
+ function recurrenceOutcomeLine(outcome) {
11
+ if (!outcome || typeof outcome !== 'object')
12
+ return null;
13
+ switch (outcome.state) {
14
+ case 'armed': {
15
+ const next = outcome.successor?.identifier || outcome.successor?.id || 'the next occurrence';
16
+ const when = outcome.successor?.scheduledFor ? ` for ${outcome.successor.scheduledFor}` : '';
17
+ return `Recurrence: next occurrence ${next} is scheduled${when}.`;
18
+ }
19
+ case 'ended':
20
+ return `Recurrence: the series has ended; ${ENDED_BECAUSE[outcome.reason ?? ''] ?? 'nothing further is due'}.`;
21
+ case 'paused':
22
+ return 'Recurrence: the series is paused, so no next occurrence was scheduled.';
23
+ case 'routine_managed':
24
+ return 'Recurrence: a Routine owns this series, so no next occurrence was scheduled here.';
25
+ case 'not_armed':
26
+ return 'Recurrence: the series is live but no next occurrence was scheduled; check it with `tasks show`.';
27
+ default:
28
+ return null;
29
+ }
30
+ }
@@ -295,6 +295,41 @@ export declare function assertKindFlagsApply(kind: unknown, flags: Record<string
295
295
  * by itself (OSK-15492). Absent when the server reported nothing to say.
296
296
  */
297
297
  export declare function describeScheduledFire(result: Record<string, unknown>): string | undefined;
298
+ /**
299
+ * Was the fire admitted? `in_progress` and `awaiting_runtime` are admissions whose terminal arrives
300
+ * later (every agent-turn fire answers `in_progress`), and a repeat of a fire that was admitted is
301
+ * `duplicate` with the original beside it; none of them is a refusal (OSK-3867 made the same mistake
302
+ * about `in_progress` once). Only these are success for `triggers fire --strict`.
303
+ */
304
+ export declare function fireWasAccepted(result: Record<string, unknown>): boolean;
305
+ /**
306
+ * The lines a fire or replay owes beyond `outcome=… reason=…` (OSK-15494): the sentence the
307
+ * server wrote ("Replayed fire … resolved a step that was not waiting when that fire ran:
308
+ * run …, step …"), or, for a debounced fire, when a retry will be accepted. Empty when the server
309
+ * reported nothing more.
310
+ */
311
+ export declare function describeFireDetail(result: Record<string, unknown>): string[];
312
+ /**
313
+ * A projected fire as the trigger's own clock reads it. The header said `America/New_York` over
314
+ * values that were UTC instants, so nothing on the line was in the zone it named (OSK-15407). The
315
+ * instant itself stays beside it, exact.
316
+ */
317
+ export declare function formatFireInZone(iso: string, timeZone: string | undefined): string;
318
+ /**
319
+ * The lines `triggers preflight` prints under "Next fires". A fire the trigger's own working or
320
+ * quiet window would skip is marked, because the gate above is evaluated at the current instant
321
+ * only and a window that excludes every scheduled time otherwise read as healthy (OSK-15407).
322
+ */
323
+ type PreflightSchedule = NonNullable<TriggerPreflightReport['schedule']>;
324
+ export declare function nextFireLines(schedule: PreflightSchedule | null | undefined): string[];
325
+ /**
326
+ * A destination as `key=value` pairs, without Mongo's storage fields.
327
+ *
328
+ * A nested object that is longer than a short locator (`agentChat` is the Agent's whole chat row,
329
+ * with the preview of its last message) is named by its id, not dumped: the line printed ~700
330
+ * characters of unrelated chat state into a preflight (OSK-16200). `--json` carries all of it.
331
+ */
332
+ export declare function destinationLine(destination: Record<string, unknown>): string;
298
333
  export declare function withQuery(path: string, params: Record<string, string | number | boolean | string[] | undefined>): string;
299
334
  /**
300
335
  * Build the universal-engine scope block for an agent-owned trigger.
@@ -349,6 +384,12 @@ export declare class AgentTriggerScopeError extends Error {
349
384
  */
350
385
  export declare function fetchAgentScopedTrigger(agentId: string, triggerId: string): Promise<TriggerDefinition>;
351
386
  export declare function scopeLabel(trigger: TriggerDefinition): string;
387
+ /**
388
+ * When an armed `once` trigger is due: its `once.runAt` (or `once.at`), and only while it has not
389
+ * fired. A follow-up check-in due next month had no `schedule.nextRunAt`, so every table said `-` and
390
+ * `triggers show` said `Armed: yes` with no time (OSK-15416).
391
+ */
392
+ export declare function onceDueAt(trigger: TriggerDefinition): string | null;
352
393
  export declare function nextRunLabel(trigger: TriggerDefinition): string;
353
394
  /**
354
395
  * Whether a trigger will actually fire — not merely whether someone left it on.
@@ -511,3 +552,4 @@ export declare const triggerApi: {
511
552
  setSpaceAssistant: (spaceId: string, body: Record<string, unknown>) => Promise<SpaceAssistantResponse>;
512
553
  deleteSpaceAssistant: (spaceId: string) => Promise<Record<string, unknown>>;
513
554
  };
555
+ export {};