autowonder 0.2.0 → 0.2.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.
package/README.md CHANGED
@@ -1,50 +1,44 @@
1
1
  # autowonder
2
2
 
3
- Local runtime daemon for executing AI agent dispatch packages.
3
+ AutoWonder 本地 agent runtime。安装后启动 daemon,持续轮询本地 assignment 队列并执行任务包。
4
4
 
5
- ## Quick Start
5
+ ## 连接 AutoWonder 服务
6
6
 
7
7
  ```bash
8
- # Connect to server (like harness)
9
- npx -y autowonder connect --server-url https://autowonder.example.com --token <your-token>
8
+ # Qoder CLI
9
+ npx -y autowonder@0.2.2 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider qoder
10
10
 
11
- # Or run locally
12
- npx -y autowonder start
13
- npx -y autowonder dispatch ./assignment.json
14
- ```
11
+ # Claude Code
12
+ npx -y autowonder@0.2.2 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider claude
15
13
 
16
- ## Commands
14
+ # Codex CLI
15
+ npx -y autowonder@0.2.2 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider codex
16
+ ```
17
17
 
18
- | Command | Description |
19
- |---------|-------------|
20
- | `autowonder connect` | Connect to server and run in foreground |
21
- | `autowonder start` | Start daemon in background |
22
- | `autowonder stop` | Stop background daemon |
23
- | `autowonder status` | Show daemon status and runtime info |
24
- | `autowonder dispatch <file>` | Submit a dispatch assignment |
25
- | `autowonder install` | Install/update daemon binary |
18
+ `connect` 会安装当前 npm 包内置的 daemon,并使用页面生成的 WebSocket endpoint、Token 和执行器 ID 建立连接。目标机器必须提前安装并登录对应的 Qoder CLI、Claude Code 或 Codex CLI;runtime 会继承当前用户的 HOME、环境变量和 CLI 登录状态。
26
19
 
27
- ## Options
20
+ ## 提交 assignment
28
21
 
29
- - `--provider <name>` Agent CLI: claude, codex, qoder (auto-detected)
30
- - `--max-tasks <n>` — Max concurrent dispatches (default: 2)
31
- - `--server-url <url>` — Server URL for remote task polling
32
- - `--token <token>` — Authentication token for server
33
- - `--name <name>` — Runtime instance name (default: hostname)
22
+ daemon 默认轮询 `~/autowonder_workspaces/assignments/*.json`。队列里放 assignment JSON,不放 package zip;JSON `taskPackageRef.downloadUrl` 应指向可下载的 OSS、HTTP 或本地 package 地址。
34
23
 
35
- ## Requirements
24
+ ```bash
25
+ queue="$HOME/autowonder_workspaces/assignments"
26
+ mkdir -p "$queue"
27
+ cp ./assignment.json "$queue/.assignment.tmp"
28
+ mv "$queue/.assignment.tmp" "$queue/assignment.json"
29
+ ```
36
30
 
37
- - One of: Claude Code CLI, Codex CLI, or Qoder CLI
38
- - For building from source: Go 1.22+ and Git
31
+ 也可以直接提交到本地 API:
39
32
 
40
- ## How It Works
33
+ ```bash
34
+ npx -y autowonder@0.2.2 dispatch ./assignment.json
35
+ ```
41
36
 
42
- 1. Downloads or builds the `autowonder-daemon` binary
43
- 2. Starts a local HTTP API on `127.0.0.1:34989`
44
- 3. Accepts dispatch assignments via file queue, API, or server polling
45
- 4. Executes packages using the configured agent provider
46
- 5. Produces structured artifacts, results, and observability
37
+ ## 管理 daemon
47
38
 
48
- ## License
39
+ ```bash
40
+ npx -y autowonder@0.2.2 status
41
+ npx -y autowonder@0.2.2 stop
42
+ ```
49
43
 
50
- Apache-2.0
44
+ 默认 API 是 `http://127.0.0.1:34989`,日志位于 `~/.autowonder/daemon.log`。npm 包不包含任何 agent、MCP 或服务端凭证。
package/bin/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  const { execFileSync, spawn, spawnSync } = require("child_process");
5
+ const crypto = require("crypto");
5
6
  const path = require("path");
6
7
  const fs = require("fs");
7
8
  const os = require("os");
@@ -14,6 +15,7 @@ const BIN_DIR = path.join(AUTOWONDER_HOME, "bin");
14
15
  const DAEMON_BIN = path.join(BIN_DIR, "autowonder-daemon");
15
16
  const CONFIG_PATH = path.join(AUTOWONDER_HOME, "config.json");
16
17
  const PID_PATH = path.join(AUTOWONDER_HOME, "daemon.pid");
18
+ const STATE_PATH = path.join(AUTOWONDER_HOME, "daemon-state.json");
17
19
  const LOG_PATH = path.join(AUTOWONDER_HOME, "daemon.log");
18
20
 
19
21
  const BINARY_BASE_URL = process.env.AUTOWONDER_BINARY_URL || "";
@@ -40,17 +42,105 @@ function detectProvider() {
40
42
  const providers = [];
41
43
  try { execFileSync("which", ["claude"], { stdio: "ignore" }); providers.push("claude"); } catch {}
42
44
  try { execFileSync("which", ["codex"], { stdio: "ignore" }); providers.push("codex"); } catch {}
43
- try { execFileSync("which", ["qoder"], { stdio: "ignore" }); providers.push("qoder"); } catch {}
45
+ try { execFileSync("which", ["qodercli"], { stdio: "ignore" }); providers.push("qoder"); } catch {
46
+ try { execFileSync("which", ["qoder"], { stdio: "ignore" }); providers.push("qoder"); } catch {}
47
+ }
44
48
  return providers;
45
49
  }
46
50
 
47
51
  function daemonRunning() {
48
- if (!fs.existsSync(PID_PATH)) return false;
49
- const pid = parseInt(fs.readFileSync(PID_PATH, "utf8").trim(), 10);
50
- if (!pid) return false;
52
+ const pid = readPid();
53
+ return Boolean(pid && processRunning(pid) && ownsDaemonProcess(pid));
54
+ }
55
+
56
+ function processRunning(pid) {
51
57
  try { process.kill(pid, 0); return true; } catch { return false; }
52
58
  }
53
59
 
60
+ function processFingerprint(pid) {
61
+ const result = spawnSync("ps", ["-p", String(pid), "-o", "lstart=", "-o", "command="], {
62
+ encoding: "utf8",
63
+ timeout: 2000,
64
+ });
65
+ return result.status === 0 ? (result.stdout || "").trim() : "";
66
+ }
67
+
68
+ function legacyDaemonProcess(pid) {
69
+ const result = spawnSync("ps", ["-p", String(pid), "-o", "command="], {
70
+ encoding: "utf8",
71
+ timeout: 2000,
72
+ });
73
+ if (result.status !== 0) return false;
74
+ const command = (result.stdout || "").trim();
75
+ return command === DAEMON_BIN || command.startsWith(`${DAEMON_BIN} `);
76
+ }
77
+
78
+ function readDaemonState() {
79
+ if (!fs.existsSync(STATE_PATH)) return null;
80
+ try { return JSON.parse(fs.readFileSync(STATE_PATH, "utf8")); } catch { return null; }
81
+ }
82
+
83
+ function ownsDaemonProcess(pid) {
84
+ const state = readDaemonState();
85
+ if (state && state.pid === pid) {
86
+ const fingerprint = processFingerprint(pid);
87
+ return Boolean(fingerprint && fingerprint === state.fingerprint);
88
+ }
89
+ return legacyDaemonProcess(pid);
90
+ }
91
+
92
+ function clearDaemonState() {
93
+ try { fs.unlinkSync(PID_PATH); } catch {}
94
+ try { fs.unlinkSync(STATE_PATH); } catch {}
95
+ }
96
+
97
+ function saveDaemonState(pid, instanceId) {
98
+ const fingerprint = processFingerprint(pid);
99
+ if (!fingerprint) return false;
100
+ fs.writeFileSync(PID_PATH, String(pid) + "\n");
101
+ fs.writeFileSync(STATE_PATH, JSON.stringify({ pid, fingerprint, instanceId }) + "\n", { mode: 0o600 });
102
+ return true;
103
+ }
104
+
105
+ async function waitForProcessExit(pid, timeoutMs = 5000) {
106
+ const deadline = Date.now() + timeoutMs;
107
+ while (Date.now() < deadline) {
108
+ if (!processRunning(pid)) return true;
109
+ await new Promise((resolve) => setTimeout(resolve, 50));
110
+ }
111
+ return !processRunning(pid);
112
+ }
113
+
114
+ function checkDaemonHealth(addr, instanceId) {
115
+ return new Promise((resolve) => {
116
+ const request = http.get(`http://${addr}/health`, { timeout: 500 }, (response) => {
117
+ let body = "";
118
+ response.setEncoding("utf8");
119
+ response.on("data", (chunk) => { body += chunk; });
120
+ response.on("end", () => {
121
+ try {
122
+ const health = JSON.parse(body);
123
+ resolve(response.statusCode === 200 && health.status === "ok" && health.instanceId === instanceId);
124
+ } catch {
125
+ resolve(false);
126
+ }
127
+ });
128
+ });
129
+ request.on("timeout", () => request.destroy());
130
+ request.on("error", () => resolve(false));
131
+ });
132
+ }
133
+
134
+ async function waitForDaemonHealth(addr, pid, instanceId, timeoutMs = 5000) {
135
+ const deadline = Date.now() + timeoutMs;
136
+ while (Date.now() < deadline) {
137
+ if (!processRunning(pid)) return false;
138
+ if (await checkDaemonHealth(addr, instanceId)) return true;
139
+ await new Promise((resolve) => setTimeout(resolve, 50));
140
+ }
141
+ return false;
142
+ }
143
+
54
144
  function readPid() {
55
145
  if (!fs.existsSync(PID_PATH)) return null;
56
146
  const pid = parseInt(fs.readFileSync(PID_PATH, "utf8").trim(), 10);
@@ -125,7 +215,12 @@ function tryDownload(url, dest) {
125
215
  function doRequest(requestUrl, redirects) {
126
216
  if (redirects > 5) { resolve(false); return; }
127
217
  const parsedUrl = new URL(requestUrl);
128
- const options = { hostname: parsedUrl.hostname, path: parsedUrl.pathname + parsedUrl.search, headers: { "User-Agent": "autowonder-npm" } };
218
+ const options = {
219
+ hostname: parsedUrl.hostname,
220
+ port: parsedUrl.port || undefined,
221
+ path: parsedUrl.pathname + parsedUrl.search,
222
+ headers: { "User-Agent": "autowonder-npm" },
223
+ };
129
224
  client.get(options, (res) => {
130
225
  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
131
226
  doRequest(res.headers.location, redirects + 1);
@@ -177,28 +272,32 @@ function buildFromSource() {
177
272
  // ─── Commands ────────────────────────────────────────────────────────
178
273
 
179
274
  async function cmdConnect(args) {
180
- const flags = parseFlags(args, ["token", "server-url", "provider", "name", "max-tasks"]);
275
+ const flags = parseFlags(args, ["ws-url", "token", "executor-id", "provider", "name", "max-tasks"]);
181
276
 
182
- if (!flags.token && !flags["server-url"]) {
183
- error("Usage: autowonder connect --server-url <url> --token <token> [--provider claude] [--name my-machine]");
277
+ if (!flags["ws-url"] || !flags.token || !flags["executor-id"]) {
278
+ error("Usage: autowonder connect --ws-url <url> --token <token> --executor-id <id> [--provider qoder]");
184
279
  process.exit(1);
185
280
  }
186
281
 
187
- const installed = await installBinary();
282
+ // A version-pinned npx command must always run the binary bundled with that package.
283
+ const installed = await installBinary({ force: true });
188
284
  if (!installed) process.exit(1);
189
285
 
190
286
  const providers = detectProvider();
191
- const provider = flags.provider || providers[0] || "claude";
192
- if (providers.length === 0) {
193
- error(`No agent CLI detected. Install one of: claude, codex, qoder`);
287
+ const provider = flags.provider || providers[0];
288
+ if (!provider) {
289
+ error(`No agent CLI detected. Install one of: claude, codex, qodercli`);
194
290
  process.exit(1);
195
291
  }
196
292
 
293
+ const executorId = flags["executor-id"];
294
+
197
295
  const config = {
198
- serverUrl: flags["server-url"] || "",
199
- token: flags.token || "",
296
+ serverUrl: flags["ws-url"],
297
+ token: flags.token,
298
+ executorId,
200
299
  provider,
201
- name: flags.name || os.hostname(),
300
+ name: flags.name || `daemon_${provider}_${executorId}`,
202
301
  maxTasks: parseInt(flags["max-tasks"] || "2", 10),
203
302
  };
204
303
  saveConfig(config);
@@ -215,9 +314,13 @@ async function cmdConnect(args) {
215
314
  AUTOWONDER_PROVIDER: config.provider,
216
315
  AUTOWONDER_MAX_CONCURRENT_DISPATCHES: String(config.maxTasks),
217
316
  AUTOWONDER_DAEMON_ID: config.name,
317
+ AUTOWONDER_RUNTIME_ID: `rt_${provider}_${executorId}`,
318
+ AUTOWONDER_WORKSPACE_ROOT: path.join(os.homedir(), `autowonder_workspaces_${provider}_${executorId}`),
319
+ AUTOWONDER_LOCAL_API_ADDR: "127.0.0.1:0",
320
+ AUTOWONDER_SERVER_WS_URL: config.serverUrl,
321
+ AUTOWONDER_EXECUTOR_TOKEN: config.token,
322
+ AUTOWONDER_EXECUTOR_ID: executorId,
218
323
  };
219
- if (config.serverUrl) env.AUTOWONDER_SERVER_URL = config.serverUrl;
220
- if (config.token) env.AUTOWONDER_SERVER_TOKEN = config.token;
221
324
 
222
325
  const child = spawn(DAEMON_BIN, [], { env, stdio: "inherit" });
223
326
  child.on("exit", (code) => process.exit(code || 0));
@@ -230,7 +333,7 @@ async function cmdStart(args) {
230
333
 
231
334
  if (daemonRunning()) {
232
335
  log("Daemon is already running.");
233
- return;
336
+ return true;
234
337
  }
235
338
 
236
339
  const installed = await installBinary();
@@ -245,6 +348,7 @@ async function cmdStart(args) {
245
348
 
246
349
  ensureDir(AUTOWONDER_HOME);
247
350
  const logFd = fs.openSync(LOG_PATH, "a");
351
+ const instanceId = crypto.randomUUID();
248
352
  const child = spawn(DAEMON_BIN, [], {
249
353
  detached: true,
250
354
  stdio: ["ignore", logFd, logFd],
@@ -253,28 +357,52 @@ async function cmdStart(args) {
253
357
  AUTOWONDER_PROVIDER: provider,
254
358
  AUTOWONDER_LOCAL_API_ADDR: addr,
255
359
  AUTOWONDER_MAX_CONCURRENT_DISPATCHES: maxTasks,
360
+ AUTOWONDER_DAEMON_INSTANCE_ID: instanceId,
256
361
  },
257
362
  });
363
+ fs.closeSync(logFd);
258
364
  fs.writeFileSync(PID_PATH, String(child.pid) + "\n");
365
+
366
+ if (!await waitForDaemonHealth(addr, child.pid, instanceId) || !saveDaemonState(child.pid, instanceId)) {
367
+ if (processRunning(child.pid)) {
368
+ try { child.kill("SIGTERM"); } catch {}
369
+ await waitForProcessExit(child.pid, 2000);
370
+ }
371
+ clearDaemonState();
372
+ error(`Daemon failed to become healthy at http://${addr}/health.`);
373
+ process.exitCode = 1;
374
+ return false;
375
+ }
376
+
259
377
  child.unref();
260
- fs.closeSync(logFd);
261
378
 
262
379
  log(`Daemon started (pid=${child.pid})`);
263
380
  log(` API: http://${addr}`);
264
381
  log(` Log: ${LOG_PATH}`);
265
382
  log(` PID: ${PID_PATH}`);
383
+ return true;
266
384
  }
267
385
 
268
- function cmdStop() {
386
+ async function cmdStop(options = {}) {
269
387
  const pid = readPid();
270
- if (!pid || !daemonRunning()) {
271
- log("Daemon is not running.");
272
- try { fs.unlinkSync(PID_PATH); } catch {}
273
- return;
388
+ if (!pid || !processRunning(pid)) {
389
+ if (!options.quiet) log("Daemon is not running.");
390
+ clearDaemonState();
391
+ return true;
392
+ }
393
+ if (!ownsDaemonProcess(pid)) {
394
+ if (!options.quiet) log(`Ignoring stale daemon PID ${pid}; it belongs to another process.`);
395
+ clearDaemonState();
396
+ return true;
274
397
  }
275
398
  process.kill(pid, "SIGTERM");
276
- log(`Stopped daemon (pid=${pid})`);
277
- try { fs.unlinkSync(PID_PATH); } catch {}
399
+ if (!await waitForProcessExit(pid)) {
400
+ error(`Daemon did not stop within 5 seconds (pid=${pid}).`);
401
+ return false;
402
+ }
403
+ if (!options.quiet) log(`Stopped daemon (pid=${pid})`);
404
+ clearDaemonState();
405
+ return true;
278
406
  }
279
407
 
280
408
  async function cmdStatus() {
@@ -340,7 +468,7 @@ async function cmdDispatch(args) {
340
468
  "-X", "POST",
341
469
  "-H", "Content-Type: application/json",
342
470
  "-d", body,
343
- `http://${DEFAULT_API_ADDR}/dispatches`,
471
+ `http://${DEFAULT_API_ADDR}/dispatches/local`,
344
472
  ], { encoding: "utf8", timeout: 10000 });
345
473
 
346
474
  const lines = (res.stdout || "").trim().split("\n");
@@ -358,9 +486,14 @@ async function cmdDispatch(args) {
358
486
  }
359
487
 
360
488
  async function cmdInstall(args) {
361
- const flags = parseFlags(args, ["force"]);
362
- const installed = await installBinary({ force: flags.force === "true" || args.includes("--force") });
489
+ const flags = parseFlags(args, ["force", "provider", "max-tasks", "addr"]);
490
+ const force = flags.force === "true" || args.includes("--force");
491
+ if (force && daemonRunning() && !await cmdStop({ quiet: true })) {
492
+ process.exit(1);
493
+ }
494
+ const installed = await installBinary({ force });
363
495
  if (!installed) process.exit(1);
496
+ if (!await cmdStart(args)) process.exit(1);
364
497
  }
365
498
 
366
499
  function cmdHelp() {
@@ -368,25 +501,26 @@ function cmdHelp() {
368
501
  autowonder — local runtime for AI agent dispatch execution
369
502
 
370
503
  Usage:
371
- autowonder connect --server-url <url> --token <token> Connect to server and run
504
+ autowonder connect --ws-url <url> --token <token> --executor-id <id> Connect to server and run
372
505
  autowonder start [--provider claude] Start daemon in background
373
506
  autowonder stop Stop background daemon
374
507
  autowonder status Show daemon status
375
508
  autowonder dispatch <assignment.json> Submit a dispatch
376
- autowonder install [--force] Install/update daemon binary
509
+ autowonder install [--force] Install/update and start daemon
377
510
 
378
511
  Options:
379
512
  --provider <name> Agent provider: claude, codex, qoder (default: auto-detect)
380
513
  --max-tasks <n> Max concurrent dispatches (default: 2)
381
514
  --addr <host:port> Local API address (default: 127.0.0.1:34989)
382
- --server-url <url> Server URL for remote dispatch
383
- --token <token> Authentication token
384
- --name <name> Runtime name (default: hostname)
515
+ --ws-url <url> Executor WebSocket endpoint
516
+ --token <token> Executor authentication token
517
+ --executor-id <id> Executor ID
518
+ --name <name> Runtime name (default: provider + executor ID)
385
519
  -h, --help Show this help
386
520
 
387
521
  Examples:
388
522
  # Quick start — connect to server
389
- npx -y autowonder connect --server-url https://autowonder.example.com --token xxx
523
+ npx -y autowonder connect --ws-url wss://autowonder.example.com/ws/executor --token xxx --executor-id 10000 --provider qoder
390
524
 
391
525
  # Local mode — start daemon, submit tasks manually
392
526
  npx -y autowonder start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autowonder",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "AutoWonder local runtime — execute AI agent dispatch packages on your machine",
5
5
  "bin": {
6
6
  "autowonder": "bin/cli.js"
@@ -28,5 +28,9 @@
28
28
  },
29
29
  "engines": {
30
30
  "node": ">=16"
31
+ },
32
+ "scripts": {
33
+ "build": "./scripts/build-and-publish.sh --build",
34
+ "test": "node --test test/*.test.js"
31
35
  }
32
36
  }
@@ -18,6 +18,18 @@ PKG_DIR="$(dirname "$SCRIPT_DIR")"
18
18
  VENDOR_DIR="$PKG_DIR/vendor"
19
19
  SOURCE_DIR="${AUTOWONDER_SOURCE_DIR:-}"
20
20
  SOURCE_REPO="${AUTOWONDER_SOURCE_REPO:-}"
21
+ TEMP_SOURCE_DIR=""
22
+ NPM_CONFIG_FILE=""
23
+
24
+ cleanup() {
25
+ if [[ -n "$TEMP_SOURCE_DIR" ]]; then
26
+ rm -rf "$TEMP_SOURCE_DIR"
27
+ fi
28
+ if [[ -n "$NPM_CONFIG_FILE" ]]; then
29
+ rm -f "$NPM_CONFIG_FILE"
30
+ fi
31
+ }
32
+ trap cleanup EXIT
21
33
 
22
34
  BUILD_ONLY=false
23
35
  if [[ "${1:-}" == "--build" ]]; then
@@ -27,11 +39,19 @@ fi
27
39
  echo "━━━ autowonder npm build ━━━"
28
40
 
29
41
  # Get source code
42
+ if [[ -z "$SOURCE_DIR" && -f "$PKG_DIR/../go.mod" ]]; then
43
+ SOURCE_DIR="$(cd "$PKG_DIR/.." && pwd)"
44
+ fi
45
+
30
46
  if [[ -n "$SOURCE_DIR" && -d "$SOURCE_DIR" ]]; then
31
47
  echo "Using local source: $SOURCE_DIR"
32
48
  else
33
- SOURCE_DIR=$(mktemp -d)
34
- trap "rm -rf $SOURCE_DIR" EXIT
49
+ if [[ -z "$SOURCE_REPO" ]]; then
50
+ echo "Error: set AUTOWONDER_SOURCE_DIR or AUTOWONDER_SOURCE_REPO"
51
+ exit 1
52
+ fi
53
+ TEMP_SOURCE_DIR="$(mktemp -d)"
54
+ SOURCE_DIR="$TEMP_SOURCE_DIR"
35
55
  echo "Cloning from $SOURCE_REPO..."
36
56
  git clone --depth 1 "$SOURCE_REPO" "$SOURCE_DIR"
37
57
  fi
@@ -51,7 +71,7 @@ for i in "${!platforms[@]}"; do
51
71
  output="$VENDOR_DIR/autowonder-daemon-${goos}-${goarch}"
52
72
 
53
73
  echo "Building $platform..."
54
- (cd "$SOURCE_DIR" && GOOS="$goos" GOARCH="$goarch" go build -o "$output" -ldflags="-s -w" ./cmd/autowonder-daemon)
74
+ (cd "$SOURCE_DIR" && CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" go build -o "$output" -ldflags="-s -w" ./cmd/autowonder-daemon)
55
75
  chmod +x "$output"
56
76
  echo " -> $output ($(du -h "$output" | cut -f1))"
57
77
  done
@@ -76,10 +96,10 @@ if [[ -z "${NPM_TOKEN:-}" ]]; then
76
96
  exit 1
77
97
  fi
78
98
 
79
- echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
80
- npm version patch --no-git-tag-version
81
- npm publish --access public
82
- rm -f .npmrc
99
+ umask 077
100
+ NPM_CONFIG_FILE="$(mktemp)"
101
+ printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$NPM_CONFIG_FILE"
102
+ npm publish --access public --userconfig "$NPM_CONFIG_FILE"
83
103
 
84
104
  echo ""
85
105
  echo "Published $(node -p "require('./package.json').name + '@' + require('./package.json').version")"