@phnx-labs/agents-cli 1.22.15 → 1.22.17
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 +52 -0
- package/README.md +13 -2
- package/dist/bin/agents +0 -0
- package/dist/commands/cloud.d.ts +0 -1
- package/dist/commands/cloud.js +19 -185
- package/dist/commands/doctor.js +16 -14
- package/dist/commands/exec.js +153 -16
- package/dist/commands/feed.d.ts +1 -0
- package/dist/commands/feed.js +36 -32
- package/dist/commands/humans.d.ts +10 -0
- package/dist/commands/humans.js +91 -0
- package/dist/commands/resume.d.ts +17 -0
- package/dist/commands/resume.js +80 -0
- package/dist/commands/run-cloud.d.ts +26 -0
- package/dist/commands/run-cloud.js +162 -0
- package/dist/commands/sessions.d.ts +13 -4
- package/dist/commands/sessions.js +37 -19
- package/dist/commands/versions.js +4 -2
- package/dist/commands/view.js +1 -54
- package/dist/index.js +3 -2
- package/dist/lib/agents.js +4 -2
- package/dist/lib/channels/send.d.ts +5 -2
- package/dist/lib/channels/send.js +16 -13
- package/dist/lib/cloud/dispatch.d.ts +27 -0
- package/dist/lib/cloud/dispatch.js +214 -0
- package/dist/lib/devices/doctor-findings.d.ts +14 -0
- package/dist/lib/devices/doctor-findings.js +92 -51
- package/dist/lib/exec.d.ts +6 -2
- package/dist/lib/exec.js +22 -4
- package/dist/lib/feed-post.d.ts +2 -2
- package/dist/lib/feed-post.js +15 -10
- package/dist/lib/hooks.d.ts +4 -2
- package/dist/lib/hooks.js +173 -23
- package/dist/lib/hosts/remote-cmd.js +8 -0
- package/dist/lib/humans.d.ts +26 -0
- package/dist/lib/humans.js +73 -0
- package/dist/lib/memory.js +2 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/migrate.js +112 -4
- package/dist/lib/notify.d.ts +4 -4
- package/dist/lib/notify.js +4 -3
- package/dist/lib/permissions.js +12 -10
- package/dist/lib/placement.d.ts +8 -4
- package/dist/lib/placement.js +14 -8
- package/dist/lib/projects.d.ts +3 -3
- package/dist/lib/projects.js +19 -17
- 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/bundles.d.ts +11 -0
- package/dist/lib/secrets/bundles.js +122 -5
- package/dist/lib/session/actor-sidecar.d.ts +5 -2
- package/dist/lib/session/actor-sidecar.js +4 -2
- package/dist/lib/session/db.d.ts +2 -1
- package/dist/lib/session/db.js +19 -3
- package/dist/lib/session/types.d.ts +4 -0
- package/dist/lib/settings-manifest.js +7 -1
- package/dist/lib/staleness/writers/sources.d.ts +6 -1
- package/dist/lib/staleness/writers/sources.js +93 -7
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +5 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +6 -6
- package/dist/lib/types.d.ts +52 -0
- package/dist/lib/versions.js +48 -14
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { getUserAgentsDir, getAgentsDir, getEnabledExtraRepos, getCommandsDir, getSkillsDir, getHooksDir } from '../../state.js';
|
|
13
|
-
import { safeJoin } from '../../paths.js';
|
|
13
|
+
import { isSafeSegmentName, safeJoin } from '../../paths.js';
|
|
14
14
|
/** Trusted source bases for content-like kinds. Project layer excluded. */
|
|
15
15
|
export function trustedSourceBases() {
|
|
16
16
|
return [
|
|
@@ -119,14 +119,100 @@ export function listPluginSkillNames(options = {}) {
|
|
|
119
119
|
}
|
|
120
120
|
return Array.from(names);
|
|
121
121
|
}
|
|
122
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Subdirectories under hooks/ that are never group dirs.
|
|
124
|
+
* Must stay in lockstep with HOOK_GROUP_SKIP_DIRS in hooks.ts.
|
|
125
|
+
*/
|
|
126
|
+
const HOOK_GROUP_SKIP_DIRS = new Set(['node_modules', '.git', '.cache']);
|
|
127
|
+
const HOOK_SCRIPT_EXTS = new Set([
|
|
128
|
+
'.sh', '.bash', '.zsh', '.py', '.js', '.ts', '.mjs', '.cjs', '.rb', '.pl', '.ps1', '.cmd', '.bat',
|
|
129
|
+
]);
|
|
130
|
+
function dirHasTopLevelScripts(groupDir) {
|
|
131
|
+
let entries;
|
|
132
|
+
try {
|
|
133
|
+
entries = fs.readdirSync(groupDir);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
for (const name of entries) {
|
|
139
|
+
if (name.startsWith('.'))
|
|
140
|
+
continue;
|
|
141
|
+
const ext = path.extname(name).toLowerCase();
|
|
142
|
+
if (!HOOK_SCRIPT_EXTS.has(ext))
|
|
143
|
+
continue;
|
|
144
|
+
if (isLiveFile(path.join(groupDir, name)))
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
function findNestedHookFile(hooksRoot, basename) {
|
|
150
|
+
if (!isLiveDir(hooksRoot))
|
|
151
|
+
return null;
|
|
152
|
+
let entries;
|
|
153
|
+
try {
|
|
154
|
+
entries = fs.readdirSync(hooksRoot).sort();
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
for (const name of entries) {
|
|
160
|
+
if (name.startsWith('.') || HOOK_GROUP_SKIP_DIRS.has(name))
|
|
161
|
+
continue;
|
|
162
|
+
const groupDir = path.join(hooksRoot, name);
|
|
163
|
+
if (!isLiveDir(groupDir))
|
|
164
|
+
continue;
|
|
165
|
+
// Only group dirs (those with scripts) contribute nested scripts.
|
|
166
|
+
if (!dirHasTopLevelScripts(groupDir))
|
|
167
|
+
continue;
|
|
168
|
+
const candidate = path.join(groupDir, basename);
|
|
169
|
+
if (isLiveFile(candidate))
|
|
170
|
+
return candidate;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Find the trusted source for a hook by name.
|
|
176
|
+
* - File basename (`04-session-identity.sh`) or relative path
|
|
177
|
+
* (`session-starts/04-session-identity.sh`) → script file
|
|
178
|
+
* - Directory basename (`tests`) → directory bundle (fixtures-only etc.)
|
|
179
|
+
*/
|
|
123
180
|
export function resolveHookSource(name) {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
...getEnabledExtraRepos().map((e) =>
|
|
181
|
+
const roots = [
|
|
182
|
+
path.join(getUserAgentsDir(), 'hooks'),
|
|
183
|
+
getHooksDir(),
|
|
184
|
+
...getEnabledExtraRepos().map((e) => path.join(e.dir, 'hooks')),
|
|
128
185
|
];
|
|
129
|
-
|
|
186
|
+
const base = path.basename(name);
|
|
187
|
+
for (const hooksRoot of roots) {
|
|
188
|
+
// Exact relative path (top-level or group/name) — multi-segment needs
|
|
189
|
+
// path.join + containment, not safeJoin (single-segment only).
|
|
190
|
+
if (name.includes('/') || name.includes('\\')) {
|
|
191
|
+
const candidate = path.resolve(hooksRoot, name);
|
|
192
|
+
const rootResolved = path.resolve(hooksRoot);
|
|
193
|
+
if ((candidate === rootResolved || candidate.startsWith(rootResolved + path.sep)) &&
|
|
194
|
+
isLiveFile(candidate)) {
|
|
195
|
+
return candidate;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (isSafeSegmentName(name)) {
|
|
199
|
+
try {
|
|
200
|
+
const top = safeJoin(hooksRoot, name);
|
|
201
|
+
// File script first; then directory bundle (e.g. tests/ fixtures).
|
|
202
|
+
if (isLiveFile(top))
|
|
203
|
+
return top;
|
|
204
|
+
if (isLiveDir(top) && !dirHasTopLevelScripts(top))
|
|
205
|
+
return top;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
/* invalid segment */
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const nested = findNestedHookFile(hooksRoot, base);
|
|
212
|
+
if (nested)
|
|
213
|
+
return nested;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
130
216
|
}
|
|
131
217
|
/** All trusted command-skill source roots, used to dedup name collisions for commands-as-skills writes. */
|
|
132
218
|
export function trustedSkillRoots() {
|
|
@@ -46,6 +46,7 @@ export declare const loadRoutines: ModuleLoader;
|
|
|
46
46
|
export declare const loadMonitors: ModuleLoader;
|
|
47
47
|
export declare const loadProjects: ModuleLoader;
|
|
48
48
|
export declare const loadRun: ModuleLoader;
|
|
49
|
+
export declare const loadResume: ModuleLoader;
|
|
49
50
|
export declare const loadFork: ModuleLoader;
|
|
50
51
|
export declare const loadDefaults: ModuleLoader;
|
|
51
52
|
export declare const loadSet: ModuleLoader;
|
|
@@ -104,6 +105,7 @@ export declare const loadShare: ModuleLoader;
|
|
|
104
105
|
export declare const loadAudit: ModuleLoader;
|
|
105
106
|
export declare const loadWebhook: ModuleLoader;
|
|
106
107
|
export declare const loadFunnel: ModuleLoader;
|
|
108
|
+
export declare const loadHumans: ModuleLoader;
|
|
107
109
|
/**
|
|
108
110
|
* Commands whose modules pull in the SQLite-backed session/cloud stack. They are
|
|
109
111
|
* registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
|
|
@@ -24,6 +24,7 @@ export const loadRoutines = async () => (await import('../../commands/routines.j
|
|
|
24
24
|
export const loadMonitors = async () => (await import('../../commands/monitors.js')).registerMonitorsCommands;
|
|
25
25
|
export const loadProjects = async () => (await import('../../commands/projects.js')).registerProjectsCommands;
|
|
26
26
|
export const loadRun = async () => (await import('../../commands/exec.js')).registerRunCommand;
|
|
27
|
+
export const loadResume = async () => (await import('../../commands/resume.js')).registerResumeCommand;
|
|
27
28
|
export const loadFork = async () => (await import('../../commands/fork.js')).registerForkCommand;
|
|
28
29
|
export const loadDefaults = async () => (await import('../../commands/defaults.js')).registerDefaultsCommands;
|
|
29
30
|
export const loadSet = async () => (await import('../../commands/set.js')).registerSetCommand;
|
|
@@ -82,6 +83,7 @@ export const loadShare = async () => (await import('../../commands/share.js')).r
|
|
|
82
83
|
export const loadAudit = async () => (await import('../../commands/audit.js')).registerAuditCommands;
|
|
83
84
|
export const loadWebhook = async () => (await import('../../commands/webhook.js')).registerWebhookCommand;
|
|
84
85
|
export const loadFunnel = async () => (await import('../../commands/funnel.js')).registerFunnelCommand;
|
|
86
|
+
export const loadHumans = async () => (await import('../../commands/humans.js')).registerHumansCommands;
|
|
85
87
|
/**
|
|
86
88
|
* Commands whose modules pull in the SQLite-backed session/cloud stack. They are
|
|
87
89
|
* registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
|
|
@@ -93,6 +95,7 @@ export const loadFunnel = async () => (await import('../../commands/funnel.js'))
|
|
|
93
95
|
// same SQLite stack, same post-help registration order as sessions.
|
|
94
96
|
export const LAZY_COMMAND_NAMES = new Set([
|
|
95
97
|
'sessions',
|
|
98
|
+
'resume',
|
|
96
99
|
'roster',
|
|
97
100
|
'teams',
|
|
98
101
|
'cloud',
|
|
@@ -145,6 +148,7 @@ export const COMMAND_LOADERS = {
|
|
|
145
148
|
monitors: [loadMonitors],
|
|
146
149
|
projects: [loadProjects],
|
|
147
150
|
run: [loadRun],
|
|
151
|
+
resume: [loadResume],
|
|
148
152
|
fork: [loadFork],
|
|
149
153
|
defaults: [loadDefaults],
|
|
150
154
|
set: [loadSet],
|
|
@@ -221,4 +225,5 @@ export const COMMAND_LOADERS = {
|
|
|
221
225
|
audit: [loadAudit],
|
|
222
226
|
webhook: [loadWebhook],
|
|
223
227
|
funnel: [loadFunnel],
|
|
228
|
+
humans: [loadHumans],
|
|
224
229
|
};
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
* and leaves a back-compat symlink at the old path.
|
|
24
24
|
*/
|
|
25
25
|
import type { Meta } from './types.js';
|
|
26
|
+
/** Return the absolute path to the humans.yaml file. */
|
|
27
|
+
export declare function getHumansFilePath(): string;
|
|
26
28
|
/**
|
|
27
29
|
* Header prepended to every agents.yaml the CLI writes (central and per-device
|
|
28
30
|
* docs). Carries the yaml-language-server schema hint so editors validate the
|
package/dist/lib/state.js
CHANGED
|
@@ -60,6 +60,10 @@ const LEGACY_SYSTEM_AGENTS_DIR = path.join(HOME, '.agents-system');
|
|
|
60
60
|
const META_FILE = path.join(USER_AGENTS_DIR, 'agents.yaml');
|
|
61
61
|
/** Legacy location — used only for one-shot migration in readMeta(). */
|
|
62
62
|
const SYSTEM_META_FILE = path.join(SYSTEM_AGENTS_DIR, 'agents.yaml');
|
|
63
|
+
/** Canonical path for the humans.yaml owner-identity/channel config. */
|
|
64
|
+
const HUMANS_FILE = path.join(USER_AGENTS_DIR, 'humans.yaml');
|
|
65
|
+
/** Return the absolute path to the humans.yaml file. */
|
|
66
|
+
export function getHumansFilePath() { return process.env.AGENTS_HUMANS_FILE ?? HUMANS_FILE; }
|
|
63
67
|
// ─── System resource dirs ─────────────────────────────────────────────────────
|
|
64
68
|
const SYSTEM_COMMANDS_DIR = path.join(SYSTEM_AGENTS_DIR, 'commands');
|
|
65
69
|
const SYSTEM_HOOKS_DIR = path.join(SYSTEM_AGENTS_DIR, 'hooks');
|
|
@@ -300,7 +304,7 @@ export function getResolvedRulesDir() { return SYSTEM_RULES_DIR; }
|
|
|
300
304
|
/** Path to MCP server YAML configs — system repo. */
|
|
301
305
|
export function getMcpDir() { return SYSTEM_MCP_DIR; }
|
|
302
306
|
/** Path to permission group YAML files — system repo. */
|
|
303
|
-
export function getPermissionsDir() { return SYSTEM_PERMISSIONS_DIR; }
|
|
307
|
+
export function getPermissionsDir() { return process.env.AGENTS_SYSTEM_PERMISSIONS_DIR ?? SYSTEM_PERMISSIONS_DIR; }
|
|
304
308
|
/** Path to subagent definition directories — system repo. */
|
|
305
309
|
export function getSubagentsDir() { return SYSTEM_SUBAGENTS_DIR; }
|
|
306
310
|
/** Path to ~/.agents/.system/hooks/promptcuts.yaml (system defaults). */
|
|
@@ -359,7 +363,7 @@ export function getUserHooksDir() { return USER_HOOKS_DIR; }
|
|
|
359
363
|
export function getUserSkillsDir() { return USER_SKILLS_DIR; }
|
|
360
364
|
export function getUserRulesDir() { return USER_RULES_DIR; }
|
|
361
365
|
export function getUserMcpDir() { return USER_MCP_DIR; }
|
|
362
|
-
export function getUserPermissionsDir() { return USER_PERMISSIONS_DIR; }
|
|
366
|
+
export function getUserPermissionsDir() { return process.env.AGENTS_USER_PERMISSIONS_DIR ?? USER_PERMISSIONS_DIR; }
|
|
363
367
|
export function getUserSubagentsDir() { return USER_SUBAGENTS_DIR; }
|
|
364
368
|
export function getSystemWorkflowsDir() { return SYSTEM_WORKFLOWS_DIR; }
|
|
365
369
|
export function getUserWorkflowsDir() { return USER_WORKFLOWS_DIR; }
|
|
@@ -674,8 +678,6 @@ export function ensureAgentsDir() {
|
|
|
674
678
|
fs.mkdirSync(PACKAGES_DIR, opts);
|
|
675
679
|
if (!fs.existsSync(ROUTINES_DIR))
|
|
676
680
|
fs.mkdirSync(ROUTINES_DIR, opts);
|
|
677
|
-
if (!fs.existsSync(WEBHOOKS_DIR))
|
|
678
|
-
fs.mkdirSync(WEBHOOKS_DIR, opts);
|
|
679
681
|
if (!fs.existsSync(RUNS_DIR))
|
|
680
682
|
fs.mkdirSync(RUNS_DIR, opts);
|
|
681
683
|
if (!fs.existsSync(VERSIONS_DIR))
|
|
@@ -694,8 +696,6 @@ export function ensureAgentsDir() {
|
|
|
694
696
|
fs.mkdirSync(SYSTEM_PERMISSIONS_DIR, opts);
|
|
695
697
|
if (!fs.existsSync(SYSTEM_SUBAGENTS_DIR))
|
|
696
698
|
fs.mkdirSync(SYSTEM_SUBAGENTS_DIR, opts);
|
|
697
|
-
if (!fs.existsSync(SYSTEM_WEBHOOKS_DIR))
|
|
698
|
-
fs.mkdirSync(SYSTEM_WEBHOOKS_DIR, opts);
|
|
699
699
|
try {
|
|
700
700
|
fs.chmodSync(SYSTEM_AGENTS_DIR, 0o700);
|
|
701
701
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1002,6 +1002,58 @@ export interface Meta {
|
|
|
1002
1002
|
projects?: string[];
|
|
1003
1003
|
};
|
|
1004
1004
|
}
|
|
1005
|
+
/** A single delivery channel entry in humans.yaml. */
|
|
1006
|
+
export interface HumanChannel {
|
|
1007
|
+
/** Canonical channel identifier (e.g. "imessage", "call"). */
|
|
1008
|
+
id: string;
|
|
1009
|
+
/** Provider transport (e.g. "rush", "twilio"). */
|
|
1010
|
+
transport: string;
|
|
1011
|
+
/** Provider-specific recipient (phone number, user id, address). */
|
|
1012
|
+
to?: string;
|
|
1013
|
+
/** If true the channel is watched for incoming messages. */
|
|
1014
|
+
watch?: boolean;
|
|
1015
|
+
/** Shell command to invoke (for call channels). */
|
|
1016
|
+
cmd?: string;
|
|
1017
|
+
/** Credentials bundle name (`agents secrets`). */
|
|
1018
|
+
creds?: string;
|
|
1019
|
+
/** Whether the channel is intrusive (e.g. voice call). */
|
|
1020
|
+
intrusive?: boolean;
|
|
1021
|
+
}
|
|
1022
|
+
/** Severity-to-channel-list escalation policy in humans.yaml. */
|
|
1023
|
+
export interface HumanPolicy {
|
|
1024
|
+
low?: string[];
|
|
1025
|
+
normal?: string[];
|
|
1026
|
+
critical?: string[];
|
|
1027
|
+
}
|
|
1028
|
+
/** Owner identity and contact configuration in humans.yaml. */
|
|
1029
|
+
export interface HumanOwner {
|
|
1030
|
+
/** Display name. */
|
|
1031
|
+
name?: string;
|
|
1032
|
+
/** IANA timezone string (e.g. "America/Los_Angeles"). */
|
|
1033
|
+
timezone?: string;
|
|
1034
|
+
/** Quiet hours as "HH:MM-HH:MM" in local time. */
|
|
1035
|
+
quiet_hours?: string;
|
|
1036
|
+
/** Default notification severity when unspecified. */
|
|
1037
|
+
default_severity?: 'low' | 'normal' | 'critical';
|
|
1038
|
+
/** Short-form channel config for `agents send --to owner` (channel + recipient). */
|
|
1039
|
+
notify?: {
|
|
1040
|
+
channel: string;
|
|
1041
|
+
to: string;
|
|
1042
|
+
};
|
|
1043
|
+
/** Full delivery-channel definitions. */
|
|
1044
|
+
channels?: HumanChannel[];
|
|
1045
|
+
/** Severity-to-channel escalation policy. */
|
|
1046
|
+
policy?: HumanPolicy;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Versioned humans.yaml config — owner identity, channels, and notification
|
|
1050
|
+
* policy. Written to ~/.agents/humans.yaml by `migrateHumans()`.
|
|
1051
|
+
*/
|
|
1052
|
+
export interface HumansConfig {
|
|
1053
|
+
/** Schema version. Always 1. */
|
|
1054
|
+
version: 1;
|
|
1055
|
+
owner?: HumanOwner;
|
|
1056
|
+
}
|
|
1005
1057
|
/** Persisted agent-host entry in agents.yaml (overlay or inline). */
|
|
1006
1058
|
export interface HostEntry {
|
|
1007
1059
|
/** `ssh-config`: reach via the bare name (ssh resolves). `inline`: use address/user below. */
|
package/dist/lib/versions.js
CHANGED
|
@@ -185,15 +185,24 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
result.skills = filterNamesForActiveResourceProfile('skills', Array.from(skillNames), sourceMapFromResources('skills', cwd));
|
|
188
|
-
// Hooks
|
|
189
|
-
// script
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
188
|
+
// Hooks:
|
|
189
|
+
// - top-level script files
|
|
190
|
+
// - one-level *group* dirs that contain top-level scripts (session-starts/*.sh)
|
|
191
|
+
// → each script is its own hook resource (install name = basename)
|
|
192
|
+
// - other directories (e.g. tests/ with fixtures only) → directory bundles,
|
|
193
|
+
// copied wholesale as one resource (pre-existing layout)
|
|
194
|
+
// Auxiliary content like README.md / promptcuts.yaml is not a hook. Older sync
|
|
195
|
+
// runs chmod 0o755'd everything, so an exec bit alone is not the signal.
|
|
194
196
|
const NON_SCRIPT_EXTS = new Set(['.md', '.markdown', '.rst', '.txt', '.yaml', '.yml', '.json', '.toml', '.ini', '.conf']);
|
|
195
197
|
const SCRIPT_EXTS = new Set(['.sh', '.bash', '.zsh', '.py', '.js', '.ts', '.mjs', '.cjs', '.rb', '.pl', '.ps1']);
|
|
198
|
+
const HOOK_GROUP_SKIP = new Set(['node_modules', '.git', '.cache']);
|
|
196
199
|
const hookNames = new Set();
|
|
200
|
+
const isHookScriptName = (fileName, mode) => {
|
|
201
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
202
|
+
if (SCRIPT_EXTS.has(ext))
|
|
203
|
+
return true;
|
|
204
|
+
return (mode & 0o111) !== 0 && !NON_SCRIPT_EXTS.has(ext);
|
|
205
|
+
};
|
|
197
206
|
for (const { base } of resourceBases) {
|
|
198
207
|
const hooksDir = path.join(base, 'hooks');
|
|
199
208
|
if (!fs.existsSync(hooksDir))
|
|
@@ -202,22 +211,47 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
202
211
|
if (name.startsWith('.'))
|
|
203
212
|
continue;
|
|
204
213
|
try {
|
|
205
|
-
const
|
|
214
|
+
const full = path.join(hooksDir, name);
|
|
215
|
+
const stat = fs.lstatSync(full);
|
|
206
216
|
if (stat.isSymbolicLink())
|
|
207
217
|
continue;
|
|
208
|
-
if (stat.
|
|
209
|
-
|
|
218
|
+
if (stat.isFile()) {
|
|
219
|
+
if (isHookScriptName(name, stat.mode))
|
|
220
|
+
hookNames.add(name);
|
|
210
221
|
continue;
|
|
211
222
|
}
|
|
212
|
-
if (!stat.
|
|
223
|
+
if (!stat.isDirectory() || HOOK_GROUP_SKIP.has(name))
|
|
213
224
|
continue;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
225
|
+
// Group vs bundle: if the dir has any top-level script files, expand
|
|
226
|
+
// them; otherwise treat the whole dir as one hook resource.
|
|
227
|
+
let nestedNames;
|
|
228
|
+
try {
|
|
229
|
+
nestedNames = fs.readdirSync(full);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
217
232
|
continue;
|
|
218
233
|
}
|
|
219
|
-
|
|
234
|
+
const scripts = [];
|
|
235
|
+
for (const nested of nestedNames) {
|
|
236
|
+
if (nested.startsWith('.'))
|
|
237
|
+
continue;
|
|
238
|
+
try {
|
|
239
|
+
const nfull = path.join(full, nested);
|
|
240
|
+
const nstat = fs.lstatSync(nfull);
|
|
241
|
+
if (nstat.isSymbolicLink() || !nstat.isFile())
|
|
242
|
+
continue;
|
|
243
|
+
if (isHookScriptName(nested, nstat.mode))
|
|
244
|
+
scripts.push(nested);
|
|
245
|
+
}
|
|
246
|
+
catch { /* ignore */ }
|
|
247
|
+
}
|
|
248
|
+
if (scripts.length > 0) {
|
|
249
|
+
for (const s of scripts)
|
|
250
|
+
hookNames.add(s);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
220
253
|
hookNames.add(name);
|
|
254
|
+
}
|
|
221
255
|
}
|
|
222
256
|
catch { /* ignore unreadable */ }
|
|
223
257
|
}
|
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.17",
|
|
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",
|