@terminus-ai/cli 0.0.2 → 0.0.3

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
@@ -767,7 +767,7 @@ Bare `terminus` (or `terminus help`) prints its version and the everyday command
767
767
  with what it does on one line (`<word>` is a placeholder, `[ ]` is optional):
768
768
 
769
769
  ```text
770
- Terminus v0.0.2, created by Terminus Intelligence
770
+ Terminus v0.0.3, created by Terminus Intelligence
771
771
 
772
772
  Account:
773
773
  terminus login Open your browser and sign in
package/bin/devserver.mjs CHANGED
@@ -28,7 +28,6 @@
28
28
  * visitor chose, and only an agent that offers a choice has one.
29
29
  */
30
30
 
31
- import { execFile } from "node:child_process";
32
31
  import { randomBytes } from "node:crypto";
33
32
  import { watch } from "node:fs";
34
33
  import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
@@ -37,6 +36,7 @@ import path from "node:path";
37
36
  import { fileURLToPath } from "node:url";
38
37
 
39
38
  import { DEV_DIRECTORY, ensureDevDirectory, TERMINUS_DIRECTORY } from "./files.mjs";
39
+ import { openWithPlatform } from "./open.mjs";
40
40
 
41
41
  import {
42
42
  AgentDevSession,
@@ -719,22 +719,6 @@ async function listLane(root, skip) {
719
719
  return entries;
720
720
  }
721
721
 
722
- export async function openWithPlatform(target) {
723
- const command =
724
- process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
725
- const args = process.platform === "win32" ? ["/c", "start", "", target] : [target];
726
- return new Promise((resolve) => {
727
- try {
728
- const child = execFile(command, args, { detached: true, stdio: "ignore" }, (error) => {
729
- resolve(!error);
730
- });
731
- child.unref();
732
- } catch {
733
- resolve(false);
734
- }
735
- });
736
- }
737
-
738
722
  // ---------------------------------------------------------------------------
739
723
  // The server
740
724
  // ---------------------------------------------------------------------------
package/bin/open.mjs ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Opening a link or a file the way the person's own system does: the browser
3
+ * for a URL, the default app for a file. Every command that opens something
4
+ * comes through here — `terminus login`, `terminus dev`, `terminus service
5
+ * dev`, and the dev UI's Open — so the Windows rules below live once.
6
+ *
7
+ * macOS and Linux take the target as one argument to `open` / `xdg-open`,
8
+ * and no shell reads it. Windows opens things with `start`, a cmd.exe
9
+ * built-in, and cmd.exe parses the command line itself: an unquoted `&` ends
10
+ * the command (so `terminus login` opened its sign-in URL only up to the
11
+ * first `&`, and the page refused the sign-in), and runs whatever follows (a
12
+ * file named `notes&calc.exe` ran calc.exe). So the target goes to `start`
13
+ * inside double quotes, where cmd.exe reads its operators as text, with
14
+ * delayed expansion off, and the command line is handed over verbatim:
15
+ * Node's own quoting escapes quotes the way C programs read them, which
16
+ * cmd.exe does not. `%NAME%` still expands inside quotes, but only for a
17
+ * variable that is set, and a URL's `%3A%2F` names none. The failure list
18
+ * behind this is in test/open.test.mjs.
19
+ */
20
+
21
+ import { execFile } from "node:child_process";
22
+
23
+ /**
24
+ * The program, arguments and spawn options that open `target` on
25
+ * `platform`, or null for a target that cannot be quoted for cmd.exe: a
26
+ * double quote or a line break would end the quoting early, and neither
27
+ * occurs in a Windows file name or a serialized URL.
28
+ */
29
+ export function openCommand(target, platform = process.platform) {
30
+ if (platform === "darwin") return { command: "open", args: [target], options: {} };
31
+ if (platform !== "win32") return { command: "xdg-open", args: [target], options: {} };
32
+ if (/["\r\n\0]/u.test(target)) return null;
33
+ // /s strips exactly the outer quotes around the command; START takes its
34
+ // first quoted argument as the window title, so an empty one goes first.
35
+ return {
36
+ command: "cmd",
37
+ args: ["/d", "/v:off", "/s", "/c", `"start "" "${target}""`],
38
+ options: { windowsVerbatimArguments: true },
39
+ };
40
+ }
41
+
42
+ /** Open `target`; resolves whether the system accepted it. */
43
+ export async function openWithPlatform(target) {
44
+ const plan = openCommand(target);
45
+ if (!plan) return false;
46
+ return new Promise((resolve) => {
47
+ try {
48
+ const child = execFile(
49
+ plan.command,
50
+ plan.args,
51
+ { ...plan.options, detached: true, stdio: "ignore" },
52
+ (error) => {
53
+ resolve(!error);
54
+ },
55
+ );
56
+ child.unref();
57
+ } catch {
58
+ resolve(false);
59
+ }
60
+ });
61
+ }
@@ -36,7 +36,8 @@ import {
36
36
  refuseForeignHost,
37
37
  resolveDevPortRange,
38
38
  } from "./dev-ports.mjs";
39
- import { contentTypeFor, openWithPlatform, timingSafeTokenMatch } from "./devserver.mjs";
39
+ import { contentTypeFor, timingSafeTokenMatch } from "./devserver.mjs";
40
+ import { openWithPlatform } from "./open.mjs";
40
41
 
41
42
  const SERVICE_DEV_PORT = 4750;
42
43
  const PORT_ATTEMPTS = 50;
package/bin/terminus.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { Buffer } from "node:buffer";
4
- import { execFile } from "node:child_process";
5
4
  import { randomBytes } from "node:crypto";
6
5
  import { mkdir, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
7
6
  import { createServer } from "node:http";
@@ -43,6 +42,7 @@ import {
43
42
  sessionPath,
44
43
  sessionTokenIsUsable,
45
44
  } from "./http.mjs";
45
+ import { openWithPlatform } from "./open.mjs";
46
46
  import { SYNC_DIRECTORY, byKey, entryKey, localChanges, readSyncRecord, writeSyncRecord } from "./sync.mjs";
47
47
  import { formatReleaseVersion, parseReleaseVersion } from "./versioning.mjs";
48
48
 
@@ -502,7 +502,7 @@ async function loginWithBrowser(base, flags = {}) {
502
502
  console.log("Opening your browser to log in to Terminus.");
503
503
  console.log(`If it does not open, visit:\n${loginUrl}`);
504
504
  if (!flags.no_browser) {
505
- await openBrowser(loginUrl);
505
+ await openWithPlatform(loginUrl);
506
506
  }
507
507
 
508
508
  const callbackResult = await callback.wait();
@@ -739,22 +739,6 @@ async function startLoginCallbackServer(expectedState, brandOrigin) {
739
739
  };
740
740
  }
741
741
 
742
- async function openBrowser(url) {
743
- const command =
744
- process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
745
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
746
- return new Promise((resolve) => {
747
- try {
748
- const child = execFile(command, args, { detached: true, stdio: "ignore" }, (error) => {
749
- resolve(!error);
750
- });
751
- child.unref();
752
- } catch {
753
- resolve(false);
754
- }
755
- });
756
- }
757
-
758
742
  /** A login with the profile of the account it signs in as. */
759
743
  async function loginWithCurrentProfile(api, login, currentProfile = null) {
760
744
  const profile = currentProfile ?? await api.me();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terminus-ai/cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Terminus CLI (`terminus`): search and use skills, and develop, run, and publish apps, services, and agents on the Terminus platform.",
5
5
  "license": "MIT",
6
6
  "type": "module",