@mattstack/rt-client 0.4.1 → 0.6.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.
@@ -32,79 +32,10 @@ import { runCapture } from "./exec.ts";
32
32
  import { machineSettingsPath } from "./paths.ts";
33
33
  import { readStore } from "./stores.ts";
34
34
 
35
- // Full-URL forms: scheme://[user[:pass]@]host/path https, ssh, git, http, ...
36
- const URL_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/(?:[^@/]+@)?([^/]+)\/(.+)$/;
35
+ import { normalizeRemote, type RepoIdentity } from "./identity-codec.ts";
37
36
 
38
- // scp-like scp syntax: [user@]host:path (git@gitlab.com:group/repo.git).
39
- // Deliberately excludes anything starting with "/" (absolute local paths)
40
- // so a Windows-drive-letter-free local remote never falsely matches.
41
- const SCP_RE = /^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/;
42
-
43
- export type RepoIdentity =
44
- | { kind: "remote"; id: string }
45
- | { kind: "path"; id: string };
46
-
47
- /**
48
- * The wire form crosses the daemon socket, sits in board config, and lands in
49
- * console's `/runs/:repo/...` URL — all of which need one slash-free segment.
50
- * `encodeURIComponent` guarantees that and is exactly reversible.
51
- */
52
- export function serializeIdentity(id: RepoIdentity): string {
53
- return `${id.kind}:${encodeURIComponent(id.id)}`;
54
- }
55
-
56
- export function parseIdentity(wire: string): RepoIdentity | null {
57
- const colon = wire.indexOf(":");
58
- if (colon === -1) return null;
59
- const kind = wire.slice(0, colon);
60
- if (kind !== "remote" && kind !== "path") return null;
61
- const encoded = wire.slice(colon + 1);
62
- let id: string;
63
- try {
64
- id = decodeURIComponent(encoded);
65
- } catch {
66
- return null;
67
- }
68
- // Canonical wires only: the id segment must be byte-for-byte what
69
- // serializeIdentity emits. Guard sites validate with parseIdentity and then
70
- // use the WIRE as a single path component (repoDataDir et al.) — a
71
- // hand-built wire with a literal "/" ("path:../..") would otherwise parse
72
- // and escape the state directory.
73
- if (encodeURIComponent(id) !== encoded) return null;
74
- return { kind, id };
75
- }
76
-
77
- /**
78
- * Pure normalization: `remote` → `host/path` (lowercase host, `.git` and
79
- * embedded credentials stripped) or null when the remote doesn't match a
80
- * recognized host form (local paths, garbage input).
81
- */
82
- export function normalizeRemote(remote: string): string | null {
83
- const trimmed = remote.trim();
84
- if (!trimmed) return null;
85
-
86
- let host: string | undefined;
87
- let path: string | undefined;
88
-
89
- const urlMatch = URL_RE.exec(trimmed);
90
- if (urlMatch) {
91
- host = urlMatch[1];
92
- path = urlMatch[2];
93
- } else if (!trimmed.startsWith("/") && !trimmed.startsWith("~")) {
94
- const scpMatch = SCP_RE.exec(trimmed);
95
- if (scpMatch) {
96
- host = scpMatch[1];
97
- path = scpMatch[2];
98
- }
99
- }
100
-
101
- if (!host || !path) return null;
102
-
103
- const normalizedPath = path.replace(/\.git$/, "").replace(/^\/+/, "").replace(/\/+$/, "");
104
- if (!normalizedPath) return null;
105
-
106
- return `${host.toLowerCase()}/${normalizedPath}`;
107
- }
37
+ export { serializeIdentity, parseIdentity, normalizeRemote } from "./identity-codec.ts";
38
+ export type { RepoIdentity } from "./identity-codec.ts";
108
39
 
109
40
  /**
110
41
  * The sync helper every non-derivation call site uses: machine-store
@@ -228,6 +228,13 @@ export const REGISTRY: readonly SettingDef[] = [
228
228
  merge: "replace",
229
229
  description: "Absolute path to the installed mattstack.app bundle, written by the app at launch so rt stops hardcoding ~/Applications.",
230
230
  },
231
+ {
232
+ key: "mattstack.mode",
233
+ type: "string",
234
+ scopes: ["machine"],
235
+ merge: "replace",
236
+ description: "The machine's intended flavor, \"dev\" or \"prod\". Normally written by `rt settings dev-mode` after a successful handoff; a manual `rt settings set` is the blessed repair escape hatch — the daemon park loop converges on whatever this says. Unset ⇒ derived from the dev wrapper's presence.",
237
+ },
231
238
  {
232
239
  key: "rt.integrations",
233
240
  type: "object",
package/src/transport.ts CHANGED
@@ -19,6 +19,15 @@ export interface RtResponse<T = unknown> {
19
19
  export interface RtClientOptions {
20
20
  sockPath?: string;
21
21
  wsUrl?: string;
22
+ /** Per-call override of rtCommand's own default (15s); chat's pulse wrapper needs an 800ms hook budget. */
23
+ timeoutMs?: number;
24
+ /**
25
+ * Test seam for createRelay (relay.ts): swaps the daemon subscription for
26
+ * a fake without a live WebSocket server. Typed structurally against
27
+ * relay.ts's `subscribe` rather than importing its RelayEventType, which
28
+ * would make this module depend on the one that already depends on it.
29
+ */
30
+ subscribeImpl?: (onEvent: (type: string, data: unknown) => void, opts?: RtClientOptions) => () => void;
22
31
  }
23
32
 
24
33
  // Duplicates the ~/.mattstack/rt layout: rt-client has no dependency on rt's