@skrr-ai/cli 0.1.108 → 0.1.110
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/README.md +10 -0
- package/dist/base-command.d.ts +74 -0
- package/dist/base-command.js +182 -24
- package/dist/commands/agents/triggers/create.js +1 -1
- package/dist/commands/agents/triggers/update.js +1 -1
- package/dist/commands/agents/update.d.ts +25 -0
- package/dist/commands/agents/update.js +98 -3
- package/dist/commands/commitments/accounting.js +7 -2
- package/dist/commands/commitments/cost.d.ts +11 -0
- package/dist/commands/commitments/cost.js +26 -3
- package/dist/commands/commitments/create.js +2 -1
- package/dist/commands/commitments/operating-review.d.ts +15 -0
- package/dist/commands/commitments/operating-review.js +103 -50
- package/dist/commands/commitments/update.js +12 -5
- package/dist/commands/daemon/install.js +17 -1
- package/dist/commands/daemon/login.js +14 -0
- package/dist/commands/doctor.js +9 -0
- package/dist/commands/goals/list.js +6 -1
- package/dist/commands/grants/rotate.js +4 -0
- package/dist/commands/harnesses/prune.d.ts +26 -0
- package/dist/commands/harnesses/prune.js +172 -0
- package/dist/commands/login.js +53 -14
- package/dist/commands/machines/list.js +28 -6
- package/dist/commands/projects/show.js +1 -1
- package/dist/commands/revoke-daemon.js +2 -1
- package/dist/commands/spaces/workflows/archive.d.ts +1 -0
- package/dist/commands/spaces/workflows/archive.js +1 -0
- package/dist/commands/spaces/workflows/artifact.d.ts +1 -0
- package/dist/commands/spaces/workflows/artifact.js +1 -0
- package/dist/commands/spaces/workflows/cancel-node.d.ts +1 -0
- package/dist/commands/spaces/workflows/cancel-node.js +1 -0
- package/dist/commands/spaces/workflows/cancel.d.ts +1 -0
- package/dist/commands/spaces/workflows/cancel.js +6 -2
- package/dist/commands/spaces/workflows/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/create.js +7 -1
- package/dist/commands/spaces/workflows/events.d.ts +1 -0
- package/dist/commands/spaces/workflows/events.js +1 -0
- package/dist/commands/spaces/workflows/fixtures/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/fixtures/create.js +1 -0
- package/dist/commands/spaces/workflows/fixtures/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/fixtures/list.js +1 -0
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +1 -0
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +1 -0
- package/dist/commands/spaces/workflows/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/list.js +1 -0
- package/dist/commands/spaces/workflows/node-types.d.ts +2 -0
- package/dist/commands/spaces/workflows/node-types.js +24 -6
- package/dist/commands/spaces/workflows/pause.d.ts +1 -0
- package/dist/commands/spaces/workflows/pause.js +1 -0
- package/dist/commands/spaces/workflows/plan/show.d.ts +1 -0
- package/dist/commands/spaces/workflows/plan/show.js +1 -0
- package/dist/commands/spaces/workflows/plan/update.d.ts +1 -0
- package/dist/commands/spaces/workflows/plan/update.js +1 -0
- package/dist/commands/spaces/workflows/proposals/apply.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/apply.js +1 -0
- package/dist/commands/spaces/workflows/proposals/confirm.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/confirm.js +1 -0
- package/dist/commands/spaces/workflows/proposals/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/create.js +1 -0
- package/dist/commands/spaces/workflows/proposals/dismiss.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/dismiss.js +1 -0
- package/dist/commands/spaces/workflows/proposals/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/proposals/list.js +1 -0
- package/dist/commands/spaces/workflows/publish.d.ts +1 -0
- package/dist/commands/spaces/workflows/publish.js +9 -0
- package/dist/commands/spaces/workflows/reconcile.d.ts +1 -0
- package/dist/commands/spaces/workflows/reconcile.js +1 -0
- package/dist/commands/spaces/workflows/resume.d.ts +1 -0
- package/dist/commands/spaces/workflows/resume.js +1 -0
- package/dist/commands/spaces/workflows/retry.d.ts +1 -0
- package/dist/commands/spaces/workflows/retry.js +1 -0
- package/dist/commands/spaces/workflows/run.d.ts +1 -0
- package/dist/commands/spaces/workflows/run.js +14 -7
- package/dist/commands/spaces/workflows/runs.d.ts +1 -0
- package/dist/commands/spaces/workflows/runs.js +1 -0
- package/dist/commands/spaces/workflows/show.d.ts +25 -0
- package/dist/commands/spaces/workflows/show.js +42 -4
- package/dist/commands/spaces/workflows/signal.d.ts +12 -0
- package/dist/commands/spaces/workflows/signal.js +40 -2
- package/dist/commands/spaces/workflows/start.d.ts +1 -0
- package/dist/commands/spaces/workflows/start.js +4 -1
- package/dist/commands/spaces/workflows/task-execution/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/task-execution/create.js +1 -0
- package/dist/commands/spaces/workflows/test.d.ts +1 -0
- package/dist/commands/spaces/workflows/test.js +1 -0
- package/dist/commands/spaces/workflows/triggers/create.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/create.js +1 -0
- package/dist/commands/spaces/workflows/triggers/fire.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/fire.js +1 -0
- package/dist/commands/spaces/workflows/triggers/list.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/list.js +1 -0
- package/dist/commands/spaces/workflows/triggers/update.d.ts +1 -0
- package/dist/commands/spaces/workflows/triggers/update.js +1 -0
- package/dist/commands/spaces/workflows/update.d.ts +1 -0
- package/dist/commands/spaces/workflows/update.js +4 -0
- package/dist/commands/spaces/workflows/validate.d.ts +1 -0
- package/dist/commands/spaces/workflows/validate.js +27 -2
- package/dist/commands/tasks/activity.js +17 -3
- package/dist/commands/tasks/bulk-update.d.ts +1 -0
- package/dist/commands/tasks/bulk-update.js +7 -0
- package/dist/commands/tasks/complete.js +2 -1
- package/dist/commands/tasks/create-tree.d.ts +69 -0
- package/dist/commands/tasks/create-tree.js +413 -47
- package/dist/commands/tasks/list.d.ts +2 -0
- package/dist/commands/tasks/list.js +25 -3
- package/dist/commands/tasks/rearm-recurrence.d.ts +22 -0
- package/dist/commands/tasks/rearm-recurrence.js +87 -0
- package/dist/commands/tasks/show.d.ts +9 -0
- package/dist/commands/tasks/show.js +79 -33
- package/dist/commands/tasks/timeline.js +4 -0
- package/dist/commands/tasks/unschedule.js +5 -1
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +17 -2
- package/dist/commands/triggers/audit.js +12 -3
- package/dist/commands/triggers/create.js +9 -3
- package/dist/commands/triggers/fire-detail.d.ts +7 -0
- package/dist/commands/triggers/fire-detail.js +30 -12
- package/dist/commands/triggers/fire.js +13 -3
- package/dist/commands/triggers/parse-intent.d.ts +7 -0
- package/dist/commands/triggers/parse-intent.js +31 -9
- package/dist/commands/triggers/update.js +1 -1
- package/dist/commands/workspaces/grants.js +20 -3
- package/dist/commands/workspaces/members.d.ts +20 -0
- package/dist/commands/workspaces/members.js +64 -0
- package/dist/commands/workspaces/stewardship.js +14 -2
- package/dist/help.js +12 -0
- package/dist/hooks/command-not-found.js +27 -1
- package/dist/lib/command-miss.d.ts +52 -2
- package/dist/lib/command-miss.js +187 -18
- package/dist/lib/commitment-loop-status.d.ts +8 -0
- package/dist/lib/commitment-loop-status.js +45 -0
- package/dist/lib/commitment-product.js +5 -2
- package/dist/lib/commitments.d.ts +18 -3
- package/dist/lib/commitments.js +85 -45
- package/dist/lib/daemon-command-hint.d.ts +15 -0
- package/dist/lib/daemon-command-hint.js +21 -0
- package/dist/lib/daemon-machine-identity-copy.d.ts +14 -0
- package/dist/lib/daemon-machine-identity-copy.js +27 -0
- package/dist/lib/daemon-machine-identity.d.ts +27 -0
- package/dist/lib/daemon-machine-identity.js +61 -0
- package/dist/lib/daemon-setup.js +10 -1
- package/dist/lib/daemonBrokerRefusal.js +3 -2
- package/dist/lib/daemonHandoff.d.ts +19 -1
- package/dist/lib/daemonHandoff.js +61 -1
- package/dist/lib/deployed-build.d.ts +43 -0
- package/dist/lib/deployed-build.js +66 -0
- package/dist/lib/grants.d.ts +27 -1
- package/dist/lib/grants.js +39 -21
- package/dist/lib/harnesses.d.ts +71 -0
- package/dist/lib/harnesses.js +70 -0
- package/dist/lib/json-mode.d.ts +11 -0
- package/dist/lib/json-mode.js +19 -0
- package/dist/lib/json-output.d.ts +25 -0
- package/dist/lib/json-output.js +30 -0
- package/dist/lib/list-output.d.ts +10 -1
- package/dist/lib/list-output.js +22 -1
- package/dist/lib/local-daemon-readiness.d.ts +90 -0
- package/dist/lib/local-daemon-readiness.js +143 -0
- package/dist/lib/machines.d.ts +117 -0
- package/dist/lib/machines.js +139 -0
- package/dist/lib/managed-instruction-local.d.ts +2 -0
- package/dist/lib/managed-instruction-local.js +2 -0
- package/dist/lib/profile-machine-identity-guard.d.ts +18 -0
- package/dist/lib/profile-machine-identity-guard.js +39 -0
- package/dist/lib/request-field-flags.d.ts +20 -0
- package/dist/lib/request-field-flags.js +34 -0
- package/dist/lib/stewardship-output.d.ts +1 -1
- package/dist/lib/stewardship-output.js +26 -30
- package/dist/lib/task-batch.d.ts +61 -4
- package/dist/lib/task-batch.js +316 -80
- package/dist/lib/task-economics.d.ts +15 -0
- package/dist/lib/task-economics.js +39 -2
- package/dist/lib/task-recurrence-outcome.d.ts +6 -6
- package/dist/lib/task-recurrence-outcome.js +27 -3
- package/dist/lib/tasks.d.ts +39 -0
- package/dist/lib/tasks.js +132 -0
- package/dist/lib/triggers.d.ts +42 -1
- package/dist/lib/triggers.js +98 -2
- package/dist/lib/workflow-issues.d.ts +14 -0
- package/dist/lib/workflow-issues.js +32 -0
- package/dist/lib/workflow-run-view.d.ts +59 -0
- package/dist/lib/workflow-run-view.js +110 -0
- package/dist/lib/workspaces.d.ts +48 -4
- package/dist/lib/workspaces.js +55 -0
- package/dist/lib/write-target.d.ts +29 -0
- package/dist/lib/write-target.js +72 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +5 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +60 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +130 -6
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.js +50 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +60 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +128 -6
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.js +46 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +7523 -7140
- package/oclif.manifest.json +473 -59
- package/package.json +28 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DaemonMachineIdentitySkew } from './daemon-machine-identity';
|
|
2
|
+
/**
|
|
3
|
+
* What to tell someone whose installed background service would register their
|
|
4
|
+
* profile on the default profile's machine row (OSK-S-440).
|
|
5
|
+
*
|
|
6
|
+
* The consequence is stated before the remedy because the remedy costs something
|
|
7
|
+
* (updating a service) and the consequence is what justifies it: a second
|
|
8
|
+
* profile on the default's row replaces its Daemon and its device-key pin, and
|
|
9
|
+
* the default profile's next refresh is refused until it signs in again.
|
|
10
|
+
*
|
|
11
|
+
* Lives here, apart from the comparison, because this is copy and reads
|
|
12
|
+
* `BRAND.displayName`; the comparison is pure and imports nothing from it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function describeDaemonMachineIdentitySkew(skew: DaemonMachineIdentitySkew): string[];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeDaemonMachineIdentitySkew = describeDaemonMachineIdentitySkew;
|
|
4
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
|
+
/**
|
|
6
|
+
* What to tell someone whose installed background service would register their
|
|
7
|
+
* profile on the default profile's machine row (OSK-S-440).
|
|
8
|
+
*
|
|
9
|
+
* The consequence is stated before the remedy because the remedy costs something
|
|
10
|
+
* (updating a service) and the consequence is what justifies it: a second
|
|
11
|
+
* profile on the default's row replaces its Daemon and its device-key pin, and
|
|
12
|
+
* the default profile's next refresh is refused until it signs in again.
|
|
13
|
+
*
|
|
14
|
+
* Lives here, apart from the comparison, because this is copy and reads
|
|
15
|
+
* `BRAND.displayName`; the comparison is pure and imports nothing from it.
|
|
16
|
+
*/
|
|
17
|
+
function describeDaemonMachineIdentitySkew(skew) {
|
|
18
|
+
const product = data_provider_1.BRAND.displayName;
|
|
19
|
+
const said = skew.reported
|
|
20
|
+
? `it reports machine identity ${skew.reported}, where profile '${skew.profile}' has ${skew.expected}`
|
|
21
|
+
: `it does not report a machine identity at all, so ${product} cannot tell which one it would use`;
|
|
22
|
+
return [
|
|
23
|
+
`The ${product} background service at ${skew.binary} does not support a separate machine identity per profile: ${said}.`,
|
|
24
|
+
`Using it for '${skew.profile}' would register that profile on the default profile's machine and could replace the default profile's device-key pin. Nothing was changed.`,
|
|
25
|
+
`Update the background service (skrr daemon update), or put a current skrrd first on your PATH, then run: skrr login --profile ${skew.profile}`,
|
|
26
|
+
];
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface DaemonMachineIdentitySkew {
|
|
2
|
+
/** The profile being signed in. */
|
|
3
|
+
profile: string;
|
|
4
|
+
/** The machine identity this profile has, as decided by the profile's own record. */
|
|
5
|
+
expected: string;
|
|
6
|
+
/** What the daemon binary says it will send; null when it said nothing. */
|
|
7
|
+
reported: string | null;
|
|
8
|
+
/** The `skrrd` that was asked. */
|
|
9
|
+
binary: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Null when the daemon will register this profile under the identity it has;
|
|
13
|
+
* otherwise what disagrees.
|
|
14
|
+
*
|
|
15
|
+
* Not applicable to the default profile (its identity is the hardware value in
|
|
16
|
+
* every version), to a hosted runtime (the control plane issues it), or when no
|
|
17
|
+
* profile is named.
|
|
18
|
+
*
|
|
19
|
+
* A daemon that reports NOTHING is a skew, not a pass: this check exists because
|
|
20
|
+
* an older binary cannot say, and "could not confirm" must not read as "fine" on
|
|
21
|
+
* the step that binds a credential to a machine row.
|
|
22
|
+
*/
|
|
23
|
+
export declare function daemonMachineIdentitySkew(opts: {
|
|
24
|
+
profile: string | null | undefined;
|
|
25
|
+
binary: string;
|
|
26
|
+
reported: string | null | undefined;
|
|
27
|
+
}): DaemonMachineIdentitySkew | null;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.daemonMachineIdentitySkew = daemonMachineIdentitySkew;
|
|
4
|
+
/**
|
|
5
|
+
* daemon-machine-identity.ts — does the installed daemon give this profile the
|
|
6
|
+
* machine identity the CLI decided it has?
|
|
7
|
+
*
|
|
8
|
+
* Two programs compute one identity: the CLI (and the profile's persisted
|
|
9
|
+
* `machine-identity.json`) say a profile that started new has its own machine
|
|
10
|
+
* row, and the DAEMON is what registers. A daemon that predates per-profile
|
|
11
|
+
* identities (OSK-13845) does not read that file; it sends the hardware value
|
|
12
|
+
* whatever the profile is. Nothing noticed. The CLI minted the daemon's credential
|
|
13
|
+
* for what the daemon REPORTED (`accept-handoff --print-id`), so the credential
|
|
14
|
+
* was bound to the default machine's row, the service registered there, and the
|
|
15
|
+
* failure OSK-13845 exists to prevent happened anyway: two profiles on one Daemon
|
|
16
|
+
* row and one device-key pin, the second profile's interactive login re-pinning
|
|
17
|
+
* the first one's key (OSK-S-440, profile `aapexp`).
|
|
18
|
+
*
|
|
19
|
+
* Measured, not inferred: the same new profile, in an empty home, answered by
|
|
20
|
+
* two `skrrd` binaries. The current one reports `<hardware>:p-<hash>` and writes
|
|
21
|
+
* `machine-identity.json` and `daemon-identity.json`; the build that was first on
|
|
22
|
+
* the dogfooder's PATH (a checkout `dist` from before OSK-13845, whose version
|
|
23
|
+
* string still reads the checkout's package.json) reports the bare hardware value
|
|
24
|
+
* and writes neither. The profile it left behind had a `machine-identity.json`
|
|
25
|
+
* (the CLI's) and no `daemon-identity.json` after four daemon starts.
|
|
26
|
+
*
|
|
27
|
+
* The fix is to compare, not to guess a version: ask the binary what it will
|
|
28
|
+
* send, compare with what this profile's decided identity is, and refuse BEFORE
|
|
29
|
+
* anything is minted or installed when they differ. Comparing the value is the
|
|
30
|
+
* only test that cannot be wrong about a build that is older than its own
|
|
31
|
+
* version string says.
|
|
32
|
+
*
|
|
33
|
+
* Pure: the I/O (spawning the binary) is in `daemonHandoff.ts`, the copy in
|
|
34
|
+
* `daemon-machine-identity-copy.ts`.
|
|
35
|
+
*/
|
|
36
|
+
const machine_uuid_1 = require("@skrr-ai/auth-core/machine-uuid");
|
|
37
|
+
const keychain_1 = require("./keychain");
|
|
38
|
+
/**
|
|
39
|
+
* Null when the daemon will register this profile under the identity it has;
|
|
40
|
+
* otherwise what disagrees.
|
|
41
|
+
*
|
|
42
|
+
* Not applicable to the default profile (its identity is the hardware value in
|
|
43
|
+
* every version), to a hosted runtime (the control plane issues it), or when no
|
|
44
|
+
* profile is named.
|
|
45
|
+
*
|
|
46
|
+
* A daemon that reports NOTHING is a skew, not a pass: this check exists because
|
|
47
|
+
* an older binary cannot say, and "could not confirm" must not read as "fine" on
|
|
48
|
+
* the step that binds a credential to a machine row.
|
|
49
|
+
*/
|
|
50
|
+
function daemonMachineIdentitySkew(opts) {
|
|
51
|
+
const profile = opts.profile?.trim();
|
|
52
|
+
if (!profile || profile === keychain_1.DEFAULT_PROFILE)
|
|
53
|
+
return null;
|
|
54
|
+
if ((0, machine_uuid_1.hostedRuntimeIdOverride)('OVERSKY_MACHINE_UUID'))
|
|
55
|
+
return null;
|
|
56
|
+
const expected = (0, machine_uuid_1.getMachineUuidForProfile)(profile);
|
|
57
|
+
const reported = opts.reported?.trim() || null;
|
|
58
|
+
if (reported === expected)
|
|
59
|
+
return null;
|
|
60
|
+
return { profile, expected, reported, binary: opts.binary };
|
|
61
|
+
}
|
package/dist/lib/daemon-setup.js
CHANGED
|
@@ -33,7 +33,9 @@ exports.offerDaemonSetup = offerDaemonSetup;
|
|
|
33
33
|
* with an instruction when there is not, and never on the opt-out.
|
|
34
34
|
*/
|
|
35
35
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
36
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
36
37
|
const prompt_1 = require("./prompt");
|
|
38
|
+
const daemon_command_hint_1 = require("./daemon-command-hint");
|
|
37
39
|
const daemon_binding_1 = require("./daemon-binding");
|
|
38
40
|
const daemon_handoff_opt_out_1 = require("./daemon-handoff-opt-out");
|
|
39
41
|
const exec_runtime_binary_1 = require("./exec-runtime-binary");
|
|
@@ -94,8 +96,15 @@ async function offerDaemonSetup(opts = {}) {
|
|
|
94
96
|
}
|
|
95
97
|
const confirmImpl = opts.confirmImpl ?? prompt_1.confirm;
|
|
96
98
|
log('');
|
|
97
|
-
log(
|
|
99
|
+
log(` This computer has no ${data_provider_1.BRAND.displayName} background service. It is what lets your agents work here —`);
|
|
98
100
|
log(' running commands, editing files, and driving a browser on this machine.');
|
|
101
|
+
// What it does UNASKED is part of the decision, and it used to be found
|
|
102
|
+
// afterwards in a status table (OSK-16439). The defaults are the daemon's
|
|
103
|
+
// (`DEFAULT_CONFIG.permissions`, pinned against this sentence by
|
|
104
|
+
// `unattended-posture.spec.ts` in the daemon): reads, writes, shell commands
|
|
105
|
+
// and Claude sessions run without a prompt until someone changes them.
|
|
106
|
+
log(' Until you change it, agents can read files, edit files and run shell commands here');
|
|
107
|
+
log(` without asking you first. To be asked before each call instead: \`${(0, daemon_command_hint_1.daemonCommandHint)('config', { extraArgs: '--require-approvals' })}\``);
|
|
99
108
|
// This creates a persistent service with broad local capabilities, so a
|
|
100
109
|
// bare Enter must visibly and behaviorally decline it. `confirm` names its
|
|
101
110
|
// second argument `defaultNo`; passing false showed `[Y/n]` and accepted an
|
|
@@ -5,6 +5,7 @@ exports.describeBrokerRefusal = describeBrokerRefusal;
|
|
|
5
5
|
exports.describeGuestDaemonUnavailable = describeGuestDaemonUnavailable;
|
|
6
6
|
exports.describeLocalDaemonUnavailable = describeLocalDaemonUnavailable;
|
|
7
7
|
exports.describeBrokerFailure = describeBrokerFailure;
|
|
8
|
+
const daemon_command_hint_1 = require("./daemon-command-hint");
|
|
8
9
|
/**
|
|
9
10
|
* True when a daemon ANSWERED the hand-off and refused it — as opposed to no
|
|
10
11
|
* daemon being there to ask (`no_bootstrap`, a stale descriptor nobody listens
|
|
@@ -89,12 +90,12 @@ function describeBrokerRefusal(failure, bin = 'skrr') {
|
|
|
89
90
|
}
|
|
90
91
|
else if (code === 'DEVICE_PROOF_PIN_MISMATCH') {
|
|
91
92
|
remedy =
|
|
92
|
-
`Run \`${bin}
|
|
93
|
+
`Run \`${(0, daemon_command_hint_1.daemonCommandHint)('login', { bin })}\` to sign the background service in again — an interactive daemon login re-pins its device key — then retry. ` +
|
|
93
94
|
`\`${bin} login\` signs this CLI in on its own instead.`;
|
|
94
95
|
}
|
|
95
96
|
else if (DAEMON_RELOGIN_CODES.has(code) || failure.reason === 'daemon_not_authed') {
|
|
96
97
|
remedy =
|
|
97
|
-
`Run \`${bin}
|
|
98
|
+
`Run \`${(0, daemon_command_hint_1.daemonCommandHint)('login', { bin })}\` to sign the background service in again, then retry. ` +
|
|
98
99
|
`\`${bin} login\` signs this CLI in on its own instead.`;
|
|
99
100
|
}
|
|
100
101
|
else if (failure.reason === 'ci_token_refused') {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DaemonMachineIdentitySkew } from './daemon-machine-identity';
|
|
1
2
|
/**
|
|
2
3
|
* Hand the local daemon a credential of its own, right after `skrr login`.
|
|
3
4
|
*
|
|
@@ -40,8 +41,25 @@ export interface DaemonHandoffOutcome {
|
|
|
40
41
|
* The two absent cases are separated because they need different work — one
|
|
41
42
|
* needs a binary fetched, the other only needs the service registered.
|
|
42
43
|
*/
|
|
43
|
-
reason?: 'opted-out' | 'brokered' | 'no-binary' | 'legacy-binary' | 'unsupported-daemon' | 'no-service' | 'foreign-root' | 'mint-refused' | 'platform-managed';
|
|
44
|
+
reason?: 'opted-out' | 'brokered' | 'no-binary' | 'legacy-binary' | 'unsupported-daemon' | 'no-service' | 'foreign-root' | 'mint-refused' | 'platform-managed' | 'machine-identity-skew';
|
|
45
|
+
/** Set with `reason: 'machine-identity-skew'`; the caller words it. */
|
|
46
|
+
skew?: DaemonMachineIdentitySkew;
|
|
44
47
|
}
|
|
45
48
|
export declare function handOffToLocalDaemon(env?: NodeJS.ProcessEnv, opts?: {
|
|
46
49
|
loginFlow?: string;
|
|
47
50
|
}): Promise<DaemonHandoffOutcome>;
|
|
51
|
+
/**
|
|
52
|
+
* Ask the installed daemon which machine identity it will register `profile`
|
|
53
|
+
* under, and say what disagrees with the profile's own (OSK-S-440).
|
|
54
|
+
*
|
|
55
|
+
* For the commands that install or sign in a daemon WITHOUT going through the
|
|
56
|
+
* credential hand-off (`skrr daemon install`, `skrr daemon login`): the same
|
|
57
|
+
* question, asked of the binary they are about to run. Null when the daemon
|
|
58
|
+
* agrees, when the profile is the default one, or when there is no binary to ask
|
|
59
|
+
* (the caller has its own answer for that).
|
|
60
|
+
*
|
|
61
|
+
* A binary that cannot be asked at all - too old to have `accept-handoff`, or
|
|
62
|
+
* one that fails the probe - is reported as a skew with no identity, because the
|
|
63
|
+
* point of asking is not to bind a profile to a machine row on a guess.
|
|
64
|
+
*/
|
|
65
|
+
export declare function checkLocalDaemonMachineIdentity(profile: string | null | undefined, binary?: string | null): Promise<DaemonMachineIdentitySkew | null>;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.handOffToLocalDaemon = handOffToLocalDaemon;
|
|
4
|
+
exports.checkLocalDaemonMachineIdentity = checkLocalDaemonMachineIdentity;
|
|
4
5
|
const node_child_process_1 = require("node:child_process");
|
|
5
6
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
6
7
|
const config_1 = require("./config");
|
|
7
8
|
const daemon_handoff_opt_out_1 = require("./daemon-handoff-opt-out");
|
|
9
|
+
const daemon_machine_identity_1 = require("./daemon-machine-identity");
|
|
8
10
|
const dedicated_service_1 = require("./dedicated-service");
|
|
9
11
|
const exec_runtime_binary_1 = require("./exec-runtime-binary");
|
|
10
12
|
const keychain_1 = require("./keychain");
|
|
@@ -256,7 +258,8 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
256
258
|
return {
|
|
257
259
|
status: 'skipped',
|
|
258
260
|
reason: 'unsupported-daemon',
|
|
259
|
-
|
|
261
|
+
// The remedy is named by the caller, spelled for the profile (OSK-16435).
|
|
262
|
+
detail: 'the installed daemon is too old to accept a hand-off',
|
|
260
263
|
};
|
|
261
264
|
}
|
|
262
265
|
// Ask the daemon which id it will bind, rather than deriving it here. The id
|
|
@@ -274,6 +277,26 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
274
277
|
};
|
|
275
278
|
}
|
|
276
279
|
const { idSource, machineUuid, daemonServer } = parseIdProvenance(printed.stderr);
|
|
280
|
+
// Before anything is minted or installed: does THIS binary register the profile
|
|
281
|
+
// under the machine identity the profile has? The mint below binds the
|
|
282
|
+
// credential to the machine uuid the daemon just REPORTED, so a binary that
|
|
283
|
+
// reports the hardware value for a profile that has its own would put it on the
|
|
284
|
+
// default profile's row - and the install that follows would run that same
|
|
285
|
+
// binary as a service. Nothing about the id or the provenance would look wrong.
|
|
286
|
+
const skew = (0, daemon_machine_identity_1.daemonMachineIdentitySkew)({
|
|
287
|
+
profile: (0, keychain_1.getActiveProfile)(),
|
|
288
|
+
binary,
|
|
289
|
+
reported: machineUuid,
|
|
290
|
+
});
|
|
291
|
+
if (skew) {
|
|
292
|
+
return {
|
|
293
|
+
status: 'failed',
|
|
294
|
+
reason: 'machine-identity-skew',
|
|
295
|
+
skew,
|
|
296
|
+
detail: `the installed daemon reports machine identity ${skew.reported ?? '(none)'} ` +
|
|
297
|
+
`where profile '${skew.profile}' has ${skew.expected}`,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
277
300
|
// A well-formed id is not the same as a real daemon. With no service installed
|
|
278
301
|
// the daemon derives one from whatever directory this process is in, so the id
|
|
279
302
|
// above names a daemon that does not exist and will not exist under that id if
|
|
@@ -353,3 +376,40 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
353
376
|
}),
|
|
354
377
|
};
|
|
355
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Ask the installed daemon which machine identity it will register `profile`
|
|
381
|
+
* under, and say what disagrees with the profile's own (OSK-S-440).
|
|
382
|
+
*
|
|
383
|
+
* For the commands that install or sign in a daemon WITHOUT going through the
|
|
384
|
+
* credential hand-off (`skrr daemon install`, `skrr daemon login`): the same
|
|
385
|
+
* question, asked of the binary they are about to run. Null when the daemon
|
|
386
|
+
* agrees, when the profile is the default one, or when there is no binary to ask
|
|
387
|
+
* (the caller has its own answer for that).
|
|
388
|
+
*
|
|
389
|
+
* A binary that cannot be asked at all - too old to have `accept-handoff`, or
|
|
390
|
+
* one that fails the probe - is reported as a skew with no identity, because the
|
|
391
|
+
* point of asking is not to bind a profile to a machine row on a guess.
|
|
392
|
+
*/
|
|
393
|
+
async function checkLocalDaemonMachineIdentity(profile, binary = (0, exec_runtime_binary_1.findRuntimeBinary)()) {
|
|
394
|
+
if (!profile || profile === keychain_1.DEFAULT_PROFILE || !binary)
|
|
395
|
+
return null;
|
|
396
|
+
const printed = await runOversky(binary, ['accept-handoff', '--print-id', '--profile', profile], {
|
|
397
|
+
timeoutMs: PROBE_TIMEOUT_MS,
|
|
398
|
+
});
|
|
399
|
+
if (!printed.ok) {
|
|
400
|
+
// A daemon too old to have `accept-handoff` answers "unknown command": it is
|
|
401
|
+
// older than per-profile identities too, which is the skew itself.
|
|
402
|
+
if (/unknown command/i.test(printed.stderr)) {
|
|
403
|
+
return (0, daemon_machine_identity_1.daemonMachineIdentitySkew)({ profile, binary, reported: null });
|
|
404
|
+
}
|
|
405
|
+
// Any other failed probe says nothing about the daemon's identity, and
|
|
406
|
+
// refusing a sign-in on a flaky probe would be a worse failure than the one
|
|
407
|
+
// this guards: let the command run and meet whatever the daemon itself says.
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
return (0, daemon_machine_identity_1.daemonMachineIdentitySkew)({
|
|
411
|
+
profile,
|
|
412
|
+
binary,
|
|
413
|
+
reported: parseIdProvenance(printed.stderr).machineUuid,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the server this CLI talks to is running: the commit its API was built
|
|
3
|
+
* from and the commit its web shell was built from (OSK-15497).
|
|
4
|
+
*
|
|
5
|
+
* Both are public facts the server already publishes for anyone:
|
|
6
|
+
* - the API: `GET /api/health` → `build.commit`, baked into the image once at
|
|
7
|
+
* build time by the release pipeline;
|
|
8
|
+
* - the web: `GET /web-version.json` → `sha`, written into the shell when it
|
|
9
|
+
* is published.
|
|
10
|
+
*
|
|
11
|
+
* They are read separately and shown separately because they legitimately
|
|
12
|
+
* differ: a release that changes only API code keeps the published shell, and
|
|
13
|
+
* a frontend-only release keeps the API image. Collapsing them into one
|
|
14
|
+
* "deployed version" would be wrong whenever that happens, which is often.
|
|
15
|
+
*
|
|
16
|
+
* Neither read needs a credential, and neither failure is the user's problem,
|
|
17
|
+
* so a failure is reported as "could not read" with the reason, never as an
|
|
18
|
+
* error that fails `doctor`.
|
|
19
|
+
*/
|
|
20
|
+
export type BuildCommitRead = {
|
|
21
|
+
/** The 40-hex commit, or null when the server did not name one. */
|
|
22
|
+
commit: string | null;
|
|
23
|
+
/** Where it was read from, so a reader can check it themselves. */
|
|
24
|
+
source: string;
|
|
25
|
+
/** Why there is no commit: unreadable, or the server reports an unstamped build. */
|
|
26
|
+
error: string | null;
|
|
27
|
+
};
|
|
28
|
+
export type DeployedBuild = {
|
|
29
|
+
api: BuildCommitRead;
|
|
30
|
+
web: BuildCommitRead;
|
|
31
|
+
};
|
|
32
|
+
type FetchLike = (url: string, init?: {
|
|
33
|
+
headers?: Record<string, string>;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}) => Promise<{
|
|
36
|
+
ok: boolean;
|
|
37
|
+
status: number;
|
|
38
|
+
json: () => Promise<unknown>;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function readDeployedBuild(baseURL: string, fetchImpl?: FetchLike): Promise<DeployedBuild>;
|
|
41
|
+
/** The lines `skrr doctor` prints under the server. */
|
|
42
|
+
export declare function deployedBuildLines(build: DeployedBuild): string[];
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What the server this CLI talks to is running: the commit its API was built
|
|
4
|
+
* from and the commit its web shell was built from (OSK-15497).
|
|
5
|
+
*
|
|
6
|
+
* Both are public facts the server already publishes for anyone:
|
|
7
|
+
* - the API: `GET /api/health` → `build.commit`, baked into the image once at
|
|
8
|
+
* build time by the release pipeline;
|
|
9
|
+
* - the web: `GET /web-version.json` → `sha`, written into the shell when it
|
|
10
|
+
* is published.
|
|
11
|
+
*
|
|
12
|
+
* They are read separately and shown separately because they legitimately
|
|
13
|
+
* differ: a release that changes only API code keeps the published shell, and
|
|
14
|
+
* a frontend-only release keeps the API image. Collapsing them into one
|
|
15
|
+
* "deployed version" would be wrong whenever that happens, which is often.
|
|
16
|
+
*
|
|
17
|
+
* Neither read needs a credential, and neither failure is the user's problem,
|
|
18
|
+
* so a failure is reported as "could not read" with the reason, never as an
|
|
19
|
+
* error that fails `doctor`.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.readDeployedBuild = readDeployedBuild;
|
|
23
|
+
exports.deployedBuildLines = deployedBuildLines;
|
|
24
|
+
const COMMIT = /^[a-f0-9]{40}$/;
|
|
25
|
+
const TIMEOUT_MS = 5_000;
|
|
26
|
+
async function readCommit(url, pick, unstamped, fetchImpl) {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetchImpl(url, {
|
|
29
|
+
headers: { accept: 'application/json' },
|
|
30
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok)
|
|
33
|
+
return { commit: null, source: url, error: `HTTP ${res.status}` };
|
|
34
|
+
const value = pick(await res.json());
|
|
35
|
+
if (typeof value === 'string' && COMMIT.test(value)) {
|
|
36
|
+
return { commit: value, source: url, error: null };
|
|
37
|
+
}
|
|
38
|
+
return { commit: null, source: url, error: unstamped };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return {
|
|
42
|
+
commit: null,
|
|
43
|
+
source: url,
|
|
44
|
+
error: err instanceof Error ? err.message : String(err),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function readDeployedBuild(baseURL, fetchImpl = fetch) {
|
|
49
|
+
const origin = baseURL.replace(/\/+$/, '');
|
|
50
|
+
const [api, web] = await Promise.all([
|
|
51
|
+
readCommit(`${origin}/api/health`, (body) => body?.build?.commit, 'the server does not report the commit its API was built from', fetchImpl),
|
|
52
|
+
readCommit(`${origin}/web-version.json`, (body) => body?.sha, 'the published web shell carries no commit', fetchImpl),
|
|
53
|
+
]);
|
|
54
|
+
return { api, web };
|
|
55
|
+
}
|
|
56
|
+
const short = (sha) => sha.slice(0, 12);
|
|
57
|
+
/** The lines `skrr doctor` prints under the server. */
|
|
58
|
+
function deployedBuildLines(build) {
|
|
59
|
+
const describe = (read) => read.commit ? short(read.commit) : `unknown (${read.error ?? 'no commit'})`;
|
|
60
|
+
const lines = [`Deployed: API ${describe(build.api)} · web ${describe(build.web)}`];
|
|
61
|
+
if (build.api.commit && build.web.commit && build.api.commit !== build.web.commit) {
|
|
62
|
+
lines.push(' Built from different commits. That is normal: a release that changes only ' +
|
|
63
|
+
'one of them keeps the other as it was.');
|
|
64
|
+
}
|
|
65
|
+
return lines;
|
|
66
|
+
}
|
package/dist/lib/grants.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GrantAccess, GrantAccessSource } from '@skrr-ai/data-provider';
|
|
2
|
+
import { type TableColumn } from './format';
|
|
2
3
|
export declare const GRANT_PRINCIPAL_KINDS: readonly ["agent", "commitment"];
|
|
3
4
|
export declare const GRANT_CREDENTIAL_KINDS: readonly ["env", "bearer", "basic", "json"];
|
|
4
5
|
export interface GrantPrincipal {
|
|
@@ -20,7 +21,8 @@ export interface Grant {
|
|
|
20
21
|
configured: boolean;
|
|
21
22
|
kind?: string;
|
|
22
23
|
envKeys: string[];
|
|
23
|
-
|
|
24
|
+
/** When the value was last set (create or rotate). Never a digest of it. */
|
|
25
|
+
setAt?: string;
|
|
24
26
|
} | null;
|
|
25
27
|
tools: string[];
|
|
26
28
|
runtime: {
|
|
@@ -58,4 +60,28 @@ export declare const grantApi: {
|
|
|
58
60
|
* gate that could drift.
|
|
59
61
|
*/
|
|
60
62
|
export declare function parseEnvPairs(pairs: string[]): Record<string, string>;
|
|
63
|
+
/** The cells every Grant table shows, in one place (OSK-15357). */
|
|
64
|
+
export type GrantTableCells = {
|
|
65
|
+
id: string;
|
|
66
|
+
label: string;
|
|
67
|
+
provider: string;
|
|
68
|
+
access: string;
|
|
69
|
+
principal: string;
|
|
70
|
+
credential: string;
|
|
71
|
+
runtime: string;
|
|
72
|
+
state: string;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* One Grant as table cells. `skrr grants list` and `skrr workspaces grants`
|
|
76
|
+
* both read this, so a column with the same header means the same thing in
|
|
77
|
+
* both and the two tables can be compared by eye. They used not to: the
|
|
78
|
+
* workspace table printed capabilities under ACCESS while this one printed the
|
|
79
|
+
* declared access class.
|
|
80
|
+
*/
|
|
81
|
+
export declare function grantTableCells(g: Pick<Grant, 'id' | 'label' | 'provider' | 'access' | 'accessSource' | 'credential' | 'revokedAt' | 'active'> & {
|
|
82
|
+
principal?: Partial<GrantPrincipal> | null;
|
|
83
|
+
runtime?: Partial<Grant['runtime']> | null;
|
|
84
|
+
}): GrantTableCells;
|
|
85
|
+
/** The shared columns, in order; a caller may add its own around them. */
|
|
86
|
+
export declare const GRANT_TABLE_COLUMNS: Array<TableColumn<GrantTableCells>>;
|
|
61
87
|
export declare function renderGrantList(grants: Grant[], log: (line: string) => void): void;
|
package/dist/lib/grants.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.grantApi = exports.GRANT_CREDENTIAL_KINDS = exports.GRANT_PRINCIPAL_KINDS = void 0;
|
|
3
|
+
exports.GRANT_TABLE_COLUMNS = exports.grantApi = exports.GRANT_CREDENTIAL_KINDS = exports.GRANT_PRINCIPAL_KINDS = void 0;
|
|
4
4
|
exports.parseEnvPairs = parseEnvPairs;
|
|
5
|
+
exports.grantTableCells = grantTableCells;
|
|
5
6
|
exports.renderGrantList = renderGrantList;
|
|
6
7
|
/**
|
|
7
8
|
* grants.ts — client for the Grant vault (`/api/grants/*`).
|
|
@@ -51,35 +52,52 @@ function parseEnvPairs(pairs) {
|
|
|
51
52
|
}
|
|
52
53
|
return out;
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
/**
|
|
56
|
+
* One Grant as table cells. `skrr grants list` and `skrr workspaces grants`
|
|
57
|
+
* both read this, so a column with the same header means the same thing in
|
|
58
|
+
* both and the two tables can be compared by eye. They used not to: the
|
|
59
|
+
* workspace table printed capabilities under ACCESS while this one printed the
|
|
60
|
+
* declared access class.
|
|
61
|
+
*/
|
|
62
|
+
function grantTableCells(g) {
|
|
63
|
+
const principal = g.principal ?? null;
|
|
64
|
+
let state = 'expired';
|
|
65
|
+
if (g.revokedAt)
|
|
66
|
+
state = 'revoked';
|
|
67
|
+
else if (g.active)
|
|
68
|
+
state = 'active';
|
|
69
|
+
return {
|
|
60
70
|
id: g.id,
|
|
61
71
|
label: g.label,
|
|
62
|
-
provider: g.provider,
|
|
72
|
+
provider: g.provider || '—',
|
|
63
73
|
access: g.access
|
|
64
74
|
? `${g.access} (${g.accessSource === 'attested' ? 'attested' : 'declared, not verified'})`
|
|
65
75
|
: 'undeclared',
|
|
66
|
-
principal:
|
|
76
|
+
principal: principal?.agentId || principal?.commitmentId || principal?.kind || '—',
|
|
67
77
|
// NAMES only. This is the audit surface; a value never reaches the CLI.
|
|
68
78
|
credential: g.credential?.configured
|
|
69
79
|
? g.credential.envKeys.join(',') || g.credential.kind || 'set'
|
|
70
80
|
: '-',
|
|
71
|
-
runtime: [g.runtime
|
|
81
|
+
runtime: [g.runtime?.sandbox ? 'sandbox' : null, g.runtime?.daemon ? 'daemon' : null]
|
|
72
82
|
.filter(Boolean)
|
|
73
83
|
.join('+') || 'none',
|
|
74
|
-
state
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
state,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** The shared columns, in order; a caller may add its own around them. */
|
|
88
|
+
exports.GRANT_TABLE_COLUMNS = [
|
|
89
|
+
{ key: 'label', header: 'LABEL', maxWidth: 26 },
|
|
90
|
+
{ key: 'provider', header: 'PROVIDER', maxWidth: 12 },
|
|
91
|
+
{ key: 'access', header: 'ACCESS', maxWidth: 32 },
|
|
92
|
+
{ key: 'principal', header: 'PRINCIPAL', maxWidth: 24 },
|
|
93
|
+
{ key: 'credential', header: 'VARIABLES', maxWidth: 34 },
|
|
94
|
+
{ key: 'runtime', header: 'RUNTIME', maxWidth: 16 },
|
|
95
|
+
{ key: 'state', header: 'STATE', maxWidth: 8 },
|
|
96
|
+
];
|
|
97
|
+
function renderGrantList(grants, log) {
|
|
98
|
+
if (grants.length === 0) {
|
|
99
|
+
log('No grants.');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
(0, format_1.renderTable)(grants.map((g) => grantTableCells(g)), [...exports.GRANT_TABLE_COLUMNS, { key: 'id', header: 'ID', verbatim: true }], log);
|
|
85
103
|
}
|
package/dist/lib/harnesses.d.ts
CHANGED
|
@@ -238,6 +238,77 @@ export declare function updateHarness(id: string, patch: Record<string, unknown>
|
|
|
238
238
|
harness?: Harness;
|
|
239
239
|
}>;
|
|
240
240
|
export declare function forgetHarness(id: string): Promise<unknown>;
|
|
241
|
+
/**
|
|
242
|
+
* Why a superseded-looking row is KEPT by `harnesses prune`. Declared by the
|
|
243
|
+
* server (`pruneSupersededHarnesses.js`, `REASONS`) and worded once, in
|
|
244
|
+
* `harnessPruneReasonSentence`, so the table and `--json` never disagree.
|
|
245
|
+
*/
|
|
246
|
+
export type HarnessPruneReason = 'recent' | 'latest_registration' | 'no_live_sibling' | 'pinned' | 'granted' | 'default' | 'aliased' | 'changed_since_plan';
|
|
247
|
+
export interface HarnessPruneRow {
|
|
248
|
+
id: string;
|
|
249
|
+
daemonId: string | null;
|
|
250
|
+
provider: string | null;
|
|
251
|
+
name: string | null;
|
|
252
|
+
lastSeenAt: string | null;
|
|
253
|
+
machineUuid: string | null;
|
|
254
|
+
/** The live sibling that replaced a row this call removes (or would). */
|
|
255
|
+
supersededBy?: {
|
|
256
|
+
id: string;
|
|
257
|
+
daemonId: string | null;
|
|
258
|
+
lastSeenAt: string | null;
|
|
259
|
+
};
|
|
260
|
+
/** Set on kept rows. */
|
|
261
|
+
reason?: HarnessPruneReason;
|
|
262
|
+
/** For `pinned`: who holds the pin. Other people's agents are a count, never named. */
|
|
263
|
+
detail?: {
|
|
264
|
+
agents?: Array<{
|
|
265
|
+
id: string;
|
|
266
|
+
name: string | null;
|
|
267
|
+
}>;
|
|
268
|
+
moreAgents?: number;
|
|
269
|
+
otherAuthors?: number;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
export interface HarnessPruneResult {
|
|
273
|
+
dryRun: boolean;
|
|
274
|
+
limit: number;
|
|
275
|
+
minUnseenMs: number;
|
|
276
|
+
scan: {
|
|
277
|
+
rows: number;
|
|
278
|
+
complete: boolean;
|
|
279
|
+
};
|
|
280
|
+
/** Rows this call removes (dry run: would remove). */
|
|
281
|
+
remove: HarnessPruneRow[];
|
|
282
|
+
/** Rows this call actually removed; 0 on a dry run. */
|
|
283
|
+
removedCount: number;
|
|
284
|
+
/** Removable rows beyond `limit`; the next call takes them. */
|
|
285
|
+
remaining: number;
|
|
286
|
+
kept: HarnessPruneRow[];
|
|
287
|
+
keptTruncated: boolean;
|
|
288
|
+
counts: Record<string, number>;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Remove the caller's superseded harness rows - or, by default, say which would go.
|
|
292
|
+
*
|
|
293
|
+
* ONE route for both: the server makes the decision once and this only asks for it
|
|
294
|
+
* to be shown (`dryRun: true`, also its own default) or carried out. The caller
|
|
295
|
+
* never lists rows and picks, so there is no client-side rule to drift from the
|
|
296
|
+
* server's. A write, so `apiFetch` gives it an idempotency key and a retried
|
|
297
|
+
* request replays the recorded answer instead of running again.
|
|
298
|
+
*/
|
|
299
|
+
export declare function pruneHarnesses(options?: {
|
|
300
|
+
dryRun: boolean;
|
|
301
|
+
limit?: number;
|
|
302
|
+
minUnseenMs?: number;
|
|
303
|
+
}): Promise<HarnessPruneResult>;
|
|
304
|
+
/**
|
|
305
|
+
* `--older-than` takes a whole number with a unit: `90m`, `36h`, `7d`. A bare
|
|
306
|
+
* number is hours. Returns milliseconds. The server never goes below its offline
|
|
307
|
+
* window whatever this says.
|
|
308
|
+
*/
|
|
309
|
+
export declare function parseHarnessPruneAge(value: string): number;
|
|
310
|
+
/** One sentence per reason a row was kept. The only place they are worded. */
|
|
311
|
+
export declare function harnessPruneReasonSentence(row: HarnessPruneRow): string;
|
|
241
312
|
/**
|
|
242
313
|
* A per-USER display name. Distinct from `updateHarness({name})`, which renames
|
|
243
314
|
* the harness for everyone it is shared with — this only changes what YOU see,
|