@phnx-labs/agents-cli 1.20.92 → 1.21.0
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.md +385 -0
- package/README.md +3 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/beta.js +3 -5
- package/dist/commands/doctor.js +23 -0
- package/dist/commands/events.js +91 -1
- package/dist/commands/exec.js +13 -0
- package/dist/commands/mailboxes.js +39 -1
- package/dist/commands/message.js +12 -1
- package/dist/commands/packages.js +14 -5
- package/dist/commands/projects.d.ts +38 -0
- package/dist/commands/projects.js +355 -48
- package/dist/commands/routines.js +27 -1
- package/dist/commands/rules.js +6 -0
- package/dist/commands/secrets.d.ts +17 -0
- package/dist/commands/secrets.js +198 -7
- package/dist/commands/send.d.ts +14 -12
- package/dist/commands/send.js +105 -35
- package/dist/commands/sessions.d.ts +17 -0
- package/dist/commands/sessions.js +16 -8
- package/dist/commands/setup-browser.js +5 -1
- package/dist/commands/setup-fleet.js +5 -0
- package/dist/commands/setup-preferences.d.ts +53 -0
- package/dist/commands/setup-preferences.js +142 -0
- package/dist/commands/setup.js +8 -9
- package/dist/commands/ssh.js +220 -10
- package/dist/commands/sync.d.ts +2 -2
- package/dist/commands/sync.js +13 -12
- package/dist/commands/view.js +4 -0
- package/dist/commands/watchdog.js +26 -3
- package/dist/index.js +38 -6
- package/dist/lib/activity.d.ts +15 -0
- package/dist/lib/activity.js +26 -2
- package/dist/lib/auto-dispatch.d.ts +6 -1
- package/dist/lib/auto-dispatch.js +7 -2
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/channels/send.d.ts +83 -0
- package/dist/lib/channels/send.js +112 -0
- package/dist/lib/daemon.d.ts +13 -0
- package/dist/lib/daemon.js +80 -60
- package/dist/lib/device-config.d.ts +82 -0
- package/dist/lib/device-config.js +296 -0
- package/dist/lib/event-provenance.d.ts +19 -0
- package/dist/lib/event-provenance.js +48 -0
- package/dist/lib/events-ingest.d.ts +46 -0
- package/dist/lib/events-ingest.js +182 -0
- package/dist/lib/events.d.ts +17 -5
- package/dist/lib/events.js +59 -56
- package/dist/lib/feed.d.ts +1 -1
- package/dist/lib/feed.js +2 -0
- package/dist/lib/git.d.ts +13 -2
- package/dist/lib/git.js +38 -6
- package/dist/lib/linear-project-counts.d.ts +129 -0
- package/dist/lib/linear-project-counts.js +206 -0
- package/dist/lib/linear-projects.d.ts +62 -0
- package/dist/lib/linear-projects.js +144 -0
- package/dist/lib/mailbox-gc.js +30 -7
- package/dist/lib/mailbox.d.ts +14 -1
- package/dist/lib/mailbox.js +35 -3
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +5 -21
- package/dist/lib/menubar/install-menubar.js +35 -44
- package/dist/lib/menubar/notify-desktop.d.ts +17 -2
- package/dist/lib/menubar/notify-desktop.js +8 -2
- package/dist/lib/migrate.js +1 -1
- package/dist/lib/models.js +21 -11
- package/dist/lib/overdue.d.ts +1 -1
- package/dist/lib/overdue.js +54 -19
- package/dist/lib/project-import.d.ts +96 -0
- package/dist/lib/project-import.js +171 -0
- package/dist/lib/project-probe.d.ts +75 -0
- package/dist/lib/project-probe.js +160 -0
- package/dist/lib/project-resources.d.ts +8 -0
- package/dist/lib/project-resources.js +31 -3
- package/dist/lib/project-status.d.ts +32 -1
- package/dist/lib/project-status.js +82 -1
- package/dist/lib/projects.d.ts +6 -0
- package/dist/lib/projects.js +12 -0
- package/dist/lib/remote-agents-json.d.ts +14 -1
- package/dist/lib/remote-agents-json.js +21 -3
- package/dist/lib/routine-notify.d.ts +11 -0
- package/dist/lib/routine-notify.js +22 -0
- package/dist/lib/routines.d.ts +53 -0
- package/dist/lib/routines.js +105 -4
- package/dist/lib/rules/run-sync.d.ts +18 -0
- package/dist/lib/rules/run-sync.js +92 -0
- package/dist/lib/run-notify.js +3 -0
- package/dist/lib/scheduler.js +12 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/audit.d.ts +1 -1
- package/dist/lib/secrets/audit.js +53 -10
- package/dist/lib/secrets/index.d.ts +4 -5
- package/dist/lib/secrets/index.js +4 -5
- package/dist/lib/secrets/list-filter.d.ts +20 -5
- package/dist/lib/secrets/list-filter.js +22 -6
- package/dist/lib/secrets/usage-db.d.ts +106 -0
- package/dist/lib/secrets/usage-db.js +236 -0
- package/dist/lib/session/active.d.ts +16 -2
- package/dist/lib/session/active.js +8 -4
- package/dist/lib/session/bundle.js +5 -1
- package/dist/lib/session/remote-active.d.ts +5 -1
- package/dist/lib/session/remote-active.js +4 -1
- package/dist/lib/session/remote-list.d.ts +1 -15
- package/dist/lib/session/remote-list.js +22 -111
- package/dist/lib/session/sync/config.d.ts +13 -5
- package/dist/lib/session/sync/config.js +21 -13
- package/dist/lib/sqlite.js +28 -1
- package/dist/lib/startup/command-registry.d.ts +0 -2
- package/dist/lib/startup/command-registry.js +1 -5
- package/dist/lib/state.d.ts +19 -2
- package/dist/lib/state.js +99 -10
- package/dist/lib/sync-umbrella.d.ts +11 -23
- package/dist/lib/sync-umbrella.js +14 -45
- package/dist/lib/teams/agents.d.ts +35 -3
- package/dist/lib/teams/agents.js +52 -8
- package/dist/lib/teams/scheduler.d.ts +29 -2
- package/dist/lib/teams/scheduler.js +60 -16
- package/dist/lib/types.d.ts +22 -5
- package/dist/lib/versions.d.ts +6 -0
- package/dist/lib/versions.js +6 -4
- package/package.json +1 -2
- package/dist/commands/drive.d.ts +0 -10
- package/dist/commands/drive.js +0 -183
- package/dist/commands/hq.d.ts +0 -2
- package/dist/commands/hq.js +0 -58
- package/dist/commands/sessions-sync.d.ts +0 -17
- package/dist/commands/sessions-sync.js +0 -135
- package/dist/commands/sync-provision.d.ts +0 -23
- package/dist/commands/sync-provision.js +0 -107
- package/dist/lib/drive-sync.d.ts +0 -45
- package/dist/lib/drive-sync.js +0 -238
- package/dist/lib/hq/floor.d.ts +0 -87
- package/dist/lib/hq/floor.js +0 -243
- package/dist/lib/session/sync/crdt.d.ts +0 -44
- package/dist/lib/session/sync/crdt.js +0 -119
- package/dist/lib/session/sync/manifest.d.ts +0 -62
- package/dist/lib/session/sync/manifest.js +0 -100
- package/dist/lib/session/sync/provision.d.ts +0 -49
- package/dist/lib/session/sync/provision.js +0 -91
- package/dist/lib/session/sync/r2.d.ts +0 -32
- package/dist/lib/session/sync/r2.js +0 -121
- package/dist/lib/session/sync/sync.d.ts +0 -106
- package/dist/lib/session/sync/sync.js +0 -374
package/dist/commands/secrets.js
CHANGED
|
@@ -31,6 +31,7 @@ import { readMeta } from '../lib/state.js';
|
|
|
31
31
|
import { parseDuration } from '../lib/hooks/cache.js';
|
|
32
32
|
import { emit, query } from '../lib/events.js';
|
|
33
33
|
import { emitSecretAudit } from '../lib/secrets/audit.js';
|
|
34
|
+
import { getBundleUsage, getAllBundleUsage, getUsageHistory, SECRET_USAGE_EVENTS, } from '../lib/secrets/usage-db.js';
|
|
34
35
|
import { frequentlyPromptedBundles } from '../lib/secrets/unlock-hints.js';
|
|
35
36
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
36
37
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
@@ -733,6 +734,54 @@ function countExpiringSoon(meta) {
|
|
|
733
734
|
}
|
|
734
735
|
return n;
|
|
735
736
|
}
|
|
737
|
+
/** Human labels for the usage events, in the order `view` prints them. */
|
|
738
|
+
const USAGE_EVENT_LABELS = {
|
|
739
|
+
access: 'accessed',
|
|
740
|
+
unlock: 'unlocked',
|
|
741
|
+
import: 'imported',
|
|
742
|
+
export: 'exported',
|
|
743
|
+
create: 'created',
|
|
744
|
+
view: 'viewed',
|
|
745
|
+
};
|
|
746
|
+
/**
|
|
747
|
+
* Compact one-line usage summary for `secrets view` — each recorded event with
|
|
748
|
+
* its count and how long ago it last happened ("accessed 42× (last 2h ago) ·
|
|
749
|
+
* exported 3× (last 1d ago)"). Empty string when nothing has been recorded yet.
|
|
750
|
+
* Pure — unit-tested.
|
|
751
|
+
*/
|
|
752
|
+
export function formatUsageLine(summary) {
|
|
753
|
+
if (!summary || summary.total === 0)
|
|
754
|
+
return '';
|
|
755
|
+
const parts = [];
|
|
756
|
+
for (const ev of SECRET_USAGE_EVENTS) {
|
|
757
|
+
const stat = summary.events[ev];
|
|
758
|
+
if (stat.count === 0)
|
|
759
|
+
continue;
|
|
760
|
+
const age = stat.last ? relativeAge(stat.last) : null;
|
|
761
|
+
parts.push(`${USAGE_EVENT_LABELS[ev]} ${stat.count}×${age ? ` (last ${age})` : ''}`);
|
|
762
|
+
}
|
|
763
|
+
return parts.join(' · ');
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* The held-state line for `secrets view`: whether the secrets-agent currently
|
|
767
|
+
* holds this bundle (so reads are prompt-free) and for how long, or that Touch
|
|
768
|
+
* ID is required on the next read. Only meaningful for keychain bundles on macOS
|
|
769
|
+
* — file/vault backends have no broker to hold, and a `never`-policy bundle is
|
|
770
|
+
* always readable — so both return an empty line and the caller skips it.
|
|
771
|
+
* `heldExpiresAt` is the max expiry across every held scope.
|
|
772
|
+
*/
|
|
773
|
+
export function renderViewStatusLine(b, heldExpiresAt) {
|
|
774
|
+
if (process.platform !== 'darwin')
|
|
775
|
+
return '';
|
|
776
|
+
if ((b.backend ?? 'keychain') !== 'keychain')
|
|
777
|
+
return '';
|
|
778
|
+
if (bundlePolicy(b) === 'never')
|
|
779
|
+
return '';
|
|
780
|
+
if (heldExpiresAt && heldExpiresAt > Date.now()) {
|
|
781
|
+
return chalk.green(`status: unlocked (held ${compactRemaining(heldExpiresAt)}; reads are prompt-free until it locks)`);
|
|
782
|
+
}
|
|
783
|
+
return chalk.gray('status: locked (Touch ID required on the next read; `agents secrets unlock` holds it)');
|
|
784
|
+
}
|
|
736
785
|
/**
|
|
737
786
|
* The EXPIRING cell. Counts keys needing attention — already expired plus due
|
|
738
787
|
* within 30 days — and colours by the worst of the two: red once anything has
|
|
@@ -819,9 +868,13 @@ export function registerSecretsCommands(program) {
|
|
|
819
868
|
# Inject the bundle into an agent run
|
|
820
869
|
agents run claude "deploy the worker" --secrets prod
|
|
821
870
|
|
|
822
|
-
# See what's in the bundle (values masked); shows its
|
|
871
|
+
# See what's in the bundle (values masked); shows its policy, held state, and usage
|
|
823
872
|
agents secrets view prod
|
|
824
873
|
|
|
874
|
+
# Order the list by how recently / how often each bundle is used
|
|
875
|
+
agents secrets list --sort used
|
|
876
|
+
agents secrets list --sort uses
|
|
877
|
+
|
|
825
878
|
# Stop a noisy automation bundle from prompting every run: ask once a week
|
|
826
879
|
agents secrets policy prod hold
|
|
827
880
|
|
|
@@ -839,6 +892,17 @@ export function registerSecretsCommands(program) {
|
|
|
839
892
|
never touch disk in plaintext. Every item is device-local and gated by Touch ID
|
|
840
893
|
or device passcode; cross-machine sync is handled by 'agents secrets push/pull'.
|
|
841
894
|
|
|
895
|
+
Naming: name a bundle after the thing it holds credentials for, so an agent can
|
|
896
|
+
guess it without listing. For a website, use its domain WITH the real suffix —
|
|
897
|
+
'stripe.com', 'openai.ai', 'github.com'. For a desktop app, use the app's binary
|
|
898
|
+
suffix — 'slack.app' (macOS) or 'photoshop.exe' (Windows). Always pass
|
|
899
|
+
'--description' so 'list' / 'view' explain the bundle without opening it; an
|
|
900
|
+
undescribed bundle prints a "No description found" nudge. 'view' also shows
|
|
901
|
+
whether the bundle is currently unlocked (held by the agent) and how often / how
|
|
902
|
+
recently it has been created, imported, exported, viewed, and accessed;
|
|
903
|
+
'list --sort used|uses' orders by recency / frequency, and
|
|
904
|
+
'agents secrets activity [bundle]' prints the recent value-free event timeline.
|
|
905
|
+
|
|
842
906
|
Touch ID noise: macOS pops a prompt per bundle per process. Each bundle has
|
|
843
907
|
a prompt policy, shown in the POLICY column of 'agents secrets list':
|
|
844
908
|
hold (default) ask once, then serve it silently from the local agent
|
|
@@ -868,7 +932,7 @@ export function registerSecretsCommands(program) {
|
|
|
868
932
|
`,
|
|
869
933
|
});
|
|
870
934
|
registerCommandGroups(cmd, [
|
|
871
|
-
{ title: 'Bundle commands', names: ['list', 'view', 'create', 'rename', 'describe', 'delete'] },
|
|
935
|
+
{ title: 'Bundle commands', names: ['list', 'view', 'activity', 'create', 'rename', 'describe', 'delete'] },
|
|
872
936
|
{ title: 'Secret commands', names: ['add', 'rotate', 'remove', 'import', 'export'] },
|
|
873
937
|
{ title: 'Agent commands', names: ['start', 'stop', 'unlock', 'lock', 'status', 'policy'] },
|
|
874
938
|
{ title: 'Raw item commands', names: ['get', 'set'] },
|
|
@@ -893,7 +957,7 @@ export function registerSecretsCommands(program) {
|
|
|
893
957
|
.option('--expired', 'Only bundles with at least one key whose expiry has already passed')
|
|
894
958
|
.option('--expiring [days]', `Only bundles with a key falling due within N days (default ${DEFAULT_EXPIRING_DAYS})`)
|
|
895
959
|
.option('--unused <duration>', 'Only bundles not read since this far back (e.g. 30d, 4w, 3mo). Never-used bundles always match')
|
|
896
|
-
.addOption(new Option('--sort <field>', 'Sort by: name (default), used, created, updated, expiry').choices([...SORT_FIELDS]))
|
|
960
|
+
.addOption(new Option('--sort <field>', 'Sort by: name (default), used (most recently used), uses (most frequently accessed), created, updated, expiry').choices([...SORT_FIELDS]))
|
|
897
961
|
.option('-n, --limit <n>', 'Show at most this many bundles (after filtering and sorting)')
|
|
898
962
|
.action(async (query, opts) => {
|
|
899
963
|
const targets = parseHostsOption(opts);
|
|
@@ -942,11 +1006,20 @@ export function registerSecretsCommands(program) {
|
|
|
942
1006
|
/* broker not running — render policy without the countdown */
|
|
943
1007
|
}
|
|
944
1008
|
}
|
|
1009
|
+
// Value-free usage read-model backs `--sort used|uses` and the --json
|
|
1010
|
+
// `uses`/`usage` fields. Open the DB only when a caller actually needs it
|
|
1011
|
+
// (a machine payload, or a usage-ranked sort) so a plain human `list`
|
|
1012
|
+
// stays a pure keychain read.
|
|
1013
|
+
const needUsage = Boolean(opts.json) || sortField === 'used' || sortField === 'uses';
|
|
1014
|
+
const usage = needUsage ? getAllBundleUsage() : new Map();
|
|
1015
|
+
const usageHints = new Map();
|
|
1016
|
+
for (const [name, s] of usage)
|
|
1017
|
+
usageHints.set(name, { lastUsedAt: s.lastUsedAt, uses: s.events.access.count });
|
|
945
1018
|
// Filter, sort, then cap — before the --json branch, so the machine
|
|
946
1019
|
// payload is the exact twin of the table (the sessions ordering rule).
|
|
947
1020
|
const now = Date.now();
|
|
948
1021
|
const matched = all.filter((b) => bundleMatchesFilter(b, filter, { held, now }));
|
|
949
|
-
const sorted = sortBundles(matched, sortField);
|
|
1022
|
+
const sorted = sortBundles(matched, sortField, usageHints);
|
|
950
1023
|
const bundles = limit === undefined ? sorted : sorted.slice(0, limit);
|
|
951
1024
|
if (opts.json) {
|
|
952
1025
|
// Discovery payload for agents: metadata only, no secret values. Gated on
|
|
@@ -974,6 +1047,11 @@ export function registerSecretsCommands(program) {
|
|
|
974
1047
|
// stale past timestamp as if the bundle were still warm, disagreeing
|
|
975
1048
|
// with the table and the filter about the very same bundle.
|
|
976
1049
|
heldExpiresAt: liveHold(held.get(b.name), now),
|
|
1050
|
+
// Value-free usage from the read-model: `uses` is the recorded access
|
|
1051
|
+
// (read/inject) count; `usage` is the full per-kind rollup. Null when
|
|
1052
|
+
// nothing has been recorded yet.
|
|
1053
|
+
uses: usage.get(b.name)?.events.access.count ?? 0,
|
|
1054
|
+
usage: usage.get(b.name) ?? null,
|
|
977
1055
|
}));
|
|
978
1056
|
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
979
1057
|
return;
|
|
@@ -1010,6 +1088,9 @@ export function registerSecretsCommands(program) {
|
|
|
1010
1088
|
# Everything, newest-used first
|
|
1011
1089
|
agents secrets list --sort used
|
|
1012
1090
|
|
|
1091
|
+
# Order by how often each bundle is read (most-accessed first)
|
|
1092
|
+
agents secrets list --sort uses
|
|
1093
|
+
|
|
1013
1094
|
# Find one by name or description
|
|
1014
1095
|
agents secrets list github
|
|
1015
1096
|
|
|
@@ -1044,6 +1125,51 @@ export function registerSecretsCommands(program) {
|
|
|
1044
1125
|
- --unused matches bundles never read at all, not just old ones.
|
|
1045
1126
|
- Filters apply before --json, so the JSON is the exact twin of the table.
|
|
1046
1127
|
- Filters are forwarded over --host, so a remote list narrows the same way.
|
|
1128
|
+
- --sort used|uses read the value-free usage read-model (~/.agents/secrets/secrets.db); see 'agents secrets activity'.
|
|
1129
|
+
`,
|
|
1130
|
+
});
|
|
1131
|
+
const activityCmd = cmd
|
|
1132
|
+
.command('activity [name]')
|
|
1133
|
+
.description('Show the recent value-free usage timeline (created / imported / exported / viewed / accessed / unlocked) for one bundle, or across all bundles')
|
|
1134
|
+
.option('-n, --limit <n>', 'Show at most this many events (default 20)')
|
|
1135
|
+
.option('--json', 'Emit machine-readable JSON (metadata only — never secret values)')
|
|
1136
|
+
.action((name, opts) => {
|
|
1137
|
+
let limit = opts.limit === undefined ? 20 : Number(opts.limit);
|
|
1138
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
1139
|
+
console.error(chalk.red(`Invalid --limit '${opts.limit}'. Use a whole number of events, e.g. --limit 50.`));
|
|
1140
|
+
process.exit(1);
|
|
1141
|
+
}
|
|
1142
|
+
const events = getUsageHistory(name, limit);
|
|
1143
|
+
if (opts.json) {
|
|
1144
|
+
process.stdout.write(JSON.stringify(events) + '\n');
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
if (events.length === 0) {
|
|
1148
|
+
console.log(chalk.gray(name ? `No recorded activity for '${name}'.` : 'No recorded secrets activity yet.'));
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
for (const e of events) {
|
|
1152
|
+
const who = e.agent ? chalk.gray(` by ${e.agent}`) : '';
|
|
1153
|
+
const where = e.source ? chalk.gray(` (${e.source}${e.host ? ` ${e.host}` : ''})`) : '';
|
|
1154
|
+
const bundleTag = name ? '' : chalk.cyan(` ${e.bundle}`);
|
|
1155
|
+
console.log(`${chalk.gray(relativeAge(e.ts).padEnd(10))} ${chalk.bold(e.event.padEnd(8))}${bundleTag}${who}${where}`);
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
setHelpSections(activityCmd, {
|
|
1159
|
+
examples: `
|
|
1160
|
+
# The recent value-free event timeline across every bundle
|
|
1161
|
+
agents secrets activity
|
|
1162
|
+
|
|
1163
|
+
# Just one bundle, more history
|
|
1164
|
+
agents secrets activity anthropic.com --limit 50
|
|
1165
|
+
|
|
1166
|
+
# Machine-readable
|
|
1167
|
+
agents secrets activity anthropic.com --json
|
|
1168
|
+
`,
|
|
1169
|
+
notes: `
|
|
1170
|
+
- Reads the value-free usage read-model (~/.agents/secrets/secrets.db), fed off the
|
|
1171
|
+
same emitSecretAudit chokepoint as 'agents events'. Never a secret value.
|
|
1172
|
+
- History is bounded to the last 90 days; the full audit trail is 'agents events --module secrets'.
|
|
1047
1173
|
`,
|
|
1048
1174
|
});
|
|
1049
1175
|
cmd
|
|
@@ -1090,6 +1216,29 @@ export function registerSecretsCommands(program) {
|
|
|
1090
1216
|
process.exit(1);
|
|
1091
1217
|
}
|
|
1092
1218
|
const entries = describeBundle(bundle);
|
|
1219
|
+
// Value-free usage read-model (per-kind counts + recency + per-agent) for
|
|
1220
|
+
// both the human and --json views. Read before recording this view so the
|
|
1221
|
+
// just-happened view isn't counted in its own summary.
|
|
1222
|
+
const usage = getBundleUsage(bundle.name);
|
|
1223
|
+
// Is the secrets-agent currently holding this bundle? A held bundle reads
|
|
1224
|
+
// prompt-free. macOS keychain only; take the max expiry across every scope
|
|
1225
|
+
// it's held under. Soft-fails to "not held" if the broker is down.
|
|
1226
|
+
let heldExpiresAt = null;
|
|
1227
|
+
if (process.platform === 'darwin' && (bundle.backend ?? 'keychain') === 'keychain') {
|
|
1228
|
+
try {
|
|
1229
|
+
for (const e of await agentStatus()) {
|
|
1230
|
+
if (e.name === bundle.name && (heldExpiresAt === null || e.expiresAt > heldExpiresAt)) {
|
|
1231
|
+
heldExpiresAt = e.expiresAt;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
catch {
|
|
1236
|
+
/* broker not running — render as not held */
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
// Record the inspection through the ONE chokepoint (events.jsonl + the
|
|
1240
|
+
// usage read-model). Value-free; a masked `view` is metadata only.
|
|
1241
|
+
emitSecretAudit({ event: 'secrets.view', bundle: bundle.name, operation: opts.json ? 'view --json' : 'view', source: 'view', status: 'success' });
|
|
1093
1242
|
if (opts.json) {
|
|
1094
1243
|
// Machine-readable discovery for agents. Values are null unless --reveal
|
|
1095
1244
|
// (which still routes through the same non-TTY/plaintext gate + audit
|
|
@@ -1158,14 +1307,25 @@ export function registerSecretsCommands(program) {
|
|
|
1158
1307
|
createdAt: bundle.created_at ?? null,
|
|
1159
1308
|
updatedAt: bundle.updated_at ?? null,
|
|
1160
1309
|
lastUsed: bundle.last_used ?? null,
|
|
1310
|
+
// Held state + value-free usage rollup, same data the human view
|
|
1311
|
+
// shows. `heldExpiresAt` is null when the bundle is not held.
|
|
1312
|
+
heldExpiresAt,
|
|
1313
|
+
usage: usage ?? null,
|
|
1314
|
+
uses: usage?.events.access.count ?? 0,
|
|
1161
1315
|
revealed: reveal,
|
|
1162
1316
|
keys,
|
|
1163
1317
|
}) + '\n');
|
|
1164
1318
|
return;
|
|
1165
1319
|
}
|
|
1166
1320
|
console.log(chalk.bold(bundle.name));
|
|
1167
|
-
if (bundle.description)
|
|
1321
|
+
if (bundle.description) {
|
|
1168
1322
|
console.log(chalk.gray(safePrint(bundle.description)));
|
|
1323
|
+
}
|
|
1324
|
+
else {
|
|
1325
|
+
// A described bundle is self-documenting for the next agent/human that
|
|
1326
|
+
// reads it. Nudge — never block — toward adding one.
|
|
1327
|
+
console.log(chalk.yellow(`No description found. Add one: agents secrets describe ${bundle.name} "what this bundle is for"`));
|
|
1328
|
+
}
|
|
1169
1329
|
if (bundle.allow_exec)
|
|
1170
1330
|
console.log(chalk.yellow('allow_exec: true'));
|
|
1171
1331
|
if (bundle.backend === 'file')
|
|
@@ -1183,12 +1343,22 @@ export function registerSecretsCommands(program) {
|
|
|
1183
1343
|
? chalk.gray(`policy: hold (ask once, then held for ${formatHoldWindow(secretsHoldMs())} — until sleep / logout; screen-lock does not drop it)`)
|
|
1184
1344
|
: chalk.gray('policy: always (asks for Touch ID every time — never auto-held)'));
|
|
1185
1345
|
}
|
|
1346
|
+
const statusLine = renderViewStatusLine(bundle, heldExpiresAt);
|
|
1347
|
+
if (statusLine)
|
|
1348
|
+
console.log(statusLine);
|
|
1186
1349
|
if (bundle.created_at)
|
|
1187
1350
|
console.log(chalk.gray(`created_at: ${bundle.created_at} (${humanAge(bundle.created_at)})`));
|
|
1188
1351
|
if (bundle.updated_at)
|
|
1189
1352
|
console.log(chalk.gray(`updated_at: ${bundle.updated_at} (${humanAge(bundle.updated_at)})`));
|
|
1190
1353
|
if (bundle.last_used)
|
|
1191
1354
|
console.log(chalk.gray(`last_used: ${bundle.last_used} (${humanAge(bundle.last_used)})`));
|
|
1355
|
+
const usageLine = formatUsageLine(usage);
|
|
1356
|
+
if (usageLine)
|
|
1357
|
+
console.log(chalk.gray(`usage: ${usageLine}`));
|
|
1358
|
+
if (usage && usage.byAgent.length > 0) {
|
|
1359
|
+
const top = usage.byAgent.slice(0, 3).map((a) => `${a.agent} ${a.count}×`).join(', ');
|
|
1360
|
+
console.log(chalk.gray(`by: ${top}`));
|
|
1361
|
+
}
|
|
1192
1362
|
console.log();
|
|
1193
1363
|
if (entries.length === 0) {
|
|
1194
1364
|
console.log(chalk.gray('(no keys)'));
|
|
@@ -1357,8 +1527,8 @@ export function registerSecretsCommands(program) {
|
|
|
1357
1527
|
});
|
|
1358
1528
|
cmd
|
|
1359
1529
|
.command('create [name]')
|
|
1360
|
-
.description('Create an empty bundle')
|
|
1361
|
-
.option('--description <text>', 'Free-form description')
|
|
1530
|
+
.description('Create an empty bundle. Name it after what it holds — a website by domain (stripe.com, openai.ai), a desktop app by its binary suffix (slack.app, photoshop.exe) — and pass --description.')
|
|
1531
|
+
.option('--description <text>', 'Free-form description (recommended — an undescribed bundle prints a "No description found" nudge)')
|
|
1362
1532
|
.option('--allow-exec', 'Allow exec: refs in this bundle (off by default)')
|
|
1363
1533
|
.option('--policy <policy>', "prompt policy: hold (default, ask once per hold window — secrets.agent.holdMs, 7d by default), always (ask every time), or never (silent, NO biometry ACL — needs --i-understand). 'daily'/'session' are accepted aliases for 'hold'.")
|
|
1364
1534
|
.addOption(new Option('--tier <policy>', 'deprecated alias for --policy').hideHelp())
|
|
@@ -1395,6 +1565,9 @@ export function registerSecretsCommands(program) {
|
|
|
1395
1565
|
vars: {},
|
|
1396
1566
|
};
|
|
1397
1567
|
writeBundle(bundle);
|
|
1568
|
+
// Record the create through the ONE chokepoint (events.jsonl + the usage
|
|
1569
|
+
// read-model). Value-free — bundle name only.
|
|
1570
|
+
emitSecretAudit({ event: 'secrets.create', bundle: resolvedName, operation: 'create', source: 'create', status: 'success' });
|
|
1398
1571
|
const policyTag = bundlePolicy(bundle) === 'hold'
|
|
1399
1572
|
? 'policy: hold'
|
|
1400
1573
|
: bundlePolicy(bundle) === 'always'
|
|
@@ -1406,6 +1579,11 @@ export function registerSecretsCommands(program) {
|
|
|
1406
1579
|
backend === 'vault' ? 'synced' : null,
|
|
1407
1580
|
].filter(Boolean);
|
|
1408
1581
|
console.log(chalk.green(`Bundle '${resolvedName}' created (${tags.join(', ')}).`));
|
|
1582
|
+
if (!bundle.description) {
|
|
1583
|
+
// A described bundle is self-documenting for the next agent that reads
|
|
1584
|
+
// `list` / `view`. Nudge toward one at create time — never blocking.
|
|
1585
|
+
console.log(chalk.yellow(`No description found. Add one: agents secrets describe ${resolvedName} "what this bundle is for"`));
|
|
1586
|
+
}
|
|
1409
1587
|
if (bundlePolicy(bundle) === 'never') {
|
|
1410
1588
|
console.log(chalk.red('Stored without biometry protection — reads are silent. Automation-only; rotate anything sensitive out of it.'));
|
|
1411
1589
|
}
|
|
@@ -1807,6 +1985,7 @@ Examples:
|
|
|
1807
1985
|
const resolvedBundleName = bundleName ?? (await pickBundleName('import into'));
|
|
1808
1986
|
const bundle = resolveImportBundle(resolvedBundleName, opts.backend, opts.synced);
|
|
1809
1987
|
const { added, skipped } = applyEnvToBundle(bundle, env, opts);
|
|
1988
|
+
emitSecretAudit({ event: 'secrets.import', bundle: bundle.name, operation: 'import --from-file', source: 'file', status: 'success', keyCount: added });
|
|
1810
1989
|
console.log(chalk.green(`Imported ${added} key(s) from file${skipped ? `, skipped ${skipped} (already set, pass --force)` : ''}.`));
|
|
1811
1990
|
return;
|
|
1812
1991
|
}
|
|
@@ -1820,6 +1999,7 @@ Examples:
|
|
|
1820
1999
|
const env = await remoteResolveEnv(target, resolvedBundleName, { osLookupName: opts.host });
|
|
1821
2000
|
const bundle = resolveImportBundle(resolvedBundleName, opts.backend, opts.synced);
|
|
1822
2001
|
const { added, skipped } = applyEnvToBundle(bundle, env, opts);
|
|
2002
|
+
emitSecretAudit({ event: 'secrets.import', bundle: bundle.name, operation: 'import --from-ssh', source: 'ssh', host: opts.host, status: 'success', keyCount: added });
|
|
1823
2003
|
console.log(chalk.green(`Imported ${added} key(s) from ${opts.host}${skipped ? `, skipped ${skipped} (already set, pass --force)` : ''}.`));
|
|
1824
2004
|
return;
|
|
1825
2005
|
}
|
|
@@ -1855,6 +2035,7 @@ Examples:
|
|
|
1855
2035
|
for (const { envKey, value } of secrets)
|
|
1856
2036
|
env[envKey] = value;
|
|
1857
2037
|
const { added, skipped } = applyEnvToBundle(bundle, env, opts);
|
|
2038
|
+
emitSecretAudit({ event: 'secrets.import', bundle: bundle.name, operation: `import --from 1password:${vault}`, source: '1password', status: 'success', keyCount: added });
|
|
1858
2039
|
if (opSkipped.length) {
|
|
1859
2040
|
console.log(chalk.yellow(`Skipped ${opSkipped.length} item(s) with no importable fields.`));
|
|
1860
2041
|
}
|
|
@@ -1864,6 +2045,7 @@ Examples:
|
|
|
1864
2045
|
const raw = readImportDotenv(source.path);
|
|
1865
2046
|
const pairs = parseDotenv(raw);
|
|
1866
2047
|
const { added, skipped } = applyEnvToBundle(bundle, pairs, opts);
|
|
2048
|
+
emitSecretAudit({ event: 'secrets.import', bundle: bundle.name, operation: 'import --from env-file', source: 'env-file', status: 'success', keyCount: added });
|
|
1867
2049
|
console.log(chalk.green(`Imported ${added} key(s)${skipped ? `, skipped ${skipped} (already set, pass --force)` : ''}.`));
|
|
1868
2050
|
}
|
|
1869
2051
|
}
|
|
@@ -1903,6 +2085,7 @@ Examples:
|
|
|
1903
2085
|
}
|
|
1904
2086
|
const { env } = readAndResolveBundleEnv(resolvedBundleName, { caller: 'export --to-file', keyMode: 'storage', agentOnly: isHeadlessSecretsContext() });
|
|
1905
2087
|
exportBundleToFile(env, opts.toFile, passphrase);
|
|
2088
|
+
emitSecretAudit({ event: 'secrets.export', bundle: resolvedBundleName, operation: 'export --to-file', source: 'file', status: 'success', keyCount: Object.keys(env).length });
|
|
1906
2089
|
console.log(chalk.green(`Exported ${Object.keys(env).length} key(s) to ${opts.toFile}`));
|
|
1907
2090
|
return;
|
|
1908
2091
|
}
|
|
@@ -2009,6 +2192,7 @@ Examples:
|
|
|
2009
2192
|
const remoteMsg = (res.stdout || '').trim().split('\n').map((l) => l.trim()).filter(Boolean).pop();
|
|
2010
2193
|
console.log(chalk.green(`${host} -> '${resolvedBundleName}': ${remoteMsg || `${keyCount} key(s) exported`}`));
|
|
2011
2194
|
}
|
|
2195
|
+
emitSecretAudit({ event: 'secrets.export', bundle: resolvedBundleName, operation: `export --host ${hosts.join(',')}`, source: 'ssh', host: hosts.join(','), status: failures > 0 ? 'error' : 'success', keyCount });
|
|
2012
2196
|
if (failures > 0)
|
|
2013
2197
|
process.exit(1);
|
|
2014
2198
|
return;
|
|
@@ -2043,6 +2227,7 @@ Examples:
|
|
|
2043
2227
|
parts.push(`${overwritten} overwritten`);
|
|
2044
2228
|
if (skipped)
|
|
2045
2229
|
parts.push(`${skipped} skipped (already exist, pass --force)`);
|
|
2230
|
+
emitSecretAudit({ event: 'secrets.export', bundle: resolvedBundleName, operation: `export --to-1password ${vault}`, source: '1password', status: 'success', keyCount: created + overwritten });
|
|
2046
2231
|
console.log(chalk.green(`Exported to 1Password vault '${vault}': ${parts.join(', ')}.`));
|
|
2047
2232
|
return;
|
|
2048
2233
|
}
|
|
@@ -2077,6 +2262,12 @@ Examples:
|
|
|
2077
2262
|
const output = needsQuotes ? `'${v.replace(/'/g, `'\\''`)}'` : v;
|
|
2078
2263
|
process.stdout.write(`export ${exportKey}=${output}\n`);
|
|
2079
2264
|
}
|
|
2265
|
+
// Record the shell export (the `eval "$(...)"` path). The `--format json`
|
|
2266
|
+
// branch above is the machine-to-machine remote-resolve transport (already
|
|
2267
|
+
// counted as an access on this machine by the resolve above), so it is
|
|
2268
|
+
// deliberately not counted here as an export — that would tally every peer
|
|
2269
|
+
// pull as an export.
|
|
2270
|
+
emitSecretAudit({ event: 'secrets.export', bundle: resolvedBundleName, operation: 'export --plaintext', source: 'shell', status: 'success', keyCount: Object.keys(env).length });
|
|
2080
2271
|
}
|
|
2081
2272
|
catch (err) {
|
|
2082
2273
|
if (isPromptCancelled(err))
|
package/dist/commands/send.d.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `agents send
|
|
3
|
-
* any registered channel (mailbox, telegram, imessage, slack, discord, desktop, apps).
|
|
2
|
+
* `agents send` — deliver a message over any registered channel provider.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* messaging gateway is down.
|
|
4
|
+
* Envelope (flag-first, industry-shaped):
|
|
5
|
+
* agents send --to <dest> --text "…" [--channel <name>] [--attach …] [--url …]
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Destination:
|
|
8
|
+
* --to owner expands to notify.owner.{channel,to} in agents.yaml
|
|
9
|
+
* --to <id> channel-specific recipient (requires --channel)
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* Compat: positional text still works (`agents send "hi" --channel … --to …`).
|
|
12
|
+
*
|
|
13
|
+
* `agents notify` is the same delivery path with owner defaults
|
|
14
|
+
* (`send --to owner`). Not a second stack. Not agent control — use
|
|
15
|
+
* `agents message` / `agents sessions inject` for running agents.
|
|
16
|
+
*
|
|
17
|
+
* Feed / activity are a different plane (record + read); feed.broadcast may
|
|
18
|
+
* call this command as a forward sink.
|
|
17
19
|
*/
|
|
18
20
|
import type { Command } from 'commander';
|
|
19
21
|
export declare function registerSendCommand(program: Command): void;
|
package/dist/commands/send.js
CHANGED
|
@@ -1,20 +1,39 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { die } from '../lib/format.js';
|
|
3
|
+
import { setHelpSections } from '../lib/help.js';
|
|
3
4
|
import { readMeta } from '../lib/state.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import { sendMessage } from '../lib/channels/send.js';
|
|
6
|
+
function mergeAttachments(opts) {
|
|
7
|
+
const list = [...(opts.attach ?? []), ...(opts.attachment ?? [])];
|
|
8
|
+
return list.length ? list : undefined;
|
|
9
|
+
}
|
|
10
|
+
function toInput(positionalText, opts, ownerMode) {
|
|
11
|
+
return {
|
|
12
|
+
text: opts.text,
|
|
13
|
+
positionalText,
|
|
14
|
+
to: opts.to,
|
|
15
|
+
channel: opts.channel,
|
|
11
16
|
thread: opts.thread,
|
|
12
|
-
attachments: opts
|
|
17
|
+
attachments: mergeAttachments(opts),
|
|
18
|
+
urls: opts.url,
|
|
13
19
|
from: opts.from,
|
|
14
20
|
dryRun: opts.dryRun,
|
|
15
|
-
|
|
21
|
+
ownerMode,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function runSend(positionalText, opts, ownerMode) {
|
|
25
|
+
const meta = readMeta();
|
|
26
|
+
const out = await sendMessage(toInput(positionalText, opts, ownerMode), meta);
|
|
27
|
+
if ('error' in out) {
|
|
28
|
+
die(out.error);
|
|
29
|
+
}
|
|
30
|
+
const { result, envelope } = out;
|
|
16
31
|
if (opts.json) {
|
|
17
|
-
console.log(JSON.stringify(
|
|
32
|
+
console.log(JSON.stringify({
|
|
33
|
+
...result,
|
|
34
|
+
text: envelope.text,
|
|
35
|
+
dryRun: Boolean(envelope.dryRun),
|
|
36
|
+
}));
|
|
18
37
|
if (!result.ok)
|
|
19
38
|
process.exit(1);
|
|
20
39
|
return;
|
|
@@ -23,40 +42,91 @@ async function doSend(text, channel, to, opts) {
|
|
|
23
42
|
die(`send failed [${result.channel} → ${result.id}]: ${result.error ?? 'unknown error'}`);
|
|
24
43
|
}
|
|
25
44
|
const suffix = result.msgId ? chalk.dim(` (${result.msgId})`) : '';
|
|
26
|
-
const dry =
|
|
45
|
+
const dry = envelope.dryRun ? chalk.dim(' [dry-run]') : '';
|
|
27
46
|
console.log(chalk.green(`Sent via ${result.channel} → ${result.id}`) + suffix + dry);
|
|
28
47
|
}
|
|
48
|
+
const SHARED_NOTES = `
|
|
49
|
+
Planes (do not mix them up):
|
|
50
|
+
send / notify - DELIVER a message to a recipient (this command)
|
|
51
|
+
feed post - RECORD progress / milestones (optional broadcast may call send)
|
|
52
|
+
activity - READ the activity stream (not a send path)
|
|
53
|
+
message / inject - CONTROL a running agent (mailbox answer or terminal keystroke)
|
|
54
|
+
|
|
55
|
+
--to owner is an address alias for notify.owner in agents.yaml, not a
|
|
56
|
+
special control path. Agent resume / PTY inject stay on message and
|
|
57
|
+
sessions inject.
|
|
58
|
+
`;
|
|
29
59
|
export function registerSendCommand(program) {
|
|
30
|
-
program
|
|
31
|
-
.command('send
|
|
32
|
-
.description('
|
|
33
|
-
.
|
|
34
|
-
.
|
|
60
|
+
const sendCmd = program
|
|
61
|
+
.command('send [text]')
|
|
62
|
+
.description('Deliver a message through a channel provider (imessage, slack, desktop, mailbox, …). Prefer --text/--to flags.')
|
|
63
|
+
.option('--text <text>', 'message body (preferred over positional text)')
|
|
64
|
+
.option('--to <target>', 'recipient id, or "owner" for notify.owner in agents.yaml')
|
|
65
|
+
.option('--channel <name>', 'channel / provider (required unless --to owner)')
|
|
35
66
|
.option('--thread <id>', 'channel thread id / timestamp')
|
|
36
|
-
.option('--
|
|
67
|
+
.option('--attach <path...>', 'local file attachment path (repeatable)')
|
|
68
|
+
.option('--attachment <path...>', 'alias of --attach')
|
|
69
|
+
.option('--url <url...>', 'link or remote media URL to include in the body (repeatable)')
|
|
37
70
|
.option('--from <who>', 'sender label (mailbox)')
|
|
38
71
|
.option('--json', 'output JSON')
|
|
39
|
-
.option('--dry-run', 'resolve + build but do not send')
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
.option('--dry-run', 'resolve + build but do not send');
|
|
73
|
+
setHelpSections(sendCmd, {
|
|
74
|
+
examples: `
|
|
75
|
+
# Flag-first envelope (preferred)
|
|
76
|
+
agents send --channel imessage --to "+18055550100" --text "PR #1803 is green"
|
|
77
|
+
agents send --to owner --text "need a decision on the release"
|
|
78
|
+
agents send --channel desktop --to local --text "deploy finished" --url https://example.com/pr/1
|
|
79
|
+
agents send --channel mailbox --to <session-id> --text "peer note" --from orchestrator
|
|
80
|
+
|
|
81
|
+
# Attach a local file
|
|
82
|
+
agents send --to owner --text "screenshot" --attach ./out/cover.png
|
|
83
|
+
|
|
84
|
+
# Owner alias without the notify verb
|
|
85
|
+
agents send --to owner --text "wiring test"
|
|
86
|
+
|
|
87
|
+
# Legacy positional text still works
|
|
88
|
+
agents send "hi" --channel desktop --to local
|
|
89
|
+
|
|
90
|
+
# Dry-run (resolve provider, no delivery)
|
|
91
|
+
agents send --to owner --text "probe" --dry-run --json
|
|
92
|
+
`,
|
|
93
|
+
notes: SHARED_NOTES,
|
|
42
94
|
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
95
|
+
sendCmd.action(async (text, opts) => {
|
|
96
|
+
await runSend(text, opts, false);
|
|
97
|
+
});
|
|
98
|
+
const notifyCmd = program
|
|
99
|
+
.command('notify [text]')
|
|
100
|
+
.description('Deliver to the owner (alias of send --to owner). Channel + target default from notify.owner in agents.yaml.')
|
|
101
|
+
.option('--text <text>', 'message body (preferred over positional text)')
|
|
46
102
|
.option('--channel <name>', 'override owner channel')
|
|
47
|
-
.option('--to <target>', 'override owner target')
|
|
103
|
+
.option('--to <target>', 'override owner target (or pass a non-owner dest with --channel)')
|
|
48
104
|
.option('--thread <id>', 'channel thread id / timestamp')
|
|
49
|
-
.option('--
|
|
105
|
+
.option('--attach <path...>', 'local file attachment path (repeatable)')
|
|
106
|
+
.option('--attachment <path...>', 'alias of --attach')
|
|
107
|
+
.option('--url <url...>', 'link or remote media URL to include in the body (repeatable)')
|
|
108
|
+
.option('--from <who>', 'sender label (mailbox)')
|
|
50
109
|
.option('--json', 'output JSON')
|
|
51
|
-
.option('--dry-run', 'resolve + build but do not send')
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
110
|
+
.option('--dry-run', 'resolve + build but do not send');
|
|
111
|
+
setHelpSections(notifyCmd, {
|
|
112
|
+
examples: `
|
|
113
|
+
# Same as: agents send --to owner --text "…"
|
|
114
|
+
agents notify --text "Build finished — PR #1346 is green"
|
|
115
|
+
agents notify "legacy positional still works"
|
|
116
|
+
|
|
117
|
+
# Override channel for this one ping
|
|
118
|
+
agents notify --text "fallback" --channel desktop --to local
|
|
119
|
+
|
|
120
|
+
agents notify --text "probe" --dry-run --json
|
|
121
|
+
`,
|
|
122
|
+
notes: `
|
|
123
|
+
notify ≡ send --to owner. One delivery stack (lib/channels). Set
|
|
124
|
+
notify.owner.{channel,to} in agents.yaml once per machine/fleet.
|
|
125
|
+
|
|
126
|
+
${SHARED_NOTES}
|
|
127
|
+
`,
|
|
128
|
+
});
|
|
129
|
+
notifyCmd.action(async (text, opts) => {
|
|
130
|
+
await runSend(text, opts, true);
|
|
61
131
|
});
|
|
62
132
|
}
|
|
@@ -315,6 +315,13 @@ export declare function isBareBrowserListing(options: SessionsOptions, query: st
|
|
|
315
315
|
* `runSessionBrowser` picker cannot represent.
|
|
316
316
|
*/
|
|
317
317
|
export declare function hasNoBrowserDisqualifyingFlags(options: SessionsOptions, query: string | undefined): boolean;
|
|
318
|
+
/** Resolve a session by id/query globally and print its compact preview (no pager).
|
|
319
|
+
* Backs `--preview` — the fast path for the "peek before resume" hot loop. */
|
|
320
|
+
export declare function renderSessionPreview(query: string, scope: {
|
|
321
|
+
agent?: string;
|
|
322
|
+
project?: string;
|
|
323
|
+
local?: boolean;
|
|
324
|
+
}): Promise<void>;
|
|
318
325
|
/**
|
|
319
326
|
* The one-line live status banner shown above a session preview: the glyph, the
|
|
320
327
|
* status word, and — when the session needs a human or has LOST one — a plain
|
|
@@ -351,6 +358,16 @@ export declare function teamBadge(session: SessionMeta): {
|
|
|
351
358
|
* when `showTicket` — otherwise a listing with no refs would waste a column of
|
|
352
359
|
* dashes and needlessly truncate the topic. Worktree stays a trailing badge. */
|
|
353
360
|
export declare function flatSessionRow(session: SessionMeta, live?: ActiveSession, showTicket?: boolean, cols?: PickerColumns, favorite?: boolean): string;
|
|
361
|
+
/**
|
|
362
|
+
* Live-session index for enriching the default listing, or undefined when
|
|
363
|
+
* enrichment is off (`--no-live`) or irrelevant (`--json`, which serializes
|
|
364
|
+
* SessionMeta). Full detection (incl. the headless `ps` scan) is deliberate:
|
|
365
|
+
* bare-CLI and tmux agents are the common case here, and skipping them would
|
|
366
|
+
* leave the glyph almost never showing. The listing is a one-shot user action,
|
|
367
|
+
* not a hot loop, so the `ps`/`lsof` cost is acceptable; `--no-live` is the
|
|
368
|
+
* escape hatch. Never throws — a probe failure just yields a plain listing.
|
|
369
|
+
*/
|
|
370
|
+
export declare function maybeLiveIndex(options: SessionsOptions): Promise<Map<string, ActiveSession> | undefined>;
|
|
354
371
|
/**
|
|
355
372
|
* Group key for the overview: resolve the cwd through the same canonical
|
|
356
373
|
* resolver the `agents activity` timeline groups by — a defined project's name
|