@shardflux/cli 0.13.6 → 0.14.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +40 -4
  3. package/dist/acp-catalog.d.ts +152 -0
  4. package/dist/acp-catalog.js +287 -0
  5. package/dist/acp-journal.d.ts +18 -0
  6. package/dist/acp-journal.js +232 -0
  7. package/dist/acp-rpc.d.ts +41 -0
  8. package/dist/acp-rpc.js +115 -0
  9. package/dist/acp.d.ts +27 -0
  10. package/dist/acp.js +290 -0
  11. package/dist/agent-commands.d.ts +10 -2
  12. package/dist/agent-commands.js +37 -18
  13. package/dist/agent-machine.d.ts +6 -0
  14. package/dist/agent-machine.js +93 -0
  15. package/dist/agent-repository.js +1 -1
  16. package/dist/agent-state.d.ts +3 -0
  17. package/dist/agent-state.js +33 -12
  18. package/dist/agent-target-commands.d.ts +4 -1
  19. package/dist/agent-target-commands.js +64 -22
  20. package/dist/agent-targets.d.ts +6 -4
  21. package/dist/agent-targets.js +72 -31
  22. package/dist/args.d.ts +1 -1
  23. package/dist/args.js +53 -14
  24. package/dist/command-center-actions.js +26 -3
  25. package/dist/command-center.js +20 -4
  26. package/dist/conversation-commands.d.ts +3 -0
  27. package/dist/conversation-commands.js +36 -0
  28. package/dist/conversation-local-resume.d.ts +8 -0
  29. package/dist/conversation-local-resume.js +56 -0
  30. package/dist/conversation-local.d.ts +7 -0
  31. package/dist/conversation-local.js +61 -0
  32. package/dist/conversation-router.d.ts +12 -0
  33. package/dist/conversation-router.js +89 -0
  34. package/dist/conversation-stash.d.ts +11 -0
  35. package/dist/conversation-stash.js +158 -0
  36. package/dist/conversation-terminal.d.ts +3 -0
  37. package/dist/conversation-terminal.js +74 -0
  38. package/dist/conversation-worktree.d.ts +4 -0
  39. package/dist/conversation-worktree.js +22 -0
  40. package/dist/conversations.d.ts +53 -0
  41. package/dist/conversations.js +118 -0
  42. package/dist/device-bridge.d.ts +80 -0
  43. package/dist/device-bridge.js +398 -0
  44. package/dist/device-helper.d.ts +3 -0
  45. package/dist/device-helper.js +104 -0
  46. package/dist/errors.js +5 -0
  47. package/dist/format.js +3 -2
  48. package/dist/handlers.js +3 -1
  49. package/dist/main.js +15 -3
  50. package/dist/project-sync.d.ts +5 -1
  51. package/dist/project-sync.js +7 -6
  52. package/dist/t3-provider.d.ts +6 -0
  53. package/dist/t3-provider.js +99 -0
  54. package/dist/t3-runtime.d.ts +6 -0
  55. package/dist/t3-runtime.js +63 -0
  56. package/dist/target-lock.d.ts +3 -0
  57. package/dist/target-lock.js +143 -0
  58. package/package.json +3 -3
@@ -0,0 +1,74 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { AgentStateStore } from "./agent-state.js";
3
+ import { projectIdentity, projectLabels } from "./project-identity.js";
4
+ import { createConversation, conversationWorkspace, conversationsUnavailable, listConversations, localConversations, rememberConversation, resumeConversation, restoreConversationBinding } from "./conversations.js";
5
+ import { terminal } from "./agent-commands.js";
6
+ import { cloudClaudeLogin, loginClaudeInWorkspace, requireClaudeTerminal } from "./claude-cloud-login.js";
7
+ import { syncAgentCredentials } from "./agent-credentials.js";
8
+ import { prepareProjectTemplate } from "./project-template-setup.js";
9
+ import { projectWorkspacePath, seedAgentRepository } from "./agent-repository.js";
10
+ import { syncAgentConfig } from "./agent-config-sync.js";
11
+ import { savedMachine } from "./filesystem-state.js";
12
+ import { ensureMenubar, MenubarManager } from "./menubar.js";
13
+ import { UsageError } from "./errors.js";
14
+ import { localResumeTerminal } from "./conversation-local-resume.js";
15
+ import { agentReachability } from "./agent-reachability.js";
16
+ /** Select a durable conversation, then use the harness's native terminal for all interaction. */
17
+ export const launchConversationProject = async (ctx, values, [path = './', explicit]) => {
18
+ const project = projectIdentity(path), store = new AgentStateStore(ctx.store.dir);
19
+ const app = explicit === 'claude' || explicit === 'codex' ? explicit : store.app(project.remote ?? project.folder);
20
+ if (explicit && explicit !== app)
21
+ throw new UsageError('Choose claude or codex.');
22
+ if (values.resume === true) {
23
+ // The native pickers are VM-local. C-K6's project picker must select the binding first.
24
+ const records = localConversations(ctx);
25
+ const conversations = (await listConversations(ctx)).filter(c => records[c.id]?.folder === project.folder && c.harness === app);
26
+ if (!conversations.length)
27
+ throw new UsageError('This project has no saved conversations. Start one with shard ./.');
28
+ ctx.io.stderr.write(conversations.map((c, i) => `${i + 1}. ${c.title || c.key} (${c.state})\n`).join(''));
29
+ const selected = await ctx.input.secret('Continue conversation number: ', 'conversation number', 'pipe its number');
30
+ const c = conversations[Number(selected) - 1];
31
+ if (!/^\d+$/.test(selected) || !c)
32
+ throw new UsageError('Choose a conversation number from the list.');
33
+ if (records[c.id]?.handed_off && (c.state === 'done' || c.state === 'archived'))
34
+ return localResumeTerminal(ctx, c, project.folder);
35
+ const next = await resumeConversation(ctx, c);
36
+ rememberConversation(ctx, next, project.folder);
37
+ const ws = await conversationWorkspace(ctx, next);
38
+ await restoreConversationBinding(ctx, next, ws);
39
+ return terminal(ctx, ws, app, next.key.split('/').at(-1), project.remote ?? project.folder, values, undefined, next.worktree_path, { id: next.id, session: next.harness_session_id });
40
+ }
41
+ await syncAgentCredentials(ctx.cloud(), { home: ctx.io.env.HOME, redact: value => ctx.addSecret(value) });
42
+ const signedIn = app === 'claude' ? await cloudClaudeLogin(ctx) : true;
43
+ if (!signedIn)
44
+ requireClaudeTerminal(ctx);
45
+ let created;
46
+ try {
47
+ created = await createConversation(ctx, project.folder, app, 'terminal');
48
+ }
49
+ catch (e) {
50
+ if (!conversationsUnavailable(e))
51
+ throw e;
52
+ // Optional migration: preserve the native terminal on the old control plane, without reviving main sync.
53
+ const name = `c-${randomUUID().slice(0, 12)}`;
54
+ const prepared = await prepareProjectTemplate(ctx, project, undefined);
55
+ const ws = await ctx.cloud().workspaces.open({ ...agentReachability(values), key: `${project.slug}/${name}`, template: prepared.template,
56
+ labels: { ...projectLabels(project, name, app), ...prepared.labels, repo_path: projectWorkspacePath(project) },
57
+ wait: { timeoutMs: ctx.wakeTimeoutMs, signal: ctx.signal } });
58
+ await seedAgentRepository(ctx, ws, project.folder, { populate: true });
59
+ if (!signedIn)
60
+ await loginClaudeInWorkspace(ctx, ws);
61
+ if (!(await savedMachine(ctx))?.template)
62
+ await syncAgentConfig(ctx, ws);
63
+ return terminal(ctx, ws, app, name, project.remote ?? project.folder, values, prepared.setupHash);
64
+ }
65
+ const { conversation: c, workspace: ws } = created;
66
+ if (values.reachable !== undefined)
67
+ await ctx.cloud().workspaces.open({ key: ws.key, ...agentReachability(values), wait: false });
68
+ if (!signedIn)
69
+ await loginClaudeInWorkspace(ctx, ws);
70
+ if (!(await savedMachine(ctx))?.template)
71
+ await syncAgentConfig(ctx, ws);
72
+ await ensureMenubar(ctx.store.dir, ctx.apiUrl, new MenubarManager({ home: ctx.io.env.HOME, signal: ctx.signal }));
73
+ return terminal(ctx, ws, app, c.key.split('/').at(-1), project.remote ?? project.folder, values, undefined, c.worktree_path, { id: c.id, session: c.harness_session_id });
74
+ };
@@ -0,0 +1,4 @@
1
+ import type { Ctx } from './commands.js';
2
+ import type { Conversation } from './conversations.js';
3
+ export declare function checkConversationWorktree(ctx: Pick<Ctx, 'store' | 'apiUrl'>, c: Conversation, folder: string, root: string): void;
4
+ export declare function rememberConversationWorktree(ctx: Pick<Ctx, 'store' | 'apiUrl'>, c: Conversation, folder: string, root: string): void;
@@ -0,0 +1,22 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { writePrivateFile } from "./credentials.js";
5
+ import { UsageError } from "./errors.js";
6
+ function identity(ctx, c, folder, root) {
7
+ const owner = { api_url: ctx.apiUrl, project_id: c.project_id, conversation_id: c.id, folder: realpathSync(folder), root };
8
+ const key = createHash('sha256').update(owner.folder).update(root).digest('hex');
9
+ return { owner, file: join(ctx.store.dir, 'conversation-worktrees', `${key}.json`) };
10
+ }
11
+ export function checkConversationWorktree(ctx, c, folder, root) {
12
+ const { owner, file } = identity(ctx, c, folder, root);
13
+ if (lstatSync(root).isSymbolicLink() || !existsSync(file) || lstatSync(file).isSymbolicLink() || (lstatSync(file).mode & 0o077))
14
+ throw new UsageError('The local conversation worktree has no matching private ownership record.');
15
+ const saved = JSON.parse(readFileSync(file, 'utf8'));
16
+ if (Object.entries(owner).some(([key, value]) => saved[key] !== value))
17
+ throw new UsageError('The local conversation worktree belongs to another conversation or API profile.');
18
+ }
19
+ export function rememberConversationWorktree(ctx, c, folder, root) {
20
+ const { owner, file } = identity(ctx, c, folder, root);
21
+ writePrivateFile(dirname(file), file, JSON.stringify(owner) + '\n');
22
+ }
@@ -0,0 +1,53 @@
1
+ import type { Workspace } from '@shardflux/sdk';
2
+ import type { Ctx } from './commands.js';
3
+ export type Harness = 'claude' | 'codex';
4
+ export interface Conversation {
5
+ id: string;
6
+ project_id: string;
7
+ key: string;
8
+ title: string;
9
+ summary: string;
10
+ harness: Harness;
11
+ harness_session_id: string | null;
12
+ route: 'terminal' | 'acp' | 'codex-app' | 'claude-desktop' | 't3-ssh';
13
+ workspace_id: string;
14
+ worktree_path: string;
15
+ branch: string;
16
+ base_commit: string;
17
+ state: 'working' | 'needs_you' | 'unresolved' | 'done' | 'archived';
18
+ created_at: string;
19
+ ended_at: string | null;
20
+ last_activity_at?: string;
21
+ durable?: {
22
+ branch_sha: string;
23
+ transcript_bytes: number;
24
+ branch_in_hub: boolean;
25
+ transcript_in_project_space: boolean;
26
+ } | null;
27
+ ports?: number[];
28
+ restore?: Record<string, unknown> | null;
29
+ }
30
+ export interface LocalConversation {
31
+ api_url: string;
32
+ folder: string;
33
+ project_id: string;
34
+ conversation: Conversation;
35
+ handed_off?: boolean;
36
+ }
37
+ export declare const conversationName: (c: Conversation) => string;
38
+ export declare function validateConversation(c: Conversation): Conversation;
39
+ export declare function localConversations(ctx: Pick<Ctx, 'store' | 'apiUrl'>): Record<string, LocalConversation>;
40
+ export declare function rememberConversation(ctx: Pick<Ctx, 'store' | 'apiUrl'>, c: Conversation, folder: string, handedOff?: boolean): void;
41
+ export declare function conversationsUnavailable(e: unknown): boolean;
42
+ export declare function getConversation(ctx: Ctx, id: string): Promise<Conversation>;
43
+ export declare function listConversations(ctx: Ctx, projectId?: string): Promise<Conversation[]>;
44
+ export declare function resolveConversation(ctx: Ctx, ref: string, folder?: string): Promise<Conversation>;
45
+ /** Re-resolve the binding on every attach: a move changes the VM, never the conversation identity. */
46
+ export declare function conversationWorkspace(ctx: Ctx, c: Conversation): Promise<Workspace>;
47
+ export declare function restoreConversationBinding(ctx: Ctx, c: Conversation, ws: Workspace): Promise<void>;
48
+ export declare function createConversationRecord(ctx: Ctx, folder: string, harness: Harness, route: Conversation['route'], name?: string): Promise<Conversation>;
49
+ export declare function createConversation(ctx: Ctx, folder: string, harness: Harness, route: Conversation['route'], name?: string): Promise<{
50
+ conversation: Conversation;
51
+ workspace: Workspace;
52
+ }>;
53
+ export declare function resumeConversation(ctx: Ctx, c: Conversation): Promise<Conversation>;
@@ -0,0 +1,118 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { ShardfluxApiError } from '@shardflux/sdk';
3
+ import { AgentStateStore } from "./agent-state.js";
4
+ import { gitRunner } from "./agent-git.js";
5
+ import { projectIdentity } from "./project-identity.js";
6
+ import { seedAgentRepository } from "./agent-repository.js";
7
+ import { UsageError } from "./errors.js";
8
+ import { conversationClient } from "./conversation-commands.js";
9
+ export const conversationName = (c) => {
10
+ const name = c.key.split('/').at(-1);
11
+ if (!/^[a-z0-9][a-z0-9-]{0,47}$/.test(name) || c.branch !== `shard/${name}`)
12
+ throw new Error('Conversation returned an invalid branch or name');
13
+ return name;
14
+ };
15
+ export function validateConversation(c) {
16
+ if (!c || !/^conv_[a-z0-9]{26}$/.test(c.id) || !c.project_id || !c.workspace_id || !c.worktree_path?.startsWith('/home/user/') || /[\0\r\n]/.test(c.worktree_path) || !['claude', 'codex'].includes(c.harness))
17
+ throw new Error('Conversation response omitted its identity or binding');
18
+ conversationName(c);
19
+ return c;
20
+ }
21
+ export function localConversations(ctx) {
22
+ const records = new AgentStateStore(ctx.store.dir).read().conversations ?? {};
23
+ return Object.fromEntries(Object.entries(records).filter(([, r]) => r.api_url === ctx.apiUrl));
24
+ }
25
+ export function rememberConversation(ctx, c, folder, handedOff = false) {
26
+ validateConversation(c);
27
+ new AgentStateStore(ctx.store.dir).update(doc => {
28
+ const records = doc.conversations ?? {};
29
+ records[c.id] = { api_url: ctx.apiUrl, folder, project_id: c.project_id, conversation: c, ...(handedOff ? { handed_off: true } : {}) };
30
+ doc.conversations = records;
31
+ });
32
+ }
33
+ export function conversationsUnavailable(e) {
34
+ return e instanceof ShardfluxApiError && ((e.status === 503 && e.details?.reason === 'conversations_unavailable') || (e.status === 404 && !e.details?.reason));
35
+ }
36
+ export async function getConversation(ctx, id) {
37
+ const data = await conversationClient(ctx).conversations.get(id);
38
+ const c = { ...data, ports: data.card.ports };
39
+ return validateConversation(c);
40
+ }
41
+ export async function listConversations(ctx, projectId) {
42
+ const all = [];
43
+ let cursor;
44
+ do {
45
+ const page = await conversationClient(ctx).conversations.list({ ...(projectId ? { projectId } : {}), limit: 200, ...(cursor ? { cursor } : {}) });
46
+ all.push(...page.data.map(validateConversation));
47
+ cursor = page.nextCursor ?? undefined;
48
+ } while (cursor);
49
+ return all;
50
+ }
51
+ export async function resolveConversation(ctx, ref, folder) {
52
+ if (ref.startsWith('conv_'))
53
+ return getConversation(ctx, ref);
54
+ const matches = (await listConversations(ctx)).filter(c => c.key === ref || conversationName(c) === ref);
55
+ const records = localConversations(ctx);
56
+ const scoped = folder ? matches.filter(c => records[c.id]?.folder === projectIdentity(folder).folder) : matches;
57
+ if (scoped.length !== 1)
58
+ throw new UsageError(scoped.length ? 'Use the full conversation key or id.' : 'Conversation not found.');
59
+ return scoped[0];
60
+ }
61
+ /** Re-resolve the binding on every attach: a move changes the VM, never the conversation identity. */
62
+ export async function conversationWorkspace(ctx, c) {
63
+ const ws = await conversationClient(ctx).workspaces.get(c.workspace_id);
64
+ if (ws.data.deleted_at)
65
+ throw new UsageError('This conversation is saved. Resume it to continue.');
66
+ if (!ws.ready)
67
+ await ws.wake({ timeoutMs: ctx.wakeTimeoutMs, signal: ctx.signal });
68
+ return ws;
69
+ }
70
+ export async function restoreConversationBinding(ctx, c, ws) {
71
+ if (!c.restore || c.restore.kind === 'new')
72
+ return;
73
+ const cell = ws.cell();
74
+ if (c.restore.kind === 'join' && c.restore.read_only === true) {
75
+ const review = await cell.exec.run(['sf-agent', 'conversation', 'restore', '--args', '{"start":false}'], { cwd: c.worktree_path, env: { SF_CONVERSATION_ID: c.id }, timeoutMs: 120_000, signal: ctx.signal });
76
+ if (review.exitCode !== 0)
77
+ throw new Error(`Reviewer restore failed: ${review.stderr.trim()}`);
78
+ return;
79
+ }
80
+ await cell.files.mkdir(c.worktree_path, { parents: true });
81
+ const init = await cell.exec.run(['git', '-C', c.worktree_path, 'init'], { timeoutMs: 30_000, signal: ctx.signal });
82
+ if (init.exitCode !== 0)
83
+ throw new Error(`Conversation restore could not initialize its repository: ${init.stderr.trim()}`);
84
+ const result = await cell.exec.run(['sf-agent', 'conversation', 'restore', '--args', '{"start":false}'], { cwd: c.worktree_path, env: { SF_CONVERSATION_ID: c.id }, timeoutMs: 120_000, signal: ctx.signal });
85
+ if (result.exitCode !== 0)
86
+ throw new Error(`Conversation restore failed: ${result.stderr.trim()}`);
87
+ }
88
+ export async function createConversationRecord(ctx, folder, harness, route, name) {
89
+ const project = projectIdentity(folder);
90
+ // Detect the optional control-plane feature before applying the new Git contract to old APIs.
91
+ await conversationClient(ctx).conversations.list({ limit: 1 });
92
+ const git = gitRunner(project.folder, { ...process.env, ...ctx.io.env }, ctx.signal);
93
+ const commit = await git(['rev-parse', 'HEAD']);
94
+ const branch = await git(['symbolic-ref', '--quiet', '--short', 'HEAD']).catch(() => undefined);
95
+ const response = await conversationClient(ctx).conversations.create({ repoRemote: project.remote ?? `file://${project.folder}`, harness, route, mode: 'new',
96
+ base: { commit, ...(branch ? { branch } : {}) }, ...(name ? { name } : {}), projectId: ctx.store.profile(ctx.apiUrl).project_id }, { idempotencyKey: randomUUID(), signal: ctx.signal });
97
+ const c = validateConversation({ ...response.conversation, restore: response.restore });
98
+ rememberConversation(ctx, c, project.folder);
99
+ return c;
100
+ }
101
+ export async function createConversation(ctx, folder, harness, route, name) {
102
+ const project = projectIdentity(folder);
103
+ const c = await createConversationRecord(ctx, project.folder, harness, route, name);
104
+ const ws = await conversationWorkspace(ctx, c);
105
+ // This is a one-time seed. A conversation never watches or writes the owner's checkout.
106
+ await seedAgentRepository(ctx, ws, project.folder, { root: c.worktree_path, populate: true });
107
+ const cell = ws.cell();
108
+ const result = await cell.exec.run(['git', '-C', c.worktree_path, 'checkout', '-B', c.branch, c.base_commit], { timeoutMs: 30_000, signal: ctx.signal });
109
+ if (result.exitCode !== 0)
110
+ throw new Error(`Conversation branch could not be prepared: ${result.stderr.trim()}`);
111
+ return { conversation: c, workspace: ws };
112
+ }
113
+ export async function resumeConversation(ctx, c) {
114
+ if (c.state !== 'done' && c.state !== 'archived')
115
+ return getConversation(ctx, c.id);
116
+ const response = await conversationClient(ctx).conversations.resume(c.id, { idempotencyKey: randomUUID(), signal: ctx.signal });
117
+ return validateConversation({ ...response.conversation, restore: response.restore });
118
+ }
@@ -0,0 +1,80 @@
1
+ import type { CellClient, Workspace } from '@shardflux/sdk';
2
+ import type { Ctx } from './commands.js';
3
+ import type { Conversation } from './conversations.js';
4
+ export type DeviceChoice = 'once' | 'always' | 'deny';
5
+ export interface DeviceRequest {
6
+ id: string;
7
+ project_id: string;
8
+ conversation_id: string;
9
+ state: string;
10
+ device_id: string | null;
11
+ kind: 'exec' | 'files_pull' | 'files_push' | 'port_forward' | 'port_reverse' | 'try_locally';
12
+ exec?: {
13
+ argv: string[];
14
+ cwd_relative?: string;
15
+ timeout_seconds?: number;
16
+ };
17
+ files?: {
18
+ local_path_relative: string;
19
+ remote_path: string;
20
+ max_bytes?: number;
21
+ };
22
+ port?: {
23
+ remote_port: number;
24
+ local_port: number;
25
+ };
26
+ }
27
+ export interface DeviceApproval {
28
+ type: 'approval';
29
+ request_id: string;
30
+ project_id: string;
31
+ conversation_id: string;
32
+ summary: string;
33
+ outside: boolean;
34
+ }
35
+ type Result = {
36
+ state: 'approved' | 'denied' | 'running' | 'done' | 'failed';
37
+ exit_code?: number;
38
+ stdout_b64?: string;
39
+ stderr_b64?: string;
40
+ files_artifact?: string;
41
+ };
42
+ /** Resolve every existing ancestor, including symlinks. Missing leaf files do not hide an outside parent. */
43
+ export declare function devicePath(root: string, requested?: string): {
44
+ path: string;
45
+ outside: boolean;
46
+ };
47
+ export declare class DeviceGrants {
48
+ readonly file: string;
49
+ constructor(dir: string);
50
+ key(api: string, project: string): string;
51
+ read(): Record<string, {
52
+ root: string;
53
+ kinds: string[];
54
+ }>;
55
+ allows(api: string, project: string, root: string, kind: string, outside: boolean): boolean;
56
+ save(api: string, project: string, root: string, kind: string): Promise<void>;
57
+ }
58
+ export declare function deviceSummary(r: DeviceRequest): string;
59
+ export declare function tryLocallyAction(r: DeviceRequest): 'start' | 'stop' | null;
60
+ export declare function ensureConversationWorktree(ctx: Ctx, c: Conversation, folder: string, ws?: Workspace): Promise<string>;
61
+ export declare function deviceExec(argv: string[], cwd: string, timeoutSeconds?: number, signal?: AbortSignal): Promise<Result>;
62
+ export declare class DeviceExecutor {
63
+ #private;
64
+ constructor(ctx: Ctx, ask: (approval: DeviceApproval) => Promise<DeviceChoice>, deviceId: string);
65
+ result(r: DeviceRequest, result: Result): Promise<void>;
66
+ execute(r: DeviceRequest): Promise<void>;
67
+ reap(): Promise<void>;
68
+ close(): Promise<void>;
69
+ }
70
+ export declare function devicePort(ws: Workspace, cell: CellClient, kind: 'port_forward' | 'port_reverse', ports: {
71
+ local_port: number;
72
+ remote_port: number;
73
+ }, signal: AbortSignal): Promise<{
74
+ stop(): Promise<void>;
75
+ }>;
76
+ export declare function serveAcpDeviceApprovals(dir: string, id: string, ask: (a: DeviceApproval) => Promise<DeviceChoice>): Promise<{
77
+ stop(): Promise<void>;
78
+ }>;
79
+ export declare function askConnectedAcp(dir: string, approval: DeviceApproval, signal: AbortSignal): Promise<DeviceChoice | null>;
80
+ export {};