@drawsy/companion 0.1.28 → 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 CHANGED
@@ -6,13 +6,13 @@ The CLI runs the same local Companion server without the desktop app. It connect
6
6
 
7
7
  - Windows, macOS, or Linux.
8
8
  - Node.js 22 or later with npm, installed for your user account.
9
- - The public npm package `@drawsy/companion`, once it has been published and Drawsy's ownership of the `@drawsy` npm scope is confirmed.
9
+ - The public npm package [@drawsy/companion](https://www.npmjs.com/package/@drawsy/companion).
10
10
 
11
- The npm commands below are not live until the package is published to the public registry and scope ownership is confirmed. A public package does not require a paid private npm plan. No paid app signing certificate or Microsoft Store approval is required for this Node.js CLI package; the separate desktop app has its own release requirements.
11
+ The package is published under Drawsy's npm account. A public package does not require a paid private npm plan. No paid app signing certificate or Microsoft Store approval is required for this Node.js CLI package; the separate desktop app has its own release requirements.
12
12
 
13
13
  ## Install permanently
14
14
 
15
- After the availability conditions above are met, install the public npm package:
15
+ Install the public npm package:
16
16
 
17
17
  ```sh
18
18
  npm install --global --ignore-scripts @drawsy/companion
@@ -26,9 +26,9 @@ 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. For app details, see [app.drawsyai.com](https://app.drawsyai.com). Closing the terminal does not stop the background server.
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
- To install and set up in one line, after the public npm package is published and the `@drawsy` scope ownership is confirmed:
31
+ To install and set up in one line:
32
32
 
33
33
  macOS/Linux:
34
34
 
@@ -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 published GitHub release tarball, use its versioned `.tgz` asset. For CLI version 0.1.28:
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.28/drawsy-companion-0.1.28.tgz
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
- Run `drawsy-companion autostart disable`, then `drawsy-companion stop` before updating from the public npm registry:
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 `drawsy-companion setup` after installation to restore background startup. This also refreshes the saved Node.js executable path if your Node installation changed.
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
 
@@ -92,7 +94,7 @@ This does not uninstall your Codex/OpenCode installation.
92
94
 
93
95
  ## Release preparation
94
96
 
95
- Before the first public publication, a maintainer must control the npm account and `@drawsy` scope. Follow [the npm publishing guide](npm-publishing.md) to stage and approve the initial package, then configure GitHub trusted publishing. Future public package releases use this repository's **Publish Companion CLI to npm** GitHub workflow. It requires the current `main` commit, rejects a mismatched existing CLI version tag, builds and packages the CLI, and publishes the package with npm provenance. It runs no tests and launches no app or server.
97
+ The first public version, 0.1.28, was built and uploaded by GitHub Actions and approved through npm's staged publication flow. Follow [the npm publishing guide](https://github.com/DrawsyAI/drawsy-ai-companion/blob/main/docs/npm-publishing.md) for GitHub trusted publishing setup. Future public package releases use this repository's **Publish Companion CLI to npm** GitHub workflow. It requires the current `main` commit, rejects a mismatched existing CLI version tag, builds and packages the CLI, and publishes the package with npm provenance. It runs no tests and launches no app or server.
96
98
 
97
99
  The **Build Companion CLI release** GitHub workflow builds and packages a selected commit, runs no tests, and launches no app or server. It uploads the tarball and `CLI-SHA256SUMS` to a draft release named `cli-v` followed by the package version. Review and manually verify the draft before publishing it; existing published releases are never replaced. This GitHub tarball is an optional install source.
98
100
 
@@ -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
+ }
@@ -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
- console.log(`Drawsy Companion ${manifest.version} running at ${bridge.address}`);
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
- console.log(`Companion ${existing.version} is already running.`);
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
- console.log(`Companion ${manifest.version} is running in the background. Open https://drawsyai.com`);
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 = "help", option, ...extra] = process.argv.slice(2);
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
- console.log(running ? "Companion CLI stopped." : "Companion CLI is not running.");
262
+ await showStatus({ hint: running ? "Companion CLI stopped." : "Companion CLI is not running." });
234
263
  break;
235
264
  }
236
265
  case "status": {
237
- const running = await control("status");
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
- console.log("Startup at login disabled; CLI shutdown requested.");
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
- console.log("Companion running; startup at login enabled.");
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
- await enableAutostart(process.execPath, entry);
283
- console.log("Startup at login enabled. Run drawsy-companion status to check readiness; open https://drawsyai.com when running.");
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
- help();
326
+ await printHelp(manifest.version);
293
327
  break;
294
328
  case "--version":
295
329
  console.log(manifest.version);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawsy/companion",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Drawsy's local Companion server and background command-line controls",