@skrr-ai/cli 0.1.40 → 0.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-command.d.ts +9 -0
- package/dist/base-command.js +18 -3
- package/dist/commands/agent-shares/list-for.js +4 -11
- package/dist/commands/agent-shares/received.js +4 -11
- package/dist/commands/agent-shares/sent.js +4 -11
- package/dist/commands/agent-shares/with.js +3 -10
- package/dist/commands/agents/activity.js +1 -1
- package/dist/commands/agents/api-actions/add.js +6 -0
- package/dist/commands/agents/chat.js +6 -1
- package/dist/commands/agents/create.js +6 -1
- package/dist/commands/agents/manager/ensure.d.ts +18 -0
- package/dist/commands/agents/manager/ensure.js +56 -0
- package/dist/commands/agents/show.js +4 -0
- package/dist/commands/agents/tools/auth.d.ts +6 -0
- package/dist/commands/agents/tools/auth.js +27 -1
- package/dist/commands/agents/tools/index.js +11 -1
- package/dist/commands/agents/update.js +6 -1
- package/dist/commands/code/install.d.ts +4 -2
- package/dist/commands/code/install.js +15 -14
- package/dist/commands/convos/list.js +1 -3
- package/dist/commands/convos/show.js +4 -11
- package/dist/commands/daemon/index.js +3 -3
- package/dist/commands/daemon/install.js +4 -4
- package/dist/commands/daemon/login.js +1 -1
- package/dist/commands/daemon/logs.d.ts +3 -1
- package/dist/commands/daemon/logs.js +16 -1
- package/dist/commands/daemon/start.js +3 -3
- package/dist/commands/daemon/status.d.ts +9 -0
- package/dist/commands/daemon/status.js +22 -0
- package/dist/commands/dm/messages.js +1 -11
- package/dist/commands/followups/cancel.js +1 -1
- package/dist/commands/followups/remind.js +4 -0
- package/dist/commands/followups/resolve.js +1 -1
- package/dist/commands/followups/show.js +16 -0
- package/dist/commands/harnesses/forget.js +20 -3
- package/dist/commands/harnesses/install.d.ts +18 -0
- package/dist/commands/harnesses/install.js +146 -25
- package/dist/commands/harnesses/installers.d.ts +1 -1
- package/dist/commands/harnesses/installers.js +36 -12
- package/dist/commands/harnesses/list.d.ts +74 -2
- package/dist/commands/harnesses/list.js +231 -15
- package/dist/commands/harnesses/ping.d.ts +2 -0
- package/dist/commands/harnesses/ping.js +21 -0
- package/dist/commands/harnesses/show.d.ts +22 -0
- package/dist/commands/harnesses/show.js +116 -17
- package/dist/commands/harnesses/update.d.ts +8 -0
- package/dist/commands/harnesses/update.js +25 -1
- package/dist/commands/harnesses/usage.d.ts +2 -0
- package/dist/commands/harnesses/usage.js +7 -2
- package/dist/commands/instructions/status.js +13 -0
- package/dist/commands/instructions/update-target.d.ts +1 -0
- package/dist/commands/instructions/update-target.js +11 -3
- package/dist/commands/login.js +5 -5
- package/dist/commands/machines/list.js +8 -9
- package/dist/commands/machines/show.js +46 -17
- package/dist/commands/messages/show.js +3 -10
- package/dist/commands/shares/get.js +2 -9
- package/dist/commands/shares/list.js +1 -9
- package/dist/commands/shortcuts/show.js +4 -11
- package/dist/commands/skills/import-as-actions.js +6 -15
- package/dist/commands/skills/local.js +6 -0
- package/dist/commands/spaces/actions/run.js +3 -4
- package/dist/commands/spaces/briefs/get.js +2 -9
- package/dist/commands/spaces/briefs/list.js +1 -9
- package/dist/commands/spaces/conversations.js +1 -9
- package/dist/commands/spaces/heartbeats/list.js +1 -9
- package/dist/commands/spaces/heartbeats/status.js +2 -9
- package/dist/commands/tasks/activity.js +1 -10
- package/dist/commands/tasks/approvals/list.js +1 -9
- package/dist/commands/tasks/attachments/list.js +1 -9
- package/dist/commands/tasks/comments/list.js +1 -9
- package/dist/commands/tasks/complete.js +13 -0
- package/dist/commands/tasks/dri-status.js +2 -9
- package/dist/commands/tasks/events/append.js +9 -7
- package/dist/commands/tasks/execution-timeline.js +1 -9
- package/dist/commands/tasks/monitor.js +1 -9
- package/dist/commands/tasks/result/submit.js +14 -0
- package/dist/commands/tasks/runs.js +1 -9
- package/dist/commands/tasks/show.js +5 -12
- package/dist/commands/token/list.d.ts +8 -0
- package/dist/commands/token/list.js +51 -6
- package/dist/help.js +8 -12
- package/dist/lib/agent-home-workspace.d.ts +19 -0
- package/dist/lib/agent-home-workspace.js +39 -0
- package/dist/lib/agent-resolver.js +2 -2
- package/dist/lib/agentic-stream.d.ts +22 -1
- package/dist/lib/agentic-stream.js +48 -7
- package/dist/lib/cli-installers.d.ts +124 -2
- package/dist/lib/cli-installers.js +196 -13
- package/dist/lib/command-miss.d.ts +22 -0
- package/dist/lib/command-miss.js +78 -0
- package/dist/lib/daemon-setup.js +2 -2
- package/dist/lib/daemon-target.d.ts +14 -0
- package/dist/lib/daemon-target.js +32 -0
- package/dist/lib/daemonBroker.js +1 -1
- package/dist/lib/daemonHandoff.js +1 -1
- package/dist/lib/exec-runtime-binary.js +2 -2
- package/dist/lib/followups.d.ts +12 -0
- package/dist/lib/followups.js +14 -1
- package/dist/lib/format.d.ts +11 -0
- package/dist/lib/format.js +21 -0
- package/dist/lib/harness-labels.d.ts +39 -0
- package/dist/lib/harness-labels.js +60 -0
- package/dist/lib/harnesses.d.ts +6 -0
- package/dist/lib/instruction-input.d.ts +13 -0
- package/dist/lib/instruction-input.js +20 -1
- package/dist/lib/local-skills.d.ts +44 -1
- package/dist/lib/local-skills.js +58 -2
- package/dist/lib/login.js +5 -5
- package/dist/lib/machines.d.ts +6 -0
- package/dist/lib/machines.js +68 -2
- package/dist/lib/runtime-tool-support.d.ts +34 -0
- package/dist/lib/runtime-tool-support.js +77 -0
- package/dist/lib/share-rows.d.ts +17 -0
- package/dist/lib/share-rows.js +35 -0
- package/dist/lib/task-closure.d.ts +13 -0
- package/dist/lib/task-closure.js +17 -0
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5066 -5033
- package/oclif.manifest.json +33472 -33360
- package/package.json +6 -3
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.INSTALL_POLL_INTERVAL_MS = exports.INSTALL_MAX_WAIT_MS = void 0;
|
|
3
|
+
exports.INSTALL_POLL_INTERVAL_MS = exports.INSTALL_MAX_WAIT_MS = exports.ROW_READINESS_TIMEOUT_MS = exports.MANAGED_CLI_PROVIDER_SINCE = void 0;
|
|
4
|
+
exports.describeProvidersNotOffered = describeProvidersNotOffered;
|
|
4
5
|
exports.providersNotOffered = providersNotOffered;
|
|
5
6
|
exports.listCliInstallers = listCliInstallers;
|
|
6
7
|
exports.readinessOf = readinessOf;
|
|
8
|
+
exports.readinessByProvider = readinessByProvider;
|
|
9
|
+
exports.readinessKey = readinessKey;
|
|
10
|
+
exports.readinessForRows = readinessForRows;
|
|
7
11
|
exports.startManagedCliInstall = startManagedCliInstall;
|
|
12
|
+
exports.startManagedCliUpdate = startManagedCliUpdate;
|
|
13
|
+
exports.updateOutcomeLine = updateOutcomeLine;
|
|
8
14
|
exports.readManagedCliInstallJob = readManagedCliInstallJob;
|
|
15
|
+
exports.unseenLogLines = unseenLogLines;
|
|
16
|
+
exports.newLogLinesByCount = newLogLinesByCount;
|
|
9
17
|
exports.followManagedCliInstallJob = followManagedCliInstallJob;
|
|
10
18
|
/**
|
|
11
19
|
* The daemon's managed-CLI installer inventory, as the CLI reads it.
|
|
@@ -21,19 +29,24 @@ exports.followManagedCliInstallJob = followManagedCliInstallJob;
|
|
|
21
29
|
* The CLI called the route from exactly one place (`commands/code/install.ts`),
|
|
22
30
|
* for one provider, to read one field, and showed the user none of it. So from
|
|
23
31
|
* the CLI there was no way to learn that a harness listed as `online` had no
|
|
24
|
-
* binary at all — which is the state this account's
|
|
32
|
+
* binary at all — which is the state this account's first-party harness row was in.
|
|
25
33
|
*
|
|
26
34
|
* The shape lives here rather than beside a command because two commands read
|
|
27
35
|
* it now. Root `CLAUDE.md`: a second declaration is how a surface silently
|
|
28
36
|
* drifts from the server that produced it.
|
|
29
37
|
*/
|
|
38
|
+
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
30
39
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
31
40
|
const api_fetch_1 = require("./api-fetch");
|
|
41
|
+
/** A provider in its canonical spelling, or '' for a missing one. */
|
|
42
|
+
function canonical(value) {
|
|
43
|
+
return typeof value === 'string' ? String((0, data_provider_1.canonicalHarnessProvider)(value) ?? value) : '';
|
|
44
|
+
}
|
|
32
45
|
/**
|
|
33
46
|
* Providers the platform knows about that this daemon did not offer.
|
|
34
47
|
*
|
|
35
48
|
* The inventory is whatever the CONNECTED daemon supports, and an older daemon
|
|
36
|
-
* legitimately supports fewer: `devin` arrived in
|
|
49
|
+
* legitimately supports fewer: `devin` arrived in the 0.8.45 daemon release, so a machine
|
|
37
50
|
* running 0.8.35 answers with six providers instead of seven. Observed here —
|
|
38
51
|
* the desktop app restarted the daemon from its bundled 0.8.35 copy and Devin
|
|
39
52
|
* disappeared from the install list between two page loads, with nothing on
|
|
@@ -45,19 +58,47 @@ const api_fetch_1 = require("./api-fetch");
|
|
|
45
58
|
* is already on this side of the wire. Naming the gap is what turns "Devin is
|
|
46
59
|
* gone" into "this daemon is older than Devin".
|
|
47
60
|
*/
|
|
61
|
+
/**
|
|
62
|
+
* The first STABLE daemon release (`daemon-v*` tag) whose
|
|
63
|
+
* MANAGED_CLI_PROVIDER_IDS included each provider. Release tags, not
|
|
64
|
+
* package.json at the commit that added it: the version is bumped before a
|
|
65
|
+
* release is cut, so the commit that added devin already read 0.8.43 while the
|
|
66
|
+
* 0.8.43 release did not contain it — this table said "added in 0.8.43" to a
|
|
67
|
+
* 0.8.43 user (OSK-9537 verifier). Derived with, per provider:
|
|
68
|
+
* for t in $(git tag -l 'daemon-v*' | sort -V); do
|
|
69
|
+
* git show "$t:daemon/src/cli-installers.ts" | grep -q "'devin'" && echo "$t" && break
|
|
70
|
+
* done
|
|
71
|
+
* Providers present since managed installers first shipped (0.8.1) are omitted.
|
|
72
|
+
*/
|
|
73
|
+
exports.MANAGED_CLI_PROVIDER_SINCE = Object.freeze({
|
|
74
|
+
// First offered under its older spelling; the canonical key covers both.
|
|
75
|
+
[auth_core_1.FIRST_PARTY_HARNESS.provider]: '0.8.2',
|
|
76
|
+
devin: '0.8.45',
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* "Not offered" with the release that added each provider, so a reader learns
|
|
80
|
+
* WHICH update fixes it rather than only that one might (OSK-9537).
|
|
81
|
+
*/
|
|
82
|
+
function describeProvidersNotOffered(missing) {
|
|
83
|
+
return missing
|
|
84
|
+
.map((provider) => {
|
|
85
|
+
const since = exports.MANAGED_CLI_PROVIDER_SINCE[canonical(provider)];
|
|
86
|
+
return since ? `${provider} (added in daemon ${since})` : provider;
|
|
87
|
+
})
|
|
88
|
+
.join(', ');
|
|
89
|
+
}
|
|
48
90
|
function providersNotOffered(offered, known) {
|
|
49
91
|
// Canonicalize BOTH sides. An older daemon answers under the spelling it was
|
|
50
|
-
// built with — the 0.8.35 build reports the first-party harness
|
|
51
|
-
//
|
|
92
|
+
// built with — the 0.8.35 build reports the first-party harness under its
|
|
93
|
+
// older spelling, so a raw comparison against the current one reported it missing on
|
|
52
94
|
// a machine where it is installed and working. Caught by running this against
|
|
53
95
|
// the very daemon the finding was about: the first version of this helper
|
|
54
|
-
// said "Not offered: devin,
|
|
55
|
-
const canonical = (value) => typeof value === 'string' ? String((0, data_provider_1.canonicalHarnessProvider)(value) ?? value) : '';
|
|
96
|
+
// said "Not offered: devin, <first-party harness>" when only `devin` was true.
|
|
56
97
|
const seen = new Set((offered ?? []).map((provider) => canonical(provider.provider)).filter(Boolean));
|
|
57
98
|
return known.filter((provider) => !seen.has(canonical(provider)));
|
|
58
99
|
}
|
|
59
|
-
async function listCliInstallers(daemonId, credential) {
|
|
60
|
-
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers`, { credential: credential });
|
|
100
|
+
async function listCliInstallers(daemonId, credential, opts = {}) {
|
|
101
|
+
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers`, { credential: credential, ...(opts.signal ? { signal: opts.signal } : {}) });
|
|
61
102
|
}
|
|
62
103
|
/**
|
|
63
104
|
* The one-word readiness verdict for a provider, and whether it is good news.
|
|
@@ -95,18 +136,143 @@ function readinessOf(provider) {
|
|
|
95
136
|
if (!auth) {
|
|
96
137
|
return {
|
|
97
138
|
label: 'unknown',
|
|
98
|
-
detail:
|
|
139
|
+
detail: (0, data_provider_1.managedCliReadinessUnknownMessage)(),
|
|
99
140
|
ok: false,
|
|
100
141
|
};
|
|
101
142
|
}
|
|
102
143
|
return { label: auth.status, detail: auth.message, ok: auth.status === 'ready' };
|
|
103
144
|
}
|
|
145
|
+
/** A job the daemon runs to install one managed provider. */
|
|
146
|
+
/**
|
|
147
|
+
* Readiness per provider for one daemon, keyed by CANONICAL provider — or null
|
|
148
|
+
* when the daemon could not be asked (disconnected, older, refused).
|
|
149
|
+
*
|
|
150
|
+
* The harness row's `authStatus` is the DAEMON's standing with skrr, written
|
|
151
|
+
* across every row of a daemon at once, so `harnesses show`, `machines show`
|
|
152
|
+
* and `ping` answered "ok" per provider for a Devin its own CLI called "Not
|
|
153
|
+
* logged in" (OSK-9263, OSK-9265). This is the per-provider answer those
|
|
154
|
+
* surfaces print instead. Best-effort by design: a failure here must never
|
|
155
|
+
* fail the command that asked.
|
|
156
|
+
*/
|
|
157
|
+
async function readinessByProvider(daemonId, credential, opts = {}) {
|
|
158
|
+
let response;
|
|
159
|
+
try {
|
|
160
|
+
response = await listCliInstallers(daemonId, credential, opts);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const out = new Map();
|
|
166
|
+
for (const provider of response.providers ?? []) {
|
|
167
|
+
const key = canonical(provider.provider);
|
|
168
|
+
if (key)
|
|
169
|
+
out.set(key, readinessOf(provider));
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
/** The canonical provider a harness row names, for looking it up in that map. */
|
|
174
|
+
function readinessKey(provider) {
|
|
175
|
+
return canonical(provider);
|
|
176
|
+
}
|
|
177
|
+
/** How long one daemon may take to answer before a list prints `-` for it. */
|
|
178
|
+
exports.ROW_READINESS_TIMEOUT_MS = 5_000;
|
|
179
|
+
/**
|
|
180
|
+
* Readiness for many harness rows at once: ONE installer read per connected
|
|
181
|
+
* daemon, all in parallel, however many rows each daemon has.
|
|
182
|
+
*
|
|
183
|
+
* Only a row whose daemon can answer is asked for — online and not superseded;
|
|
184
|
+
* an offline or replaced registration has nobody on the other end. Each read is
|
|
185
|
+
* bounded, because a list must not hang on one slow machine; a daemon that does
|
|
186
|
+
* not answer in time reads as unknown (`undefined`), never as `ready`.
|
|
187
|
+
*
|
|
188
|
+
* `asked` and `answered` let a caller tell "this daemon does not report it"
|
|
189
|
+
* from "nothing could be asked", which call for different next steps.
|
|
190
|
+
*/
|
|
191
|
+
async function readinessForRows(rows, credential, opts = {}) {
|
|
192
|
+
const daemonIds = [
|
|
193
|
+
...new Set(rows
|
|
194
|
+
.filter((row) => row.status === 'online' && !row.superseded && row.daemonId)
|
|
195
|
+
.map((row) => row.daemonId)),
|
|
196
|
+
];
|
|
197
|
+
const timeoutMs = opts.timeoutMs ?? exports.ROW_READINESS_TIMEOUT_MS;
|
|
198
|
+
const answers = await Promise.all(daemonIds.map(async (daemonId) => [
|
|
199
|
+
daemonId,
|
|
200
|
+
await readinessByProvider(daemonId, credential, {
|
|
201
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
202
|
+
}),
|
|
203
|
+
]));
|
|
204
|
+
const byDaemon = new Map(answers);
|
|
205
|
+
return {
|
|
206
|
+
lookup: (row) => row.status === 'online' && !row.superseded && row.daemonId
|
|
207
|
+
? byDaemon.get(row.daemonId)?.get(readinessKey(row.provider))
|
|
208
|
+
: undefined,
|
|
209
|
+
daemonAnswered: (row) => Boolean(row.daemonId && byDaemon.get(row.daemonId)),
|
|
210
|
+
asked: daemonIds.length,
|
|
211
|
+
answered: answers.filter(([, answer]) => answer !== null).length,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
104
214
|
async function startManagedCliInstall(daemonId, provider, credential) {
|
|
105
215
|
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/${encodeURIComponent(provider)}/install`, { method: 'POST', body: {}, credential: credential });
|
|
106
216
|
}
|
|
217
|
+
/** Ask the daemon to update an installed CLI in place (OSK-10096). */
|
|
218
|
+
async function startManagedCliUpdate(daemonId, provider, credential) {
|
|
219
|
+
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/${encodeURIComponent(provider)}/update`, { method: 'POST', body: {}, credential: credential });
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* How an update job ended, from the versions the daemon recorded on it:
|
|
223
|
+
* "Updated Claude Code 2.1.270 → 2.1.280." or "Claude Code is still 2.1.270:
|
|
224
|
+
* its updater found nothing newer." Null when the job does not carry both.
|
|
225
|
+
*/
|
|
226
|
+
function updateOutcomeLine(job) {
|
|
227
|
+
const before = job.previousVersion;
|
|
228
|
+
const after = job.installedVersion;
|
|
229
|
+
if (!before || !after)
|
|
230
|
+
return null;
|
|
231
|
+
const name = job.displayName || job.provider || 'The CLI';
|
|
232
|
+
return before === after
|
|
233
|
+
? `${name} is still ${after}: its updater found nothing newer.`
|
|
234
|
+
: `Updated ${name} ${before} → ${after}.`;
|
|
235
|
+
}
|
|
107
236
|
async function readManagedCliInstallJob(daemonId, jobId, credential) {
|
|
108
237
|
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/jobs/${encodeURIComponent(jobId)}`, { credential: credential });
|
|
109
238
|
}
|
|
239
|
+
const MAX_TRACKED_LOG_LINES = 400;
|
|
240
|
+
/**
|
|
241
|
+
* The lines of a job's log tail not yet printed.
|
|
242
|
+
*
|
|
243
|
+
* The tail is a SLIDING window: once an installer writes more than the daemon
|
|
244
|
+
* keeps, earlier lines fall off the front, so an index into it skips or repeats
|
|
245
|
+
* lines. Instead, find the longest run at the start of the new tail that matches
|
|
246
|
+
* the end of what was already printed, and print only what follows it.
|
|
247
|
+
*/
|
|
248
|
+
function unseenLogLines(printed, tail) {
|
|
249
|
+
for (let overlap = Math.min(printed.length, tail.length); overlap > 0; overlap -= 1) {
|
|
250
|
+
let matches = true;
|
|
251
|
+
for (let i = 0; i < overlap; i += 1) {
|
|
252
|
+
if (printed[printed.length - overlap + i] !== tail[i]) {
|
|
253
|
+
matches = false;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (matches)
|
|
258
|
+
return tail.slice(overlap);
|
|
259
|
+
}
|
|
260
|
+
return [...tail];
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* The new lines of a job's tail when the daemon reports its running line count:
|
|
264
|
+
* exactly the last `total − printedCount`, and how many of those already fell
|
|
265
|
+
* out of the window (the daemon keeps the last 200). Unlike overlap matching,
|
|
266
|
+
* this cannot mistake a repeated line for one already printed.
|
|
267
|
+
*/
|
|
268
|
+
function newLogLinesByCount(printedCount, tail, total) {
|
|
269
|
+
const unseen = Math.max(0, total - printedCount);
|
|
270
|
+
if (unseen === 0)
|
|
271
|
+
return { lines: [], skipped: 0 };
|
|
272
|
+
if (unseen > tail.length)
|
|
273
|
+
return { lines: [...tail], skipped: unseen - tail.length };
|
|
274
|
+
return { lines: tail.slice(tail.length - unseen), skipped: 0 };
|
|
275
|
+
}
|
|
110
276
|
/** A 100 MB download over a hotel connection is the case this has to survive. */
|
|
111
277
|
exports.INSTALL_MAX_WAIT_MS = 10 * 60_000;
|
|
112
278
|
exports.INSTALL_POLL_INTERVAL_MS = 2_000;
|
|
@@ -125,16 +291,33 @@ exports.INSTALL_POLL_INTERVAL_MS = 2_000;
|
|
|
125
291
|
*/
|
|
126
292
|
async function followManagedCliInstallJob(input) {
|
|
127
293
|
const deadline = Date.now() + (input.maxWaitMs ?? exports.INSTALL_MAX_WAIT_MS);
|
|
128
|
-
let printed =
|
|
294
|
+
let printed = [];
|
|
295
|
+
let printedCount = 0;
|
|
129
296
|
for (;;) {
|
|
130
297
|
const res = await readManagedCliInstallJob(input.daemonId, input.jobId, input.credential);
|
|
131
298
|
const job = res.job;
|
|
132
299
|
if (!job)
|
|
133
300
|
return { kind: 'missing', jobId: input.jobId };
|
|
134
301
|
if (input.onLogLine) {
|
|
135
|
-
|
|
302
|
+
// `logTail`, the field the daemon sends — this read `log`, which no daemon
|
|
303
|
+
// has ever sent, so every install printed nothing until it ended (OSK-10103).
|
|
304
|
+
const tail = job.logTail ?? job.log ?? [];
|
|
305
|
+
let fresh;
|
|
306
|
+
if (typeof job.logLineCount === 'number') {
|
|
307
|
+
// Exact: the daemon says how many lines exist in total (OSK-10179).
|
|
308
|
+
const { lines, skipped } = newLogLinesByCount(printedCount, tail, job.logLineCount);
|
|
309
|
+
if (skipped > 0) {
|
|
310
|
+
input.onLogLine(`(… ${skipped} log line${skipped === 1 ? '' : 's'} not shown)`);
|
|
311
|
+
}
|
|
312
|
+
fresh = lines;
|
|
313
|
+
printedCount = Math.max(printedCount, job.logLineCount);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
fresh = unseenLogLines(printed, tail);
|
|
317
|
+
}
|
|
318
|
+
for (const line of fresh)
|
|
136
319
|
input.onLogLine(line);
|
|
137
|
-
printed =
|
|
320
|
+
printed = [...printed, ...fresh].slice(-MAX_TRACKED_LOG_LINES);
|
|
138
321
|
}
|
|
139
322
|
if (job.status === 'succeeded' || job.status === 'failed')
|
|
140
323
|
return { kind: 'settled', job };
|
|
@@ -23,6 +23,8 @@ export type CommandLookup = {
|
|
|
23
23
|
bin: string;
|
|
24
24
|
findCommand: (id: string) => {
|
|
25
25
|
flags?: Record<string, unknown>;
|
|
26
|
+
args?: Record<string, unknown>;
|
|
27
|
+
strict?: boolean;
|
|
26
28
|
} | undefined;
|
|
27
29
|
commandIDs: string[];
|
|
28
30
|
topicNames: string[];
|
|
@@ -34,6 +36,26 @@ export declare function editDistance(a: string, b: string): number;
|
|
|
34
36
|
* only a verdict.
|
|
35
37
|
*/
|
|
36
38
|
export declare function nearestCommands(id: string, all: string[], limit?: number): string[];
|
|
39
|
+
/**
|
|
40
|
+
* The command id a `--help` request is about, resolved the way oclif resolves
|
|
41
|
+
* the same words when they are RUN.
|
|
42
|
+
*
|
|
43
|
+
* The help override used to join every leading word into the id, so
|
|
44
|
+
* `skrr harnesses install devin --help` asked for `harnesses:install:devin`,
|
|
45
|
+
* found no such command, and reported that `harnesses install` "does not take
|
|
46
|
+
* the positional argument `devin`" — the positional its own help lists
|
|
47
|
+
* (OSK-10099). Running the same words works, because oclif stops collecting id
|
|
48
|
+
* words at the first resolved command that takes arguments: from there on a
|
|
49
|
+
* word is an argument, not part of the name. Adding `--help` to a command you
|
|
50
|
+
* are about to run is the commonest way to check it, so the two must agree.
|
|
51
|
+
*
|
|
52
|
+
* Mirrors `collateSpacedCmdIDFromArgs` in `@oclif/core/lib/help/util` (not a
|
|
53
|
+
* public export): a word extends the id while the longer id is a command or a
|
|
54
|
+
* prefix of one; otherwise it is appended only when the id so far is not a
|
|
55
|
+
* command that accepts arguments, so a genuinely wrong word under a command
|
|
56
|
+
* that takes none (`daemon reload`) still reaches the miss message.
|
|
57
|
+
*/
|
|
58
|
+
export declare function helpSubjectId(tokens: readonly string[], config: CommandLookup): string;
|
|
37
59
|
/**
|
|
38
60
|
* The message for a colon-joined id that did not resolve.
|
|
39
61
|
*
|
package/dist/lib/command-miss.js
CHANGED
|
@@ -22,10 +22,44 @@
|
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
23
|
exports.editDistance = editDistance;
|
|
24
24
|
exports.nearestCommands = nearestCommands;
|
|
25
|
+
exports.helpSubjectId = helpSubjectId;
|
|
25
26
|
exports.describeCommandMiss = describeCommandMiss;
|
|
26
27
|
exports.lookupFromConfig = lookupFromConfig;
|
|
27
28
|
/** A uuid-ish token, which is what a stray space id looks like. */
|
|
28
29
|
const LOOKS_LIKE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
30
|
+
const DAEMON_READINESS = [
|
|
31
|
+
{
|
|
32
|
+
id: 'harnesses:installers',
|
|
33
|
+
usage: 'harnesses installers --daemon <id>',
|
|
34
|
+
answers: 'whether each coding-agent CLI on that machine is installed, and signed in where the daemon can tell',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'daemon:status',
|
|
38
|
+
usage: 'daemon status',
|
|
39
|
+
answers: 'whether the daemon itself is running and connected',
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
const ANSWERED_ELSEWHERE = {
|
|
43
|
+
daemon: {
|
|
44
|
+
health: DAEMON_READINESS,
|
|
45
|
+
readiness: DAEMON_READINESS,
|
|
46
|
+
ready: DAEMON_READINESS,
|
|
47
|
+
doctor: DAEMON_READINESS,
|
|
48
|
+
check: DAEMON_READINESS,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
// `skrr daemons <sub>` is the same topic.
|
|
52
|
+
const ANSWERED_ELSEWHERE_BY_COMMAND = { ...ANSWERED_ELSEWHERE, daemons: ANSWERED_ELSEWHERE.daemon };
|
|
53
|
+
function describeAnsweredElsewhere(config, resolved, extras) {
|
|
54
|
+
if (extras.length !== 1)
|
|
55
|
+
return null;
|
|
56
|
+
const targets = (ANSWERED_ELSEWHERE_BY_COMMAND[resolved]?.[extras[0].toLowerCase()] ?? []).filter((target) => config.findCommand(target.id) !== undefined);
|
|
57
|
+
if (targets.length === 0)
|
|
58
|
+
return null;
|
|
59
|
+
const spoken = `${config.bin} ${resolved.split(':').join(' ')}`;
|
|
60
|
+
return (`\`${spoken}\` has no \`${extras[0]}\`. Depending on what you want to know:\n` +
|
|
61
|
+
targets.map((t) => ` \`${config.bin} ${t.usage}\` — ${t.answers}`).join('\n'));
|
|
62
|
+
}
|
|
29
63
|
/** Cheap edit distance, bounded — this runs only on a miss. */
|
|
30
64
|
function editDistance(a, b) {
|
|
31
65
|
const rows = a.length + 1;
|
|
@@ -60,6 +94,47 @@ function nearestCommands(id, all, limit = 3) {
|
|
|
60
94
|
.slice(0, limit)
|
|
61
95
|
.map(({ candidate }) => candidate));
|
|
62
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* The command id a `--help` request is about, resolved the way oclif resolves
|
|
99
|
+
* the same words when they are RUN.
|
|
100
|
+
*
|
|
101
|
+
* The help override used to join every leading word into the id, so
|
|
102
|
+
* `skrr harnesses install devin --help` asked for `harnesses:install:devin`,
|
|
103
|
+
* found no such command, and reported that `harnesses install` "does not take
|
|
104
|
+
* the positional argument `devin`" — the positional its own help lists
|
|
105
|
+
* (OSK-10099). Running the same words works, because oclif stops collecting id
|
|
106
|
+
* words at the first resolved command that takes arguments: from there on a
|
|
107
|
+
* word is an argument, not part of the name. Adding `--help` to a command you
|
|
108
|
+
* are about to run is the commonest way to check it, so the two must agree.
|
|
109
|
+
*
|
|
110
|
+
* Mirrors `collateSpacedCmdIDFromArgs` in `@oclif/core/lib/help/util` (not a
|
|
111
|
+
* public export): a word extends the id while the longer id is a command or a
|
|
112
|
+
* prefix of one; otherwise it is appended only when the id so far is not a
|
|
113
|
+
* command that accepts arguments, so a genuinely wrong word under a command
|
|
114
|
+
* that takes none (`daemon reload`) still reaches the miss message.
|
|
115
|
+
*/
|
|
116
|
+
function helpSubjectId(tokens, config) {
|
|
117
|
+
const usable = new Set();
|
|
118
|
+
for (const id of [...config.commandIDs, ...config.topicNames]) {
|
|
119
|
+
const parts = id.split(':');
|
|
120
|
+
for (let i = 1; i <= parts.length; i += 1)
|
|
121
|
+
usable.add(parts.slice(0, i).join(':'));
|
|
122
|
+
}
|
|
123
|
+
const taken = [];
|
|
124
|
+
for (const token of tokens) {
|
|
125
|
+
if (token.startsWith('-') || token.includes('='))
|
|
126
|
+
break;
|
|
127
|
+
if (usable.has([...taken, token].join(':'))) {
|
|
128
|
+
taken.push(token);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const current = taken.length > 0 ? config.findCommand(taken.join(':')) : undefined;
|
|
132
|
+
if (current && (current.strict === false || Object.keys(current.args ?? {}).length > 0))
|
|
133
|
+
break;
|
|
134
|
+
taken.push(token);
|
|
135
|
+
}
|
|
136
|
+
return taken.join(':');
|
|
137
|
+
}
|
|
63
138
|
/**
|
|
64
139
|
* The message for a colon-joined id that did not resolve.
|
|
65
140
|
*
|
|
@@ -81,6 +156,9 @@ function describeCommandMiss(config, id) {
|
|
|
81
156
|
if (!found)
|
|
82
157
|
continue;
|
|
83
158
|
const extras = parts.slice(take);
|
|
159
|
+
const redirect = describeAnsweredElsewhere(config, parts.slice(0, take).join(':'), extras);
|
|
160
|
+
if (redirect)
|
|
161
|
+
return redirect;
|
|
84
162
|
const spoken = `${config.bin} ${parts.slice(0, take).join(' ')}`;
|
|
85
163
|
// Suggest the flag only when the command actually has it, so the hint can
|
|
86
164
|
// never point at something that does not exist.
|
package/dist/lib/daemon-setup.js
CHANGED
|
@@ -62,7 +62,7 @@ async function offerDaemonSetup(opts = {}) {
|
|
|
62
62
|
}
|
|
63
63
|
const confirmImpl = opts.confirmImpl ?? prompt_1.confirm;
|
|
64
64
|
log('');
|
|
65
|
-
log(' This
|
|
65
|
+
log(' This computer has no skrr background service. It is what lets your agents work here —');
|
|
66
66
|
log(' running commands, editing files, and driving a browser on this machine.');
|
|
67
67
|
// This creates a persistent service with broad local capabilities, so a
|
|
68
68
|
// bare Enter must visibly and behaviorally decline it. `confirm` names its
|
|
@@ -100,7 +100,7 @@ async function offerDaemonSetup(opts = {}) {
|
|
|
100
100
|
if (err.message === 'runtime-binary-not-installed') {
|
|
101
101
|
return {
|
|
102
102
|
status: 'failed',
|
|
103
|
-
detail: 'the
|
|
103
|
+
detail: 'the background service vanished between fetching and installing it',
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
106
|
return { status: 'failed', detail: err.message };
|
|
@@ -99,5 +99,19 @@ export interface ResolveDaemonTargetInput {
|
|
|
99
99
|
listActiveDaemons: () => Promise<ActiveDaemonRow[]>;
|
|
100
100
|
}
|
|
101
101
|
export declare function resolveDaemonTarget(input: ResolveDaemonTargetInput): Promise<DaemonTarget>;
|
|
102
|
+
/**
|
|
103
|
+
* Daemons that are CONNECTED right now, under the id the daemon-control API
|
|
104
|
+
* takes (`daemon_…`, `dedicated-runtime-…`).
|
|
105
|
+
*
|
|
106
|
+
* `listActiveDaemons` returns Daemon RESOURCE rows: "active" means not revoked,
|
|
107
|
+
* and `id` is the resource's own id. The installer routes relay an RPC to a
|
|
108
|
+
* connected daemon by its connection id, so that list could not be used to pick
|
|
109
|
+
* one: its refusal counted 50 "active daemons" — most of them sandboxes gone for
|
|
110
|
+
* weeks — and suggested values like `d_69eb2d1e...` that `--daemon` then failed
|
|
111
|
+
* to find, and a single "active" daemon would have been auto-picked by an id no
|
|
112
|
+
* route accepts (OSK-9993). Online harness rows carry exactly the ids and the
|
|
113
|
+
* liveness needed, one row per (daemon, provider).
|
|
114
|
+
*/
|
|
115
|
+
export declare function listConnectedDaemons(credential: ResolvedCredential): Promise<ActiveDaemonRow[]>;
|
|
102
116
|
/** The same list `skrr list-daemons` prints, so a refusal can name values that work. */
|
|
103
117
|
export declare function listActiveDaemons(credential: ResolvedCredential): Promise<ActiveDaemonRow[]>;
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
46
|
exports.localMachine = localMachine;
|
|
47
47
|
exports.resolveDaemonTarget = resolveDaemonTarget;
|
|
48
|
+
exports.listConnectedDaemons = listConnectedDaemons;
|
|
48
49
|
exports.listActiveDaemons = listActiveDaemons;
|
|
49
50
|
const api_fetch_1 = require("./api-fetch");
|
|
50
51
|
const daemonBroker_1 = require("./daemonBroker");
|
|
@@ -103,6 +104,37 @@ async function resolveDaemonTarget(input) {
|
|
|
103
104
|
return { kind: 'none' };
|
|
104
105
|
return { kind: 'ambiguous', daemons };
|
|
105
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Daemons that are CONNECTED right now, under the id the daemon-control API
|
|
109
|
+
* takes (`daemon_…`, `dedicated-runtime-…`).
|
|
110
|
+
*
|
|
111
|
+
* `listActiveDaemons` returns Daemon RESOURCE rows: "active" means not revoked,
|
|
112
|
+
* and `id` is the resource's own id. The installer routes relay an RPC to a
|
|
113
|
+
* connected daemon by its connection id, so that list could not be used to pick
|
|
114
|
+
* one: its refusal counted 50 "active daemons" — most of them sandboxes gone for
|
|
115
|
+
* weeks — and suggested values like `d_69eb2d1e...` that `--daemon` then failed
|
|
116
|
+
* to find, and a single "active" daemon would have been auto-picked by an id no
|
|
117
|
+
* route accepts (OSK-9993). Online harness rows carry exactly the ids and the
|
|
118
|
+
* liveness needed, one row per (daemon, provider).
|
|
119
|
+
*/
|
|
120
|
+
async function listConnectedDaemons(credential) {
|
|
121
|
+
const res = await (0, api_fetch_1.apiFetch)('/api/harnesses?status=online', { credential });
|
|
122
|
+
const byDaemon = new Map();
|
|
123
|
+
for (const row of res.harnesses ?? []) {
|
|
124
|
+
if (row.status !== 'online' || !row.daemonId || byDaemon.has(row.daemonId))
|
|
125
|
+
continue;
|
|
126
|
+
// A harness is named "<provider> — <machine>"; the machine half names the daemon.
|
|
127
|
+
const separator = typeof row.name === 'string' ? row.name.indexOf(' — ') : -1;
|
|
128
|
+
byDaemon.set(row.daemonId, {
|
|
129
|
+
id: row.daemonId,
|
|
130
|
+
name: separator === -1 ? undefined : row.name.slice(separator + 3),
|
|
131
|
+
isActive: true,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Stable order: the server returns rows most-recently-seen first, so two
|
|
135
|
+
// commands refusing one ambiguity listed the same daemons in different orders.
|
|
136
|
+
return [...byDaemon.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
137
|
+
}
|
|
106
138
|
/** The same list `skrr list-daemons` prints, so a refusal can name values that work. */
|
|
107
139
|
async function listActiveDaemons(credential) {
|
|
108
140
|
const res = await (0, api_fetch_1.apiFetch)('/api/daemon-resources?status=active&limit=50', { credential });
|
package/dist/lib/daemonBroker.js
CHANGED
|
@@ -311,7 +311,7 @@ async function attemptDaemonBrokerLogin(opts) {
|
|
|
311
311
|
return {
|
|
312
312
|
ok: false,
|
|
313
313
|
reason: 'base_url_mismatch',
|
|
314
|
-
detail: `
|
|
314
|
+
detail: `The skrr background service on this computer is not logged in to ${opts.baseURL}; it is logged in to ${mismatchedBootstrap.serverUrl}.`,
|
|
315
315
|
daemonServerUrl: mismatchedBootstrap.serverUrl,
|
|
316
316
|
};
|
|
317
317
|
}
|
|
@@ -208,7 +208,7 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
208
208
|
return {
|
|
209
209
|
status: 'skipped',
|
|
210
210
|
reason: 'legacy-binary',
|
|
211
|
-
detail: 'a legacy oversky
|
|
211
|
+
detail: 'a legacy oversky background service is installed; bootstrap a current skrr one',
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
214
|
// An older daemon has no `accept-handoff`, and spawning it would fail with
|
|
@@ -64,7 +64,7 @@ exports.RUNTIME_BINARY_NAMES = ['skrrd', 'oversky'];
|
|
|
64
64
|
*/
|
|
65
65
|
function forwardedFlagsNote(verb, opts = {}) {
|
|
66
66
|
const base = opts.bootstrapsRuntime
|
|
67
|
-
? `If
|
|
67
|
+
? `If the background service is not on this computer yet, this command first fetches the signed release. After it is ` +
|
|
68
68
|
`installed, flags are forwarded to \`skrrd ${verb}\` unchanged; run \`skrrd ${verb} --help\` ` +
|
|
69
69
|
`for this verb's own flags, or \`skrrd --help\` for global ones such as \`--profile\`.`
|
|
70
70
|
: `Flags are forwarded to \`skrrd ${verb}\` unchanged, so they are not listed here —` +
|
|
@@ -196,7 +196,7 @@ function isExecutable(p) {
|
|
|
196
196
|
*/
|
|
197
197
|
function notInstalledMessage() {
|
|
198
198
|
return [
|
|
199
|
-
'The skrr
|
|
199
|
+
'The skrr background service is not installed on this computer.',
|
|
200
200
|
'',
|
|
201
201
|
'Install:',
|
|
202
202
|
' macOS: brew install dush1023/oversky/oversky',
|
package/dist/lib/followups.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { withQuery } from './triggers';
|
|
2
2
|
type Query = Parameters<typeof withQuery>[1];
|
|
3
3
|
type Json = Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* The line a settle verb prints. A follow-up that had already ended is said to
|
|
6
|
+
* have ended, with the terminal it actually has, rather than reported as the
|
|
7
|
+
* thing this command would have done (OSK-10038).
|
|
8
|
+
*/
|
|
9
|
+
export declare function settledLine(id: string, result: Record<string, unknown>, done: string): string;
|
|
4
10
|
/**
|
|
5
11
|
* The `id` argument every follow-up command that addresses one takes.
|
|
6
12
|
*
|
|
@@ -37,6 +43,10 @@ export interface FollowUpRow {
|
|
|
37
43
|
/** Who settled it: `trigger_fire` for the runtime, otherwise the actor. */
|
|
38
44
|
terminalBy?: string | null;
|
|
39
45
|
escalatedInitiativeId?: string | null;
|
|
46
|
+
/** The gate the fire passes: exempt_silent, exempt_user_directed, exempt_system, user_directed_guarded, autonomy_ladder, approval_ladder. */
|
|
47
|
+
gate?: string | null;
|
|
48
|
+
/** Set while its fire waits on an offline machine; it escalates after 60 minutes. */
|
|
49
|
+
parkedAt?: string | null;
|
|
40
50
|
capsule?: unknown;
|
|
41
51
|
nextRunAt?: string | null;
|
|
42
52
|
deadlineAt?: string | null;
|
|
@@ -46,6 +56,8 @@ export interface FollowUpRow {
|
|
|
46
56
|
export interface FollowUpCreateResponse {
|
|
47
57
|
followup: FollowUpRow | null;
|
|
48
58
|
reused?: boolean;
|
|
59
|
+
/** A reused reminder asked for at a different time moved to that time. */
|
|
60
|
+
rescheduled?: boolean;
|
|
49
61
|
dropped?: false | 'budget';
|
|
50
62
|
proposal?: boolean;
|
|
51
63
|
budget?: Json;
|
package/dist/lib/followups.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.followupsApi = exports.WATCH_WAIT_KINDS = exports.FOLLOWUP_RESOLVE_OUTCOMES = exports.FOLLOWUP_STATES = exports.FOLLOWUP_KINDS = exports.followUpIdArg = void 0;
|
|
4
|
+
exports.settledLine = settledLine;
|
|
4
5
|
exports.resolveFollowUpAgent = resolveFollowUpAgent;
|
|
5
6
|
exports.parseSubjectRef = parseSubjectRef;
|
|
6
7
|
exports.followupSummaryLine = followupSummaryLine;
|
|
@@ -26,6 +27,18 @@ const triggers_1 = require("./triggers");
|
|
|
26
27
|
const agent_resolver_1 = require("./agent-resolver");
|
|
27
28
|
const MOUNT = '/api/followups';
|
|
28
29
|
const base = (id) => `${MOUNT}/${encodeURIComponent(id)}`;
|
|
30
|
+
/**
|
|
31
|
+
* The line a settle verb prints. A follow-up that had already ended is said to
|
|
32
|
+
* have ended, with the terminal it actually has, rather than reported as the
|
|
33
|
+
* thing this command would have done (OSK-10038).
|
|
34
|
+
*/
|
|
35
|
+
function settledLine(id, result, done) {
|
|
36
|
+
const followup = result.followup;
|
|
37
|
+
if (result.alreadySettled === true) {
|
|
38
|
+
return `Follow-up ${id} was already settled${followup?.terminal ? ` (${followup.terminal})` : ''}; nothing changed.`;
|
|
39
|
+
}
|
|
40
|
+
return `Follow-up ${id} ${done}.`;
|
|
41
|
+
}
|
|
29
42
|
/**
|
|
30
43
|
* The `id` argument every follow-up command that addresses one takes.
|
|
31
44
|
*
|
|
@@ -79,7 +92,7 @@ function resolveFollowUpAgent(explicitAgentId, env = process.env) {
|
|
|
79
92
|
if (explicitAgentId && explicitAgentId !== sessionAgent.id) {
|
|
80
93
|
throw new agent_resolver_1.AgentResolutionError({
|
|
81
94
|
code: 'AGENT_CONTEXT_CONFLICT',
|
|
82
|
-
message: 'This
|
|
95
|
+
message: 'This session is bound to its session agent. Do not override it with another agent ID.',
|
|
83
96
|
suggestion: 'Use the bound session agent, or start a new daemon session for a different agent.',
|
|
84
97
|
details: { explicitAgentId, sessionAgentId: sessionAgent.id, source: sessionAgent.source },
|
|
85
98
|
});
|
package/dist/lib/format.d.ts
CHANGED
|
@@ -78,6 +78,17 @@ export declare function renderTable<T extends Record<string, string>>(rows: T[],
|
|
|
78
78
|
* should print it in the reader's zone and name that zone once.
|
|
79
79
|
*/
|
|
80
80
|
export declare function localStamp(value: unknown): string;
|
|
81
|
+
/**
|
|
82
|
+
* One timestamp that carries its own zone: `2026-08-21 17:54:03 PDT`.
|
|
83
|
+
*
|
|
84
|
+
* For key/value output and any row that cannot share a header naming the zone.
|
|
85
|
+
* About 25 commands formatted with `toISOString().slice(0, 19)` — UTC with no
|
|
86
|
+
* marker, which reads as local time — while `harnesses list` and `machines`
|
|
87
|
+
* printed local time with the zone named, so one CLI kept two clocks
|
|
88
|
+
* (OSK-10041). Seconds, because the commands that print these (task runs,
|
|
89
|
+
* activity, monitor) order events seconds apart. Never throws.
|
|
90
|
+
*/
|
|
91
|
+
export declare function localTime(value: unknown): string;
|
|
81
92
|
/**
|
|
82
93
|
* `PDT` — the zone `localStamp` renders in, to be named once per command
|
|
83
94
|
* rather than repeated on every row.
|