@phnx-labs/agents-cli 1.22.17 → 1.22.19
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 +33 -0
- package/dist/bin/agents +0 -0
- package/dist/lib/channels/providers/rush.d.ts +2 -0
- package/dist/lib/channels/providers/rush.js +20 -5
- package/dist/lib/channels/registry.d.ts +2 -0
- package/dist/lib/channels/send.d.ts +1 -0
- package/dist/lib/channels/send.js +2 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/notify.js +5 -1
- package/dist/lib/refresh.js +35 -21
- package/dist/lib/resources.js +124 -0
- 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/session/active.js +7 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.19
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`agents sync --local -y` refreshes every installed version, not only the default.**
|
|
8
|
+
Unattended reconcile (`refresh({ skipPrompts })`) previously wrote resources and
|
|
9
|
+
registered hooks into each agent's default version alone, so non-default homes
|
|
10
|
+
kept stale hooks after a system update. Unattended refresh now loops
|
|
11
|
+
`listInstalledVersions` for both resource sync and hook registration.
|
|
12
|
+
Interactive refresh still targets the default only. Source:
|
|
13
|
+
`apps/cli/src/lib/refresh.ts`.
|
|
14
|
+
|
|
15
|
+
## 1.22.18
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **`agents sync` re-copies nested system hooks after content changes.**
|
|
20
|
+
`listResources('hooks')` treated event-group directories (`pre-tool-use/`) as
|
|
21
|
+
resource names, so `system:*` pattern expansion never included nested scripts
|
|
22
|
+
like `git-guard.sh`. Force sync then left stale flat copies in version homes
|
|
23
|
+
forever. Hooks discovery now expands one-level group dirs the same way as
|
|
24
|
+
`getAvailableResources` / `listHookEntriesFromDir`. Source:
|
|
25
|
+
`apps/cli/src/lib/resources.ts`.
|
|
26
|
+
|
|
27
|
+
- Route owner iMessage notifications through Rush's verified owner message endpoint instead of requiring a live daemon channel registration. (RUSH-2193)
|
|
28
|
+
|
|
29
|
+
- **`agents sessions --active` now carries `terminalId` on tmux-hosted rows (RUSH-2192).**
|
|
30
|
+
Grok/Codex (and every `ag-*` tmux pane) get their `AGENT_TERMINAL_ID` from the launch
|
|
31
|
+
registry's by-pid entry. The ps-scan path already set `terminalId`; the tmux source —
|
|
32
|
+
which wins dedupe for interactive agents — omitted it, so Factory could never join a
|
|
33
|
+
tab to its live session even when SessionStart preserved the key. Source:
|
|
34
|
+
`apps/cli/src/lib/session/active.ts`.
|
|
35
|
+
|
|
3
36
|
## 1.22.17
|
|
4
37
|
|
|
5
38
|
- **Codex versions no longer share one account — each keeps its own login.** Installing a new Codex version used to copy the current default version's `.codex/auth.json` into the new version home, so `agents view` reported the same ChatGPT account for every installed Codex and you could never sign two versions into two accounts. The credential is now excluded from settings carry-forward (config, prompts, and rules still carry), matching how Claude omits `.claude.json`. A fresh Codex version installs signed-out; run `codex login` (or `agents run codex --version <v>`) inside it to authenticate that version's own account. Source: `apps/cli/src/lib/settings-manifest.ts`, `apps/cli/src/commands/versions.ts`.
|
package/dist/bin/agents
CHANGED
|
Binary file
|
|
@@ -3,4 +3,6 @@ export type RushChannel = 'telegram' | 'imessage' | 'slack' | 'discord';
|
|
|
3
3
|
export declare const RUSH_CHANNELS: RushChannel[];
|
|
4
4
|
/** Build the `rush send` argv (exported for tests). */
|
|
5
5
|
export declare function buildRushSendArgs(channel: RushChannel, text: string, opts: SendOptions): string[];
|
|
6
|
+
/** Build the owner-scoped iMessage argv. */
|
|
7
|
+
export declare function buildRushOwnerMessageArgs(text: string): string[];
|
|
6
8
|
export declare const rushProviders: ChannelProvider[];
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Rush
|
|
2
|
+
* Rush channel providers — telegram / imessage / slack / discord.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the contract. `rush send --json` prints {"ok":true,"channel":..,"id":..}.
|
|
4
|
+
* Addressable channels use `rush send`, which routes through the daemon's live
|
|
5
|
+
* gateways. Owner-scoped iMessage uses `rush message send`; it is backed by the
|
|
6
|
+
* verified Rush owner account and does not require a daemon channel registration.
|
|
8
7
|
*/
|
|
9
8
|
import { execFile } from 'child_process';
|
|
10
9
|
import { promisify } from 'util';
|
|
@@ -19,6 +18,10 @@ export function buildRushSendArgs(channel, text, opts) {
|
|
|
19
18
|
args.push('--attachment', a);
|
|
20
19
|
return args;
|
|
21
20
|
}
|
|
21
|
+
/** Build the owner-scoped iMessage argv. */
|
|
22
|
+
export function buildRushOwnerMessageArgs(text) {
|
|
23
|
+
return ['message', 'send', '--text', text];
|
|
24
|
+
}
|
|
22
25
|
function rushProvider(channel) {
|
|
23
26
|
return {
|
|
24
27
|
name: channel,
|
|
@@ -34,6 +37,18 @@ function rushProvider(channel) {
|
|
|
34
37
|
return { ok: false, channel, id: opts.target, error: 'rush CLI not found on PATH' };
|
|
35
38
|
}
|
|
36
39
|
try {
|
|
40
|
+
if (channel === 'imessage' && opts.ownerScoped) {
|
|
41
|
+
if ((opts.attachments?.length ?? 0) > 0) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
channel,
|
|
45
|
+
id: opts.target,
|
|
46
|
+
error: 'owner-scoped iMessage does not support attachments',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
await execFileAsync('rush', buildRushOwnerMessageArgs(text));
|
|
50
|
+
return { ok: true, channel, id: opts.target };
|
|
51
|
+
}
|
|
37
52
|
const { stdout } = await execFileAsync('rush', buildRushSendArgs(channel, text, opts));
|
|
38
53
|
const parsed = JSON.parse(stdout);
|
|
39
54
|
return {
|
|
@@ -17,6 +17,8 @@ export interface SendOptions {
|
|
|
17
17
|
attachments?: string[];
|
|
18
18
|
/** Sender label (used by the mailbox provider). */
|
|
19
19
|
from?: string;
|
|
20
|
+
/** Destination was resolved through the verified owner alias. */
|
|
21
|
+
ownerScoped?: boolean;
|
|
20
22
|
/** Resolve + build the delivery but do not actually send. */
|
|
21
23
|
dryRun?: boolean;
|
|
22
24
|
}
|
|
@@ -86,6 +86,7 @@ export function resolveSendEnvelope(input, meta) {
|
|
|
86
86
|
thread: input.thread?.trim() || undefined,
|
|
87
87
|
attachments: attachments.length ? attachments : undefined,
|
|
88
88
|
from: input.from?.trim() || undefined,
|
|
89
|
+
ownerScoped: usedOwnerAlias || (input.ownerMode === true && !input.to?.trim()),
|
|
89
90
|
dryRun: input.dryRun,
|
|
90
91
|
},
|
|
91
92
|
};
|
|
@@ -102,6 +103,7 @@ export async function deliverEnvelope(envelope, meta) {
|
|
|
102
103
|
thread: envelope.thread,
|
|
103
104
|
attachments: envelope.attachments,
|
|
104
105
|
from: envelope.from,
|
|
106
|
+
ownerScoped: envelope.ownerScoped,
|
|
105
107
|
dryRun: envelope.dryRun,
|
|
106
108
|
});
|
|
107
109
|
}
|
|
Binary file
|
|
Binary file
|
package/dist/lib/notify.js
CHANGED
|
@@ -57,7 +57,11 @@ export async function sendToOwner(text, options = {}) {
|
|
|
57
57
|
if (!provider) {
|
|
58
58
|
return { ok: false, channel, id: target, error };
|
|
59
59
|
}
|
|
60
|
-
return provider.send(text, {
|
|
60
|
+
return provider.send(text, {
|
|
61
|
+
target,
|
|
62
|
+
ownerScoped: options.target === undefined,
|
|
63
|
+
dryRun: options.dryRun,
|
|
64
|
+
});
|
|
61
65
|
}
|
|
62
66
|
export async function notifyUrgentBlock(block, options = {}) {
|
|
63
67
|
if (block.notifiedAt) {
|
package/dist/lib/refresh.js
CHANGED
|
@@ -114,7 +114,11 @@ export async function refresh(options = {}) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
// 3. Sync resources
|
|
117
|
+
// 3. Sync resources into version homes.
|
|
118
|
+
// Unattended (`skipPrompts` / `agents sync --yes --local`): every installed
|
|
119
|
+
// version — otherwise non-default homes keep stale hooks after a system
|
|
120
|
+
// update (fleet multi-harness: only the default version was refreshed).
|
|
121
|
+
// Interactive: default only; re-run `agents sync <agent>@all` for the rest.
|
|
118
122
|
const cliStates = await getAllCliStates();
|
|
119
123
|
const agentsToSync = agentFilter ? [agentFilter] : MANAGED_AGENT_IDS;
|
|
120
124
|
const available = getAvailableResources();
|
|
@@ -124,6 +128,9 @@ export async function refresh(options = {}) {
|
|
|
124
128
|
const defaultVer = getGlobalDefault(agentId);
|
|
125
129
|
if (!defaultVer)
|
|
126
130
|
continue;
|
|
131
|
+
const versionsToSync = skipPrompts
|
|
132
|
+
? listInstalledVersions(agentId)
|
|
133
|
+
: [defaultVer];
|
|
127
134
|
const actuallySynced = getActuallySyncedResources(agentId, defaultVer);
|
|
128
135
|
const newResources = getNewResources(available, actuallySynced, getProjectOnlyResources());
|
|
129
136
|
const hasAnySynced = actuallySynced.commands.length > 0 ||
|
|
@@ -155,24 +162,29 @@ export async function refresh(options = {}) {
|
|
|
155
162
|
forceFullSync = true;
|
|
156
163
|
}
|
|
157
164
|
if (forceFullSync || (selection && Object.keys(selection).length > 0)) {
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
165
|
+
const kinds = new Set();
|
|
166
|
+
for (const ver of versionsToSync) {
|
|
167
|
+
const syncResult = syncResourcesToVersion(agentId, ver, selection, forceFullSync ? { force: true } : undefined);
|
|
168
|
+
if (syncResult.commands)
|
|
169
|
+
kinds.add('commands');
|
|
170
|
+
if (syncResult.skills)
|
|
171
|
+
kinds.add('skills');
|
|
172
|
+
if (syncResult.hooks)
|
|
173
|
+
kinds.add('hooks');
|
|
174
|
+
if (syncResult.memory.length > 0)
|
|
175
|
+
kinds.add('memory');
|
|
176
|
+
if (syncResult.permissions)
|
|
177
|
+
kinds.add('permissions');
|
|
178
|
+
if (syncResult.mcp.length > 0)
|
|
179
|
+
kinds.add('mcp');
|
|
180
|
+
if (syncResult.plugins.length > 0)
|
|
181
|
+
kinds.add('plugins');
|
|
182
|
+
}
|
|
183
|
+
if (kinds.size > 0) {
|
|
184
|
+
const verNote = versionsToSync.length > 1
|
|
185
|
+
? chalk.gray(` (${versionsToSync.length} versions)`)
|
|
186
|
+
: '';
|
|
187
|
+
console.log(chalk.green(` Synced: ${[...kinds].join(', ')}`) + verNote);
|
|
176
188
|
}
|
|
177
189
|
}
|
|
178
190
|
}
|
|
@@ -185,7 +197,7 @@ export async function refresh(options = {}) {
|
|
|
185
197
|
}
|
|
186
198
|
}
|
|
187
199
|
}
|
|
188
|
-
// 4. Register hooks as lifecycle events
|
|
200
|
+
// 4. Register hooks as lifecycle events (same version set as resource sync)
|
|
189
201
|
const hookManifest = parseHookManifest();
|
|
190
202
|
if (Object.keys(hookManifest).length > 0) {
|
|
191
203
|
let hookRegistered = 0;
|
|
@@ -195,7 +207,9 @@ export async function refresh(options = {}) {
|
|
|
195
207
|
continue;
|
|
196
208
|
const versions = listInstalledVersions(agentId);
|
|
197
209
|
const defaultVer = getGlobalDefault(agentId);
|
|
198
|
-
const targetVersions =
|
|
210
|
+
const targetVersions = skipPrompts
|
|
211
|
+
? versions
|
|
212
|
+
: (defaultVer ? [defaultVer] : versions.slice(-1));
|
|
199
213
|
for (const ver of targetVersions) {
|
|
200
214
|
const home = getVersionHomePath(agentId, ver);
|
|
201
215
|
const result = registerHooksToSettings(agentId, home, hookManifest);
|
package/dist/lib/resources.js
CHANGED
|
@@ -128,6 +128,130 @@ export function listResources(kind, cwd) {
|
|
|
128
128
|
[path.join(getSystemAgentsDir(), kind), 'system', getSystemAgentsDir()],
|
|
129
129
|
...extraRepos.map((e) => [path.join(e.dir, kind), e.alias, e.dir]),
|
|
130
130
|
];
|
|
131
|
+
// Hooks use a one-level event-group layout (hooks/pre-tool-use/git-guard.sh).
|
|
132
|
+
// A flat readdir treats `pre-tool-use` as the resource name, so `system:*`
|
|
133
|
+
// pattern expansion never includes nested scripts — and `agents sync --force`
|
|
134
|
+
// leaves stale flat copies in version homes forever. Mirror getAvailableResources:
|
|
135
|
+
// expand group dirs that hold scripts (install name = basename with extension),
|
|
136
|
+
// keep fixture-only dirs as bundles, and keep top-level scripts as resources.
|
|
137
|
+
// Keep this logic self-contained (no hooks.ts import) so vi.mock of hooks.js
|
|
138
|
+
// in versions tests does not break listResources.
|
|
139
|
+
if (kind === 'hooks') {
|
|
140
|
+
const HOOK_SCRIPT_EXTS = new Set([
|
|
141
|
+
'.sh', '.bash', '.zsh', '.py', '.js', '.ts', '.mjs', '.cjs', '.rb', '.pl', '.ps1', '.cmd', '.bat',
|
|
142
|
+
]);
|
|
143
|
+
const HOOK_NON_SCRIPT_EXTS = new Set([
|
|
144
|
+
'.md', '.markdown', '.rst', '.txt', '.yaml', '.yml', '.json', '.toml', '.ini', '.conf',
|
|
145
|
+
]);
|
|
146
|
+
const HOOK_GROUP_SKIP = new Set(['node_modules', '.git', '.cache']);
|
|
147
|
+
const isHookScriptName = (fileName, mode) => {
|
|
148
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
149
|
+
if (HOOK_SCRIPT_EXTS.has(ext))
|
|
150
|
+
return true;
|
|
151
|
+
return (mode & 0o111) !== 0 && !HOOK_NON_SCRIPT_EXTS.has(ext);
|
|
152
|
+
};
|
|
153
|
+
for (const [dir, source, repoRoot] of roots) {
|
|
154
|
+
if (!fs.existsSync(dir))
|
|
155
|
+
continue;
|
|
156
|
+
let top;
|
|
157
|
+
try {
|
|
158
|
+
top = fs.readdirSync(dir);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
for (const name of top) {
|
|
164
|
+
if (name.startsWith('.'))
|
|
165
|
+
continue;
|
|
166
|
+
const full = path.join(dir, name);
|
|
167
|
+
let stat;
|
|
168
|
+
try {
|
|
169
|
+
stat = fs.lstatSync(full);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (stat.isSymbolicLink())
|
|
175
|
+
continue;
|
|
176
|
+
if (stat.isFile()) {
|
|
177
|
+
if (!isHookScriptName(name, stat.mode))
|
|
178
|
+
continue;
|
|
179
|
+
// Docs that live beside hooks (README/AGENTS) are not resources.
|
|
180
|
+
const raw = name.replace(/\.(md|yaml|yml)$/, '');
|
|
181
|
+
if (isDirectoryDoc(kind, raw))
|
|
182
|
+
continue;
|
|
183
|
+
if (seen.has(name))
|
|
184
|
+
continue;
|
|
185
|
+
if (!resourceIsActive(kind, name, source))
|
|
186
|
+
continue;
|
|
187
|
+
seen.add(name);
|
|
188
|
+
results.push(withProvenance({
|
|
189
|
+
name,
|
|
190
|
+
path: full,
|
|
191
|
+
source,
|
|
192
|
+
repoRoot,
|
|
193
|
+
}));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (!stat.isDirectory() || HOOK_GROUP_SKIP.has(name))
|
|
197
|
+
continue;
|
|
198
|
+
let nested;
|
|
199
|
+
try {
|
|
200
|
+
nested = fs.readdirSync(full);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const scripts = [];
|
|
206
|
+
for (const nestedName of nested) {
|
|
207
|
+
if (nestedName.startsWith('.'))
|
|
208
|
+
continue;
|
|
209
|
+
const nfull = path.join(full, nestedName);
|
|
210
|
+
let nstat;
|
|
211
|
+
try {
|
|
212
|
+
nstat = fs.lstatSync(nfull);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (nstat.isSymbolicLink() || !nstat.isFile())
|
|
218
|
+
continue;
|
|
219
|
+
if (isHookScriptName(nestedName, nstat.mode))
|
|
220
|
+
scripts.push(nestedName);
|
|
221
|
+
}
|
|
222
|
+
if (scripts.length > 0) {
|
|
223
|
+
for (const script of scripts) {
|
|
224
|
+
if (seen.has(script))
|
|
225
|
+
continue;
|
|
226
|
+
if (!resourceIsActive(kind, script, source))
|
|
227
|
+
continue;
|
|
228
|
+
seen.add(script);
|
|
229
|
+
results.push(withProvenance({
|
|
230
|
+
name: script,
|
|
231
|
+
path: path.join(full, script),
|
|
232
|
+
source,
|
|
233
|
+
repoRoot,
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// Fixture-only directory bundle (hooks/tests/fixtures/…).
|
|
239
|
+
if (seen.has(name))
|
|
240
|
+
continue;
|
|
241
|
+
if (!resourceIsActive(kind, name, source))
|
|
242
|
+
continue;
|
|
243
|
+
seen.add(name);
|
|
244
|
+
results.push(withProvenance({
|
|
245
|
+
name,
|
|
246
|
+
path: full,
|
|
247
|
+
source,
|
|
248
|
+
repoRoot,
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return results;
|
|
254
|
+
}
|
|
131
255
|
for (const [dir, source, repoRoot] of roots) {
|
|
132
256
|
if (!fs.existsSync(dir))
|
|
133
257
|
continue;
|
|
Binary file
|
|
Binary file
|
|
@@ -1419,12 +1419,16 @@ export async function listTmuxAgentSessions() {
|
|
|
1419
1419
|
topic,
|
|
1420
1420
|
tokPerSec,
|
|
1421
1421
|
sessionFile,
|
|
1422
|
-
//
|
|
1423
|
-
//
|
|
1424
|
-
startedAtMs: birthtimeMs,
|
|
1422
|
+
// Prefer the launch registry's start when known (more accurate than
|
|
1423
|
+
// transcript birth for a resumed/reused file); else transcript times.
|
|
1424
|
+
startedAtMs: liveEntry?.startedAtMs ?? birthtimeMs,
|
|
1425
1425
|
lastActivityMs: mtimeMs,
|
|
1426
1426
|
provenance,
|
|
1427
1427
|
owner: resolveOwner(liveEntry?.actor, id.sessionId ?? sessionIdFromFile(sessionFile)),
|
|
1428
|
+
// Factory / --active join key: AGENT_TERMINAL_ID stamped on the launch
|
|
1429
|
+
// registry and preserved by SessionStart. Without this, Grok/Codex tmux
|
|
1430
|
+
// panes never surface terminalId even when by-pid has it (RUSH-2192).
|
|
1431
|
+
terminalId: liveEntry?.terminalId,
|
|
1428
1432
|
// An id-less pane keys its dedupe on the unique pane, so two anonymous
|
|
1429
1433
|
// co-located panes stay two rows instead of folding into one.
|
|
1430
1434
|
paneId: id.sessionId ?? sessionIdFromFile(sessionFile) ? undefined : pane,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.19",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|