@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 +14 -1
- package/assets/dev/actionway-dev-entry.mjs +19 -0
- package/assets/dev/actionway-dev-launcher.mjs +370 -0
- package/assets/dev/install-actionway-dev.mjs +185 -0
- package/assets/skill/actionway-dev/SKILL.md +29 -0
- package/assets/skill/actionway-dev/agents/openai.yaml +4 -0
- package/dist/index.js +156 -44
- package/package.json +1 -1
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.
|
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
|
-
|
|
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 ===
|
|
12172
|
-
|
|
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 ${
|
|
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,
|
|
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)
|
|
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", `${
|
|
12380
|
-
`npm registry check for ${
|
|
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 ${
|
|
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
|
-
[
|
|
12390
|
-
|
|
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:
|
|
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
|
-
|
|
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" ?
|
|
13252
|
+
const binName = platform === "win32" ? `${distribution.commandName}.cmd` : distribution.commandName;
|
|
13197
13253
|
let resolvedCache;
|
|
13198
|
-
const resolved = () => resolvedCache !== void 0 ? resolvedCache : resolvedCache = resolveCommand(
|
|
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(
|
|
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",
|
|
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
|
|
13212
|
-
|
|
13213
|
-
) : pass(
|
|
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(
|
|
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(
|
|
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" ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
|
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(
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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(
|
|
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