@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.
- package/CHANGELOG.md +28 -0
- package/README.md +40 -4
- package/dist/acp-catalog.d.ts +152 -0
- package/dist/acp-catalog.js +287 -0
- package/dist/acp-journal.d.ts +18 -0
- package/dist/acp-journal.js +232 -0
- package/dist/acp-rpc.d.ts +41 -0
- package/dist/acp-rpc.js +115 -0
- package/dist/acp.d.ts +27 -0
- package/dist/acp.js +290 -0
- package/dist/agent-commands.d.ts +10 -2
- package/dist/agent-commands.js +37 -18
- package/dist/agent-machine.d.ts +6 -0
- package/dist/agent-machine.js +93 -0
- package/dist/agent-repository.js +1 -1
- package/dist/agent-state.d.ts +3 -0
- package/dist/agent-state.js +33 -12
- package/dist/agent-target-commands.d.ts +4 -1
- package/dist/agent-target-commands.js +64 -22
- package/dist/agent-targets.d.ts +6 -4
- package/dist/agent-targets.js +72 -31
- package/dist/args.d.ts +1 -1
- package/dist/args.js +53 -14
- package/dist/command-center-actions.js +26 -3
- package/dist/command-center.js +20 -4
- package/dist/conversation-commands.d.ts +3 -0
- package/dist/conversation-commands.js +36 -0
- package/dist/conversation-local-resume.d.ts +8 -0
- package/dist/conversation-local-resume.js +56 -0
- package/dist/conversation-local.d.ts +7 -0
- package/dist/conversation-local.js +61 -0
- package/dist/conversation-router.d.ts +12 -0
- package/dist/conversation-router.js +89 -0
- package/dist/conversation-stash.d.ts +11 -0
- package/dist/conversation-stash.js +158 -0
- package/dist/conversation-terminal.d.ts +3 -0
- package/dist/conversation-terminal.js +74 -0
- package/dist/conversation-worktree.d.ts +4 -0
- package/dist/conversation-worktree.js +22 -0
- package/dist/conversations.d.ts +53 -0
- package/dist/conversations.js +118 -0
- package/dist/device-bridge.d.ts +80 -0
- package/dist/device-bridge.js +398 -0
- package/dist/device-helper.d.ts +3 -0
- package/dist/device-helper.js +104 -0
- package/dist/errors.js +5 -0
- package/dist/format.js +3 -2
- package/dist/handlers.js +3 -1
- package/dist/main.js +15 -3
- package/dist/project-sync.d.ts +5 -1
- package/dist/project-sync.js +7 -6
- package/dist/t3-provider.d.ts +6 -0
- package/dist/t3-provider.js +99 -0
- package/dist/t3-runtime.d.ts +6 -0
- package/dist/t3-runtime.js +63 -0
- package/dist/target-lock.d.ts +3 -0
- package/dist/target-lock.js +143 -0
- package/package.json +3 -3
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Every shard invocation registers the one installed T3 provider without adding terminal noise. */
|
|
2
|
+
export declare function registerInstalledT3(env: Record<string, string | undefined>, dir: string, signal?: AbortSignal): Promise<void>;
|
|
3
|
+
/** A version number alone is insufficient: the installed 0.0.45 desktop lacks the upstream ACP driver. */
|
|
4
|
+
export declare function t3BundleSupportsAcp(app: string): boolean;
|
|
5
|
+
/** Desktop 0.0.45 watches this server settings file, including symlink targets. No encrypted stores. */
|
|
6
|
+
export declare function configureT3Provider(env: Record<string, string | undefined>, dir: string, remove?: boolean, signal?: AbortSignal): Promise<boolean>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readFileSync, realpathSync, openSync, readSync, closeSync, statSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { writePrivateFile } from "./credentials.js";
|
|
5
|
+
import { withTargetLock } from "./target-lock.js";
|
|
6
|
+
import { installedTargets } from "./agent-targets.js";
|
|
7
|
+
const ID = 'shardflux';
|
|
8
|
+
const provider = { driver: 'acpRegistry', displayName: 'Shardflux', enabled: true, config: { source: 'local', commandPath: 'shard', commandArgs: ['acp'] } };
|
|
9
|
+
const support = new Map();
|
|
10
|
+
/** Every shard invocation registers the one installed T3 provider without adding terminal noise. */
|
|
11
|
+
export async function registerInstalledT3(env, dir, signal) {
|
|
12
|
+
if (process.platform !== 'darwin')
|
|
13
|
+
return;
|
|
14
|
+
if (!installedTargets(env).targets.includes('t3'))
|
|
15
|
+
return;
|
|
16
|
+
try {
|
|
17
|
+
await configureT3Provider(env, dir, false, signal);
|
|
18
|
+
}
|
|
19
|
+
catch { /* User-owned malformed or conflicting settings stay intact. */ }
|
|
20
|
+
}
|
|
21
|
+
/** A version number alone is insufficient: the installed 0.0.45 desktop lacks the upstream ACP driver. */
|
|
22
|
+
export function t3BundleSupportsAcp(app) {
|
|
23
|
+
const archive = join(app, 'Contents/Resources/app.asar');
|
|
24
|
+
let fd;
|
|
25
|
+
try {
|
|
26
|
+
const stat = statSync(archive), stamp = `${stat.size}:${stat.mtimeMs}`;
|
|
27
|
+
if (support.get(archive)?.stamp === stamp)
|
|
28
|
+
return support.get(archive).capable;
|
|
29
|
+
fd = openSync(archive, 'r');
|
|
30
|
+
const sizes = Buffer.alloc(16);
|
|
31
|
+
if (readSync(fd, sizes, 0, 16, 0) !== 16)
|
|
32
|
+
return false;
|
|
33
|
+
const headerSize = sizes.readUInt32LE(12);
|
|
34
|
+
if (headerSize < 1 || headerSize > 16 << 20)
|
|
35
|
+
return false;
|
|
36
|
+
const header = Buffer.alloc(headerSize);
|
|
37
|
+
if (readSync(fd, header, 0, headerSize, 16) !== headerSize)
|
|
38
|
+
return false;
|
|
39
|
+
let entry = JSON.parse(header.toString());
|
|
40
|
+
for (const part of ['apps', 'server', 'dist', 'bin.mjs']) {
|
|
41
|
+
const child = entry.files?.[part];
|
|
42
|
+
if (!child)
|
|
43
|
+
return false;
|
|
44
|
+
entry = child;
|
|
45
|
+
}
|
|
46
|
+
const size = entry.size, offset = Number(entry.offset) + 8 + sizes.readUInt32LE(4);
|
|
47
|
+
if (!Number.isSafeInteger(size) || !size || size < 1 || size > 64 << 20 || !Number.isSafeInteger(offset) || offset < 16 || offset + size > stat.size)
|
|
48
|
+
return false;
|
|
49
|
+
const bytes = entry.unpacked ? readFileSync(join(`${archive}.unpacked`, 'apps/server/dist/bin.mjs')) : Buffer.alloc(size);
|
|
50
|
+
if (!entry.unpacked && readSync(fd, bytes, 0, size, offset) !== size)
|
|
51
|
+
return false;
|
|
52
|
+
const capable = /ProviderDriverKind\.make\(["']acpRegistry["']\)/.test(bytes.toString());
|
|
53
|
+
support.set(archive, { stamp, capable });
|
|
54
|
+
return capable;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
if (fd !== undefined)
|
|
61
|
+
closeSync(fd);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Desktop 0.0.45 watches this server settings file, including symlink targets. No encrypted stores. */
|
|
65
|
+
export async function configureT3Provider(env, dir, remove = false, signal) {
|
|
66
|
+
return withTargetLock(dir, () => {
|
|
67
|
+
let file = join(env.T3CODE_HOME || join(env.HOME || homedir(), '.t3'), 'userdata', 'settings.json');
|
|
68
|
+
let raw = '{}';
|
|
69
|
+
try {
|
|
70
|
+
file = realpathSync(file);
|
|
71
|
+
raw = readFileSync(file, 'utf8');
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error.code !== 'ENOENT')
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
const parsed = JSON.parse(raw);
|
|
78
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
79
|
+
throw new Error('T3 settings must be an object');
|
|
80
|
+
const doc = parsed;
|
|
81
|
+
if (doc.providerInstances !== undefined && (!doc.providerInstances || typeof doc.providerInstances !== 'object' || Array.isArray(doc.providerInstances)))
|
|
82
|
+
throw new Error('T3 providers must be an object');
|
|
83
|
+
const instances = doc.providerInstances ?? {};
|
|
84
|
+
const existing = instances[ID];
|
|
85
|
+
// A user-owned instance with this ID is never silently replaced or removed.
|
|
86
|
+
if (existing && !(existing.driver === provider.driver && existing.config?.source === 'local' && existing.config.commandPath === 'shard' && JSON.stringify(existing.config.commandArgs) === '["acp"]'))
|
|
87
|
+
throw new Error('T3 already has a different provider named shardflux');
|
|
88
|
+
const desired = existing ? { ...existing, config: { ...existing.config, ...provider.config } } : provider;
|
|
89
|
+
if (remove ? !existing : JSON.stringify(existing) === JSON.stringify(desired))
|
|
90
|
+
return false;
|
|
91
|
+
if (remove)
|
|
92
|
+
delete instances[ID];
|
|
93
|
+
else
|
|
94
|
+
instances[ID] = desired;
|
|
95
|
+
doc.providerInstances = instances;
|
|
96
|
+
writePrivateFile(dirname(file), file, JSON.stringify(doc, null, 2) + '\n');
|
|
97
|
+
return true;
|
|
98
|
+
}, signal);
|
|
99
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Workspace } from '@shardflux/sdk';
|
|
2
|
+
import type { Ctx } from './commands.js';
|
|
3
|
+
/** T3 v0.0.45 packages/ssh/src/tunnel.ts: identical cache, archive, checksum and completion marker. */
|
|
4
|
+
export declare function t3InstallScript(version: string): string;
|
|
5
|
+
/** Start a supervised guest exec session; only launch is awaited, never download or extraction. */
|
|
6
|
+
export declare function prewarmT3(ctx: Ctx, ws: Workspace, version: string): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** T3 v0.0.45 packages/ssh/src/tunnel.ts: identical cache, archive, checksum and completion marker. */
|
|
2
|
+
export function t3InstallScript(version) {
|
|
3
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version))
|
|
4
|
+
throw new Error('T3 requires an exact release version');
|
|
5
|
+
return `set -eu
|
|
6
|
+
T3_ARCHIVE_VERSION='${version}'
|
|
7
|
+
T3_RUNTIME_DIR="$HOME/.t3/runtime/versions/$T3_ARCHIVE_VERSION"
|
|
8
|
+
t3_runtime_ready() {
|
|
9
|
+
[ -x "$T3_RUNTIME_DIR/t3" ] && [ "$(cat "$T3_RUNTIME_DIR/.install-complete" 2>/dev/null)" = "$T3_ARCHIVE_VERSION" ]
|
|
10
|
+
}
|
|
11
|
+
t3_runtime_ready && exit 0
|
|
12
|
+
mkdir -p "$HOME/.t3/runtime/versions"
|
|
13
|
+
T3_LOCK="$HOME/.t3/runtime/versions/.$T3_ARCHIVE_VERSION.install.lock"
|
|
14
|
+
# A prewarm never waits for T3 or another prewarm. T3's own launcher joins a live installer.
|
|
15
|
+
if ! mkdir "$T3_LOCK" 2>/dev/null; then
|
|
16
|
+
T3_LOCK_OWNER="$(cat "$T3_LOCK/pid" 2>/dev/null || true)"
|
|
17
|
+
case "$T3_LOCK_OWNER" in ''|*[!0-9]*) exit 0 ;; esac
|
|
18
|
+
if kill -0 "$T3_LOCK_OWNER" 2>/dev/null; then exit 0; fi
|
|
19
|
+
rm -rf "$T3_LOCK"
|
|
20
|
+
mkdir "$T3_LOCK" 2>/dev/null || exit 0
|
|
21
|
+
fi
|
|
22
|
+
trap 'rm -rf "$T3_LOCK"' EXIT
|
|
23
|
+
printf '%s\\n' "$$" > "$T3_LOCK/pid.tmp"
|
|
24
|
+
mv "$T3_LOCK/pid.tmp" "$T3_LOCK/pid"
|
|
25
|
+
t3_runtime_ready && exit 0
|
|
26
|
+
case "$(uname -s)" in Linux) T3_PLATFORM=linux ;; Darwin) T3_PLATFORM=darwin ;; *) exit 1 ;; esac
|
|
27
|
+
case "$(uname -m)" in arm64|aarch64) T3_ARCH=arm64 ;; x86_64|amd64) T3_ARCH=x64 ;; *) exit 1 ;; esac
|
|
28
|
+
T3_ARCHIVE="t3-$T3_ARCHIVE_VERSION-$T3_PLATFORM-$T3_ARCH.tar.gz"
|
|
29
|
+
T3_STAGING="$(mktemp -d "$HOME/.t3/runtime/versions/.staging-XXXXXX")"
|
|
30
|
+
trap 'rm -rf "$T3_STAGING" "$T3_LOCK"' EXIT
|
|
31
|
+
T3_RELEASE_BASE_URL=https://github.com/pingdotgg/t3code/releases/download
|
|
32
|
+
t3_fetch() {
|
|
33
|
+
if command -v curl >/dev/null 2>&1; then curl -fsSL --connect-timeout 30 --max-time "$3" "$1" -o "$2"
|
|
34
|
+
elif command -v wget >/dev/null 2>&1; then wget -q --timeout=30 --tries=1 "$1" -O "$2"
|
|
35
|
+
else exit 1
|
|
36
|
+
fi
|
|
37
|
+
}
|
|
38
|
+
t3_fetch "$T3_RELEASE_BASE_URL/v$T3_ARCHIVE_VERSION/SHA256SUMS" "$T3_STAGING/SHA256SUMS" 30
|
|
39
|
+
t3_fetch "$T3_RELEASE_BASE_URL/v$T3_ARCHIVE_VERSION/$T3_ARCHIVE" "$T3_STAGING/$T3_ARCHIVE" 240
|
|
40
|
+
T3_EXPECTED="$(grep " \\*\\{0,1\\}$T3_ARCHIVE$" "$T3_STAGING/SHA256SUMS" | cut -d' ' -f1)"
|
|
41
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
42
|
+
T3_ACTUAL="$(sha256sum "$T3_STAGING/$T3_ARCHIVE" | cut -d' ' -f1)"
|
|
43
|
+
else
|
|
44
|
+
T3_ACTUAL="$(shasum -a 256 "$T3_STAGING/$T3_ARCHIVE" | cut -d' ' -f1)"
|
|
45
|
+
fi
|
|
46
|
+
if [ -z "$T3_EXPECTED" ] || [ "$T3_ACTUAL" != "$T3_EXPECTED" ]; then
|
|
47
|
+
printf 'Checksum mismatch for %s.\\n' "$T3_ARCHIVE" >&2; exit 1
|
|
48
|
+
fi
|
|
49
|
+
tar -xzf "$T3_STAGING/$T3_ARCHIVE" -C "$T3_STAGING" --strip-components=1
|
|
50
|
+
rm -f "$T3_STAGING/$T3_ARCHIVE" "$T3_STAGING/SHA256SUMS"
|
|
51
|
+
"$T3_STAGING/t3" --version >/dev/null 2>&1
|
|
52
|
+
printf '%s\\n' "$T3_ARCHIVE_VERSION" > "$T3_STAGING/.install-complete"
|
|
53
|
+
rm -rf "$T3_RUNTIME_DIR"
|
|
54
|
+
mv "$T3_STAGING" "$T3_RUNTIME_DIR"
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
/** Start a supervised guest exec session; only launch is awaited, never download or extraction. */
|
|
58
|
+
export async function prewarmT3(ctx, ws, version) {
|
|
59
|
+
const cell = ws.cell({ agentLabel: ctx.agentLabel, transitionTimeoutMs: ctx.wakeTimeoutMs });
|
|
60
|
+
const session = await cell.exec.start({ argv: ['sh', '-c', t3InstallScript(version)], user: 'user', timeout_ms: 300_000 }, ctx.signal);
|
|
61
|
+
if (session.state === 'failed_to_start')
|
|
62
|
+
throw new Error('T3 runtime prewarm could not start');
|
|
63
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { once } from 'node:events';
|
|
3
|
+
import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, rmdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { createConnection, createServer } from 'node:net';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
const missing = (e) => e.code === 'ENOENT';
|
|
7
|
+
function alive(pid) {
|
|
8
|
+
try {
|
|
9
|
+
process.kill(pid, 0);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
if (e.code === 'ESRCH')
|
|
14
|
+
return false;
|
|
15
|
+
throw e;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function removeOwner(lock, name) {
|
|
19
|
+
// A contender can only remove this owner's unique record. rmdir is atomic
|
|
20
|
+
// and refuses a replacement owner's nonempty directory.
|
|
21
|
+
rmSync(join(lock, name), { force: true });
|
|
22
|
+
try {
|
|
23
|
+
rmdirSync(lock);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
if (!missing(e) && !['ENOTEMPTY', 'EEXIST'].includes(e.code ?? ''))
|
|
27
|
+
throw e;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function waitForOwner(owner, signal) {
|
|
31
|
+
signal?.throwIfAborted();
|
|
32
|
+
await new Promise((resolve, reject) => {
|
|
33
|
+
const socket = createConnection({ host: '127.0.0.1', port: owner.port });
|
|
34
|
+
let identity = '';
|
|
35
|
+
const finish = (error) => {
|
|
36
|
+
signal?.removeEventListener('abort', abort);
|
|
37
|
+
socket.destroy();
|
|
38
|
+
if (error)
|
|
39
|
+
reject(error instanceof Error ? error : new Error('target update aborted', { cause: error }));
|
|
40
|
+
else
|
|
41
|
+
resolve();
|
|
42
|
+
};
|
|
43
|
+
const abort = () => finish(signal?.reason ?? new Error('target update aborted'));
|
|
44
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
45
|
+
socket.on('data', bytes => {
|
|
46
|
+
identity += String(bytes);
|
|
47
|
+
if (!`${owner.token}\n`.startsWith(identity))
|
|
48
|
+
finish(new Error('target lock owner identity changed'));
|
|
49
|
+
});
|
|
50
|
+
socket.once('end', () => finish());
|
|
51
|
+
socket.once('error', error => { if (['ECONNREFUSED', 'ECONNRESET'].includes(error.code ?? ''))
|
|
52
|
+
finish();
|
|
53
|
+
else
|
|
54
|
+
finish(error); });
|
|
55
|
+
if (signal?.aborted)
|
|
56
|
+
abort();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** PID ownership is published atomically. A kernel connection wakes waiters on
|
|
60
|
+
* release or process death, including SIGKILL; there is no polling or deadline. */
|
|
61
|
+
export async function withTargetLock(dir, work, signal) {
|
|
62
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
63
|
+
const token = randomUUID(), name = `owner-${token}.json`;
|
|
64
|
+
const lock = join(dir, 'targets.lock'), candidate = join(dir, `.targets-lock-${token}`);
|
|
65
|
+
const peers = new Set();
|
|
66
|
+
let released = false;
|
|
67
|
+
const server = createServer(socket => {
|
|
68
|
+
peers.add(socket);
|
|
69
|
+
socket.once('close', () => peers.delete(socket));
|
|
70
|
+
socket.on('error', () => socket.destroy());
|
|
71
|
+
socket.write(`${token}\n`);
|
|
72
|
+
if (released)
|
|
73
|
+
socket.end();
|
|
74
|
+
});
|
|
75
|
+
let acquired = false;
|
|
76
|
+
try {
|
|
77
|
+
server.listen(0, '127.0.0.1');
|
|
78
|
+
await once(server, 'listening');
|
|
79
|
+
mkdirSync(candidate, { mode: 0o700 });
|
|
80
|
+
writeFileSync(join(candidate, name), JSON.stringify({ pid: process.pid, port: server.address().port, token }), { mode: 0o600 });
|
|
81
|
+
while (!acquired) {
|
|
82
|
+
signal?.throwIfAborted();
|
|
83
|
+
try {
|
|
84
|
+
renameSync(candidate, lock);
|
|
85
|
+
acquired = true;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
if (!['EEXIST', 'ENOTEMPTY'].includes(e.code ?? ''))
|
|
89
|
+
throw e;
|
|
90
|
+
let names;
|
|
91
|
+
try {
|
|
92
|
+
names = readdirSync(lock);
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
if (missing(e))
|
|
96
|
+
continue;
|
|
97
|
+
throw e;
|
|
98
|
+
}
|
|
99
|
+
for (const record of names) {
|
|
100
|
+
if (!/^owner-[0-9a-f-]+\.json$/.test(record))
|
|
101
|
+
throw new Error('targets.lock has no recoverable owner record', { cause: e });
|
|
102
|
+
let owner;
|
|
103
|
+
try {
|
|
104
|
+
owner = JSON.parse(readFileSync(join(lock, record), 'utf8'));
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
if (missing(e))
|
|
108
|
+
continue;
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
if (!Number.isSafeInteger(owner.pid) || owner.pid <= 0 || !Number.isInteger(owner.port) || owner.port < 1 || owner.port > 65535 || record !== `owner-${owner.token}.json`)
|
|
112
|
+
throw new Error('invalid targets.lock owner', { cause: e });
|
|
113
|
+
if (alive(owner.pid))
|
|
114
|
+
await waitForOwner(owner, signal);
|
|
115
|
+
removeOwner(lock, record);
|
|
116
|
+
}
|
|
117
|
+
if (!names.length) {
|
|
118
|
+
try {
|
|
119
|
+
rmdirSync(lock);
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
if (!missing(e) && e.code !== 'ENOTEMPTY')
|
|
123
|
+
throw e;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
signal?.throwIfAborted();
|
|
129
|
+
return await work();
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
released = true;
|
|
133
|
+
if (acquired)
|
|
134
|
+
removeOwner(lock, name);
|
|
135
|
+
rmSync(candidate, { recursive: true, force: true });
|
|
136
|
+
const closed = new Promise(resolve => server.close(() => resolve()));
|
|
137
|
+
// A local peer need not answer our FIN. Do not let an unauthenticated
|
|
138
|
+
// half-open connection retain this process after its lock is released.
|
|
139
|
+
for (const peer of peers)
|
|
140
|
+
peer.destroy();
|
|
141
|
+
await closed;
|
|
142
|
+
}
|
|
143
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "shard: the Shardflux command line. Create an account, sign in, set up keys and billing, then open, run commands in, move files to, suspend, resume and fork serverless agent workspaces.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"smol-toml": "1.9.0",
|
|
48
48
|
"snappyjs": "0.7.0",
|
|
49
49
|
"yaml": "2.9.1",
|
|
50
|
-
"@shardflux/sdk": "^0.
|
|
50
|
+
"@shardflux/sdk": "^0.19.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@eslint/js": "10.0.1",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"typescript-eslint": "8.70.1"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@shardflux/menubar-darwin": "^0.
|
|
61
|
+
"@shardflux/menubar-darwin": "^0.2.0"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"build": "node scripts/build.mjs",
|