pwragent 0.0.0 → 1.3.7

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 (88) hide show
  1. package/README.md +494 -2
  2. package/bin/pwragent.js +10 -1
  3. package/dist/THIRD_PARTY_LICENSES +165 -0
  4. package/dist/federation-core.js +19569 -0
  5. package/dist/ws-LICENSE +20 -0
  6. package/examples/pwragent-headless.service +25 -0
  7. package/licenses/xterm-LICENSE +26 -0
  8. package/node_modules/node-addon-api/LICENSE.md +9 -0
  9. package/node_modules/node-addon-api/README.md +319 -0
  10. package/node_modules/node-addon-api/common.gypi +20 -0
  11. package/node_modules/node-addon-api/except.gypi +25 -0
  12. package/node_modules/node-addon-api/index.js +12 -0
  13. package/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  14. package/node_modules/node-addon-api/napi-inl.h +6607 -0
  15. package/node_modules/node-addon-api/napi.h +3201 -0
  16. package/node_modules/node-addon-api/node_addon_api.gyp +32 -0
  17. package/node_modules/node-addon-api/node_api.gyp +9 -0
  18. package/node_modules/node-addon-api/noexcept.gypi +26 -0
  19. package/node_modules/node-addon-api/nothing.c +0 -0
  20. package/node_modules/node-addon-api/package-support.json +21 -0
  21. package/node_modules/node-addon-api/package.json +480 -0
  22. package/node_modules/node-addon-api/tools/README.md +73 -0
  23. package/node_modules/node-addon-api/tools/check-napi.js +99 -0
  24. package/node_modules/node-addon-api/tools/clang-format.js +71 -0
  25. package/node_modules/node-addon-api/tools/conversion.js +301 -0
  26. package/node_modules/node-addon-api/tools/eslint-format.js +79 -0
  27. package/node_modules/node-pty/LICENSE +69 -0
  28. package/node_modules/node-pty/README.md +164 -0
  29. package/node_modules/node-pty/binding.gyp +150 -0
  30. package/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  31. package/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  32. package/node_modules/node-pty/lib/index.js +52 -0
  33. package/node_modules/node-pty/lib/interfaces.js +7 -0
  34. package/node_modules/node-pty/lib/shared/conout.js +10 -0
  35. package/node_modules/node-pty/lib/terminal.js +190 -0
  36. package/node_modules/node-pty/lib/types.js +7 -0
  37. package/node_modules/node-pty/lib/unixTerminal.js +351 -0
  38. package/node_modules/node-pty/lib/utils.js +38 -0
  39. package/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
  40. package/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
  41. package/node_modules/node-pty/lib/windowsTerminal.js +209 -0
  42. package/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  43. package/node_modules/node-pty/package.json +66 -0
  44. package/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  45. package/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  46. package/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  47. package/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  48. package/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  49. package/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  50. package/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  51. package/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  52. package/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  53. package/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  54. package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  55. package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  56. package/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  57. package/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  58. package/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  59. package/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  60. package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  61. package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  62. package/node_modules/node-pty/scripts/post-install.js +76 -0
  63. package/node_modules/node-pty/scripts/prebuild.js +34 -0
  64. package/node_modules/node-pty/src/unix/pty.cc +876 -0
  65. package/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  66. package/node_modules/node-pty/src/win/conpty.cc +616 -0
  67. package/node_modules/node-pty/src/win/conpty.h +41 -0
  68. package/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  69. package/node_modules/node-pty/src/win/path_util.cc +95 -0
  70. package/node_modules/node-pty/src/win/path_util.h +26 -0
  71. package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-arm64/OpenConsole.exe +0 -0
  72. package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-arm64/conpty.dll +0 -0
  73. package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-x64/OpenConsole.exe +0 -0
  74. package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-x64/conpty.dll +0 -0
  75. package/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  76. package/package.json +32 -5
  77. package/src/cli.js +123 -0
  78. package/src/client.js +27 -0
  79. package/src/codex.js +112 -0
  80. package/src/daemon.js +125 -0
  81. package/src/federation-backend.js +453 -0
  82. package/src/federation-core.ts +24 -0
  83. package/src/federation.js +454 -0
  84. package/src/handoffs.js +141 -0
  85. package/src/projects.js +140 -0
  86. package/src/runtime.js +265 -0
  87. package/src/store.js +80 -0
  88. package/src/wire.js +33 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Copyright (c) 2019, Microsoft Corporation (MIT License).
3
+ */
4
+
5
+ #define NODE_ADDON_API_DISABLE_DEPRECATED
6
+ #include <napi.h>
7
+ #include <windows.h>
8
+
9
+ static Napi::Value ApiConsoleProcessList(const Napi::CallbackInfo& info) {
10
+ Napi::Env env(info.Env());
11
+ if (info.Length() != 1 ||
12
+ !info[0].IsNumber()) {
13
+ throw Napi::Error::New(env, "Usage: getConsoleProcessList(shellPid)");
14
+ }
15
+
16
+ const DWORD pid = info[0].As<Napi::Number>().Uint32Value();
17
+
18
+ if (!FreeConsole()) {
19
+ throw Napi::Error::New(env, "FreeConsole failed");
20
+ }
21
+ if (!AttachConsole(pid)) {
22
+ throw Napi::Error::New(env, "AttachConsole failed");
23
+ }
24
+ auto processList = std::vector<DWORD>(64);
25
+ auto processCount = GetConsoleProcessList(&processList[0], static_cast<DWORD>(processList.size()));
26
+ if (processList.size() < processCount) {
27
+ processList.resize(processCount);
28
+ processCount = GetConsoleProcessList(&processList[0], static_cast<DWORD>(processList.size()));
29
+ }
30
+ FreeConsole();
31
+
32
+ Napi::Array result = Napi::Array::New(env);
33
+ for (DWORD i = 0; i < processCount; i++) {
34
+ result.Set(i, Napi::Number::New(env, processList[i]));
35
+ }
36
+ return result;
37
+ }
38
+
39
+ Napi::Object init(Napi::Env env, Napi::Object exports) {
40
+ exports.Set("getConsoleProcessList", Napi::Function::New(env, ApiConsoleProcessList));
41
+ return exports;
42
+ };
43
+
44
+ NODE_API_MODULE(NODE_GYP_MODULE_NAME, init);
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
3
+ * Copyright (c) 2016, Daniel Imms (MIT License).
4
+ * Copyright (c) 2018, Microsoft Corporation (MIT License).
5
+ */
6
+
7
+ #include <stdexcept>
8
+ #include <Shlwapi.h> // PathCombine
9
+ #include <Windows.h>
10
+ #include "path_util.h"
11
+
12
+ namespace path_util {
13
+
14
+ std::wstring to_wstring(const Napi::String& str) {
15
+ const std::u16string & u16 = str.Utf16Value();
16
+ return std::wstring(u16.begin(), u16.end());
17
+ }
18
+
19
+ std::string wstring_to_string(const std::wstring &wide_string) {
20
+ if (wide_string.empty()) {
21
+ return "";
22
+ }
23
+ const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, &wide_string.at(0), (int)wide_string.size(), nullptr, 0, nullptr, nullptr);
24
+ if (size_needed <= 0) {
25
+ return "";
26
+ }
27
+ std::string result(size_needed, 0);
28
+ WideCharToMultiByte(CP_UTF8, 0, &wide_string.at(0), (int)wide_string.size(), &result.at(0), size_needed, nullptr, nullptr);
29
+ return result;
30
+ }
31
+
32
+ const char* from_wstring(const wchar_t* wstr) {
33
+ int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
34
+ if (bufferSize <= 0) {
35
+ return "";
36
+ }
37
+ char *output = new char[bufferSize];
38
+ int status = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, output, bufferSize, NULL, NULL);
39
+ if (status == 0) {
40
+ return "";
41
+ }
42
+ return output;
43
+ }
44
+
45
+ bool file_exists(std::wstring filename) {
46
+ DWORD attr = ::GetFileAttributesW(filename.c_str());
47
+ if (attr == INVALID_FILE_ATTRIBUTES || (attr & FILE_ATTRIBUTE_DIRECTORY)) {
48
+ return false;
49
+ }
50
+ return true;
51
+ }
52
+
53
+ // cmd.exe -> C:\Windows\system32\cmd.exe
54
+ std::wstring get_shell_path(std::wstring filename) {
55
+ std::wstring shellpath;
56
+
57
+ if (file_exists(filename)) {
58
+ return shellpath;
59
+ }
60
+
61
+ wchar_t* buffer_ = new wchar_t[MAX_ENV];
62
+ int read = ::GetEnvironmentVariableW(L"Path", buffer_, MAX_ENV);
63
+ if (read) {
64
+ std::wstring delimiter = L";";
65
+ size_t pos = 0;
66
+ std::vector<std::wstring> paths;
67
+ std::wstring buffer(buffer_);
68
+ while ((pos = buffer.find(delimiter)) != std::wstring::npos) {
69
+ paths.push_back(buffer.substr(0, pos));
70
+ buffer.erase(0, pos + delimiter.length());
71
+ }
72
+
73
+ const wchar_t *filename_ = filename.c_str();
74
+
75
+ for (size_t i = 0; i < paths.size(); ++i) {
76
+ std::wstring path = paths[i];
77
+ wchar_t searchPath[MAX_PATH];
78
+ ::PathCombineW(searchPath, const_cast<wchar_t*>(path.c_str()), filename_);
79
+
80
+ if (searchPath == NULL) {
81
+ continue;
82
+ }
83
+
84
+ if (file_exists(searchPath)) {
85
+ shellpath = searchPath;
86
+ break;
87
+ }
88
+ }
89
+ }
90
+
91
+ delete[] buffer_;
92
+ return shellpath;
93
+ }
94
+
95
+ } // namespace path_util
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
3
+ * Copyright (c) 2016, Daniel Imms (MIT License).
4
+ * Copyright (c) 2018, Microsoft Corporation (MIT License).
5
+ */
6
+
7
+ #ifndef NODE_PTY_PATH_UTIL_H_
8
+ #define NODE_PTY_PATH_UTIL_H_
9
+
10
+ #define NODE_ADDON_API_DISABLE_DEPRECATED
11
+ #include <napi.h>
12
+ #include <string>
13
+
14
+ #define MAX_ENV 65536
15
+
16
+ namespace path_util {
17
+
18
+ std::wstring to_wstring(const Napi::String& str);
19
+ std::string wstring_to_string(const std::wstring &wide_string);
20
+ const char* from_wstring(const wchar_t* wstr);
21
+ bool file_exists(std::wstring filename);
22
+ std::wstring get_shell_path(std::wstring filename);
23
+
24
+ } // namespace path_util
25
+
26
+ #endif // NODE_PTY_PATH_UTIL_H_
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Copyright (c) 2017, Daniel Imms (MIT License).
3
+ * Copyright (c) 2018, Microsoft Corporation (MIT License).
4
+ */
5
+
6
+ declare module 'node-pty' {
7
+ /**
8
+ * Forks a process as a pseudoterminal.
9
+ * @param file The file to launch.
10
+ * @param args The file's arguments as argv (string[]) or in a pre-escaped CommandLine format
11
+ * (string). Note that the CommandLine option is only available on Windows and is expected to be
12
+ * escaped properly.
13
+ * @param options The options of the terminal.
14
+ * @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx
15
+ * @see Parsing C++ Command-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
16
+ * @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx
17
+ */
18
+ export function spawn(file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty;
19
+
20
+ export interface IBasePtyForkOptions {
21
+
22
+ /**
23
+ * Name of the terminal to be set in environment ($TERM variable).
24
+ */
25
+ name?: string;
26
+
27
+ /**
28
+ * Number of intial cols of the pty.
29
+ */
30
+ cols?: number;
31
+
32
+ /**
33
+ * Number of initial rows of the pty.
34
+ */
35
+ rows?: number;
36
+
37
+ /**
38
+ * Working directory to be set for the child program.
39
+ */
40
+ cwd?: string;
41
+
42
+ /**
43
+ * Environment to be set for the child program.
44
+ */
45
+ env?: { [key: string]: string | undefined };
46
+
47
+ /**
48
+ * String encoding of the underlying pty.
49
+ * If set, incoming data will be decoded to strings and outgoing strings to bytes applying this encoding.
50
+ * If unset, incoming data will be delivered as raw bytes (Buffer type).
51
+ * By default 'utf8' is assumed, to unset it explicitly set it to `null`.
52
+ */
53
+ encoding?: string | null;
54
+
55
+ /**
56
+ * (EXPERIMENTAL)
57
+ * Whether to enable flow control handling (false by default). If enabled a message of `flowControlPause`
58
+ * will pause the socket and thus blocking the child program execution due to buffer back pressure.
59
+ * A message of `flowControlResume` will resume the socket into flow mode.
60
+ * For performance reasons only a single message as a whole will match (no message part matching).
61
+ * If flow control is enabled the `flowControlPause` and `flowControlResume` messages are not forwarded to
62
+ * the underlying pseudoterminal.
63
+ */
64
+ handleFlowControl?: boolean;
65
+
66
+ /**
67
+ * (EXPERIMENTAL)
68
+ * The string that should pause the pty when `handleFlowControl` is true. Default is XOFF ('\x13').
69
+ */
70
+ flowControlPause?: string;
71
+
72
+ /**
73
+ * (EXPERIMENTAL)
74
+ * The string that should resume the pty when `handleFlowControl` is true. Default is XON ('\x11').
75
+ */
76
+ flowControlResume?: string;
77
+ }
78
+
79
+ export interface IPtyForkOptions extends IBasePtyForkOptions {
80
+ /**
81
+ * Security warning: use this option with great caution,
82
+ * as opened file descriptors with higher privileges might leak to the child program.
83
+ */
84
+ uid?: number;
85
+ gid?: number;
86
+ }
87
+
88
+ export interface IWindowsPtyForkOptions extends IBasePtyForkOptions {
89
+ /**
90
+ * Whether to use the ConPTY system on Windows. When this is not set, ConPTY will be used when
91
+ * the Windows build number is >= 18309 (instead of winpty). Note that ConPTY is available from
92
+ * build 17134 but is too unstable to enable by default.
93
+ *
94
+ * @deprecated This option is ignored and will be removed in a future version.
95
+ * https://github.com/microsoft/node-pty/issues/871
96
+ */
97
+ useConpty?: boolean;
98
+
99
+ /**
100
+ * (EXPERIMENTAL)
101
+ *
102
+ * Whether to use the conpty.dll shipped with the node-pty package instead of the one built into
103
+ * Windows. Defaults to false.
104
+ */
105
+ useConptyDll?: boolean;
106
+
107
+ /**
108
+ * Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty.
109
+ * @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole
110
+ */
111
+ conptyInheritCursor?: boolean;
112
+ }
113
+
114
+ /**
115
+ * An interface representing a pseudoterminal.
116
+ */
117
+ export interface IPty {
118
+ /**
119
+ * The process ID of the outer process.
120
+ */
121
+ readonly pid: number;
122
+
123
+ /**
124
+ * The column size in characters.
125
+ */
126
+ readonly cols: number;
127
+
128
+ /**
129
+ * The row size in characters.
130
+ */
131
+ readonly rows: number;
132
+
133
+ /**
134
+ * The title of the active process.
135
+ */
136
+ readonly process: string;
137
+
138
+ /**
139
+ * (EXPERIMENTAL)
140
+ * Whether to handle flow control. Useful to disable/re-enable flow control during runtime.
141
+ * Use this for binary data that is likely to contain the `flowControlPause` string by accident.
142
+ */
143
+ handleFlowControl: boolean;
144
+
145
+ /**
146
+ * Adds an event listener for when a data event fires. This happens when data is returned from
147
+ * the pty.
148
+ * @returns an `IDisposable` to stop listening.
149
+ */
150
+ readonly onData: IEvent<string>;
151
+
152
+ /**
153
+ * Adds an event listener for when an exit event fires. This happens when the pty exits.
154
+ * @returns an `IDisposable` to stop listening.
155
+ */
156
+ readonly onExit: IEvent<{ exitCode: number, signal?: number }>;
157
+
158
+ /**
159
+ * Resizes the dimensions of the pty.
160
+ * @param columns The number of columns to use.
161
+ * @param rows The number of rows to use.
162
+ * @param pixelSize Optional pixel dimensions of the pty. On Unix, this sets the `ws_xpixel`
163
+ * and `ws_ypixel` fields of the `winsize` struct. Applications running in the pty can read
164
+ * these values via the `TIOCGWINSZ` ioctl. This parameter is ignored on Windows.
165
+ */
166
+ resize(columns: number, rows: number, pixelSize?: { width: number, height: number }): void;
167
+
168
+ /**
169
+ * Clears the pty's internal representation of its buffer. This is a no-op
170
+ * unless on Windows/ConPTY. This is useful if the buffer is cleared on the
171
+ * frontend in order to synchronize state with the backend to avoid ConPTY
172
+ * possibly reprinting the screen.
173
+ */
174
+ clear(): void;
175
+
176
+ /**
177
+ * Writes data to the pty.
178
+ * @param data The data to write.
179
+ */
180
+ write(data: string | Buffer): void;
181
+
182
+ /**
183
+ * Kills the pty.
184
+ * @param signal The signal to use, defaults to SIGHUP. This parameter is not supported on
185
+ * Windows.
186
+ * @throws Will throw when signal is used on Windows.
187
+ */
188
+ kill(signal?: string): void;
189
+
190
+ /**
191
+ * Pauses the pty for customizable flow control.
192
+ */
193
+ pause(): void;
194
+
195
+ /**
196
+ * Resumes the pty for customizable flow control.
197
+ */
198
+ resume(): void;
199
+ }
200
+
201
+ /**
202
+ * An object that can be disposed via a dispose function.
203
+ */
204
+ export interface IDisposable {
205
+ dispose(): void;
206
+ }
207
+
208
+ /**
209
+ * An event that can be listened to.
210
+ * @returns an `IDisposable` to stop listening.
211
+ */
212
+ export interface IEvent<T> {
213
+ (listener: (e: T) => any): IDisposable;
214
+ }
215
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "pwragent",
3
- "version": "0.0.0",
3
+ "version": "1.3.7",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "Coming soon: PwrAgent command-line tools.",
6
+ "description": "Experimental headless PwrAgent daemon and command-line client.",
7
7
  "author": "PwrDrvr LLC <harold@pwrdrvr.com>",
8
8
  "license": "MIT",
9
9
  "copyright": "Copyright (c) 2026 PwrDrvr LLC",
@@ -27,16 +27,43 @@
27
27
  },
28
28
  "files": [
29
29
  "bin",
30
+ "src",
31
+ "examples",
30
32
  "README.md",
31
- "LICENSE"
33
+ "LICENSE",
34
+ "dist",
35
+ "licenses"
32
36
  ],
33
37
  "publishConfig": {
34
- "access": "public"
38
+ "access": "public",
39
+ "registry": "https://registry.npmjs.org",
40
+ "tag": "latest"
35
41
  },
36
42
  "bin": {
37
43
  "pwragent": "bin/pwragent.js"
38
44
  },
39
45
  "scripts": {
40
- "pack:check": "npm pack --dry-run"
46
+ "test": "node --test test/*.test.js scripts/*.test.mjs",
47
+ "pack:check": "node scripts/pack-check.mjs",
48
+ "build": "node scripts/build.mjs",
49
+ "pretest": "node scripts/build.mjs",
50
+ "prepack": "node scripts/build.mjs",
51
+ "release:check": "node scripts/release.mjs"
52
+ },
53
+ "devDependencies": {
54
+ "@pwragent/federation": "workspace:*",
55
+ "@pwragent/shared": "workspace:*",
56
+ "esbuild": "0.28.1",
57
+ "@pwragent/agent-tools": "workspace:*"
58
+ },
59
+ "dependencies": {
60
+ "node-pty": "1.2.0-beta.15"
61
+ },
62
+ "bundleDependencies": [
63
+ "node-pty"
64
+ ],
65
+ "pwragentRelease": {
66
+ "tag": "v1.3.7",
67
+ "commit": "e5a9fab9c90daa2f0bed661768969dc5c5301415"
41
68
  }
42
69
  }
package/src/cli.js ADDED
@@ -0,0 +1,123 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import path from "node:path";
4
+ import { parseArgs } from "node:util";
5
+ import { setTimeout as delay } from "node:timers/promises";
6
+ import { call } from "./client.js";
7
+ import { serve } from "./daemon.js";
8
+ import { ACTIVE } from "./store.js";
9
+
10
+ const help = `PwrAgent Headless (experimental, Linux/macOS, Node >=22)
11
+
12
+ pwragent serve --workspace /project --codex-home /codex-home --codex-bin /path/to/codex
13
+ pwragent project-list
14
+ pwragent project-create --name NAME [--label NAME]
15
+ pwragent project-clone --name NAME --remote URL [--label NAME]
16
+ pwragent project-add|project-remove --path /workspace/project
17
+ pwragent git-auth-configure --config-file /outpost/git-auth-settings.json
18
+ pwragent status | list
19
+ pwragent submit --id JOB --prompt-file /task.txt [--sandbox read-only|workspace-write]
20
+ [--model MODEL] [--thread THREAD_ID] [--cwd /workspace/project]
21
+ pwragent show|read|interrupt|wait --id JOB
22
+ pwragent federation-join --invite-file /invite.txt --gateway ws://127.0.0.1:47830 [--label NAME]
23
+ pwragent federation-invite [--port 47831] [--label NAME]
24
+ pwragent federation-status | federation-leave
25
+ pwragent federation-revoke --peer INSTANCE_ID
26
+
27
+ Use --profile NAME (or PWRAGENT_PROFILE, default: default).
28
+ State: $PWRAGENT_HOME/profiles/NAME/state/headless (default root: ~/.pwragent).
29
+ serve runs in the foreground; use systemd to own its lifetime remotely.
30
+ submit returns durable acceptance. wait exits 0 only for completed, 2 otherwise.
31
+ Approvals are never granted. Job IDs cannot be reused for different input.
32
+ Federation uses private retained identity files and the standard encrypted protocol.
33
+ No provisioning, login, queue, automatic replay, or local messaging providers.
34
+ `;
35
+
36
+ export async function main(args) {
37
+ const { values, positionals } = parseArgs({ args, allowPositionals: true, strict: true, options: {
38
+ help: { type: "boolean", short: "h" },
39
+ profile: { type: "string" },
40
+ workspace: { type: "string" },
41
+ "codex-home": { type: "string" },
42
+ "codex-bin": { type: "string" },
43
+ id: { type: "string" },
44
+ "prompt-file": { type: "string" },
45
+ thread: { type: "string" },
46
+ model: { type: "string" },
47
+ sandbox: { type: "string" },
48
+ "invite-file": { type: "string" },
49
+ gateway: { type: "string" },
50
+ label: { type: "string" },
51
+ port: { type: "string" },
52
+ peer: { type: "string" },
53
+ name: { type: "string" }, remote: { type: "string" }, path: { type: "string" }, cwd: { type: "string" }, "config-file": { type: "string" },
54
+ } });
55
+ if (values.help || !positionals.length) { console.log(help); return 0; }
56
+ if (positionals.length !== 1) throw new Error("Expected one command; see --help");
57
+ const command = positionals[0];
58
+ const allowed = {
59
+ serve: ["workspace", "codex-home", "codex-bin"],
60
+ submit: ["id", "prompt-file", "thread", "model", "sandbox", "cwd"],
61
+ "project-list": [], "project-create": ["name", "label"], "project-clone": ["name", "remote", "label"],
62
+ "project-add": ["path", "label"], "project-remove": ["path"], "git-auth-configure": ["config-file"],
63
+ show: ["id"], read: ["id"], interrupt: ["id"], wait: ["id"], status: [], list: [],
64
+ "federation-join": ["invite-file", "gateway", "label"],
65
+ "federation-invite": ["port", "label"],
66
+ "federation-status": [], "federation-leave": [], "federation-revoke": ["peer"],
67
+ };
68
+ if (!Object.hasOwn(allowed, command)) throw new Error(`Unknown command: ${command}`);
69
+ for (const option of Object.keys(values)) {
70
+ if (option !== "profile" && !allowed[command].includes(option)) throw new Error(`--${option} does not apply to ${command}`);
71
+ }
72
+ const profile = values.profile || process.env.PWRAGENT_PROFILE || "default";
73
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(profile)) throw new Error("Invalid profile name");
74
+ const root = process.env.PWRAGENT_HOME || path.join(homedir(), ".pwragent");
75
+ if (!path.isAbsolute(root)) throw new Error("PWRAGENT_HOME must be absolute");
76
+ const directory = path.join(root, "profiles", profile, "state", "headless");
77
+ if (command === "serve") {
78
+ return serve({ directory, workspace: values.workspace, home: values["codex-home"], command: values["codex-bin"], profile });
79
+ }
80
+ if (command.startsWith("project-") || command === "git-auth-configure") {
81
+ let params = { name: values.name, remote: values.remote, path: values.path, label: values.label };
82
+ if (command === "project-clone" && !values.remote) throw new Error("--remote is required");
83
+ if (command === "git-auth-configure") {
84
+ const file = values["config-file"];
85
+ if (!file || !statSync(file).isFile() || statSync(file).size > 16384) throw new Error("--config-file must name an outpost JSON file of at most 16 KiB");
86
+ params = JSON.parse(readFileSync(file, "utf8"));
87
+ }
88
+ console.log(JSON.stringify(await call(directory, command, params)));
89
+ return 0;
90
+ }
91
+ if (command.startsWith("federation-")) {
92
+ let invite;
93
+ if (command === "federation-join") {
94
+ const file = values["invite-file"];
95
+ if (!file || !statSync(file).isFile() || statSync(file).size > 64 * 1024) throw new Error("--invite-file must name a file of at most 64 KiB");
96
+ invite = readFileSync(file, "utf8").trim();
97
+ }
98
+ console.log(JSON.stringify(await call(directory, command, { invite, gateway: values.gateway, label: values.label,
99
+ port: values.port === undefined ? undefined : Number(values.port), peer: values.peer })));
100
+ return 0;
101
+ }
102
+ if (allowed[command].includes("id") && !values.id) throw new Error("--id is required");
103
+ if (command === "submit") {
104
+ const file = values["prompt-file"];
105
+ if (!file || !statSync(file).isFile() || statSync(file).size > 256 * 1024) throw new Error("--prompt-file must name a file of at most 256 KiB");
106
+ const result = await call(directory, "submit", { id: values.id, prompt: readFileSync(file, "utf8"),
107
+ threadId: values.thread, cwd: values.cwd, model: values.model, sandbox: values.sandbox });
108
+ console.log(JSON.stringify(result));
109
+ return 0;
110
+ }
111
+ if (command === "wait") {
112
+ for (;;) {
113
+ const job = await call(directory, "show", { id: values.id });
114
+ if (!ACTIVE.has(job.status)) {
115
+ console.log(JSON.stringify(job));
116
+ return job.status === "completed" ? 0 : 2;
117
+ }
118
+ await delay(1000);
119
+ }
120
+ }
121
+ console.log(JSON.stringify(await call(directory, command, { id: values.id })));
122
+ return 0;
123
+ }
package/src/client.js ADDED
@@ -0,0 +1,27 @@
1
+ import net from "node:net";
2
+ import path from "node:path";
3
+ import { readMessages, MAX_REQUEST_BYTES, MAX_RESPONSE_BYTES } from "./wire.js";
4
+
5
+ export function call(directory, method, params = {}) {
6
+ const data = JSON.stringify({ version: 1, method, params }) + "\n";
7
+ if (Buffer.byteLength(data) > MAX_REQUEST_BYTES) return Promise.reject(new Error("Request exceeds 512 KiB"));
8
+ return new Promise((resolve, reject) => {
9
+ const socket = net.createConnection(path.join(directory, "rpc.sock"));
10
+ let settled = false;
11
+ const finish = (error, result) => {
12
+ if (settled) return;
13
+ settled = true;
14
+ socket.destroy();
15
+ if (error) reject(error); else resolve(result);
16
+ };
17
+ socket.setTimeout(45000, () => finish(new Error("Daemon request timed out; use the same job ID to check acceptance")));
18
+ socket.on("error", (error) => finish(new Error(`Cannot contact headless daemon (${error.code || error.message})`)));
19
+ socket.on("close", () => finish(new Error("Daemon disconnected; use the same job ID to check acceptance")));
20
+ socket.on("connect", () => socket.write(data));
21
+ readMessages(socket, MAX_RESPONSE_BYTES, (message) => {
22
+ if (typeof message.error === "string") finish(new Error(message.error));
23
+ else if ("result" in message) finish(null, message.result);
24
+ else finish(new Error("Invalid daemon response"));
25
+ }, (error) => finish(error));
26
+ });
27
+ }