@ouro.bot/cli 0.1.0-alpha.723 → 0.1.0-alpha.725
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/changelog.json +295 -275
- package/dist/heart/active-work.js +10 -3
- package/dist/heart/core.js +343 -77
- package/dist/heart/daemon/cli-exec.js +132 -15
- package/dist/heart/daemon/cli-help.js +13 -2
- package/dist/heart/daemon/cli-parse.js +82 -1
- package/dist/heart/daemon/daemon.js +286 -33
- package/dist/heart/daemon/doctor.js +82 -6
- package/dist/heart/daemon/habit-private-wake.js +6 -1
- package/dist/heart/habits/habit-cancel.js +810 -0
- package/dist/heart/habits/habit-lifecycle.js +1327 -0
- package/dist/heart/habits/habit-migration.js +28 -23
- package/dist/heart/habits/habit-parser.js +72 -8
- package/dist/heart/habits/habit-runtime-state.js +3 -24
- package/dist/heart/habits/habit-scheduler.js +268 -50
- package/dist/heart/habits/habit-session.js +16 -0
- package/dist/heart/hatch/hatch-flow.js +6 -4
- package/dist/heart/mailbox/readers/runtime-readers.js +26 -27
- package/dist/heart/orientation-frame.js +58 -1
- package/dist/heart/provider-attempt.js +4 -1
- package/dist/heart/providers/anthropic.js +23 -2
- package/dist/heart/start-of-turn-packet.js +14 -11
- package/dist/heart/streaming.js +565 -185
- package/dist/mind/obligation-steering.js +15 -9
- package/dist/mind/prompt.js +58 -26
- package/dist/nerves/coverage/run-artifacts.js +55 -35
- package/dist/nerves/review/cli.js +2 -2
- package/dist/nerves/review/core.js +14 -7
- package/dist/repertoire/tools-base.js +2 -0
- package/dist/repertoire/tools-habits.js +103 -0
- package/dist/repertoire/tools.js +18 -3
- package/dist/rsvp/cli.js +126 -20
- package/dist/rsvp/diagnostics.js +12 -2
- package/dist/rsvp/habit-policy.js +7 -0
- package/dist/rsvp/habit-stage.js +19 -4
- package/dist/rsvp/native-habit-runner.js +151 -11
- package/dist/rsvp/outbound-state.js +360 -0
- package/dist/senses/bluebubbles/client.js +90 -17
- package/dist/senses/bluebubbles/inbound-log.js +1 -0
- package/dist/senses/bluebubbles/index.js +662 -425
- package/dist/senses/bluebubbles/model.js +57 -13
- package/dist/senses/bluebubbles/mutation-log.js +21 -10
- package/dist/senses/bluebubbles/reaction-policy.js +55 -0
- package/dist/senses/bluebubbles/semantic-receipts.js +1818 -0
- package/dist/senses/cli/tui-store.js +1 -0
- package/dist/senses/cli.js +1 -0
- package/dist/senses/habit-lifecycle-guard.js +24 -0
- package/dist/senses/ingress-evidence.js +2 -0
- package/dist/senses/mail.js +129 -0
- package/dist/senses/pipeline.js +90 -24
- package/dist/senses/private-runtime-worker.js +21 -20
- package/dist/senses/private-runtime.js +79 -29
- package/dist/senses/shared-turn.js +1 -0
- package/dist/senses/teams.js +1 -0
- package/package.json +6 -1
|
@@ -1444,8 +1444,41 @@ async function checkManualCloneBundles(deps) {
|
|
|
1444
1444
|
}
|
|
1445
1445
|
// ── toDaemonCommand ──
|
|
1446
1446
|
function toDaemonCommand(command) {
|
|
1447
|
+
if (command.kind === "habit.probe") {
|
|
1448
|
+
return {
|
|
1449
|
+
kind: "habit.probe",
|
|
1450
|
+
agent: command.agent,
|
|
1451
|
+
habitName: command.habitName,
|
|
1452
|
+
noSend: true,
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1447
1455
|
return command;
|
|
1448
1456
|
}
|
|
1457
|
+
function habitProbeJsonResponse(response) {
|
|
1458
|
+
const data = response.data && typeof response.data === "object" && !Array.isArray(response.data)
|
|
1459
|
+
? response.data
|
|
1460
|
+
: null;
|
|
1461
|
+
const status = typeof data?.status === "string" ? data.status : "degraded";
|
|
1462
|
+
if (response.ok
|
|
1463
|
+
&& data?.noSend === true
|
|
1464
|
+
&& typeof data.transportInvocationCount === "number"
|
|
1465
|
+
&& data.transportInvocationCount === 0) {
|
|
1466
|
+
return JSON.stringify({
|
|
1467
|
+
noSend: true,
|
|
1468
|
+
status,
|
|
1469
|
+
transportInvocationCount: 0,
|
|
1470
|
+
}, null, 2);
|
|
1471
|
+
}
|
|
1472
|
+
return JSON.stringify({
|
|
1473
|
+
ok: false,
|
|
1474
|
+
noSend: true,
|
|
1475
|
+
status,
|
|
1476
|
+
transportInvocationCount: null,
|
|
1477
|
+
error: response.error
|
|
1478
|
+
?? (!response.ok ? response.message : undefined)
|
|
1479
|
+
?? "habit probe did not produce verified zero-transport evidence",
|
|
1480
|
+
}, null, 2);
|
|
1481
|
+
}
|
|
1449
1482
|
// ── Hatch input resolution ──
|
|
1450
1483
|
async function resolveHatchInput(command, deps) {
|
|
1451
1484
|
const prompt = deps.promptInput;
|
|
@@ -3707,6 +3740,57 @@ function hostedMissingMailKeys(body, keys) {
|
|
|
3707
3740
|
rotateSourceGrant: missingSourceGrant,
|
|
3708
3741
|
};
|
|
3709
3742
|
}
|
|
3743
|
+
/**
|
|
3744
|
+
* Key ids the public registry declares for this agent, or `[]` when the
|
|
3745
|
+
* registry cannot be consulted.
|
|
3746
|
+
*
|
|
3747
|
+
* `requiredHostedKeyIds` derives the asserted set from the ensure response
|
|
3748
|
+
* alone, so anything the response omits is never asserted — and the server
|
|
3749
|
+
* omits `sourceGrant` unless a source alias is in play. That is how the
|
|
3750
|
+
* 2026-07-24 gap passed: the registry declared a source-grant key, the vault
|
|
3751
|
+
* never received its private half, and setup reported success. The registry is
|
|
3752
|
+
* the authority on what mail is currently being encrypted to, so it is
|
|
3753
|
+
* consulted too.
|
|
3754
|
+
*
|
|
3755
|
+
* Never throws. An unreachable registry degrades to the response-only
|
|
3756
|
+
* assertion, because refusing a setup that previously succeeded — on a machine
|
|
3757
|
+
* whose Azure credentials happen to be unavailable — would be a worse failure
|
|
3758
|
+
* than the one this guards against.
|
|
3759
|
+
*
|
|
3760
|
+
* The agent match is case-insensitive: the harness sends `agentId: agent`
|
|
3761
|
+
* verbatim in the ensure payload while registry records can carry the
|
|
3762
|
+
* lowercased form.
|
|
3763
|
+
*/
|
|
3764
|
+
async function hostedRegistryDeclaredKeyIds(input) {
|
|
3765
|
+
try {
|
|
3766
|
+
const publicRegistry = isPlainRecord(input.body.publicRegistry) ? input.body.publicRegistry : {};
|
|
3767
|
+
const managedIdentityClientId = stringField(input.existingMailroom ?? {}, "azureManagedIdentityClientId");
|
|
3768
|
+
const registry = await input.readRegistry({
|
|
3769
|
+
mailboxAddress: "",
|
|
3770
|
+
privateKeys: {},
|
|
3771
|
+
registryAzureAccountUrl: stringField(publicRegistry, "azureAccountUrl"),
|
|
3772
|
+
registryContainer: stringField(publicRegistry, "container"),
|
|
3773
|
+
registryBlob: stringField(publicRegistry, "blob"),
|
|
3774
|
+
...(managedIdentityClientId ? { azureManagedIdentityClientId: managedIdentityClientId } : {}),
|
|
3775
|
+
});
|
|
3776
|
+
const wanted = input.agent.toLowerCase();
|
|
3777
|
+
const isThisAgent = (agentId) => agentId.toLowerCase() === wanted;
|
|
3778
|
+
return [
|
|
3779
|
+
...registry.mailboxes.filter((mailbox) => isThisAgent(mailbox.agentId)).map((mailbox) => mailbox.keyId),
|
|
3780
|
+
...registry.sourceGrants.filter((grant) => isThisAgent(grant.agentId) && grant.enabled).map((grant) => grant.keyId),
|
|
3781
|
+
].filter((keyId) => keyId.trim().length > 0);
|
|
3782
|
+
}
|
|
3783
|
+
catch (error) {
|
|
3784
|
+
(0, runtime_1.emitNervesEvent)({
|
|
3785
|
+
level: "warn",
|
|
3786
|
+
component: "cli",
|
|
3787
|
+
event: "cli.hosted_mail_registry_assertion_skipped",
|
|
3788
|
+
message: "hosted mail key assertion could not consult the public registry",
|
|
3789
|
+
meta: { agent: input.agent, error: error instanceof Error ? error.message : String(error) },
|
|
3790
|
+
});
|
|
3791
|
+
return [];
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3710
3794
|
function assertHostedPrivateKeys(input) {
|
|
3711
3795
|
for (const keyId of input.requiredKeyIds) {
|
|
3712
3796
|
if (input.keys[keyId])
|
|
@@ -3811,10 +3895,18 @@ async function ensureAgentMailroom(agent, input, deps, progressLabel) {
|
|
|
3811
3895
|
generatedPrivateKeys = responsePrivateKeys(body.generatedPrivateKeys);
|
|
3812
3896
|
Object.assign(privateKeys, generatedPrivateKeys);
|
|
3813
3897
|
}
|
|
3898
|
+
const registryKeyIds = await hostedRegistryDeclaredKeyIds({
|
|
3899
|
+
agent,
|
|
3900
|
+
body,
|
|
3901
|
+
existingMailroom,
|
|
3902
|
+
readRegistry: deps.readMailroomRegistry ?? reader_1.readMailroomRegistry,
|
|
3903
|
+
});
|
|
3814
3904
|
assertHostedPrivateKeys({
|
|
3815
3905
|
agent,
|
|
3816
3906
|
keys: privateKeys,
|
|
3817
|
-
|
|
3907
|
+
// Union, not replacement: the response still names keys it just minted,
|
|
3908
|
+
// which a registry read taken before the write would not yet show.
|
|
3909
|
+
requiredKeyIds: [...new Set([...requiredHostedKeyIds(body), ...registryKeyIds])],
|
|
3818
3910
|
});
|
|
3819
3911
|
const publicRegistry = requiredResponseRecord(body.publicRegistry, "publicRegistry");
|
|
3820
3912
|
const blobStore = requiredResponseRecord(body.blobStore, "blobStore");
|
|
@@ -7159,8 +7251,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
7159
7251
|
return result.summary;
|
|
7160
7252
|
}
|
|
7161
7253
|
// ── habit subcommands (local, no daemon socket needed) ──
|
|
7162
|
-
if (command.kind === "habit.list" || command.kind === "habit.create" || command.kind === "habit.runs" || command.kind === "habit.inspect" || command.kind === "habit.summary") {
|
|
7163
|
-
const { parseHabitFile, renderHabitFile } = await Promise.resolve().then(() => __importStar(require("../habits/habit-parser")));
|
|
7254
|
+
if (command.kind === "habit.list" || command.kind === "habit.create" || command.kind === "habit.runs" || command.kind === "habit.inspect" || command.kind === "habit.summary" || command.kind === "habit.cancel") {
|
|
7255
|
+
const { createDegradedHabitFile, parseHabitFile, renderHabitFile } = await Promise.resolve().then(() => __importStar(require("../habits/habit-parser")));
|
|
7256
|
+
const { publishNewHabitDefinition } = await Promise.resolve().then(() => __importStar(require("../habits/habit-lifecycle")));
|
|
7164
7257
|
const { applyHabitRuntimeState } = await Promise.resolve().then(() => __importStar(require("../habits/habit-runtime-state")));
|
|
7165
7258
|
const { listHabitRunReceipts, readHabitRunReceipt } = await Promise.resolve().then(() => __importStar(require("../../arc/flight-recorder")));
|
|
7166
7259
|
const { readHabitSessionSummary } = await Promise.resolve().then(() => __importStar(require("../habits/habit-session-summary")));
|
|
@@ -7168,10 +7261,21 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
7168
7261
|
const bundleRoot = deps.agentBundleRoot ?? path.join(deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
|
|
7169
7262
|
/* v8 ignore stop */
|
|
7170
7263
|
const habitsDir = path.join(bundleRoot, "habits");
|
|
7264
|
+
if (command.kind === "habit.cancel") {
|
|
7265
|
+
const { cancelHabit } = await Promise.resolve().then(() => __importStar(require("../habits/habit-cancel")));
|
|
7266
|
+
const receipt = await cancelHabit({
|
|
7267
|
+
agentRoot: bundleRoot,
|
|
7268
|
+
habitId: command.habitName,
|
|
7269
|
+
evidenceLocator: command.evidenceLocator,
|
|
7270
|
+
authority: { kind: "offline_bridge" },
|
|
7271
|
+
}, deps.habitCancelDeps);
|
|
7272
|
+
deps.writeStdout(receipt.acknowledgement);
|
|
7273
|
+
return receipt.acknowledgement;
|
|
7274
|
+
}
|
|
7171
7275
|
if (command.kind === "habit.list") {
|
|
7172
7276
|
let files;
|
|
7173
7277
|
try {
|
|
7174
|
-
files = fs.readdirSync(habitsDir).filter((f) => f.endsWith(".md") && f !== "README.md");
|
|
7278
|
+
files = fs.readdirSync(habitsDir).filter((f) => f.endsWith(".md") && f !== "README.md").sort();
|
|
7175
7279
|
}
|
|
7176
7280
|
catch {
|
|
7177
7281
|
const message = "no habits found";
|
|
@@ -7185,10 +7289,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
7185
7289
|
}
|
|
7186
7290
|
const lines = [];
|
|
7187
7291
|
for (const file of files) {
|
|
7188
|
-
const
|
|
7189
|
-
|
|
7292
|
+
const filePath = path.join(habitsDir, file);
|
|
7293
|
+
let habit;
|
|
7294
|
+
try {
|
|
7295
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
7296
|
+
habit = applyHabitRuntimeState(bundleRoot, parseHabitFile(fileContent, filePath));
|
|
7297
|
+
}
|
|
7298
|
+
catch (error) {
|
|
7299
|
+
habit = createDegradedHabitFile(filePath, "read_error", "", error instanceof Error ? error.message : String(error));
|
|
7300
|
+
}
|
|
7190
7301
|
const lastRunStr = habit.lastRun ?? "never";
|
|
7191
|
-
|
|
7302
|
+
const degradedReason = habit.status === "degraded" ? ` degradedReason=${habit.degradedReason}` : "";
|
|
7303
|
+
lines.push(`${habit.name} cadence=${habit.cadence ?? "none"} status=${habit.status}${degradedReason} lastRun=${lastRunStr}`);
|
|
7192
7304
|
}
|
|
7193
7305
|
const message = lines.join("\n");
|
|
7194
7306
|
deps.writeStdout(message);
|
|
@@ -7272,12 +7384,6 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
7272
7384
|
}
|
|
7273
7385
|
// habit.create
|
|
7274
7386
|
const filePath = path.join(habitsDir, `${command.name}.md`);
|
|
7275
|
-
if (fs.existsSync(filePath)) {
|
|
7276
|
-
const message = `error: habit '${command.name}' already exists`;
|
|
7277
|
-
deps.writeStdout(message);
|
|
7278
|
-
return message;
|
|
7279
|
-
}
|
|
7280
|
-
fs.mkdirSync(habitsDir, { recursive: true });
|
|
7281
7387
|
const now = new Date().toISOString();
|
|
7282
7388
|
const habitContent = renderHabitFile({
|
|
7283
7389
|
title: command.name,
|
|
@@ -7285,7 +7391,16 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
7285
7391
|
status: "active",
|
|
7286
7392
|
created: now,
|
|
7287
7393
|
}, `Habit: ${command.name}`);
|
|
7288
|
-
|
|
7394
|
+
const publication = publishNewHabitDefinition({
|
|
7395
|
+
agentRoot: bundleRoot,
|
|
7396
|
+
habitId: command.name,
|
|
7397
|
+
bytes: habitContent,
|
|
7398
|
+
});
|
|
7399
|
+
if (publication === "exists") {
|
|
7400
|
+
const message = `error: habit '${command.name}' already exists`;
|
|
7401
|
+
deps.writeStdout(message);
|
|
7402
|
+
return message;
|
|
7403
|
+
}
|
|
7289
7404
|
const message = `created: ${filePath}`;
|
|
7290
7405
|
deps.writeStdout(message);
|
|
7291
7406
|
return message;
|
|
@@ -8258,7 +8373,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
8258
8373
|
? command.json
|
|
8259
8374
|
? formatDaemonStatusJsonOutput(response)
|
|
8260
8375
|
: (0, cli_render_1.formatDaemonStatusOutput)(response, fallbackMessage)
|
|
8261
|
-
:
|
|
8376
|
+
: command.kind === "habit.probe" && command.json
|
|
8377
|
+
? habitProbeJsonResponse(response)
|
|
8378
|
+
: fallbackMessage;
|
|
8262
8379
|
deps.writeStdout(message);
|
|
8263
8380
|
return message;
|
|
8264
8381
|
}
|
|
@@ -10,6 +10,7 @@ exports.levenshteinDistance = levenshteinDistance;
|
|
|
10
10
|
exports.suggestCommand = suggestCommand;
|
|
11
11
|
exports.getGroupedHelp = getGroupedHelp;
|
|
12
12
|
exports.getCommandHelp = getCommandHelp;
|
|
13
|
+
const core_1 = require("../../nerves/review/core");
|
|
13
14
|
// ── Registry ──
|
|
14
15
|
exports.COMMAND_REGISTRY = {
|
|
15
16
|
up: {
|
|
@@ -142,7 +143,7 @@ exports.COMMAND_REGISTRY = {
|
|
|
142
143
|
description: "Manage agent habits",
|
|
143
144
|
usage: "ouro habit <subcommand> [--agent <name>]",
|
|
144
145
|
example: "ouro habit list",
|
|
145
|
-
subcommands: ["list", "create", "runs", "inspect", "summary", "poke"],
|
|
146
|
+
subcommands: ["list", "create", "runs", "inspect", "summary", "poke", "cancel", "probe"],
|
|
146
147
|
},
|
|
147
148
|
rsvp: {
|
|
148
149
|
category: "Habits",
|
|
@@ -186,7 +187,7 @@ exports.COMMAND_REGISTRY = {
|
|
|
186
187
|
},
|
|
187
188
|
"nerves-review": {
|
|
188
189
|
category: "Internal",
|
|
189
|
-
description:
|
|
190
|
+
description: `Read-only review of recent nerves events from one agent log stream (default: ${core_1.DEFAULT_NERVES_PROCESS})`,
|
|
190
191
|
usage: "ouro nerves-review [--agent <name>] [--process <name>] [--component <substr>] [--event <substr>] [--level <level>] [--since <duration>] [--limit <n>] [--json]",
|
|
191
192
|
example: "ouro nerves-review --agent slugger --component daemon --event habit --since 30m --json",
|
|
192
193
|
},
|
|
@@ -368,6 +369,16 @@ exports.COMMAND_REGISTRY = {
|
|
|
368
369
|
},
|
|
369
370
|
};
|
|
370
371
|
const SUBCOMMAND_HELP = {
|
|
372
|
+
"habit probe": {
|
|
373
|
+
description: "Probe one habit through its real dispatch path with an irrevocable no-send boundary; it cannot send and reports zero transport invocations",
|
|
374
|
+
usage: "ouro habit probe --agent <name> --habit <name> --no-send [--json]",
|
|
375
|
+
example: "ouro habit probe --agent slugger --habit report-name --no-send --json",
|
|
376
|
+
},
|
|
377
|
+
"habit cancel": {
|
|
378
|
+
description: "Cancel a habit offline from a previously verified evidence bridge",
|
|
379
|
+
usage: "ouro habit cancel --agent <name> --habit <name> --evidence <bridge-id>",
|
|
380
|
+
example: "ouro habit cancel --agent slugger --habit report-name --evidence pre-v1-bluebubbles-event-id",
|
|
381
|
+
},
|
|
371
382
|
"private decisions": {
|
|
372
383
|
description: "Read recent private-runtime allow/deny decisions from the policy ledger",
|
|
373
384
|
usage: "ouro private decisions [--agent <name>] [--limit <n>] [--json]",
|
|
@@ -18,6 +18,7 @@ const friends_1 = require("@ouro.bot/friends");
|
|
|
18
18
|
const cli_help_1 = require("./cli-help");
|
|
19
19
|
const cli_desk_1 = require("./cli-desk");
|
|
20
20
|
const flight_recorder_1 = require("../../arc/flight-recorder");
|
|
21
|
+
const core_1 = require("../../nerves/review/core");
|
|
21
22
|
const vault_items_1 = require("./vault-items");
|
|
22
23
|
// ── Shared helpers ──
|
|
23
24
|
function extractAgentFlag(args) {
|
|
@@ -112,6 +113,8 @@ function usage() {
|
|
|
112
113
|
" ouro habit runs [--agent <name>] [--limit <n>]",
|
|
113
114
|
" ouro habit inspect [--agent <name>] <runId>",
|
|
114
115
|
" ouro habit summary [--agent <name>] (--run-id <id>|--habit <name>|--operation-id <id>) [--which latest|previous|latest-success|latest-failure] [--json]",
|
|
116
|
+
" ouro habit cancel --agent <name> --habit <name> --evidence <bridge-id>",
|
|
117
|
+
" ouro habit probe --agent <name> --habit <name> --no-send [--json]",
|
|
115
118
|
" ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
|
|
116
119
|
" ouro bluebubbles replay [--agent <name>] --message-guid <guid> [--event-type new-message|updated-message] [--json]",
|
|
117
120
|
" ouro bluebubbles context-smoke [--agent <name>] --message-guid <guid> [--persist] [--json]",
|
|
@@ -314,6 +317,84 @@ function parsePokeCommand(args) {
|
|
|
314
317
|
function parseHabitCommand(args) {
|
|
315
318
|
const { agent, rest } = extractAgentFlag(args);
|
|
316
319
|
const sub = rest[0];
|
|
320
|
+
if (sub === "probe") {
|
|
321
|
+
if (args.includes("--allow-send")) {
|
|
322
|
+
throw new Error("habit probe cannot allow send");
|
|
323
|
+
}
|
|
324
|
+
if (args.filter((value) => value === "--agent").length !== 1
|
|
325
|
+
|| !agent
|
|
326
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(agent))
|
|
327
|
+
throw new Error("habit probe requires --agent <name>");
|
|
328
|
+
let habitName;
|
|
329
|
+
let json = false;
|
|
330
|
+
let sawNoSend = false;
|
|
331
|
+
const options = rest.slice(1);
|
|
332
|
+
for (let index = 0; index < options.length; index += 1) {
|
|
333
|
+
const option = options[index];
|
|
334
|
+
if (option === "--json") {
|
|
335
|
+
if (json)
|
|
336
|
+
throw new Error("habit probe received duplicate --json");
|
|
337
|
+
json = true;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (option === "--no-send") {
|
|
341
|
+
if (sawNoSend)
|
|
342
|
+
throw new Error("habit probe received duplicate --no-send");
|
|
343
|
+
sawNoSend = true;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (option === "--habit" && options[index + 1]) {
|
|
347
|
+
if (habitName !== undefined)
|
|
348
|
+
throw new Error("habit probe received duplicate --habit");
|
|
349
|
+
habitName = options[index + 1];
|
|
350
|
+
index += 1;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
throw new Error("Usage: ouro habit probe --agent <name> --habit <name> --no-send [--json]");
|
|
354
|
+
}
|
|
355
|
+
if (!habitName)
|
|
356
|
+
throw new Error("habit probe requires --habit <name>");
|
|
357
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(habitName)) {
|
|
358
|
+
throw new Error("habit probe requires a valid --habit name");
|
|
359
|
+
}
|
|
360
|
+
return { kind: "habit.probe", agent, habitName, noSend: true, json };
|
|
361
|
+
}
|
|
362
|
+
if (sub === "cancel") {
|
|
363
|
+
if (args.filter((value) => value === "--agent").length !== 1
|
|
364
|
+
|| !agent
|
|
365
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(agent)) {
|
|
366
|
+
throw new Error(`Usage\n${usage()}`);
|
|
367
|
+
}
|
|
368
|
+
let habitName;
|
|
369
|
+
let evidenceLocator;
|
|
370
|
+
const options = rest.slice(1);
|
|
371
|
+
for (let index = 0; index < options.length; index += 1) {
|
|
372
|
+
const option = options[index];
|
|
373
|
+
const value = options[index + 1];
|
|
374
|
+
if ((option !== "--habit" && option !== "--evidence") || value === undefined) {
|
|
375
|
+
throw new Error(`Usage\n${usage()}`);
|
|
376
|
+
}
|
|
377
|
+
if (option === "--habit") {
|
|
378
|
+
if (habitName !== undefined)
|
|
379
|
+
throw new Error(`Usage\n${usage()}`);
|
|
380
|
+
habitName = value;
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
if (evidenceLocator !== undefined)
|
|
384
|
+
throw new Error(`Usage\n${usage()}`);
|
|
385
|
+
evidenceLocator = value;
|
|
386
|
+
}
|
|
387
|
+
index += 1;
|
|
388
|
+
}
|
|
389
|
+
if (!habitName || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(habitName)) {
|
|
390
|
+
throw new Error("habit cancellation requires a valid --habit name");
|
|
391
|
+
}
|
|
392
|
+
if (!evidenceLocator
|
|
393
|
+
|| evidenceLocator.startsWith("capture:")
|
|
394
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(evidenceLocator))
|
|
395
|
+
throw new Error("habit cancellation requires an offline evidence bridge ID");
|
|
396
|
+
return { kind: "habit.cancel", agent, habitName, evidenceLocator };
|
|
397
|
+
}
|
|
317
398
|
if (sub === "list") {
|
|
318
399
|
return { kind: "habit.list", ...(agent ? { agent } : {}) };
|
|
319
400
|
}
|
|
@@ -2225,7 +2306,7 @@ function parseRsvpSmokeCommand(args) {
|
|
|
2225
2306
|
}
|
|
2226
2307
|
function parseNervesReviewCommand(args) {
|
|
2227
2308
|
const { agent, rest } = extractAgentFlag(args);
|
|
2228
|
-
let processName =
|
|
2309
|
+
let processName = core_1.DEFAULT_NERVES_PROCESS;
|
|
2229
2310
|
let component;
|
|
2230
2311
|
let event;
|
|
2231
2312
|
let level;
|