@drawsy/companion 0.1.29 → 0.1.30
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/README.md +7 -5
- package/dist/drawsy/cli-ui.js +121 -0
- package/dist/drawsy/cli.js +65 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Once installed, run:
|
|
|
26
26
|
drawsy-companion setup
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
`setup` starts Companion in the background and enables startup when you sign in. Then open [Drawsy](https://drawsyai.com), the URL allowed by the Companion connection policy.
|
|
29
|
+
Running `drawsy-companion` by itself shows the local status without changing startup settings. `setup` starts Companion in the background and enables startup when you sign in. Then open [Drawsy](https://drawsyai.com), the URL allowed by the Companion connection policy. Closing the terminal does not stop the background server.
|
|
30
30
|
|
|
31
31
|
To install and set up in one line:
|
|
32
32
|
|
|
@@ -46,10 +46,10 @@ The Windows `.cmd` commands avoid requiring a PowerShell script execution policy
|
|
|
46
46
|
|
|
47
47
|
### Optional GitHub tarball source
|
|
48
48
|
|
|
49
|
-
If you prefer to install a
|
|
49
|
+
If you prefer to install a GitHub release tarball, use its versioned `.tgz` asset. For CLI version 0.1.30:
|
|
50
50
|
|
|
51
51
|
```sh
|
|
52
|
-
npm install --global --ignore-scripts https://github.com/DrawsyAI/drawsy-ai-companion/releases/download/cli-v0.1.
|
|
52
|
+
npm install --global --ignore-scripts https://github.com/DrawsyAI/drawsy-ai-companion/releases/download/cli-v0.1.30/drawsy-companion-0.1.30.tgz
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
The tarball is built by this repository's CLI release workflow and is available after its draft release is published. Use the tarball asset, not the desktop ZIP or AppX.
|
|
@@ -72,13 +72,15 @@ The desktop app and CLI use the same local server address. Quit the desktop app
|
|
|
72
72
|
|
|
73
73
|
## Update or remove
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
The CLI does not update itself. npm distributes the package; Node runs it. To update, stop the running server first. If startup at sign-in is enabled, `autostart disable` also stops it:
|
|
76
76
|
|
|
77
77
|
```sh
|
|
78
|
+
drawsy-companion autostart disable
|
|
78
79
|
npm install --global --ignore-scripts @drawsy/companion@latest
|
|
80
|
+
drawsy-companion setup
|
|
79
81
|
```
|
|
80
82
|
|
|
81
|
-
Run `
|
|
83
|
+
Run `setup` after installation only if you want Companion to start at sign-in. Otherwise, run `drawsy-companion start` when you want it for the current login session. If startup was already disabled, use `drawsy-companion stop` before installing the update instead.
|
|
82
84
|
|
|
83
85
|
Before removing the package, disable startup at login, stop Companion, and remove the package:
|
|
84
86
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const colors = {
|
|
2
|
+
violet: "38;2;101;84;217",
|
|
3
|
+
coral: "38;2;255;125;136",
|
|
4
|
+
blue: "38;2;88;162;232",
|
|
5
|
+
orange: "38;2;245;119;45",
|
|
6
|
+
green: "38;2;55;153;80",
|
|
7
|
+
muted: "38;2;137;143;156",
|
|
8
|
+
amber: "38;2;210;153;34",
|
|
9
|
+
};
|
|
10
|
+
const canColor = () => Boolean(process.stdout.isTTY && !("NO_COLOR" in process.env) && process.env.TERM !== "dumb");
|
|
11
|
+
const paint = (text, color, bold = false) => canColor() ? `\u001b[${bold ? "1;" : ""}${color}m${text}\u001b[0m` : text;
|
|
12
|
+
const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
13
|
+
function wordmark() {
|
|
14
|
+
return [
|
|
15
|
+
paint("D", colors.violet, true),
|
|
16
|
+
paint("r", colors.violet, true),
|
|
17
|
+
paint("a", colors.coral, true),
|
|
18
|
+
paint("w", colors.orange, true),
|
|
19
|
+
paint("s", colors.green, true),
|
|
20
|
+
paint("y", colors.blue, true),
|
|
21
|
+
].join("");
|
|
22
|
+
}
|
|
23
|
+
async function printHeader(animate) {
|
|
24
|
+
const letters = ["D", "r", "a", "w", "s", "y"];
|
|
25
|
+
const letterColors = [colors.violet, colors.violet, colors.coral, colors.orange, colors.green, colors.blue];
|
|
26
|
+
const animated = animate && process.stdout.isTTY && !process.env.CI && process.env.TERM !== "dumb";
|
|
27
|
+
if (animated) {
|
|
28
|
+
let visible = "";
|
|
29
|
+
for (let index = 0; index < letters.length; index++) {
|
|
30
|
+
visible += paint(letters[index], letterColors[index], true);
|
|
31
|
+
process.stdout.write(`\r ${visible} Companion`);
|
|
32
|
+
await delay(38);
|
|
33
|
+
}
|
|
34
|
+
process.stdout.write("\n");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log(`\n ${wordmark()} Companion`);
|
|
38
|
+
}
|
|
39
|
+
/** Animate one drawn stroke across the line only in an interactive terminal. */
|
|
40
|
+
export function startDrawsyIndicator(label) {
|
|
41
|
+
if (!process.stdout.isTTY || process.env.CI || process.env.TERM === "dumb") {
|
|
42
|
+
return { stop: () => undefined };
|
|
43
|
+
}
|
|
44
|
+
const width = 9;
|
|
45
|
+
let position = 0;
|
|
46
|
+
let direction = 1;
|
|
47
|
+
const render = () => {
|
|
48
|
+
const stroke = `${"-".repeat(position)}${paint("/", colors.violet)}${"-".repeat(width - position - 1)}`;
|
|
49
|
+
process.stdout.write(`\r\u001b[2K ${stroke} ${label}`);
|
|
50
|
+
position += direction;
|
|
51
|
+
if (position === width - 1 || position === 0)
|
|
52
|
+
direction *= -1;
|
|
53
|
+
};
|
|
54
|
+
render();
|
|
55
|
+
const timer = setInterval(render, 75);
|
|
56
|
+
timer.unref();
|
|
57
|
+
return {
|
|
58
|
+
stop(message) {
|
|
59
|
+
clearInterval(timer);
|
|
60
|
+
process.stdout.write(`\r\u001b[2K ${paint("OK", colors.green, true)} ${message}\n`);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function printDashboard(state, animate = false) {
|
|
65
|
+
await printHeader(animate);
|
|
66
|
+
console.log(` ${paint("Local bridge", colors.muted)} ${paint(`v${state.version}`, colors.muted)}`);
|
|
67
|
+
console.log("");
|
|
68
|
+
const companionLabel = {
|
|
69
|
+
running: ["Running", colors.green],
|
|
70
|
+
starting: ["Starting", colors.amber],
|
|
71
|
+
stopped: ["Not running", colors.muted],
|
|
72
|
+
other: ["Another Companion is running", colors.amber],
|
|
73
|
+
};
|
|
74
|
+
const [companionText, companionColor] = companionLabel[state.companion];
|
|
75
|
+
const companionVersion = state.companionVersion ? ` · v${state.companionVersion}` : "";
|
|
76
|
+
console.log(` ${"Companion".padEnd(12)} ${paint(`${companionText}${companionVersion}`, companionColor)}`);
|
|
77
|
+
for (const engine of state.engines) {
|
|
78
|
+
let label;
|
|
79
|
+
let color;
|
|
80
|
+
if (!engine.installed) {
|
|
81
|
+
label = "Not found";
|
|
82
|
+
color = colors.muted;
|
|
83
|
+
}
|
|
84
|
+
else if (engine.id === "opencode" && process.platform === "win32") {
|
|
85
|
+
label = "Installed · unavailable on Windows";
|
|
86
|
+
color = colors.amber;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
label = `Available${engine.version ? ` · v${engine.version}` : ""}`;
|
|
90
|
+
color = colors.green;
|
|
91
|
+
}
|
|
92
|
+
console.log(` ${engine.name.padEnd(12)} ${paint(label, color)}`);
|
|
93
|
+
}
|
|
94
|
+
if (state.autostart !== undefined) {
|
|
95
|
+
console.log(` ${"At sign-in".padEnd(12)} ${paint(state.autostart ? "Enabled" : "Off", state.autostart ? colors.green : colors.muted)}`);
|
|
96
|
+
}
|
|
97
|
+
if (state.includeLinks) {
|
|
98
|
+
console.log("");
|
|
99
|
+
console.log(` Open Drawsy ${paint("https://drawsyai.com", colors.blue)}`);
|
|
100
|
+
console.log(` Plans ${paint("https://app.drawsyai.com/pricing", colors.muted)}`);
|
|
101
|
+
}
|
|
102
|
+
if (state.hint) {
|
|
103
|
+
console.log("");
|
|
104
|
+
console.log(` ${paint(state.hint, colors.muted)}`);
|
|
105
|
+
}
|
|
106
|
+
console.log("");
|
|
107
|
+
}
|
|
108
|
+
export async function printHelp(version) {
|
|
109
|
+
await printHeader(false);
|
|
110
|
+
console.log(` ${paint(`v${version}`, colors.muted)} · local canvas bridge`);
|
|
111
|
+
console.log("");
|
|
112
|
+
console.log(" no command Show status and links");
|
|
113
|
+
console.log(" setup Start now and at sign-in");
|
|
114
|
+
console.log(" start Start for this login session");
|
|
115
|
+
console.log(" serve Run in this terminal");
|
|
116
|
+
console.log(" status | stop | logs");
|
|
117
|
+
console.log(" autostart enable | disable");
|
|
118
|
+
console.log("");
|
|
119
|
+
console.log(" Update: npm install --global --ignore-scripts @drawsy/companion@latest");
|
|
120
|
+
console.log("");
|
|
121
|
+
}
|
package/dist/drawsy/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createWriteStream } from "node:fs";
|
|
|
11
11
|
import { createDrawsyBridge } from "./bridge.js";
|
|
12
12
|
import { readLocalEngineStatus } from "./engine-status.js";
|
|
13
13
|
import { enableAutostart, disableAutostart, autostartEnabled } from "./cli-autostart.js";
|
|
14
|
+
import { printDashboard, printHelp, startDrawsyIndicator } from "./cli-ui.js";
|
|
14
15
|
const directory = path.join(homedir(), ".drawsy-companion");
|
|
15
16
|
const lockDirectory = path.join(directory, "runtime.lock");
|
|
16
17
|
const recordPath = path.join(lockDirectory, "runtime.json");
|
|
@@ -67,6 +68,29 @@ const bridgePresent = async () => {
|
|
|
67
68
|
return null;
|
|
68
69
|
}
|
|
69
70
|
};
|
|
71
|
+
const showStatus = async (options = {}) => {
|
|
72
|
+
const running = await control("status");
|
|
73
|
+
const other = running ? null : await bridgePresent();
|
|
74
|
+
const autostart = await autostartEnabled();
|
|
75
|
+
const companion = running ? "running" : other ? "other" : options.missingState ?? "stopped";
|
|
76
|
+
const hint = options.hint ?? (other
|
|
77
|
+
? "Quit the desktop app before starting CLI mode."
|
|
78
|
+
: running
|
|
79
|
+
? autostart ? "Ready for Drawsy." : "Run drawsy-companion setup to keep it ready at sign-in."
|
|
80
|
+
: companion === "starting"
|
|
81
|
+
? "Startup is enabled; run drawsy-companion status to check readiness."
|
|
82
|
+
: "Run drawsy-companion setup to start now and at sign-in.");
|
|
83
|
+
await printDashboard({
|
|
84
|
+
version: manifest.version,
|
|
85
|
+
companion,
|
|
86
|
+
companionVersion: running?.version ?? other?.version,
|
|
87
|
+
autostart,
|
|
88
|
+
engines: readLocalEngineStatus(),
|
|
89
|
+
includeLinks: options.includeLinks,
|
|
90
|
+
hint,
|
|
91
|
+
}, options.animate);
|
|
92
|
+
return { running, other, autostart };
|
|
93
|
+
};
|
|
70
94
|
const acquire = async () => {
|
|
71
95
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
72
96
|
try {
|
|
@@ -148,7 +172,14 @@ const serve = async (backgroundLog) => {
|
|
|
148
172
|
await writeFile(recordPath, JSON.stringify({ pid: process.pid, port: address.port, token, version: manifest.version }), { mode: 0o600 });
|
|
149
173
|
process.once("SIGINT", () => { void shutdown(); });
|
|
150
174
|
process.once("SIGTERM", () => { void shutdown(); });
|
|
151
|
-
|
|
175
|
+
await printDashboard({
|
|
176
|
+
version: manifest.version,
|
|
177
|
+
companion: "running",
|
|
178
|
+
companionVersion: manifest.version,
|
|
179
|
+
engines: readLocalEngineStatus(),
|
|
180
|
+
includeLinks: !backgroundLog,
|
|
181
|
+
hint: backgroundLog ? `Local bridge ready at ${bridge.address}.` : "Press Ctrl+C to stop.",
|
|
182
|
+
});
|
|
152
183
|
process.send?.({ ready: true });
|
|
153
184
|
process.disconnect?.();
|
|
154
185
|
}
|
|
@@ -165,13 +196,14 @@ const serve = async (backgroundLog) => {
|
|
|
165
196
|
const start = async () => {
|
|
166
197
|
const existing = await control("status");
|
|
167
198
|
if (existing) {
|
|
168
|
-
|
|
199
|
+
await showStatus({ includeLinks: true });
|
|
169
200
|
return;
|
|
170
201
|
}
|
|
171
202
|
if (await bridgePresent())
|
|
172
203
|
throw new Error("The desktop app or another Companion server is running. Quit it first; this command will not replace it.");
|
|
173
204
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
174
205
|
const output = await open(logPath, "a", 0o600);
|
|
206
|
+
const indicator = startDrawsyIndicator("Starting local Companion");
|
|
175
207
|
try {
|
|
176
208
|
await new Promise((resolve, reject) => {
|
|
177
209
|
const child = spawn(process.execPath, [entry, "serve"], {
|
|
@@ -192,30 +224,27 @@ const start = async () => {
|
|
|
192
224
|
});
|
|
193
225
|
});
|
|
194
226
|
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
indicator.stop("Startup did not finish.");
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
195
231
|
finally {
|
|
196
232
|
await output.close();
|
|
197
233
|
}
|
|
198
|
-
|
|
234
|
+
indicator.stop("Local Companion started.");
|
|
235
|
+
await showStatus({ includeLinks: true });
|
|
199
236
|
};
|
|
200
|
-
const help = () => console.log(`Drawsy Companion ${manifest.version}
|
|
201
|
-
|
|
202
|
-
setup Start now and automatically when you sign in
|
|
203
|
-
start Start in the background for this login session
|
|
204
|
-
serve Run visibly in this terminal (Ctrl+C stops it)
|
|
205
|
-
stop Stop the CLI server; keep login startup configured
|
|
206
|
-
status Show server and automatic startup status
|
|
207
|
-
logs Show the latest local log output
|
|
208
|
-
autostart enable Enable startup at login
|
|
209
|
-
autostart disable Disable startup at login and stop its managed job
|
|
210
|
-
|
|
211
|
-
Requires Node.js 22 or later. Codex/OpenCode installation and login stay unchanged.
|
|
212
|
-
Update or remove the package with npm after disabling automatic startup.
|
|
213
|
-
Only the CLI's authenticated process is stopped; the desktop app is never killed.`);
|
|
214
237
|
try {
|
|
215
|
-
const [command = "
|
|
238
|
+
const [command = "welcome", option, ...extra] = process.argv.slice(2);
|
|
216
239
|
if (extra.length || (option && command !== "autostart" && !(command === "serve" && option === "--background-log")))
|
|
217
240
|
throw new Error("Unexpected arguments. Use drawsy-companion help.");
|
|
218
241
|
switch (command) {
|
|
242
|
+
case "welcome":
|
|
243
|
+
await showStatus({
|
|
244
|
+
includeLinks: true,
|
|
245
|
+
animate: true,
|
|
246
|
+
});
|
|
247
|
+
break;
|
|
219
248
|
case "serve":
|
|
220
249
|
await serve(option === "--background-log");
|
|
221
250
|
break;
|
|
@@ -230,17 +259,11 @@ try {
|
|
|
230
259
|
if (alive(running.pid))
|
|
231
260
|
throw new Error("Shutdown is still in progress. Check status before restarting.");
|
|
232
261
|
}
|
|
233
|
-
|
|
262
|
+
await showStatus({ hint: running ? "Companion CLI stopped." : "Companion CLI is not running." });
|
|
234
263
|
break;
|
|
235
264
|
}
|
|
236
265
|
case "status": {
|
|
237
|
-
|
|
238
|
-
const other = running ? null : await bridgePresent();
|
|
239
|
-
console.log(running ? `Companion CLI ${running.version} running.` : other ? `Another Companion server ${other.version ?? ""} is running.` : "Companion is not running.");
|
|
240
|
-
console.log(`Startup at login: ${await autostartEnabled() ? "enabled" : "disabled"}.`);
|
|
241
|
-
for (const engine of readLocalEngineStatus()) {
|
|
242
|
-
console.log(`${engine.name}: ${engine.id === "opencode" && process.platform === "win32" ? "not supported by Companion on Windows" : engine.installed ? `available${engine.version ? ` (${engine.version})` : ""}` : "not found"}.`);
|
|
243
|
-
}
|
|
266
|
+
await showStatus();
|
|
244
267
|
break;
|
|
245
268
|
}
|
|
246
269
|
case "logs": {
|
|
@@ -269,18 +292,29 @@ try {
|
|
|
269
292
|
if (action === "disable") {
|
|
270
293
|
await control("stop");
|
|
271
294
|
await disableAutostart();
|
|
272
|
-
|
|
295
|
+
await showStatus({ hint: "Startup at sign-in disabled." });
|
|
273
296
|
}
|
|
274
297
|
else if (action === "enable") {
|
|
275
298
|
if (await autostartEnabled() && await control("status")) {
|
|
276
|
-
|
|
299
|
+
await showStatus({ includeLinks: true, hint: "Ready at sign-in." });
|
|
277
300
|
break;
|
|
278
301
|
}
|
|
279
302
|
if (await control("status") || await bridgePresent())
|
|
280
303
|
throw new Error("Stop Companion first, then run setup. Existing sessions will not be interrupted automatically.");
|
|
281
304
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
282
|
-
|
|
283
|
-
|
|
305
|
+
const indicator = startDrawsyIndicator("Setting up sign-in startup");
|
|
306
|
+
try {
|
|
307
|
+
await enableAutostart(process.execPath, entry);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
indicator.stop("Setup could not be completed.");
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
indicator.stop("Startup at sign-in enabled.");
|
|
314
|
+
await showStatus({
|
|
315
|
+
includeLinks: true,
|
|
316
|
+
missingState: "starting",
|
|
317
|
+
});
|
|
284
318
|
}
|
|
285
319
|
else
|
|
286
320
|
throw new Error("Use autostart enable or autostart disable.");
|
|
@@ -289,7 +323,7 @@ try {
|
|
|
289
323
|
case "help":
|
|
290
324
|
case "--help":
|
|
291
325
|
case "-h":
|
|
292
|
-
|
|
326
|
+
await printHelp(manifest.version);
|
|
293
327
|
break;
|
|
294
328
|
case "--version":
|
|
295
329
|
console.log(manifest.version);
|