@actionway/cli 0.18.5 → 0.18.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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @actionway/cli — 本地端壳(bin: `actionway`)
2
2
 
3
3
  actionway CLI 的本地端壳(迁移文档 `docs/DL_TO_ACTIONWAY_CLI_MIGRATION.md` §3.2 / §3.4):
4
- 终端用户在自己机器上的 Coding Agent(Codex / Claude Code)通过它调用 Actionway 能力。
4
+ 终端用户在自己机器上的 Coding Agent(Codex / Claude Code / WorkBuddy)通过它调用 Actionway 能力。
5
5
 
6
6
  ## 安装
7
7
 
@@ -25,12 +25,35 @@ actionway init
25
25
  `doctor` 只输出脱敏后的 Node/npm、registry、global PATH、配置目录、loopback
26
26
  和 OAuth discovery 检查;它不会修改 npm、代理、CA、PATH 或 PowerShell policy。
27
27
 
28
+ ### Development 通道
29
+
30
+ 成功合并并部署 `dev` 后,workflow 从同一 bundle 生成独立公共包 `@actionway/cli-dev`。它只暴露 `actionway-dev`,固定连接 `https://dev.actionway.ai`,使用 `${HOME}/.actionway-dev` 和 `actionway-dev` Skill;不会安装或覆盖正式 `actionway`:
31
+
32
+ ```bash
33
+ npm install --global @actionway/cli-dev@latest
34
+ actionway-dev doctor
35
+ actionway-dev init
36
+ actionway-dev status
37
+ ```
38
+
39
+ `actionway-dev update` 只读取和更新 `@actionway/cli-dev@latest`。每个 dev npm 版本包含不可变 source SHA 与 Actions run metadata;同一 tarball 也保存在私有 GitHub prerelease,后者仅用于内部审计和精确回滚,普通安装不需要 GitHub 账号。
40
+
41
+ 本地 Agent 在每条 Actionway 命令通过全局参数声明当前宿主:
42
+
43
+ ```bash
44
+ actionway --agent-host=codex tools search "generate a product image"
45
+ actionway --agent-host=claude_code tools search "generate a product image"
46
+ actionway --agent-host=workbuddy tools search "generate a product image"
47
+ ```
48
+
49
+ 不带 `--agent-host` 表示用户直接使用 CLI。该声明不写入共享凭据,所以同一个用户和全局 CLI 可以交替被多个 Agent 使用。CLI 同时从每次进程的 Node.js 运行环境识别 Windows、macOS、WSL 或 Linux;这些字段只作为 Site 侧产品分析事件属性,不参与鉴权、计费或 Gateway 路由。
50
+
28
51
  - **auth 唯一途径:Clerk OAuth(PKCE)**。issuer / client_id 运行时从
29
52
  `{public origin}/api/auth/cli-config` 发现;浏览器授权走随机 `127.0.0.1` 回调端口;
30
53
  凭据存 `~/.actionway/credentials.json`(POSIX 上为 0600 owner-only 文件;
31
54
  Windows 上 chmod 无效,保密性依赖 `%USERPROFILE%` 的继承 ACL——不要把
32
55
  `ACTIONWAY_CONFIG_DIR` 指到共享目录)。CLI 从不打印或存储 OAuth client secret。
33
- - **命令面 = 明码写出的注册表**(无 profile flag、无运行期分叉):
56
+ - **命令面 = 明码写出的注册表**(无 capability profile flag;production/development 只切换包、命令、状态、Skill、更新源与 Gateway):
34
57
  - `tools search / inspect / call` 是长尾 capability 的规范发现与执行面;
35
58
  - 高频 typed command 继续提供参数与文件 UX,但投影到同一个 Tool Call;
36
59
  - `account invitation` 获取或首次创建当前账户的永久邀请码、链接和服务端本地化分享文案;
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { homedir } from "node:os";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ process.env.ACTIONWAY_CLI_CHANNEL = "development";
9
+ process.env.ACTIONWAY_CONFIG_DIR ||= join(homedir(), ".actionway-dev");
10
+ process.env.ACTIONWAY_GATEWAY_URL ||= "https://dev.actionway.ai";
11
+
12
+ const executable = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "index.js");
13
+ const result = spawnSync(process.execPath, [executable, ...process.argv.slice(2)], {
14
+ stdio: "inherit",
15
+ env: process.env,
16
+ windowsHide: true,
17
+ });
18
+ if (result.error) throw result.error;
19
+ process.exit(result.status || 0);
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { createHash } from "node:crypto";
5
+ import {
6
+ cpSync,
7
+ existsSync,
8
+ mkdirSync,
9
+ mkdtempSync,
10
+ readFileSync,
11
+ rmSync,
12
+ writeFileSync,
13
+ } from "node:fs";
14
+ import { homedir, tmpdir } from "node:os";
15
+ import { dirname, join, resolve } from "node:path";
16
+
17
+ const repository = (process.env.ACTIONWAY_DEV_REPOSITORY || "PawLogic/actionway").trim();
18
+ const packageName = "@actionway/cli-dev";
19
+ const devHome = resolve(process.env.ACTIONWAY_DEV_HOME || join(homedir(), ".actionway-dev"));
20
+ const metadataPath = join(devHome, "current-cli.json");
21
+ const updateIntervalMs = Number(process.env.ACTIONWAY_DEV_UPDATE_INTERVAL_MS || 300_000);
22
+ const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
23
+
24
+ export function commandInvocation(command, args, platform = process.platform, env = process.env) {
25
+ if (platform === "win32" && /\.(?:cmd|bat)$/i.test(command)) {
26
+ const commandProcessor = (env.ComSpec ?? env.COMSPEC ?? "cmd.exe").trim().replace(/^"|"$/g, "") || "cmd.exe";
27
+ return {
28
+ command: commandProcessor,
29
+ args: ["/d", "/s", "/c", command, ...args],
30
+ };
31
+ }
32
+ return { command, args: [...args] };
33
+ }
34
+
35
+ function run(command, args, { capture = false, env = process.env } = {}) {
36
+ const invocation = commandInvocation(command, args, process.platform, env);
37
+ const result = spawnSync(invocation.command, invocation.args, {
38
+ encoding: "utf8",
39
+ env,
40
+ stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit",
41
+ windowsHide: true,
42
+ });
43
+ if (result.error) throw result.error;
44
+ if (result.status !== 0) {
45
+ if (capture && result.stderr) process.stderr.write(result.stderr);
46
+ const error = new Error(`${command} exited with status ${result.status}`);
47
+ error.exitCode = result.status || 1;
48
+ throw error;
49
+ }
50
+ return capture ? result.stdout.trim() : "";
51
+ }
52
+
53
+ function readMetadata() {
54
+ if (!existsSync(metadataPath)) return null;
55
+ return JSON.parse(readFileSync(metadataPath, "utf8"));
56
+ }
57
+
58
+ function writeMetadata(metadata) {
59
+ mkdirSync(devHome, { recursive: true, mode: 0o700 });
60
+ writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, { mode: 0o600 });
61
+ }
62
+
63
+ export function validateReleaseManifest(manifest, expectedRepository = repository) {
64
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
65
+ throw new Error("Development release manifest must be a JSON object.");
66
+ }
67
+ if (
68
+ manifest.schema_version !== 1 ||
69
+ manifest.channel !== "development" ||
70
+ manifest.distribution !== "github_release"
71
+ ) {
72
+ throw new Error("Development release manifest has an unsupported schema, channel, or distribution.");
73
+ }
74
+ if (manifest.repository !== expectedRepository) {
75
+ throw new Error("Development release manifest uses an unexpected repository.");
76
+ }
77
+ if (typeof manifest.source_sha !== "string" || !/^[0-9a-f]{40}$/.test(manifest.source_sha)) {
78
+ throw new Error("Development release manifest has an invalid source_sha.");
79
+ }
80
+ if (manifest.release_tag !== `actionway-dev-${manifest.source_sha}`) {
81
+ throw new Error("Development release manifest tag does not match source_sha.");
82
+ }
83
+ if (manifest.gateway_url !== "https://dev.actionway.ai") {
84
+ throw new Error("Development release manifest does not target the development Gateway.");
85
+ }
86
+ if (!manifest.archive || typeof manifest.archive !== "object" || Array.isArray(manifest.archive)) {
87
+ throw new Error("Development release manifest is missing archive metadata.");
88
+ }
89
+ if (typeof manifest.archive.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(manifest.archive.sha256)) {
90
+ throw new Error("Development release manifest has an invalid archive sha256.");
91
+ }
92
+ if (
93
+ manifest.archive.asset_name !==
94
+ `actionway-cli-${manifest.source_sha}-${manifest.archive.sha256}.tgz`
95
+ ) {
96
+ throw new Error("Development release archive asset does not match source_sha and sha256.");
97
+ }
98
+ if (!Number.isSafeInteger(manifest.archive.size_bytes) || manifest.archive.size_bytes <= 0) {
99
+ throw new Error("Development release manifest has an invalid archive size.");
100
+ }
101
+ if (!Number.isSafeInteger(manifest.run_id) || manifest.run_id <= 0) {
102
+ throw new Error("Development release manifest has an invalid run_id.");
103
+ }
104
+ if (
105
+ manifest.run_url !==
106
+ `https://github.com/${manifest.repository}/actions/runs/${manifest.run_id}`
107
+ ) {
108
+ throw new Error("Development release manifest has an invalid run_url.");
109
+ }
110
+ if (manifest.artifact_name !== `actionway-cli-dev-${manifest.source_sha}`) {
111
+ throw new Error("Development release manifest has an invalid artifact_name.");
112
+ }
113
+ if (typeof manifest.published_at !== "string" || !Number.isFinite(Date.parse(manifest.published_at))) {
114
+ throw new Error("Development release manifest has an invalid published_at timestamp.");
115
+ }
116
+
117
+ return {
118
+ headSha: manifest.source_sha,
119
+ runId: manifest.run_id,
120
+ runUrl: manifest.run_url,
121
+ artifact: manifest.artifact_name,
122
+ releaseTag: manifest.release_tag,
123
+ archiveAsset: manifest.archive.asset_name,
124
+ archiveSha256: manifest.archive.sha256,
125
+ archiveSize: manifest.archive.size_bytes,
126
+ archivePath: null,
127
+ };
128
+ }
129
+
130
+ function resolveLatestRelease() {
131
+ const localArchive = process.env.ACTIONWAY_DEV_ARCHIVE?.trim();
132
+ if (localArchive) {
133
+ return {
134
+ headSha: (process.env.ACTIONWAY_DEV_SOURCE_SHA || "local-development").trim(),
135
+ runId: null,
136
+ runUrl: null,
137
+ artifact: null,
138
+ releaseTag: null,
139
+ archiveAsset: null,
140
+ archiveSha256: null,
141
+ archiveSize: null,
142
+ archivePath: resolve(localArchive),
143
+ };
144
+ }
145
+
146
+ const manifest = JSON.parse(run(npmCommand, ["view", `${packageName}@latest`, "--json"], { capture: true }));
147
+ if (manifest?.name !== packageName || typeof manifest.version !== "string") {
148
+ throw new Error(`npm did not return a valid ${packageName}@latest manifest.`);
149
+ }
150
+ const metadata = manifest.actionway;
151
+ if (
152
+ metadata?.channel !== "development" ||
153
+ !/^[0-9a-f]{40}$/.test(metadata.source_sha || "") ||
154
+ !Number.isSafeInteger(metadata.run_id) ||
155
+ metadata.run_id <= 0 ||
156
+ metadata.run_url !== `https://github.com/${repository}/actions/runs/${metadata.run_id}` ||
157
+ metadata.gateway_url !== "https://dev.actionway.ai"
158
+ ) {
159
+ throw new Error(`The published ${packageName}@${manifest.version} metadata is invalid.`);
160
+ }
161
+ if (typeof manifest.dist?.integrity !== "string" || !manifest.dist.integrity.startsWith("sha512-")) {
162
+ throw new Error(`The published ${packageName}@${manifest.version} integrity is missing.`);
163
+ }
164
+ return {
165
+ headSha: metadata.source_sha,
166
+ runId: metadata.run_id,
167
+ runUrl: metadata.run_url,
168
+ artifact: `actionway-cli-dev-${metadata.source_sha}`,
169
+ releaseTag: `actionway-dev-${metadata.source_sha}`,
170
+ archiveAsset: null,
171
+ archiveSha256: null,
172
+ archiveSize: null,
173
+ archivePath: null,
174
+ packageVersion: manifest.version,
175
+ npmIntegrity: manifest.dist.integrity,
176
+ };
177
+ }
178
+
179
+ function installSkill(packageRoot) {
180
+ const source = join(packageRoot, "assets", "skill", "actionway-dev");
181
+ if (!existsSync(source)) throw new Error("The development CLI artifact is missing its actionway-dev Skill.");
182
+ const explicitTarget = process.env.ACTIONWAY_DEV_SKILL_DIR?.trim();
183
+ const officialRoot = join(homedir(), ".agents", "skills");
184
+ const legacyRoot = join(homedir(), ".codex", "skills");
185
+ const skillRoot = process.env.CODEX_HOME?.trim()
186
+ ? join(resolve(process.env.CODEX_HOME), "skills")
187
+ : existsSync(legacyRoot) && !existsSync(officialRoot)
188
+ ? legacyRoot
189
+ : officialRoot;
190
+ const target = explicitTarget ? resolve(explicitTarget) : join(skillRoot, "actionway-dev");
191
+ mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
192
+ rmSync(target, { recursive: true, force: true });
193
+ cpSync(source, target, { recursive: true });
194
+ return target;
195
+ }
196
+
197
+ function refreshInstalledAssets(metadata) {
198
+ const packageRoot = join(metadata.runtime_dir, "node_modules", "@actionway", "cli-dev");
199
+ if (!existsSync(packageRoot)) throw new Error("The installed Actionway development CLI package is missing.");
200
+ const skillDir = installSkill(packageRoot);
201
+ const packagedLauncher = join(packageRoot, "assets", "dev", "actionway-dev-launcher.mjs");
202
+ const installedLauncher = join(devHome, "bin", "actionway-dev-launcher.mjs");
203
+ if (existsSync(packagedLauncher)) {
204
+ mkdirSync(dirname(installedLauncher), { recursive: true, mode: 0o700 });
205
+ cpSync(packagedLauncher, installedLauncher, { force: true });
206
+ }
207
+ return { ...metadata, skill_dir: skillDir };
208
+ }
209
+
210
+ export function verifyArchiveBytes(bytes, expectedSize, expectedSha256) {
211
+ const archive = Buffer.from(bytes);
212
+ if (archive.byteLength !== expectedSize) {
213
+ throw new Error(
214
+ `Development archive size mismatch: expected ${expectedSize}, received ${archive.byteLength}.`,
215
+ );
216
+ }
217
+ const actualSha256 = createHash("sha256").update(archive).digest("hex");
218
+ if (actualSha256 !== expectedSha256) throw new Error("Development archive checksum mismatch.");
219
+ return archive;
220
+ }
221
+
222
+ function installRelease(release) {
223
+ const downloadDir = mkdtempSync(join(tmpdir(), "actionway-dev-cli-"));
224
+ try {
225
+ let archivePath = release.archivePath;
226
+ if (!archivePath) {
227
+ const packed = JSON.parse(
228
+ run(npmCommand, ["pack", `${packageName}@${release.packageVersion}`, "--pack-destination", downloadDir, "--json"], {
229
+ capture: true,
230
+ }),
231
+ );
232
+ const filename = packed?.[0]?.filename;
233
+ if (typeof filename !== "string") throw new Error(`npm pack did not return ${packageName}@${release.packageVersion}.`);
234
+ archivePath = join(downloadDir, filename);
235
+ const actualIntegrity = `sha512-${createHash("sha512").update(readFileSync(archivePath)).digest("base64")}`;
236
+ if (actualIntegrity !== release.npmIntegrity) throw new Error("Development npm archive integrity mismatch.");
237
+ }
238
+ if (!existsSync(archivePath)) throw new Error(`Actionway development CLI archive not found: ${archivePath}`);
239
+
240
+ const runtimeDir = join(devHome, "runtimes", release.headSha);
241
+ mkdirSync(runtimeDir, { recursive: true, mode: 0o700 });
242
+ run(npmCommand, [
243
+ "install",
244
+ "--ignore-scripts",
245
+ "--no-audit",
246
+ "--no-fund",
247
+ "--prefix",
248
+ runtimeDir,
249
+ archivePath,
250
+ ]);
251
+
252
+ const packageRoot = join(runtimeDir, "node_modules", "@actionway", "cli-dev");
253
+ const executable = join(runtimeDir, "node_modules", ".bin", process.platform === "win32" ? "actionway-dev.cmd" : "actionway-dev");
254
+ if (!existsSync(executable)) throw new Error("The development CLI artifact did not install an actionway-dev executable.");
255
+ const skillDir = installSkill(packageRoot);
256
+
257
+ const packagedLauncher = join(packageRoot, "assets", "dev", "actionway-dev-launcher.mjs");
258
+ const installedLauncher = join(devHome, "bin", "actionway-dev-launcher.mjs");
259
+ if (existsSync(packagedLauncher)) {
260
+ mkdirSync(dirname(installedLauncher), { recursive: true, mode: 0o700 });
261
+ cpSync(packagedLauncher, installedLauncher, { force: true });
262
+ }
263
+
264
+ const now = new Date().toISOString();
265
+ const metadata = {
266
+ version: 1,
267
+ source_sha: release.headSha,
268
+ run_id: release.runId,
269
+ run_url: release.runUrl,
270
+ artifact: release.artifact,
271
+ release_tag: release.releaseTag,
272
+ archive_asset: release.archiveAsset,
273
+ archive_sha256: release.archiveSha256,
274
+ package: packageName,
275
+ package_version: release.packageVersion ?? null,
276
+ npm_integrity: release.npmIntegrity ?? null,
277
+ runtime_dir: runtimeDir,
278
+ skill_dir: skillDir,
279
+ gateway_url: "https://dev.actionway.ai",
280
+ installed_at: now,
281
+ last_checked_at: now,
282
+ };
283
+ writeMetadata(metadata);
284
+ return metadata;
285
+ } finally {
286
+ rmSync(downloadDir, { recursive: true, force: true });
287
+ }
288
+ }
289
+
290
+ function checkForUpdate({ install, skipInterval = false } = {}) {
291
+ const current = readMetadata();
292
+ if (!skipInterval && current?.last_checked_at) {
293
+ const age = Date.now() - Date.parse(current.last_checked_at);
294
+ if (Number.isFinite(age) && age >= 0 && age < updateIntervalMs) return current;
295
+ }
296
+
297
+ const latest = resolveLatestRelease();
298
+ const updateAvailable = current?.source_sha !== latest.headSha;
299
+ if (!install) {
300
+ process.stdout.write(
301
+ `${JSON.stringify({
302
+ ok: true,
303
+ update_available: updateAvailable,
304
+ installed_source_sha: current?.source_sha || null,
305
+ latest_source_sha: latest.headSha,
306
+ })}\n`,
307
+ );
308
+ return current;
309
+ }
310
+ if (!updateAvailable) {
311
+ const refreshed = refreshInstalledAssets(current);
312
+ refreshed.last_checked_at = new Date().toISOString();
313
+ writeMetadata(refreshed);
314
+ return refreshed;
315
+ }
316
+ return installRelease(latest);
317
+ }
318
+
319
+ function executableFor(metadata) {
320
+ return join(
321
+ metadata.runtime_dir,
322
+ "node_modules",
323
+ ".bin",
324
+ process.platform === "win32" ? "actionway-dev.cmd" : "actionway-dev",
325
+ );
326
+ }
327
+
328
+ function main(args = process.argv.slice(2)) {
329
+ if (args[0] === "status") {
330
+ process.stdout.write(`${JSON.stringify({ ok: true, channel: "development", ...readMetadata() }, null, 2)}\n`);
331
+ return 0;
332
+ }
333
+ if (args[0] === "update") {
334
+ const checkOnly = args.includes("--check");
335
+ const metadata = checkForUpdate({ install: !checkOnly, skipInterval: true });
336
+ if (!checkOnly && metadata) {
337
+ process.stdout.write(`Actionway development CLI is current at ${metadata.source_sha}.\n`);
338
+ }
339
+ return 0;
340
+ }
341
+
342
+ let metadata = readMetadata();
343
+ try {
344
+ metadata = checkForUpdate({ install: true }) || metadata;
345
+ } catch (error) {
346
+ if (!metadata) throw error;
347
+ process.stderr.write(`Actionway development update check failed; using ${metadata.source_sha}: ${error.message}\n`);
348
+ }
349
+ if (!metadata) throw new Error("Actionway development CLI is not installed. Run the installer first.");
350
+
351
+ const executable = executableFor(metadata);
352
+ if (!existsSync(executable)) throw new Error("The installed Actionway development CLI runtime is missing.");
353
+ const childEnv = {
354
+ ...process.env,
355
+ ACTIONWAY_CONFIG_DIR: devHome,
356
+ ACTIONWAY_GATEWAY_URL: "https://dev.actionway.ai",
357
+ };
358
+ const invocation = commandInvocation(executable, args.length > 0 ? args : ["--help"], process.platform, childEnv);
359
+ const result = spawnSync(invocation.command, invocation.args, {
360
+ stdio: "inherit",
361
+ env: childEnv,
362
+ windowsHide: true,
363
+ });
364
+ if (result.error) throw result.error;
365
+ return result.status || 0;
366
+ }
367
+
368
+ if (process.env.ACTIONWAY_DEV_ASSET_IMPORT_ONLY !== "1") {
369
+ process.exit(main());
370
+ }
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import {
5
+ accessSync,
6
+ chmodSync,
7
+ constants,
8
+ copyFileSync,
9
+ existsSync,
10
+ mkdirSync,
11
+ readFileSync,
12
+ writeFileSync,
13
+ } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import { delimiter, dirname, join, resolve } from "node:path";
16
+ import { fileURLToPath, pathToFileURL } from "node:url";
17
+
18
+ const repository = (process.env.ACTIONWAY_DEV_REPOSITORY || "PawLogic/actionway").trim();
19
+ const bootstrapRef = (process.env.ACTIONWAY_DEV_BOOTSTRAP_REF || "dev").trim();
20
+ const devHome = resolve(process.env.ACTIONWAY_DEV_HOME || join(homedir(), ".actionway-dev"));
21
+ const installedLauncher = join(devHome, "bin", "actionway-dev-launcher.mjs");
22
+ const marker = "Actionway managed development launcher";
23
+
24
+ export function commandInvocation(command, args, platform = process.platform, env = process.env) {
25
+ if (platform === "win32" && /\.(?:cmd|bat)$/i.test(command)) {
26
+ const commandProcessor = (env.ComSpec ?? env.COMSPEC ?? "cmd.exe").trim().replace(/^"|"$/g, "") || "cmd.exe";
27
+ return {
28
+ command: commandProcessor,
29
+ args: ["/d", "/s", "/c", command, ...args],
30
+ };
31
+ }
32
+ return { command, args: [...args] };
33
+ }
34
+
35
+ function run(command, args, { capture = false } = {}) {
36
+ const invocation = commandInvocation(command, args);
37
+ const result = spawnSync(invocation.command, invocation.args, {
38
+ encoding: "utf8",
39
+ env: process.env,
40
+ stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit",
41
+ windowsHide: true,
42
+ });
43
+ if (result.error) throw result.error;
44
+ if (result.status !== 0) {
45
+ if (capture && result.stderr) process.stderr.write(result.stderr);
46
+ throw new Error(`${command} exited with status ${result.status || 1}`);
47
+ }
48
+ return capture ? result.stdout : "";
49
+ }
50
+
51
+ function requireNode20() {
52
+ const major = Number(process.versions.node.split(".")[0]);
53
+ if (!Number.isInteger(major) || major < 20) {
54
+ throw new Error(`Actionway development requires Node.js 20 or newer; found ${process.version}.`);
55
+ }
56
+ }
57
+
58
+ function pathEntries() {
59
+ return (process.env.PATH || "")
60
+ .split(delimiter)
61
+ .map((entry) => entry.trim())
62
+ .filter(Boolean)
63
+ .map((entry) => resolve(entry));
64
+ }
65
+
66
+ function samePath(left, right) {
67
+ return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
68
+ }
69
+
70
+ function isOnPath(directory) {
71
+ const target = resolve(directory);
72
+ return pathEntries().some((entry) => samePath(entry, target));
73
+ }
74
+
75
+ function ensureWritable(directory) {
76
+ try {
77
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
78
+ accessSync(directory, constants.W_OK);
79
+ return true;
80
+ } catch {
81
+ return false;
82
+ }
83
+ }
84
+
85
+ function existingActionwayBin() {
86
+ const executableNames = process.platform === "win32" ? ["actionway.cmd", "actionway.exe", "actionway"] : ["actionway"];
87
+ for (const directory of pathEntries()) {
88
+ if (executableNames.some((name) => existsSync(join(directory, name))) && ensureWritable(directory)) return directory;
89
+ }
90
+ return null;
91
+ }
92
+
93
+ function npmGlobalBin() {
94
+ try {
95
+ const prefix = run(process.platform === "win32" ? "npm.cmd" : "npm", ["prefix", "--global"], {
96
+ capture: true,
97
+ }).trim();
98
+ if (!prefix) return null;
99
+ const directory = process.platform === "win32" ? resolve(prefix) : resolve(prefix, "bin");
100
+ return isOnPath(directory) && ensureWritable(directory) ? directory : null;
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+
106
+ function resolveBinDir() {
107
+ if (process.env.ACTIONWAY_DEV_BIN_DIR?.trim()) {
108
+ const explicit = resolve(process.env.ACTIONWAY_DEV_BIN_DIR.trim());
109
+ if (!ensureWritable(explicit)) throw new Error(`Development CLI bin directory is not writable: ${explicit}`);
110
+ return explicit;
111
+ }
112
+ return existingActionwayBin() || npmGlobalBin() || join(homedir(), ".local", "bin");
113
+ }
114
+
115
+ function localLauncherSource() {
116
+ const candidates = [
117
+ process.env.ACTIONWAY_DEV_LAUNCHER?.trim(),
118
+ join(dirname(fileURLToPath(import.meta.url)), "actionway-dev-launcher.mjs"),
119
+ join(process.cwd(), "packages", "cli", "assets", "dev", "actionway-dev-launcher.mjs"),
120
+ ].filter(Boolean);
121
+ return candidates.find((candidate) => existsSync(resolve(candidate))) || null;
122
+ }
123
+
124
+ function fetchLauncher() {
125
+ const endpoint = `repos/${repository}/contents/packages/cli/assets/dev/actionway-dev-launcher.mjs?ref=${encodeURIComponent(bootstrapRef)}`;
126
+ const source = run(
127
+ "gh",
128
+ ["api", "-H", "Accept: application/vnd.github.raw+json", endpoint],
129
+ { capture: true },
130
+ );
131
+ if (!source.startsWith("#!/usr/bin/env node")) {
132
+ throw new Error(`GitHub did not return the Actionway development launcher from ${repository}@${bootstrapRef}.`);
133
+ }
134
+ return source;
135
+ }
136
+
137
+ function installLauncher() {
138
+ mkdirSync(dirname(installedLauncher), { recursive: true, mode: 0o700 });
139
+ const localSource = localLauncherSource();
140
+ if (localSource) copyFileSync(resolve(localSource), installedLauncher);
141
+ else writeFileSync(installedLauncher, fetchLauncher(), { mode: 0o600 });
142
+ run(process.execPath, ["--check", installedLauncher], { capture: true });
143
+ }
144
+
145
+ function installShim(binDir) {
146
+ if (!ensureWritable(binDir)) throw new Error(`Development CLI bin directory is not writable: ${binDir}`);
147
+ const shimPath = join(binDir, process.platform === "win32" ? "actionway-dev.cmd" : "actionway-dev");
148
+ if (existsSync(shimPath) && !readFileSync(shimPath, "utf8").includes(marker)) {
149
+ throw new Error(`Refusing to overwrite unmanaged executable: ${shimPath}`);
150
+ }
151
+ const shim =
152
+ process.platform === "win32"
153
+ ? `@echo off\r\nrem ${marker}\r\nnode "${installedLauncher}" %*\r\n`
154
+ : `#!/usr/bin/env node\n// ${marker}\nimport(${JSON.stringify(pathToFileURL(installedLauncher).href)});\n`;
155
+ writeFileSync(shimPath, shim, { mode: 0o755 });
156
+ if (process.platform !== "win32") chmodSync(shimPath, 0o755);
157
+ return shimPath;
158
+ }
159
+
160
+ function main() {
161
+ requireNode20();
162
+ run(process.platform === "win32" ? "npm.cmd" : "npm", ["--version"], { capture: true });
163
+ if (!process.env.ACTIONWAY_DEV_ARCHIVE?.trim()) {
164
+ run("gh", ["auth", "status", "--hostname", "github.com"], { capture: true });
165
+ }
166
+ installLauncher();
167
+ const binDir = resolveBinDir();
168
+ const shimPath = installShim(binDir);
169
+ run(process.execPath, [installedLauncher, "update"]);
170
+
171
+ process.stdout.write(`Installed ${shimPath}.\n`);
172
+ if (!isOnPath(binDir)) {
173
+ process.stdout.write(`Add ${binDir} to PATH, then open a new terminal.\n`);
174
+ }
175
+ process.stdout.write("Stable command: actionway\nDevelopment command: actionway-dev\n");
176
+ }
177
+
178
+ if (process.env.ACTIONWAY_DEV_ASSET_IMPORT_ONLY !== "1") {
179
+ try {
180
+ main();
181
+ } catch (error) {
182
+ process.stderr.write(`Actionway development installation failed: ${error instanceof Error ? error.message : String(error)}\n`);
183
+ process.exit(1);
184
+ }
185
+ }
@@ -9,6 +9,17 @@ metadata:
9
9
 
10
10
  Use the installed `actionway` CLI and its server-backed Capability Registry as the source of truth. Do not memorize a complete command catalog, Schema, provider list, or price table in this Skill.
11
11
 
12
+ ## Declare this Agent
13
+
14
+ Use one invocation prefix for every Actionway command in this Agent session, including update, authentication, discovery, calls, and waits:
15
+
16
+ - Codex: `actionway --agent-host=codex`
17
+ - Claude Code: `actionway --agent-host=claude_code`
18
+ - WorkBuddy: `actionway --agent-host=workbuddy`
19
+ - A human directly using the CLI: `actionway` without `--agent-host`
20
+
21
+ Where the instructions below show `actionway ...`, insert this Agent's `--agent-host` immediately after `actionway`. Do not persist one host in shared credentials: the same user and global CLI may be used by multiple Agents.
22
+
12
23
  ## Prepare
13
24
 
14
25
  1. Run Actionway commands with network access and permission to read `~/.actionway`. On Windows, use the generated `actionway.cmd` entrypoint when PowerShell policy blocks `actionway.ps1`. If setup or update fails, run `actionway.cmd doctor` on Windows or `actionway doctor` elsewhere from the host terminal and preserve its failed check IDs; do not weaken registry, proxy, CA, PATH, or PowerShell policy without user approval. If a sandboxed command reports `E_NOT_AUTHENTICATED` for an already logged-in user, rerun the same command with host permission before starting login again.
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: actionway-dev
3
+ description: Use the installed Actionway development channel to discover, inspect, price, and call capabilities against dev.actionway.ai; test confirmed calls and async jobs; or inspect the deployed dev CLI source SHA without touching the stable Actionway installation. Trigger for Actionway dev environment, staging verification, a newly merged dev deployment, or pre-production Codex testing.
4
+ ---
5
+
6
+ # Actionway Dev
7
+
8
+ Use only the `actionway-dev` executable. Never substitute the stable `actionway` command.
9
+
10
+ ## Prepare
11
+
12
+ 1. Run `actionway-dev update` once per Agent session. It follows public `@actionway/cli-dev@latest` from the latest successful `dev` deployment and never updates the stable `@actionway/cli` package.
13
+ 2. Run `actionway-dev status` when the user asks which source SHA is installed.
14
+ 3. Run `actionway-dev whoami`. On `E_NOT_AUTHENTICATED`, run `actionway-dev init` and ask the user only to approve browser login. Never request or expose credentials.
15
+
16
+ ## Discover and call
17
+
18
+ 1. For long-tail capabilities run `actionway-dev tools search "<intent>"`, then inspect exactly one supported result with `actionway-dev tools inspect <tool_ref>`.
19
+ 2. Match the inspected Schema exactly and call with `actionway-dev tools call <tool_ref> --input-json '<object>'`.
20
+ 3. On `confirmation_required`, present the server plan and quote. Resume only after approval with `actionway-dev tools call --resume <call_ref>`.
21
+ 4. On `accepted`, immediately run `actionway-dev wait --job-ref=<uuid>`. Reuse the same reference until terminal state; never resubmit a pending call.
22
+
23
+ Use typed commands only when `actionway-dev --help` exposes an exact fit. Read command help and `--print-schema`, then run `--dry-run` before a real typed call.
24
+
25
+ ## Boundaries
26
+
27
+ - Treat the development Gateway as the authority for Schema, price, confirmation, and results.
28
+ - Development calls can consume development credits and real upstream vendor cost.
29
+ - Do not run the stable CLI's updater, publish npm packages, or change production configuration.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Actionway Dev"
3
+ short_description: "Use the isolated Actionway development channel"
4
+ default_prompt: "Use $actionway-dev to test a capability against the latest Actionway development deployment."