@platless/axom-cli 0.1.0

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 (90) hide show
  1. package/README.md +64 -0
  2. package/dist/args.js +29 -0
  3. package/dist/cli.js +76 -0
  4. package/dist/commands/auth.js +52 -0
  5. package/dist/commands/execute.js +40 -0
  6. package/dist/commands/guide.js +38 -0
  7. package/dist/commands/record.js +99 -0
  8. package/dist/commands/skills.js +69 -0
  9. package/dist/commands/struct.js +50 -0
  10. package/dist/commands/update.js +88 -0
  11. package/dist/commands/whoami.js +8 -0
  12. package/dist/commands/workstation.js +171 -0
  13. package/dist/config.js +131 -0
  14. package/dist/credentialBroker.js +340 -0
  15. package/dist/credentials.js +211 -0
  16. package/dist/install/agentsDir.js +35 -0
  17. package/dist/install/claude/commands.js +89 -0
  18. package/dist/install/claude/materialize.js +42 -0
  19. package/dist/install/installer.js +45 -0
  20. package/dist/install/targets/claude.js +154 -0
  21. package/dist/mcp.js +152 -0
  22. package/dist/output.js +37 -0
  23. package/dist/workstation/acp/acpAgents.js +1287 -0
  24. package/dist/workstation/acp/acpClient.js +691 -0
  25. package/dist/workstation/acp/acpSession.js +1047 -0
  26. package/dist/workstation/acp/adoption.js +396 -0
  27. package/dist/workstation/acp/auth.js +80 -0
  28. package/dist/workstation/acp/authUrlSniffer.js +52 -0
  29. package/dist/workstation/acp/binaryInstall.js +411 -0
  30. package/dist/workstation/acp/configOptions.js +257 -0
  31. package/dist/workstation/acp/contract.js +494 -0
  32. package/dist/workstation/acp/draftInterceptor.js +154 -0
  33. package/dist/workstation/acp/draftSubagents.js +38 -0
  34. package/dist/workstation/acp/installErrors.js +89 -0
  35. package/dist/workstation/acp/installProbe.js +363 -0
  36. package/dist/workstation/acp/permission.js +327 -0
  37. package/dist/workstation/acp/registry/catalog.js +29 -0
  38. package/dist/workstation/acp/registry/launch.js +128 -0
  39. package/dist/workstation/acp/registry/registryClient.js +160 -0
  40. package/dist/workstation/acp/registry/registryTypes.js +8 -0
  41. package/dist/workstation/acp/sessionUpdateMapper.js +602 -0
  42. package/dist/workstation/acp/test/fakeAgent.js +435 -0
  43. package/dist/workstation/acp/test/sessionUpdateFixtures.js +165 -0
  44. package/dist/workstation/acp/timedCommand.js +47 -0
  45. package/dist/workstation/acp/uvxInstall.js +248 -0
  46. package/dist/workstation/acp/validate.js +134 -0
  47. package/dist/workstation/agentSessions.js +1628 -0
  48. package/dist/workstation/channels/agent.js +96 -0
  49. package/dist/workstation/channels/computer.js +91 -0
  50. package/dist/workstation/channels/management.js +231 -0
  51. package/dist/workstation/channels/url.js +42 -0
  52. package/dist/workstation/config.js +47 -0
  53. package/dist/workstation/connection/encryptedChannel.js +261 -0
  54. package/dist/workstation/connection/retryPolicy.js +14 -0
  55. package/dist/workstation/connection/supervisor.js +353 -0
  56. package/dist/workstation/crypto.js +63 -0
  57. package/dist/workstation/daemon.js +225 -0
  58. package/dist/workstation/daemonctl.js +95 -0
  59. package/dist/workstation/exec.js +112 -0
  60. package/dist/workstation/journal.js +257 -0
  61. package/dist/workstation/names.js +17 -0
  62. package/dist/workstation/oauth/browser.js +24 -0
  63. package/dist/workstation/oauth/deviceAuthorization.js +198 -0
  64. package/dist/workstation/osinfo.js +4 -0
  65. package/dist/workstation/protocol/agent.js +606 -0
  66. package/dist/workstation/protocol/common.js +136 -0
  67. package/dist/workstation/protocol/computer.js +31 -0
  68. package/dist/workstation/protocol/management.js +116 -0
  69. package/dist/workstation/register.js +42 -0
  70. package/dist/workstation/service/installer.js +362 -0
  71. package/dist/workstation/service/launchd.js +46 -0
  72. package/dist/workstation/service/loginShell.js +36 -0
  73. package/dist/workstation/service/systemd.js +48 -0
  74. package/dist/workstation/service/types.js +73 -0
  75. package/dist/workstation/supervision.js +52 -0
  76. package/dist/workstation/systemMetrics.js +271 -0
  77. package/dist/workstation/update/artifact.js +61 -0
  78. package/dist/workstation/update/coordinator.js +240 -0
  79. package/dist/workstation/update/npmInstaller.js +91 -0
  80. package/dist/workstation/update/redact.js +36 -0
  81. package/dist/workstation/update/runUpdate.js +215 -0
  82. package/dist/workstation/update/stateStore.js +87 -0
  83. package/dist/workstation/update/taskRegistry.js +142 -0
  84. package/dist/workstation/update/types.js +3 -0
  85. package/dist/workstation/update/watchdog.js +311 -0
  86. package/dist/workstation/version.js +12 -0
  87. package/package.json +39 -0
  88. package/skills/axom/SKILL.md +83 -0
  89. package/skills/axom-definition/SKILL.md +74 -0
  90. package/skills/axom-platform/SKILL.md +78 -0
package/dist/mcp.js ADDED
@@ -0,0 +1,152 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
+ import { AuthError } from './config.js';
4
+ import { accessTokenFor } from './credentials.js';
5
+ import { CLIENT_VERSION } from './workstation/version.js';
6
+ export async function callAxomTool(server, toolName, args) {
7
+ const normalizedServer = normalizeServerUrl(server);
8
+ const token = await ensureFreshToken(normalizedServer);
9
+ try {
10
+ return await callAxomToolOnce(normalizedServer, token, toolName, args);
11
+ }
12
+ catch (error) {
13
+ if (isInsufficientScope(error))
14
+ throw insufficientScopeError(normalizedServer);
15
+ if (!isUnauthorized(error))
16
+ throw error;
17
+ // An env token cannot be refreshed: `accessTokenFor` returns the same literal whatever
18
+ // `forceRefresh` says (credentials.ts's env branch returns before any store lookup), so the
19
+ // retry below would repeat the identical request, land on the identical 401, and then advise
20
+ // `axom auth` — a browser login, inside a container that has no browser and gets its credential
21
+ // from the environment. Say what is actually wrong instead, and skip the pointless second call.
22
+ if (usesEnvAccessToken())
23
+ throw envAccessTokenRejectedError(normalizedServer);
24
+ }
25
+ const refreshedToken = await refreshTokens(normalizedServer);
26
+ try {
27
+ return await callAxomToolOnce(normalizedServer, refreshedToken, toolName, args);
28
+ }
29
+ catch (error) {
30
+ if (isInsufficientScope(error))
31
+ throw insufficientScopeError(normalizedServer);
32
+ if (isUnauthorized(error)) {
33
+ throw new AuthError(`MCP authentication failed with ${normalizedServer}. Run: axom auth --server ${normalizedServer}`);
34
+ }
35
+ throw error;
36
+ }
37
+ }
38
+ async function callAxomToolOnce(server, token, toolName, args) {
39
+ const client = new Client({ name: 'axom-cli', version: CLIENT_VERSION });
40
+ const transport = new StreamableHTTPClientTransport(new URL(`${server}/mcp`), {
41
+ requestInit: {
42
+ headers: { Authorization: `Bearer ${token}` },
43
+ },
44
+ });
45
+ try {
46
+ await client.connect(transport);
47
+ const result = await client.callTool({ name: toolName, arguments: args });
48
+ return toToolOutcome(result);
49
+ }
50
+ finally {
51
+ try {
52
+ await transport.terminateSession();
53
+ }
54
+ catch {
55
+ // Best-effort cleanup: command execution should not fail on session teardown.
56
+ }
57
+ try {
58
+ await client.close();
59
+ }
60
+ catch {
61
+ // Best-effort cleanup: command execution should not fail on transport close.
62
+ }
63
+ }
64
+ }
65
+ function toToolOutcome(result) {
66
+ const value = result;
67
+ const content = Array.isArray(value.content) ? value.content : [];
68
+ const text = content
69
+ .flatMap((item) => {
70
+ if (item && typeof item === 'object' && 'type' in item && item.type === 'text' && 'text' in item) {
71
+ return typeof item.text === 'string' ? [item.text] : [];
72
+ }
73
+ return [];
74
+ })
75
+ .join('\n');
76
+ return {
77
+ text,
78
+ structuredContent: isRecord(value.structuredContent) ? value.structuredContent : undefined,
79
+ isError: value.isError === true,
80
+ };
81
+ }
82
+ function isRecord(value) {
83
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
84
+ }
85
+ function isUnauthorized(error) {
86
+ const candidate = error;
87
+ if (candidate.status === 401 || candidate.code === 401 || candidate.response?.status === 401)
88
+ return true;
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ return /\b401\b/.test(message);
91
+ }
92
+ /**
93
+ * `/mcp` returns 403 (not 401) when the bearer token is valid but lacks the `mcp` scope — this is
94
+ * how a `workstation`-only access token (the default for a token minted without `--scope mcp`)
95
+ * looks from the client. Retrying with a refreshed token would not help, since the new token would
96
+ * carry the same scopes, so this is checked before the 401/refresh path, not folded into it.
97
+ */
98
+ export function isInsufficientScope(error) {
99
+ const candidate = error;
100
+ if (candidate.status === 403 || candidate.code === 403 || candidate.response?.status === 403)
101
+ return true;
102
+ const message = error instanceof Error ? error.message : String(error);
103
+ return /\b403\b/.test(message);
104
+ }
105
+ export function insufficientScopeError(server) {
106
+ const hint = usesEnvAccessToken()
107
+ ? ' Set AXOM_ACCESS_TOKEN to a token that includes the "mcp" scope, or run axom auth.'
108
+ : '';
109
+ return new AuthError(`MCP token is missing the "mcp" scope for ${server}.${hint}`);
110
+ }
111
+ /**
112
+ * Is this process authenticating with `AXOM_ACCESS_TOKEN` rather than a stored login? Mirrors
113
+ * `accessTokenFor`'s own env branch exactly, blank-as-unset included, so the two cannot disagree
114
+ * about which credential is in play.
115
+ */
116
+ function usesEnvAccessToken() {
117
+ return Boolean(process.env.AXOM_ACCESS_TOKEN?.trim());
118
+ }
119
+ /**
120
+ * The 401 counterpart of [insufficientScopeError]. A rejected env token is not a stale session that
121
+ * a re-login fixes -- the literal in the environment is invalid, expired, or revoked -- so the
122
+ * remedy is a new token in the environment, not `axom auth` on a machine with a browser.
123
+ */
124
+ export function envAccessTokenRejectedError(server) {
125
+ return new AuthError(`AXOM_ACCESS_TOKEN was rejected by ${server}: the token is invalid, expired, or revoked. ` +
126
+ 'Create a new one under Settings → Security → Access Tokens and set AXOM_ACCESS_TOKEN to it.');
127
+ }
128
+ function normalizeServerUrl(value) {
129
+ return value.trim().replace(/\/+$/, '');
130
+ }
131
+ /**
132
+ * Gets a usable access token from the credential layer, refreshing when its in-memory cache is
133
+ * absent or near expiry. The refresh token is stored in the OS credential store; config.ts's
134
+ * ServerCredentials is only a non-secret authenticated marker and is not consumed here.
135
+ */
136
+ export async function ensureFreshToken(server) {
137
+ const normalizedServer = normalizeServerUrl(server);
138
+ const accessToken = await accessTokenFor(normalizedServer);
139
+ if (!accessToken)
140
+ throw notAuthenticated(normalizedServer);
141
+ return accessToken;
142
+ }
143
+ export async function refreshTokens(server) {
144
+ const normalizedServer = normalizeServerUrl(server);
145
+ const accessToken = await accessTokenFor(normalizedServer, true);
146
+ if (!accessToken)
147
+ throw notAuthenticated(normalizedServer);
148
+ return accessToken;
149
+ }
150
+ function notAuthenticated(server) {
151
+ return new AuthError(`Not authenticated with ${server}. Run: axom auth --server ${server}`);
152
+ }
package/dist/output.js ADDED
@@ -0,0 +1,37 @@
1
+ import { AuthError, UsageError } from './config.js';
2
+ export function printOutcome(outcome) {
3
+ if (outcome.isError) {
4
+ console.error(outcome.structuredContent === undefined ? outcome.text : JSON.stringify(outcome.structuredContent, null, 2));
5
+ process.exitCode = 1;
6
+ return;
7
+ }
8
+ if (outcome.structuredContent !== undefined) {
9
+ console.log(JSON.stringify(outcome.structuredContent, null, 2));
10
+ return;
11
+ }
12
+ try {
13
+ console.log(JSON.stringify(JSON.parse(outcome.text), null, 2));
14
+ }
15
+ catch {
16
+ console.log(outcome.text);
17
+ }
18
+ }
19
+ export async function runCommand(fn) {
20
+ try {
21
+ await fn();
22
+ }
23
+ catch (error) {
24
+ if (error instanceof UsageError) {
25
+ console.error(error.message);
26
+ process.exitCode = 2;
27
+ return;
28
+ }
29
+ if (error instanceof AuthError) {
30
+ console.error(error.message);
31
+ process.exitCode = 3;
32
+ return;
33
+ }
34
+ console.error(error instanceof Error ? error.message : String(error));
35
+ process.exitCode = 1;
36
+ }
37
+ }