@zonease/happy 0.6.39 → 0.6.41

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 (78) hide show
  1. package/bin/happy.mjs +10 -0
  2. package/dist/{api-CIZKyfSn.mjs → api-K2-jr9ar.mjs} +86 -6
  3. package/dist/{api-CwOCQeLH.cjs → api-XeHlmApX.cjs} +87 -7
  4. package/dist/{auth-BIleVCw0.cjs → auth-BQvEuSs5.cjs} +2 -2
  5. package/dist/{auth-Cas9DR3b.cjs → auth-BbvPcnkY.cjs} +4 -4
  6. package/dist/{auth-0VnlXT9f.mjs → auth-CjLkoM3m.mjs} +4 -4
  7. package/dist/{auth-BE8dD20T.mjs → auth-DS8fzlin.mjs} +2 -2
  8. package/dist/{browser-Dll5hxOV.mjs → browser-C05Ak6RL.mjs} +1 -1
  9. package/dist/{browser-Cc08m7of.cjs → browser-C05SP_Tt.cjs} +1 -1
  10. package/dist/{claudeLocal-WkgLbl1a.cjs → claudeLocal-CpDhLCcZ.cjs} +1 -1
  11. package/dist/{claudeLocal-CPT3zcIU.mjs → claudeLocal-DJNRlHIC.mjs} +1 -1
  12. package/dist/{config-Draa-5XC.cjs → config-B0mns9fF.cjs} +1 -1
  13. package/dist/{config-rTT4qTU5.mjs → config-DWWtjKQx.mjs} +1 -1
  14. package/dist/{connect-CJZ73syK.cjs → connect-BxCScVNX.cjs} +4 -4
  15. package/dist/{connect-BfC1tYP_.mjs → connect-DXQC1CJe.mjs} +4 -4
  16. package/dist/{controlClient-DZMIsKsT.cjs → controlClient-DgELDZgM.cjs} +10 -1
  17. package/dist/{controlClient-CM_QcJnB.mjs → controlClient-DgjVRN8D.mjs} +10 -2
  18. package/dist/deviceKeys-BpIAWz13.cjs +182 -0
  19. package/dist/deviceKeys-CejCdWpw.mjs +175 -0
  20. package/dist/{doctor-DZYB6IVc.mjs → doctor-B-LCMVN9.mjs} +3 -3
  21. package/dist/{doctor-Cya9YSA0.cjs → doctor-DznasgP3.cjs} +3 -3
  22. package/dist/{downloadImage-MQMrUUxS.cjs → downloadImage-B_ygLAXY.cjs} +5 -5
  23. package/dist/{downloadImage-DfSFLb-h.mjs → downloadImage-ia5SzlCV.mjs} +3 -3
  24. package/dist/{enroll-BxlbVftP.cjs → enroll-BvfpRUGJ.cjs} +3 -3
  25. package/dist/{enroll-C52x8oly.mjs → enroll-HjUqyGnD.mjs} +3 -3
  26. package/dist/{evidence-Ccd9iYcj.mjs → evidence-BEQd1dCh.mjs} +1 -1
  27. package/dist/{evidence-CjWBG5rX.cjs → evidence-CVgTjBrq.cjs} +1 -1
  28. package/dist/{formatImageMessage-DbyG8c2j.cjs → formatImageMessage-CmJTP_hU.cjs} +1 -1
  29. package/dist/{formatImageMessage-B4lSxlk2.mjs → formatImageMessage-DVs57t3x.mjs} +1 -1
  30. package/dist/index.cjs +58 -43
  31. package/dist/index.mjs +58 -43
  32. package/dist/{install-C_cp8b2-.cjs → install-2Yl_L582.cjs} +2 -2
  33. package/dist/{install-oRAjVLjP.mjs → install-B3mMGISS.mjs} +1 -1
  34. package/dist/{install-BokHRgFW.cjs → install-Ck22tYHP.cjs} +1 -1
  35. package/dist/{install-DAeCn8ae.mjs → install-UTz_vYvB.mjs} +2 -2
  36. package/dist/lib.cjs +3 -3
  37. package/dist/lib.d.cts +77 -0
  38. package/dist/lib.d.mts +77 -0
  39. package/dist/lib.mjs +3 -3
  40. package/dist/{loadableToolOutput-Cuygmd4K.mjs → loadableToolOutput-4EvraaNY.mjs} +3 -3
  41. package/dist/{loadableToolOutput-DDCJjywt.cjs → loadableToolOutput-BZYlaqHn.cjs} +3 -3
  42. package/dist/{persistence-BFjvtUad.mjs → persistence-CIUFyOwz.mjs} +2 -2
  43. package/dist/{persistence-JLUn_Nou.cjs → persistence-DrfN7TzY.cjs} +1 -1
  44. package/dist/push-CCOXCcM_.cjs +80 -0
  45. package/dist/push-USXj_pv0.mjs +78 -0
  46. package/dist/{run-BDn2aE3Q.cjs → run-D3CopmEb.cjs} +45 -9
  47. package/dist/{run-DBZqN6Kn.mjs → run-Dy6KX6m-.mjs} +45 -9
  48. package/dist/{runClaude-DNHvERX0.mjs → runClaude-CWux2RDq.mjs} +14 -14
  49. package/dist/{runClaude-uaKxPZPm.cjs → runClaude-aHzvx6Ha.cjs} +15 -15
  50. package/dist/{runCodex-C-RTM0wA.mjs → runCodex-D0ZxSj3O.mjs} +13 -13
  51. package/dist/{runCodex-82UrZLme.cjs → runCodex-G-Aq531U.cjs} +13 -13
  52. package/dist/{runGemini-Bm6-4Xm2.mjs → runGemini-DZC2i6vA.mjs} +15 -15
  53. package/dist/{runGemini-CXMr4Jwb.cjs → runGemini-DxGgDAIM.cjs} +15 -15
  54. package/dist/{runOneShot-1B6RMYT7.cjs → runOneShot-BgT-JNMA.cjs} +2 -2
  55. package/dist/{runOneShot-ioARRHrx.mjs → runOneShot-cxlk7VKA.mjs} +2 -2
  56. package/dist/{serve-CwibwU6N.mjs → serve-CKyvJNs0.mjs} +6 -6
  57. package/dist/{serve-BtdAhkIE.cjs → serve-Dr3wPrtK.cjs} +6 -6
  58. package/dist/{session-D--F87zL.cjs → session-dYzbOo_W.cjs} +5 -5
  59. package/dist/{session-2sU4Bu1R.mjs → session-vf2ZjFto.mjs} +5 -5
  60. package/dist/{sessionKeyCache-D2BwO91C.mjs → sessionKeyCache-D2u3JUys.mjs} +1 -1
  61. package/dist/{sessionKeyCache-4Tp8pJ3N.cjs → sessionKeyCache-dDquDzn0.cjs} +1 -1
  62. package/dist/{sessionList-BCSw3qYt.mjs → sessionList-C1k2PNg_.mjs} +2 -2
  63. package/dist/{sessionList-ICJsjNtJ.cjs → sessionList-DmmtC7uR.cjs} +2 -2
  64. package/dist/{sessionSummary-lHGBWQSb.mjs → sessionSummary-5a1QtFVS.mjs} +3 -3
  65. package/dist/{sessionSummary-Bkz52BPh.cjs → sessionSummary-DGLoVOxd.cjs} +3 -3
  66. package/dist/{spawnHappyCLI-D_V-AAoP.cjs → spawnHappyCLI-CarRI4GM.cjs} +1 -1
  67. package/dist/{spawnHappyCLI-CoORX0YW.mjs → spawnHappyCLI-D3EkTFIn.mjs} +1 -1
  68. package/dist/{ssh-DL_AkRAD.mjs → ssh-B0hvEYIl.mjs} +137 -103
  69. package/dist/{ssh-BTOlhhxi.cjs → ssh-CBgVwKsB.cjs} +142 -106
  70. package/dist/{task-CSpaC9HX.cjs → task-CMunPKCV.cjs} +6 -6
  71. package/dist/{task-CQzrHRZe.mjs → task-CRo6W-3_.mjs} +6 -6
  72. package/dist/{uninstall-nFMFGUVM.mjs → uninstall-BALE2up0.mjs} +2 -2
  73. package/dist/{uninstall-BiKyRAb1.cjs → uninstall-BJ3eEGM4.cjs} +2 -2
  74. package/dist/{update-Dw8pgsAT.cjs → update-Bb3iijy8.cjs} +3 -3
  75. package/dist/{update-BcqRHXf2.mjs → update-BfjtLrhO.mjs} +3 -3
  76. package/package.json +1 -1
  77. package/dist/deviceExec-CU1R9CRb.cjs +0 -84
  78. package/dist/deviceExec-D93AOVV4.mjs +0 -81
package/dist/lib.d.cts CHANGED
@@ -1606,6 +1606,80 @@ type SpawnSessionResult = {
1606
1606
  * Similar to ApiSessionClient but for machine-scoped connections
1607
1607
  */
1608
1608
 
1609
+ interface ServerToDaemonEvents {
1610
+ update: (data: Update) => void;
1611
+ 'rpc-request': (data: {
1612
+ method: string;
1613
+ params: string;
1614
+ }, callback: (response: string) => void) => void;
1615
+ 'rpc-registered': (data: {
1616
+ method: string;
1617
+ }) => void;
1618
+ 'rpc-unregistered': (data: {
1619
+ method: string;
1620
+ }) => void;
1621
+ 'rpc-error': (data: {
1622
+ type: string;
1623
+ error: string;
1624
+ }) => void;
1625
+ auth: (data: {
1626
+ success: boolean;
1627
+ user: string;
1628
+ }) => void;
1629
+ error: (data: {
1630
+ message: string;
1631
+ }) => void;
1632
+ }
1633
+ interface DaemonToServerEvents {
1634
+ 'machine-alive': (data: {
1635
+ machineId: string;
1636
+ time: number;
1637
+ }) => void;
1638
+ 'machine-update-metadata': (data: {
1639
+ machineId: string;
1640
+ metadata: string;
1641
+ expectedVersion: number;
1642
+ }, cb: (answer: {
1643
+ result: 'error';
1644
+ } | {
1645
+ result: 'version-mismatch';
1646
+ version: number;
1647
+ metadata: string;
1648
+ } | {
1649
+ result: 'success';
1650
+ version: number;
1651
+ metadata: string;
1652
+ }) => void) => void;
1653
+ 'machine-update-state': (data: {
1654
+ machineId: string;
1655
+ daemonState: string;
1656
+ expectedVersion: number;
1657
+ }, cb: (answer: {
1658
+ result: 'error';
1659
+ } | {
1660
+ result: 'version-mismatch';
1661
+ version: number;
1662
+ daemonState: string;
1663
+ } | {
1664
+ result: 'success';
1665
+ version: number;
1666
+ daemonState: string;
1667
+ }) => void) => void;
1668
+ 'rpc-register': (data: {
1669
+ method: string;
1670
+ }) => void;
1671
+ 'rpc-unregister': (data: {
1672
+ method: string;
1673
+ }) => void;
1674
+ 'rpc-call': (data: {
1675
+ method: string;
1676
+ params: any;
1677
+ }, callback: (response: {
1678
+ ok: boolean;
1679
+ result?: any;
1680
+ error?: string;
1681
+ }) => void) => void;
1682
+ }
1609
1683
  type MachineRpcHandlers = {
1610
1684
  spawnSession: (options: SpawnSessionOptions) => Promise<SpawnSessionResult>;
1611
1685
  stopSession: (sessionId: string) => boolean;
@@ -1641,6 +1715,9 @@ declare class ApiMachineClient {
1641
1715
  private machine;
1642
1716
  private currentRuntimeDaemonState?;
1643
1717
  private socket;
1718
+ /** The daemon's live connection. Reused for device RPC so a CLI command does
1719
+ * not spend ~500ms building its own TLS + WebSocket handshake. */
1720
+ get rpcSocket(): Socket<ServerToDaemonEvents, DaemonToServerEvents>;
1644
1721
  private keepAliveInterval;
1645
1722
  private rpcHandlerManager;
1646
1723
  private claudeCache;
package/dist/lib.d.mts CHANGED
@@ -1606,6 +1606,80 @@ type SpawnSessionResult = {
1606
1606
  * Similar to ApiSessionClient but for machine-scoped connections
1607
1607
  */
1608
1608
 
1609
+ interface ServerToDaemonEvents {
1610
+ update: (data: Update) => void;
1611
+ 'rpc-request': (data: {
1612
+ method: string;
1613
+ params: string;
1614
+ }, callback: (response: string) => void) => void;
1615
+ 'rpc-registered': (data: {
1616
+ method: string;
1617
+ }) => void;
1618
+ 'rpc-unregistered': (data: {
1619
+ method: string;
1620
+ }) => void;
1621
+ 'rpc-error': (data: {
1622
+ type: string;
1623
+ error: string;
1624
+ }) => void;
1625
+ auth: (data: {
1626
+ success: boolean;
1627
+ user: string;
1628
+ }) => void;
1629
+ error: (data: {
1630
+ message: string;
1631
+ }) => void;
1632
+ }
1633
+ interface DaemonToServerEvents {
1634
+ 'machine-alive': (data: {
1635
+ machineId: string;
1636
+ time: number;
1637
+ }) => void;
1638
+ 'machine-update-metadata': (data: {
1639
+ machineId: string;
1640
+ metadata: string;
1641
+ expectedVersion: number;
1642
+ }, cb: (answer: {
1643
+ result: 'error';
1644
+ } | {
1645
+ result: 'version-mismatch';
1646
+ version: number;
1647
+ metadata: string;
1648
+ } | {
1649
+ result: 'success';
1650
+ version: number;
1651
+ metadata: string;
1652
+ }) => void) => void;
1653
+ 'machine-update-state': (data: {
1654
+ machineId: string;
1655
+ daemonState: string;
1656
+ expectedVersion: number;
1657
+ }, cb: (answer: {
1658
+ result: 'error';
1659
+ } | {
1660
+ result: 'version-mismatch';
1661
+ version: number;
1662
+ daemonState: string;
1663
+ } | {
1664
+ result: 'success';
1665
+ version: number;
1666
+ daemonState: string;
1667
+ }) => void) => void;
1668
+ 'rpc-register': (data: {
1669
+ method: string;
1670
+ }) => void;
1671
+ 'rpc-unregister': (data: {
1672
+ method: string;
1673
+ }) => void;
1674
+ 'rpc-call': (data: {
1675
+ method: string;
1676
+ params: any;
1677
+ }, callback: (response: {
1678
+ ok: boolean;
1679
+ result?: any;
1680
+ error?: string;
1681
+ }) => void) => void;
1682
+ }
1609
1683
  type MachineRpcHandlers = {
1610
1684
  spawnSession: (options: SpawnSessionOptions) => Promise<SpawnSessionResult>;
1611
1685
  stopSession: (sessionId: string) => boolean;
@@ -1641,6 +1715,9 @@ declare class ApiMachineClient {
1641
1715
  private machine;
1642
1716
  private currentRuntimeDaemonState?;
1643
1717
  private socket;
1718
+ /** The daemon's live connection. Reused for device RPC so a CLI command does
1719
+ * not spend ~500ms building its own TLS + WebSocket handshake. */
1720
+ get rpcSocket(): Socket<ServerToDaemonEvents, DaemonToServerEvents>;
1644
1721
  private keepAliveInterval;
1645
1722
  private rpcHandlerManager;
1646
1723
  private claudeCache;
package/dist/lib.mjs CHANGED
@@ -1,5 +1,5 @@
1
- export { A as ApiClient, a as ApiSessionClient } from './api-CIZKyfSn.mjs';
2
- export { c as configuration, l as logger } from './persistence-BFjvtUad.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient } from './api-K2-jr9ar.mjs';
2
+ export { c as configuration, l as logger } from './persistence-CIUFyOwz.mjs';
3
3
  export { R as RawJSONLinesSchema } from './types-BoJh4Qfv.mjs';
4
4
  import 'axios';
5
5
  import 'node:events';
@@ -28,6 +28,6 @@ import 'tweetnacl';
28
28
  import '@stablelib/base64';
29
29
  import 'node:readline';
30
30
  import 'expo-server-sdk';
31
- import './sessionKeyCache-D2BwO91C.mjs';
31
+ import './sessionKeyCache-D2u3JUys.mjs';
32
32
  import 'chalk';
33
33
  import 'fs';
@@ -1,11 +1,11 @@
1
- import { l as logger, c as configuration, p as packageJson } from './persistence-BFjvtUad.mjs';
1
+ import { l as logger, c as configuration, p as packageJson } from './persistence-CIUFyOwz.mjs';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { readFileSync, existsSync, readdirSync } from 'node:fs';
4
4
  import os, { homedir } from 'node:os';
5
5
  import { join, basename, dirname } from 'node:path';
6
6
  import { p as projectPath } from './projectPath-BTBIMB57.mjs';
7
- import { r as resolveToolsDir, s as startOfflineReconnection, b as saveToolOutputRecord } from './api-CIZKyfSn.mjs';
8
- import { a as detectGitWorktree } from './downloadImage-DfSFLb-h.mjs';
7
+ import { r as resolveToolsDir, s as startOfflineReconnection, b as saveToolOutputRecord } from './api-K2-jr9ar.mjs';
8
+ import { a as detectGitWorktree } from './downloadImage-ia5SzlCV.mjs';
9
9
 
10
10
  class BasePermissionHandler {
11
11
  pendingRequests = /* @__PURE__ */ new Map();
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var logger = require('./persistence-JLUn_Nou.cjs');
3
+ var logger = require('./persistence-DrfN7TzY.cjs');
4
4
  var node_crypto = require('node:crypto');
5
5
  var fs = require('node:fs');
6
6
  var os = require('node:os');
7
7
  var path = require('node:path');
8
8
  var projectPath = require('./projectPath-ChLhdOyt.cjs');
9
- var api = require('./api-CwOCQeLH.cjs');
10
- var downloadImage = require('./downloadImage-MQMrUUxS.cjs');
9
+ var api = require('./api-XeHlmApX.cjs');
10
+ var downloadImage = require('./downloadImage-B_ygLAXY.cjs');
11
11
 
12
12
  class BasePermissionHandler {
13
13
  pendingRequests = /* @__PURE__ */ new Map();
@@ -14,7 +14,7 @@ function isDebug() {
14
14
  }
15
15
 
16
16
  var name = "@zonease/happy";
17
- var version = "0.6.39";
17
+ var version = "0.6.41";
18
18
  var description = "Mobile and Web client for Claude Code, Codex, and Gemini";
19
19
  var author = "ZonEaseTech";
20
20
  var license = "MIT";
@@ -941,4 +941,4 @@ var persistence = /*#__PURE__*/Object.freeze({
941
941
  writeDaemonState: writeDaemonState
942
942
  });
943
943
 
944
- export { getProfileEnvironmentVariables as A, encryptLegacy as B, getRandomBytes as C, libsodiumEncryptForPublicKey as D, logger$1 as E, persistence as F, readSettings as a, clearCredentials as b, configuration as c, clearMachineId as d, decodeBase64 as e, encodeBase64 as f, encrypt as g, decrypt as h, isDebug as i, readDaemonState as j, encryptWithDataKey as k, logger as l, decryptWithDataKey as m, clearDaemonState as n, libsodiumPublicKeyFromSecretKey as o, packageJson as p, decryptLegacy as q, readCredentials as r, encodeBase64Url as s, writeCredentialsLegacy as t, updateSettings as u, acquireDaemonLock as v, writeCredentialsDataKey as w, writeDaemonState as x, releaseDaemonLock as y, validateProfileForAgent as z };
944
+ export { getProfileEnvironmentVariables as A, encryptLegacy as B, getRandomBytes as C, libsodiumEncryptForPublicKey as D, logger$1 as E, persistence as F, readSettings as a, clearCredentials as b, configuration as c, clearMachineId as d, encodeBase64 as e, encrypt as f, decrypt as g, decodeBase64 as h, isDebug as i, encryptWithDataKey as j, decryptWithDataKey as k, logger as l, readDaemonState as m, clearDaemonState as n, libsodiumPublicKeyFromSecretKey as o, packageJson as p, decryptLegacy as q, readCredentials as r, encodeBase64Url as s, writeCredentialsLegacy as t, updateSettings as u, acquireDaemonLock as v, writeCredentialsDataKey as w, writeDaemonState as x, releaseDaemonLock as y, validateProfileForAgent as z };
@@ -35,7 +35,7 @@ function isDebug() {
35
35
  }
36
36
 
37
37
  var name = "@zonease/happy";
38
- var version = "0.6.39";
38
+ var version = "0.6.41";
39
39
  var description = "Mobile and Web client for Claude Code, Codex, and Gemini";
40
40
  var author = "ZonEaseTech";
41
41
  var license = "MIT";
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ var promises = require('node:fs/promises');
4
+ var socket_ioClient = require('socket.io-client');
5
+ var logger = require('./persistence-DrfN7TzY.cjs');
6
+ var ssh = require('./ssh-CBgVwKsB.cjs');
7
+ require('node:fs');
8
+ require('node:os');
9
+ require('node:path');
10
+ require('zod');
11
+ require('node:crypto');
12
+ require('tweetnacl');
13
+ require('chalk');
14
+ require('fs');
15
+ require('./deviceKeys-BpIAWz13.cjs');
16
+ require('axios');
17
+ require('./auth-BQvEuSs5.cjs');
18
+ require('qrcode-terminal');
19
+ require('./time-Bj2HRkKH.cjs');
20
+ require('./browser-C05SP_Tt.cjs');
21
+ require('open');
22
+ require('react');
23
+ require('ink');
24
+ require('./controlClient-DgELDZgM.cjs');
25
+ require('./projectPath-ChLhdOyt.cjs');
26
+ require('path');
27
+ require('url');
28
+
29
+ const CHUNK_BYTES = 8 * 1024 * 1024;
30
+ async function pushToDevice(credentials, query, localPath, remotePath) {
31
+ const resolved = await ssh.resolveDeviceForCommand(credentials, query);
32
+ if (!resolved) return 1;
33
+ const { device, key, variant } = resolved;
34
+ const info = await promises.stat(localPath).catch(() => null);
35
+ if (!info?.isFile()) {
36
+ console.error(`Not a file: ${localPath}`);
37
+ return 1;
38
+ }
39
+ const contents = await promises.readFile(localPath);
40
+ const socket = socket_ioClient.io(logger.configuration.serverUrl, {
41
+ auth: { token: credentials.token, clientType: "user-scoped" },
42
+ path: "/v1/updates",
43
+ transports: ["websocket"]
44
+ });
45
+ await new Promise((resolve, reject) => {
46
+ socket.once("connect", () => resolve());
47
+ socket.once("connect_error", reject);
48
+ });
49
+ const writeChunk = async (path, chunk) => {
50
+ const answer = await socket.timeout(12e4).emitWithAck("rpc-call", {
51
+ method: `${device.id}:writeFile`,
52
+ params: logger.encodeBase64(logger.encrypt(key, variant, { path, content: chunk.toString("base64") }))
53
+ });
54
+ if (!answer?.ok) throw new Error(answer?.error || "writeFile failed");
55
+ const result = logger.decrypt(key, variant, logger.decodeBase64(answer.result));
56
+ if (!result?.success) throw new Error(result?.error || "Device refused the write");
57
+ };
58
+ try {
59
+ if (contents.length <= CHUNK_BYTES) {
60
+ await writeChunk(remotePath, contents);
61
+ console.error(`pushed ${contents.length} bytes to ${device.name}:${remotePath}`);
62
+ return 0;
63
+ }
64
+ const parts = Math.ceil(contents.length / CHUNK_BYTES);
65
+ for (let i = 0; i < parts; i++) {
66
+ await writeChunk(`${remotePath}.part-${i}`, contents.subarray(i * CHUNK_BYTES, (i + 1) * CHUNK_BYTES));
67
+ console.error(` part ${i + 1}/${parts}`);
68
+ }
69
+ console.error(`pushed ${contents.length} bytes to ${device.name}:${remotePath}.part-0..${parts - 1}`);
70
+ console.error(`join with: cat '${remotePath}'.part-* > '${remotePath}' && rm -f '${remotePath}'.part-*`);
71
+ return 0;
72
+ } catch (error) {
73
+ console.error(error instanceof Error ? error.message : String(error));
74
+ return 1;
75
+ } finally {
76
+ socket.close();
77
+ }
78
+ }
79
+
80
+ exports.pushToDevice = pushToDevice;
@@ -0,0 +1,78 @@
1
+ import { stat, readFile } from 'node:fs/promises';
2
+ import { io } from 'socket.io-client';
3
+ import { c as configuration, e as encodeBase64, f as encrypt, g as decrypt, h as decodeBase64 } from './persistence-CIUFyOwz.mjs';
4
+ import { resolveDeviceForCommand } from './ssh-B0hvEYIl.mjs';
5
+ import 'node:fs';
6
+ import 'node:os';
7
+ import 'node:path';
8
+ import 'zod';
9
+ import 'node:crypto';
10
+ import 'tweetnacl';
11
+ import 'chalk';
12
+ import 'fs';
13
+ import './deviceKeys-CejCdWpw.mjs';
14
+ import 'axios';
15
+ import './auth-DS8fzlin.mjs';
16
+ import 'qrcode-terminal';
17
+ import './time-DgsBLJA_.mjs';
18
+ import './browser-C05Ak6RL.mjs';
19
+ import 'open';
20
+ import 'react';
21
+ import 'ink';
22
+ import './controlClient-DgjVRN8D.mjs';
23
+ import './projectPath-BTBIMB57.mjs';
24
+ import 'path';
25
+ import 'url';
26
+
27
+ const CHUNK_BYTES = 8 * 1024 * 1024;
28
+ async function pushToDevice(credentials, query, localPath, remotePath) {
29
+ const resolved = await resolveDeviceForCommand(credentials, query);
30
+ if (!resolved) return 1;
31
+ const { device, key, variant } = resolved;
32
+ const info = await stat(localPath).catch(() => null);
33
+ if (!info?.isFile()) {
34
+ console.error(`Not a file: ${localPath}`);
35
+ return 1;
36
+ }
37
+ const contents = await readFile(localPath);
38
+ const socket = io(configuration.serverUrl, {
39
+ auth: { token: credentials.token, clientType: "user-scoped" },
40
+ path: "/v1/updates",
41
+ transports: ["websocket"]
42
+ });
43
+ await new Promise((resolve, reject) => {
44
+ socket.once("connect", () => resolve());
45
+ socket.once("connect_error", reject);
46
+ });
47
+ const writeChunk = async (path, chunk) => {
48
+ const answer = await socket.timeout(12e4).emitWithAck("rpc-call", {
49
+ method: `${device.id}:writeFile`,
50
+ params: encodeBase64(encrypt(key, variant, { path, content: chunk.toString("base64") }))
51
+ });
52
+ if (!answer?.ok) throw new Error(answer?.error || "writeFile failed");
53
+ const result = decrypt(key, variant, decodeBase64(answer.result));
54
+ if (!result?.success) throw new Error(result?.error || "Device refused the write");
55
+ };
56
+ try {
57
+ if (contents.length <= CHUNK_BYTES) {
58
+ await writeChunk(remotePath, contents);
59
+ console.error(`pushed ${contents.length} bytes to ${device.name}:${remotePath}`);
60
+ return 0;
61
+ }
62
+ const parts = Math.ceil(contents.length / CHUNK_BYTES);
63
+ for (let i = 0; i < parts; i++) {
64
+ await writeChunk(`${remotePath}.part-${i}`, contents.subarray(i * CHUNK_BYTES, (i + 1) * CHUNK_BYTES));
65
+ console.error(` part ${i + 1}/${parts}`);
66
+ }
67
+ console.error(`pushed ${contents.length} bytes to ${device.name}:${remotePath}.part-0..${parts - 1}`);
68
+ console.error(`join with: cat '${remotePath}'.part-* > '${remotePath}' && rm -f '${remotePath}'.part-*`);
69
+ return 0;
70
+ } catch (error) {
71
+ console.error(error instanceof Error ? error.message : String(error));
72
+ return 1;
73
+ } finally {
74
+ socket.close();
75
+ }
76
+ }
77
+
78
+ export { pushToDevice };
@@ -3,14 +3,15 @@
3
3
  var fs$1 = require('fs/promises');
4
4
  var os = require('os');
5
5
  var tmp = require('tmp');
6
- var api = require('./api-CwOCQeLH.cjs');
7
- var logger = require('./persistence-JLUn_Nou.cjs');
8
- var auth = require('./auth-BIleVCw0.cjs');
6
+ var api = require('./api-XeHlmApX.cjs');
7
+ var logger = require('./persistence-DrfN7TzY.cjs');
8
+ var auth = require('./auth-BQvEuSs5.cjs');
9
9
  var child_process = require('child_process');
10
- var doctor = require('./doctor-Cya9YSA0.cjs');
11
- var spawnHappyCLI = require('./spawnHappyCLI-D_V-AAoP.cjs');
12
- var controlClient = require('./controlClient-DZMIsKsT.cjs');
13
- var install = require('./install-BokHRgFW.cjs');
10
+ var doctor = require('./doctor-DznasgP3.cjs');
11
+ var spawnHappyCLI = require('./spawnHappyCLI-CarRI4GM.cjs');
12
+ var controlClient = require('./controlClient-DgELDZgM.cjs');
13
+ var deviceKeys = require('./deviceKeys-BpIAWz13.cjs');
14
+ var install = require('./install-Ck22tYHP.cjs');
14
15
  var fastify = require('fastify');
15
16
  var fs = require('node:fs');
16
17
  var promises = require('node:fs/promises');
@@ -182,7 +183,8 @@ function startDaemonControlServer({
182
183
  stopSession,
183
184
  spawnSession,
184
185
  requestShutdown,
185
- onHappySessionWebhook
186
+ onHappySessionWebhook,
187
+ execOnDevice
186
188
  }) {
187
189
  return new Promise((resolve) => {
188
190
  const app = fastify({
@@ -288,6 +290,23 @@ function startDaemonControlServer({
288
290
  }))
289
291
  };
290
292
  });
293
+ typed.post("/device-exec", {
294
+ schema: {
295
+ body: z.z.object({
296
+ deviceId: z.z.string(),
297
+ command: z.z.string(),
298
+ timeoutMs: z.z.number().optional()
299
+ })
300
+ }
301
+ }, async (request, reply) => {
302
+ const { deviceId, command, timeoutMs } = request.body;
303
+ logger.logger.debug(`[CONTROL SERVER] Device exec request for ${deviceId}`);
304
+ try {
305
+ return { ok: true, result: await execOnDevice(deviceId, command, timeoutMs ?? 3e5) };
306
+ } catch (error) {
307
+ return reply.code(200).send({ ok: false, error: error instanceof Error ? error.message : String(error) });
308
+ }
309
+ });
291
310
  typed.post("/stop-session", {
292
311
  schema: {
293
312
  body: z.z.object({
@@ -1996,12 +2015,28 @@ ${error.message}
1996
2015
  }
1997
2016
  pidToTrackedSession.delete(pid);
1998
2017
  };
2018
+ let connectedMachine = null;
1999
2019
  const { port: controlPort, stop: stopControlServer } = await startDaemonControlServer({
2000
2020
  getChildren: getCurrentChildren,
2001
2021
  stopSession,
2002
2022
  spawnSession,
2003
2023
  requestShutdown: () => requestShutdown("happy-cli"),
2004
- onHappySessionWebhook
2024
+ onHappySessionWebhook,
2025
+ execOnDevice: async (deviceId, command, timeoutMs) => {
2026
+ if (!connectedMachine) {
2027
+ throw new Error("Daemon is not connected yet");
2028
+ }
2029
+ const material = await deviceKeys.readCachedDeviceKey(deviceId);
2030
+ if (!material) {
2031
+ throw new Error(`No cached key for device ${deviceId}`);
2032
+ }
2033
+ return await deviceKeys.deviceExec(connectedMachine.rpcSocket, credentials, deviceId, command, {
2034
+ deviceKeyBase64: logger.encodeBase64(material.key),
2035
+ deviceKeyVariant: material.variant,
2036
+ timeout: timeoutMs,
2037
+ skipMachineLookup: true
2038
+ });
2039
+ }
2005
2040
  });
2006
2041
  const fileState = {
2007
2042
  pid: process.pid,
@@ -2035,6 +2070,7 @@ ${error.message}
2035
2070
  orchestratorCancel: handleOrchestratorCancel
2036
2071
  });
2037
2072
  apiMachine.connect();
2073
+ connectedMachine = apiMachine;
2038
2074
  apiMachine.updateMachineMetadata((current) => ({ ...current, ...machineMetadata })).catch((error) => {
2039
2075
  logger.logger.debug("[DAEMON RUN] Failed to update machine metadata", error);
2040
2076
  });
@@ -1,14 +1,15 @@
1
1
  import fs from 'fs/promises';
2
2
  import os from 'os';
3
3
  import * as tmp from 'tmp';
4
- import { A as ApiClient } from './api-CIZKyfSn.mjs';
5
- import { c as configuration, l as logger, p as packageJson, v as acquireDaemonLock, a as readSettings, i as isDebug, x as writeDaemonState, j as readDaemonState, y as releaseDaemonLock, z as validateProfileForAgent, A as getProfileEnvironmentVariables } from './persistence-BFjvtUad.mjs';
6
- import { authAndSetupMachineIfNeeded } from './auth-BE8dD20T.mjs';
4
+ import { A as ApiClient } from './api-K2-jr9ar.mjs';
5
+ import { c as configuration, l as logger, p as packageJson, v as acquireDaemonLock, a as readSettings, i as isDebug, e as encodeBase64, x as writeDaemonState, m as readDaemonState, y as releaseDaemonLock, z as validateProfileForAgent, A as getProfileEnvironmentVariables } from './persistence-CIUFyOwz.mjs';
6
+ import { authAndSetupMachineIfNeeded } from './auth-DS8fzlin.mjs';
7
7
  import { spawn, execSync, exec } from 'child_process';
8
- import { getEnvironmentInfo } from './doctor-DZYB6IVc.mjs';
9
- import { s as spawnHappyCLI } from './spawnHappyCLI-CoORX0YW.mjs';
10
- import { isDaemonRunningCurrentlyInstalledHappyVersion, stopDaemon, cleanupDaemonState } from './controlClient-CM_QcJnB.mjs';
11
- import { P as PLIST_LABEL } from './install-oRAjVLjP.mjs';
8
+ import { getEnvironmentInfo } from './doctor-B-LCMVN9.mjs';
9
+ import { s as spawnHappyCLI } from './spawnHappyCLI-D3EkTFIn.mjs';
10
+ import { isDaemonRunningCurrentlyInstalledHappyVersion, stopDaemon, cleanupDaemonState } from './controlClient-DgjVRN8D.mjs';
11
+ import { a as readCachedDeviceKey, d as deviceExec } from './deviceKeys-CejCdWpw.mjs';
12
+ import { P as PLIST_LABEL } from './install-B3mMGISS.mjs';
12
13
  import fastify from 'fastify';
13
14
  import { createReadStream } from 'node:fs';
14
15
  import { stat } from 'node:fs/promises';
@@ -161,7 +162,8 @@ function startDaemonControlServer({
161
162
  stopSession,
162
163
  spawnSession,
163
164
  requestShutdown,
164
- onHappySessionWebhook
165
+ onHappySessionWebhook,
166
+ execOnDevice
165
167
  }) {
166
168
  return new Promise((resolve) => {
167
169
  const app = fastify({
@@ -267,6 +269,23 @@ function startDaemonControlServer({
267
269
  }))
268
270
  };
269
271
  });
272
+ typed.post("/device-exec", {
273
+ schema: {
274
+ body: z.object({
275
+ deviceId: z.string(),
276
+ command: z.string(),
277
+ timeoutMs: z.number().optional()
278
+ })
279
+ }
280
+ }, async (request, reply) => {
281
+ const { deviceId, command, timeoutMs } = request.body;
282
+ logger.debug(`[CONTROL SERVER] Device exec request for ${deviceId}`);
283
+ try {
284
+ return { ok: true, result: await execOnDevice(deviceId, command, timeoutMs ?? 3e5) };
285
+ } catch (error) {
286
+ return reply.code(200).send({ ok: false, error: error instanceof Error ? error.message : String(error) });
287
+ }
288
+ });
270
289
  typed.post("/stop-session", {
271
290
  schema: {
272
291
  body: z.object({
@@ -1975,12 +1994,28 @@ ${error.message}
1975
1994
  }
1976
1995
  pidToTrackedSession.delete(pid);
1977
1996
  };
1997
+ let connectedMachine = null;
1978
1998
  const { port: controlPort, stop: stopControlServer } = await startDaemonControlServer({
1979
1999
  getChildren: getCurrentChildren,
1980
2000
  stopSession,
1981
2001
  spawnSession,
1982
2002
  requestShutdown: () => requestShutdown("happy-cli"),
1983
- onHappySessionWebhook
2003
+ onHappySessionWebhook,
2004
+ execOnDevice: async (deviceId, command, timeoutMs) => {
2005
+ if (!connectedMachine) {
2006
+ throw new Error("Daemon is not connected yet");
2007
+ }
2008
+ const material = await readCachedDeviceKey(deviceId);
2009
+ if (!material) {
2010
+ throw new Error(`No cached key for device ${deviceId}`);
2011
+ }
2012
+ return await deviceExec(connectedMachine.rpcSocket, credentials, deviceId, command, {
2013
+ deviceKeyBase64: encodeBase64(material.key),
2014
+ deviceKeyVariant: material.variant,
2015
+ timeout: timeoutMs,
2016
+ skipMachineLookup: true
2017
+ });
2018
+ }
1984
2019
  });
1985
2020
  const fileState = {
1986
2021
  pid: process.pid,
@@ -2014,6 +2049,7 @@ ${error.message}
2014
2049
  orchestratorCancel: handleOrchestratorCancel
2015
2050
  });
2016
2051
  apiMachine.connect();
2052
+ connectedMachine = apiMachine;
2017
2053
  apiMachine.updateMachineMetadata((current) => ({ ...current, ...machineMetadata })).catch((error) => {
2018
2054
  logger.debug("[DAEMON RUN] Failed to update machine metadata", error);
2019
2055
  });