looply-cli 0.2.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/.src-hash +1 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +77 -69
- package/dist/bin.js.map +1 -1
- package/dist/serve-cmd.d.ts +115 -0
- package/dist/serve-cmd.d.ts.map +1 -0
- package/dist/serve-cmd.js +505 -0
- package/dist/serve-cmd.js.map +1 -0
- package/dist/serve.d.ts +167 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +336 -0
- package/dist/serve.js.map +1 -0
- package/package.json +54 -54
package/dist/serve.d.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { type MachineFs } from './machine-store';
|
|
2
|
+
import type { ProbeFn } from './run-lease';
|
|
3
|
+
/**
|
|
4
|
+
* `looply serve` — ONE PROCESS FOR EVERY REPOSITORY ON THE MACHINE.
|
|
5
|
+
*
|
|
6
|
+
* A daemon serves the repository it was started in, and nothing else. A person
|
|
7
|
+
* with five repositories had five terminals open for ever, and the sixth one
|
|
8
|
+
* they forgot about simply went quiet. `serve` keeps a list of repositories in
|
|
9
|
+
* the Looply home and runs one ordinary `looply daemon --yes` per entry, as a
|
|
10
|
+
* child, restarting each one that falls over.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ IT IS A SUPERVISOR, NOT A SECOND DAEMON. Every child is the same daemon a
|
|
13
|
+
* person would start by hand — same per-repo lock, same registration, same
|
|
14
|
+
* server conversation — so there is exactly one implementation of "serve this
|
|
15
|
+
* repository" and this file only decides which ones are running.
|
|
16
|
+
*
|
|
17
|
+
* This module is the pure half: the registry, the backoff, the diff between
|
|
18
|
+
* what runs and what is listed, the supervisor lock and the startup file. The
|
|
19
|
+
* process wiring lives in `serve-cmd.ts`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SERVED_FILE = "served.json";
|
|
22
|
+
export declare const SERVE_LOCK_FILE = "serve.lock";
|
|
23
|
+
export declare const SERVE_LOG_FILE = "serve.log";
|
|
24
|
+
export declare const STARTUP_FILE = "looply-serve.cmd";
|
|
25
|
+
/** The registry is re-read this often, so `serve add`/`remove` land without a restart. */
|
|
26
|
+
export declare const REGISTRY_POLL_MS = 30000;
|
|
27
|
+
export declare const BACKOFF_FIRST_MS = 2000;
|
|
28
|
+
export declare const BACKOFF_MAX_MS = 60000;
|
|
29
|
+
/** A child that stayed up this long has earned a fresh backoff. */
|
|
30
|
+
export declare const BACKOFF_RESET_AFTER_MS: number;
|
|
31
|
+
export type Platform = NodeJS.Platform;
|
|
32
|
+
export interface ServedRepo {
|
|
33
|
+
/** Absolute and normalised; see `normaliseRepoPath`. */
|
|
34
|
+
readonly path: string;
|
|
35
|
+
readonly addedAt: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ServedRegistry {
|
|
38
|
+
readonly version: 1;
|
|
39
|
+
readonly repos: readonly ServedRepo[];
|
|
40
|
+
}
|
|
41
|
+
export declare const EMPTY_REGISTRY: ServedRegistry;
|
|
42
|
+
/**
|
|
43
|
+
* The machine store's filesystem, plus the three things a supervisor needs that
|
|
44
|
+
* a credential file does not. `write` is the machine store's own temp-then-rename,
|
|
45
|
+
* so the registry is never seen half-written by a supervisor polling it.
|
|
46
|
+
*/
|
|
47
|
+
export interface ServeFs extends MachineFs {
|
|
48
|
+
isDir(path: string): boolean;
|
|
49
|
+
remove(path: string): void;
|
|
50
|
+
/** Create only if absent. False when something is already there. */
|
|
51
|
+
createExclusive(path: string, text: string): boolean;
|
|
52
|
+
}
|
|
53
|
+
export declare const nodeServeFs: ServeFs;
|
|
54
|
+
export declare function servedFilePath(home?: string): string;
|
|
55
|
+
export declare function serveLockPath(home?: string): string;
|
|
56
|
+
export declare function serveLogPath(home?: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* One spelling per folder.
|
|
59
|
+
*
|
|
60
|
+
* ⚠️ ON WINDOWS `C:/Code/App/`, `c:\code\app` AND `C:\Code\App` ARE ONE FOLDER.
|
|
61
|
+
* Stored three ways they would be three entries and three children, and two of
|
|
62
|
+
* them would lose the per-repo daemon lock to the first and restart for ever.
|
|
63
|
+
* So the stored path is resolved, backslashed, trailing-separator-free and has
|
|
64
|
+
* an upper-case drive letter; and comparison goes through `repoKey`, which also
|
|
65
|
+
* folds case, because the filesystem does.
|
|
66
|
+
*/
|
|
67
|
+
export declare function normaliseRepoPath(input: string, cwd: string, platform: Platform): string;
|
|
68
|
+
export declare function repoKey(path: string, platform: Platform): string;
|
|
69
|
+
/** The folder name a child's output is prefixed with. */
|
|
70
|
+
export declare function repoLabel(path: string, platform: Platform): string;
|
|
71
|
+
export declare function configPathOf(repo: string, platform: Platform): string;
|
|
72
|
+
/**
|
|
73
|
+
* Read the registry. Absent is empty.
|
|
74
|
+
*
|
|
75
|
+
* 🔴 CORRUPT IS LOUD, NOT EMPTY. Reading an unparseable file as "nothing is
|
|
76
|
+
* served" would let the next `serve add` write a one-entry file over it, and
|
|
77
|
+
* every other repository the person listed would vanish without a word. The
|
|
78
|
+
* same rule `MachineStore.load` keeps for the identity.
|
|
79
|
+
*/
|
|
80
|
+
export declare function readRegistry(fs: ServeFs, file: string): ServedRegistry;
|
|
81
|
+
export declare function writeRegistry(fs: ServeFs, file: string, registry: ServedRegistry): void;
|
|
82
|
+
export declare function addRepo(registry: ServedRegistry, path: string, addedAt: string, platform: Platform): {
|
|
83
|
+
registry: ServedRegistry;
|
|
84
|
+
added: boolean;
|
|
85
|
+
};
|
|
86
|
+
export declare function removeRepo(registry: ServedRegistry, path: string, platform: Platform): {
|
|
87
|
+
registry: ServedRegistry;
|
|
88
|
+
removed: boolean;
|
|
89
|
+
};
|
|
90
|
+
/** 2s, 4s, 8s … capped at a minute. `failures` counts consecutive short lives. */
|
|
91
|
+
export declare function backoffDelay(failures: number): number;
|
|
92
|
+
/**
|
|
93
|
+
* The restart schedule for one child.
|
|
94
|
+
*
|
|
95
|
+
* ⚠️ RESET ON UPTIME, NOT ON EXIT CODE. A daemon that ran all night and then
|
|
96
|
+
* lost its network is not the same as one that dies on start, and a clean exit
|
|
97
|
+
* code says nothing about which it was. Five minutes up is the line.
|
|
98
|
+
*/
|
|
99
|
+
export declare class Backoff {
|
|
100
|
+
private failures;
|
|
101
|
+
private startedAt;
|
|
102
|
+
started(now: number): void;
|
|
103
|
+
/** The child is gone (or could not start); how long to wait before the next try. */
|
|
104
|
+
exited(now: number): number;
|
|
105
|
+
}
|
|
106
|
+
export interface ReconcilePlan {
|
|
107
|
+
readonly start: readonly ServedRepo[];
|
|
108
|
+
/** Keys of running entries no longer listed. */
|
|
109
|
+
readonly stop: readonly string[];
|
|
110
|
+
readonly keep: readonly string[];
|
|
111
|
+
}
|
|
112
|
+
/** What to start, stop and leave alone, given what runs and what is listed. */
|
|
113
|
+
export declare function reconcile(running: Iterable<string>, wanted: readonly ServedRepo[], platform: Platform): ReconcilePlan;
|
|
114
|
+
/** Splits a stream into whole lines; the tail waits for the next chunk. */
|
|
115
|
+
export declare class LineSplitter {
|
|
116
|
+
private carry;
|
|
117
|
+
push(chunk: string): string[];
|
|
118
|
+
flush(): string[];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* ONE SUPERVISOR PER MACHINE.
|
|
122
|
+
*
|
|
123
|
+
* Two supervisors would start two children per repository; the second of each
|
|
124
|
+
* pair loses the per-repo daemon lock, exits, and is restarted for ever. So the
|
|
125
|
+
* supervisor takes `~/.looply/serve.lock` first.
|
|
126
|
+
*
|
|
127
|
+
* ⚠️ STALE-TOLERANT BY PID. A supervisor killed at logoff leaves its lock
|
|
128
|
+
* behind, and a lock that outlived its process must not stop the next login
|
|
129
|
+
* from serving anything. A lock naming a pid that is not alive is taken over;
|
|
130
|
+
* an unreadable one is litter and taken over too. A pid that answers — or that
|
|
131
|
+
* cannot be judged (`unknown`: it exists and belongs to someone else) — is
|
|
132
|
+
* held, and the answer is a refusal.
|
|
133
|
+
*/
|
|
134
|
+
export type ServeLockResult = {
|
|
135
|
+
readonly kind: 'taken';
|
|
136
|
+
readonly tookOverFrom?: number;
|
|
137
|
+
release(): void;
|
|
138
|
+
} | {
|
|
139
|
+
readonly kind: 'held';
|
|
140
|
+
readonly pid: number;
|
|
141
|
+
};
|
|
142
|
+
export declare function takeServeLock(fs: ServeFs, path: string, self: {
|
|
143
|
+
pid: number;
|
|
144
|
+
at: string;
|
|
145
|
+
}, probe: ProbeFn): ServeLockResult;
|
|
146
|
+
export declare function windowsStartupFolder(env: NodeJS.ProcessEnv): string | null;
|
|
147
|
+
export interface LaunchPaths {
|
|
148
|
+
/** Absolute path of the node executable (`process.execPath`). */
|
|
149
|
+
readonly node: string;
|
|
150
|
+
/** Absolute path of this CLI's own `bin.js`. */
|
|
151
|
+
readonly bin: string;
|
|
152
|
+
/** Where the supervisor's output is appended. */
|
|
153
|
+
readonly log: string;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The file `serve install` puts in the Startup folder.
|
|
157
|
+
*
|
|
158
|
+
* ⚠️ THE REDIRECT IS INSIDE THE `cmd /c`. Written after `start`, `>>` would
|
|
159
|
+
* capture `start`'s own (empty) output and the supervisor's would go nowhere.
|
|
160
|
+
* `cmd /c ""a" "b" >> "c""` strips the outermost pair of quotes and runs the
|
|
161
|
+
* rest, which keeps paths with spaces intact. And `cmd.exe` is named from
|
|
162
|
+
* `%SystemRoot%`, never barely — the same rule the code keeps for every spawn.
|
|
163
|
+
*/
|
|
164
|
+
export declare function windowsStartupScript(p: LaunchPaths): string;
|
|
165
|
+
/** What a person on macOS or Linux puts in a launchd agent or a systemd user unit. */
|
|
166
|
+
export declare function unixServiceHint(p: LaunchPaths, platform: Platform): string;
|
|
167
|
+
//# sourceMappingURL=serve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}
|
package/dist/serve.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LineSplitter = exports.Backoff = exports.nodeServeFs = exports.EMPTY_REGISTRY = exports.BACKOFF_RESET_AFTER_MS = exports.BACKOFF_MAX_MS = exports.BACKOFF_FIRST_MS = exports.REGISTRY_POLL_MS = exports.STARTUP_FILE = exports.SERVE_LOG_FILE = exports.SERVE_LOCK_FILE = exports.SERVED_FILE = void 0;
|
|
4
|
+
exports.servedFilePath = servedFilePath;
|
|
5
|
+
exports.serveLockPath = serveLockPath;
|
|
6
|
+
exports.serveLogPath = serveLogPath;
|
|
7
|
+
exports.normaliseRepoPath = normaliseRepoPath;
|
|
8
|
+
exports.repoKey = repoKey;
|
|
9
|
+
exports.repoLabel = repoLabel;
|
|
10
|
+
exports.configPathOf = configPathOf;
|
|
11
|
+
exports.readRegistry = readRegistry;
|
|
12
|
+
exports.writeRegistry = writeRegistry;
|
|
13
|
+
exports.addRepo = addRepo;
|
|
14
|
+
exports.removeRepo = removeRepo;
|
|
15
|
+
exports.backoffDelay = backoffDelay;
|
|
16
|
+
exports.reconcile = reconcile;
|
|
17
|
+
exports.takeServeLock = takeServeLock;
|
|
18
|
+
exports.windowsStartupFolder = windowsStartupFolder;
|
|
19
|
+
exports.windowsStartupScript = windowsStartupScript;
|
|
20
|
+
exports.unixServiceHint = unixServiceHint;
|
|
21
|
+
const node_fs_1 = require("node:fs");
|
|
22
|
+
const node_os_1 = require("node:os");
|
|
23
|
+
const node_path_1 = require("node:path");
|
|
24
|
+
const machine_store_1 = require("./machine-store");
|
|
25
|
+
/**
|
|
26
|
+
* `looply serve` — ONE PROCESS FOR EVERY REPOSITORY ON THE MACHINE.
|
|
27
|
+
*
|
|
28
|
+
* A daemon serves the repository it was started in, and nothing else. A person
|
|
29
|
+
* with five repositories had five terminals open for ever, and the sixth one
|
|
30
|
+
* they forgot about simply went quiet. `serve` keeps a list of repositories in
|
|
31
|
+
* the Looply home and runs one ordinary `looply daemon --yes` per entry, as a
|
|
32
|
+
* child, restarting each one that falls over.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ IT IS A SUPERVISOR, NOT A SECOND DAEMON. Every child is the same daemon a
|
|
35
|
+
* person would start by hand — same per-repo lock, same registration, same
|
|
36
|
+
* server conversation — so there is exactly one implementation of "serve this
|
|
37
|
+
* repository" and this file only decides which ones are running.
|
|
38
|
+
*
|
|
39
|
+
* This module is the pure half: the registry, the backoff, the diff between
|
|
40
|
+
* what runs and what is listed, the supervisor lock and the startup file. The
|
|
41
|
+
* process wiring lives in `serve-cmd.ts`.
|
|
42
|
+
*/
|
|
43
|
+
exports.SERVED_FILE = 'served.json';
|
|
44
|
+
exports.SERVE_LOCK_FILE = 'serve.lock';
|
|
45
|
+
exports.SERVE_LOG_FILE = 'serve.log';
|
|
46
|
+
exports.STARTUP_FILE = 'looply-serve.cmd';
|
|
47
|
+
/** The registry is re-read this often, so `serve add`/`remove` land without a restart. */
|
|
48
|
+
exports.REGISTRY_POLL_MS = 30_000;
|
|
49
|
+
exports.BACKOFF_FIRST_MS = 2_000;
|
|
50
|
+
exports.BACKOFF_MAX_MS = 60_000;
|
|
51
|
+
/** A child that stayed up this long has earned a fresh backoff. */
|
|
52
|
+
exports.BACKOFF_RESET_AFTER_MS = 5 * 60_000;
|
|
53
|
+
exports.EMPTY_REGISTRY = { version: 1, repos: [] };
|
|
54
|
+
exports.nodeServeFs = {
|
|
55
|
+
...machine_store_1.nodeMachineFs,
|
|
56
|
+
isDir: (p) => {
|
|
57
|
+
try {
|
|
58
|
+
return (0, node_fs_1.statSync)(p).isDirectory();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
remove: (p) => {
|
|
65
|
+
try {
|
|
66
|
+
(0, node_fs_1.unlinkSync)(p);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
if (e.code !== 'ENOENT')
|
|
70
|
+
throw e;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
createExclusive: (p, text) => {
|
|
74
|
+
try {
|
|
75
|
+
(0, node_fs_1.writeFileSync)(p, text, { flag: 'wx', mode: 0o600 });
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
if (e.code === 'EEXIST')
|
|
80
|
+
return false;
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
function servedFilePath(home = (0, node_os_1.homedir)()) {
|
|
86
|
+
return (0, node_path_1.join)((0, machine_store_1.machineHome)(home), exports.SERVED_FILE);
|
|
87
|
+
}
|
|
88
|
+
function serveLockPath(home = (0, node_os_1.homedir)()) {
|
|
89
|
+
return (0, node_path_1.join)((0, machine_store_1.machineHome)(home), exports.SERVE_LOCK_FILE);
|
|
90
|
+
}
|
|
91
|
+
function serveLogPath(home = (0, node_os_1.homedir)()) {
|
|
92
|
+
return (0, node_path_1.join)((0, machine_store_1.machineHome)(home), exports.SERVE_LOG_FILE);
|
|
93
|
+
}
|
|
94
|
+
const pathFor = (platform) => (platform === 'win32' ? node_path_1.win32 : node_path_1.posix);
|
|
95
|
+
/**
|
|
96
|
+
* One spelling per folder.
|
|
97
|
+
*
|
|
98
|
+
* ⚠️ ON WINDOWS `C:/Code/App/`, `c:\code\app` AND `C:\Code\App` ARE ONE FOLDER.
|
|
99
|
+
* Stored three ways they would be three entries and three children, and two of
|
|
100
|
+
* them would lose the per-repo daemon lock to the first and restart for ever.
|
|
101
|
+
* So the stored path is resolved, backslashed, trailing-separator-free and has
|
|
102
|
+
* an upper-case drive letter; and comparison goes through `repoKey`, which also
|
|
103
|
+
* folds case, because the filesystem does.
|
|
104
|
+
*/
|
|
105
|
+
function normaliseRepoPath(input, cwd, platform) {
|
|
106
|
+
const p = pathFor(platform);
|
|
107
|
+
let out = p.resolve(cwd, input);
|
|
108
|
+
const root = p.parse(out).root;
|
|
109
|
+
while (out.length > root.length && (out.endsWith(p.sep) || out.endsWith('/')))
|
|
110
|
+
out = out.slice(0, -1);
|
|
111
|
+
if (platform === 'win32' && /^[a-z]:/.test(out))
|
|
112
|
+
out = out[0].toUpperCase() + out.slice(1);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
function repoKey(path, platform) {
|
|
116
|
+
return platform === 'win32' ? path.replace(/\//g, '\\').toLowerCase() : path;
|
|
117
|
+
}
|
|
118
|
+
/** The folder name a child's output is prefixed with. */
|
|
119
|
+
function repoLabel(path, platform) {
|
|
120
|
+
return pathFor(platform).basename(path) || path;
|
|
121
|
+
}
|
|
122
|
+
function configPathOf(repo, platform) {
|
|
123
|
+
return pathFor(platform).join(repo, '.looply', 'config.yaml');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Read the registry. Absent is empty.
|
|
127
|
+
*
|
|
128
|
+
* 🔴 CORRUPT IS LOUD, NOT EMPTY. Reading an unparseable file as "nothing is
|
|
129
|
+
* served" would let the next `serve add` write a one-entry file over it, and
|
|
130
|
+
* every other repository the person listed would vanish without a word. The
|
|
131
|
+
* same rule `MachineStore.load` keeps for the identity.
|
|
132
|
+
*/
|
|
133
|
+
function readRegistry(fs, file) {
|
|
134
|
+
if (!fs.exists(file))
|
|
135
|
+
return exports.EMPTY_REGISTRY;
|
|
136
|
+
let parsed;
|
|
137
|
+
try {
|
|
138
|
+
parsed = JSON.parse(fs.read(file));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
throw new Error(`${file} is not valid JSON — fix it or move it aside, then add the repositories again`);
|
|
142
|
+
}
|
|
143
|
+
const o = parsed;
|
|
144
|
+
if (typeof o !== 'object' || o === null || o.version !== 1 || !Array.isArray(o.repos)) {
|
|
145
|
+
throw new Error(`${file} is not a version 1 served-repositories list`);
|
|
146
|
+
}
|
|
147
|
+
const repos = [];
|
|
148
|
+
for (const r of o.repos) {
|
|
149
|
+
const e = r;
|
|
150
|
+
if (typeof e !== 'object' || e === null || typeof e.path !== 'string' || e.path === '')
|
|
151
|
+
continue;
|
|
152
|
+
repos.push({ path: e.path, addedAt: typeof e.addedAt === 'string' ? e.addedAt : '' });
|
|
153
|
+
}
|
|
154
|
+
return { version: 1, repos };
|
|
155
|
+
}
|
|
156
|
+
function writeRegistry(fs, file, registry) {
|
|
157
|
+
fs.mkdirp((0, node_path_1.dirname)(file));
|
|
158
|
+
fs.write(file, `${JSON.stringify(registry, null, 2)}\n`);
|
|
159
|
+
}
|
|
160
|
+
function addRepo(registry, path, addedAt, platform) {
|
|
161
|
+
const key = repoKey(path, platform);
|
|
162
|
+
if (registry.repos.some((r) => repoKey(r.path, platform) === key))
|
|
163
|
+
return { registry, added: false };
|
|
164
|
+
return { registry: { version: 1, repos: [...registry.repos, { path, addedAt }] }, added: true };
|
|
165
|
+
}
|
|
166
|
+
function removeRepo(registry, path, platform) {
|
|
167
|
+
const key = repoKey(path, platform);
|
|
168
|
+
const repos = registry.repos.filter((r) => repoKey(r.path, platform) !== key);
|
|
169
|
+
return { registry: { version: 1, repos }, removed: repos.length !== registry.repos.length };
|
|
170
|
+
}
|
|
171
|
+
/** 2s, 4s, 8s … capped at a minute. `failures` counts consecutive short lives. */
|
|
172
|
+
function backoffDelay(failures) {
|
|
173
|
+
const n = Math.max(0, Math.floor(failures));
|
|
174
|
+
// Capped before the power, so a long night of failures cannot overflow to Infinity.
|
|
175
|
+
if (n >= 16)
|
|
176
|
+
return exports.BACKOFF_MAX_MS;
|
|
177
|
+
return Math.min(exports.BACKOFF_MAX_MS, exports.BACKOFF_FIRST_MS * 2 ** n);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The restart schedule for one child.
|
|
181
|
+
*
|
|
182
|
+
* ⚠️ RESET ON UPTIME, NOT ON EXIT CODE. A daemon that ran all night and then
|
|
183
|
+
* lost its network is not the same as one that dies on start, and a clean exit
|
|
184
|
+
* code says nothing about which it was. Five minutes up is the line.
|
|
185
|
+
*/
|
|
186
|
+
class Backoff {
|
|
187
|
+
failures = 0;
|
|
188
|
+
startedAt = null;
|
|
189
|
+
started(now) {
|
|
190
|
+
this.startedAt = now;
|
|
191
|
+
}
|
|
192
|
+
/** The child is gone (or could not start); how long to wait before the next try. */
|
|
193
|
+
exited(now) {
|
|
194
|
+
if (this.startedAt !== null && now - this.startedAt >= exports.BACKOFF_RESET_AFTER_MS)
|
|
195
|
+
this.failures = 0;
|
|
196
|
+
this.startedAt = null;
|
|
197
|
+
const delay = backoffDelay(this.failures);
|
|
198
|
+
this.failures += 1;
|
|
199
|
+
return delay;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.Backoff = Backoff;
|
|
203
|
+
/** What to start, stop and leave alone, given what runs and what is listed. */
|
|
204
|
+
function reconcile(running, wanted, platform) {
|
|
205
|
+
const have = new Set(running);
|
|
206
|
+
const want = new Map();
|
|
207
|
+
for (const r of wanted) {
|
|
208
|
+
const k = repoKey(r.path, platform);
|
|
209
|
+
if (!want.has(k))
|
|
210
|
+
want.set(k, r);
|
|
211
|
+
}
|
|
212
|
+
const start = [];
|
|
213
|
+
const keep = [];
|
|
214
|
+
for (const [k, r] of want)
|
|
215
|
+
have.has(k) ? keep.push(k) : start.push(r);
|
|
216
|
+
const stop = [...have].filter((k) => !want.has(k));
|
|
217
|
+
return { start, stop, keep };
|
|
218
|
+
}
|
|
219
|
+
/** Splits a stream into whole lines; the tail waits for the next chunk. */
|
|
220
|
+
class LineSplitter {
|
|
221
|
+
carry = '';
|
|
222
|
+
push(chunk) {
|
|
223
|
+
const parts = (this.carry + chunk).split(/\r?\n/);
|
|
224
|
+
this.carry = parts.pop() ?? '';
|
|
225
|
+
return parts;
|
|
226
|
+
}
|
|
227
|
+
flush() {
|
|
228
|
+
const rest = this.carry;
|
|
229
|
+
this.carry = '';
|
|
230
|
+
return rest === '' ? [] : [rest];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
exports.LineSplitter = LineSplitter;
|
|
234
|
+
function takeServeLock(fs, path, self, probe) {
|
|
235
|
+
fs.mkdirp((0, node_path_1.dirname)(path));
|
|
236
|
+
const text = `${JSON.stringify({ pid: self.pid, at: self.at })}\n`;
|
|
237
|
+
let tookOverFrom;
|
|
238
|
+
// Twice at most: once to find a stale lock, once to take its place. A third
|
|
239
|
+
// collision is another supervisor that won the same race, and it holds.
|
|
240
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
241
|
+
if (fs.createExclusive(path, text)) {
|
|
242
|
+
return {
|
|
243
|
+
kind: 'taken',
|
|
244
|
+
...(tookOverFrom !== undefined ? { tookOverFrom } : {}),
|
|
245
|
+
release: () => {
|
|
246
|
+
// Only our own: a supervisor that lost its lock to a takeover must not
|
|
247
|
+
// delete the successor's.
|
|
248
|
+
if (lockPid(fs, path) === self.pid)
|
|
249
|
+
fs.remove(path);
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const holder = lockPid(fs, path);
|
|
254
|
+
if (holder !== null && holder !== self.pid && probe(holder) !== 'dead')
|
|
255
|
+
return { kind: 'held', pid: holder };
|
|
256
|
+
if (holder !== null && holder !== self.pid)
|
|
257
|
+
tookOverFrom = holder;
|
|
258
|
+
fs.remove(path);
|
|
259
|
+
}
|
|
260
|
+
const holder = lockPid(fs, path);
|
|
261
|
+
return { kind: 'held', pid: holder ?? 0 };
|
|
262
|
+
}
|
|
263
|
+
function lockPid(fs, path) {
|
|
264
|
+
try {
|
|
265
|
+
const o = JSON.parse(fs.read(path));
|
|
266
|
+
return typeof o.pid === 'number' && Number.isInteger(o.pid) && o.pid > 0 ? o.pid : null;
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function windowsStartupFolder(env) {
|
|
273
|
+
const appData = env['APPDATA'];
|
|
274
|
+
if (!appData)
|
|
275
|
+
return null;
|
|
276
|
+
return node_path_1.win32.join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* The file `serve install` puts in the Startup folder.
|
|
280
|
+
*
|
|
281
|
+
* ⚠️ THE REDIRECT IS INSIDE THE `cmd /c`. Written after `start`, `>>` would
|
|
282
|
+
* capture `start`'s own (empty) output and the supervisor's would go nowhere.
|
|
283
|
+
* `cmd /c ""a" "b" >> "c""` strips the outermost pair of quotes and runs the
|
|
284
|
+
* rest, which keeps paths with spaces intact. And `cmd.exe` is named from
|
|
285
|
+
* `%SystemRoot%`, never barely — the same rule the code keeps for every spawn.
|
|
286
|
+
*/
|
|
287
|
+
function windowsStartupScript(p) {
|
|
288
|
+
return [
|
|
289
|
+
'@echo off',
|
|
290
|
+
'rem Written by `looply serve install`; `looply serve uninstall` removes it.',
|
|
291
|
+
'rem Starts the Looply supervisor minimised at login; output goes to the log below.',
|
|
292
|
+
`start "" /min "%SystemRoot%\\System32\\cmd.exe" /c ""${p.node}" "${p.bin}" serve --no-color >> "${p.log}" 2>&1"`,
|
|
293
|
+
'',
|
|
294
|
+
].join('\r\n');
|
|
295
|
+
}
|
|
296
|
+
/** What a person on macOS or Linux puts in a launchd agent or a systemd user unit. */
|
|
297
|
+
function unixServiceHint(p, platform) {
|
|
298
|
+
if (platform === 'darwin') {
|
|
299
|
+
return [
|
|
300
|
+
'looply serve install writes no system files here. To start the supervisor at login, save this as',
|
|
301
|
+
'~/Library/LaunchAgents/dev.looply.serve.plist and run `launchctl load -w` on it:',
|
|
302
|
+
'',
|
|
303
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
304
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
305
|
+
'<plist version="1.0"><dict>',
|
|
306
|
+
' <key>Label</key><string>dev.looply.serve</string>',
|
|
307
|
+
' <key>ProgramArguments</key><array>',
|
|
308
|
+
` <string>${p.node}</string><string>${p.bin}</string><string>serve</string><string>--no-color</string>`,
|
|
309
|
+
' </array>',
|
|
310
|
+
' <key>RunAtLoad</key><true/>',
|
|
311
|
+
' <key>KeepAlive</key><true/>',
|
|
312
|
+
` <key>StandardOutPath</key><string>${p.log}</string>`,
|
|
313
|
+
` <key>StandardErrorPath</key><string>${p.log}</string>`,
|
|
314
|
+
'</dict></plist>',
|
|
315
|
+
'',
|
|
316
|
+
].join('\n');
|
|
317
|
+
}
|
|
318
|
+
return [
|
|
319
|
+
'looply serve install writes no system files here. To start the supervisor at login, save this as',
|
|
320
|
+
'~/.config/systemd/user/looply-serve.service and run `systemctl --user enable looply-serve` and `systemctl --user start looply-serve`:',
|
|
321
|
+
'',
|
|
322
|
+
'[Unit]',
|
|
323
|
+
'Description=Looply supervisor (looply serve)',
|
|
324
|
+
'',
|
|
325
|
+
'[Service]',
|
|
326
|
+
`ExecStart=${p.node} ${p.bin} serve --no-color`,
|
|
327
|
+
'Restart=on-failure',
|
|
328
|
+
`StandardOutput=append:${p.log}`,
|
|
329
|
+
`StandardError=append:${p.log}`,
|
|
330
|
+
'',
|
|
331
|
+
'[Install]',
|
|
332
|
+
'WantedBy=default.target',
|
|
333
|
+
'',
|
|
334
|
+
].join('\n');
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "looply-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Leave your coding agent running. looply picks the work, holds the rails, and proves every change before it is kept.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"bin": {
|
|
7
|
-
"looply": "dist/bin.js"
|
|
8
|
-
},
|
|
9
|
-
"main": "dist/index.js",
|
|
10
|
-
"types": "dist/index.d.ts",
|
|
11
|
-
"files": [
|
|
12
|
-
"dist",
|
|
13
|
-
"win",
|
|
14
|
-
"README.md",
|
|
15
|
-
"LICENSE"
|
|
16
|
-
],
|
|
17
|
-
"keywords": [
|
|
18
|
-
"claude",
|
|
19
|
-
"claude-code",
|
|
20
|
-
"coding-agent",
|
|
21
|
-
"agent",
|
|
22
|
-
"autonomous",
|
|
23
|
-
"loop",
|
|
24
|
-
"ci",
|
|
25
|
-
"codegen",
|
|
26
|
-
"automation"
|
|
27
|
-
],
|
|
28
|
-
"scripts": {
|
|
29
|
-
"build": "tsc -b",
|
|
30
|
-
"clean": "tsc -b --clean",
|
|
31
|
-
"prepack": "npm run build",
|
|
32
|
-
"prepublishOnly": "npm run build"
|
|
33
|
-
},
|
|
34
|
-
"dependencies": {
|
|
35
|
-
"@looply/core": "0.
|
|
36
|
-
"pdfjs-dist": "5.6.205",
|
|
37
|
-
"qrcode-terminal": "^0.12.0",
|
|
38
|
-
"yaml": "^2.5.1"
|
|
39
|
-
},
|
|
40
|
-
"repository": {
|
|
41
|
-
"type": "git",
|
|
42
|
-
"url": "git+https://github.com/georgeemichel/looply.git"
|
|
43
|
-
},
|
|
44
|
-
"homepage": "https://looply-agent-app.web.app",
|
|
45
|
-
"bugs": {
|
|
46
|
-
"url": "https://github.com/georgeemichel/looply/issues"
|
|
47
|
-
},
|
|
48
|
-
"engines": {
|
|
49
|
-
"node": ">=20"
|
|
50
|
-
},
|
|
51
|
-
"publishConfig": {
|
|
52
|
-
"access": "public"
|
|
53
|
-
}
|
|
54
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "looply-cli",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Leave your coding agent running. looply picks the work, holds the rails, and proves every change before it is kept.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"looply": "dist/bin.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"win",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"coding-agent",
|
|
21
|
+
"agent",
|
|
22
|
+
"autonomous",
|
|
23
|
+
"loop",
|
|
24
|
+
"ci",
|
|
25
|
+
"codegen",
|
|
26
|
+
"automation"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -b",
|
|
30
|
+
"clean": "tsc -b --clean",
|
|
31
|
+
"prepack": "npm run build",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@looply/core": "0.3.0",
|
|
36
|
+
"pdfjs-dist": "5.6.205",
|
|
37
|
+
"qrcode-terminal": "^0.12.0",
|
|
38
|
+
"yaml": "^2.5.1"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/georgeemichel/looply.git"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://looply-agent-app.web.app",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/georgeemichel/looply/issues"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=20"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
}
|
|
54
|
+
}
|