@phnx-labs/agents-cli 1.20.56 → 1.20.58
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 +26 -1
- package/README.md +34 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/defaults.js +24 -0
- package/dist/commands/exec.js +28 -4
- package/dist/commands/secrets.d.ts +3 -2
- package/dist/commands/secrets.js +35 -25
- package/dist/commands/teams.d.ts +20 -1
- package/dist/commands/teams.js +105 -2
- package/dist/commands/versions.js +11 -3
- package/dist/commands/view.js +19 -4
- package/dist/lib/agents.d.ts +21 -0
- package/dist/lib/agents.js +28 -4
- package/dist/lib/daemon.d.ts +5 -5
- package/dist/lib/daemon.js +88 -17
- package/dist/lib/git.d.ts +9 -0
- package/dist/lib/git.js +12 -0
- package/dist/lib/hosts/dispatch.d.ts +21 -0
- package/dist/lib/hosts/dispatch.js +88 -5
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/permissions.d.ts +19 -1
- package/dist/lib/permissions.js +137 -0
- package/dist/lib/project-root.d.ts +65 -0
- package/dist/lib/project-root.js +133 -0
- package/dist/lib/resources/permissions.js +2 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/secrets/agent.d.ts +48 -18
- package/dist/lib/secrets/agent.js +288 -165
- package/dist/lib/secrets/remote.js +1 -0
- package/dist/lib/session/active.d.ts +3 -0
- package/dist/lib/session/active.js +1 -0
- package/dist/lib/session/parse.js +38 -15
- package/dist/lib/session/state.d.ts +4 -1
- package/dist/lib/session/state.js +18 -1
- package/dist/lib/session/types.d.ts +8 -0
- package/dist/lib/staleness/detectors/permissions.js +42 -0
- package/dist/lib/staleness/detectors/subagents.js +30 -0
- package/dist/lib/staleness/writers/subagents.js +13 -1
- package/dist/lib/subagents.d.ts +22 -0
- package/dist/lib/subagents.js +146 -0
- package/dist/lib/teams/agents.d.ts +30 -0
- package/dist/lib/teams/agents.js +271 -42
- package/dist/lib/types.d.ts +13 -0
- package/dist/lib/versions.d.ts +39 -0
- package/dist/lib/versions.js +199 -12
- package/package.json +1 -1
- package/scripts/postinstall.js +26 -11
package/dist/lib/permissions.js
CHANGED
|
@@ -440,6 +440,37 @@ export function convertToClaudeFormat(set) {
|
|
|
440
440
|
}
|
|
441
441
|
return { permissions };
|
|
442
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Map a canonical rule to Cursor CLI syntax.
|
|
445
|
+
* Cursor uses Shell(...) instead of Bash(...); other tools keep TitleCase names.
|
|
446
|
+
* https://cursor.com/docs/cli/reference/permissions
|
|
447
|
+
*/
|
|
448
|
+
function canonicalToCursorRule(perm) {
|
|
449
|
+
if (perm === 'Bash' || perm.startsWith('Bash(')) {
|
|
450
|
+
return perm.replace(/^Bash/, 'Shell');
|
|
451
|
+
}
|
|
452
|
+
// Canonical WebSearch maps to WebFetch family for network allow.
|
|
453
|
+
if (perm.startsWith('WebSearch(')) {
|
|
454
|
+
return perm.replace(/^WebSearch/, 'WebFetch');
|
|
455
|
+
}
|
|
456
|
+
// Cursor has no Edit prefix — file writes use Write(...).
|
|
457
|
+
if (perm === 'Edit' || perm.startsWith('Edit(')) {
|
|
458
|
+
return perm.replace(/^Edit/, 'Write');
|
|
459
|
+
}
|
|
460
|
+
return perm;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Convert canonical permission set to Cursor CLI format
|
|
464
|
+
* (`~/.cursor/cli-config.json` permissions.allow/deny).
|
|
465
|
+
*/
|
|
466
|
+
export function convertToCursorFormat(set) {
|
|
467
|
+
return {
|
|
468
|
+
permissions: {
|
|
469
|
+
allow: set.allow.map(canonicalToCursorRule),
|
|
470
|
+
deny: (set.deny ?? []).map(canonicalToCursorRule),
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
}
|
|
443
474
|
/**
|
|
444
475
|
* Parse canonical permission pattern to extract tool and pattern.
|
|
445
476
|
* "Bash(git *)" -> { tool: "bash", pattern: "git *" }
|
|
@@ -592,6 +623,57 @@ function canonicalToGrokRule(perm, action) {
|
|
|
592
623
|
}
|
|
593
624
|
return { action, tool, pattern };
|
|
594
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Convert canonical permissions to Kiro CLI v3 capability rules.
|
|
628
|
+
* Kiro stores these rules in ~/.kiro/settings/permissions.yaml.
|
|
629
|
+
*/
|
|
630
|
+
export function convertToKiroFormat(set) {
|
|
631
|
+
const rules = [];
|
|
632
|
+
for (const perm of set.allow) {
|
|
633
|
+
const rule = canonicalToKiroRule(perm, 'allow');
|
|
634
|
+
if (rule)
|
|
635
|
+
rules.push(rule);
|
|
636
|
+
}
|
|
637
|
+
for (const perm of set.deny ?? []) {
|
|
638
|
+
const rule = canonicalToKiroRule(perm, 'deny');
|
|
639
|
+
if (rule)
|
|
640
|
+
rules.push(rule);
|
|
641
|
+
}
|
|
642
|
+
return { rules };
|
|
643
|
+
}
|
|
644
|
+
function canonicalToKiroRule(perm, effect) {
|
|
645
|
+
if (BLANKET_BASH_FORMS.has(perm)) {
|
|
646
|
+
return { capability: 'shell', effect };
|
|
647
|
+
}
|
|
648
|
+
const parsed = parseCanonicalPreserveCase(perm);
|
|
649
|
+
const capability = KIRO_CAPABILITY_BY_TOOL[parsed.tool.toLowerCase()];
|
|
650
|
+
if (!capability)
|
|
651
|
+
return null;
|
|
652
|
+
if (parsed.pattern === null || parsed.pattern === '*' || parsed.pattern === '**') {
|
|
653
|
+
return { capability, effect };
|
|
654
|
+
}
|
|
655
|
+
const lowerTool = parsed.tool.toLowerCase();
|
|
656
|
+
const pattern = lowerTool === 'bash'
|
|
657
|
+
? normalizeBashPattern(parsed.pattern)
|
|
658
|
+
: (lowerTool === 'webfetch' || lowerTool === 'websearch') && parsed.pattern.startsWith('domain:')
|
|
659
|
+
? parsed.pattern.slice('domain:'.length)
|
|
660
|
+
: parsed.pattern;
|
|
661
|
+
return { capability, effect, match: [pattern] };
|
|
662
|
+
}
|
|
663
|
+
const KIRO_CAPABILITY_BY_TOOL = {
|
|
664
|
+
bash: 'shell',
|
|
665
|
+
read: 'fs_read',
|
|
666
|
+
grep: 'fs_read',
|
|
667
|
+
glob: 'fs_read',
|
|
668
|
+
write: 'fs_write',
|
|
669
|
+
edit: 'fs_write',
|
|
670
|
+
notebookedit: 'fs_write',
|
|
671
|
+
webfetch: 'web_fetch',
|
|
672
|
+
websearch: 'web_search',
|
|
673
|
+
mcp: 'mcp',
|
|
674
|
+
subagent: 'subagent',
|
|
675
|
+
skill: 'skill',
|
|
676
|
+
};
|
|
595
677
|
/**
|
|
596
678
|
* Parse a canonical permission string preserving the tool's original casing.
|
|
597
679
|
* `parseCanonicalPattern` lowercases the tool name, which is fine for Grok
|
|
@@ -1258,6 +1340,61 @@ export function applyPermissionsToVersion(agentId, set, versionHome, merge = tru
|
|
|
1258
1340
|
fs.writeFileSync(configPath, TOML.stringify(config), 'utf-8');
|
|
1259
1341
|
return { success: true };
|
|
1260
1342
|
}
|
|
1343
|
+
if (agentId === 'cursor') {
|
|
1344
|
+
// Cursor CLI permissions live in ~/.cursor/cli-config.json
|
|
1345
|
+
const configPath = path.join(configDir, 'cli-config.json');
|
|
1346
|
+
let config = {};
|
|
1347
|
+
if (fs.existsSync(configPath)) {
|
|
1348
|
+
try {
|
|
1349
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1350
|
+
}
|
|
1351
|
+
catch { /* start fresh */ }
|
|
1352
|
+
}
|
|
1353
|
+
const converted = convertToCursorFormat(set);
|
|
1354
|
+
if (merge && config.permissions && typeof config.permissions === 'object') {
|
|
1355
|
+
const existing = config.permissions;
|
|
1356
|
+
const allow = new Set([...(existing.allow || []), ...converted.permissions.allow]);
|
|
1357
|
+
const deny = new Set([...(existing.deny || []), ...converted.permissions.deny]);
|
|
1358
|
+
config.permissions = { allow: [...allow], deny: [...deny] };
|
|
1359
|
+
}
|
|
1360
|
+
else {
|
|
1361
|
+
config.permissions = converted.permissions;
|
|
1362
|
+
}
|
|
1363
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
1364
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
1365
|
+
return { success: true };
|
|
1366
|
+
}
|
|
1367
|
+
if (agentId === 'kiro') {
|
|
1368
|
+
const permissionsPath = path.join(versionHome, '.kiro', 'settings', 'permissions.yaml');
|
|
1369
|
+
let config = {};
|
|
1370
|
+
if (fs.existsSync(permissionsPath)) {
|
|
1371
|
+
const parsed = yaml.parse(fs.readFileSync(permissionsPath, 'utf-8'));
|
|
1372
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
1373
|
+
config = parsed;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
const newRules = convertToKiroFormat(set).rules;
|
|
1377
|
+
if (merge) {
|
|
1378
|
+
const existingRules = Array.isArray(config.rules) ? config.rules : [];
|
|
1379
|
+
const seen = new Set();
|
|
1380
|
+
config.rules = [...existingRules, ...newRules].filter((rule) => {
|
|
1381
|
+
if (!rule || typeof rule !== 'object' || Array.isArray(rule))
|
|
1382
|
+
return false;
|
|
1383
|
+
const record = rule;
|
|
1384
|
+
const key = `${String(record.effect)}|${String(record.capability)}|${JSON.stringify(record.match ?? [])}|${JSON.stringify(record.exclude ?? [])}`;
|
|
1385
|
+
if (seen.has(key))
|
|
1386
|
+
return false;
|
|
1387
|
+
seen.add(key);
|
|
1388
|
+
return true;
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
else {
|
|
1392
|
+
config.rules = newRules;
|
|
1393
|
+
}
|
|
1394
|
+
fs.mkdirSync(path.dirname(permissionsPath), { recursive: true });
|
|
1395
|
+
fs.writeFileSync(permissionsPath, yaml.stringify(config), 'utf-8');
|
|
1396
|
+
return { success: true };
|
|
1397
|
+
}
|
|
1261
1398
|
return { success: false, error: `Agent '${agentId}' does not support permissions` };
|
|
1262
1399
|
}
|
|
1263
1400
|
catch (err) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projects-root resolution for the `agents run --project <slug>` shorthand.
|
|
3
|
+
*
|
|
4
|
+
* Projects follow a predictable layout — `<root>/<repo>` (e.g.
|
|
5
|
+
* `~/src/github.com/<user>/<repo>`), with git worktrees under
|
|
6
|
+
* `<repo>/.agents/worktrees/<slug>`. The root is auto-inferred from the repo you
|
|
7
|
+
* launch inside (the directory ABOVE the git root) and cached in `agents.yaml`
|
|
8
|
+
* so later runs resolve a bare slug from anywhere. It is stored home-relative
|
|
9
|
+
* (`~/…`) when it sits under `$HOME`, so the SAME value resolves on a remote
|
|
10
|
+
* host whose home differs (`/home/<user>` vs `/Users/<user>`): a `--host` run
|
|
11
|
+
* keeps the `~` and lets the remote login shell expand it (see `remoteCdPrefix`
|
|
12
|
+
* in `hosts/dispatch.ts`), while a local run expands `~` against the local home.
|
|
13
|
+
*/
|
|
14
|
+
/** Rewrite an absolute path under the local home to a `~/`-relative string; pass others through. */
|
|
15
|
+
export declare function toHomeRelative(abs: string): string;
|
|
16
|
+
/** Expand a leading `~`/`$HOME` against the LOCAL home. Other paths pass through unchanged. */
|
|
17
|
+
export declare function expandLocalHome(p: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Make a `--cwd`/`--project` value portable to a remote host: an absolute path
|
|
20
|
+
* under the LOCAL home (which the local shell already expanded from `~`) becomes
|
|
21
|
+
* `~/…` so the *remote* shell re-roots it at its own home. Paths already anchored
|
|
22
|
+
* at `~`/`$HOME` pass through; other absolute or relative paths are left as-is
|
|
23
|
+
* (used verbatim on the host). Explicit `--remote-cwd` is NOT run through this —
|
|
24
|
+
* it is a literal remote path by contract.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toRemotePortable(p: string): string;
|
|
27
|
+
/** The configured projects root (home-relative or absolute), or undefined when unset. */
|
|
28
|
+
export declare function getProjectRoot(): string | undefined;
|
|
29
|
+
/** Set (override) the cached projects root. Stored home-relative when under `$HOME`. */
|
|
30
|
+
export declare function setProjectRoot(rootPath: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Infer the projects root from `cwd`: the directory ABOVE the git repo root
|
|
33
|
+
* (cwd inside `~/src/github.com/user/repo` → `~/src/github.com/user`). Returns a
|
|
34
|
+
* home-relative string when under `$HOME`; undefined when `cwd` is not in a repo.
|
|
35
|
+
*/
|
|
36
|
+
export declare function inferProjectRoot(cwd: string): Promise<string | undefined>;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the projects root, auto-inferring and caching on first use. Throws an
|
|
39
|
+
* actionable error when it is neither configured nor inferrable from `cwd`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function ensureProjectRoot(cwd: string): Promise<string>;
|
|
42
|
+
export interface ProjectRef {
|
|
43
|
+
slug: string;
|
|
44
|
+
worktree?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Parse a `--project` value of the form `<slug>[@<worktree>]`. */
|
|
47
|
+
export declare function parseProjectRef(ref: string): ProjectRef;
|
|
48
|
+
/**
|
|
49
|
+
* Join a root + `--project` ref into a working directory. Pure (no I/O) so the
|
|
50
|
+
* slug/worktree layout is unit-testable. `forRemote` keeps the path
|
|
51
|
+
* home-relative (`~/…`) for the remote shell to expand; otherwise it is expanded
|
|
52
|
+
* against the local home into an absolute path.
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildProjectPath(root: string, ref: string, forRemote: boolean): string;
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a `--project` ref to a working directory, inferring/caching the root.
|
|
57
|
+
*
|
|
58
|
+
* `forRemote: true` returns a home-relative path (`~/…`) so the REMOTE login
|
|
59
|
+
* shell expands `~`/`$HOME` to its own home. `forRemote: false` returns an
|
|
60
|
+
* absolute local path and verifies it exists (so a mistyped slug fails loudly).
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveProjectRef(ref: string, opts: {
|
|
63
|
+
forRemote: boolean;
|
|
64
|
+
cwd?: string;
|
|
65
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projects-root resolution for the `agents run --project <slug>` shorthand.
|
|
3
|
+
*
|
|
4
|
+
* Projects follow a predictable layout — `<root>/<repo>` (e.g.
|
|
5
|
+
* `~/src/github.com/<user>/<repo>`), with git worktrees under
|
|
6
|
+
* `<repo>/.agents/worktrees/<slug>`. The root is auto-inferred from the repo you
|
|
7
|
+
* launch inside (the directory ABOVE the git root) and cached in `agents.yaml`
|
|
8
|
+
* so later runs resolve a bare slug from anywhere. It is stored home-relative
|
|
9
|
+
* (`~/…`) when it sits under `$HOME`, so the SAME value resolves on a remote
|
|
10
|
+
* host whose home differs (`/home/<user>` vs `/Users/<user>`): a `--host` run
|
|
11
|
+
* keeps the `~` and lets the remote login shell expand it (see `remoteCdPrefix`
|
|
12
|
+
* in `hosts/dispatch.ts`), while a local run expands `~` against the local home.
|
|
13
|
+
*/
|
|
14
|
+
import * as os from 'os';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import { readMeta, updateMeta } from './state.js';
|
|
18
|
+
import { getMainRepoRoot } from './git.js';
|
|
19
|
+
const HOME = process.env.HOME ?? os.homedir();
|
|
20
|
+
/** Rewrite an absolute path under the local home to a `~/`-relative string; pass others through. */
|
|
21
|
+
export function toHomeRelative(abs) {
|
|
22
|
+
const rel = path.relative(HOME, abs);
|
|
23
|
+
if (rel === '')
|
|
24
|
+
return '~';
|
|
25
|
+
if (!rel.startsWith('..') && !path.isAbsolute(rel))
|
|
26
|
+
return `~/${rel}`;
|
|
27
|
+
return abs;
|
|
28
|
+
}
|
|
29
|
+
/** Expand a leading `~`/`$HOME` against the LOCAL home. Other paths pass through unchanged. */
|
|
30
|
+
export function expandLocalHome(p) {
|
|
31
|
+
if (p === '~' || p === '$HOME')
|
|
32
|
+
return HOME;
|
|
33
|
+
if (p.startsWith('~/'))
|
|
34
|
+
return path.join(HOME, p.slice(2));
|
|
35
|
+
if (p.startsWith('$HOME/'))
|
|
36
|
+
return path.join(HOME, p.slice(6));
|
|
37
|
+
return p;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Make a `--cwd`/`--project` value portable to a remote host: an absolute path
|
|
41
|
+
* under the LOCAL home (which the local shell already expanded from `~`) becomes
|
|
42
|
+
* `~/…` so the *remote* shell re-roots it at its own home. Paths already anchored
|
|
43
|
+
* at `~`/`$HOME` pass through; other absolute or relative paths are left as-is
|
|
44
|
+
* (used verbatim on the host). Explicit `--remote-cwd` is NOT run through this —
|
|
45
|
+
* it is a literal remote path by contract.
|
|
46
|
+
*/
|
|
47
|
+
export function toRemotePortable(p) {
|
|
48
|
+
if (p.startsWith('~') || p.startsWith('$HOME'))
|
|
49
|
+
return p;
|
|
50
|
+
if (path.isAbsolute(p))
|
|
51
|
+
return toHomeRelative(p);
|
|
52
|
+
return p;
|
|
53
|
+
}
|
|
54
|
+
/** The configured projects root (home-relative or absolute), or undefined when unset. */
|
|
55
|
+
export function getProjectRoot() {
|
|
56
|
+
return readMeta().projectRoot;
|
|
57
|
+
}
|
|
58
|
+
/** Set (override) the cached projects root. Stored home-relative when under `$HOME`. */
|
|
59
|
+
export function setProjectRoot(rootPath) {
|
|
60
|
+
const stored = toHomeRelative(path.resolve(expandLocalHome(rootPath)));
|
|
61
|
+
updateMeta({ projectRoot: stored });
|
|
62
|
+
return stored;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Infer the projects root from `cwd`: the directory ABOVE the git repo root
|
|
66
|
+
* (cwd inside `~/src/github.com/user/repo` → `~/src/github.com/user`). Returns a
|
|
67
|
+
* home-relative string when under `$HOME`; undefined when `cwd` is not in a repo.
|
|
68
|
+
*/
|
|
69
|
+
export async function inferProjectRoot(cwd) {
|
|
70
|
+
try {
|
|
71
|
+
const mainRoot = await getMainRepoRoot(cwd);
|
|
72
|
+
return toHomeRelative(path.dirname(mainRoot));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the projects root, auto-inferring and caching on first use. Throws an
|
|
80
|
+
* actionable error when it is neither configured nor inferrable from `cwd`.
|
|
81
|
+
*/
|
|
82
|
+
export async function ensureProjectRoot(cwd) {
|
|
83
|
+
const existing = getProjectRoot();
|
|
84
|
+
if (existing)
|
|
85
|
+
return existing;
|
|
86
|
+
const inferred = await inferProjectRoot(cwd);
|
|
87
|
+
if (!inferred) {
|
|
88
|
+
throw new Error('Could not determine your projects root. Run once from inside a project ' +
|
|
89
|
+
'(a git repo under your projects dir) so it can be inferred, or set it:\n' +
|
|
90
|
+
' agents defaults project-root ~/src/github.com/<you>');
|
|
91
|
+
}
|
|
92
|
+
updateMeta({ projectRoot: inferred });
|
|
93
|
+
process.stderr.write(`[project] cached projects root: ${inferred}\n`);
|
|
94
|
+
return inferred;
|
|
95
|
+
}
|
|
96
|
+
/** Parse a `--project` value of the form `<slug>[@<worktree>]`. */
|
|
97
|
+
export function parseProjectRef(ref) {
|
|
98
|
+
const at = ref.indexOf('@');
|
|
99
|
+
if (at === -1)
|
|
100
|
+
return { slug: ref };
|
|
101
|
+
return { slug: ref.slice(0, at), worktree: ref.slice(at + 1) || undefined };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Join a root + `--project` ref into a working directory. Pure (no I/O) so the
|
|
105
|
+
* slug/worktree layout is unit-testable. `forRemote` keeps the path
|
|
106
|
+
* home-relative (`~/…`) for the remote shell to expand; otherwise it is expanded
|
|
107
|
+
* against the local home into an absolute path.
|
|
108
|
+
*/
|
|
109
|
+
export function buildProjectPath(root, ref, forRemote) {
|
|
110
|
+
const { slug, worktree } = parseProjectRef(ref);
|
|
111
|
+
if (!slug)
|
|
112
|
+
throw new Error(`Invalid --project value: "${ref}"`);
|
|
113
|
+
let rel = `${root}/${slug}`;
|
|
114
|
+
if (worktree)
|
|
115
|
+
rel += `/.agents/worktrees/${worktree}`;
|
|
116
|
+
return forRemote ? rel : path.resolve(expandLocalHome(rel));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Resolve a `--project` ref to a working directory, inferring/caching the root.
|
|
120
|
+
*
|
|
121
|
+
* `forRemote: true` returns a home-relative path (`~/…`) so the REMOTE login
|
|
122
|
+
* shell expands `~`/`$HOME` to its own home. `forRemote: false` returns an
|
|
123
|
+
* absolute local path and verifies it exists (so a mistyped slug fails loudly).
|
|
124
|
+
*/
|
|
125
|
+
export async function resolveProjectRef(ref, opts) {
|
|
126
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
127
|
+
const root = await ensureProjectRoot(cwd);
|
|
128
|
+
const resolved = buildProjectPath(root, ref, opts.forRemote);
|
|
129
|
+
if (!opts.forRemote && !fs.existsSync(resolved)) {
|
|
130
|
+
throw new Error(`Project path not found: ${resolved}`);
|
|
131
|
+
}
|
|
132
|
+
return resolved;
|
|
133
|
+
}
|
|
@@ -69,6 +69,8 @@ function getAgentConfigPath(agent, versionHome) {
|
|
|
69
69
|
return path.join(versionHome, '.opencode', 'opencode.jsonc');
|
|
70
70
|
case 'kimi':
|
|
71
71
|
return path.join(versionHome, '.kimi-code', 'config.toml');
|
|
72
|
+
case 'kiro':
|
|
73
|
+
return path.join(versionHome, '.kiro', 'settings', 'permissions.yaml');
|
|
72
74
|
default:
|
|
73
75
|
return null;
|
|
74
76
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - Union: All resources from all layers are combined
|
|
6
6
|
* - Override on name conflict: Higher layer wins (project > user > system)
|
|
7
7
|
*/
|
|
8
|
-
export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'antigravity' | 'grok' | 'kimi' | 'hermes' | 'forge';
|
|
8
|
+
export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'kiro' | 'antigravity' | 'grok' | 'kimi' | 'hermes' | 'forge';
|
|
9
9
|
export type Layer = 'system' | 'user' | 'project';
|
|
10
10
|
export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent' | 'workflow' | 'memory';
|
|
11
11
|
/** A resolved resource with its origin layer. */
|
|
@@ -74,23 +74,23 @@ export interface AgentStatusEntry {
|
|
|
74
74
|
expiresAt: number;
|
|
75
75
|
keyCount: number;
|
|
76
76
|
}
|
|
77
|
-
/** True if
|
|
77
|
+
/** True if a legacy standalone-broker launchd plist is still installed. */
|
|
78
78
|
export declare function secretsAgentServiceInstalled(): boolean;
|
|
79
79
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
80
|
+
* Retire the legacy standalone secrets-agent launchd service: bootout the job
|
|
81
|
+
* (falling back to the legacy `unload`) and remove its plist so the always-on
|
|
82
|
+
* daemon owns the broker socket. Idempotent and best-effort — a no-op when no
|
|
83
|
+
* legacy plist is present. Does NOT wipe held bundles: the booted-out process's
|
|
84
|
+
* memory is gone anyway, and the daemon-hosted broker starts fresh.
|
|
84
85
|
*/
|
|
85
|
-
export declare function
|
|
86
|
+
export declare function retireLegacySecretsAgentService(): void;
|
|
86
87
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* and
|
|
88
|
+
* Stop the persistent broker for `agents secrets stop`: wipe whatever the broker
|
|
89
|
+
* holds (forces Touch ID again on the next read), then retire any legacy
|
|
90
|
+
* standalone service. The daemon-hosted broker itself is left running — it is
|
|
91
|
+
* the always-on backbone, and stopping it would take down unrelated background
|
|
92
|
+
* work (routines, browser IPC, session-sync).
|
|
91
93
|
*/
|
|
92
|
-
export declare function kickstartSecretsAgentService(): void;
|
|
93
|
-
/** Stop + remove the persistent broker service, and wipe whatever it held. */
|
|
94
94
|
export declare function uninstallSecretsAgentService(): Promise<void>;
|
|
95
95
|
export type Request = {
|
|
96
96
|
cmd: 'ping';
|
|
@@ -171,7 +171,30 @@ export declare function shouldWipeOnWatchEvent(chunk: string): boolean;
|
|
|
171
171
|
*/
|
|
172
172
|
export declare function runSecretsAgent(opts?: {
|
|
173
173
|
service?: boolean;
|
|
174
|
-
}): Promise<
|
|
174
|
+
}): Promise<{
|
|
175
|
+
close(): void;
|
|
176
|
+
} | null>;
|
|
177
|
+
/**
|
|
178
|
+
* Host the secrets broker inside the always-on daemon (#416).
|
|
179
|
+
*
|
|
180
|
+
* Serves the SAME socket and wire protocol as the standalone `runSecretsAgent`
|
|
181
|
+
* — so every existing client (`agentGetSync`, `agentPing`, `agentAutoLoadSync`)
|
|
182
|
+
* keeps working unchanged, no PROTOCOL_VERSION bump — but it is daemon-safe:
|
|
183
|
+
*
|
|
184
|
+
* - no pid-file single-instance guard (the daemon owns the instance);
|
|
185
|
+
* - no `process.exit`, no SIGTERM/SIGINT handlers, no self-heal/idle-exit
|
|
186
|
+
* (those would kill the daemon — the daemon is the always-on backbone and
|
|
187
|
+
* manages its own version/lifecycle). The sweep only TTL-evicts.
|
|
188
|
+
*
|
|
189
|
+
* The caller (`runDaemon`) normally invokes this only when no broker answers
|
|
190
|
+
* its initial ping. Binding still arbitrates ownership through the same shared
|
|
191
|
+
* path as the standalone service: a live owner wins, while only an unreachable
|
|
192
|
+
* stale socket is reclaimed. Returns a handle the daemon closes on shutdown,
|
|
193
|
+
* or null off-darwin (nothing to broker without biometry).
|
|
194
|
+
*/
|
|
195
|
+
export declare function startHostedBroker(): Promise<{
|
|
196
|
+
close(): void;
|
|
197
|
+
} | null>;
|
|
175
198
|
/** True if a broker socket exists at all. Cheap; gates the sync read so the
|
|
176
199
|
* never-unlocked path stays a single stat. */
|
|
177
200
|
export declare function agentSocketExists(): boolean;
|
|
@@ -246,14 +269,21 @@ export declare function agentLock(name?: string): Promise<number>;
|
|
|
246
269
|
* predates the server-side exclusion, so this keeps the internal entry from
|
|
247
270
|
* surfacing in `agents secrets status` in that skew window. */
|
|
248
271
|
export declare function agentStatus(): Promise<AgentStatusEntry[]>;
|
|
272
|
+
/** Ping result: whether a broker is reachable + speaking our protocol, and the
|
|
273
|
+
* version of the code it's running (for staleness detection). */
|
|
274
|
+
export declare function agentPing(): Promise<{
|
|
275
|
+
reachable: boolean;
|
|
276
|
+
cliVersion?: string;
|
|
277
|
+
}>;
|
|
249
278
|
/**
|
|
250
279
|
* Ensure a broker is running and reachable. Returns true once the socket answers
|
|
251
280
|
* a ping. macOS only.
|
|
252
281
|
*
|
|
253
|
-
* Prefers the
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* Only when the
|
|
257
|
-
*
|
|
282
|
+
* Prefers the always-on daemon, which hosts the broker socket (#416): retire any
|
|
283
|
+
* legacy standalone launchd service so the daemon owns the socket, then bring the
|
|
284
|
+
* daemon up (Path 0) — one supervised backbone that survives the whole login
|
|
285
|
+
* session, so subsequent reads never cold-start. Only when the daemon can't be
|
|
286
|
+
* used do we fall back to a one-off detached broker (Path 1) — the model that
|
|
287
|
+
* gets starved under heavy load, so it's last.
|
|
258
288
|
*/
|
|
259
289
|
export declare function ensureAgentRunning(timeoutMs?: number): Promise<boolean>;
|