@skrr-ai/cli 0.1.43 → 0.1.44
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 +2 -0
- package/dist/base-command.js +1 -0
- package/dist/commands/balance/index.js +1 -1
- package/dist/commands/balance/show.d.ts +32 -0
- package/dist/commands/balance/show.js +74 -3
- package/dist/commands/code/handover.d.ts +1 -0
- package/dist/commands/code/handover.js +4 -0
- package/dist/commands/code/jobs/run.d.ts +1 -0
- package/dist/commands/code/jobs/run.js +4 -0
- package/dist/commands/daemon/install.js +7 -0
- package/dist/commands/harnesses/leases/show.js +14 -0
- package/dist/commands/instructions/install.d.ts +22 -0
- package/dist/commands/instructions/install.js +83 -7
- package/dist/commands/instructions/list.js +5 -0
- package/dist/commands/instructions/show.d.ts +6 -0
- package/dist/commands/instructions/show.js +34 -1
- package/dist/commands/instructions/status.js +17 -1
- package/dist/commands/payments/wallet.js +2 -2
- package/dist/commands/tasks/create.d.ts +6 -0
- package/dist/commands/tasks/create.js +26 -3
- package/dist/commands/tasks/labels/attach.js +4 -0
- package/dist/commands/tasks/list.d.ts +1 -0
- package/dist/commands/tasks/list.js +9 -0
- package/dist/commands/tasks/show.d.ts +23 -0
- package/dist/commands/tasks/show.js +61 -1
- package/dist/commands/tasks/update.js +12 -0
- package/dist/commands/views/create.js +12 -2
- package/dist/commands/views/list.js +3 -2
- package/dist/commands/views/show.js +2 -0
- package/dist/lib/agentic-stream.d.ts +10 -4
- package/dist/lib/agentic-stream.js +25 -11
- package/dist/lib/cli-installers.js +9 -1
- package/dist/lib/daemon-setup.d.ts +18 -1
- package/dist/lib/daemon-setup.js +33 -1
- package/dist/lib/first-party-harness-agent.d.ts +16 -1
- package/dist/lib/first-party-harness-agent.js +41 -12
- package/dist/lib/first-party-harness-doctor.js +34 -16
- package/dist/lib/first-party-harness.d.ts +18 -11
- package/dist/lib/first-party-harness.js +26 -21
- package/dist/lib/harnesses.d.ts +6 -0
- package/dist/lib/instruction-input.d.ts +21 -0
- package/dist/lib/instruction-input.js +30 -0
- package/dist/lib/instruction-provenance.d.ts +54 -0
- package/dist/lib/instruction-provenance.js +84 -0
- package/dist/lib/task-view-render.d.ts +14 -0
- package/dist/lib/task-view-render.js +55 -0
- package/dist/lib/tasks.d.ts +18 -0
- package/dist/lib/tasks.js +22 -1
- package/dist/lib/views/vocabulary.d.ts +1 -1
- package/dist/lib/views/vocabulary.js +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +75 -24
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +143 -34
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/legacyStatePreflight.d.ts +21 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/legacyStatePreflight.js +75 -19
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +75 -24
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +138 -34
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/legacyStatePreflight.d.ts +21 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/legacyStatePreflight.js +74 -19
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3516 -3513
- package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
- package/oclif.manifest.json +21649 -21626
- package/package.json +3 -3
|
@@ -55,18 +55,26 @@ exports.resolveFirstPartyHarnessAgent = resolveFirstPartyHarnessAgent;
|
|
|
55
55
|
exports.readConfiguredAgentId = readConfiguredAgentId;
|
|
56
56
|
exports.setConfiguredAgentId = setConfiguredAgentId;
|
|
57
57
|
exports.clearConfiguredAgentId = clearConfiguredAgentId;
|
|
58
|
+
exports.collectRosterPages = collectRosterPages;
|
|
58
59
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
59
60
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
60
61
|
const api_fetch_1 = require("./api-fetch");
|
|
61
62
|
const config_1 = require("./config");
|
|
62
63
|
const first_party_harness_managed_1 = require("./first-party-harness-managed");
|
|
64
|
+
/** Page size for one `/api/agents` roster request. */
|
|
65
|
+
const ROSTER_PAGE_SIZE = 100;
|
|
63
66
|
/**
|
|
64
|
-
*
|
|
67
|
+
* The most agents the roster will read, across pages.
|
|
65
68
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
69
|
+
* The roster is not only the refusal's "did you mean" list. Resolution also finds
|
|
70
|
+
* the user's EXISTING default agent in it (`createDefault: false`) and matches a
|
|
71
|
+
* configured agent by name, so one fixed page answered "you have no default agent"
|
|
72
|
+
* for an account whose default was agent 198 of 210 (found by the end-to-end run
|
|
73
|
+
* on 2026-09-17). Pages are followed until the server says there are no more, up
|
|
74
|
+
* to this bound, which keeps a pathological account from turning one session
|
|
75
|
+
* start into an unbounded crawl.
|
|
68
76
|
*/
|
|
69
|
-
const
|
|
77
|
+
const ROSTER_MAX = 1000;
|
|
70
78
|
/** How many candidates a refusal prints before it summarizes the rest. */
|
|
71
79
|
const CANDIDATES_SHOWN = 12;
|
|
72
80
|
class FirstPartyHarnessAgentError extends Error {
|
|
@@ -369,14 +377,35 @@ function writeConfigKey(id) {
|
|
|
369
377
|
* `fields=summary` drops the prompt-sized fields; this needs an id and a name.
|
|
370
378
|
*/
|
|
371
379
|
async function listAgents(signal) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
+
return collectRosterPages((cursor) => (0, api_fetch_1.apiFetch)(`/api/agents?limit=${ROSTER_PAGE_SIZE}&fields=summary&requiredPermission=${data_provider_1.PermissionBits.EDIT}` +
|
|
381
|
+
(cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''), signal ? { signal } : {}));
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Read roster pages until the server reports no more, up to `max` rows.
|
|
385
|
+
*
|
|
386
|
+
* A page that does not say `has_more` with a usable `after` cursor ends the walk,
|
|
387
|
+
* so an older server that ignores `cursor` (or returns a bare array) costs exactly
|
|
388
|
+
* the one request it always did.
|
|
389
|
+
*/
|
|
390
|
+
async function collectRosterPages(fetchPage, max = ROSTER_MAX) {
|
|
391
|
+
const rows = [];
|
|
392
|
+
let cursor;
|
|
393
|
+
do {
|
|
394
|
+
const response = await fetchPage(cursor);
|
|
395
|
+
const page = Array.isArray(response)
|
|
396
|
+
? response
|
|
397
|
+
: Array.isArray(response?.data)
|
|
398
|
+
? response.data
|
|
399
|
+
: Array.isArray(response?.agents)
|
|
400
|
+
? response.agents
|
|
401
|
+
: [];
|
|
402
|
+
rows.push(...page);
|
|
403
|
+
const next = !Array.isArray(response) && response?.has_more === true && typeof response.after === 'string'
|
|
404
|
+
? response.after
|
|
405
|
+
: undefined;
|
|
406
|
+
cursor = next && next !== cursor && page.length > 0 ? next : undefined;
|
|
407
|
+
} while (cursor && rows.length < max);
|
|
408
|
+
return rows.slice(0, max);
|
|
380
409
|
}
|
|
381
410
|
/**
|
|
382
411
|
* The caller's own user id, memoized FOR THIS PROCESS ONLY.
|
|
@@ -263,8 +263,16 @@ function stateDirCheck(cwd, env) {
|
|
|
263
263
|
remedy: `This is a build bug — report it. Do not run ${auth_core_1.FIRST_PARTY_HARNESS.displayName} from this directory.`,
|
|
264
264
|
};
|
|
265
265
|
}
|
|
266
|
+
// The engine still reads a project directory under a legacy namespace, after the
|
|
267
|
+
// current one (OSK-8674); one that exists is worth naming, or "not created yet"
|
|
268
|
+
// reads as "no project config" on a repository that has some.
|
|
269
|
+
const legacyProjects = (0, auth_core_1.firstPartyHarnessEngineProjectDirectories)()
|
|
270
|
+
.slice(1)
|
|
271
|
+
.map((directory) => node_path_1.default.join(node_path_1.default.resolve(cwd), directory))
|
|
272
|
+
.filter((directory) => (0, node_fs_1.existsSync)(directory));
|
|
266
273
|
const parts = [
|
|
267
274
|
`project: ${project}${(0, node_fs_1.existsSync)(project) ? '' : ' (not created yet)'}`,
|
|
275
|
+
...legacyProjects.map((directory) => `also read (legacy): ${directory}`),
|
|
268
276
|
`user: ${user}${(0, node_fs_1.existsSync)(user) ? '' : ' (not created yet)'}`,
|
|
269
277
|
];
|
|
270
278
|
return { name: 'state-paths', status: 'ok', detail: parts.join('; ') };
|
|
@@ -306,21 +314,29 @@ function compatCheck(cwd, env) {
|
|
|
306
314
|
// session only when the daemon links them in, which `skrr skills local` shows.
|
|
307
315
|
const root = node_path_1.default.resolve(cwd);
|
|
308
316
|
const home = env.HOME || (0, node_os_1.homedir)();
|
|
309
|
-
|
|
310
|
-
const noCompatPrompts =
|
|
317
|
+
// The engine reads its variables under every namespace it has had, current first.
|
|
318
|
+
const noCompatPrompts = (0, auth_core_1.readFirstPartyHarnessEngineEnv)(env, 'noCompatPrompts').name;
|
|
311
319
|
const engineHomeDir = (0, first_party_harness_1.engineInstructionHome)(env);
|
|
312
320
|
const shown = (abs) => abs.startsWith(root + node_path_1.default.sep)
|
|
313
321
|
? node_path_1.default.relative(root, abs)
|
|
314
322
|
: abs.startsWith(home + node_path_1.default.sep)
|
|
315
323
|
? `~/${node_path_1.default.relative(home, abs)}`
|
|
316
324
|
: abs;
|
|
325
|
+
// The engine's project instruction file under every project directory it reads,
|
|
326
|
+
// current first. It reads the first that exists, so a legacy one beside a current
|
|
327
|
+
// one is present and NOT read — said, rather than listed as if it applied.
|
|
328
|
+
const projectInstructions = (0, auth_core_1.firstPartyHarnessEngineProjectDirectories)().map((directory) => node_path_1.default.join(root, directory, 'AGENTS.md'));
|
|
317
329
|
const declared = [
|
|
318
|
-
{
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
330
|
+
...projectInstructions.map((abs, i) => {
|
|
331
|
+
const newer = projectInstructions.slice(0, i).find((candidate) => (0, node_fs_1.existsSync)(candidate));
|
|
332
|
+
return {
|
|
333
|
+
abs,
|
|
334
|
+
kind: 'instructions',
|
|
335
|
+
owner: 'engine',
|
|
336
|
+
scope: 'project',
|
|
337
|
+
...(newer ? { disabled: `superseded by ${node_path_1.default.relative(root, newer)}` } : {}),
|
|
338
|
+
};
|
|
339
|
+
}),
|
|
324
340
|
{
|
|
325
341
|
abs: node_path_1.default.join(engineHomeDir, 'AGENTS.md'),
|
|
326
342
|
kind: 'instructions',
|
|
@@ -338,14 +354,14 @@ function compatCheck(cwd, env) {
|
|
|
338
354
|
kind: 'instructions',
|
|
339
355
|
owner: 'compatibility',
|
|
340
356
|
scope: 'project',
|
|
341
|
-
...(noCompatPrompts ? { disabled:
|
|
357
|
+
...(noCompatPrompts ? { disabled: noCompatPrompts } : {}),
|
|
342
358
|
},
|
|
343
359
|
{
|
|
344
360
|
abs: node_path_1.default.join(home, '.claude', 'CLAUDE.md'),
|
|
345
361
|
kind: 'instructions',
|
|
346
362
|
owner: 'compatibility',
|
|
347
363
|
scope: 'user',
|
|
348
|
-
...(noCompatPrompts ? { disabled:
|
|
364
|
+
...(noCompatPrompts ? { disabled: noCompatPrompts } : {}),
|
|
349
365
|
},
|
|
350
366
|
{
|
|
351
367
|
abs: node_path_1.default.join(root, '.agents', 'skills'),
|
|
@@ -494,15 +510,17 @@ const EGRESS_DENIED_HOSTS = [
|
|
|
494
510
|
'social-cards.sst.dev',
|
|
495
511
|
'opencode.internal',
|
|
496
512
|
];
|
|
497
|
-
/**
|
|
498
|
-
|
|
513
|
+
/**
|
|
514
|
+
* The ENGINE reads this one under its own names, current namespace first; the
|
|
515
|
+
* platform only reports it, naming the variable that actually supplied it.
|
|
516
|
+
*/
|
|
499
517
|
function egressCheck(env) {
|
|
500
|
-
const override = env
|
|
501
|
-
if (override === '1' || override === 'true') {
|
|
518
|
+
const override = (0, auth_core_1.readFirstPartyHarnessEngineEnv)(env, 'allowUpstreamEgress');
|
|
519
|
+
if (override.value === '1' || override.value === 'true') {
|
|
502
520
|
return {
|
|
503
521
|
name: 'upstream-egress',
|
|
504
522
|
status: 'warn',
|
|
505
|
-
detail: `${
|
|
523
|
+
detail: `${override.name} is set — ${auth_core_1.FIRST_PARTY_HARNESS.displayName} may reach upstream hosted services`,
|
|
506
524
|
remedy: 'Unset it unless you are deliberately debugging upstream behaviour. Session content, ' +
|
|
507
525
|
'repository names, and account tokens can leave for a third party while it is set.',
|
|
508
526
|
};
|
|
@@ -746,7 +764,7 @@ function sessionAuthorityCheck(env) {
|
|
|
746
764
|
remedy: 'Intended for local use, and nothing to fix. Worth knowing because it is NOT the same ' +
|
|
747
765
|
'trust plane as a daemon-dispatched run — and neither plane is an OS-level sandbox ' +
|
|
748
766
|
'(no seccomp, container, or namespace). An untrusted autonomous workload needs a hosted ' +
|
|
749
|
-
|
|
767
|
+
"or container profile; see the threat model in the engine's repository.",
|
|
750
768
|
};
|
|
751
769
|
}
|
|
752
770
|
}
|
|
@@ -47,11 +47,13 @@ export declare function engineHome(env?: NodeJS.ProcessEnv): string;
|
|
|
47
47
|
* The user-level instruction root the engine owns.
|
|
48
48
|
*
|
|
49
49
|
* Read under the platform's neutral variable first, then under the variable the
|
|
50
|
-
* ENGINE itself reads
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* the engine it
|
|
54
|
-
*
|
|
50
|
+
* ENGINE itself reads, under every name it reads it by, current namespace first
|
|
51
|
+
* (`readFirstPartyHarnessEngineEnv(env, 'home')`; the namespace moved in OSK-8674
|
|
52
|
+
* and the engine reads both). That is not a legacy fallback: it is the engine's own
|
|
53
|
+
* variable, the engine honours it whatever the platform does, and a CLI that
|
|
54
|
+
* ignored it would report one instruction root while the engine it launches used
|
|
55
|
+
* another. {@link engineOwnedEnv} closes the other direction, handing a
|
|
56
|
+
* neutral-variable value to the engine under its own names.
|
|
55
57
|
*/
|
|
56
58
|
export declare function engineInstructionHome(env?: NodeJS.ProcessEnv): string;
|
|
57
59
|
/**
|
|
@@ -378,12 +380,17 @@ export declare function engineSpawnEnv(env: NodeJS.ProcessEnv, extraEnv: Record<
|
|
|
378
380
|
* `skrr code doctor` honour it while the engine it launches ignored it — two
|
|
379
381
|
* answers to "where do my instructions live" from one command.
|
|
380
382
|
*
|
|
381
|
-
*
|
|
382
|
-
* engine's
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
383
|
+
* Under EVERY name the engine has read (`firstPartyHarnessEngineEnvEntries`). The
|
|
384
|
+
* engine's namespace moved (OSK-8674), and the binary this CLI launches may be a
|
|
385
|
+
* build from either side of that move; a value handed over under one name only
|
|
386
|
+
* reaches half of them.
|
|
387
|
+
*
|
|
388
|
+
* When the neutral variable supplied the value, every engine name gets it: the
|
|
389
|
+
* neutral one wins, as it does in {@link engineInstructionHome}. When only an
|
|
390
|
+
* engine name did, only the engine names that are UNSET are filled in — the user's
|
|
391
|
+
* own values are inherited already, and two different values the user set under
|
|
392
|
+
* two names are theirs to keep. The resolved path is passed rather than the raw
|
|
393
|
+
* value, so the engine and the doctor agree on `~` expansion too.
|
|
387
394
|
*
|
|
388
395
|
* Not a credential and never one: it cannot reintroduce anything the sanitizer
|
|
389
396
|
* strips, and `extraEnv` still layers over it.
|
|
@@ -84,14 +84,17 @@ function engineHome(env = process.env) {
|
|
|
84
84
|
* The user-level instruction root the engine owns.
|
|
85
85
|
*
|
|
86
86
|
* Read under the platform's neutral variable first, then under the variable the
|
|
87
|
-
* ENGINE itself reads
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* the engine it
|
|
91
|
-
*
|
|
87
|
+
* ENGINE itself reads, under every name it reads it by, current namespace first
|
|
88
|
+
* (`readFirstPartyHarnessEngineEnv(env, 'home')`; the namespace moved in OSK-8674
|
|
89
|
+
* and the engine reads both). That is not a legacy fallback: it is the engine's own
|
|
90
|
+
* variable, the engine honours it whatever the platform does, and a CLI that
|
|
91
|
+
* ignored it would report one instruction root while the engine it launches used
|
|
92
|
+
* another. {@link engineOwnedEnv} closes the other direction, handing a
|
|
93
|
+
* neutral-variable value to the engine under its own names.
|
|
92
94
|
*/
|
|
93
95
|
function engineInstructionHome(env = process.env) {
|
|
94
|
-
const configured = (0, auth_core_1.readFirstPartyHarnessEnv)(env, 'home').value ??
|
|
96
|
+
const configured = (0, auth_core_1.readFirstPartyHarnessEnv)(env, 'home').value ??
|
|
97
|
+
(0, auth_core_1.readFirstPartyHarnessEngineEnv)(env, 'home').value;
|
|
95
98
|
// Falls through to `engineHome()` rather than re-deriving `~/.skrr` — the two
|
|
96
99
|
// describe the same root, and re-deriving it is what made them disagree under
|
|
97
100
|
// `OVERSKY_CONFIG_DIR` (OSK-300).
|
|
@@ -103,11 +106,6 @@ function engineInstructionHome(env = process.env) {
|
|
|
103
106
|
return node_path_1.default.join((0, node_os_1.homedir)(), configured.slice(2));
|
|
104
107
|
return node_path_1.default.resolve(configured);
|
|
105
108
|
}
|
|
106
|
-
/** The engine's own home variable, trimmed; undefined when unset or blank. */
|
|
107
|
-
function engineHomeVariable(env) {
|
|
108
|
-
const raw = env[auth_core_1.FIRST_PARTY_HARNESS_ENGINE.env.home];
|
|
109
|
-
return typeof raw === 'string' && raw.trim() !== '' ? raw.trim() : undefined;
|
|
110
|
-
}
|
|
111
109
|
/**
|
|
112
110
|
* The managed install location for one spelling of the binary (canonical by
|
|
113
111
|
* default) — where the daemon, the Desktop app, and
|
|
@@ -142,7 +140,7 @@ function legacyManagedEnginePaths(env = process.env) {
|
|
|
142
140
|
function legacyManagedEngineBinDirs(env = process.env) {
|
|
143
141
|
if ((0, auth_core_1.isConfigRootOverridden)(env))
|
|
144
142
|
return [];
|
|
145
|
-
return
|
|
143
|
+
return (0, auth_core_1.firstPartyHarnessEngineHomeDirnames)().map((home) => node_path_1.default.join((0, config_1.priorConfigDir)(), home, 'bin'));
|
|
146
144
|
}
|
|
147
145
|
/**
|
|
148
146
|
* Unmanaged well-known locations, checked after PATH.
|
|
@@ -606,22 +604,29 @@ mode) {
|
|
|
606
604
|
* `skrr code doctor` honour it while the engine it launches ignored it — two
|
|
607
605
|
* answers to "where do my instructions live" from one command.
|
|
608
606
|
*
|
|
609
|
-
*
|
|
610
|
-
* engine's
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
607
|
+
* Under EVERY name the engine has read (`firstPartyHarnessEngineEnvEntries`). The
|
|
608
|
+
* engine's namespace moved (OSK-8674), and the binary this CLI launches may be a
|
|
609
|
+
* build from either side of that move; a value handed over under one name only
|
|
610
|
+
* reaches half of them.
|
|
611
|
+
*
|
|
612
|
+
* When the neutral variable supplied the value, every engine name gets it: the
|
|
613
|
+
* neutral one wins, as it does in {@link engineInstructionHome}. When only an
|
|
614
|
+
* engine name did, only the engine names that are UNSET are filled in — the user's
|
|
615
|
+
* own values are inherited already, and two different values the user set under
|
|
616
|
+
* two names are theirs to keep. The resolved path is passed rather than the raw
|
|
617
|
+
* value, so the engine and the doctor agree on `~` expansion too.
|
|
615
618
|
*
|
|
616
619
|
* Not a credential and never one: it cannot reintroduce anything the sanitizer
|
|
617
620
|
* strips, and `extraEnv` still layers over it.
|
|
618
621
|
*/
|
|
619
622
|
function engineOwnedEnv(env) {
|
|
620
|
-
const
|
|
623
|
+
const everyName = (value) => (0, auth_core_1.firstPartyHarnessEngineEnvEntries)('home', value);
|
|
621
624
|
if ((0, auth_core_1.readFirstPartyHarnessEnv)(env, 'home').value) {
|
|
622
|
-
|
|
625
|
+
return everyName(engineInstructionHome(env));
|
|
623
626
|
}
|
|
624
|
-
|
|
627
|
+
if (!(0, auth_core_1.readFirstPartyHarnessEngineEnv)(env, 'home').value)
|
|
628
|
+
return {};
|
|
629
|
+
return Object.fromEntries(Object.entries(everyName(engineInstructionHome(env))).filter(([name]) => !(typeof env[name] === 'string' && env[name].trim() !== '')));
|
|
625
630
|
}
|
|
626
631
|
async function execEngine(args, env = process.env, options = {}) {
|
|
627
632
|
// Move an engine installed under an older binary name onto the canonical one,
|
package/dist/lib/harnesses.d.ts
CHANGED
|
@@ -101,6 +101,12 @@ export interface HarnessLease {
|
|
|
101
101
|
machineProduct?: string;
|
|
102
102
|
machineProductLabel?: string;
|
|
103
103
|
displayName?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Who pays: the workspace whose account the lease bills, or `null` for the
|
|
106
|
+
* owner's personal account (`MachineLease.toSafeJSON`). Absent from a server
|
|
107
|
+
* that predates it, which is not the same as personal.
|
|
108
|
+
*/
|
|
109
|
+
workspaceId?: string | null;
|
|
104
110
|
state?: string;
|
|
105
111
|
stateReason?: string;
|
|
106
112
|
lifecycleSemantics?: string;
|
|
@@ -76,6 +76,27 @@ export declare function describeTargetSync(target: {
|
|
|
76
76
|
updatedAt?: string;
|
|
77
77
|
driftCount?: number;
|
|
78
78
|
}, now?: number, liveDaemonIds?: string[]): string;
|
|
79
|
+
/**
|
|
80
|
+
* The whole error of every failed target, and what to do about it.
|
|
81
|
+
*
|
|
82
|
+
* The STATUS column caps at 78 characters, so a daemon error that names a path
|
|
83
|
+
* was cut mid-path — "must be inside a Git repository: /private/…" — and the
|
|
84
|
+
* table offered nothing past it: no remedy, and no way to learn that
|
|
85
|
+
* `instructions remove` was the way out (OSK-4626). The column keeps its cap;
|
|
86
|
+
* the full sentence and the next step go below the table, the way the re-bind
|
|
87
|
+
* commands do.
|
|
88
|
+
*
|
|
89
|
+
* The daemon retries a failed target on its next sync (every five minutes, or
|
|
90
|
+
* sooner on any change), so fixing the cause is itself a remedy and needs no
|
|
91
|
+
* command. A failed REMOVAL is the other case: it can only be abandoned when
|
|
92
|
+
* its daemon is gone, which is the one place `purge-target --force` belongs.
|
|
93
|
+
*/
|
|
94
|
+
export declare function failedTargetLines(targets: Array<{
|
|
95
|
+
id: string;
|
|
96
|
+
status?: string;
|
|
97
|
+
desiredState?: string;
|
|
98
|
+
lastError?: string;
|
|
99
|
+
}>, bin?: string): string[];
|
|
79
100
|
/**
|
|
80
101
|
* The command that re-binds a stalled target to the daemon now running on its
|
|
81
102
|
* machine, or null when no such daemon is live.
|
|
@@ -7,6 +7,7 @@ exports.resolveBundleLabel = resolveBundleLabel;
|
|
|
7
7
|
exports.parseDaemonId = parseDaemonId;
|
|
8
8
|
exports.sameMachineDaemon = sameMachineDaemon;
|
|
9
9
|
exports.describeTargetSync = describeTargetSync;
|
|
10
|
+
exports.failedTargetLines = failedTargetLines;
|
|
10
11
|
exports.rebindCommandFor = rebindCommandFor;
|
|
11
12
|
const promises_1 = require("node:fs/promises");
|
|
12
13
|
async function readStdin() {
|
|
@@ -188,6 +189,35 @@ function describeTargetSync(target, now = Date.now(), liveDaemonIds = []) {
|
|
|
188
189
|
}
|
|
189
190
|
return `${base} — stalled ${days}d; daemon may be gone: remove, then purge-target --force`;
|
|
190
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* The whole error of every failed target, and what to do about it.
|
|
194
|
+
*
|
|
195
|
+
* The STATUS column caps at 78 characters, so a daemon error that names a path
|
|
196
|
+
* was cut mid-path — "must be inside a Git repository: /private/…" — and the
|
|
197
|
+
* table offered nothing past it: no remedy, and no way to learn that
|
|
198
|
+
* `instructions remove` was the way out (OSK-4626). The column keeps its cap;
|
|
199
|
+
* the full sentence and the next step go below the table, the way the re-bind
|
|
200
|
+
* commands do.
|
|
201
|
+
*
|
|
202
|
+
* The daemon retries a failed target on its next sync (every five minutes, or
|
|
203
|
+
* sooner on any change), so fixing the cause is itself a remedy and needs no
|
|
204
|
+
* command. A failed REMOVAL is the other case: it can only be abandoned when
|
|
205
|
+
* its daemon is gone, which is the one place `purge-target --force` belongs.
|
|
206
|
+
*/
|
|
207
|
+
function failedTargetLines(targets, bin = 'skrr') {
|
|
208
|
+
const failed = targets.filter((target) => target.status === 'error');
|
|
209
|
+
if (failed.length === 0)
|
|
210
|
+
return [];
|
|
211
|
+
const lines = ['', 'Failed targets:'];
|
|
212
|
+
for (const target of failed) {
|
|
213
|
+
const removing = target.desiredState === 'removed';
|
|
214
|
+
lines.push(` ${target.id} — ${removing ? 'removal failed' : 'install failed'}: ${target.lastError || 'no reason reported'}`);
|
|
215
|
+
lines.push(removing
|
|
216
|
+
? ` Fix the cause and its daemon retries on the next sync. If that daemon is gone for good: \`${bin} instructions purge-target ${target.id} --force\``
|
|
217
|
+
: ` Fix the cause and its daemon retries on the next sync, or drop the target: \`${bin} instructions remove ${target.id}\``);
|
|
218
|
+
}
|
|
219
|
+
return lines;
|
|
220
|
+
}
|
|
191
221
|
/**
|
|
192
222
|
* The command that re-binds a stalled target to the daemon now running on its
|
|
193
223
|
* machine, or null when no such daemon is live.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* instruction-provenance.ts — tell a prompt's two version counters apart.
|
|
3
|
+
*
|
|
4
|
+
* A managed instruction prompt has its OWN version counter (1, 2, 3 — every
|
|
5
|
+
* saved body), and a prompt installed from a built-in template also carries
|
|
6
|
+
* the TEMPLATE release each body came from (1, 2, 5, 17 — skipping whatever an
|
|
7
|
+
* install never received). Every table labelled both `v`, and they agree only
|
|
8
|
+
* by coincidence: bundle v3 was built-in v5 on the installation that found this
|
|
9
|
+
* (dogfood OSK-4647), so "the preset went to v5" read as two versions behind.
|
|
10
|
+
* The provenance that settles it (`sourceTemplateKey`/`sourceTemplateVersion`)
|
|
11
|
+
* was reachable through `--json` alone.
|
|
12
|
+
*
|
|
13
|
+
* So the prompt's counter stays `v<n>`, and a template release is always
|
|
14
|
+
* written `built-in v<n>` — never a bare `v` that could be either.
|
|
15
|
+
*/
|
|
16
|
+
export interface TemplateProvenance {
|
|
17
|
+
sourceTemplateKey?: string;
|
|
18
|
+
sourceTemplateVersion?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `built-in v5` for a body taken from a built-in template, `built-in` when the
|
|
22
|
+
* row is recognised as one but its release was never derivable (the provenance
|
|
23
|
+
* backfill stamps the key without inventing a number), and `` for a body that
|
|
24
|
+
* was not taken from a template at all.
|
|
25
|
+
*/
|
|
26
|
+
export declare function builtInLabel(version: TemplateProvenance | null | undefined): string;
|
|
27
|
+
interface VersionLike extends TemplateProvenance {
|
|
28
|
+
versionNumber?: number;
|
|
29
|
+
}
|
|
30
|
+
interface BundleLike extends TemplateProvenance {
|
|
31
|
+
currentVersionNumber?: number;
|
|
32
|
+
currentVersion?: VersionLike | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The APPLIED cell of `instructions status`: `v3 (built-in v5)` when the
|
|
36
|
+
* applied body is the prompt's current one and that one came from a template.
|
|
37
|
+
* Only then is the release known without fetching the version history, so any
|
|
38
|
+
* other case says `v3` and nothing it cannot back.
|
|
39
|
+
*/
|
|
40
|
+
export declare function appliedVersionLabel(appliedVersionNumber: number | undefined, bundle: BundleLike | undefined): string;
|
|
41
|
+
/**
|
|
42
|
+
* One sentence for `instructions show`: which built-in template a prompt comes
|
|
43
|
+
* from, whether its current body IS a built-in release, and whether a newer
|
|
44
|
+
* release exists — the question no command answered before. `STATUS: available`
|
|
45
|
+
* on a version row means "not archived", not "newer than yours".
|
|
46
|
+
*
|
|
47
|
+
* `latestTemplateVersion` is the release this server would install today;
|
|
48
|
+
* undefined when it could not be read, in which case nothing is claimed about
|
|
49
|
+
* it. Returns null for a prompt that was not installed from a template.
|
|
50
|
+
*/
|
|
51
|
+
export declare function describeTemplateProvenance(bundle: BundleLike & {
|
|
52
|
+
currentVersionNumber: number;
|
|
53
|
+
}, latestTemplateVersion?: number): string | null;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* instruction-provenance.ts — tell a prompt's two version counters apart.
|
|
4
|
+
*
|
|
5
|
+
* A managed instruction prompt has its OWN version counter (1, 2, 3 — every
|
|
6
|
+
* saved body), and a prompt installed from a built-in template also carries
|
|
7
|
+
* the TEMPLATE release each body came from (1, 2, 5, 17 — skipping whatever an
|
|
8
|
+
* install never received). Every table labelled both `v`, and they agree only
|
|
9
|
+
* by coincidence: bundle v3 was built-in v5 on the installation that found this
|
|
10
|
+
* (dogfood OSK-4647), so "the preset went to v5" read as two versions behind.
|
|
11
|
+
* The provenance that settles it (`sourceTemplateKey`/`sourceTemplateVersion`)
|
|
12
|
+
* was reachable through `--json` alone.
|
|
13
|
+
*
|
|
14
|
+
* So the prompt's counter stays `v<n>`, and a template release is always
|
|
15
|
+
* written `built-in v<n>` — never a bare `v` that could be either.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.builtInLabel = builtInLabel;
|
|
19
|
+
exports.appliedVersionLabel = appliedVersionLabel;
|
|
20
|
+
exports.describeTemplateProvenance = describeTemplateProvenance;
|
|
21
|
+
/**
|
|
22
|
+
* `built-in v5` for a body taken from a built-in template, `built-in` when the
|
|
23
|
+
* row is recognised as one but its release was never derivable (the provenance
|
|
24
|
+
* backfill stamps the key without inventing a number), and `` for a body that
|
|
25
|
+
* was not taken from a template at all.
|
|
26
|
+
*/
|
|
27
|
+
function builtInLabel(version) {
|
|
28
|
+
if (!version?.sourceTemplateKey)
|
|
29
|
+
return '';
|
|
30
|
+
const release = version.sourceTemplateVersion;
|
|
31
|
+
return Number.isInteger(release) && Number(release) > 0 ? `built-in v${release}` : 'built-in';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The APPLIED cell of `instructions status`: `v3 (built-in v5)` when the
|
|
35
|
+
* applied body is the prompt's current one and that one came from a template.
|
|
36
|
+
* Only then is the release known without fetching the version history, so any
|
|
37
|
+
* other case says `v3` and nothing it cannot back.
|
|
38
|
+
*/
|
|
39
|
+
function appliedVersionLabel(appliedVersionNumber, bundle) {
|
|
40
|
+
if (!appliedVersionNumber)
|
|
41
|
+
return '-';
|
|
42
|
+
const base = `v${appliedVersionNumber}`;
|
|
43
|
+
const current = bundle?.currentVersion;
|
|
44
|
+
if (!current || current.versionNumber !== appliedVersionNumber)
|
|
45
|
+
return base;
|
|
46
|
+
const builtIn = builtInLabel(current);
|
|
47
|
+
return builtIn ? `${base} (${builtIn})` : base;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* One sentence for `instructions show`: which built-in template a prompt comes
|
|
51
|
+
* from, whether its current body IS a built-in release, and whether a newer
|
|
52
|
+
* release exists — the question no command answered before. `STATUS: available`
|
|
53
|
+
* on a version row means "not archived", not "newer than yours".
|
|
54
|
+
*
|
|
55
|
+
* `latestTemplateVersion` is the release this server would install today;
|
|
56
|
+
* undefined when it could not be read, in which case nothing is claimed about
|
|
57
|
+
* it. Returns null for a prompt that was not installed from a template.
|
|
58
|
+
*/
|
|
59
|
+
function describeTemplateProvenance(bundle, latestTemplateVersion) {
|
|
60
|
+
if (!bundle.sourceTemplateKey)
|
|
61
|
+
return null;
|
|
62
|
+
const current = bundle.currentVersion;
|
|
63
|
+
const head = `From built-in template ${bundle.sourceTemplateKey}.`;
|
|
64
|
+
if (!current)
|
|
65
|
+
return head;
|
|
66
|
+
const release = current.sourceTemplateKey ? current.sourceTemplateVersion : undefined;
|
|
67
|
+
const version = `v${bundle.currentVersionNumber}`;
|
|
68
|
+
const latestKnown = Number.isInteger(latestTemplateVersion);
|
|
69
|
+
if (current.sourceTemplateKey) {
|
|
70
|
+
const builtIn = builtInLabel(current);
|
|
71
|
+
if (!latestKnown || !Number.isInteger(release))
|
|
72
|
+
return `${head} ${version} is ${builtIn}.`;
|
|
73
|
+
if (Number(latestTemplateVersion) > Number(release)) {
|
|
74
|
+
return `${head} ${version} is ${builtIn}; built-in v${latestTemplateVersion} is newer.`;
|
|
75
|
+
}
|
|
76
|
+
return `${head} ${version} is ${builtIn}, the latest.`;
|
|
77
|
+
}
|
|
78
|
+
// Newer releases are promoted only onto a prompt whose body is still a
|
|
79
|
+
// built-in one, so an edit is exactly when "is there a newer one" matters.
|
|
80
|
+
const edited = `${head} ${version} is your own edit, so newer built-in releases are not applied to it`;
|
|
81
|
+
return latestKnown
|
|
82
|
+
? `${edited}; the latest is built-in v${latestTemplateVersion}.`
|
|
83
|
+
: `${edited}.`;
|
|
84
|
+
}
|
|
@@ -13,12 +13,15 @@
|
|
|
13
13
|
* go.
|
|
14
14
|
*/
|
|
15
15
|
export interface TaskViewFilters {
|
|
16
|
+
quickFilter?: string;
|
|
16
17
|
statuses?: string[];
|
|
18
|
+
statusTypes?: string[];
|
|
17
19
|
priorities?: string[];
|
|
18
20
|
assignees?: string[];
|
|
19
21
|
labels?: string[];
|
|
20
22
|
goalIds?: string[];
|
|
21
23
|
projectIds?: string[];
|
|
24
|
+
cycleIds?: string[];
|
|
22
25
|
search?: string;
|
|
23
26
|
}
|
|
24
27
|
export interface TaskViewLike {
|
|
@@ -69,3 +72,14 @@ export declare function immutableViewPatchKeys(body: Record<string, unknown>): s
|
|
|
69
72
|
* than listing six empty ones.
|
|
70
73
|
*/
|
|
71
74
|
export declare function describeTaskView(view: TaskViewLike): string[];
|
|
75
|
+
/**
|
|
76
|
+
* One line saying what a view SELECTS, for a table cell.
|
|
77
|
+
*
|
|
78
|
+
* `views list` had a SELECTION column whose value was the word `criteria` for
|
|
79
|
+
* every view that was not a pinned task list — a kind label sitting where the
|
|
80
|
+
* reader looks for an answer (dogfood OSK-4646). Values that fit are named
|
|
81
|
+
* (statuses, priorities, the quick filter, the search); ids are counted, since
|
|
82
|
+
* a row of UUIDs answers nothing at a glance and `views show` prints them in
|
|
83
|
+
* full.
|
|
84
|
+
*/
|
|
85
|
+
export declare function summarizeTaskViewSelection(view: TaskViewLike): string;
|
|
@@ -18,6 +18,12 @@ exports.EDITABLE_VIEW_PATCH_FIELDS = void 0;
|
|
|
18
18
|
exports.unshareableViewWarning = unshareableViewWarning;
|
|
19
19
|
exports.immutableViewPatchKeys = immutableViewPatchKeys;
|
|
20
20
|
exports.describeTaskView = describeTaskView;
|
|
21
|
+
exports.summarizeTaskViewSelection = summarizeTaskViewSelection;
|
|
22
|
+
/** `all` is the quick filter that filters nothing; any other one narrows. */
|
|
23
|
+
function narrowingQuickFilter(filters) {
|
|
24
|
+
const quick = filters.quickFilter;
|
|
25
|
+
return quick && quick !== 'all' ? quick : undefined;
|
|
26
|
+
}
|
|
21
27
|
/**
|
|
22
28
|
* OSK-4859 — a view created with no active workspace comes back with no
|
|
23
29
|
* `workspaceId`, and the server then refuses to share it
|
|
@@ -97,13 +103,20 @@ function describeTaskView(view) {
|
|
|
97
103
|
add('pinned tasks', `${pinned.length}: ${pinned.join(', ')}`);
|
|
98
104
|
}
|
|
99
105
|
const f = view.filters ?? {};
|
|
106
|
+
// Every selection field the create schema accepts. `--quick-filter` was a
|
|
107
|
+
// create flag this renderer never printed, so a saved quick filter was
|
|
108
|
+
// exactly the kind of selection that "did not take" invisibly.
|
|
109
|
+
const quick = narrowingQuickFilter(f);
|
|
100
110
|
const filterRows = [
|
|
111
|
+
['quick filter', quick ? [quick] : undefined],
|
|
101
112
|
['statuses', f.statuses],
|
|
113
|
+
['status types', f.statusTypes],
|
|
102
114
|
['priorities', f.priorities],
|
|
103
115
|
['assignees', f.assignees],
|
|
104
116
|
['labels', f.labels],
|
|
105
117
|
['goals', f.goalIds],
|
|
106
118
|
['projects', f.projectIds],
|
|
119
|
+
['cycles', f.cycleIds],
|
|
107
120
|
];
|
|
108
121
|
let anyFilter = false;
|
|
109
122
|
for (const [label, values] of filterRows) {
|
|
@@ -123,3 +136,45 @@ function describeTaskView(view) {
|
|
|
123
136
|
add('sort', describeShape(view.sort));
|
|
124
137
|
return lines;
|
|
125
138
|
}
|
|
139
|
+
const counted = (count, singular, plural = `${singular}s`) => `${count} ${count === 1 ? singular : plural}`;
|
|
140
|
+
/**
|
|
141
|
+
* One line saying what a view SELECTS, for a table cell.
|
|
142
|
+
*
|
|
143
|
+
* `views list` had a SELECTION column whose value was the word `criteria` for
|
|
144
|
+
* every view that was not a pinned task list — a kind label sitting where the
|
|
145
|
+
* reader looks for an answer (dogfood OSK-4646). Values that fit are named
|
|
146
|
+
* (statuses, priorities, the quick filter, the search); ids are counted, since
|
|
147
|
+
* a row of UUIDs answers nothing at a glance and `views show` prints them in
|
|
148
|
+
* full.
|
|
149
|
+
*/
|
|
150
|
+
function summarizeTaskViewSelection(view) {
|
|
151
|
+
const parts = [];
|
|
152
|
+
const pinned = view.taskIds ?? [];
|
|
153
|
+
if (pinned.length)
|
|
154
|
+
parts.push(counted(pinned.length, 'pinned task'));
|
|
155
|
+
const spaces = view.spaceIds ?? [];
|
|
156
|
+
parts.push(spaces.length ? counted(spaces.length, 'space') : 'all spaces');
|
|
157
|
+
const f = view.filters ?? {};
|
|
158
|
+
const quick = narrowingQuickFilter(f);
|
|
159
|
+
if (quick)
|
|
160
|
+
parts.push(quick);
|
|
161
|
+
if (f.statuses?.length)
|
|
162
|
+
parts.push(`status ${f.statuses.join('/')}`);
|
|
163
|
+
if (f.statusTypes?.length)
|
|
164
|
+
parts.push(`status type ${f.statusTypes.join('/')}`);
|
|
165
|
+
if (f.priorities?.length)
|
|
166
|
+
parts.push(`priority ${f.priorities.join('/')}`);
|
|
167
|
+
if (f.assignees?.length)
|
|
168
|
+
parts.push(counted(f.assignees.length, 'assignee'));
|
|
169
|
+
if (f.labels?.length)
|
|
170
|
+
parts.push(counted(f.labels.length, 'label'));
|
|
171
|
+
if (f.goalIds?.length)
|
|
172
|
+
parts.push(counted(f.goalIds.length, 'goal'));
|
|
173
|
+
if (f.projectIds?.length)
|
|
174
|
+
parts.push(counted(f.projectIds.length, 'project'));
|
|
175
|
+
if (f.cycleIds?.length)
|
|
176
|
+
parts.push(counted(f.cycleIds.length, 'cycle'));
|
|
177
|
+
if (f.search)
|
|
178
|
+
parts.push(`"${f.search}"`);
|
|
179
|
+
return parts.join(', ');
|
|
180
|
+
}
|