@sand.ai/vibable-cli 0.1.0 → 0.3.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/dist/args.d.ts +28 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +56 -0
- package/dist/args.js.map +1 -1
- package/dist/help.d.ts +2 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +127 -0
- package/dist/help.js.map +1 -0
- package/dist/hosting.d.ts +23 -0
- package/dist/hosting.d.ts.map +1 -0
- package/dist/hosting.js +486 -0
- package/dist/hosting.js.map +1 -0
- package/dist/index.js +90 -9
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.ts +8 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/server-sent-events.d.ts +9 -0
- package/dist/server-sent-events.d.ts.map +1 -0
- package/dist/server-sent-events.js +25 -0
- package/dist/server-sent-events.js.map +1 -0
- package/dist/sync.d.ts +19 -0
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +362 -21
- package/dist/sync.js.map +1 -1
- package/package.json +2 -3
package/dist/protocol.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,kBAAkB,CAAC;AACxD,eAAO,MAAM,qBAAqB,QAAkB,CAAC;AACrD,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,MAAM,MAAM,sBAAsB,GAC9B,QAAQ,GACR,cAAc,GACd,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,MAAM,uBAAuB,GAAG;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,sBAAsB,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GACD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpC,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAcF,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,WAUrD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,UAa/C"}
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,kBAAkB,CAAC;AACxD,eAAO,MAAM,qBAAqB,QAAkB,CAAC;AACrD,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,MAAM,MAAM,sBAAsB,GAC9B,QAAQ,GACR,cAAc,GACd,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,MAAM,uBAAuB,GAAG;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,sBAAsB,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,uBAAuB,CAAC;IACjC,YAAY,EAAE,qBAAqB,GAAG,IAAI,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GACD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpC,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAcF,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,WAUrD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,UAa/C"}
|
package/dist/protocol.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,wBAAwB,GAAG,eAAe,CAAC;AACxD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AACrD,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,wBAAwB,GAAG,eAAe,CAAC;AACxD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AACrD,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAkD9C,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IACpC,MAAM;IACN,UAAU;IACV,cAAc;IACd,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,OAAO;IACP,UAAU;CACX,CAAC,CAAC;AAEH,MAAM,UAAU,yBAAyB,CAAC,IAAY;IACpD,OAAO,IAAI;SACR,KAAK,CAAC,OAAO,CAAC;SACd,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CACH,CAAC,OAAO,EAAE,EAAE,CACV,qBAAqB,CAAC,GAAG,CAAC,OAAO,CAAC;QAClC,OAAO,KAAK,WAAW;QACvB,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAC7B,CAAC;AACN,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,IACE,IAAI,CAAC,MAAM,KAAK,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,IAAI;QAClB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QACnB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QACpB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAClC,yBAAyB,CAAC,IAAI,CAAC,EAC/B,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,4CAA4C,IAAI,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-sent-events.d.ts","sourceRoot":"","sources":["../src/server-sent-events.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG;IACvD,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB,CAOA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function extractServerSentEvents(source) {
|
|
2
|
+
const normalized = source.replace(/\r\n/gu, "\n");
|
|
3
|
+
const blocks = normalized.split("\n\n");
|
|
4
|
+
const remainder = blocks.pop() ?? "";
|
|
5
|
+
const events = blocks.flatMap(parseEventBlock);
|
|
6
|
+
return { events, remainder };
|
|
7
|
+
}
|
|
8
|
+
function parseEventBlock(block) {
|
|
9
|
+
let event = "message";
|
|
10
|
+
const data = [];
|
|
11
|
+
for (const line of block.split("\n")) {
|
|
12
|
+
if (line.startsWith(":"))
|
|
13
|
+
continue;
|
|
14
|
+
const separator = line.indexOf(":");
|
|
15
|
+
const field = separator < 0 ? line : line.slice(0, separator);
|
|
16
|
+
const rawValue = separator < 0 ? "" : line.slice(separator + 1);
|
|
17
|
+
const value = rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue;
|
|
18
|
+
if (field === "event")
|
|
19
|
+
event = value;
|
|
20
|
+
if (field === "data")
|
|
21
|
+
data.push(value);
|
|
22
|
+
}
|
|
23
|
+
return data.length > 0 ? [{ event, data: data.join("\n") }] : [];
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=server-sent-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-sent-events.js","sourceRoot":"","sources":["../src/server-sent-events.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,uBAAuB,CAAC,MAAc;IAIpD,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAE/C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC/B,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,IAAI,KAAK,GAAG,SAAS,CAAC;IACtB,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,KAAK,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QAChE,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACtE,IAAI,KAAK,KAAK,OAAO;YAAE,KAAK,GAAG,KAAK,CAAC;QACrC,IAAI,KAAK,KAAK,MAAM;YAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACnE,CAAC"}
|
package/dist/sync.d.ts
CHANGED
|
@@ -14,11 +14,18 @@ export type LocalEntry = {
|
|
|
14
14
|
hash: string;
|
|
15
15
|
executable: boolean;
|
|
16
16
|
};
|
|
17
|
+
type SyncDaemonState = {
|
|
18
|
+
pid: number;
|
|
19
|
+
status: "starting" | "running";
|
|
20
|
+
startedAt: string;
|
|
21
|
+
logFile: string;
|
|
22
|
+
};
|
|
17
23
|
export declare function connectAndWatch(input: {
|
|
18
24
|
apiBaseUrl: string;
|
|
19
25
|
sessionId: string;
|
|
20
26
|
token: string;
|
|
21
27
|
directory: string;
|
|
28
|
+
onConnected?: () => void | Promise<void>;
|
|
22
29
|
}): Promise<void>;
|
|
23
30
|
export declare function runRemoteCommand(input: {
|
|
24
31
|
apiBaseUrl: string;
|
|
@@ -28,12 +35,23 @@ export declare function runRemoteCommand(input: {
|
|
|
28
35
|
command: string[];
|
|
29
36
|
}): Promise<void>;
|
|
30
37
|
export declare function readLocalConfig(cwd?: string): Promise<LocalConfig>;
|
|
38
|
+
export declare function startSyncDaemon(cwd?: string): Promise<SyncDaemonState>;
|
|
39
|
+
export declare function isSyncDaemonRunning(cwd?: string): Promise<boolean>;
|
|
40
|
+
export declare function runSyncDaemon(root: string): Promise<void>;
|
|
41
|
+
export declare function showSyncDaemonStatus(cwd?: string): Promise<void>;
|
|
42
|
+
export declare function stopSyncDaemon(cwd?: string): Promise<void>;
|
|
31
43
|
export declare function manifestEntriesToLocalEntries(entries: RemoteDevManifestEntry[]): Map<string, LocalEntry>;
|
|
32
44
|
export declare function buildSyncEvents(previous: Map<string, LocalEntry>, next: Map<string, LocalEntry>): RemoteDevSyncEvent[];
|
|
33
45
|
export declare function findRemoteDevConflicts(previous: Map<string, LocalEntry> | null, remote: Map<string, LocalEntry>, local: Map<string, LocalEntry>): string[];
|
|
34
46
|
export declare function scanLocalTree(root: string, options?: {
|
|
35
47
|
onSkippedLargeFile?: (relativePath: string, size: number) => void;
|
|
36
48
|
}): Promise<Map<string, LocalEntry>>;
|
|
49
|
+
export declare function enableRemoteDevAppAuth(config: LocalConfig): Promise<void>;
|
|
50
|
+
export declare function runRemoteDataMigration(input: {
|
|
51
|
+
config: LocalConfig;
|
|
52
|
+
inputPath: string;
|
|
53
|
+
wait: boolean;
|
|
54
|
+
}): Promise<void>;
|
|
37
55
|
export declare class HttpError extends Error {
|
|
38
56
|
readonly status: number;
|
|
39
57
|
constructor(message: string, status: number);
|
|
@@ -53,4 +71,5 @@ export declare function hydrateUpsertContents(root: string, events: RemoteDevSyn
|
|
|
53
71
|
hash: string;
|
|
54
72
|
executable?: boolean;
|
|
55
73
|
})[]>;
|
|
74
|
+
export {};
|
|
56
75
|
//# sourceMappingURL=sync.d.ts.map
|
package/dist/sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
|
package/dist/sync.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { lstat, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { lstat, mkdir, open, readdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { assertRemoteDevPath, remoteDevMaxEventsPerBatch, remoteDevMaxFileBytes, remoteDevProtocolVersion, shouldIgnoreRemoteDevPath, } from "./protocol.js";
|
|
5
6
|
import { extractStoredZip } from "./zip.js";
|
|
6
7
|
export const cliVersion = "0.1.0";
|
|
7
|
-
const heartbeatIntervalMs =
|
|
8
|
+
const heartbeatIntervalMs = 2_000;
|
|
9
|
+
const checkpointQuietPeriodMs = 3_000;
|
|
8
10
|
export async function connectAndWatch(input) {
|
|
9
11
|
const root = path.resolve(input.directory);
|
|
10
12
|
await mkdir(root, { recursive: true });
|
|
@@ -18,6 +20,7 @@ export async function connectAndWatch(input) {
|
|
|
18
20
|
localPath: root,
|
|
19
21
|
},
|
|
20
22
|
});
|
|
23
|
+
const agentInstructions = requireAgentInstructions(connected);
|
|
21
24
|
const skippedLargeFiles = new Set();
|
|
22
25
|
const onSkippedLargeFile = (relativePath, size) => {
|
|
23
26
|
const key = `${relativePath}:${size}`;
|
|
@@ -34,6 +37,7 @@ export async function connectAndWatch(input) {
|
|
|
34
37
|
});
|
|
35
38
|
await extractStoredZip({ archive, destination: root });
|
|
36
39
|
}
|
|
40
|
+
await writeAgentInstructions(root, agentInstructions);
|
|
37
41
|
const manifest = await getJson({
|
|
38
42
|
...input,
|
|
39
43
|
path: `/api/remote-dev/sessions/${input.sessionId}/manifest`,
|
|
@@ -53,30 +57,90 @@ export async function connectAndWatch(input) {
|
|
|
53
57
|
let previous = remoteBaseline;
|
|
54
58
|
let sequence = connected.session.lastSequence + 1;
|
|
55
59
|
let lastHeartbeatAt = 0;
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
let lastChangeAt = 0;
|
|
61
|
+
let lastCheckpointSequence = connected.session.lastSequence;
|
|
62
|
+
process.stdout.write(`Connected local development session ${input.sessionId}\n`);
|
|
63
|
+
await input.onConnected?.();
|
|
64
|
+
await new Promise((resolve, reject) => {
|
|
58
65
|
let running = false;
|
|
66
|
+
let stopping = false;
|
|
67
|
+
let activeTick = Promise.resolve();
|
|
68
|
+
const cleanupSignals = () => {
|
|
69
|
+
process.removeListener("SIGINT", handleSigint);
|
|
70
|
+
process.removeListener("SIGTERM", handleSigterm);
|
|
71
|
+
};
|
|
72
|
+
const stop = () => {
|
|
73
|
+
if (stopping)
|
|
74
|
+
return;
|
|
75
|
+
stopping = true;
|
|
76
|
+
clearInterval(interval);
|
|
77
|
+
activeTick
|
|
78
|
+
.then(async () => {
|
|
79
|
+
let stopError = null;
|
|
80
|
+
try {
|
|
81
|
+
const finalSync = await syncOnce({
|
|
82
|
+
...input,
|
|
83
|
+
root,
|
|
84
|
+
sequence,
|
|
85
|
+
previous,
|
|
86
|
+
onSkippedLargeFile,
|
|
87
|
+
});
|
|
88
|
+
previous = finalSync.nextEntries;
|
|
89
|
+
sequence = finalSync.nextSequence;
|
|
90
|
+
if (getAppliedSequence(sequence) > lastCheckpointSequence) {
|
|
91
|
+
await checkpointRemoteDev(input);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
stopError = error;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await exitRemoteDev(input);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
stopError ??= error;
|
|
102
|
+
}
|
|
103
|
+
cleanupSignals();
|
|
104
|
+
if (stopError)
|
|
105
|
+
throw stopError;
|
|
106
|
+
process.stdout.write("Saved and disconnected local development session.\n");
|
|
107
|
+
resolve();
|
|
108
|
+
})
|
|
109
|
+
.catch((error) => {
|
|
110
|
+
cleanupSignals();
|
|
111
|
+
reject(error);
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
const handleSigint = () => stop();
|
|
115
|
+
const handleSigterm = () => stop();
|
|
116
|
+
process.once("SIGINT", handleSigint);
|
|
117
|
+
process.once("SIGTERM", handleSigterm);
|
|
59
118
|
const interval = setInterval(() => {
|
|
60
|
-
if (running) {
|
|
119
|
+
if (running || stopping) {
|
|
61
120
|
return;
|
|
62
121
|
}
|
|
63
122
|
running = true;
|
|
64
|
-
|
|
123
|
+
activeTick = runConnectLoopTick({
|
|
65
124
|
...input,
|
|
66
125
|
root,
|
|
67
126
|
sequence,
|
|
68
127
|
previous,
|
|
69
128
|
lastHeartbeatAt,
|
|
129
|
+
lastChangeAt,
|
|
130
|
+
lastCheckpointSequence,
|
|
70
131
|
onSkippedLargeFile,
|
|
71
132
|
})
|
|
72
133
|
.then((result) => {
|
|
73
134
|
previous = result.nextEntries;
|
|
74
135
|
sequence = result.nextSequence;
|
|
75
136
|
lastHeartbeatAt = result.nextHeartbeatAt;
|
|
137
|
+
lastChangeAt = result.nextChangeAt;
|
|
138
|
+
lastCheckpointSequence = result.nextCheckpointSequence;
|
|
76
139
|
})
|
|
77
140
|
.catch((error) => {
|
|
78
141
|
if (isTerminalRemoteDevError(error)) {
|
|
79
142
|
clearInterval(interval);
|
|
143
|
+
cleanupSignals();
|
|
80
144
|
reject(error);
|
|
81
145
|
return;
|
|
82
146
|
}
|
|
@@ -88,13 +152,56 @@ export async function connectAndWatch(input) {
|
|
|
88
152
|
}, 500);
|
|
89
153
|
});
|
|
90
154
|
}
|
|
155
|
+
function requireAgentInstructions(response) {
|
|
156
|
+
const instructions = response.agentInstructions?.trim();
|
|
157
|
+
if (!instructions) {
|
|
158
|
+
throw new Error("Remote dev connect response did not include agent instructions. Update the Vibable API before connecting.");
|
|
159
|
+
}
|
|
160
|
+
return instructions;
|
|
161
|
+
}
|
|
91
162
|
async function runConnectLoopTick(input) {
|
|
92
163
|
const now = Date.now();
|
|
164
|
+
const result = await syncOnce(input);
|
|
165
|
+
let nextResult = result;
|
|
93
166
|
const nextHeartbeatAt = now - input.lastHeartbeatAt >= heartbeatIntervalMs
|
|
94
|
-
? await sendHeartbeat(
|
|
167
|
+
? await sendHeartbeat({
|
|
168
|
+
...input,
|
|
169
|
+
clean: true,
|
|
170
|
+
lastSequence: getAppliedSequence(result.nextSequence),
|
|
171
|
+
}).then(async (heartbeat) => {
|
|
172
|
+
if (heartbeat.flushRequest) {
|
|
173
|
+
nextResult = await syncOnce({
|
|
174
|
+
...input,
|
|
175
|
+
sequence: result.nextSequence,
|
|
176
|
+
previous: result.nextEntries,
|
|
177
|
+
});
|
|
178
|
+
await sendFlushAck({
|
|
179
|
+
...input,
|
|
180
|
+
requestId: heartbeat.flushRequest.id,
|
|
181
|
+
lastSequence: getAppliedSequence(nextResult.nextSequence),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return now;
|
|
185
|
+
})
|
|
95
186
|
: input.lastHeartbeatAt;
|
|
96
|
-
const
|
|
97
|
-
|
|
187
|
+
const changedThisTick = result.changed || nextResult.changed;
|
|
188
|
+
const nextChangeAt = changedThisTick ? now : input.lastChangeAt;
|
|
189
|
+
const appliedSequence = getAppliedSequence(nextResult.nextSequence);
|
|
190
|
+
const shouldCheckpoint = !changedThisTick &&
|
|
191
|
+
appliedSequence > input.lastCheckpointSequence &&
|
|
192
|
+
now - nextChangeAt >= checkpointQuietPeriodMs;
|
|
193
|
+
if (shouldCheckpoint) {
|
|
194
|
+
await checkpointRemoteDev(input);
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
...nextResult,
|
|
198
|
+
changed: changedThisTick,
|
|
199
|
+
nextHeartbeatAt,
|
|
200
|
+
nextChangeAt,
|
|
201
|
+
nextCheckpointSequence: shouldCheckpoint
|
|
202
|
+
? appliedSequence
|
|
203
|
+
: input.lastCheckpointSequence,
|
|
204
|
+
};
|
|
98
205
|
}
|
|
99
206
|
export async function runRemoteCommand(input) {
|
|
100
207
|
const command = input.command[0];
|
|
@@ -122,7 +229,110 @@ export async function runRemoteCommand(input) {
|
|
|
122
229
|
process.exitCode = exitCode;
|
|
123
230
|
}
|
|
124
231
|
export async function readLocalConfig(cwd = process.cwd()) {
|
|
125
|
-
|
|
232
|
+
return (await readLocalProject(cwd)).config;
|
|
233
|
+
}
|
|
234
|
+
export async function startSyncDaemon(cwd = process.cwd()) {
|
|
235
|
+
const project = await readLocalProject(cwd);
|
|
236
|
+
const existing = await readSyncDaemonState(project.root);
|
|
237
|
+
if (existing && isProcessRunning(existing.pid)) {
|
|
238
|
+
process.stdout.write(`Vibable sync is already running (pid ${existing.pid}).\n`);
|
|
239
|
+
return existing;
|
|
240
|
+
}
|
|
241
|
+
const logFile = path.join(project.root, ".vibable", "sync.log");
|
|
242
|
+
const output = await open(logFile, "a", 0o600);
|
|
243
|
+
try {
|
|
244
|
+
const entryFile = process.argv[1];
|
|
245
|
+
if (!entryFile)
|
|
246
|
+
throw new Error("Cannot locate the Vibable CLI entry file.");
|
|
247
|
+
const child = spawn(process.execPath, [entryFile, "sync", "daemon", project.root], {
|
|
248
|
+
cwd: project.root,
|
|
249
|
+
detached: true,
|
|
250
|
+
stdio: ["ignore", output.fd, output.fd],
|
|
251
|
+
});
|
|
252
|
+
if (!child.pid)
|
|
253
|
+
throw new Error("Failed to start Vibable sync.");
|
|
254
|
+
const state = {
|
|
255
|
+
pid: child.pid,
|
|
256
|
+
status: "starting",
|
|
257
|
+
startedAt: new Date().toISOString(),
|
|
258
|
+
logFile,
|
|
259
|
+
};
|
|
260
|
+
await writeSyncDaemonState(project.root, state);
|
|
261
|
+
child.unref();
|
|
262
|
+
process.stdout.write(`Vibable sync started in the background (pid ${child.pid}).\n`);
|
|
263
|
+
return state;
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
await output.close();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
export async function isSyncDaemonRunning(cwd = process.cwd()) {
|
|
270
|
+
const { root } = await readLocalProject(cwd);
|
|
271
|
+
const state = await readSyncDaemonState(root);
|
|
272
|
+
return Boolean(state && isProcessRunning(state.pid));
|
|
273
|
+
}
|
|
274
|
+
export async function runSyncDaemon(root) {
|
|
275
|
+
const project = await readLocalProject(root);
|
|
276
|
+
const state = await waitForOwnSyncDaemonState(project.root);
|
|
277
|
+
if (!state || state.pid !== process.pid) {
|
|
278
|
+
throw new Error("Vibable sync daemon state does not match this process.");
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
await connectAndWatch({
|
|
282
|
+
...project.config,
|
|
283
|
+
directory: project.root,
|
|
284
|
+
onConnected: async () => {
|
|
285
|
+
await writeSyncDaemonState(project.root, { ...state, status: "running" });
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
const current = await readSyncDaemonState(project.root);
|
|
291
|
+
if (current?.pid === process.pid) {
|
|
292
|
+
await unlink(syncDaemonStatePath(project.root)).catch((error) => {
|
|
293
|
+
if (!isMissingFileError(error))
|
|
294
|
+
throw error;
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function waitForOwnSyncDaemonState(root) {
|
|
300
|
+
const deadline = Date.now() + 2_000;
|
|
301
|
+
while (Date.now() < deadline) {
|
|
302
|
+
const state = await readSyncDaemonState(root);
|
|
303
|
+
if (state?.pid === process.pid)
|
|
304
|
+
return state;
|
|
305
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
export async function showSyncDaemonStatus(cwd = process.cwd()) {
|
|
310
|
+
const { root } = await readLocalProject(cwd);
|
|
311
|
+
const state = await readSyncDaemonState(root);
|
|
312
|
+
const running = Boolean(state && isProcessRunning(state.pid));
|
|
313
|
+
process.stdout.write(`${JSON.stringify({ running, ...(state ?? {}) }, null, 2)}\n`);
|
|
314
|
+
}
|
|
315
|
+
export async function stopSyncDaemon(cwd = process.cwd()) {
|
|
316
|
+
const { root } = await readLocalProject(cwd);
|
|
317
|
+
const state = await readSyncDaemonState(root);
|
|
318
|
+
if (!state || !isProcessRunning(state.pid)) {
|
|
319
|
+
await unlink(syncDaemonStatePath(root)).catch(() => undefined);
|
|
320
|
+
process.stdout.write("Vibable sync is not running.\n");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
process.kill(state.pid, "SIGTERM");
|
|
324
|
+
const deadline = Date.now() + 30_000;
|
|
325
|
+
while (Date.now() < deadline && isProcessRunning(state.pid)) {
|
|
326
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
327
|
+
}
|
|
328
|
+
if (isProcessRunning(state.pid)) {
|
|
329
|
+
throw new Error("Vibable sync is still saving. Check .vibable/sync.log and retry.");
|
|
330
|
+
}
|
|
331
|
+
process.stdout.write("Vibable sync saved and stopped.\n");
|
|
332
|
+
}
|
|
333
|
+
async function readLocalProject(cwd) {
|
|
334
|
+
const located = await readNearestLocalConfig(path.resolve(cwd));
|
|
335
|
+
const parsed = JSON.parse(located.source);
|
|
126
336
|
if (!parsed ||
|
|
127
337
|
typeof parsed !== "object" ||
|
|
128
338
|
!("apiBaseUrl" in parsed) ||
|
|
@@ -134,9 +344,12 @@ export async function readLocalConfig(cwd = process.cwd()) {
|
|
|
134
344
|
throw new Error("Invalid .vibable/config.json");
|
|
135
345
|
}
|
|
136
346
|
return {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
347
|
+
root: located.root,
|
|
348
|
+
config: {
|
|
349
|
+
apiBaseUrl: parsed.apiBaseUrl,
|
|
350
|
+
sessionId: parsed.sessionId,
|
|
351
|
+
token: parsed.token,
|
|
352
|
+
},
|
|
140
353
|
};
|
|
141
354
|
}
|
|
142
355
|
export function manifestEntriesToLocalEntries(entries) {
|
|
@@ -163,7 +376,7 @@ async function readNearestLocalConfig(startDirectory) {
|
|
|
163
376
|
while (true) {
|
|
164
377
|
const configPath = path.join(currentDirectory, ".vibable", "config.json");
|
|
165
378
|
try {
|
|
166
|
-
return await readFile(configPath, "utf8");
|
|
379
|
+
return { root: currentDirectory, source: await readFile(configPath, "utf8") };
|
|
167
380
|
}
|
|
168
381
|
catch (error) {
|
|
169
382
|
if (!isMissingFileError(error)) {
|
|
@@ -177,6 +390,38 @@ async function readNearestLocalConfig(startDirectory) {
|
|
|
177
390
|
currentDirectory = parentDirectory;
|
|
178
391
|
}
|
|
179
392
|
}
|
|
393
|
+
async function readSyncDaemonState(root) {
|
|
394
|
+
try {
|
|
395
|
+
const value = JSON.parse(await readFile(syncDaemonStatePath(root), "utf8"));
|
|
396
|
+
if (typeof value.pid === "number" &&
|
|
397
|
+
(value.status === "starting" || value.status === "running") &&
|
|
398
|
+
typeof value.startedAt === "string" &&
|
|
399
|
+
typeof value.logFile === "string") {
|
|
400
|
+
return value;
|
|
401
|
+
}
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
if (isMissingFileError(error))
|
|
406
|
+
return null;
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
async function writeSyncDaemonState(root, state) {
|
|
411
|
+
await writeFile(syncDaemonStatePath(root), `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
412
|
+
}
|
|
413
|
+
function syncDaemonStatePath(root) {
|
|
414
|
+
return path.join(root, ".vibable", "sync-daemon.json");
|
|
415
|
+
}
|
|
416
|
+
function isProcessRunning(pid) {
|
|
417
|
+
try {
|
|
418
|
+
process.kill(pid, 0);
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
180
425
|
function isMissingFileError(error) {
|
|
181
426
|
return Boolean(error &&
|
|
182
427
|
typeof error === "object" &&
|
|
@@ -189,7 +434,7 @@ async function syncOnce(input) {
|
|
|
189
434
|
: {});
|
|
190
435
|
const events = buildSyncEvents(input.previous, nextEntries);
|
|
191
436
|
if (events.length === 0) {
|
|
192
|
-
return { nextEntries, nextSequence: input.sequence };
|
|
437
|
+
return { nextEntries, nextSequence: input.sequence, changed: false };
|
|
193
438
|
}
|
|
194
439
|
let nextSequence = input.sequence;
|
|
195
440
|
for (const chunk of chunkEvents(events, remoteDevMaxEventsPerBatch)) {
|
|
@@ -208,7 +453,7 @@ async function syncOnce(input) {
|
|
|
208
453
|
}
|
|
209
454
|
await writeLocalState(input.root, { baseline: localEntriesToStateEntries(nextEntries) });
|
|
210
455
|
process.stdout.write(`synced ${events.length} change(s)\n`);
|
|
211
|
-
return { nextEntries, nextSequence };
|
|
456
|
+
return { nextEntries, nextSequence, changed: true };
|
|
212
457
|
}
|
|
213
458
|
export function buildSyncEvents(previous, next) {
|
|
214
459
|
const events = [];
|
|
@@ -332,14 +577,49 @@ async function getJson(input) {
|
|
|
332
577
|
}
|
|
333
578
|
return (await response.json());
|
|
334
579
|
}
|
|
580
|
+
function getAppliedSequence(nextSequence) {
|
|
581
|
+
return nextSequence - 1;
|
|
582
|
+
}
|
|
335
583
|
async function sendHeartbeat(input) {
|
|
336
|
-
await postJson({
|
|
584
|
+
return await postJson({
|
|
337
585
|
apiBaseUrl: input.apiBaseUrl,
|
|
338
586
|
sessionId: input.sessionId,
|
|
339
587
|
token: input.token,
|
|
340
588
|
path: `/api/remote-dev/sessions/${input.sessionId}/heartbeat`,
|
|
589
|
+
body: {
|
|
590
|
+
...(input.clean !== undefined ? { clean: input.clean } : {}),
|
|
591
|
+
...(input.lastSequence !== undefined ? { lastSequence: input.lastSequence } : {}),
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
async function checkpointRemoteDev(input) {
|
|
596
|
+
await postJson({
|
|
597
|
+
...input,
|
|
598
|
+
path: `/api/remote-dev/sessions/${input.sessionId}/checkpoint`,
|
|
341
599
|
body: {},
|
|
342
600
|
});
|
|
601
|
+
process.stdout.write("checkpointed remote changes\n");
|
|
602
|
+
}
|
|
603
|
+
async function exitRemoteDev(input) {
|
|
604
|
+
const response = await fetch(`${trimTrailingSlash(input.apiBaseUrl)}/api/remote-dev/sessions/${input.sessionId}`, {
|
|
605
|
+
method: "DELETE",
|
|
606
|
+
headers: { authorization: `Bearer ${input.token}` },
|
|
607
|
+
});
|
|
608
|
+
if (!response.ok) {
|
|
609
|
+
throw new HttpError(await readErrorMessage(response), response.status);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
async function sendFlushAck(input) {
|
|
613
|
+
await postJson({
|
|
614
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
615
|
+
sessionId: input.sessionId,
|
|
616
|
+
token: input.token,
|
|
617
|
+
path: `/api/remote-dev/sessions/${input.sessionId}/flush-ack`,
|
|
618
|
+
body: {
|
|
619
|
+
requestId: input.requestId,
|
|
620
|
+
lastSequence: input.lastSequence,
|
|
621
|
+
},
|
|
622
|
+
});
|
|
343
623
|
}
|
|
344
624
|
async function getBinary(input) {
|
|
345
625
|
const response = await fetch(`${trimTrailingSlash(input.apiBaseUrl)}${input.path}`, {
|
|
@@ -356,13 +636,74 @@ async function writeLocalConfig(root, config) {
|
|
|
356
636
|
const configDirectory = path.join(root, ".vibable");
|
|
357
637
|
await mkdir(configDirectory, { recursive: true });
|
|
358
638
|
await writeFile(path.join(configDirectory, "config.json"), JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
359
|
-
|
|
360
|
-
|
|
639
|
+
}
|
|
640
|
+
const vibableAgentsStartMarker = "<!-- VIBABLE_REMOTE_DEV_START -->";
|
|
641
|
+
const vibableAgentsEndMarker = "<!-- VIBABLE_REMOTE_DEV_END -->";
|
|
642
|
+
async function writeAgentInstructions(root, instructions) {
|
|
643
|
+
const configDirectory = path.join(root, ".vibable");
|
|
644
|
+
await mkdir(configDirectory, { recursive: true });
|
|
645
|
+
await writeFile(path.join(configDirectory, "AGENTS.md"), formatRemoteDevAgentsSection(instructions));
|
|
646
|
+
await writeFile(path.join(root, "AGENTS.md"), mergeRootAgentsInstructions(await readFile(path.join(root, "AGENTS.md"), "utf8").catch((error) => {
|
|
647
|
+
if (isMissingFileError(error)) {
|
|
648
|
+
return "";
|
|
649
|
+
}
|
|
650
|
+
throw error;
|
|
651
|
+
}), instructions));
|
|
652
|
+
}
|
|
653
|
+
function mergeRootAgentsInstructions(existingContent, instructions) {
|
|
654
|
+
const section = formatRemoteDevAgentsSection(instructions).trimEnd();
|
|
655
|
+
const nextSection = [
|
|
656
|
+
vibableAgentsStartMarker,
|
|
657
|
+
section,
|
|
658
|
+
vibableAgentsEndMarker,
|
|
659
|
+
].join("\n");
|
|
660
|
+
const start = existingContent.indexOf(vibableAgentsStartMarker);
|
|
661
|
+
const end = existingContent.indexOf(vibableAgentsEndMarker);
|
|
662
|
+
if (start >= 0 && end > start) {
|
|
663
|
+
return `${existingContent.slice(0, start)}${nextSection}${existingContent.slice(end + vibableAgentsEndMarker.length)}`.trimEnd() + "\n";
|
|
664
|
+
}
|
|
665
|
+
const trimmed = existingContent.trimEnd();
|
|
666
|
+
return `${trimmed ? `${trimmed}\n\n` : ""}${nextSection}\n`;
|
|
667
|
+
}
|
|
668
|
+
function formatRemoteDevAgentsSection(instructions) {
|
|
669
|
+
return [
|
|
670
|
+
"# Vibable Local Development",
|
|
361
671
|
"",
|
|
362
|
-
"
|
|
672
|
+
"Only use `pnpm dlx @sand.ai/vibable-cli@latest exec -- <command>` when a command must run in the remote feature container, such as focused tests, type checks, builds, or runtime commands that cannot run locally.",
|
|
673
|
+
"Do not use remote execution for work that can be completed by reading or editing the synced local files.",
|
|
674
|
+
"Use an installed `vibable-*` Skill when available. If a Skill is missing or may be stale, run `pnpm dlx @sand.ai/vibable-cli@latest guide <skill>` to read the current public version.",
|
|
363
675
|
"Do not sync or edit `.vibable/config.json` manually.",
|
|
364
676
|
"",
|
|
365
|
-
|
|
677
|
+
instructions.trim(),
|
|
678
|
+
"",
|
|
679
|
+
].join("\n");
|
|
680
|
+
}
|
|
681
|
+
export async function enableRemoteDevAppAuth(config) {
|
|
682
|
+
await postJson({
|
|
683
|
+
...config,
|
|
684
|
+
path: `/api/remote-dev/sessions/${config.sessionId}/app-auth/enable`,
|
|
685
|
+
body: {},
|
|
686
|
+
});
|
|
687
|
+
process.stdout.write("Vibable App Auth enabled.\n");
|
|
688
|
+
}
|
|
689
|
+
export async function runRemoteDataMigration(input) {
|
|
690
|
+
const operationText = input.inputPath === "-"
|
|
691
|
+
? await readStdin()
|
|
692
|
+
: await readFile(path.resolve(input.inputPath), "utf8");
|
|
693
|
+
const operation = JSON.parse(operationText);
|
|
694
|
+
const migration = await postJson({
|
|
695
|
+
...input.config,
|
|
696
|
+
path: `/api/remote-dev/sessions/${input.config.sessionId}/data/migrations${input.wait ? "?wait=1" : ""}`,
|
|
697
|
+
body: operation,
|
|
698
|
+
});
|
|
699
|
+
process.stdout.write(`${JSON.stringify(migration, null, 2)}\n`);
|
|
700
|
+
}
|
|
701
|
+
async function readStdin() {
|
|
702
|
+
const chunks = [];
|
|
703
|
+
for await (const chunk of process.stdin) {
|
|
704
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
705
|
+
}
|
|
706
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
366
707
|
}
|
|
367
708
|
async function readLocalState(root) {
|
|
368
709
|
try {
|