@skrr-ai/cli 0.1.108 → 0.1.109

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 (174) hide show
  1. package/README.md +10 -0
  2. package/dist/base-command.d.ts +59 -0
  3. package/dist/base-command.js +118 -7
  4. package/dist/commands/agents/update.d.ts +14 -0
  5. package/dist/commands/agents/update.js +40 -1
  6. package/dist/commands/commitments/accounting.js +7 -2
  7. package/dist/commands/commitments/cost.d.ts +11 -0
  8. package/dist/commands/commitments/cost.js +26 -3
  9. package/dist/commands/commitments/create.js +2 -1
  10. package/dist/commands/commitments/operating-review.d.ts +15 -0
  11. package/dist/commands/commitments/operating-review.js +103 -50
  12. package/dist/commands/commitments/update.js +12 -5
  13. package/dist/commands/doctor.js +9 -0
  14. package/dist/commands/goals/list.js +6 -1
  15. package/dist/commands/grants/rotate.js +4 -0
  16. package/dist/commands/login.js +42 -14
  17. package/dist/commands/machines/list.js +28 -6
  18. package/dist/commands/projects/show.js +1 -1
  19. package/dist/commands/revoke-daemon.js +2 -1
  20. package/dist/commands/spaces/workflows/archive.d.ts +1 -0
  21. package/dist/commands/spaces/workflows/archive.js +1 -0
  22. package/dist/commands/spaces/workflows/artifact.d.ts +1 -0
  23. package/dist/commands/spaces/workflows/artifact.js +1 -0
  24. package/dist/commands/spaces/workflows/cancel-node.d.ts +1 -0
  25. package/dist/commands/spaces/workflows/cancel-node.js +1 -0
  26. package/dist/commands/spaces/workflows/cancel.d.ts +1 -0
  27. package/dist/commands/spaces/workflows/cancel.js +6 -2
  28. package/dist/commands/spaces/workflows/create.d.ts +1 -0
  29. package/dist/commands/spaces/workflows/create.js +7 -1
  30. package/dist/commands/spaces/workflows/events.d.ts +1 -0
  31. package/dist/commands/spaces/workflows/events.js +1 -0
  32. package/dist/commands/spaces/workflows/fixtures/create.d.ts +1 -0
  33. package/dist/commands/spaces/workflows/fixtures/create.js +1 -0
  34. package/dist/commands/spaces/workflows/fixtures/list.d.ts +1 -0
  35. package/dist/commands/spaces/workflows/fixtures/list.js +1 -0
  36. package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +1 -0
  37. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +1 -0
  38. package/dist/commands/spaces/workflows/list.d.ts +1 -0
  39. package/dist/commands/spaces/workflows/list.js +1 -0
  40. package/dist/commands/spaces/workflows/node-types.d.ts +2 -0
  41. package/dist/commands/spaces/workflows/node-types.js +24 -6
  42. package/dist/commands/spaces/workflows/pause.d.ts +1 -0
  43. package/dist/commands/spaces/workflows/pause.js +1 -0
  44. package/dist/commands/spaces/workflows/plan/show.d.ts +1 -0
  45. package/dist/commands/spaces/workflows/plan/show.js +1 -0
  46. package/dist/commands/spaces/workflows/plan/update.d.ts +1 -0
  47. package/dist/commands/spaces/workflows/plan/update.js +1 -0
  48. package/dist/commands/spaces/workflows/proposals/apply.d.ts +1 -0
  49. package/dist/commands/spaces/workflows/proposals/apply.js +1 -0
  50. package/dist/commands/spaces/workflows/proposals/confirm.d.ts +1 -0
  51. package/dist/commands/spaces/workflows/proposals/confirm.js +1 -0
  52. package/dist/commands/spaces/workflows/proposals/create.d.ts +1 -0
  53. package/dist/commands/spaces/workflows/proposals/create.js +1 -0
  54. package/dist/commands/spaces/workflows/proposals/dismiss.d.ts +1 -0
  55. package/dist/commands/spaces/workflows/proposals/dismiss.js +1 -0
  56. package/dist/commands/spaces/workflows/proposals/list.d.ts +1 -0
  57. package/dist/commands/spaces/workflows/proposals/list.js +1 -0
  58. package/dist/commands/spaces/workflows/publish.d.ts +1 -0
  59. package/dist/commands/spaces/workflows/publish.js +9 -0
  60. package/dist/commands/spaces/workflows/reconcile.d.ts +1 -0
  61. package/dist/commands/spaces/workflows/reconcile.js +1 -0
  62. package/dist/commands/spaces/workflows/resume.d.ts +1 -0
  63. package/dist/commands/spaces/workflows/resume.js +1 -0
  64. package/dist/commands/spaces/workflows/retry.d.ts +1 -0
  65. package/dist/commands/spaces/workflows/retry.js +1 -0
  66. package/dist/commands/spaces/workflows/run.d.ts +1 -0
  67. package/dist/commands/spaces/workflows/run.js +14 -7
  68. package/dist/commands/spaces/workflows/runs.d.ts +1 -0
  69. package/dist/commands/spaces/workflows/runs.js +1 -0
  70. package/dist/commands/spaces/workflows/show.d.ts +25 -0
  71. package/dist/commands/spaces/workflows/show.js +42 -4
  72. package/dist/commands/spaces/workflows/signal.d.ts +12 -0
  73. package/dist/commands/spaces/workflows/signal.js +40 -2
  74. package/dist/commands/spaces/workflows/start.d.ts +1 -0
  75. package/dist/commands/spaces/workflows/start.js +1 -0
  76. package/dist/commands/spaces/workflows/task-execution/create.d.ts +1 -0
  77. package/dist/commands/spaces/workflows/task-execution/create.js +1 -0
  78. package/dist/commands/spaces/workflows/test.d.ts +1 -0
  79. package/dist/commands/spaces/workflows/test.js +1 -0
  80. package/dist/commands/spaces/workflows/triggers/create.d.ts +1 -0
  81. package/dist/commands/spaces/workflows/triggers/create.js +1 -0
  82. package/dist/commands/spaces/workflows/triggers/fire.d.ts +1 -0
  83. package/dist/commands/spaces/workflows/triggers/fire.js +1 -0
  84. package/dist/commands/spaces/workflows/triggers/list.d.ts +1 -0
  85. package/dist/commands/spaces/workflows/triggers/list.js +1 -0
  86. package/dist/commands/spaces/workflows/triggers/update.d.ts +1 -0
  87. package/dist/commands/spaces/workflows/triggers/update.js +1 -0
  88. package/dist/commands/spaces/workflows/update.d.ts +1 -0
  89. package/dist/commands/spaces/workflows/update.js +4 -0
  90. package/dist/commands/spaces/workflows/validate.d.ts +1 -0
  91. package/dist/commands/spaces/workflows/validate.js +15 -0
  92. package/dist/commands/tasks/activity.js +6 -2
  93. package/dist/commands/tasks/bulk-update.d.ts +1 -0
  94. package/dist/commands/tasks/bulk-update.js +7 -0
  95. package/dist/commands/tasks/complete.js +2 -1
  96. package/dist/commands/tasks/create-tree.d.ts +69 -0
  97. package/dist/commands/tasks/create-tree.js +413 -47
  98. package/dist/commands/tasks/list.d.ts +2 -0
  99. package/dist/commands/tasks/list.js +19 -1
  100. package/dist/commands/tasks/rearm-recurrence.d.ts +22 -0
  101. package/dist/commands/tasks/rearm-recurrence.js +68 -0
  102. package/dist/commands/tasks/show.d.ts +1 -0
  103. package/dist/commands/tasks/show.js +17 -3
  104. package/dist/commands/tasks/timeline.js +4 -0
  105. package/dist/commands/tasks/update.d.ts +1 -0
  106. package/dist/commands/tasks/update.js +6 -1
  107. package/dist/commands/triggers/create.js +8 -2
  108. package/dist/commands/triggers/parse-intent.d.ts +7 -0
  109. package/dist/commands/triggers/parse-intent.js +31 -9
  110. package/dist/commands/workspaces/grants.js +23 -3
  111. package/dist/commands/workspaces/members.d.ts +20 -0
  112. package/dist/commands/workspaces/members.js +64 -0
  113. package/dist/commands/workspaces/stewardship.js +14 -2
  114. package/dist/help.js +12 -0
  115. package/dist/hooks/command-not-found.js +27 -1
  116. package/dist/lib/command-miss.d.ts +52 -2
  117. package/dist/lib/command-miss.js +187 -18
  118. package/dist/lib/commitment-loop-status.d.ts +8 -0
  119. package/dist/lib/commitment-loop-status.js +45 -0
  120. package/dist/lib/commitments.d.ts +16 -2
  121. package/dist/lib/commitments.js +48 -8
  122. package/dist/lib/daemon-command-hint.d.ts +15 -0
  123. package/dist/lib/daemon-command-hint.js +21 -0
  124. package/dist/lib/daemon-setup.js +10 -1
  125. package/dist/lib/daemonBrokerRefusal.js +3 -2
  126. package/dist/lib/daemonHandoff.js +2 -1
  127. package/dist/lib/deployed-build.d.ts +43 -0
  128. package/dist/lib/deployed-build.js +66 -0
  129. package/dist/lib/grants.d.ts +27 -1
  130. package/dist/lib/grants.js +39 -21
  131. package/dist/lib/json-mode.d.ts +11 -0
  132. package/dist/lib/json-mode.js +19 -0
  133. package/dist/lib/json-output.d.ts +25 -0
  134. package/dist/lib/json-output.js +30 -0
  135. package/dist/lib/list-output.d.ts +9 -0
  136. package/dist/lib/list-output.js +20 -1
  137. package/dist/lib/local-daemon-readiness.d.ts +90 -0
  138. package/dist/lib/local-daemon-readiness.js +143 -0
  139. package/dist/lib/machines.d.ts +117 -0
  140. package/dist/lib/machines.js +139 -0
  141. package/dist/lib/managed-instruction-local.d.ts +2 -0
  142. package/dist/lib/managed-instruction-local.js +2 -0
  143. package/dist/lib/profile-machine-identity-guard.d.ts +18 -0
  144. package/dist/lib/profile-machine-identity-guard.js +29 -0
  145. package/dist/lib/request-field-flags.d.ts +20 -0
  146. package/dist/lib/request-field-flags.js +34 -0
  147. package/dist/lib/stewardship-output.d.ts +1 -1
  148. package/dist/lib/stewardship-output.js +26 -30
  149. package/dist/lib/task-batch.d.ts +61 -4
  150. package/dist/lib/task-batch.js +316 -80
  151. package/dist/lib/task-economics.d.ts +5 -0
  152. package/dist/lib/task-economics.js +15 -0
  153. package/dist/lib/task-recurrence-outcome.d.ts +6 -6
  154. package/dist/lib/task-recurrence-outcome.js +27 -3
  155. package/dist/lib/tasks.d.ts +39 -0
  156. package/dist/lib/tasks.js +132 -0
  157. package/dist/lib/triggers.d.ts +31 -1
  158. package/dist/lib/triggers.js +55 -1
  159. package/dist/lib/workflow-issues.d.ts +14 -0
  160. package/dist/lib/workflow-issues.js +32 -0
  161. package/dist/lib/workflow-run-view.d.ts +55 -0
  162. package/dist/lib/workflow-run-view.js +105 -0
  163. package/dist/lib/workspaces.d.ts +31 -4
  164. package/dist/lib/workspaces.js +43 -0
  165. package/dist/lib/write-target.d.ts +29 -0
  166. package/dist/lib/write-target.js +72 -0
  167. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.d.ts +51 -0
  168. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.js +50 -0
  169. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.d.ts +51 -0
  170. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.js +46 -0
  171. package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
  172. package/dist/node_modules/@skrr-ai/data-provider/index.js +6718 -6370
  173. package/oclif.manifest.json +376 -52
  174. package/package.json +19 -1
@@ -5,6 +5,7 @@ exports.describeBrokerRefusal = describeBrokerRefusal;
5
5
  exports.describeGuestDaemonUnavailable = describeGuestDaemonUnavailable;
6
6
  exports.describeLocalDaemonUnavailable = describeLocalDaemonUnavailable;
7
7
  exports.describeBrokerFailure = describeBrokerFailure;
8
+ const daemon_command_hint_1 = require("./daemon-command-hint");
8
9
  /**
9
10
  * True when a daemon ANSWERED the hand-off and refused it — as opposed to no
10
11
  * daemon being there to ask (`no_bootstrap`, a stale descriptor nobody listens
@@ -89,12 +90,12 @@ function describeBrokerRefusal(failure, bin = 'skrr') {
89
90
  }
90
91
  else if (code === 'DEVICE_PROOF_PIN_MISMATCH') {
91
92
  remedy =
92
- `Run \`${bin} daemon login\` to sign the background service in again — an interactive daemon login re-pins its device key — then retry. ` +
93
+ `Run \`${(0, daemon_command_hint_1.daemonCommandHint)('login', { bin })}\` to sign the background service in again — an interactive daemon login re-pins its device key — then retry. ` +
93
94
  `\`${bin} login\` signs this CLI in on its own instead.`;
94
95
  }
95
96
  else if (DAEMON_RELOGIN_CODES.has(code) || failure.reason === 'daemon_not_authed') {
96
97
  remedy =
97
- `Run \`${bin} daemon login\` to sign the background service in again, then retry. ` +
98
+ `Run \`${(0, daemon_command_hint_1.daemonCommandHint)('login', { bin })}\` to sign the background service in again, then retry. ` +
98
99
  `\`${bin} login\` signs this CLI in on its own instead.`;
99
100
  }
100
101
  else if (failure.reason === 'ci_token_refused') {
@@ -256,7 +256,8 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
256
256
  return {
257
257
  status: 'skipped',
258
258
  reason: 'unsupported-daemon',
259
- detail: 'the installed daemon predates `accept-handoff`; run `skrr daemon login` once',
259
+ // The remedy is named by the caller, spelled for the profile (OSK-16435).
260
+ detail: 'the installed daemon is too old to accept a hand-off',
260
261
  };
261
262
  }
262
263
  // Ask the daemon which id it will bind, rather than deriving it here. The id
@@ -0,0 +1,43 @@
1
+ /**
2
+ * What the server this CLI talks to is running: the commit its API was built
3
+ * from and the commit its web shell was built from (OSK-15497).
4
+ *
5
+ * Both are public facts the server already publishes for anyone:
6
+ * - the API: `GET /api/health` → `build.commit`, baked into the image once at
7
+ * build time by the release pipeline;
8
+ * - the web: `GET /web-version.json` → `sha`, written into the shell when it
9
+ * is published.
10
+ *
11
+ * They are read separately and shown separately because they legitimately
12
+ * differ: a release that changes only API code keeps the published shell, and
13
+ * a frontend-only release keeps the API image. Collapsing them into one
14
+ * "deployed version" would be wrong whenever that happens, which is often.
15
+ *
16
+ * Neither read needs a credential, and neither failure is the user's problem,
17
+ * so a failure is reported as "could not read" with the reason, never as an
18
+ * error that fails `doctor`.
19
+ */
20
+ export type BuildCommitRead = {
21
+ /** The 40-hex commit, or null when the server did not name one. */
22
+ commit: string | null;
23
+ /** Where it was read from, so a reader can check it themselves. */
24
+ source: string;
25
+ /** Why there is no commit: unreadable, or the server reports an unstamped build. */
26
+ error: string | null;
27
+ };
28
+ export type DeployedBuild = {
29
+ api: BuildCommitRead;
30
+ web: BuildCommitRead;
31
+ };
32
+ type FetchLike = (url: string, init?: {
33
+ headers?: Record<string, string>;
34
+ signal?: AbortSignal;
35
+ }) => Promise<{
36
+ ok: boolean;
37
+ status: number;
38
+ json: () => Promise<unknown>;
39
+ }>;
40
+ export declare function readDeployedBuild(baseURL: string, fetchImpl?: FetchLike): Promise<DeployedBuild>;
41
+ /** The lines `skrr doctor` prints under the server. */
42
+ export declare function deployedBuildLines(build: DeployedBuild): string[];
43
+ export {};
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ /**
3
+ * What the server this CLI talks to is running: the commit its API was built
4
+ * from and the commit its web shell was built from (OSK-15497).
5
+ *
6
+ * Both are public facts the server already publishes for anyone:
7
+ * - the API: `GET /api/health` → `build.commit`, baked into the image once at
8
+ * build time by the release pipeline;
9
+ * - the web: `GET /web-version.json` → `sha`, written into the shell when it
10
+ * is published.
11
+ *
12
+ * They are read separately and shown separately because they legitimately
13
+ * differ: a release that changes only API code keeps the published shell, and
14
+ * a frontend-only release keeps the API image. Collapsing them into one
15
+ * "deployed version" would be wrong whenever that happens, which is often.
16
+ *
17
+ * Neither read needs a credential, and neither failure is the user's problem,
18
+ * so a failure is reported as "could not read" with the reason, never as an
19
+ * error that fails `doctor`.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.readDeployedBuild = readDeployedBuild;
23
+ exports.deployedBuildLines = deployedBuildLines;
24
+ const COMMIT = /^[a-f0-9]{40}$/;
25
+ const TIMEOUT_MS = 5_000;
26
+ async function readCommit(url, pick, unstamped, fetchImpl) {
27
+ try {
28
+ const res = await fetchImpl(url, {
29
+ headers: { accept: 'application/json' },
30
+ signal: AbortSignal.timeout(TIMEOUT_MS),
31
+ });
32
+ if (!res.ok)
33
+ return { commit: null, source: url, error: `HTTP ${res.status}` };
34
+ const value = pick(await res.json());
35
+ if (typeof value === 'string' && COMMIT.test(value)) {
36
+ return { commit: value, source: url, error: null };
37
+ }
38
+ return { commit: null, source: url, error: unstamped };
39
+ }
40
+ catch (err) {
41
+ return {
42
+ commit: null,
43
+ source: url,
44
+ error: err instanceof Error ? err.message : String(err),
45
+ };
46
+ }
47
+ }
48
+ async function readDeployedBuild(baseURL, fetchImpl = fetch) {
49
+ const origin = baseURL.replace(/\/+$/, '');
50
+ const [api, web] = await Promise.all([
51
+ readCommit(`${origin}/api/health`, (body) => body?.build?.commit, 'the server does not report the commit its API was built from', fetchImpl),
52
+ readCommit(`${origin}/web-version.json`, (body) => body?.sha, 'the published web shell carries no commit', fetchImpl),
53
+ ]);
54
+ return { api, web };
55
+ }
56
+ const short = (sha) => sha.slice(0, 12);
57
+ /** The lines `skrr doctor` prints under the server. */
58
+ function deployedBuildLines(build) {
59
+ const describe = (read) => read.commit ? short(read.commit) : `unknown (${read.error ?? 'no commit'})`;
60
+ const lines = [`Deployed: API ${describe(build.api)} · web ${describe(build.web)}`];
61
+ if (build.api.commit && build.web.commit && build.api.commit !== build.web.commit) {
62
+ lines.push(' Built from different commits. That is normal: a release that changes only ' +
63
+ 'one of them keeps the other as it was.');
64
+ }
65
+ return lines;
66
+ }
@@ -1,4 +1,5 @@
1
1
  import type { GrantAccess, GrantAccessSource } from '@skrr-ai/data-provider';
2
+ import { type TableColumn } from './format';
2
3
  export declare const GRANT_PRINCIPAL_KINDS: readonly ["agent", "commitment"];
3
4
  export declare const GRANT_CREDENTIAL_KINDS: readonly ["env", "bearer", "basic", "json"];
4
5
  export interface GrantPrincipal {
@@ -20,7 +21,8 @@ export interface Grant {
20
21
  configured: boolean;
21
22
  kind?: string;
22
23
  envKeys: string[];
23
- fingerprint?: string;
24
+ /** When the value was last set (create or rotate). Never a digest of it. */
25
+ setAt?: string;
24
26
  } | null;
25
27
  tools: string[];
26
28
  runtime: {
@@ -58,4 +60,28 @@ export declare const grantApi: {
58
60
  * gate that could drift.
59
61
  */
60
62
  export declare function parseEnvPairs(pairs: string[]): Record<string, string>;
63
+ /** The cells every Grant table shows, in one place (OSK-15357). */
64
+ export type GrantTableCells = {
65
+ id: string;
66
+ label: string;
67
+ provider: string;
68
+ access: string;
69
+ principal: string;
70
+ credential: string;
71
+ runtime: string;
72
+ state: string;
73
+ };
74
+ /**
75
+ * One Grant as table cells. `skrr grants list` and `skrr workspaces grants`
76
+ * both read this, so a column with the same header means the same thing in
77
+ * both and the two tables can be compared by eye. They used not to: the
78
+ * workspace table printed capabilities under ACCESS while this one printed the
79
+ * declared access class.
80
+ */
81
+ export declare function grantTableCells(g: Pick<Grant, 'id' | 'label' | 'provider' | 'access' | 'accessSource' | 'credential' | 'revokedAt' | 'active'> & {
82
+ principal?: Partial<GrantPrincipal> | null;
83
+ runtime?: Partial<Grant['runtime']> | null;
84
+ }): GrantTableCells;
85
+ /** The shared columns, in order; a caller may add its own around them. */
86
+ export declare const GRANT_TABLE_COLUMNS: Array<TableColumn<GrantTableCells>>;
61
87
  export declare function renderGrantList(grants: Grant[], log: (line: string) => void): void;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.grantApi = exports.GRANT_CREDENTIAL_KINDS = exports.GRANT_PRINCIPAL_KINDS = void 0;
3
+ exports.GRANT_TABLE_COLUMNS = exports.grantApi = exports.GRANT_CREDENTIAL_KINDS = exports.GRANT_PRINCIPAL_KINDS = void 0;
4
4
  exports.parseEnvPairs = parseEnvPairs;
5
+ exports.grantTableCells = grantTableCells;
5
6
  exports.renderGrantList = renderGrantList;
6
7
  /**
7
8
  * grants.ts — client for the Grant vault (`/api/grants/*`).
@@ -51,35 +52,52 @@ function parseEnvPairs(pairs) {
51
52
  }
52
53
  return out;
53
54
  }
54
- function renderGrantList(grants, log) {
55
- if (grants.length === 0) {
56
- log('No grants.');
57
- return;
58
- }
59
- (0, format_1.renderTable)(grants.map((g) => ({
55
+ /**
56
+ * One Grant as table cells. `skrr grants list` and `skrr workspaces grants`
57
+ * both read this, so a column with the same header means the same thing in
58
+ * both and the two tables can be compared by eye. They used not to: the
59
+ * workspace table printed capabilities under ACCESS while this one printed the
60
+ * declared access class.
61
+ */
62
+ function grantTableCells(g) {
63
+ const principal = g.principal ?? null;
64
+ let state = 'expired';
65
+ if (g.revokedAt)
66
+ state = 'revoked';
67
+ else if (g.active)
68
+ state = 'active';
69
+ return {
60
70
  id: g.id,
61
71
  label: g.label,
62
- provider: g.provider,
72
+ provider: g.provider || '—',
63
73
  access: g.access
64
74
  ? `${g.access} (${g.accessSource === 'attested' ? 'attested' : 'declared, not verified'})`
65
75
  : 'undeclared',
66
- principal: g.principal.agentId || g.principal.commitmentId || g.principal.kind,
76
+ principal: principal?.agentId || principal?.commitmentId || principal?.kind || '—',
67
77
  // NAMES only. This is the audit surface; a value never reaches the CLI.
68
78
  credential: g.credential?.configured
69
79
  ? g.credential.envKeys.join(',') || g.credential.kind || 'set'
70
80
  : '-',
71
- runtime: [g.runtime.sandbox ? 'sandbox' : null, g.runtime.daemon ? 'daemon' : null]
81
+ runtime: [g.runtime?.sandbox ? 'sandbox' : null, g.runtime?.daemon ? 'daemon' : null]
72
82
  .filter(Boolean)
73
83
  .join('+') || 'none',
74
- state: g.revokedAt ? 'revoked' : g.active ? 'active' : 'expired',
75
- })), [
76
- { key: 'label', header: 'LABEL', maxWidth: 26 },
77
- { key: 'provider', header: 'PROVIDER', maxWidth: 12 },
78
- { key: 'access', header: 'ACCESS', maxWidth: 32 },
79
- { key: 'principal', header: 'PRINCIPAL', maxWidth: 24 },
80
- { key: 'credential', header: 'VARIABLES', maxWidth: 34 },
81
- { key: 'runtime', header: 'RUNTIME', maxWidth: 16 },
82
- { key: 'state', header: 'STATE', maxWidth: 8 },
83
- { key: 'id', header: 'ID', verbatim: true },
84
- ], log);
84
+ state,
85
+ };
86
+ }
87
+ /** The shared columns, in order; a caller may add its own around them. */
88
+ exports.GRANT_TABLE_COLUMNS = [
89
+ { key: 'label', header: 'LABEL', maxWidth: 26 },
90
+ { key: 'provider', header: 'PROVIDER', maxWidth: 12 },
91
+ { key: 'access', header: 'ACCESS', maxWidth: 32 },
92
+ { key: 'principal', header: 'PRINCIPAL', maxWidth: 24 },
93
+ { key: 'credential', header: 'VARIABLES', maxWidth: 34 },
94
+ { key: 'runtime', header: 'RUNTIME', maxWidth: 16 },
95
+ { key: 'state', header: 'STATE', maxWidth: 8 },
96
+ ];
97
+ function renderGrantList(grants, log) {
98
+ if (grants.length === 0) {
99
+ log('No grants.');
100
+ return;
101
+ }
102
+ (0, format_1.renderTable)(grants.map((g) => grantTableCells(g)), [...exports.GRANT_TABLE_COLUMNS, { key: 'id', header: 'ID', verbatim: true }], log);
85
103
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Whether a `skrr` invocation asked for JSON — ONE definition.
3
+ *
4
+ * `BaseCommand` decides this for a command that is running. A command that was
5
+ * never found has no `BaseCommand`: the `command_not_found` hook runs before one
6
+ * exists, so it asked nothing and printed prose to stderr, and a script that
7
+ * piped `--json` output to `jq` got "parse error: Invalid numeric literal"
8
+ * instead of the refusal (OSK-16434). Both now read the same rule, so a flag
9
+ * added to one cannot be missing from the other.
10
+ */
11
+ export declare function wantsJsonFromArgv(argv: readonly string[], env?: NodeJS.ProcessEnv): boolean;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wantsJsonFromArgv = wantsJsonFromArgv;
4
+ /**
5
+ * Whether a `skrr` invocation asked for JSON — ONE definition.
6
+ *
7
+ * `BaseCommand` decides this for a command that is running. A command that was
8
+ * never found has no `BaseCommand`: the `command_not_found` hook runs before one
9
+ * exists, so it asked nothing and printed prose to stderr, and a script that
10
+ * piped `--json` output to `jq` got "parse error: Invalid numeric literal"
11
+ * instead of the refusal (OSK-16434). Both now read the same rule, so a flag
12
+ * added to one cannot be missing from the other.
13
+ */
14
+ function wantsJsonFromArgv(argv, env = process.env) {
15
+ return (argv.includes('--json') ||
16
+ argv.includes('--ndjson') ||
17
+ env.SKY_JSON === '1' ||
18
+ env.OVERSKY_CLI_JSON === '1');
19
+ }
@@ -28,4 +28,29 @@ export declare function stripStorageFields(value: Json): Json;
28
28
  * untouched, as is JSON with nothing to strip, so existing formatting survives.
29
29
  */
30
30
  export declare function stripStorageFieldsFromText(text: string): string;
31
+ /**
32
+ * The outcome marker for a `--json` RESULT document (OSK-16425).
33
+ *
34
+ * A failure prints `{ok:false, error:{...}}` on stdout, so that agents get the
35
+ * refusal structured. A success printed whatever the server sent, with no `ok`
36
+ * at all — so a script could not test one field for both, and a projection such
37
+ * as `jq '{id, title}'` turned the error document into `{"id":null,"title":null}`,
38
+ * which reads as an empty success. The reporter's own diagnosis of a failed
39
+ * command was wrong for exactly that reason.
40
+ *
41
+ * Adding `ok` here, to a document a command already holds, is truthful by
42
+ * construction: the caller knows whether the command succeeded when it prints.
43
+ * It is NOT done to text in `BaseCommand.log`, the way storage fields are
44
+ * stripped, because `log` cannot see the exit status to come: `doctor`,
45
+ * `tasks wait`, `outbox retry` and `code install` print a document and then
46
+ * exit non-zero, and stamping `ok:true` on those would assert a success the
47
+ * exit code denies.
48
+ *
49
+ * Only an object can carry it. An array is returned as it is, and so is an
50
+ * object that already says `ok` — that command's own meaning wins. `ok` is put
51
+ * first so it is the first thing read.
52
+ */
53
+ export declare function withOk<T>(value: T, ok?: boolean): T | ({
54
+ ok: boolean;
55
+ } & Record<string, unknown>);
31
56
  export {};
@@ -20,6 +20,7 @@
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.stripStorageFields = stripStorageFields;
22
22
  exports.stripStorageFieldsFromText = stripStorageFieldsFromText;
23
+ exports.withOk = withOk;
23
24
  function usableId(value) {
24
25
  return (typeof value === 'string' && value.length > 0) || typeof value === 'number';
25
26
  }
@@ -76,3 +77,32 @@ function stripStorageFieldsFromText(text) {
76
77
  return text;
77
78
  return JSON.stringify(stripped, null, text.includes('\n') ? 2 : undefined);
78
79
  }
80
+ /**
81
+ * The outcome marker for a `--json` RESULT document (OSK-16425).
82
+ *
83
+ * A failure prints `{ok:false, error:{...}}` on stdout, so that agents get the
84
+ * refusal structured. A success printed whatever the server sent, with no `ok`
85
+ * at all — so a script could not test one field for both, and a projection such
86
+ * as `jq '{id, title}'` turned the error document into `{"id":null,"title":null}`,
87
+ * which reads as an empty success. The reporter's own diagnosis of a failed
88
+ * command was wrong for exactly that reason.
89
+ *
90
+ * Adding `ok` here, to a document a command already holds, is truthful by
91
+ * construction: the caller knows whether the command succeeded when it prints.
92
+ * It is NOT done to text in `BaseCommand.log`, the way storage fields are
93
+ * stripped, because `log` cannot see the exit status to come: `doctor`,
94
+ * `tasks wait`, `outbox retry` and `code install` print a document and then
95
+ * exit non-zero, and stamping `ok:true` on those would assert a success the
96
+ * exit code denies.
97
+ *
98
+ * Only an object can carry it. An array is returned as it is, and so is an
99
+ * object that already says `ok` — that command's own meaning wins. `ok` is put
100
+ * first so it is the first thing read.
101
+ */
102
+ function withOk(value, ok = true) {
103
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
104
+ return value;
105
+ if (Object.prototype.hasOwnProperty.call(value, 'ok'))
106
+ return value;
107
+ return { ok, ...value };
108
+ }
@@ -64,6 +64,15 @@ export declare function limitNote(requested: number | undefined, rowCount: numbe
64
64
  };
65
65
  /** The text-mode twin of a warning: one `Note:` line a person can act on. */
66
66
  export declare function warningLines(warnings: readonly ListWarning[] | undefined): string[];
67
+ /**
68
+ * The note a TABLE prints when it was given a JSON-only projection flag. `--compact` and
69
+ * `--fields` shape JSON output; on the table they did nothing and said nothing, so `--compact`
70
+ * looked broken (OSK-15374). Empty when neither flag was given.
71
+ */
72
+ export declare function textModeProjectionNotes(flags: {
73
+ fields?: string;
74
+ compact?: boolean;
75
+ }): string[];
67
76
  export type ListOutput = {
68
77
  stdout: string[];
69
78
  stderr: string[];
@@ -7,6 +7,7 @@ exports.toListEnvelope = toListEnvelope;
7
7
  exports.shapeListResponse = shapeListResponse;
8
8
  exports.limitNote = limitNote;
9
9
  exports.warningLines = warningLines;
10
+ exports.textModeProjectionNotes = textModeProjectionNotes;
10
11
  exports.renderListOutput = renderListOutput;
11
12
  /**
12
13
  * Shared output shaping for `skrr` LIST commands (agent-surface audit
@@ -29,6 +30,7 @@ exports.renderListOutput = renderListOutput;
29
30
  * Pure functions, no oclif: the commands own I/O, this owns the shape.
30
31
  */
31
32
  const core_1 = require("@oclif/core");
33
+ const json_output_1 = require("./json-output");
32
34
  /**
33
35
  * The navigation projection per resource: enough to identify a row, decide
34
36
  * whether it matters and fetch it with `show`. Allowlists, not drop-lists, so a
@@ -206,7 +208,11 @@ function shapeListResponse(response, options) {
206
208
  if (limit.warning)
207
209
  warnings.push(limit.warning);
208
210
  return {
209
- ...envelope,
211
+ // A list that reached this shaper succeeded; a failure never does, and prints
212
+ // `{ok:false, error}`. Saying so lets a script test one field for both, and
213
+ // keeps a projection of a failure from reading as an empty list (OSK-16425).
214
+ // The server's own `ok`, if it sent one, is kept.
215
+ ...(0, json_output_1.withOk)(envelope),
210
216
  data,
211
217
  ...(limit.applied !== undefined ? { limit_applied: limit.applied } : {}),
212
218
  ...(options.filterApplied ? { filter_applied: options.filterApplied } : {}),
@@ -247,6 +253,19 @@ function limitNote(requested, rowCount, hasMore) {
247
253
  function warningLines(warnings) {
248
254
  return (warnings ?? []).map((w) => `Note: ${w.hint}`);
249
255
  }
256
+ /**
257
+ * The note a TABLE prints when it was given a JSON-only projection flag. `--compact` and
258
+ * `--fields` shape JSON output; on the table they did nothing and said nothing, so `--compact`
259
+ * looked broken (OSK-15374). Empty when neither flag was given.
260
+ */
261
+ function textModeProjectionNotes(flags) {
262
+ const given = [flags.fields ? '--fields' : null, flags.compact ? '--compact' : null].filter(Boolean);
263
+ if (given.length === 0)
264
+ return [];
265
+ return [
266
+ `Note: ${given.join(' and ')} ${given.length > 1 ? 'shape' : 'shapes'} JSON output only; the table is unchanged. Add --json (or --ndjson) to apply ${given.length > 1 ? 'them' : 'it'}.`,
267
+ ];
268
+ }
250
269
  /**
251
270
  * Render a shaped list for `--json` (pretty envelope) or `--ndjson` (rows one per
252
271
  * line, `_meta` on stderr). Pure; the caller writes the lines.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Whether the local daemon is actually signed in and connected — as opposed to
3
+ * having been handed a credential (OSK-16437).
4
+ *
5
+ * `skrr login` used to print "The local daemon was signed in too" the moment
6
+ * `accept-handoff` exited 0. That means the credential was WRITTEN. It says
7
+ * nothing about a daemon using it, and a handoff that wrote a credential for a
8
+ * service that was not running, or that the server then refused, left a login
9
+ * that announced success on one line and failed to verify it on the next, with
10
+ * nothing saying which was true. `skrr daemon start` learned the same lesson
11
+ * ("a supervisor's successful `kickstart` means only that it accepted the
12
+ * request") and asks the daemon itself; this is that question for login.
13
+ *
14
+ * Two probes, in order of how directly they answer:
15
+ *
16
+ * 1. the loopback identity endpoint of THIS profile's daemon, which says it is
17
+ * connected as this account on this server — the only proof;
18
+ * 2. `skrrd status --json`, asked only once the first has not answered, to say
19
+ * WHY: the server refused the credential, the service is not running, or it
20
+ * is running and has not connected yet. Those have different fixes.
21
+ */
22
+ export type LocalDaemonReadiness = {
23
+ state: 'connected';
24
+ daemonId: string;
25
+ } | {
26
+ state: 'reauth_required';
27
+ profile: string;
28
+ reason: string | null;
29
+ command: string;
30
+ } | {
31
+ state: 'not_running';
32
+ profile: string;
33
+ runtimeStatus: string | null;
34
+ } | {
35
+ state: 'not_connected';
36
+ profile: string;
37
+ } | {
38
+ state: 'unreadable';
39
+ profile: string;
40
+ };
41
+ export interface ReadinessDeps {
42
+ /** One look at the loopback identity endpoint; the daemon id when it is connected as us. */
43
+ inspect: () => Promise<string | null>;
44
+ /** `skrrd status --json` for this profile. */
45
+ status: () => Promise<{
46
+ stdout: string;
47
+ code: number;
48
+ }>;
49
+ now: () => number;
50
+ sleep: (ms: number) => Promise<void>;
51
+ }
52
+ export interface ConfirmOptions {
53
+ /** How long a daemon that is still starting gets. */
54
+ waitMs?: number;
55
+ pollMs?: number;
56
+ /** How long a service that reads `stopped` is given before it is believed. */
57
+ stoppedGraceMs?: number;
58
+ /** Progress, printed once and only when the first look found nothing. */
59
+ log?: (line: string) => void;
60
+ deps?: Partial<ReadinessDeps>;
61
+ }
62
+ export declare function confirmLocalDaemonSignedIn(options?: ConfirmOptions): Promise<LocalDaemonReadiness>;
63
+ /**
64
+ * What `skrr login` prints about a daemon that was handed a credential and did
65
+ * not come up with it: what happened, why, and the one command. Pure, so every
66
+ * state can be read without a daemon.
67
+ */
68
+ export declare function describeUnreadyLocalDaemon(readiness: Exclude<LocalDaemonReadiness, {
69
+ state: 'connected';
70
+ }>, commands: {
71
+ status: string;
72
+ logs: string;
73
+ start: string;
74
+ }): string[];
75
+ /**
76
+ * The end of `skrr login` for a daemon that was handed a credential: wait for it
77
+ * to connect, then say ONE true thing. "Signed in too" only for a daemon that
78
+ * verified connected as this account; otherwise what happened, why, and the one
79
+ * command — and never both, which is what the login used to do.
80
+ */
81
+ export declare function reportLocalDaemonAfterHandoff(options: {
82
+ /** `skrr login` may have been handed the daemon's account/deployment line already. */
83
+ deliveredDetail: string;
84
+ bin: string;
85
+ log: (line: string) => void;
86
+ confirm?: typeof confirmLocalDaemonSignedIn;
87
+ }): Promise<{
88
+ daemonId?: string;
89
+ notReady: boolean;
90
+ }>;