@skrr-ai/cli 0.1.109 → 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/dist/base-command.d.ts +15 -0
- package/dist/base-command.js +64 -17
- 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 +11 -0
- package/dist/commands/agents/update.js +58 -2
- package/dist/commands/daemon/install.js +17 -1
- package/dist/commands/daemon/login.js +14 -0
- package/dist/commands/harnesses/prune.d.ts +26 -0
- package/dist/commands/harnesses/prune.js +172 -0
- package/dist/commands/login.js +11 -0
- package/dist/commands/spaces/workflows/start.js +3 -1
- package/dist/commands/spaces/workflows/validate.js +12 -2
- package/dist/commands/tasks/activity.js +11 -1
- package/dist/commands/tasks/list.js +6 -2
- package/dist/commands/tasks/rearm-recurrence.js +22 -3
- package/dist/commands/tasks/show.d.ts +8 -0
- package/dist/commands/tasks/show.js +64 -32
- package/dist/commands/tasks/unschedule.js +5 -1
- package/dist/commands/tasks/update.js +11 -1
- package/dist/commands/triggers/audit.js +12 -3
- package/dist/commands/triggers/create.js +1 -1
- 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.js +1 -1
- package/dist/commands/triggers/update.js +1 -1
- package/dist/commands/workspaces/grants.js +1 -4
- package/dist/commands/workspaces/members.js +6 -6
- package/dist/lib/commitment-product.js +5 -2
- package/dist/lib/commitments.d.ts +2 -1
- package/dist/lib/commitments.js +40 -40
- 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/daemonHandoff.d.ts +19 -1
- package/dist/lib/daemonHandoff.js +59 -0
- package/dist/lib/harnesses.d.ts +71 -0
- package/dist/lib/harnesses.js +70 -0
- package/dist/lib/list-output.d.ts +1 -1
- package/dist/lib/list-output.js +2 -0
- package/dist/lib/profile-machine-identity-guard.js +11 -1
- package/dist/lib/task-economics.d.ts +10 -0
- package/dist/lib/task-economics.js +24 -2
- package/dist/lib/triggers.d.ts +11 -0
- package/dist/lib/triggers.js +43 -1
- package/dist/lib/workflow-run-view.d.ts +4 -0
- package/dist/lib/workflow-run-view.js +5 -0
- package/dist/lib/workspaces.d.ts +24 -7
- package/dist/lib/workspaces.js +14 -2
- 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/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/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +4003 -3968
- package/oclif.manifest.json +99 -9
- package/package.json +10 -1
package/dist/base-command.d.ts
CHANGED
|
@@ -585,6 +585,21 @@ export declare function isTypedServerRefusal(body: Record<string, unknown> | nul
|
|
|
585
585
|
*/
|
|
586
586
|
export declare function isApplicationCodedUnavailable(status: number | undefined, body: Record<string, unknown> | null): boolean;
|
|
587
587
|
export declare function isRetryableFailure(status: number | undefined, body: Record<string, unknown> | null): boolean;
|
|
588
|
+
/**
|
|
589
|
+
* The `details` of a CLI error envelope, in ONE shape. The server's refusal body becomes the
|
|
590
|
+
* envelope's details; when that body carries its own `details` object (a typed refusal's
|
|
591
|
+
* findings, e.g. `signalableNodeKeys`), those fields are lifted to the same level instead of
|
|
592
|
+
* arriving as `error.details.details`, a depth no other error used (OSK-16494). A body without
|
|
593
|
+
* a nested object is passed through unchanged.
|
|
594
|
+
*/
|
|
595
|
+
export declare function envelopeDetails(body: Record<string, unknown> | null | undefined): unknown;
|
|
596
|
+
/**
|
|
597
|
+
* The hint for a `Nonexistent flags:` refusal, or null. Two cases:
|
|
598
|
+
* - a flag retired from THIS command, which this command does not define again: where it went;
|
|
599
|
+
* - a flag token holding a space (`"--outcome failed"` passed as one argument): what happened,
|
|
600
|
+
* which oclif reports only as an unknown flag.
|
|
601
|
+
*/
|
|
602
|
+
export declare function retiredFlagHint(message: string, commandId?: string, definedFlags?: readonly string[]): string | null;
|
|
588
603
|
/**
|
|
589
604
|
* P6: a parse error used to say what was wrong and drop the remedy that text
|
|
590
605
|
* mode (and every domain error) carries. Adds `fix` (one imperative line an
|
package/dist/base-command.js
CHANGED
|
@@ -17,6 +17,8 @@ exports.describeDaemonScopedCredential = describeDaemonScopedCredential;
|
|
|
17
17
|
exports.isTypedServerRefusal = isTypedServerRefusal;
|
|
18
18
|
exports.isApplicationCodedUnavailable = isApplicationCodedUnavailable;
|
|
19
19
|
exports.isRetryableFailure = isRetryableFailure;
|
|
20
|
+
exports.envelopeDetails = envelopeDetails;
|
|
21
|
+
exports.retiredFlagHint = retiredFlagHint;
|
|
20
22
|
exports.parseErrorRemedy = parseErrorRemedy;
|
|
21
23
|
const node_fs_1 = require("node:fs");
|
|
22
24
|
const core_1 = require("@oclif/core");
|
|
@@ -1191,7 +1193,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1191
1193
|
// so a caller following older instructions — including the managed block the
|
|
1192
1194
|
// platform itself installed — got a bare `Nonexistent flags:` with no hint
|
|
1193
1195
|
// that the capability moved rather than vanished. Name where each went.
|
|
1194
|
-
const retiredHint = retiredFlagHint(err.message || '');
|
|
1196
|
+
const retiredHint = retiredFlagHint(err.message || '', this.id, Object.keys(this.constructor.flags ?? {}));
|
|
1195
1197
|
if (retiredHint) {
|
|
1196
1198
|
err.message = `${err.message}\n\n${retiredHint}`;
|
|
1197
1199
|
}
|
|
@@ -1336,7 +1338,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1336
1338
|
status: e.status,
|
|
1337
1339
|
exit: 2,
|
|
1338
1340
|
retryable: false,
|
|
1339
|
-
details: body
|
|
1341
|
+
details: envelopeDetails(body),
|
|
1340
1342
|
});
|
|
1341
1343
|
}
|
|
1342
1344
|
else if (bodyCode === 'AGENT_RUNTIME_UNAVAILABLE') {
|
|
@@ -1350,7 +1352,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1350
1352
|
status: e.status,
|
|
1351
1353
|
exit: 1,
|
|
1352
1354
|
retryable: false,
|
|
1353
|
-
details: body
|
|
1355
|
+
details: envelopeDetails(body),
|
|
1354
1356
|
});
|
|
1355
1357
|
}
|
|
1356
1358
|
else if (bodyCode === 'GITHUB_APP_NOT_CONFIGURED') {
|
|
@@ -1360,7 +1362,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1360
1362
|
status: e.status,
|
|
1361
1363
|
exit: 2,
|
|
1362
1364
|
retryable: false,
|
|
1363
|
-
details: body
|
|
1365
|
+
details: envelopeDetails(body),
|
|
1364
1366
|
});
|
|
1365
1367
|
}
|
|
1366
1368
|
else if (bodyCode === 'GITHUB_UNAVAILABLE') {
|
|
@@ -1376,7 +1378,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1376
1378
|
status: e.status,
|
|
1377
1379
|
exit: 5,
|
|
1378
1380
|
retryable: true,
|
|
1379
|
-
details: body
|
|
1381
|
+
details: envelopeDetails(body),
|
|
1380
1382
|
});
|
|
1381
1383
|
}
|
|
1382
1384
|
else if (bodyCode === 'LABEL_SUGGESTION_UNAVAILABLE') {
|
|
@@ -1390,7 +1392,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1390
1392
|
status: e.status,
|
|
1391
1393
|
exit: 1,
|
|
1392
1394
|
retryable: false,
|
|
1393
|
-
details: body
|
|
1395
|
+
details: envelopeDetails(body),
|
|
1394
1396
|
});
|
|
1395
1397
|
}
|
|
1396
1398
|
else if (bodyCode === 'ASSIGNEE_NOT_IN_SPACE') {
|
|
@@ -1403,7 +1405,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1403
1405
|
status: e.status,
|
|
1404
1406
|
exit: 2,
|
|
1405
1407
|
retryable: false,
|
|
1406
|
-
details: body
|
|
1408
|
+
details: envelopeDetails(body),
|
|
1407
1409
|
});
|
|
1408
1410
|
}
|
|
1409
1411
|
else if (e.status === 401) {
|
|
@@ -1552,6 +1554,10 @@ class BaseCommand extends core_1.Command {
|
|
|
1552
1554
|
status: e.status,
|
|
1553
1555
|
exit: 4,
|
|
1554
1556
|
retryable: false,
|
|
1557
|
+
// A typed 404 carries what the server found (a workflow signal names the nodes it CAN
|
|
1558
|
+
// resolve); dropping the body here made the 404 the one refusal with no machine-readable
|
|
1559
|
+
// details while every 409 kept them (OSK-16473).
|
|
1560
|
+
...(body ? { details: envelopeDetails(body) } : {}),
|
|
1555
1561
|
});
|
|
1556
1562
|
}
|
|
1557
1563
|
else if (e.code === 'ECONNREFUSED' ||
|
|
@@ -1584,7 +1590,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1584
1590
|
status: e.status,
|
|
1585
1591
|
exit: 1,
|
|
1586
1592
|
retryable: isRetryableFailure(e.status, body),
|
|
1587
|
-
details: body
|
|
1593
|
+
details: envelopeDetails(body),
|
|
1588
1594
|
requestId: stringProp(body, 'requestId') || e.requestId,
|
|
1589
1595
|
});
|
|
1590
1596
|
}
|
|
@@ -1623,7 +1629,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1623
1629
|
status: e.status,
|
|
1624
1630
|
exit: retryable ? 5 : 1,
|
|
1625
1631
|
retryable,
|
|
1626
|
-
details: body
|
|
1632
|
+
details: envelopeDetails(body),
|
|
1627
1633
|
...(requestId ? { requestId } : {}),
|
|
1628
1634
|
});
|
|
1629
1635
|
}
|
|
@@ -1661,7 +1667,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1661
1667
|
status: e.status,
|
|
1662
1668
|
exit: 5,
|
|
1663
1669
|
retryable: true,
|
|
1664
|
-
details: body
|
|
1670
|
+
details: envelopeDetails(body),
|
|
1665
1671
|
});
|
|
1666
1672
|
}
|
|
1667
1673
|
else if (typeof e.status === 'number' && e.status >= 500) {
|
|
@@ -1689,7 +1695,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1689
1695
|
status: e.status,
|
|
1690
1696
|
exit: 1,
|
|
1691
1697
|
retryable: isRetryableFailure(e.status, body),
|
|
1692
|
-
details: body
|
|
1698
|
+
details: envelopeDetails(body),
|
|
1693
1699
|
});
|
|
1694
1700
|
}
|
|
1695
1701
|
else {
|
|
@@ -1699,7 +1705,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1699
1705
|
status: e.status,
|
|
1700
1706
|
exit: 1,
|
|
1701
1707
|
retryable: isRetryableFailure(e.status, body),
|
|
1702
|
-
details: body
|
|
1708
|
+
details: envelopeDetails(body),
|
|
1703
1709
|
});
|
|
1704
1710
|
}
|
|
1705
1711
|
}
|
|
@@ -2454,13 +2460,54 @@ const RETIRED_FLAG_REPLACEMENTS = {
|
|
|
2454
2460
|
'`tasks deliverable add`.',
|
|
2455
2461
|
'--supersede': '--supersede is implicit: submitting again creates a new Result version.',
|
|
2456
2462
|
};
|
|
2457
|
-
|
|
2463
|
+
/**
|
|
2464
|
+
* The `details` of a CLI error envelope, in ONE shape. The server's refusal body becomes the
|
|
2465
|
+
* envelope's details; when that body carries its own `details` object (a typed refusal's
|
|
2466
|
+
* findings, e.g. `signalableNodeKeys`), those fields are lifted to the same level instead of
|
|
2467
|
+
* arriving as `error.details.details`, a depth no other error used (OSK-16494). A body without
|
|
2468
|
+
* a nested object is passed through unchanged.
|
|
2469
|
+
*/
|
|
2470
|
+
function envelopeDetails(body) {
|
|
2471
|
+
if (!body)
|
|
2472
|
+
return undefined;
|
|
2473
|
+
const nested = body.details;
|
|
2474
|
+
if (!nested || typeof nested !== 'object' || Array.isArray(nested))
|
|
2475
|
+
return body;
|
|
2476
|
+
const { details: _nested, ...rest } = body;
|
|
2477
|
+
return { ...rest, ...nested };
|
|
2478
|
+
}
|
|
2479
|
+
/**
|
|
2480
|
+
* The commands those flags were retired FROM. The table was applied to every command, so a
|
|
2481
|
+
* mis-quoted `"--outcome failed"` on `triggers audit` (where --outcome is a live flag) was told
|
|
2482
|
+
* "--outcome is now --summary" (OSK-16494). A rename is a fact about one command.
|
|
2483
|
+
*/
|
|
2484
|
+
const RETIRED_FLAG_COMMANDS = new Set(['tasks:complete', 'tasks:result:submit']);
|
|
2485
|
+
/**
|
|
2486
|
+
* The hint for a `Nonexistent flags:` refusal, or null. Two cases:
|
|
2487
|
+
* - a flag retired from THIS command, which this command does not define again: where it went;
|
|
2488
|
+
* - a flag token holding a space (`"--outcome failed"` passed as one argument): what happened,
|
|
2489
|
+
* which oclif reports only as an unknown flag.
|
|
2490
|
+
*/
|
|
2491
|
+
function retiredFlagHint(message, commandId, definedFlags = []) {
|
|
2458
2492
|
if (!/nonexistent flag/i.test(message))
|
|
2459
2493
|
return null;
|
|
2460
|
-
const
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2494
|
+
const lines = [];
|
|
2495
|
+
const quoted = /(?:^|[\s,:])(--[a-z][\w-]*)[ =]([^\s,][^,\n]*)/i.exec(message.slice(message.search(/nonexistent flag/i)));
|
|
2496
|
+
const quotedFlag = quoted?.[1];
|
|
2497
|
+
if (quotedFlag && definedFlags.includes(quotedFlag.replace(/^--/, ''))) {
|
|
2498
|
+
lines.push(` "${quoted[0].trim()}" reached the command as ONE argument, so it was read as a flag named ` +
|
|
2499
|
+
`"${quoted[0].trim()}". Pass the flag and its value separately: ${quotedFlag} ${quoted[2].trim()}`);
|
|
2500
|
+
}
|
|
2501
|
+
if (commandId && RETIRED_FLAG_COMMANDS.has(commandId)) {
|
|
2502
|
+
for (const flag of Object.keys(RETIRED_FLAG_REPLACEMENTS)) {
|
|
2503
|
+
if (definedFlags.includes(flag.replace(/^--/, '')))
|
|
2504
|
+
continue;
|
|
2505
|
+
if (new RegExp(`${flag}(?![\\w-])`).test(message)) {
|
|
2506
|
+
lines.push(` ${RETIRED_FLAG_REPLACEMENTS[flag]}`);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
return lines.length ? lines.join('\n') : null;
|
|
2464
2511
|
}
|
|
2465
2512
|
/**
|
|
2466
2513
|
* P6: a parse error used to say what was wrong and drop the remedy that text
|
|
@@ -76,7 +76,7 @@ class AgentsTriggersCreate extends base_command_1.BaseCommand {
|
|
|
76
76
|
description: 'ISO-8601 datetime after which a schedule stops firing (--kind schedule)',
|
|
77
77
|
}),
|
|
78
78
|
'stale-catchup-ms': core_1.Flags.integer({
|
|
79
|
-
description: 'How late a missed occurrence may still run, in ms (
|
|
79
|
+
description: 'How late a missed occurrence may still run, in ms, from 1000 (1 second) to 31622400000 (366 days). After downtime, an occurrence more than this overdue is skipped rather than run late, and the schedule moves to its next future slot. Unset: 3600000 (1 hour).',
|
|
80
80
|
}),
|
|
81
81
|
// once
|
|
82
82
|
at: core_1.Flags.string({
|
|
@@ -63,7 +63,7 @@ class AgentsTriggersUpdate extends base_command_1.BaseCommand {
|
|
|
63
63
|
description: 'ISO-8601 datetime after which a schedule stops firing',
|
|
64
64
|
}),
|
|
65
65
|
'stale-catchup-ms': core_1.Flags.integer({
|
|
66
|
-
description: 'How late a missed occurrence may still run, in ms (
|
|
66
|
+
description: 'How late a missed occurrence may still run, in ms, from 1000 (1 second) to 31622400000 (366 days). After downtime, an occurrence more than this overdue is skipped rather than run late, and the schedule moves to its next future slot. Unset: 3600000 (1 hour).',
|
|
67
67
|
}),
|
|
68
68
|
at: core_1.Flags.string({ description: 'ISO-8601 datetime for once triggers' }),
|
|
69
69
|
entity: core_1.Flags.string({
|
|
@@ -80,6 +80,17 @@ export default class AgentsUpdate extends BaseCommand {
|
|
|
80
80
|
* Exported for tests.
|
|
81
81
|
*/
|
|
82
82
|
export declare function agentUpdateWarnings(payload: Record<string, unknown>, updated: unknown): string[];
|
|
83
|
+
/**
|
|
84
|
+
* The Agent row inside an update reply: the reply itself for an ordinary
|
|
85
|
+
* PATCH, `agent` inside the Always-On control envelope, or null when the
|
|
86
|
+
* control reply carries none (a PATCH of `isAlwaysOn` alone). Exported for tests.
|
|
87
|
+
*/
|
|
88
|
+
export declare function agentRowFromUpdate(updated: unknown): Record<string, unknown> | null;
|
|
89
|
+
/**
|
|
90
|
+
* One line for an `isAlwaysOn` write, from the receipt: applied, or already in
|
|
91
|
+
* that state so nothing changed (OSK-16477). Exported for tests.
|
|
92
|
+
*/
|
|
93
|
+
export declare function alwaysOnReceiptLine(payload: Record<string, unknown>, updated: unknown): string | null;
|
|
83
94
|
/**
|
|
84
95
|
* Warnings from the server's `writeReceipt` (OSK-15070). The receipt names
|
|
85
96
|
* every key the write saw as applied (and through which control: the legacy
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AGENT_FENCE_RETRY_CODES = void 0;
|
|
4
4
|
exports.agentUpdateWarnings = agentUpdateWarnings;
|
|
5
|
+
exports.agentRowFromUpdate = agentRowFromUpdate;
|
|
6
|
+
exports.alwaysOnReceiptLine = alwaysOnReceiptLine;
|
|
5
7
|
exports.writeReceiptWarnings = writeReceiptWarnings;
|
|
6
8
|
exports.retryOnceOnAgentFence = retryOnceOnAgentFence;
|
|
7
9
|
exports.defaultPermissionModePatch = defaultPermissionModePatch;
|
|
@@ -212,8 +214,26 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
212
214
|
this.log(JSON.stringify((0, web_url_1.withAgentUrl)(this.cliConfig.baseURL, updated), null, 2));
|
|
213
215
|
return;
|
|
214
216
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
// The Always-On control replies with its own envelope, which names no agent.
|
|
218
|
+
// Name it from the Agent's own row, not "(no name)" (OSK-16477).
|
|
219
|
+
const a = agentRowFromUpdate(updated) ?? { id: args.id };
|
|
220
|
+
if (typeof a.name !== 'string') {
|
|
221
|
+
try {
|
|
222
|
+
const row = (await data_provider_1.dataService.getAgentById({ agent_id: args.id }));
|
|
223
|
+
if (row &&
|
|
224
|
+
typeof row === 'object' &&
|
|
225
|
+
typeof row.name === 'string') {
|
|
226
|
+
a.name = row.name;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// The update already happened; a failed name read only costs the label.
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
this.log(`Updated agent ${a.id ?? a._id ?? args.id} — ${typeof a.name === 'string' ? a.name || '(no name)' : '(name unavailable)'}`);
|
|
234
|
+
const alwaysOnLine = alwaysOnReceiptLine(payload, updated);
|
|
235
|
+
if (alwaysOnLine)
|
|
236
|
+
this.log(alwaysOnLine);
|
|
217
237
|
this.log(`URL: ${(0, web_url_1.withAgentUrl)(this.cliConfig.baseURL, { ...a, id: a.id ?? a._id ?? args.id }).url ?? '-'}`);
|
|
218
238
|
const modeNote = defaultPermissionModeNote(flags['default-permission-mode'], a);
|
|
219
239
|
if (modeNote)
|
|
@@ -285,6 +305,42 @@ function agentUpdateWarnings(payload, updated) {
|
|
|
285
305
|
...(unapplied.length > 0 ? [(0, create_1.unappliedPayloadKeysWarning)(unapplied)] : []),
|
|
286
306
|
];
|
|
287
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* The Agent row inside an update reply: the reply itself for an ordinary
|
|
310
|
+
* PATCH, `agent` inside the Always-On control envelope, or null when the
|
|
311
|
+
* control reply carries none (a PATCH of `isAlwaysOn` alone). Exported for tests.
|
|
312
|
+
*/
|
|
313
|
+
function agentRowFromUpdate(updated) {
|
|
314
|
+
if (!updated || typeof updated !== 'object' || Array.isArray(updated))
|
|
315
|
+
return null;
|
|
316
|
+
const reply = updated;
|
|
317
|
+
const isControlReply = typeof reply.agentId === 'string' && reply.state !== null && typeof reply.state === 'object';
|
|
318
|
+
if (!isControlReply)
|
|
319
|
+
return { ...reply };
|
|
320
|
+
const agent = reply.agent;
|
|
321
|
+
return agent && typeof agent === 'object' && !Array.isArray(agent)
|
|
322
|
+
? { ...agent, id: agent.id ?? reply.agentId }
|
|
323
|
+
: { id: reply.agentId };
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* One line for an `isAlwaysOn` write, from the receipt: applied, or already in
|
|
327
|
+
* that state so nothing changed (OSK-16477). Exported for tests.
|
|
328
|
+
*/
|
|
329
|
+
function alwaysOnReceiptLine(payload, updated) {
|
|
330
|
+
if (typeof payload.isAlwaysOn !== 'boolean')
|
|
331
|
+
return null;
|
|
332
|
+
const receipt = (0, data_provider_1.readAgentWriteReceipt)(updated);
|
|
333
|
+
if (!receipt)
|
|
334
|
+
return null;
|
|
335
|
+
const state = payload.isAlwaysOn ? 'on' : 'off';
|
|
336
|
+
if (receipt.unchanged.some((entry) => entry.field === 'isAlwaysOn')) {
|
|
337
|
+
return `Always-On: already ${state}; nothing changed.`;
|
|
338
|
+
}
|
|
339
|
+
if (receipt.applied.some((entry) => entry.field === 'isAlwaysOn')) {
|
|
340
|
+
return `Always-On: turned ${state}.`;
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
288
344
|
/**
|
|
289
345
|
* Warnings from the server's `writeReceipt` (OSK-15070). The receipt names
|
|
290
346
|
* every key the write saw as applied (and through which control: the legacy
|
|
@@ -5,6 +5,9 @@ const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
|
|
|
5
5
|
const daemon_binding_1 = require("../../lib/daemon-binding");
|
|
6
6
|
const daemon_installer_1 = require("../../lib/daemon-installer");
|
|
7
7
|
const daemon_setup_1 = require("../../lib/daemon-setup");
|
|
8
|
+
const daemonHandoff_1 = require("../../lib/daemonHandoff");
|
|
9
|
+
const daemon_machine_identity_copy_1 = require("../../lib/daemon-machine-identity-copy");
|
|
10
|
+
const live_runtime_binary_1 = require("../../lib/live-runtime-binary");
|
|
8
11
|
/**
|
|
9
12
|
* `skrr daemon install` — install the local runtime's OS service.
|
|
10
13
|
*
|
|
@@ -83,7 +86,20 @@ class DaemonInstall extends core_1.Command {
|
|
|
83
86
|
// moment the binding is chosen, and leaving it to a separate command
|
|
84
87
|
// is how a CLI and its daemon end up on different deployments
|
|
85
88
|
// (OSK-279) without anyone doing anything wrong.
|
|
86
|
-
const
|
|
89
|
+
const installArgs = (0, daemon_binding_1.bindToCliSession)(argv);
|
|
90
|
+
// A service installed from a daemon that predates per-profile machine
|
|
91
|
+
// identities runs that daemon for good, and registers the profile on the
|
|
92
|
+
// default profile's machine row (OSK-S-440). Ask the binary about to run.
|
|
93
|
+
// `--help` and `--json` are not installs.
|
|
94
|
+
if (!installArgs.some((arg) => ['--help', '-h'].includes(arg))) {
|
|
95
|
+
const skew = await (0, daemonHandoff_1.checkLocalDaemonMachineIdentity)((0, live_runtime_binary_1.profileFromArgs)(installArgs));
|
|
96
|
+
if (skew) {
|
|
97
|
+
for (const line of (0, daemon_machine_identity_copy_1.describeDaemonMachineIdentitySkew)(skew))
|
|
98
|
+
this.logToStderr(line);
|
|
99
|
+
this.exit(78);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['install', ...installArgs]);
|
|
87
103
|
if (code === 0 &&
|
|
88
104
|
!argv.some((arg) => ['--json', '--help', '-h'].includes(arg))) {
|
|
89
105
|
this.log('Coding-agent instructions: sign in with `skrr login`, or review installation choices with `skrr instructions setup`.');
|
|
@@ -4,6 +4,8 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const daemon_binding_1 = require("../../lib/daemon-binding");
|
|
5
5
|
const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
|
|
6
6
|
const live_runtime_binary_1 = require("../../lib/live-runtime-binary");
|
|
7
|
+
const daemonHandoff_1 = require("../../lib/daemonHandoff");
|
|
8
|
+
const daemon_machine_identity_copy_1 = require("../../lib/daemon-machine-identity-copy");
|
|
7
9
|
/**
|
|
8
10
|
* `skrr daemon login` — authenticate the local runtime.
|
|
9
11
|
*
|
|
@@ -53,6 +55,18 @@ class DaemonLogin extends core_1.Command {
|
|
|
53
55
|
if (decision?.kind === 'forward-live') {
|
|
54
56
|
this.warn(`Signing in with the live daemon binary ${decision.binary}.`);
|
|
55
57
|
}
|
|
58
|
+
// The binary about to sign this profile in must register it under the
|
|
59
|
+
// machine identity the profile has (OSK-S-440): a daemon that predates
|
|
60
|
+
// per-profile identities would bind this login to the default profile's
|
|
61
|
+
// machine row. `--help` is not a login.
|
|
62
|
+
if (!boundArgv.some((arg) => ['--help', '-h'].includes(arg))) {
|
|
63
|
+
const skew = await (0, daemonHandoff_1.checkLocalDaemonMachineIdentity)((0, live_runtime_binary_1.profileFromArgs)(boundArgv), decision?.binary ?? pathBinary);
|
|
64
|
+
if (skew) {
|
|
65
|
+
for (const line of (0, daemon_machine_identity_copy_1.describeDaemonMachineIdentitySkew)(skew))
|
|
66
|
+
this.logToStderr(line);
|
|
67
|
+
this.exit(78);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
56
70
|
// `--cwd` is deliberately NOT defaulted here. `skrrd login` resolves it
|
|
57
71
|
// from the installed service definition, which is the only authoritative
|
|
58
72
|
// answer; guessing it from the CLI's cwd is exactly the mistake that made
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* Remove harness rows that a later registration of the same machine replaced.
|
|
4
|
+
*
|
|
5
|
+
* `harnesses forget` removes ONE row, and an agent pinned to it loses its pin.
|
|
6
|
+
* One machine had accumulated 93 rows from 15 daemons, 85 of them replaced, and
|
|
7
|
+
* `machines list` could fold them but nothing could remove them. This does, and
|
|
8
|
+
* only the ones that can be proven safe: the server decides, once, and says why
|
|
9
|
+
* it kept everything it kept.
|
|
10
|
+
*
|
|
11
|
+
* DRY RUN BY DEFAULT. `--yes` is the only thing that removes anything.
|
|
12
|
+
*/
|
|
13
|
+
export default class HarnessesPrune extends BaseCommand {
|
|
14
|
+
static summary: string;
|
|
15
|
+
static description: string;
|
|
16
|
+
static examples: string[];
|
|
17
|
+
static flags: {
|
|
18
|
+
yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
limit: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
|
+
'older-than': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
};
|
|
23
|
+
run(): Promise<void>;
|
|
24
|
+
private render;
|
|
25
|
+
private removeTable;
|
|
26
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_command_1 = require("../../base-command");
|
|
5
|
+
const format_1 = require("../../lib/format");
|
|
6
|
+
const harnesses_1 = require("../../lib/harnesses");
|
|
7
|
+
/**
|
|
8
|
+
* Remove harness rows that a later registration of the same machine replaced.
|
|
9
|
+
*
|
|
10
|
+
* `harnesses forget` removes ONE row, and an agent pinned to it loses its pin.
|
|
11
|
+
* One machine had accumulated 93 rows from 15 daemons, 85 of them replaced, and
|
|
12
|
+
* `machines list` could fold them but nothing could remove them. This does, and
|
|
13
|
+
* only the ones that can be proven safe: the server decides, once, and says why
|
|
14
|
+
* it kept everything it kept.
|
|
15
|
+
*
|
|
16
|
+
* DRY RUN BY DEFAULT. `--yes` is the only thing that removes anything.
|
|
17
|
+
*/
|
|
18
|
+
class HarnessesPrune extends base_command_1.BaseCommand {
|
|
19
|
+
static summary = 'Remove harness rows that a newer registration of the same machine replaced (dry run unless --yes)';
|
|
20
|
+
static description = 'A machine registers a fresh row per provider each time its daemon identity changes (a profile, a ' +
|
|
21
|
+
'working directory, a service versus a terminal), and the old rows stay. This removes the ones that ' +
|
|
22
|
+
'are provably stale and shows every one it keeps, with the reason.\n\n' +
|
|
23
|
+
'It is a DRY RUN until you pass --yes. A row is removed only if ALL of these hold: it is yours; it is ' +
|
|
24
|
+
'a daemon you run yourself (never a hosted, dedicated or cloud machine); it is not online and has not ' +
|
|
25
|
+
'been seen within --older-than; a later row of the same provider on the same machine replaced it AND ' +
|
|
26
|
+
"that replacement is online now; and nothing depends on it - no agent (yours or anyone's) is pinned " +
|
|
27
|
+
'to it, no workspace has it lent, it is not your default, and you did not give it a name.\n\n' +
|
|
28
|
+
'An agent pinned to a replaced row keeps running only because the platform adopts the live row ' +
|
|
29
|
+
'beside it, and that adoption starts from the old row; remove the row and the pin is gone. So a ' +
|
|
30
|
+
'pinned row is KEPT, with the agents named. Move the pin (`agents attach-harness`) and run this again.\n\n' +
|
|
31
|
+
'At most --limit rows are removed per run, oldest first; the rest are counted and the next run takes ' +
|
|
32
|
+
'them. Running it again when nothing qualifies removes nothing. A daemon that is still running ' +
|
|
33
|
+
're-registers on its next connection, so removing a row never stops a machine.';
|
|
34
|
+
static examples = [
|
|
35
|
+
'<%= config.bin %> harnesses prune',
|
|
36
|
+
'<%= config.bin %> harnesses prune --yes',
|
|
37
|
+
'<%= config.bin %> harnesses prune --yes --limit 20',
|
|
38
|
+
'<%= config.bin %> harnesses prune --older-than 7d --json',
|
|
39
|
+
];
|
|
40
|
+
static flags = {
|
|
41
|
+
yes: core_1.Flags.boolean({
|
|
42
|
+
description: 'Remove the rows listed. Without it nothing is removed.',
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
limit: core_1.Flags.integer({
|
|
46
|
+
description: 'Remove at most this many rows in one run (1-200, default 50). The rest wait for the next run.',
|
|
47
|
+
min: 1,
|
|
48
|
+
max: 200,
|
|
49
|
+
}),
|
|
50
|
+
'older-than': core_1.Flags.string({
|
|
51
|
+
description: 'Only rows unseen for at least this long: 90m, 36h or 7d (default 24h). The server never goes below its offline window.',
|
|
52
|
+
}),
|
|
53
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
54
|
+
};
|
|
55
|
+
async run() {
|
|
56
|
+
this.requireAuth();
|
|
57
|
+
const { flags } = await this.parse(HarnessesPrune);
|
|
58
|
+
let minUnseenMs;
|
|
59
|
+
if (flags['older-than'] !== undefined) {
|
|
60
|
+
try {
|
|
61
|
+
minUnseenMs = (0, harnesses_1.parseHarnessPruneAge)(flags['older-than']);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
this.error(err.message, { exit: 2 });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
result = await (0, harnesses_1.pruneHarnesses)({
|
|
71
|
+
dryRun: !flags.yes,
|
|
72
|
+
...(flags.limit !== undefined ? { limit: flags.limit } : {}),
|
|
73
|
+
...(minUnseenMs !== undefined ? { minUnseenMs } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
this.handleApiError(err);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (flags.json) {
|
|
81
|
+
this.printJsonResult(result);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.render(result);
|
|
85
|
+
}
|
|
86
|
+
render(result) {
|
|
87
|
+
const { dryRun } = result;
|
|
88
|
+
const removing = dryRun ? result.remove.length : result.removedCount;
|
|
89
|
+
const noun = (n) => `${n} superseded harness row${n === 1 ? '' : 's'}`;
|
|
90
|
+
if (dryRun) {
|
|
91
|
+
this.log('Dry run - nothing was removed.');
|
|
92
|
+
}
|
|
93
|
+
if (removing === 0) {
|
|
94
|
+
this.log(dryRun
|
|
95
|
+
? 'Nothing to remove.'
|
|
96
|
+
: 'Nothing was removed - no row qualified (or each one changed while this ran).');
|
|
97
|
+
}
|
|
98
|
+
else if (dryRun) {
|
|
99
|
+
this.log('');
|
|
100
|
+
this.log(`Would remove ${noun(removing)} (oldest first)${result.remaining > 0 ? `; ${result.remaining} more qualify beyond --limit ${result.limit}` : ''}:`);
|
|
101
|
+
this.removeTable(result.remove);
|
|
102
|
+
this.log('');
|
|
103
|
+
this.log('Run again with --yes to remove them.');
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
this.log(`Removed ${noun(removing)}:`);
|
|
107
|
+
this.removeTable(result.remove);
|
|
108
|
+
if (result.remaining > 0) {
|
|
109
|
+
this.log('');
|
|
110
|
+
this.log(`${result.remaining} more qualify beyond --limit ${result.limit}. Run it again to continue.`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (result.kept.length > 0) {
|
|
114
|
+
this.log('');
|
|
115
|
+
this.log(`Kept ${result.kept.length}${result.keptTruncated ? '+' : ''} offline row${result.kept.length === 1 ? '' : 's'}:`);
|
|
116
|
+
const rows = result.kept.map((row) => ({
|
|
117
|
+
id: (0, format_1.formatCell)(row.id),
|
|
118
|
+
provider: (0, format_1.formatCell)(row.provider),
|
|
119
|
+
daemon: (0, format_1.formatCell)(row.daemonId),
|
|
120
|
+
seen: (0, format_1.localStamp)(row.lastSeenAt),
|
|
121
|
+
why: (0, harnesses_1.harnessPruneReasonSentence)(row),
|
|
122
|
+
}));
|
|
123
|
+
(0, format_1.renderTable)(rows, [
|
|
124
|
+
{ key: 'id', header: 'HARNESS ID' },
|
|
125
|
+
{ key: 'provider', header: 'PROVIDER' },
|
|
126
|
+
{ key: 'daemon', header: 'DAEMON ID', verbatim: true },
|
|
127
|
+
{ key: 'seen', header: `LAST SEEN (${(0, format_1.localZone)()})` },
|
|
128
|
+
{ key: 'why', header: 'KEPT BECAUSE' },
|
|
129
|
+
], (line) => this.log(line));
|
|
130
|
+
if (result.keptTruncated) {
|
|
131
|
+
this.log('(The list is cut short; the totals below cover every row.)');
|
|
132
|
+
}
|
|
133
|
+
if (result.kept.some((row) => row.reason === 'pinned')) {
|
|
134
|
+
this.log('(A pinned row stays until its agents point elsewhere: `skrr agents attach-harness <harness> --agent <agent>`.)');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const passedOver = [];
|
|
138
|
+
const count = (key) => result.counts?.[key] ?? 0;
|
|
139
|
+
if (count('online') > 0)
|
|
140
|
+
passedOver.push(`${count('online')} online`);
|
|
141
|
+
if (count('not_personal_machine') > 0) {
|
|
142
|
+
passedOver.push(`${count('not_personal_machine')} hosted, dedicated, cloud or of unknown origin`);
|
|
143
|
+
}
|
|
144
|
+
if (count('no_machine_identity') > 0) {
|
|
145
|
+
passedOver.push(`${count('no_machine_identity')} that name no machine`);
|
|
146
|
+
}
|
|
147
|
+
if (passedOver.length > 0) {
|
|
148
|
+
this.log('');
|
|
149
|
+
this.log(`Not considered: ${passedOver.join('; ')}.`);
|
|
150
|
+
}
|
|
151
|
+
if (!result.scan.complete) {
|
|
152
|
+
this.log('');
|
|
153
|
+
this.log(`(This account has more than ${result.scan.rows} harness rows, so the plan cannot be proven complete and --yes will refuse. Use \`harnesses forget\` for individual rows.)`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
removeTable(rows) {
|
|
157
|
+
(0, format_1.renderTable)(rows.map((row) => ({
|
|
158
|
+
id: (0, format_1.formatCell)(row.id),
|
|
159
|
+
provider: (0, format_1.formatCell)(row.provider),
|
|
160
|
+
daemon: (0, format_1.formatCell)(row.daemonId),
|
|
161
|
+
seen: (0, format_1.localStamp)(row.lastSeenAt),
|
|
162
|
+
replacedBy: (0, format_1.formatCell)(row.supersededBy?.daemonId),
|
|
163
|
+
})), [
|
|
164
|
+
{ key: 'id', header: 'HARNESS ID' },
|
|
165
|
+
{ key: 'provider', header: 'PROVIDER' },
|
|
166
|
+
{ key: 'daemon', header: 'DAEMON ID', verbatim: true },
|
|
167
|
+
{ key: 'seen', header: `LAST SEEN (${(0, format_1.localZone)()})` },
|
|
168
|
+
{ key: 'replacedBy', header: 'REPLACED BY (ONLINE)', verbatim: true },
|
|
169
|
+
], (line) => this.log(line));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
exports.default = HarnessesPrune;
|
package/dist/commands/login.js
CHANGED
|
@@ -12,6 +12,7 @@ const cli_id_1 = require("../lib/cli-id");
|
|
|
12
12
|
const keychain_1 = require("../lib/keychain");
|
|
13
13
|
const profile_machine_identity_guard_1 = require("../lib/profile-machine-identity-guard");
|
|
14
14
|
const daemon_command_hint_1 = require("../lib/daemon-command-hint");
|
|
15
|
+
const daemon_machine_identity_copy_1 = require("../lib/daemon-machine-identity-copy");
|
|
15
16
|
const local_daemon_readiness_1 = require("../lib/local-daemon-readiness");
|
|
16
17
|
const login_1 = require("../lib/login");
|
|
17
18
|
const device_code_1 = require("../lib/device-code");
|
|
@@ -293,6 +294,16 @@ class Login extends base_command_1.BaseCommand {
|
|
|
293
294
|
// nothing to install and nothing to hand over (OSK-12296).
|
|
294
295
|
this.log("This machine's daemon is managed by the platform; there is nothing to set up here.");
|
|
295
296
|
}
|
|
297
|
+
else if (handoff.reason === 'machine-identity-skew' && handoff.skew) {
|
|
298
|
+
// The installed daemon would register this profile on the default
|
|
299
|
+
// profile's machine row (OSK-S-440). Nothing was minted or installed;
|
|
300
|
+
// the CLI's own sign-in above stands. The generic "run `daemon login`"
|
|
301
|
+
// line below would send the person to the same binary.
|
|
302
|
+
this.log('');
|
|
303
|
+
for (const line of (0, daemon_machine_identity_copy_1.describeDaemonMachineIdentitySkew)(handoff.skew))
|
|
304
|
+
this.log(line);
|
|
305
|
+
localDaemonNotReady = true;
|
|
306
|
+
}
|
|
296
307
|
else if (handoff.reason === 'foreign-root') {
|
|
297
308
|
// Not a failure and not a hint to re-login anything: the daemon is
|
|
298
309
|
// the default root's, and this is a second root (OSK-12189).
|
|
@@ -80,8 +80,10 @@ class SpacesWorkflowsStart extends base_command_1.BaseCommand {
|
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
82
|
const decorated = (0, space_output_1.withWorkflowUrl)(this.cliConfig.baseURL, args.spaceId, result, { workflowId: args.workflowId, runId: result.run.id });
|
|
83
|
+
// The id this command created, at the top where a script looks for it, instead of only
|
|
84
|
+
// inside `run` (OSK-16494). The nested run is unchanged.
|
|
83
85
|
if (flags.json)
|
|
84
|
-
return this.log(JSON.stringify(decorated, null, 2));
|
|
86
|
+
return this.log(JSON.stringify({ runId: result.run.id, status: result.run.status, ...decorated }, null, 2));
|
|
85
87
|
this.log(formatStartedRunLine(result));
|
|
86
88
|
// The run someone just started is the one they most want to watch, so the
|
|
87
89
|
// link comes before the command that prints the same thing as text.
|