@aiwg/cli 2026.7.25 → 2026.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +33 -0
  2. package/agentic/code/providers/capability-matrix.yaml +511 -0
  3. package/agentic/code/providers/model-capabilities.v1.json +120 -0
  4. package/agentic/code/providers/model-catalog.v1.json +96 -0
  5. package/agentic/code/providers/model-policy-evaluations.v1.json +50 -0
  6. package/agentic/code/providers/premium-model-allowlist.v1.json +36 -0
  7. package/bin/aiwg.mjs +14 -10
  8. package/dist/src/api/index.d.ts +1 -0
  9. package/dist/src/api/index.js +1 -0
  10. package/dist/src/artifacts/cli.js +55 -10
  11. package/dist/src/artifacts/fortemi-shard-export.js +107 -18
  12. package/dist/src/artifacts/types.js +4 -0
  13. package/dist/src/auth/client.js +209 -0
  14. package/dist/src/auth/config.js +38 -0
  15. package/dist/src/auth/credential-store.js +141 -0
  16. package/dist/src/auth/resource-credentials.js +25 -0
  17. package/dist/src/auth/types.js +2 -0
  18. package/dist/src/channel/manager.mjs +5 -5
  19. package/dist/src/cli/handlers/auth.js +125 -0
  20. package/dist/src/cli/handlers/help.js +1 -0
  21. package/dist/src/cli/handlers/index.js +6 -2
  22. package/dist/src/cli/handlers/job.js +97 -0
  23. package/dist/src/cli/handlers/resource-versions.js +2 -0
  24. package/dist/src/cli/handlers/runtime-info.js +2 -2
  25. package/dist/src/cli/handlers/serve.js +2 -2
  26. package/dist/src/cli/handlers/sessions.js +211 -5
  27. package/dist/src/cli/handlers/steward.js +16 -3
  28. package/dist/src/cli/handlers/subcommands.js +10 -1
  29. package/dist/src/cli/handlers/use.js +342 -43
  30. package/dist/src/config/gitignore.js +1 -0
  31. package/dist/src/extensions/commands/definitions.js +49 -5
  32. package/dist/src/extensions/manifest.js +1 -0
  33. package/dist/src/features/catalog.js +3 -3
  34. package/dist/src/jobs/executor.js +83 -0
  35. package/dist/src/jobs/flow.js +106 -0
  36. package/dist/src/jobs/gitea.js +91 -0
  37. package/dist/src/jobs/render.js +53 -0
  38. package/dist/src/jobs/runner.js +315 -0
  39. package/dist/src/jobs/types.js +3 -0
  40. package/dist/src/memory/canonical-context.js +342 -0
  41. package/dist/src/memory/context-pack.js +282 -0
  42. package/dist/src/memory/index.js +4 -0
  43. package/dist/src/memory/intake.js +118 -0
  44. package/dist/src/providers/capability-matrix.js +11 -4
  45. package/dist/src/providers/capability-matrix.yaml +39 -42
  46. package/dist/src/resources/resolver.js +1 -0
  47. package/dist/src/resources/web-release.d.ts +3 -1
  48. package/dist/src/resources/web-release.js +14 -6
  49. package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
  50. package/dist/src/serve/fleet-mission-conductor.js +293 -0
  51. package/dist/src/sessions/analytics.js +303 -0
  52. package/dist/src/sessions/importer.js +7 -1
  53. package/dist/src/sessions/index.js +2 -0
  54. package/dist/src/sessions/output-registration.js +338 -0
  55. package/dist/src/sessions/policy.js +1 -1
  56. package/dist/src/sessions/promotion.js +73 -2
  57. package/dist/src/sessions/repository.js +215 -1
  58. package/dist/src/update/notifier.mjs +13 -2
  59. package/package.json +17 -10
  60. package/tools/_resolve-impl.mjs +74 -0
  61. package/tools/agents/deploy-agents.mjs +962 -0
  62. package/tools/agents/providers/base.mjs +2954 -0
  63. package/tools/agents/providers/claude.mjs +711 -0
  64. package/tools/agents/providers/codex.mjs +699 -0
  65. package/tools/agents/providers/copilot.mjs +659 -0
  66. package/tools/agents/providers/cursor.mjs +714 -0
  67. package/tools/agents/providers/factory.mjs +1130 -0
  68. package/tools/agents/providers/hermes.mjs +663 -0
  69. package/tools/agents/providers/hook-capabilities.mjs +85 -0
  70. package/tools/agents/providers/model-role.mjs +56 -0
  71. package/tools/agents/providers/openclaw-translator.mjs +348 -0
  72. package/tools/agents/providers/openclaw.mjs +680 -0
  73. package/tools/agents/providers/opencode.mjs +675 -0
  74. package/tools/agents/providers/openhuman.mjs +292 -0
  75. package/tools/agents/providers/warp.mjs +413 -0
  76. package/tools/agents/providers/windsurf.mjs +748 -0
  77. package/tools/commands/deploy-prompts-codex.mjs +336 -0
  78. package/tools/plugin/package-plugins.mjs +1013 -0
  79. package/tools/skills/deploy-skills-codex.mjs +571 -0
@@ -0,0 +1,209 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ import { spawn } from "node:child_process";
4
+ const random = (bytes = 32) => randomBytes(bytes).toString("base64url");
5
+ export const createPkceChallenge = (verifier) => createHash("sha256").update(verifier).digest("base64url");
6
+ async function responseJson(response) {
7
+ const value = await response.json().catch(() => ({}));
8
+ return value && typeof value === "object" ? value : {};
9
+ }
10
+ function oauthError(value, fallback) {
11
+ return new Error(typeof value.error === "string" ? value.error : fallback);
12
+ }
13
+ function toCredentials(value, now = new Date()) {
14
+ if (typeof value.access_token !== "string" || typeof value.refresh_token !== "string"
15
+ || value.token_type !== "Bearer" || !Number.isSafeInteger(value.expires_in))
16
+ throw new Error("authorization server returned an invalid token response");
17
+ return {
18
+ accessToken: value.access_token,
19
+ refreshToken: value.refresh_token,
20
+ tokenType: "Bearer",
21
+ scope: String(value.scope || "").split(/\s+/).filter(Boolean),
22
+ expiresAt: new Date(now.getTime() + Number(value.expires_in) * 1000).toISOString(),
23
+ };
24
+ }
25
+ export const defaultBrowserOpener = async (url) => {
26
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd.exe" : "xdg-open";
27
+ const args = process.platform === "win32" ? ["/d", "/s", "/c", "start", "", url] : [url];
28
+ await new Promise((resolve, reject) => {
29
+ const child = spawn(command, args, { detached: true, stdio: "ignore", windowsHide: true });
30
+ child.once("error", reject);
31
+ child.once("spawn", () => { child.unref(); resolve(); });
32
+ });
33
+ };
34
+ function wait(ms, signal) {
35
+ return new Promise((resolve, reject) => {
36
+ if (signal?.aborted)
37
+ return reject(new Error("authentication canceled"));
38
+ const timer = setTimeout(resolve, ms);
39
+ const abort = () => { clearTimeout(timer); reject(new Error("authentication canceled")); };
40
+ signal?.addEventListener("abort", abort, { once: true });
41
+ });
42
+ }
43
+ export class AuthClient {
44
+ config;
45
+ store;
46
+ fetcher;
47
+ openBrowser;
48
+ now;
49
+ sleep;
50
+ constructor(config, store, fetcher = globalThis.fetch, openBrowser = defaultBrowserOpener, now = () => new Date(), sleep = wait) {
51
+ this.config = config;
52
+ this.store = store;
53
+ this.fetcher = fetcher;
54
+ this.openBrowser = openBrowser;
55
+ this.now = now;
56
+ this.sleep = sleep;
57
+ }
58
+ async request(pathname, init) {
59
+ const signal = init.signal || AbortSignal.timeout(this.config.requestTimeoutMs);
60
+ return this.fetcher(`${this.config.baseUrl}${pathname}`, { ...init, signal, redirect: "error" });
61
+ }
62
+ async loginBrowser(options = {}) {
63
+ const verifier = random(48);
64
+ const state = random();
65
+ let server;
66
+ const callback = new Promise((resolve, reject) => {
67
+ server = createServer((request, response) => {
68
+ try {
69
+ const url = new URL(request.url || "/", "http://127.0.0.1");
70
+ if (url.pathname !== "/callback" || !url.searchParams.get("code") || !url.searchParams.get("state")) {
71
+ response.writeHead(400, { "content-type": "text/plain", "cache-control": "no-store" });
72
+ response.end("Invalid authorization callback");
73
+ return;
74
+ }
75
+ response.writeHead(200, { "content-type": "text/plain", "cache-control": "no-store" });
76
+ response.end("AIWG authorization complete. You may close this window.");
77
+ resolve({ code: url.searchParams.get("code"), state: url.searchParams.get("state") });
78
+ }
79
+ catch (error) {
80
+ reject(error);
81
+ }
82
+ });
83
+ server.once("error", reject);
84
+ server.listen(0, "127.0.0.1");
85
+ });
86
+ try {
87
+ const callbackServer = server;
88
+ if (!callbackServer)
89
+ throw new Error("loopback callback server was not created");
90
+ await new Promise((resolve, reject) => { callbackServer.once("listening", resolve); callbackServer.once("error", reject); });
91
+ const address = callbackServer.address();
92
+ if (!address || typeof address === "string")
93
+ throw new Error("loopback callback did not bind a random port");
94
+ const redirectUri = `http://127.0.0.1:${address.port}/callback`;
95
+ const authorize = new URL(`${this.config.baseUrl}/oauth/authorize`);
96
+ authorize.search = new URLSearchParams({
97
+ client_id: this.config.clientId,
98
+ redirect_uri: redirectUri,
99
+ response_type: "code",
100
+ code_challenge: createPkceChallenge(verifier),
101
+ code_challenge_method: "S256",
102
+ state,
103
+ scope: this.config.scopes.join(" "),
104
+ ...(options.deviceLabel ? { device_label: options.deviceLabel } : {}),
105
+ }).toString();
106
+ await this.openBrowser(authorize.toString());
107
+ const result = await Promise.race([
108
+ callback,
109
+ new Promise((_, reject) => options.signal?.addEventListener("abort", () => reject(new Error("authentication canceled")), { once: true })),
110
+ ]);
111
+ if (result.state !== state)
112
+ throw new Error("OAuth state mismatch");
113
+ const response = await this.request("/oauth/token", {
114
+ method: "POST",
115
+ headers: { "content-type": "application/x-www-form-urlencoded" },
116
+ body: new URLSearchParams({ grant_type: "authorization_code", code: result.code, code_verifier: verifier, client_id: this.config.clientId, redirect_uri: redirectUri }),
117
+ signal: options.signal,
118
+ });
119
+ const value = await responseJson(response);
120
+ if (!response.ok)
121
+ throw oauthError(value, "authorization code exchange failed");
122
+ const credentials = toCredentials(value, this.now());
123
+ await this.store.save(credentials);
124
+ return credentials;
125
+ }
126
+ finally {
127
+ server?.closeAllConnections();
128
+ server?.close();
129
+ }
130
+ }
131
+ async loginDevice(options = {}) {
132
+ const start = await this.request("/v1/auth/device/authorization", {
133
+ method: "POST", headers: { "content-type": "application/json" }, signal: options.signal,
134
+ body: JSON.stringify({ client_id: this.config.clientId, scope: this.config.scopes.join(" "), device_label: options.deviceLabel }),
135
+ });
136
+ const value = await responseJson(start);
137
+ if (!start.ok || typeof value.device_code !== "string" || typeof value.expires_in !== "number" || typeof value.interval !== "number") {
138
+ throw oauthError(value, "device authorization failed");
139
+ }
140
+ options.onCode?.(value);
141
+ const deadline = this.now().getTime() + value.expires_in * 1000;
142
+ let interval = value.interval;
143
+ while (this.now().getTime() < deadline) {
144
+ await this.sleep(interval * 1000, options.signal);
145
+ if (this.now().getTime() >= deadline)
146
+ break;
147
+ const response = await this.request("/oauth/token", {
148
+ method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, signal: options.signal,
149
+ body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: value.device_code, client_id: this.config.clientId }),
150
+ });
151
+ const token = await responseJson(response);
152
+ if (response.ok) {
153
+ const credentials = toCredentials(token, this.now());
154
+ await this.store.save(credentials);
155
+ return credentials;
156
+ }
157
+ if (token.error === "authorization_pending")
158
+ continue;
159
+ if (token.error === "slow_down") {
160
+ interval += 5;
161
+ continue;
162
+ }
163
+ throw oauthError(token, "device authorization failed");
164
+ }
165
+ throw new Error("expired_token");
166
+ }
167
+ async refresh(credentials, signal) {
168
+ const response = await this.request("/oauth/token", {
169
+ method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, signal,
170
+ body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: credentials.refreshToken, client_id: this.config.clientId }),
171
+ });
172
+ const value = await responseJson(response);
173
+ if (!response.ok)
174
+ throw oauthError(value, "refresh failed");
175
+ const updated = toCredentials(value, this.now());
176
+ await this.store.save(updated);
177
+ return updated;
178
+ }
179
+ async status(signal) {
180
+ let credentials = await this.store.load();
181
+ if (!credentials)
182
+ throw new Error("not_authenticated");
183
+ if (Date.parse(credentials.expiresAt) <= this.now().getTime() + 30_000)
184
+ credentials = await this.refresh(credentials, signal);
185
+ let response = await this.request("/v1/me", { headers: { authorization: `Bearer ${credentials.accessToken}` }, signal });
186
+ if (response.status === 401) {
187
+ credentials = await this.refresh(credentials, signal);
188
+ response = await this.request("/v1/me", { headers: { authorization: `Bearer ${credentials.accessToken}` }, signal });
189
+ }
190
+ const value = await responseJson(response);
191
+ if (!response.ok || typeof value.sub !== "string")
192
+ throw oauthError(value, "status request failed");
193
+ return { profile: value, credentials };
194
+ }
195
+ async logout(signal) {
196
+ const credentials = await this.store.load();
197
+ try {
198
+ if (credentials)
199
+ await this.request("/oauth/revoke", {
200
+ method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, signal,
201
+ body: new URLSearchParams({ token: credentials.refreshToken, token_type_hint: "refresh_token" }),
202
+ });
203
+ }
204
+ finally {
205
+ await this.store.delete();
206
+ }
207
+ }
208
+ }
209
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,38 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export const DEFAULT_AUTH_BASE_URL = "https://releases.aiwg.io";
4
+ export const DEFAULT_AUTH_CLIENT_ID = "aiwg-cli";
5
+ export const DEFAULT_AUTH_SCOPES = ["releases:read", "profile:read"];
6
+ function cleanOrigin(value, allowLoopbackHttp = false) {
7
+ const url = new URL(value);
8
+ const loopback = ["127.0.0.1", "[::1]", "::1", "localhost"].includes(url.hostname);
9
+ if (url.protocol !== "https:" && !(allowLoopbackHttp && loopback && url.protocol === "http:")) {
10
+ throw new Error("AIWG authentication requires HTTPS; HTTP is allowed only for explicitly enabled loopback tests");
11
+ }
12
+ if (url.username || url.password || url.search || url.hash)
13
+ throw new Error("AIWG authentication URL must be a clean origin");
14
+ url.pathname = url.pathname.replace(/\/+$/, "");
15
+ return url.toString().replace(/\/$/, "");
16
+ }
17
+ export function authConfigFromEnvironment(env = process.env) {
18
+ const baseUrl = cleanOrigin(env.AIWG_AUTH_BASE_URL || DEFAULT_AUTH_BASE_URL, env.AIWG_AUTH_ALLOW_INSECURE_LOOPBACK_HTTP === "1");
19
+ const clientId = env.AIWG_AUTH_CLIENT_ID || DEFAULT_AUTH_CLIENT_ID;
20
+ if (!/^[a-z0-9][a-z0-9._-]{1,63}$/.test(clientId))
21
+ throw new Error("AIWG auth client ID is invalid");
22
+ const scopes = (env.AIWG_AUTH_SCOPES || DEFAULT_AUTH_SCOPES.join(" ")).split(/\s+/).filter(Boolean);
23
+ if (!scopes.length || scopes.some((scope) => !/^[a-z][a-z0-9._:-]{1,63}$/.test(scope)))
24
+ throw new Error("AIWG auth scopes are invalid");
25
+ return { baseUrl, clientId, scopes, requestTimeoutMs: 30_000 };
26
+ }
27
+ export function explicitResourceToken(env = process.env) {
28
+ if (env.AIWG_RESOURCE_TOKEN_FILE) {
29
+ const pathname = path.resolve(env.AIWG_RESOURCE_TOKEN_FILE);
30
+ const stat = fs.lstatSync(pathname);
31
+ if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o077) !== 0) {
32
+ throw new Error("AIWG_RESOURCE_TOKEN_FILE must be a non-symlink regular file with mode 0600");
33
+ }
34
+ return fs.readFileSync(pathname, "utf8").trim() || null;
35
+ }
36
+ return env.AIWG_RESOURCE_TOKEN?.trim() || null;
37
+ }
38
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,141 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ export const defaultCommandRunner = (command, args, stdin = "") => new Promise((resolve, reject) => {
6
+ const child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
7
+ const stdout = [];
8
+ const stderr = [];
9
+ let outputBytes = 0;
10
+ const collect = (target, chunk) => {
11
+ outputBytes += chunk.length;
12
+ if (outputBytes > 1024 * 1024)
13
+ child.kill();
14
+ else
15
+ target.push(chunk);
16
+ };
17
+ child.stdout.on("data", (chunk) => collect(stdout, chunk));
18
+ child.stderr.on("data", (chunk) => collect(stderr, chunk));
19
+ child.once("error", reject);
20
+ child.once("close", (code) => resolve({
21
+ stdout: Buffer.concat(stdout).toString("utf8"),
22
+ stderr: Buffer.concat(stderr).toString("utf8"),
23
+ exitCode: code ?? 1,
24
+ }));
25
+ child.stdin.end(stdin);
26
+ });
27
+ function parseCredentials(raw) {
28
+ const value = JSON.parse(raw);
29
+ if (!value.accessToken?.startsWith("aiwg_at_") || !value.refreshToken?.startsWith("aiwg_rt_")
30
+ || value.tokenType !== "Bearer" || !Array.isArray(value.scope) || !value.expiresAt) {
31
+ throw new Error("stored AIWG credentials are invalid");
32
+ }
33
+ return value;
34
+ }
35
+ const SERVICE = "releases.aiwg.io";
36
+ const ACCOUNT = "aiwg-cli";
37
+ class NativeCredentialStore {
38
+ run;
39
+ constructor(run = defaultCommandRunner) {
40
+ this.run = run;
41
+ }
42
+ parse(raw) { return parseCredentials(raw.trim()); }
43
+ }
44
+ export class MacOsKeychainStore extends NativeCredentialStore {
45
+ metadata = { provider: "macos-keychain", location: `Keychain:${SERVICE}/${ACCOUNT}` };
46
+ async load() {
47
+ const result = await this.run("security", ["find-generic-password", "-a", ACCOUNT, "-s", SERVICE, "-w"]);
48
+ return result.exitCode === 44 ? null : result.exitCode === 0 ? this.parse(result.stdout) : Promise.reject(new Error("macOS Keychain read failed"));
49
+ }
50
+ async save(credentials) {
51
+ const result = await this.run("security", ["add-generic-password", "-U", "-a", ACCOUNT, "-s", SERVICE, "-w"], JSON.stringify(credentials));
52
+ if (result.exitCode !== 0)
53
+ throw new Error("macOS Keychain write failed");
54
+ }
55
+ async delete() { await this.run("security", ["delete-generic-password", "-a", ACCOUNT, "-s", SERVICE]); }
56
+ }
57
+ export class LinuxSecretServiceStore extends NativeCredentialStore {
58
+ metadata = { provider: "linux-secret-service", location: `SecretService:${SERVICE}/${ACCOUNT}` };
59
+ async load() {
60
+ const result = await this.run("secret-tool", ["lookup", "service", SERVICE, "account", ACCOUNT]);
61
+ return result.exitCode === 1 ? null : result.exitCode === 0 ? this.parse(result.stdout) : Promise.reject(new Error("Linux Secret Service read failed"));
62
+ }
63
+ async save(credentials) {
64
+ const result = await this.run("secret-tool", ["store", `--label=AIWG ${SERVICE}`, "service", SERVICE, "account", ACCOUNT], JSON.stringify(credentials));
65
+ if (result.exitCode !== 0)
66
+ throw new Error("Linux Secret Service write failed");
67
+ }
68
+ async delete() { await this.run("secret-tool", ["clear", "service", SERVICE, "account", ACCOUNT]); }
69
+ }
70
+ const WINDOWS_READ = "$v=New-Object Windows.Security.Credentials.PasswordVault;try{$c=$v.Retrieve('releases.aiwg.io','aiwg-cli');$c.RetrievePassword();[Console]::Out.Write($c.Password)}catch{exit 1}";
71
+ const WINDOWS_WRITE = "$s=[Console]::In.ReadToEnd();$v=New-Object Windows.Security.Credentials.PasswordVault;$v.Add((New-Object Windows.Security.Credentials.PasswordCredential('releases.aiwg.io','aiwg-cli',$s)))";
72
+ const WINDOWS_DELETE = "$v=New-Object Windows.Security.Credentials.PasswordVault;try{$c=$v.Retrieve('releases.aiwg.io','aiwg-cli');$v.Remove($c)}catch{}";
73
+ export class WindowsCredentialManagerStore extends NativeCredentialStore {
74
+ metadata = { provider: "windows-credential-manager", location: `CredentialManager:${SERVICE}/${ACCOUNT}` };
75
+ execute(script, stdin = "") { return this.run("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], stdin); }
76
+ async load() { const result = await this.execute(WINDOWS_READ); return result.exitCode === 1 ? null : result.exitCode === 0 ? this.parse(result.stdout) : Promise.reject(new Error("Windows Credential Manager read failed")); }
77
+ async save(credentials) { if ((await this.execute(WINDOWS_WRITE, JSON.stringify(credentials))).exitCode !== 0)
78
+ throw new Error("Windows Credential Manager write failed"); }
79
+ async delete() { await this.execute(WINDOWS_DELETE); }
80
+ }
81
+ export class FileCredentialStore {
82
+ pathname;
83
+ explicitlyAllowed;
84
+ metadata;
85
+ constructor(pathname, explicitlyAllowed) {
86
+ this.pathname = pathname;
87
+ this.explicitlyAllowed = explicitlyAllowed;
88
+ this.pathname = path.resolve(pathname);
89
+ this.metadata = { provider: "file", location: this.pathname };
90
+ }
91
+ assertAllowed() { if (!this.explicitlyAllowed)
92
+ throw new Error("credential file fallback requires --allow-file-store or AIWG_AUTH_ALLOW_FILE_STORE=1"); }
93
+ async load() {
94
+ this.assertAllowed();
95
+ try {
96
+ const stat = await fs.lstat(this.pathname);
97
+ if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o077) !== 0)
98
+ throw new Error("credential file must be a non-symlink mode-0600 regular file");
99
+ return parseCredentials(await fs.readFile(this.pathname, "utf8"));
100
+ }
101
+ catch (error) {
102
+ if (error.code === "ENOENT")
103
+ return null;
104
+ throw error;
105
+ }
106
+ }
107
+ async save(credentials) {
108
+ this.assertAllowed();
109
+ await fs.mkdir(path.dirname(this.pathname), { recursive: true, mode: 0o700 });
110
+ const temporary = `${this.pathname}.${process.pid}.tmp`;
111
+ await fs.writeFile(temporary, `${JSON.stringify(credentials)}\n`, { mode: 0o600, flag: "wx" });
112
+ await fs.rename(temporary, this.pathname);
113
+ await fs.chmod(this.pathname, 0o600);
114
+ }
115
+ async delete() { this.assertAllowed(); await fs.unlink(this.pathname).catch((error) => { if (error.code !== "ENOENT")
116
+ throw error; }); }
117
+ }
118
+ export class MemoryCredentialStore {
119
+ metadata = { provider: "memory", location: "injected-memory-store" };
120
+ value = null;
121
+ async load() { return this.value ? structuredClone(this.value) : null; }
122
+ async save(value) { this.value = structuredClone(value); }
123
+ async delete() { this.value = null; }
124
+ }
125
+ export function defaultCredentialFile() {
126
+ const root = process.env.XDG_CONFIG_HOME || (process.platform === "win32" ? process.env.APPDATA : undefined) || path.join(os.homedir(), ".config");
127
+ return path.join(root, "aiwg", "credentials", "resource-auth.json");
128
+ }
129
+ export function createCredentialStore(options = {}) {
130
+ if (options.useFile)
131
+ return new FileCredentialStore(options.pathname || defaultCredentialFile(), options.allowFile === true);
132
+ const platform = options.platform || process.platform;
133
+ if (platform === "darwin")
134
+ return new MacOsKeychainStore(options.runner);
135
+ if (platform === "win32")
136
+ return new WindowsCredentialManagerStore(options.runner);
137
+ if (platform === "linux")
138
+ return new LinuxSecretServiceStore(options.runner);
139
+ throw new Error("no native credential store is available; explicitly opt in to the mode-0600 file fallback");
140
+ }
141
+ //# sourceMappingURL=credential-store.js.map
@@ -0,0 +1,25 @@
1
+ import { explicitResourceToken } from "./config.js";
2
+ import { createCredentialStore } from "./credential-store.js";
3
+ /**
4
+ * Resolve credentials for protected release downloads. Explicit compatibility
5
+ * inputs take precedence over interactive-login credentials.
6
+ */
7
+ export function createResourceCredentialProvider(env = process.env, store) {
8
+ return async () => {
9
+ const explicit = explicitResourceToken(env);
10
+ if (explicit)
11
+ return explicit;
12
+ const selected = store ?? createCredentialStore();
13
+ try {
14
+ return (await selected.load())?.accessToken ?? null;
15
+ }
16
+ catch (error) {
17
+ // A workstation without the platform keychain helper must retain access
18
+ // to public releases. Invalid/corrupt stored credentials still fail loud.
19
+ if (error.code === "ENOENT")
20
+ return null;
21
+ throw error;
22
+ }
23
+ };
24
+ }
25
+ //# sourceMappingURL=resource-credentials.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -99,10 +99,10 @@ const DEFAULT_CONFIG = {
99
99
  * Get the package root directory.
100
100
  *
101
101
  * Walks up from this file's directory looking for the nearest package.json
102
- * that names "aiwg". This works whether this module runs from its source
103
- * location (`src/channel/manager.mjs`) or from its compiled-build copy
104
- * (`dist/src/channel/manager.mjs`), both of which have package.json at
105
- * the repo root.
102
+ * that names either the full `aiwg` distribution or the lightweight
103
+ * `@aiwg/cli` distribution. This works whether this module runs from its
104
+ * source location (`src/channel/manager.mjs`) or from its compiled-build copy
105
+ * (`dist/src/channel/manager.mjs`).
106
106
  *
107
107
  * The walk is bounded to 10 levels as a safety cap.
108
108
  *
@@ -115,7 +115,7 @@ export function getPackageRoot() {
115
115
  if (existsSync(pkg)) {
116
116
  try {
117
117
  const content = JSON.parse(readFileSync(pkg, 'utf8'));
118
- if (content.name === 'aiwg') return dir;
118
+ if (content.name === 'aiwg' || content.name === '@aiwg/cli') return dir;
119
119
  } catch {
120
120
  // keep walking
121
121
  }
@@ -0,0 +1,125 @@
1
+ import { AuthClient, defaultBrowserOpener } from "../../auth/client.js";
2
+ import { authConfigFromEnvironment } from "../../auth/config.js";
3
+ import { createCredentialStore } from "../../auth/credential-store.js";
4
+ function usage() {
5
+ return [
6
+ "Usage:",
7
+ " aiwg auth login [--device] [--device-label <label>] [--store native|file] [--allow-file-store]",
8
+ " aiwg auth status [--json] [--store native|file] [--allow-file-store]",
9
+ " aiwg auth logout [--all] [--store native|file] [--allow-file-store]",
10
+ "",
11
+ "Exit codes: 0 success; 2 invalid usage; 3 not authenticated; 4 authorization denied/expired; 5 credential store unavailable; 6 network/protocol failure.",
12
+ ].join("\n");
13
+ }
14
+ function value(args, flag) {
15
+ const index = args.indexOf(flag);
16
+ if (index < 0)
17
+ return undefined;
18
+ const result = args[index + 1];
19
+ if (!result || result.startsWith("--"))
20
+ throw new Error(`${flag} requires a value`);
21
+ return result;
22
+ }
23
+ function classify(error) {
24
+ const message = error instanceof Error ? error.message : String(error);
25
+ if (message === "not_authenticated")
26
+ return 3;
27
+ if (/access_denied|expired_token|authorization canceled/.test(message))
28
+ return 4;
29
+ if (/credential|Keychain|Secret Service|Credential Manager/.test(message))
30
+ return 5;
31
+ return 6;
32
+ }
33
+ export function createAuthHandler(dependencies = {}) {
34
+ return {
35
+ id: "auth",
36
+ name: "Authentication",
37
+ description: "Log in, inspect access, and log out of paid AIWG web resources",
38
+ category: "maintenance",
39
+ aliases: [],
40
+ async execute(ctx) {
41
+ if (!ctx.args.length || ["help", "--help", "-h"].includes(ctx.args[0])) {
42
+ console.log(usage());
43
+ return { exitCode: 0 };
44
+ }
45
+ const [subcommand, ...args] = ctx.args;
46
+ if (!["login", "status", "logout"].includes(subcommand))
47
+ return { exitCode: 2, message: usage() };
48
+ try {
49
+ const storeMode = value(args, "--store") || "native";
50
+ if (!["native", "file"].includes(storeMode))
51
+ return { exitCode: 2, message: "--store must be native or file" };
52
+ const allowFile = args.includes("--allow-file-store") || dependencies.env?.AIWG_AUTH_ALLOW_FILE_STORE === "1" || process.env.AIWG_AUTH_ALLOW_FILE_STORE === "1";
53
+ const store = dependencies.store || createCredentialStore({
54
+ platform: dependencies.platform,
55
+ useFile: storeMode === "file",
56
+ allowFile,
57
+ runner: dependencies.runner,
58
+ });
59
+ if (store.metadata.provider === "file")
60
+ console.error("Warning: using explicitly opted-in mode-0600 credential file fallback.");
61
+ const config = dependencies.config || authConfigFromEnvironment(dependencies.env);
62
+ const client = new AuthClient(config, store, dependencies.fetcher, dependencies.openBrowser, dependencies.now);
63
+ if (subcommand === "login") {
64
+ const deviceLabel = value(args, "--device-label");
65
+ if (args.includes("--device")) {
66
+ await client.loginDevice({
67
+ signal: ctx.signal,
68
+ deviceLabel,
69
+ onCode(info) {
70
+ console.log(`Open: ${String(info.verification_uri)}`);
71
+ console.log(`Code: ${String(info.user_code)}`);
72
+ },
73
+ });
74
+ }
75
+ else {
76
+ await client.loginBrowser({ signal: ctx.signal, deviceLabel });
77
+ }
78
+ console.log(`Authenticated. Credentials stored in ${store.metadata.location}.`);
79
+ return { exitCode: 0 };
80
+ }
81
+ if (subcommand === "status") {
82
+ const { profile, credentials } = await client.status(ctx.signal);
83
+ const output = {
84
+ authenticated: true,
85
+ subject: profile.sub,
86
+ account: profile.email || null,
87
+ organization: profile.organization_id || null,
88
+ scopes: profile.scope?.split(/\s+/).filter(Boolean) || credentials.scope,
89
+ plan: profile.plan || null,
90
+ accessReason: profile.access_reason || null,
91
+ accessValidUntil: profile.access_valid_until || credentials.expiresAt,
92
+ credentialStore: store.metadata,
93
+ };
94
+ if (args.includes("--json"))
95
+ console.log(JSON.stringify(output));
96
+ else {
97
+ console.log(`subject: ${output.subject}`);
98
+ console.log(`account: ${output.account || "(not supplied)"}`);
99
+ console.log(`organization: ${output.organization || "(none)"}`);
100
+ console.log(`scopes: ${output.scopes.join(" ")}`);
101
+ console.log(`plan: ${output.plan || "(none)"}`);
102
+ console.log(`access_reason: ${output.accessReason || "(unknown)"}`);
103
+ console.log(`access_valid_until: ${output.accessValidUntil}`);
104
+ console.log(`credential_location: ${store.metadata.location}`);
105
+ }
106
+ return { exitCode: 0 };
107
+ }
108
+ if (args.includes("--all")) {
109
+ const opener = dependencies.openBrowser || defaultBrowserOpener;
110
+ await opener(`${config.baseUrl}/account/security?revoke=all`);
111
+ }
112
+ await client.logout(ctx.signal);
113
+ console.log(args.includes("--all")
114
+ ? "Local credentials removed. Complete revoke-all in the opened account security page."
115
+ : "Logged out and removed local credentials.");
116
+ return { exitCode: 0 };
117
+ }
118
+ catch (error) {
119
+ return { exitCode: classify(error), message: error instanceof Error ? error.message : String(error) };
120
+ }
121
+ },
122
+ };
123
+ }
124
+ export const authHandler = createAuthHandler();
125
+ //# sourceMappingURL=auth.js.map
@@ -81,6 +81,7 @@ function displayHelp() {
81
81
  ['discover "<phrase>"', 'Find skills/agents/commands/rules by capability'],
82
82
  ['show <type> <name>', 'Stream the body of an indexed artifact'],
83
83
  ['versions <list|resolve|show>', 'Browse and resolve signed AIWG web resource releases'],
84
+ ['auth <login|status|logout>', 'Authenticate for paid AIWG web resources'],
84
85
  ['index <subcommand>', 'Manage the artifact index (build/query/discover/deps/stats)'],
85
86
  ['artifacts move --to <path>', 'Move/rename the project AIWG artifact root and reindex'],
86
87
  ]);
@@ -13,6 +13,7 @@ export { createScriptRunner, DefaultScriptRunner } from './script-runner.js';
13
13
  // Import all handlers
14
14
  import { helpHandler } from './help.js';
15
15
  import { versionHandler } from './version.js';
16
+ import { authHandler } from './auth.js';
16
17
  import { useHandler } from './use.js';
17
18
  import { statusHandler, wizardHandler, migrateWorkspaceHandler, rollbackWorkspaceHandler, workspaceHandlers, } from './workspace.js';
18
19
  import { prefillCardsHandler, contributeStartHandler, validateMetadataHandler, doctorHandler, updateHandler, utilityHandlers, } from './utilities.js';
@@ -57,12 +58,13 @@ import { commandLogHandler } from './command-log.js';
57
58
  import { skillUsageHandler } from './skill-usage.js';
58
59
  import { modelsHandler } from './models.js';
59
60
  import { versionsHandler } from './resource-versions.js';
61
+ import { jobHandler } from './job.js';
60
62
  // Re-export individual handlers
61
63
  export {
62
64
  // Maintenance
63
- helpHandler, versionHandler, doctorHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
65
+ helpHandler, versionHandler, authHandler, doctorHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
64
66
  // Framework management
65
- useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler,
67
+ useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler,
66
68
  // Project
67
69
  newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
68
70
  // Workspace
@@ -114,6 +116,7 @@ export const allHandlers = [
114
116
  // Maintenance (shown first in help)
115
117
  helpHandler,
116
118
  versionHandler,
119
+ authHandler,
117
120
  doctorHandler,
118
121
  updateHandler,
119
122
  refreshHandler,
@@ -139,6 +142,7 @@ export const allHandlers = [
139
142
  issueHandler,
140
143
  issueAuditHandler,
141
144
  runHandler,
145
+ jobHandler,
142
146
  // Workspace management
143
147
  ...workspaceHandlers,
144
148
  // Subcommand handlers (MCP, catalog, index, skills)