@nextclaw/companion 0.1.1-beta.0 → 0.1.1-beta.2

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/{src/launcher/index.js → electron/launcher.js} +2 -2
  3. package/dist/{src → electron}/main.js +3 -3
  4. package/dist/{src/preload/index.js → electron/preload.js} +1 -6
  5. package/dist/{src → electron}/services/companion-application.service.js +14 -16
  6. package/dist/{src/stores/companion-runtime-state.store.js → electron/services/companion-runtime-state-persistence.service.js} +3 -3
  7. package/dist/{src/stores/companion-window-position.store.js → electron/services/companion-window-position-persistence.service.js} +4 -4
  8. package/dist/{src → electron}/services/companion-window.service.js +15 -26
  9. package/dist/electron/types/companion-shell.types.js +2 -0
  10. package/dist/src/app/app.js +17 -0
  11. package/dist/src/app/components/companion-shell.container.js +20 -0
  12. package/dist/src/app/main.js +10 -0
  13. package/dist/src/app/providers/companion-presenter.provider.js +17 -0
  14. package/dist/src/managers/companion-runtime.manager.js +152 -0
  15. package/dist/src/managers/companion-runtime.manager.test.js +45 -0
  16. package/dist/src/managers/companion-shell.manager.js +22 -0
  17. package/dist/src/presenters/companion.presenter.js +37 -0
  18. package/dist/src/services/companion-runtime-client.service.js +16 -68
  19. package/dist/src/stores/companion-runtime.store.js +27 -0
  20. package/dist/src/stores/companion-shell.store.js +18 -0
  21. package/dist/ui/assets/dist-D4AJsX_N.js +1 -0
  22. package/dist/ui/assets/index-BHBNwOpX.js +8 -0
  23. package/dist/ui/assets/index-DPctoCcE.css +1 -0
  24. package/dist/ui/index.html +13 -0
  25. package/{src/launcher/index.ts → electron/launcher.ts} +2 -2
  26. package/{src → electron}/main.ts +4 -3
  27. package/electron/preload.ts +7 -0
  28. package/{src → electron}/services/companion-application.service.ts +16 -20
  29. package/{src/stores/companion-runtime-state.store.ts → electron/services/companion-runtime-state-persistence.service.ts} +1 -1
  30. package/{src/stores/companion-window-position.store.ts → electron/services/companion-window-position-persistence.service.ts} +3 -3
  31. package/{src → electron}/services/companion-window.service.ts +23 -30
  32. package/electron/types/companion-shell.types.ts +8 -0
  33. package/index.html +12 -0
  34. package/module-structure.config.json +25 -0
  35. package/package.json +21 -9
  36. package/postcss.config.mjs +6 -0
  37. package/scripts/smoke.mjs +4 -3
  38. package/src/app/app.tsx +17 -0
  39. package/src/app/components/companion-shell.container.tsx +67 -0
  40. package/src/app/index.css +37 -0
  41. package/src/app/main.tsx +15 -0
  42. package/src/app/providers/companion-presenter.provider.tsx +27 -0
  43. package/src/managers/companion-runtime.manager.test.ts +56 -0
  44. package/src/managers/companion-runtime.manager.ts +191 -0
  45. package/src/managers/companion-shell.manager.ts +21 -0
  46. package/src/module-structure.config.json +22 -0
  47. package/src/presenters/companion.presenter.ts +36 -0
  48. package/src/services/companion-runtime-client.service.ts +21 -84
  49. package/src/stores/companion-runtime.store.ts +51 -0
  50. package/src/stores/companion-shell.store.ts +27 -0
  51. package/src/types/companion-bridge.types.d.ts +11 -0
  52. package/src/types/companion.types.ts +0 -10
  53. package/tailwind.config.mjs +32 -0
  54. package/tsconfig.json +2 -1
  55. package/vite.config.mts +16 -0
  56. package/dist/src/companion-session-view.service.test.js +0 -27
  57. package/dist/src/services/companion-session-view.service.js +0 -52
  58. package/dist/src/utils/companion-renderer-html.utils.js +0 -194
  59. package/src/companion-session-view.service.test.ts +0 -37
  60. package/src/preload/index.ts +0 -13
  61. package/src/services/companion-session-view.service.ts +0 -63
  62. package/src/utils/companion-renderer-html.utils.ts +0 -192
  63. /package/dist/{src → electron}/services/companion-tray.service.js +0 -0
  64. /package/{src → electron}/services/companion-tray.service.ts +0 -0
@@ -1,194 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.renderCompanionHtml = renderCompanionHtml;
4
- const COMPANION_HTML = `<!doctype html>
5
- <html lang="en">
6
- <head>
7
- <meta charset="utf-8" />
8
- <meta name="viewport" content="width=device-width,initial-scale=1" />
9
- <title>NextClaw Companion</title>
10
- <style>
11
- :root {
12
- color-scheme: light;
13
- --ring: rgba(12, 84, 190, 0.2);
14
- --panel: rgba(255, 255, 255, 0.88);
15
- --ink: #16324f;
16
- --muted: #5f6c7c;
17
- --idle: #d7dce2;
18
- --running: #26a269;
19
- --offline: #d64545;
20
- }
21
- * { box-sizing: border-box; }
22
- html, body {
23
- margin: 0;
24
- width: 100%;
25
- height: 100%;
26
- overflow: hidden;
27
- background: transparent;
28
- font-family: "SF Pro Display", "Helvetica Neue", sans-serif;
29
- }
30
- body {
31
- display: grid;
32
- place-items: center;
33
- }
34
- button {
35
- border: 0;
36
- background: none;
37
- padding: 0;
38
- cursor: pointer;
39
- }
40
- .shell {
41
- width: 112px;
42
- height: 132px;
43
- display: grid;
44
- justify-items: center;
45
- gap: 8px;
46
- }
47
- .avatar {
48
- width: 96px;
49
- height: 96px;
50
- border-radius: 28px;
51
- background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(235,241,247,0.94));
52
- box-shadow: 0 14px 36px rgba(13, 30, 51, 0.16), inset 0 0 0 1px rgba(255,255,255,0.82);
53
- position: relative;
54
- display: grid;
55
- place-items: center;
56
- overflow: hidden;
57
- -webkit-app-region: drag;
58
- }
59
- .avatar::after {
60
- content: "";
61
- position: absolute;
62
- inset: 4px;
63
- border-radius: 24px;
64
- box-shadow: inset 0 0 0 1px var(--ring);
65
- pointer-events: none;
66
- }
67
- .avatar img {
68
- width: 100%;
69
- height: 100%;
70
- object-fit: cover;
71
- }
72
- .initials {
73
- width: 100%;
74
- height: 100%;
75
- display: grid;
76
- place-items: center;
77
- color: var(--ink);
78
- font-size: 28px;
79
- font-weight: 700;
80
- letter-spacing: 0;
81
- background: radial-gradient(circle at top, rgba(255,255,255,0.98), rgba(223,232,242,0.96));
82
- }
83
- .status {
84
- position: absolute;
85
- right: 8px;
86
- bottom: 8px;
87
- width: 14px;
88
- height: 14px;
89
- border-radius: 999px;
90
- border: 2px solid var(--panel);
91
- background: var(--idle);
92
- }
93
- .status[data-state="running"] { background: var(--running); }
94
- .status[data-state="offline"] { background: var(--offline); }
95
- .meta {
96
- width: 100%;
97
- padding: 0 4px;
98
- text-align: center;
99
- -webkit-app-region: no-drag;
100
- cursor: pointer;
101
- }
102
- .title {
103
- color: var(--ink);
104
- font-size: 12px;
105
- font-weight: 700;
106
- line-height: 1.25;
107
- white-space: nowrap;
108
- overflow: hidden;
109
- text-overflow: ellipsis;
110
- }
111
- .subtitle {
112
- color: var(--muted);
113
- font-size: 11px;
114
- line-height: 1.2;
115
- margin-top: 2px;
116
- white-space: nowrap;
117
- overflow: hidden;
118
- text-overflow: ellipsis;
119
- }
120
- .actions {
121
- position: absolute;
122
- top: 6px;
123
- right: 6px;
124
- display: flex;
125
- gap: 4px;
126
- -webkit-app-region: no-drag;
127
- }
128
- .icon-button {
129
- width: 18px;
130
- height: 18px;
131
- border-radius: 999px;
132
- background: rgba(22, 50, 79, 0.12);
133
- color: var(--ink);
134
- font-size: 11px;
135
- font-weight: 700;
136
- display: grid;
137
- place-items: center;
138
- }
139
- </style>
140
- </head>
141
- <body>
142
- <div class="shell">
143
- <div class="avatar">
144
- <div class="actions">
145
- <button class="icon-button" id="close-button" type="button" aria-label="Quit Companion">x</button>
146
- </div>
147
- <div class="initials" id="initials">NC</div>
148
- <img id="avatar-image" alt="" hidden />
149
- <span class="status" id="status-dot" data-state="idle"></span>
150
- </div>
151
- <div class="meta" id="open-button" role="button" tabindex="0" aria-label="Open NextClaw">
152
- <div class="title" id="title">NextClaw</div>
153
- <div class="subtitle" id="subtitle">Waiting</div>
154
- </div>
155
- </div>
156
- <script>
157
- const titleNode = document.getElementById("title");
158
- const subtitleNode = document.getElementById("subtitle");
159
- const initialsNode = document.getElementById("initials");
160
- const avatarImageNode = document.getElementById("avatar-image");
161
- const statusNode = document.getElementById("status-dot");
162
- const openButtonNode = document.getElementById("open-button");
163
- const closeButtonNode = document.getElementById("close-button");
164
- const applyView = (view) => {
165
- titleNode.textContent = view.title;
166
- subtitleNode.textContent = view.subtitle;
167
- initialsNode.textContent = (view.title || "NC").slice(0, 2).toUpperCase();
168
- statusNode.dataset.state = view.state;
169
- if (view.avatarUrl) {
170
- avatarImageNode.src = view.avatarUrl;
171
- avatarImageNode.hidden = false;
172
- initialsNode.hidden = true;
173
- } else {
174
- avatarImageNode.hidden = true;
175
- avatarImageNode.removeAttribute("src");
176
- initialsNode.hidden = false;
177
- }
178
- };
179
- window.nextclawCompanion.onView(applyView);
180
- openButtonNode.addEventListener("click", () => window.nextclawCompanion.open());
181
- openButtonNode.addEventListener("keydown", (event) => {
182
- if (event.key === "Enter" || event.key === " ") {
183
- event.preventDefault();
184
- window.nextclawCompanion.open();
185
- }
186
- });
187
- closeButtonNode.addEventListener("click", () => window.nextclawCompanion.quit());
188
- window.nextclawCompanion.ready();
189
- </script>
190
- </body>
191
- </html>`;
192
- function renderCompanionHtml() {
193
- return COMPANION_HTML;
194
- }
@@ -1,37 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { CompanionSessionViewService } from "./services/companion-session-view.service.js";
3
-
4
- describe("CompanionSessionViewService", () => {
5
- it("prefers the most recently updated running session", () => {
6
- const service = new CompanionSessionViewService(
7
- "http://127.0.0.1:55667",
8
- (agentId) => `http://127.0.0.1:55667/api/agents/${agentId}/avatar`
9
- );
10
-
11
- const view = service.selectView({
12
- agents: [{ id: "writer", displayName: "Writer" }],
13
- sessions: [
14
- { sessionId: "older", agentId: "writer", messageCount: 1, updatedAt: "2026-05-05T00:00:00.000Z", status: "running" },
15
- { sessionId: "newer", agentId: "writer", messageCount: 2, updatedAt: "2026-05-06T00:00:00.000Z", status: "running" }
16
- ]
17
- });
18
-
19
- expect(view.sessionId).toBe("newer");
20
- expect(view.title).toBe("Writer");
21
- });
22
-
23
- it("falls back to an idle shell view when no running session exists", () => {
24
- const service = new CompanionSessionViewService(
25
- "http://127.0.0.1:55667",
26
- () => "http://127.0.0.1:55667/api/agents/default/avatar"
27
- );
28
-
29
- const view = service.selectView({
30
- agents: [],
31
- sessions: []
32
- });
33
-
34
- expect(view.state).toBe("idle");
35
- expect(view.subtitle).toBe("No active agent");
36
- });
37
- });
@@ -1,13 +0,0 @@
1
- import { contextBridge, ipcRenderer } from "electron";
2
- import type { CompanionAvatarView } from "../types/companion.types.js";
3
-
4
- contextBridge.exposeInMainWorld("nextclawCompanion", {
5
- onView: (listener: (view: CompanionAvatarView) => void) => {
6
- ipcRenderer.on("companion:view", (_event, view: CompanionAvatarView) => {
7
- listener(view);
8
- });
9
- },
10
- open: () => ipcRenderer.invoke("companion:open"),
11
- quit: () => ipcRenderer.invoke("companion:quit"),
12
- ready: () => ipcRenderer.send("companion:ready")
13
- });
@@ -1,63 +0,0 @@
1
- import type {
2
- CompanionAgentProfile,
3
- CompanionAvatarView,
4
- CompanionOfflineReason,
5
- CompanionSessionViewInput
6
- } from "../types/companion.types.js";
7
-
8
- export class CompanionSessionViewService {
9
- constructor(
10
- private readonly baseUrl: string,
11
- private readonly resolveAvatarUrl: (agentId: string) => string
12
- ) {}
13
-
14
- readonly selectView = (input: CompanionSessionViewInput): CompanionAvatarView => {
15
- const runningSession = [...input.sessions]
16
- .filter((session) => session.status === "running")
17
- .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
18
-
19
- if (!runningSession) {
20
- return {
21
- state: "idle",
22
- title: "NextClaw",
23
- subtitle: "No active agent",
24
- openUrl: this.baseUrl
25
- };
26
- }
27
-
28
- const agent = this.findAgent(input.agents, runningSession.agentId);
29
-
30
- return {
31
- state: "running",
32
- title: agent?.displayName?.trim() || runningSession.agentId || "Active Agent",
33
- subtitle: runningSession.sessionId,
34
- avatarUrl: runningSession.agentId ? this.resolveAvatar(agent, runningSession.agentId) : undefined,
35
- sessionId: runningSession.sessionId,
36
- agentId: runningSession.agentId,
37
- openUrl: this.baseUrl
38
- };
39
- };
40
-
41
- readonly createOfflineView = (reason?: CompanionOfflineReason): CompanionAvatarView => {
42
- return {
43
- state: "offline",
44
- title: "NextClaw",
45
- subtitle: reason?.summary?.trim() || "Runtime unavailable",
46
- openUrl: this.baseUrl
47
- };
48
- };
49
-
50
- private readonly findAgent = (
51
- agents: CompanionAgentProfile[],
52
- agentId: string | undefined
53
- ): CompanionAgentProfile | null => {
54
- if (!agentId) {
55
- return null;
56
- }
57
- return agents.find((agent) => agent.id === agentId) ?? null;
58
- };
59
-
60
- private readonly resolveAvatar = (agent: CompanionAgentProfile | null, agentId: string): string => {
61
- return agent?.avatarUrl?.trim() || this.resolveAvatarUrl(agentId);
62
- };
63
- }
@@ -1,192 +0,0 @@
1
- const COMPANION_HTML = `<!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width,initial-scale=1" />
6
- <title>NextClaw Companion</title>
7
- <style>
8
- :root {
9
- color-scheme: light;
10
- --ring: rgba(12, 84, 190, 0.2);
11
- --panel: rgba(255, 255, 255, 0.88);
12
- --ink: #16324f;
13
- --muted: #5f6c7c;
14
- --idle: #d7dce2;
15
- --running: #26a269;
16
- --offline: #d64545;
17
- }
18
- * { box-sizing: border-box; }
19
- html, body {
20
- margin: 0;
21
- width: 100%;
22
- height: 100%;
23
- overflow: hidden;
24
- background: transparent;
25
- font-family: "SF Pro Display", "Helvetica Neue", sans-serif;
26
- }
27
- body {
28
- display: grid;
29
- place-items: center;
30
- }
31
- button {
32
- border: 0;
33
- background: none;
34
- padding: 0;
35
- cursor: pointer;
36
- }
37
- .shell {
38
- width: 112px;
39
- height: 132px;
40
- display: grid;
41
- justify-items: center;
42
- gap: 8px;
43
- }
44
- .avatar {
45
- width: 96px;
46
- height: 96px;
47
- border-radius: 28px;
48
- background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(235,241,247,0.94));
49
- box-shadow: 0 14px 36px rgba(13, 30, 51, 0.16), inset 0 0 0 1px rgba(255,255,255,0.82);
50
- position: relative;
51
- display: grid;
52
- place-items: center;
53
- overflow: hidden;
54
- -webkit-app-region: drag;
55
- }
56
- .avatar::after {
57
- content: "";
58
- position: absolute;
59
- inset: 4px;
60
- border-radius: 24px;
61
- box-shadow: inset 0 0 0 1px var(--ring);
62
- pointer-events: none;
63
- }
64
- .avatar img {
65
- width: 100%;
66
- height: 100%;
67
- object-fit: cover;
68
- }
69
- .initials {
70
- width: 100%;
71
- height: 100%;
72
- display: grid;
73
- place-items: center;
74
- color: var(--ink);
75
- font-size: 28px;
76
- font-weight: 700;
77
- letter-spacing: 0;
78
- background: radial-gradient(circle at top, rgba(255,255,255,0.98), rgba(223,232,242,0.96));
79
- }
80
- .status {
81
- position: absolute;
82
- right: 8px;
83
- bottom: 8px;
84
- width: 14px;
85
- height: 14px;
86
- border-radius: 999px;
87
- border: 2px solid var(--panel);
88
- background: var(--idle);
89
- }
90
- .status[data-state="running"] { background: var(--running); }
91
- .status[data-state="offline"] { background: var(--offline); }
92
- .meta {
93
- width: 100%;
94
- padding: 0 4px;
95
- text-align: center;
96
- -webkit-app-region: no-drag;
97
- cursor: pointer;
98
- }
99
- .title {
100
- color: var(--ink);
101
- font-size: 12px;
102
- font-weight: 700;
103
- line-height: 1.25;
104
- white-space: nowrap;
105
- overflow: hidden;
106
- text-overflow: ellipsis;
107
- }
108
- .subtitle {
109
- color: var(--muted);
110
- font-size: 11px;
111
- line-height: 1.2;
112
- margin-top: 2px;
113
- white-space: nowrap;
114
- overflow: hidden;
115
- text-overflow: ellipsis;
116
- }
117
- .actions {
118
- position: absolute;
119
- top: 6px;
120
- right: 6px;
121
- display: flex;
122
- gap: 4px;
123
- -webkit-app-region: no-drag;
124
- }
125
- .icon-button {
126
- width: 18px;
127
- height: 18px;
128
- border-radius: 999px;
129
- background: rgba(22, 50, 79, 0.12);
130
- color: var(--ink);
131
- font-size: 11px;
132
- font-weight: 700;
133
- display: grid;
134
- place-items: center;
135
- }
136
- </style>
137
- </head>
138
- <body>
139
- <div class="shell">
140
- <div class="avatar">
141
- <div class="actions">
142
- <button class="icon-button" id="close-button" type="button" aria-label="Quit Companion">x</button>
143
- </div>
144
- <div class="initials" id="initials">NC</div>
145
- <img id="avatar-image" alt="" hidden />
146
- <span class="status" id="status-dot" data-state="idle"></span>
147
- </div>
148
- <div class="meta" id="open-button" role="button" tabindex="0" aria-label="Open NextClaw">
149
- <div class="title" id="title">NextClaw</div>
150
- <div class="subtitle" id="subtitle">Waiting</div>
151
- </div>
152
- </div>
153
- <script>
154
- const titleNode = document.getElementById("title");
155
- const subtitleNode = document.getElementById("subtitle");
156
- const initialsNode = document.getElementById("initials");
157
- const avatarImageNode = document.getElementById("avatar-image");
158
- const statusNode = document.getElementById("status-dot");
159
- const openButtonNode = document.getElementById("open-button");
160
- const closeButtonNode = document.getElementById("close-button");
161
- const applyView = (view) => {
162
- titleNode.textContent = view.title;
163
- subtitleNode.textContent = view.subtitle;
164
- initialsNode.textContent = (view.title || "NC").slice(0, 2).toUpperCase();
165
- statusNode.dataset.state = view.state;
166
- if (view.avatarUrl) {
167
- avatarImageNode.src = view.avatarUrl;
168
- avatarImageNode.hidden = false;
169
- initialsNode.hidden = true;
170
- } else {
171
- avatarImageNode.hidden = true;
172
- avatarImageNode.removeAttribute("src");
173
- initialsNode.hidden = false;
174
- }
175
- };
176
- window.nextclawCompanion.onView(applyView);
177
- openButtonNode.addEventListener("click", () => window.nextclawCompanion.open());
178
- openButtonNode.addEventListener("keydown", (event) => {
179
- if (event.key === "Enter" || event.key === " ") {
180
- event.preventDefault();
181
- window.nextclawCompanion.open();
182
- }
183
- });
184
- closeButtonNode.addEventListener("click", () => window.nextclawCompanion.quit());
185
- window.nextclawCompanion.ready();
186
- </script>
187
- </body>
188
- </html>`;
189
-
190
- export function renderCompanionHtml(): string {
191
- return COMPANION_HTML;
192
- }