@actionway/cli 0.18.5 → 0.18.6

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
@@ -25,12 +25,25 @@ 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
+
28
41
  - **auth 唯一途径:Clerk OAuth(PKCE)**。issuer / client_id 运行时从
29
42
  `{public origin}/api/auth/cli-config` 发现;浏览器授权走随机 `127.0.0.1` 回调端口;
30
43
  凭据存 `~/.actionway/credentials.json`(POSIX 上为 0600 owner-only 文件;
31
44
  Windows 上 chmod 无效,保密性依赖 `%USERPROFILE%` 的继承 ACL——不要把
32
45
  `ACTIONWAY_CONFIG_DIR` 指到共享目录)。CLI 从不打印或存储 OAuth client secret。
33
- - **命令面 = 明码写出的注册表**(无 profile flag、无运行期分叉):
46
+ - **命令面 = 明码写出的注册表**(无 capability profile flag;production/development 只切换包、命令、状态、Skill、更新源与 Gateway):
34
47
  - `tools search / inspect / call` 是长尾 capability 的规范发现与执行面;
35
48
  - 高频 typed command 继续提供参数与文件 UX,但投影到同一个 Tool Call;
36
49
  - `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
+ }
@@ -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."
package/dist/index.js CHANGED
@@ -12152,13 +12152,36 @@ import { chmodSync, existsSync, mkdirSync, readFileSync as readFileSync2, writeF
12152
12152
  import { homedir } from "node:os";
12153
12153
  import { join } from "node:path";
12154
12154
 
12155
+ // src/distribution.ts
12156
+ var PRODUCTION_DISTRIBUTION = {
12157
+ channel: "production",
12158
+ packageName: "@actionway/cli",
12159
+ distTag: "latest",
12160
+ commandName: "actionway",
12161
+ configDirectoryName: ".actionway",
12162
+ skillName: "actionway",
12163
+ gatewayUrl: "https://actionway.ai"
12164
+ };
12165
+ var DEVELOPMENT_DISTRIBUTION = {
12166
+ channel: "development",
12167
+ packageName: "@actionway/cli-dev",
12168
+ distTag: "latest",
12169
+ commandName: "actionway-dev",
12170
+ configDirectoryName: ".actionway-dev",
12171
+ skillName: "actionway-dev",
12172
+ gatewayUrl: "https://dev.actionway.ai"
12173
+ };
12174
+ function cliDistribution(env = process.env) {
12175
+ return env.ACTIONWAY_CLI_CHANNEL === "development" ? DEVELOPMENT_DISTRIBUTION : PRODUCTION_DISTRIBUTION;
12176
+ }
12177
+
12155
12178
  // src/version.ts
12156
12179
  import { readFileSync } from "node:fs";
12157
12180
  import { dirname as dirname2, resolve } from "node:path";
12158
12181
  import { fileURLToPath as fileURLToPath2 } from "node:url";
12159
- var PACKAGE_NAME = "@actionway/cli";
12160
- function readOwnVersion(moduleUrl = import.meta.url) {
12182
+ function readOwnPackage(moduleUrl = import.meta.url, env = process.env) {
12161
12183
  const here = dirname2(fileURLToPath2(moduleUrl));
12184
+ const expectedPackage = cliDistribution(env).packageName;
12162
12185
  for (const path of [
12163
12186
  resolve(here, "package.json"),
12164
12187
  resolve(here, "../package.json"),
@@ -12168,13 +12191,27 @@ function readOwnVersion(moduleUrl = import.meta.url) {
12168
12191
  const parsed = JSON.parse(readFileSync(path, "utf8"));
12169
12192
  if (typeof parsed !== "object" || parsed === null) continue;
12170
12193
  const manifest = parsed;
12171
- if (manifest.name === PACKAGE_NAME && typeof manifest.version === "string" && manifest.version.trim()) {
12172
- return manifest.version.trim();
12194
+ if (manifest.name === expectedPackage && typeof manifest.version === "string" && manifest.version.trim()) {
12195
+ const result = {
12196
+ name: expectedPackage,
12197
+ version: manifest.version.trim(),
12198
+ ...typeof manifest.actionway === "object" && manifest.actionway !== null ? { actionway: manifest.actionway } : {}
12199
+ };
12200
+ if (expectedPackage === "@actionway/cli-dev") {
12201
+ const metadata = result.actionway;
12202
+ if (metadata?.channel !== "development" || !/^[0-9a-f]{40}$/.test(metadata.source_sha ?? "") || !Number.isSafeInteger(metadata.run_id) || (metadata.run_id ?? 0) <= 0 || metadata.run_url !== `https://github.com/PawLogic/actionway/actions/runs/${metadata.run_id}` || metadata.gateway_url !== "https://dev.actionway.ai") {
12203
+ throw new Error("the installed @actionway/cli-dev package metadata is invalid");
12204
+ }
12205
+ }
12206
+ return result;
12173
12207
  }
12174
12208
  } catch {
12175
12209
  }
12176
12210
  }
12177
- throw new Error(`cannot determine the installed ${PACKAGE_NAME} version`);
12211
+ throw new Error(`cannot determine the installed ${expectedPackage} version`);
12212
+ }
12213
+ function readOwnVersion(moduleUrl = import.meta.url, env = process.env) {
12214
+ return readOwnPackage(moduleUrl, env).version;
12178
12215
  }
12179
12216
 
12180
12217
  // src/lib/npm-runner.ts
@@ -12275,14 +12312,14 @@ function runNpmText(args, operation, options = {}) {
12275
12312
  }
12276
12313
 
12277
12314
  // src/lib/update-state.ts
12278
- var PACKAGE_NAME2 = "@actionway/cli";
12279
12315
  var UPDATE_STATE_BASENAME = "update.json";
12280
12316
  var UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
12317
+ var DEVELOPMENT_VERSION_PATTERN = /^0\.0\.0-dev\.[1-9]\d*\.[0-9a-f]{12}$/;
12281
12318
  function actionwayConfigDir(env = process.env) {
12282
12319
  const override = (env.ACTIONWAY_CONFIG_DIR ?? "").trim();
12283
12320
  if (override) return override;
12284
12321
  const home = (env.HOME ?? "").trim() || homedir();
12285
- return join(home, ".actionway");
12322
+ return join(home, cliDistribution(env).configDirectoryName);
12286
12323
  }
12287
12324
  function updateStatePath(env = process.env) {
12288
12325
  return join(actionwayConfigDir(env), UPDATE_STATE_BASENAME);
@@ -12331,7 +12368,15 @@ function loadState(env) {
12331
12368
  if (existsSync(path)) {
12332
12369
  try {
12333
12370
  const parsed = parseState(readFileSync2(path, "utf8"));
12334
- if (parsed) return parsed;
12371
+ if (parsed) {
12372
+ if (cliDistribution(env).channel === "development") {
12373
+ if (parsed.latest_version && !DEVELOPMENT_VERSION_PATTERN.test(parsed.latest_version)) {
12374
+ return emptyState();
12375
+ }
12376
+ return { ...parsed, min_version: null };
12377
+ }
12378
+ return parsed;
12379
+ }
12335
12380
  } catch {
12336
12381
  }
12337
12382
  }
@@ -12353,6 +12398,7 @@ function readVersionState(env = process.env) {
12353
12398
  return { latest_version: state.latest_version, min_version: state.min_version, checked_at: state.checked_at };
12354
12399
  }
12355
12400
  function recordVersionSignal(signal, env = process.env, now = /* @__PURE__ */ new Date()) {
12401
+ if (cliDistribution(env).channel === "development") return;
12356
12402
  const latest = (signal.latest ?? "").trim();
12357
12403
  const min = (signal.min ?? "").trim();
12358
12404
  const validLatest = latest && isValidVersion(latest) ? latest : null;
@@ -12375,19 +12421,28 @@ function recordVersionSignal(signal, env = process.env, now = /* @__PURE__ */ ne
12375
12421
  );
12376
12422
  }
12377
12423
  function fetchLatestVersionFromNpm(env) {
12424
+ const distribution = cliDistribution(env);
12378
12425
  const output = runNpmText(
12379
- ["view", `${PACKAGE_NAME2}@latest`, "version"],
12380
- `npm registry check for ${PACKAGE_NAME2}@latest`,
12426
+ ["view", `${distribution.packageName}@${distribution.distTag}`, "version"],
12427
+ `npm registry check for ${distribution.packageName}@${distribution.distTag}`,
12381
12428
  { env, timeoutMs: 15e3 }
12382
12429
  );
12383
12430
  const version = output.split(/\r?\n/).at(-1)?.trim() ?? "";
12384
- if (!version) throw new Error(`npm returned no version for ${PACKAGE_NAME2}@latest`);
12431
+ if (!version) throw new Error(`npm returned no version for ${distribution.packageName}@${distribution.distTag}`);
12385
12432
  return version;
12386
12433
  }
12387
12434
  function installFromNpm(env) {
12435
+ const distribution = cliDistribution(env);
12388
12436
  runNpmText(
12389
- ["install", "--global", "--no-audit", "--no-fund", "--no-update-notifier", `${PACKAGE_NAME2}@latest`],
12390
- `npm global update for ${PACKAGE_NAME2}`,
12437
+ [
12438
+ "install",
12439
+ "--global",
12440
+ "--no-audit",
12441
+ "--no-fund",
12442
+ "--no-update-notifier",
12443
+ `${distribution.packageName}@${distribution.distTag}`
12444
+ ],
12445
+ `npm global update for ${distribution.packageName}`,
12391
12446
  { env, timeoutMs: 12e4 }
12392
12447
  );
12393
12448
  }
@@ -12396,12 +12451,13 @@ function isFresh(state, now) {
12396
12451
  const checkedAt = Date.parse(state.checked_at);
12397
12452
  return Number.isFinite(checkedAt) && now.getTime() - checkedAt >= 0 && now.getTime() - checkedAt < UPDATE_CACHE_TTL_MS;
12398
12453
  }
12399
- function statusFrom(currentVersion, latestVersion, checkedAt, fromCache) {
12454
+ function statusFrom(currentVersion, latestVersion, checkedAt, fromCache, env) {
12455
+ const distribution = cliDistribution(env);
12400
12456
  return {
12401
- package: PACKAGE_NAME2,
12457
+ package: distribution.packageName,
12402
12458
  current_version: currentVersion,
12403
12459
  latest_version: latestVersion,
12404
- update_available: isValidVersion(latestVersion) && compareVersions(latestVersion, currentVersion) > 0,
12460
+ update_available: isValidVersion(latestVersion) && (distribution.channel === "development" ? latestVersion !== currentVersion : compareVersions(latestVersion, currentVersion) > 0),
12405
12461
  checked_at: checkedAt,
12406
12462
  from_cache: fromCache
12407
12463
  };
@@ -12412,12 +12468,12 @@ function getUpdateStatus(options = {}) {
12412
12468
  const currentVersion = options.currentVersion ?? readOwnVersion();
12413
12469
  const state = loadState(env);
12414
12470
  if (!options.refresh && isFresh(state, now) && state.latest_version) {
12415
- return statusFrom(currentVersion, state.latest_version, state.checked_at ?? now.toISOString(), true);
12471
+ return statusFrom(currentVersion, state.latest_version, state.checked_at ?? now.toISOString(), true, env);
12416
12472
  }
12417
12473
  const latestVersion = (options.fetchLatestVersion ?? fetchLatestVersionFromNpm)(env);
12418
12474
  const checkedAt = now.toISOString();
12419
12475
  saveState({ version: 2, checked_at: checkedAt, latest_version: latestVersion, min_version: state.min_version }, env);
12420
- return statusFrom(currentVersion, latestVersion, checkedAt, false);
12476
+ return statusFrom(currentVersion, latestVersion, checkedAt, false, env);
12421
12477
  }
12422
12478
  function runUpdate(options = {}) {
12423
12479
  const env = options.env ?? process.env;
@@ -13057,7 +13113,6 @@ import { existsSync as existsSync2, rmSync as rmSync2, statSync, writeFileSync a
13057
13113
  import { createServer as createServer2 } from "node:http";
13058
13114
  import { randomBytes as randomBytes2 } from "node:crypto";
13059
13115
  import { dirname as dirname3, join as join3, posix, win32 } from "node:path";
13060
- var PACKAGE_NAME3 = "@actionway/cli";
13061
13116
  function pass(id, message) {
13062
13117
  return { id, status: "pass", message };
13063
13118
  }
@@ -13146,6 +13201,7 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
13146
13201
  const readPolicy = dependencies.readPowerShellPolicy ?? powerShellPolicy;
13147
13202
  const resolveCommand = dependencies.resolveCommand ?? defaultResolveCommand;
13148
13203
  const checks = [];
13204
+ const distribution = cliDistribution(env);
13149
13205
  const configDir = actionwayConfigDir(env);
13150
13206
  const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "", 10);
13151
13207
  checks.push(
@@ -13177,7 +13233,7 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
13177
13233
  safe.startsWith("https://registry.npmjs.org/") ? pass("scope_registry", `@actionway packages use ${safe}.`) : warn(
13178
13234
  "scope_registry",
13179
13235
  `@actionway packages are mapped to ${safe}.`,
13180
- "make sure that registry mirrors public @actionway/cli, or install from the public npm registry when policy permits"
13236
+ `make sure that registry mirrors public ${distribution.packageName}, or install from the public npm registry when policy permits`
13181
13237
  )
13182
13238
  );
13183
13239
  } else if (scopeResult.error || scopeResult.status !== 0) {
@@ -13193,14 +13249,21 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
13193
13249
  } else {
13194
13250
  const api = pathApi(platform);
13195
13251
  const binDir = platform === "win32" ? prefix : api.join(prefix, "bin");
13196
- const binName = platform === "win32" ? "actionway.cmd" : "actionway";
13252
+ const binName = platform === "win32" ? `${distribution.commandName}.cmd` : distribution.commandName;
13197
13253
  let resolvedCache;
13198
- const resolved = () => resolvedCache !== void 0 ? resolvedCache : resolvedCache = resolveCommand("actionway");
13254
+ const resolved = () => resolvedCache !== void 0 ? resolvedCache : resolvedCache = resolveCommand(distribution.commandName);
13199
13255
  checks.push(
13200
- pathContains(binDir, env, platform) ? pass("global_path", `npm global executable directory is in PATH: ${binDir}`) : resolved() ? pass("global_path", `actionway resolves from PATH at ${resolved()} (outside the npm global prefix ${binDir}).`) : fail2("global_path", `npm global executable directory is not in PATH: ${binDir}`, "add it to the user PATH and open a new terminal")
13256
+ pathContains(binDir, env, platform) ? pass("global_path", `npm global executable directory is in PATH: ${binDir}`) : resolved() ? pass(
13257
+ "global_path",
13258
+ `${distribution.commandName} resolves from PATH at ${resolved()} (outside the npm global prefix ${binDir}).`
13259
+ ) : fail2("global_path", `npm global executable directory is not in PATH: ${binDir}`, "add it to the user PATH and open a new terminal")
13201
13260
  );
13202
13261
  checks.push(
13203
- pathExists(api.join(binDir, binName)) ? pass("command", `${binName} is installed in the npm global prefix.`) : resolved() ? pass("command", `actionway command resolves at ${resolved()}.`) : fail2("command", `${binName} was not found in the npm global prefix.`, "reinstall @actionway/cli@latest in the same Node/npm environment")
13262
+ pathExists(api.join(binDir, binName)) ? pass("command", `${binName} is installed in the npm global prefix.`) : resolved() ? pass("command", `${distribution.commandName} command resolves at ${resolved()}.`) : fail2(
13263
+ "command",
13264
+ `${binName} was not found in the npm global prefix.`,
13265
+ `reinstall ${distribution.packageName}@${distribution.distTag} in the same Node/npm environment`
13266
+ )
13204
13267
  );
13205
13268
  }
13206
13269
  if (platform === "win32") {
@@ -13208,9 +13271,12 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
13208
13271
  checks.push(
13209
13272
  policy && ["Restricted", "AllSigned"].includes(policy) ? warn(
13210
13273
  "powershell_policy",
13211
- `PowerShell execution policy is ${policy}; npm's actionway.ps1 shim may be blocked.`,
13212
- "use actionway.cmd; do not weaken organization policy without approval"
13213
- ) : pass("powershell_policy", `PowerShell execution policy is ${policy || "not available"}; actionway.cmd remains the stable entrypoint.`)
13274
+ `PowerShell execution policy is ${policy}; npm's ${distribution.commandName}.ps1 shim may be blocked.`,
13275
+ `use ${distribution.commandName}.cmd; do not weaken organization policy without approval`
13276
+ ) : pass(
13277
+ "powershell_policy",
13278
+ `PowerShell execution policy is ${policy || "not available"}; ${distribution.commandName}.cmd remains the entrypoint.`
13279
+ )
13214
13280
  );
13215
13281
  }
13216
13282
  try {
@@ -13229,13 +13295,19 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
13229
13295
  checks.push({ id: "npm_registry", status: "skip", message: "npm registry check was skipped." });
13230
13296
  checks.push({ id: "actionway_oauth", status: "skip", message: "Actionway OAuth discovery check was skipped." });
13231
13297
  } else {
13232
- const packageResult = npm(["view", `${PACKAGE_NAME3}@latest`, "version"], { env, platform, timeoutMs: 15e3 });
13298
+ const packageResult = npm(
13299
+ ["view", `${distribution.packageName}@${distribution.distTag}`, "version"],
13300
+ { env, platform, timeoutMs: 15e3 }
13301
+ );
13233
13302
  checks.push(
13234
13303
  npmCheck(
13235
13304
  "npm_registry",
13236
13305
  packageResult,
13237
13306
  "Actionway package lookup",
13238
- (version) => pass("npm_registry", `${PACKAGE_NAME3}@latest is reachable (${version.split(/\r?\n/).at(-1)}).`)
13307
+ (version) => pass(
13308
+ "npm_registry",
13309
+ `${distribution.packageName}@${distribution.distTag} is reachable (${version.split(/\r?\n/).at(-1)}).`
13310
+ )
13239
13311
  )
13240
13312
  );
13241
13313
  try {
@@ -13261,9 +13333,10 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
13261
13333
  };
13262
13334
  }
13263
13335
  function registerDoctorCommand(program2) {
13336
+ const distribution = cliDistribution();
13264
13337
  program2.command("doctor").description("Diagnose Node, npm, PATH, registry, configuration, and local OAuth callback readiness.").option("--no-network", "skip npm registry and Actionway OAuth discovery checks").action(async (options) => {
13265
13338
  const report = await diagnoseActionway({ network: options.network !== false });
13266
- const command = process.platform === "win32" ? "actionway.cmd" : "actionway";
13339
+ const command = process.platform === "win32" ? `${distribution.commandName}.cmd` : distribution.commandName;
13267
13340
  ok({
13268
13341
  ...report,
13269
13342
  hint: report.ready ? `Actionway CLI prerequisites are ready. Run \`${command} init\`.` : `Resolve the failed checks, then run \`${command} doctor\` again.`
@@ -13287,15 +13360,21 @@ var InitError = class extends Error {
13287
13360
  }
13288
13361
  };
13289
13362
  var ACTIONWAY_SKILL_INSTRUCTION = "Install this skill from `source` into your agent's skill directory and make sure it loads in future sessions. If a skill named `actionway` already exists there, replace it only when it is an earlier Actionway skill (check its frontmatter metadata.version); otherwise ask the user first.";
13290
- function resolveBundledSkill(moduleUrl = import.meta.url) {
13363
+ var ACTIONWAY_DEV_SKILL_INSTRUCTION = "Install this skill from `source` into your agent's skill directory and make sure it loads in future sessions. If a skill named `actionway-dev` already exists there, replace it only when it is an earlier Actionway development skill (check its frontmatter metadata.version); otherwise ask the user first. Never replace the stable `actionway` Skill.";
13364
+ function resolveBundledSkill(moduleUrl = import.meta.url, env = process.env) {
13291
13365
  const here = dirname4(fileURLToPath3(moduleUrl));
13292
- for (const candidate of [resolve2(here, "../assets/skill/actionway"), resolve2(here, "../../assets/skill/actionway")]) {
13366
+ const skillName = cliDistribution(env).skillName;
13367
+ for (const candidate of [resolve2(here, `../assets/skill/${skillName}`), resolve2(here, `../../assets/skill/${skillName}`)]) {
13293
13368
  if (existsSync3(join4(candidate, "SKILL.md"))) return realpathSync2(candidate);
13294
13369
  }
13295
- throw new InitError("the bundled Actionway Skill is missing", "reinstall @actionway/cli, then run `actionway init` again");
13370
+ const distribution = cliDistribution(env);
13371
+ throw new InitError(
13372
+ "the bundled Actionway Skill is missing",
13373
+ `reinstall ${distribution.packageName}@${distribution.distTag}, then run \`${distribution.commandName} init\` again`
13374
+ );
13296
13375
  }
13297
13376
  function skillSourcePath(env = process.env) {
13298
- return join4(actionwayConfigDir(env), "skill", "actionway");
13377
+ return join4(actionwayConfigDir(env), "skill", cliDistribution(env).skillName);
13299
13378
  }
13300
13379
  function stampSkillVersion(skillMarkdown, version) {
13301
13380
  const newline = skillMarkdown.includes("\r\n") ? "\r\n" : "\n";
@@ -13334,18 +13413,26 @@ function stampSkillVersion(skillMarkdown, version) {
13334
13413
  }
13335
13414
  function materializeSkill(options = {}) {
13336
13415
  const env = options.env ?? process.env;
13337
- const source = options.source ? realpathSync2(options.source) : resolveBundledSkill();
13416
+ const distribution = cliDistribution(env);
13417
+ const source = options.source ? realpathSync2(options.source) : resolveBundledSkill(import.meta.url, env);
13338
13418
  if (!existsSync3(join4(source, "SKILL.md"))) {
13339
- throw new InitError("the Actionway Skill source is invalid", "reinstall @actionway/cli and retry init");
13419
+ throw new InitError(
13420
+ "the Actionway Skill source is invalid",
13421
+ `reinstall ${distribution.packageName}@${distribution.distTag} and retry init`
13422
+ );
13340
13423
  }
13341
- const version = options.version ?? readOwnVersion();
13424
+ const version = options.version ?? readOwnVersion(import.meta.url, env);
13342
13425
  const target = skillSourcePath(env);
13343
13426
  mkdirSync3(dirname4(target), { recursive: true, mode: 448 });
13344
13427
  rmSync3(target, { recursive: true, force: true });
13345
13428
  cpSync(source, target, { recursive: true });
13346
13429
  const skillPath = join4(target, "SKILL.md");
13347
13430
  writeFileSync4(skillPath, stampSkillVersion(readFileSync4(skillPath, "utf8"), version));
13348
- return { source: target, version, instruction: ACTIONWAY_SKILL_INSTRUCTION };
13431
+ return {
13432
+ source: target,
13433
+ version,
13434
+ instruction: distribution.channel === "development" ? ACTIONWAY_DEV_SKILL_INSTRUCTION : ACTIONWAY_SKILL_INSTRUCTION
13435
+ };
13349
13436
  }
13350
13437
 
13351
13438
  // src/lib/install-session.ts
@@ -13466,6 +13553,7 @@ function failFromError3(err) {
13466
13553
  fail({ code: "E_BACKEND", message: err instanceof Error ? err.message : String(err) });
13467
13554
  }
13468
13555
  function registerInitCommand(program2) {
13556
+ const distribution = cliDistribution();
13469
13557
  program2.command("init").description(
13470
13558
  "Complete browser authentication and stage the Actionway Skill for your agent to install. The CLI never writes into an agent's own configuration directory."
13471
13559
  ).option("--gateway <url>", "Actionway public origin").option("--client-id <id>", "Actionway OAuth client id (must be paired with --issuer)").option("--issuer <url>", "Actionway OAuth issuer (must be paired with --client-id)").option("--timeout-seconds <n>", "how long to wait for browser authentication").option("--install-session-id <uuid>", "onboarding install session id").option("--no-browser", "print the authorize URL instead of opening a browser").addOption(new Option("--skill-only").hideHelp()).action(async (opts) => {
@@ -13507,7 +13595,7 @@ function registerInitCommand(program2) {
13507
13595
  skill,
13508
13596
  workspace_id: account.workspaceId,
13509
13597
  ...snapshot?.user ? { user: snapshot.user } : {},
13510
- hint: "Actionway is ready. Install the skill from `skill.source` into your agent's skill directory, then run `actionway --help` to discover supported capabilities."
13598
+ hint: `Actionway is ready. Install the skill from \`skill.source\` into your agent's skill directory, then run \`${distribution.commandName} --help\` to discover supported capabilities.`
13511
13599
  });
13512
13600
  } catch (error) {
13513
13601
  failFromError3(error);
@@ -13515,6 +13603,26 @@ function registerInitCommand(program2) {
13515
13603
  });
13516
13604
  }
13517
13605
 
13606
+ // src/commands/status.ts
13607
+ function registerDevelopmentStatusCommand(program2) {
13608
+ const distribution = cliDistribution();
13609
+ if (distribution.channel !== "development") return;
13610
+ program2.command("status").description("Show the installed Actionway development channel and immutable source revision.").action(() => {
13611
+ const manifest = readOwnPackage();
13612
+ const metadata = manifest.actionway;
13613
+ ok({
13614
+ channel: "development",
13615
+ package: manifest.name,
13616
+ version: manifest.version,
13617
+ source_sha: metadata?.source_sha ?? null,
13618
+ run_id: metadata?.run_id ?? null,
13619
+ run_url: metadata?.run_url ?? null,
13620
+ gateway_url: metadata?.gateway_url ?? distribution.gatewayUrl,
13621
+ config_dir: process.env.ACTIONWAY_CONFIG_DIR ?? null
13622
+ });
13623
+ });
13624
+ }
13625
+
13518
13626
  // src/commands/update.ts
13519
13627
  import { spawnSync as spawnSync3 } from "node:child_process";
13520
13628
  function refreshSkillWithNewBinary() {
@@ -13535,7 +13643,8 @@ function refreshSkillWithNewBinary() {
13535
13643
  }
13536
13644
  }
13537
13645
  function registerUpdateCommand(program2) {
13538
- program2.command("update").description("Check for or install the latest @actionway/cli version from npm.").option("--check", "check for a newer version without installing").option("--refresh", "ignore the 24-hour update-check cache").action((opts) => {
13646
+ const distribution = cliDistribution();
13647
+ program2.command("update").description(`Check for or install the latest ${distribution.packageName} version from npm.`).option("--check", "check for a newer version without installing").option("--refresh", "ignore the 24-hour update-check cache").action((opts) => {
13539
13648
  try {
13540
13649
  if (opts.check) {
13541
13650
  ok({ ...getUpdateStatus({ ...opts.refresh ? { refresh: true } : {} }) });
@@ -13558,7 +13667,7 @@ function registerUpdateCommand(program2) {
13558
13667
  fail({
13559
13668
  code: "E_UPDATE_FAILED",
13560
13669
  message: error instanceof Error ? error.message : String(error),
13561
- hint: "retry later or reinstall @actionway/cli@latest"
13670
+ hint: `retry later or reinstall ${distribution.packageName}@${distribution.distTag}`
13562
13671
  });
13563
13672
  }
13564
13673
  });
@@ -13568,6 +13677,7 @@ function registerUpdateCommand(program2) {
13568
13677
  function withVersionMetadata(payload, deps = {}) {
13569
13678
  const env = deps.env ?? process.env;
13570
13679
  const cliVersion2 = deps.currentVersion ?? getCliVersion();
13680
+ const distribution = cliDistribution(env);
13571
13681
  const skillSource = skillSourcePath(env);
13572
13682
  const isUpdateCommandOutput = "update_available" in payload;
13573
13683
  const notes = [];
@@ -13575,12 +13685,12 @@ function withVersionMetadata(payload, deps = {}) {
13575
13685
  const state = readVersionState(env);
13576
13686
  if (!isUpdateCommandOutput && state.latest_version && isValidVersion(state.latest_version) && compareVersions(state.latest_version, cliVersion2) > 0) {
13577
13687
  notes.push(
13578
- `Update available: ${cliVersion2} -> ${state.latest_version}. Run \`actionway update\` at the next session boundary, then refresh the installed skill from ${skillSource}.`
13688
+ `Update available: ${cliVersion2} -> ${state.latest_version}. Run \`${distribution.commandName} update\` at the next session boundary, then refresh the installed skill from ${skillSource}.`
13579
13689
  );
13580
13690
  }
13581
13691
  if (state.min_version && isValidVersion(state.min_version) && compareVersions(cliVersion2, state.min_version) < 0) {
13582
13692
  notes.push(
13583
- `This CLI version (${cliVersion2}) is below the minimum supported version (${state.min_version}). Run \`actionway update\` now, then refresh the installed skill from ${skillSource}.`
13693
+ `This CLI version (${cliVersion2}) is below the minimum supported version (${state.min_version}). Run \`${distribution.commandName} update\` now, then refresh the installed skill from ${skillSource}.`
13584
13694
  );
13585
13695
  }
13586
13696
  } catch {
@@ -13799,14 +13909,16 @@ function getTransport2() {
13799
13909
  }
13800
13910
  var actionDeps = { getTransport: getTransport2, projectToolCall: projectTypedToolCall };
13801
13911
  function buildProgram() {
13912
+ const distribution = cliDistribution();
13802
13913
  const version = readOwnVersion();
13803
13914
  setCliVersion(version);
13804
- const program2 = new Command("actionway");
13915
+ const program2 = new Command(distribution.commandName);
13805
13916
  program2.version(version);
13806
13917
  program2.description("Actionway CLI: run Actionway media and research capabilities from your local agent.");
13807
13918
  registerInitCommand(program2);
13808
13919
  registerDoctorCommand(program2);
13809
13920
  registerUpdateCommand(program2);
13921
+ registerDevelopmentStatusCommand(program2);
13810
13922
  registerAuthCommands(program2);
13811
13923
  registerAccountCommands(program2, getTransport2);
13812
13924
  registerToolsCommands(program2, getTransport2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actionway/cli",
3
- "version": "0.18.5",
3
+ "version": "0.18.6",
4
4
  "description": "actionway CLI 的本地端壳:Clerk OAuth 鉴权 + cli-core 共享命令面 + init / update / skill 物化 / 版本三链路(终端用户本地 Codex / Claude Code 使用)",
5
5
  "type": "module",
6
6
  "bin": {