@skrr-ai/cli 0.1.87 → 0.1.89

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 (97) hide show
  1. package/dist/commands/agents/chat.js +27 -1
  2. package/dist/commands/computer/index.d.ts +19 -0
  3. package/dist/commands/computer/index.js +37 -0
  4. package/dist/commands/labels/create.d.ts +1 -0
  5. package/dist/commands/labels/create.js +2 -0
  6. package/dist/commands/labels/restore.d.ts +1 -0
  7. package/dist/commands/labels/restore.js +6 -3
  8. package/dist/commands/labels/update.d.ts +1 -0
  9. package/dist/commands/labels/update.js +2 -0
  10. package/dist/commands/machines/computer-adoption.d.ts +16 -0
  11. package/dist/commands/machines/computer-adoption.js +34 -0
  12. package/dist/commands/machines/dedicated/agent-browser.d.ts +25 -0
  13. package/dist/commands/machines/dedicated/agent-browser.js +65 -0
  14. package/dist/commands/machines/dedicated/archive.d.ts +26 -0
  15. package/dist/commands/machines/dedicated/archive.js +113 -0
  16. package/dist/commands/machines/dedicated/audit.d.ts +25 -0
  17. package/dist/commands/machines/dedicated/audit.js +113 -0
  18. package/dist/commands/machines/dedicated/computer.d.ts +23 -0
  19. package/dist/commands/machines/dedicated/computer.js +64 -0
  20. package/dist/commands/machines/dedicated/cp.d.ts +2 -0
  21. package/dist/commands/machines/dedicated/cp.js +74 -2
  22. package/dist/commands/machines/dedicated/git-status.d.ts +15 -0
  23. package/dist/commands/machines/dedicated/git-status.js +56 -0
  24. package/dist/commands/machines/dedicated/index.js +15 -1
  25. package/dist/commands/machines/dedicated/ls.d.ts +27 -0
  26. package/dist/commands/machines/dedicated/ls.js +100 -0
  27. package/dist/commands/machines/dedicated/mkdir.d.ts +18 -0
  28. package/dist/commands/machines/dedicated/mkdir.js +57 -0
  29. package/dist/commands/machines/dedicated/mv.d.ts +24 -0
  30. package/dist/commands/machines/dedicated/mv.js +83 -0
  31. package/dist/commands/machines/dedicated/rm.d.ts +26 -0
  32. package/dist/commands/machines/dedicated/rm.js +100 -0
  33. package/dist/commands/machines/dedicated/search.d.ts +24 -0
  34. package/dist/commands/machines/dedicated/search.js +78 -0
  35. package/dist/commands/machines/dedicated/show.js +17 -2
  36. package/dist/commands/machines/dedicated/stat.d.ts +22 -0
  37. package/dist/commands/machines/dedicated/stat.js +92 -0
  38. package/dist/commands/machines/hosted/index.js +1 -1
  39. package/dist/commands/machines/hosted/list.js +1 -1
  40. package/dist/commands/machines/hosted/start.js +1 -1
  41. package/dist/commands/machines/services/declare.d.ts +24 -0
  42. package/dist/commands/machines/services/declare.js +73 -0
  43. package/dist/commands/machines/services/index.d.ts +15 -0
  44. package/dist/commands/machines/services/index.js +31 -0
  45. package/dist/commands/machines/services/ls.d.ts +17 -0
  46. package/dist/commands/machines/services/ls.js +60 -0
  47. package/dist/commands/machines/services/withdraw.d.ts +19 -0
  48. package/dist/commands/machines/services/withdraw.js +47 -0
  49. package/dist/commands/machines/share.d.ts +29 -0
  50. package/dist/commands/machines/share.js +100 -0
  51. package/dist/commands/machines/shared.d.ts +19 -0
  52. package/dist/commands/machines/shared.js +62 -0
  53. package/dist/commands/machines/shares.d.ts +16 -0
  54. package/dist/commands/machines/shares.js +69 -0
  55. package/dist/commands/machines/unshare.d.ts +19 -0
  56. package/dist/commands/machines/unshare.js +61 -0
  57. package/dist/commands/tasks/labels/create.d.ts +1 -0
  58. package/dist/commands/tasks/labels/create.js +3 -0
  59. package/dist/commands/views/create.d.ts +1 -0
  60. package/dist/commands/views/create.js +5 -0
  61. package/dist/lib/agent-home-workspace.d.ts +7 -0
  62. package/dist/lib/agent-home-workspace.js +147 -0
  63. package/dist/lib/computer-adoption.d.ts +60 -0
  64. package/dist/lib/computer-adoption.js +72 -0
  65. package/dist/lib/computer-consent.d.ts +55 -0
  66. package/dist/lib/computer-consent.js +154 -0
  67. package/dist/lib/computer-files.d.ts +239 -0
  68. package/dist/lib/computer-files.js +707 -0
  69. package/dist/lib/dedicated-copy.d.ts +45 -9
  70. package/dist/lib/dedicated-copy.js +141 -44
  71. package/dist/lib/dedicated-machines.d.ts +80 -0
  72. package/dist/lib/dedicated-machines.js +137 -6
  73. package/dist/lib/exec-runtime-binary.d.ts +3 -1
  74. package/dist/lib/exec-runtime-binary.js +16 -2
  75. package/dist/lib/label-scope.d.ts +12 -0
  76. package/dist/lib/label-scope.js +15 -1
  77. package/dist/lib/machine-audit.d.ts +34 -0
  78. package/dist/lib/machine-audit.js +50 -0
  79. package/dist/lib/machine-grants.d.ts +70 -0
  80. package/dist/lib/machine-grants.js +47 -0
  81. package/dist/lib/machine-services.d.ts +64 -0
  82. package/dist/lib/machine-services.js +60 -0
  83. package/dist/lib/task-view-render.d.ts +2 -0
  84. package/dist/lib/task-view-render.js +1 -1
  85. package/dist/lib/views/vocabulary.d.ts +1 -1
  86. package/dist/lib/views/vocabulary.js +2 -1
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +1050 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +1196 -0
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +7 -0
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +1050 -0
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +1175 -0
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +7 -0
  93. package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
  94. package/dist/node_modules/@skrr-ai/data-provider/index.js +22391 -20727
  95. package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
  96. package/oclif.manifest.json +14767 -13095
  97. package/package.json +2 -2
@@ -1,8 +1,45 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.applyAgentHomeWorkspaceDefault = applyAgentHomeWorkspaceDefault;
4
37
  exports.lookupAgentHomeWorkspaceDefault = lookupAgentHomeWorkspaceDefault;
5
38
  exports.commitAgentHomeWorkspaceDefault = commitAgentHomeWorkspaceDefault;
39
+ exports.serverAppliesAgentHomeDefault = serverAppliesAgentHomeDefault;
40
+ exports.recordAgentHomeDefaultObservation = recordAgentHomeDefaultObservation;
41
+ const fs = __importStar(require("node:fs"));
42
+ const path = __importStar(require("node:path"));
6
43
  /**
7
44
  * The workspace an `agents chat` turn runs in when the caller named none.
8
45
  *
@@ -64,3 +101,113 @@ function commitAgentHomeWorkspaceDefault(workspaceId, env = process.env) {
64
101
  env.OVERSKY_WORKSPACE_ID = workspaceId;
65
102
  return workspaceId;
66
103
  }
104
+ /**
105
+ * OSK-13599 — whether the server this CLI talks to chooses the agent-home
106
+ * workspace itself.
107
+ *
108
+ * A server that does (it defaults a headerless `POST /api/agentic/chat` to the
109
+ * agent's CURRENT home workspace, under exactly the checks the header would
110
+ * pass) makes the pre-flight agent read above redundant, and that read is a
111
+ * full round trip (~480ms at production RTT) on every turn.
112
+ *
113
+ * What is remembered is a fact about the SERVER, never the agent's workspace:
114
+ * agents move between workspaces, so caching their workspace could start a turn
115
+ * in the wrong one, while "this origin applies the default" stays true until
116
+ * the server is rolled back. The evidence is the acceptance itself — a server
117
+ * that applies the default always answers with a `workspaceSource` key (null
118
+ * included); an older one never does. So:
119
+ *
120
+ * - first turn against an origin: no observation → the agent read happens,
121
+ * exactly as before; the acceptance records the observation;
122
+ * - later turns: the read is skipped and the server chooses;
123
+ * - a rollback: the acceptance lacks the key, the observation is dropped and
124
+ * the NEXT turn reads again. The one turn in between ran with no workspace
125
+ * (the pre-OSK-9937 behaviour), which the command reports on stderr.
126
+ *
127
+ * Best-effort everywhere: an unreadable or unwritable cache is "not observed",
128
+ * which is today's behaviour, never an error.
129
+ */
130
+ const SERVER_FEATURES_FILE = 'server-features.json';
131
+ /** Re-confirm at most this often, so a supported server is not a write per turn. */
132
+ const OBSERVATION_REFRESH_MS = 24 * 60 * 60 * 1000;
133
+ /** An observation nobody has re-confirmed for this long is not trusted. */
134
+ const OBSERVATION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
135
+ function originOf(baseURL) {
136
+ try {
137
+ return new URL(baseURL).origin;
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ }
143
+ function featuresPath(cacheDir) {
144
+ return path.join(cacheDir, SERVER_FEATURES_FILE);
145
+ }
146
+ function readFeatures(cacheDir) {
147
+ try {
148
+ const parsed = JSON.parse(fs.readFileSync(featuresPath(cacheDir), 'utf8'));
149
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
150
+ ? parsed
151
+ : {};
152
+ }
153
+ catch {
154
+ return {};
155
+ }
156
+ }
157
+ /** Does this origin's server default a headerless chat turn's workspace itself? */
158
+ function serverAppliesAgentHomeDefault(cacheDir, baseURL, now = Date.now()) {
159
+ if (!cacheDir || !baseURL)
160
+ return false;
161
+ const origin = originOf(baseURL);
162
+ if (!origin)
163
+ return false;
164
+ const seen = readFeatures(cacheDir)[origin];
165
+ if (seen?.agentHomeWorkspaceDefault !== true)
166
+ return false;
167
+ const at = Date.parse(seen.observedAt);
168
+ return Number.isFinite(at) && now - at <= OBSERVATION_MAX_AGE_MS;
169
+ }
170
+ /**
171
+ * Record what an acceptance says about the server, and return it. Writes only
172
+ * when the verdict changes or the observation is getting old.
173
+ */
174
+ function recordAgentHomeDefaultObservation(cacheDir, baseURL, accepted, now = Date.now()) {
175
+ const supported = !!accepted && typeof accepted === 'object' && 'workspaceSource' in accepted;
176
+ const verdict = supported ? 'supported' : 'unsupported';
177
+ if (!cacheDir || !baseURL)
178
+ return verdict;
179
+ const origin = originOf(baseURL);
180
+ if (!origin)
181
+ return verdict;
182
+ try {
183
+ const features = readFeatures(cacheDir);
184
+ const previous = features[origin];
185
+ if (!supported && !previous)
186
+ return verdict;
187
+ const previousAt = previous ? Date.parse(previous.observedAt) : NaN;
188
+ if (supported &&
189
+ previous?.agentHomeWorkspaceDefault === true &&
190
+ Number.isFinite(previousAt) &&
191
+ now - previousAt < OBSERVATION_REFRESH_MS) {
192
+ return verdict;
193
+ }
194
+ if (supported) {
195
+ features[origin] = {
196
+ agentHomeWorkspaceDefault: true,
197
+ observedAt: new Date(now).toISOString(),
198
+ };
199
+ }
200
+ else {
201
+ delete features[origin];
202
+ }
203
+ fs.mkdirSync(cacheDir, { recursive: true, mode: 0o700 });
204
+ const file = featuresPath(cacheDir);
205
+ const tmp = `${file}.${process.pid}.${now}.tmp`;
206
+ fs.writeFileSync(tmp, JSON.stringify(features), { mode: 0o600 });
207
+ fs.renameSync(tmp, file);
208
+ }
209
+ catch {
210
+ // Best-effort: the next turn simply reads the agent again.
211
+ }
212
+ return verdict;
213
+ }
@@ -0,0 +1,60 @@
1
+ /** One window's counts, for all daemons and for field daemons (not Dedicated Runtime guests). */
2
+ export interface ComputerAdoptionCounts {
3
+ active: number;
4
+ computerOnly: number;
5
+ both: number;
6
+ legacyOnly: number;
7
+ neither: number;
8
+ legacyOnlyShare: number | null;
9
+ announcingOldShare: number | null;
10
+ }
11
+ /** One client build that opened a `browser:liveview:*` view inside the window (OSK-13616). */
12
+ export interface ComputerLiveViewClient {
13
+ platform: string;
14
+ os: string;
15
+ version: string;
16
+ lastSeenAt: string;
17
+ }
18
+ /** The client half of the retirement criterion, from socket client identity. */
19
+ export interface ComputerLiveViewClients {
20
+ readable: boolean;
21
+ windowDays: number;
22
+ /** When the record began; the half is only measurable once it covers a whole window. */
23
+ recordingSince?: string;
24
+ coversWindow?: boolean;
25
+ clients: ComputerLiveViewClient[];
26
+ mobileVersions?: string[];
27
+ /** Clients that sent no identity: possibly an old mobile build. */
28
+ unattributed?: number;
29
+ mobileHalfMet: boolean | null;
30
+ }
31
+ /** `GET /api/machines/computer-adoption` — admin only (OSK-13578, OSK-13616). */
32
+ export interface ComputerAdoption {
33
+ generatedAt: string;
34
+ windows: Record<string, {
35
+ all: ComputerAdoptionCounts;
36
+ field: ComputerAdoptionCounts;
37
+ }>;
38
+ announced: Record<string, number>;
39
+ /** Absent from a server older than OSK-13616. */
40
+ liveViewClients?: ComputerLiveViewClients;
41
+ criterion: {
42
+ maxLegacyOnlyShare: number;
43
+ windowDays: number;
44
+ mobileMoved: string;
45
+ oldFamilies: string[];
46
+ daemonHalfMet: boolean | null;
47
+ mobileHalfMet?: boolean | null;
48
+ /** Set once the families were retired; absent from a server before OSK-13509. */
49
+ retired?: {
50
+ on: string;
51
+ reason: string;
52
+ };
53
+ };
54
+ }
55
+ /**
56
+ * How many daemons seen recently are still on the old event families rather
57
+ * than the computer protocol — the number the families' retirement waits on.
58
+ */
59
+ export declare function getComputerAdoption(): Promise<ComputerAdoption>;
60
+ export declare function computerAdoptionLines(read: ComputerAdoption): string[];
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getComputerAdoption = getComputerAdoption;
4
+ exports.computerAdoptionLines = computerAdoptionLines;
5
+ const api_fetch_1 = require("./api-fetch");
6
+ /**
7
+ * How many daemons seen recently are still on the old event families rather
8
+ * than the computer protocol — the number the families' retirement waits on.
9
+ */
10
+ async function getComputerAdoption() {
11
+ return (0, api_fetch_1.apiFetch)('/api/machines/computer-adoption');
12
+ }
13
+ function percent(share) {
14
+ return share === null ? 'n/a (no active daemons)' : `${(share * 100).toFixed(1)}%`;
15
+ }
16
+ function countsLines(label, counts) {
17
+ return [
18
+ ` ${label}: ${counts.active} active`,
19
+ ` computer only ${counts.computerOnly} · both ${counts.both} · old families only ${counts.legacyOnly} · neither ${counts.neither}`,
20
+ ` still on the old families only: ${percent(counts.legacyOnlyShare)} announcing an old family: ${percent(counts.announcingOldShare)}`,
21
+ ];
22
+ }
23
+ function mobileHalfLine(read) {
24
+ const clients = read.liveViewClients;
25
+ if (!clients)
26
+ return ` mobile half: ${read.criterion.mobileMoved}`;
27
+ if (!clients.readable)
28
+ return ' mobile half: not measurable: the client record could not be read';
29
+ const versions = clients.mobileVersions ?? [];
30
+ if (clients.mobileHalfMet === false) {
31
+ return ` mobile half (no mobile build opened a live view in ${clients.windowDays} days): NOT met — still on it: ${versions.join(', ')}`;
32
+ }
33
+ if (clients.mobileHalfMet === null && clients.coversWindow === false) {
34
+ return ` mobile half: not measurable yet: the record began ${clients.recordingSince ?? 'recently'} and needs ${clients.windowDays} days`;
35
+ }
36
+ if (clients.mobileHalfMet === null) {
37
+ return ` mobile half: not measurable: ${clients.unattributed ?? 0} client build(s) opened a live view without saying what they are`;
38
+ }
39
+ return ` mobile half (no mobile build opened a live view in ${clients.windowDays} days): MET`;
40
+ }
41
+ function clientLines(read) {
42
+ const clients = read.liveViewClients;
43
+ if (!clients?.readable || clients.clients.length === 0)
44
+ return [];
45
+ return [
46
+ '',
47
+ `Client builds that opened a browser:liveview:* view in the last ${clients.windowDays}d`,
48
+ ...clients.clients.map((client) => ` ${client.platform} ${client.os} ${client.version} last ${client.lastSeenAt}`),
49
+ ];
50
+ }
51
+ function computerAdoptionLines(read) {
52
+ const lines = [`Computer protocol adoption (${read.generatedAt})`];
53
+ for (const [window, scopes] of Object.entries(read.windows)) {
54
+ lines.push('', `Seen in the last ${window}`);
55
+ lines.push(...countsLines('all daemons', scopes.all));
56
+ lines.push(...countsLines('field daemons (no guests)', scopes.field));
57
+ }
58
+ const { criterion } = read;
59
+ let verdict = 'NOT met';
60
+ if (criterion.daemonHalfMet === null)
61
+ verdict = 'not measurable: no active daemons';
62
+ else if (criterion.daemonHalfMet)
63
+ verdict = 'MET';
64
+ lines.push('', `Retirement of ${criterion.oldFamilies.join(', ')}`);
65
+ if (criterion.retired) {
66
+ // The families are gone: the counts above are who it stranded, not who is still served.
67
+ lines.push(` RETIRED ${criterion.retired.on}: ${criterion.retired.reason}`, ' old-families-only daemons above can reach no terminal or browser; each is told to update when it registers');
68
+ }
69
+ lines.push(` daemon half (< ${(criterion.maxLegacyOnlyShare * 100).toFixed(0)}% of daemons active in ${criterion.windowDays} days on the old families only): ${verdict}`, mobileHalfLine(read));
70
+ lines.push(...clientLines(read));
71
+ return lines;
72
+ }
@@ -0,0 +1,55 @@
1
+ import { Command } from '@oclif/core';
2
+ import { type RuntimeBinaryOutput } from './exec-runtime-binary';
3
+ /**
4
+ * The bridge for `skrr computer` — the machine-local consent for the
5
+ * computer's new surfaces on a personal machine (design §18, D11;
6
+ * OSK-13532): the file surface and a person's own browser windows.
7
+ *
8
+ * The consent is a decision made ON the machine, so it is implemented by the
9
+ * local daemon (`skrrd computer allow|deny|status`) writing daemon.json —
10
+ * never by a REST route, because nothing sent from the cloud may grant it.
11
+ * Keeping the CLI a thin forwarder preserves that boundary: `skrr` carries
12
+ * the verb to the same binary `skrrd` runs it on.
13
+ */
14
+ /** Bound on the `skrrd status --json` probe: it is a read, and must never stall the command. */
15
+ export declare const DAEMON_STATUS_PROBE_TIMEOUT_MS = 8000;
16
+ /** What `skrrd status --json` says about the daemon that would answer. */
17
+ export interface AnsweringDaemon {
18
+ /** The binary `skrr` forwards to. */
19
+ binary: string | null;
20
+ /** The on-disk binary's version (`statusCommandVersion`), when it said. */
21
+ binaryVersion: string | null;
22
+ /** The running daemon's version (`liveVersion`/`version`), when it said. */
23
+ runningVersion: string | null;
24
+ daemonId: string | null;
25
+ profile: string | null;
26
+ /** `update.state` — `source-build`, `update-available`, `restart-pending`, … */
27
+ updateState: string | null;
28
+ /** `update.commands`: the install-method-correct way to update THIS daemon. */
29
+ updateCommands: string[];
30
+ restartPending: boolean;
31
+ }
32
+ export interface ComputerConsentDeps {
33
+ supportsSubcommand: (subcommand: string) => Promise<boolean | null>;
34
+ exec: (args: string[]) => Promise<number>;
35
+ capture: (args: string[], opts?: {
36
+ timeoutMs?: number;
37
+ }) => Promise<RuntimeBinaryOutput>;
38
+ findBinary: () => string | null;
39
+ }
40
+ /**
41
+ * Read `skrrd status --json` for the facts a person needs to know WHICH daemon
42
+ * answered and how it updates. Null when the status cannot be read — the
43
+ * caller then says what it can without inventing the rest.
44
+ */
45
+ export declare function readAnsweringDaemon(passthrough?: readonly string[], deps?: ComputerConsentDeps): Promise<AnsweringDaemon | null>;
46
+ /** "skrrd 0.8.103 at /path (daemon_…, running 0.8.90)" — whatever of that is known. */
47
+ export declare function describeAnsweringDaemon(daemon: AnsweringDaemon | null, binary: string | null): string;
48
+ /**
49
+ * What to tell a person whose local daemon has no `computer` verb: which
50
+ * daemon answered, and the update path for THAT install (OSK-13822). A source
51
+ * build updates by rebuilding its checkout — `skrrd status --json` already
52
+ * carries that recipe under `update.commands` — not by reinstalling.
53
+ */
54
+ export declare function computerUnsupportedMessage(daemon: AnsweringDaemon | null, binary: string | null): string;
55
+ export declare function forwardComputerConsent(command: Command, passthrough: readonly string[], deps?: ComputerConsentDeps): Promise<void>;
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DAEMON_STATUS_PROBE_TIMEOUT_MS = void 0;
4
+ exports.readAnsweringDaemon = readAnsweringDaemon;
5
+ exports.describeAnsweringDaemon = describeAnsweringDaemon;
6
+ exports.computerUnsupportedMessage = computerUnsupportedMessage;
7
+ exports.forwardComputerConsent = forwardComputerConsent;
8
+ const exec_runtime_binary_1 = require("./exec-runtime-binary");
9
+ /**
10
+ * The bridge for `skrr computer` — the machine-local consent for the
11
+ * computer's new surfaces on a personal machine (design §18, D11;
12
+ * OSK-13532): the file surface and a person's own browser windows.
13
+ *
14
+ * The consent is a decision made ON the machine, so it is implemented by the
15
+ * local daemon (`skrrd computer allow|deny|status`) writing daemon.json —
16
+ * never by a REST route, because nothing sent from the cloud may grant it.
17
+ * Keeping the CLI a thin forwarder preserves that boundary: `skrr` carries
18
+ * the verb to the same binary `skrrd` runs it on.
19
+ */
20
+ /** Bound on the `skrrd status --json` probe: it is a read, and must never stall the command. */
21
+ exports.DAEMON_STATUS_PROBE_TIMEOUT_MS = 8_000;
22
+ const defaultDeps = {
23
+ supportsSubcommand: exec_runtime_binary_1.runtimeBinarySupportsSubcommand,
24
+ exec: (args) => (0, exec_runtime_binary_1.execRuntimeBinary)(args),
25
+ capture: (args, opts) => (0, exec_runtime_binary_1.execRuntimeBinaryCapture)(args, opts),
26
+ findBinary: exec_runtime_binary_1.findRuntimeBinary,
27
+ };
28
+ function text(value) {
29
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
30
+ }
31
+ /**
32
+ * Read `skrrd status --json` for the facts a person needs to know WHICH daemon
33
+ * answered and how it updates. Null when the status cannot be read — the
34
+ * caller then says what it can without inventing the rest.
35
+ */
36
+ async function readAnsweringDaemon(passthrough = [], deps = defaultDeps) {
37
+ let output;
38
+ try {
39
+ output = await deps.capture([...profileArgs(passthrough), 'status', '--json'], {
40
+ timeoutMs: exports.DAEMON_STATUS_PROBE_TIMEOUT_MS,
41
+ });
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ let status;
47
+ try {
48
+ status = JSON.parse(output.stdout);
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ if (!status || typeof status !== 'object')
54
+ return null;
55
+ const update = status.update && typeof status.update === 'object'
56
+ ? status.update
57
+ : {};
58
+ return {
59
+ binary: deps.findBinary(),
60
+ binaryVersion: text(status.statusCommandVersion),
61
+ runningVersion: text(status.liveVersion) ?? text(status.version),
62
+ daemonId: text(status.daemonId),
63
+ profile: text(status.profile),
64
+ updateState: text(update.state),
65
+ updateCommands: Array.isArray(update.commands)
66
+ ? update.commands.filter((c) => typeof c === 'string' && c.trim() !== '')
67
+ : [],
68
+ restartPending: update.restartPending === true,
69
+ };
70
+ }
71
+ /** `--profile <name>` from the pass-through, so the status probe asks the same daemon. */
72
+ function profileArgs(passthrough) {
73
+ for (let i = 0; i < passthrough.length; i += 1) {
74
+ const arg = passthrough[i];
75
+ if (arg === '--profile' && passthrough[i + 1])
76
+ return ['--profile', passthrough[i + 1]];
77
+ if (arg.startsWith('--profile='))
78
+ return ['--profile', arg.slice('--profile='.length)];
79
+ }
80
+ return [];
81
+ }
82
+ /** "skrrd 0.8.103 at /path (daemon_…, running 0.8.90)" — whatever of that is known. */
83
+ function describeAnsweringDaemon(daemon, binary) {
84
+ const where = daemon?.binary ?? binary;
85
+ const parts = [];
86
+ parts.push(`${where ?? 'skrrd'}${daemon?.binaryVersion ? ` (v${daemon.binaryVersion} on disk)` : ''}`);
87
+ if (daemon?.runningVersion && daemon.runningVersion !== daemon.binaryVersion) {
88
+ parts.push(`running daemon v${daemon.runningVersion}`);
89
+ }
90
+ else if (daemon?.runningVersion && !daemon.binaryVersion) {
91
+ parts.push(`v${daemon.runningVersion}`);
92
+ }
93
+ if (daemon?.daemonId)
94
+ parts.push(daemon.daemonId);
95
+ return parts.join(', ');
96
+ }
97
+ /**
98
+ * What to tell a person whose local daemon has no `computer` verb: which
99
+ * daemon answered, and the update path for THAT install (OSK-13822). A source
100
+ * build updates by rebuilding its checkout — `skrrd status --json` already
101
+ * carries that recipe under `update.commands` — not by reinstalling.
102
+ */
103
+ function computerUnsupportedMessage(daemon, binary) {
104
+ const lines = [
105
+ `The local daemon does not support \`skrr computer\` yet: ${describeAnsweringDaemon(daemon, binary)}.`,
106
+ ];
107
+ if (daemon?.updateState === 'source-build') {
108
+ const recipe = daemon.updateCommands.length
109
+ ? daemon.updateCommands
110
+ : ['cd <daemon-dir> && git pull && npm run build'];
111
+ lines.push('It runs from a source checkout, which updates by rebuilding:');
112
+ for (const command of recipe)
113
+ lines.push(` ${command}`);
114
+ lines.push('Then restart it:', ' skrr daemon restart');
115
+ }
116
+ else if (daemon?.restartPending) {
117
+ lines.push('A newer daemon is already on disk; the running one predates it. Restart it:', ' skrr daemon restart');
118
+ }
119
+ else if (daemon && daemon.updateCommands.length > 0) {
120
+ lines.push('Update it:');
121
+ for (const command of daemon.updateCommands)
122
+ lines.push(` ${command}`);
123
+ lines.push('Then restart it:', ' skrr daemon restart');
124
+ }
125
+ else {
126
+ lines.push('Update it (`skrr daemon update`, or reinstall it), restart it (`skrr daemon restart`), then retry.');
127
+ }
128
+ return lines.join('\n');
129
+ }
130
+ async function forwardComputerConsent(command, passthrough, deps = defaultDeps) {
131
+ try {
132
+ // Older daemons treated unknown subcommands as a daemon startup in some
133
+ // releases. Probe the command list before forwarding so a consent verb
134
+ // cannot turn into a surprising service mutation.
135
+ if ((await deps.supportsSubcommand('computer')) === false) {
136
+ const daemon = await readAnsweringDaemon(passthrough, deps);
137
+ command.error(computerUnsupportedMessage(daemon, deps.findBinary()), { exit: 2 });
138
+ }
139
+ if (passthrough.includes('status')) {
140
+ // Which daemon answered, before its answer — on stderr, so `--json`
141
+ // output stays exactly what the daemon printed.
142
+ const daemon = await readAnsweringDaemon(passthrough, deps);
143
+ command.logToStderr(`Answered by: ${describeAnsweringDaemon(daemon, deps.findBinary())}`);
144
+ }
145
+ command.exit(await deps.exec(['computer', ...passthrough]));
146
+ }
147
+ catch (error) {
148
+ if (error.message === 'runtime-binary-not-installed') {
149
+ command.log((0, exec_runtime_binary_1.notInstalledMessage)());
150
+ command.exit(127);
151
+ }
152
+ throw error;
153
+ }
154
+ }