@aopslabs/aops 0.2.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 (171) hide show
  1. package/LICENSE +19 -0
  2. package/NOTICE +7 -0
  3. package/README.md +203 -0
  4. package/assets/agent-assets/core/SKILL.md +49 -0
  5. package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
  6. package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
  7. package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
  8. package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
  9. package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
  10. package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
  11. package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
  12. package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
  13. package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
  14. package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
  15. package/assets/agent-assets/core/references/view/SKILL.md +58 -0
  16. package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
  17. package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
  18. package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
  19. package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
  20. package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
  21. package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
  22. package/assets/agent-assets/core/user-guides/docman.md +162 -0
  23. package/assets/agent-assets/core/user-guides/projectman.md +212 -0
  24. package/assets/agent-assets/core/user-guides/sys.md +94 -0
  25. package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
  26. package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
  27. package/assets/r6-remote-session-baseline.json +19 -0
  28. package/assets/skills/aops-install/SKILL.md +231 -0
  29. package/assets/skills/aops-install/agents/openai.yaml +4 -0
  30. package/dist/commands/activity.js +352 -0
  31. package/dist/commands/agent-profile.js +653 -0
  32. package/dist/commands/agent.js +710 -0
  33. package/dist/commands/agents-md.js +125 -0
  34. package/dist/commands/api.js +84 -0
  35. package/dist/commands/archive.js +873 -0
  36. package/dist/commands/artifact.js +535 -0
  37. package/dist/commands/assets.js +1414 -0
  38. package/dist/commands/auth/device-login.js +1 -0
  39. package/dist/commands/auth/legacy-export.js +177 -0
  40. package/dist/commands/auth/login.js +426 -0
  41. package/dist/commands/chat.js +1363 -0
  42. package/dist/commands/chatv3.js +1684 -0
  43. package/dist/commands/checkpoint.js +549 -0
  44. package/dist/commands/commercial-artifact.js +475 -0
  45. package/dist/commands/commercial-license.js +594 -0
  46. package/dist/commands/community-auth.js +62 -0
  47. package/dist/commands/community-cockpit.js +185 -0
  48. package/dist/commands/community-console.js +109 -0
  49. package/dist/commands/community-doctor.js +335 -0
  50. package/dist/commands/community-server.js +2062 -0
  51. package/dist/commands/community-setup.js +284 -0
  52. package/dist/commands/discuss.js +1448 -0
  53. package/dist/commands/doc.js +5219 -0
  54. package/dist/commands/experience.js +922 -0
  55. package/dist/commands/host.js +1127 -0
  56. package/dist/commands/init.js +118 -0
  57. package/dist/commands/memory.js +3129 -0
  58. package/dist/commands/mission.js +562 -0
  59. package/dist/commands/official-catalog.js +134 -0
  60. package/dist/commands/plan.js +84 -0
  61. package/dist/commands/playbook.js +415 -0
  62. package/dist/commands/pm/context.js +6 -0
  63. package/dist/commands/pm/index.js +701 -0
  64. package/dist/commands/pm/projectman.js +6266 -0
  65. package/dist/commands/project.js +1080 -0
  66. package/dist/commands/prompt.js +1024 -0
  67. package/dist/commands/repo-sync.js +1907 -0
  68. package/dist/commands/resource.js +527 -0
  69. package/dist/commands/skill.js +1211 -0
  70. package/dist/commands/start-disciplines.js +510 -0
  71. package/dist/commands/start.js +1343 -0
  72. package/dist/commands/target.js +309 -0
  73. package/dist/commands/version.js +42 -0
  74. package/dist/commands/view.js +1852 -0
  75. package/dist/launchers/cockpit.js +28 -0
  76. package/dist/launchers/options.js +74 -0
  77. package/dist/launchers/server.js +41 -0
  78. package/dist/lib/agent-assets/envelope.js +83 -0
  79. package/dist/lib/agent-assets/gateway.js +15 -0
  80. package/dist/lib/agent-assets/guards.js +23 -0
  81. package/dist/lib/agent-assets/hosted-discovery.js +148 -0
  82. package/dist/lib/agent-assets/hosted-package-input.js +154 -0
  83. package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
  84. package/dist/lib/agent-assets/native-fs.js +589 -0
  85. package/dist/lib/agent-assets/package-manifest.js +324 -0
  86. package/dist/lib/agent-assets/portable-path.js +67 -0
  87. package/dist/lib/agent-assets/release-input.js +177 -0
  88. package/dist/lib/agent-assets/roots.js +45 -0
  89. package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
  90. package/dist/lib/agent-assets/runtime-targets.js +50 -0
  91. package/dist/lib/agent-assets/store-reader.js +1212 -0
  92. package/dist/lib/agent-assets/store-types.js +1 -0
  93. package/dist/lib/agent-assets/store-writer.js +1484 -0
  94. package/dist/lib/agent-assets/types.js +1 -0
  95. package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
  96. package/dist/lib/commercial-final-readiness.js +504 -0
  97. package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
  98. package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
  99. package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
  100. package/dist/lib/community-client-contract.js +164 -0
  101. package/dist/lib/community-cockpit-lifecycle.js +601 -0
  102. package/dist/lib/community-command-abort.js +59 -0
  103. package/dist/lib/community-diagnostic.js +237 -0
  104. package/dist/lib/community-docker-adapter.js +498 -0
  105. package/dist/lib/community-home.js +31 -0
  106. package/dist/lib/community-instance-contract.js +148 -0
  107. package/dist/lib/community-instance-layout.js +97 -0
  108. package/dist/lib/community-lifecycle.js +1631 -0
  109. package/dist/lib/community-migration-snapshot.js +770 -0
  110. package/dist/lib/community-native-application-recovery.js +425 -0
  111. package/dist/lib/community-native-child.js +240 -0
  112. package/dist/lib/community-native-database-recovery.js +456 -0
  113. package/dist/lib/community-native-lifecycle.js +2458 -0
  114. package/dist/lib/community-native-migration.js +339 -0
  115. package/dist/lib/community-native-postgres.js +596 -0
  116. package/dist/lib/community-operation-journal.js +773 -0
  117. package/dist/lib/community-operation-lock.js +596 -0
  118. package/dist/lib/community-release-resolver.js +291 -0
  119. package/dist/lib/community-release-verifier.js +475 -0
  120. package/dist/lib/community-repo-discovery.js +252 -0
  121. package/dist/lib/community-setup-server-env.js +203 -0
  122. package/dist/lib/external-url.js +110 -0
  123. package/dist/lib/official-catalog-gateway.js +108 -0
  124. package/dist/lib/official-catalog.js +364 -0
  125. package/dist/lib/server-bootstrap.js +240 -0
  126. package/dist/lib/setup-agent-assets-bridge.js +176 -0
  127. package/dist/lib/setup-agent-assets-release.js +52 -0
  128. package/dist/lib/setup-external-postgres.js +108 -0
  129. package/dist/lib/setup-init-orchestrator.js +990 -0
  130. package/dist/lib/setup-install-guide.js +52 -0
  131. package/dist/lib/setup-installer-release.js +47 -0
  132. package/dist/lib/setup-local-postgres.js +329 -0
  133. package/dist/lib/setup-official-catalog-bridge.js +148 -0
  134. package/dist/lib/setup-readiness.js +888 -0
  135. package/dist/lib/tui-launcher.js +59 -0
  136. package/dist/main.js +150 -0
  137. package/dist/seeds/agents-md.js +54 -0
  138. package/dist/utils/agent-gateway.js +275 -0
  139. package/dist/utils/agents-md.js +281 -0
  140. package/dist/utils/api.js +255 -0
  141. package/dist/utils/chatv3-session-store.js +185 -0
  142. package/dist/utils/command.js +67 -0
  143. package/dist/utils/community-host-runtime.js +376 -0
  144. package/dist/utils/config.js +1015 -0
  145. package/dist/utils/discussion-workspace.js +552 -0
  146. package/dist/utils/experience-workspace.js +326 -0
  147. package/dist/utils/guide-paths.js +67 -0
  148. package/dist/utils/hosted-sugar.js +56 -0
  149. package/dist/utils/hosted-workspace.js +229 -0
  150. package/dist/utils/memory-workspace.js +537 -0
  151. package/dist/utils/operator-runtime.js +199 -0
  152. package/dist/utils/password-crypto.js +39 -0
  153. package/dist/utils/playbook-workspace.js +479 -0
  154. package/dist/utils/project-context.js +255 -0
  155. package/dist/utils/prompts.js +70 -0
  156. package/dist/utils/refresh-race.js +28 -0
  157. package/dist/utils/repo-config.js +109 -0
  158. package/dist/utils/repo-first-projectman.js +194 -0
  159. package/dist/utils/repo-first-storage.js +13 -0
  160. package/dist/utils/repo-first-view.js +305 -0
  161. package/dist/utils/session-guidance.js +278 -0
  162. package/dist/utils/session-state.js +137 -0
  163. package/dist/utils/table.js +39 -0
  164. package/dist/utils/transient-fs.js +49 -0
  165. package/dist/utils/workspace-root.js +1 -0
  166. package/launchers/aops-cockpit.ps1 +15 -0
  167. package/launchers/aops-cockpit.sh +16 -0
  168. package/launchers/aops-server.ps1 +15 -0
  169. package/launchers/aops-server.sh +16 -0
  170. package/native/tui/win32-x64/aops-tui.exe +0 -0
  171. package/package.json +81 -0
@@ -0,0 +1 @@
1
+ export { REMOTE_SESSION_DEVICE_GRANT_TYPE, runRemoteSessionDeviceLogin, } from '@aopslabs/api-client';
@@ -0,0 +1,177 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { closeSync, constants, existsSync, fsyncSync, openSync, writeFileSync, } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { readAopsServerEnvFileContent } from '@aopslabs/aops-runtime-config';
5
+ import { Client } from 'pg';
6
+ import { parseDotEnvAssignments } from '../../lib/server-bootstrap.js';
7
+ const LEGACY_RELATIONS = Object.freeze([
8
+ 'authv2_users',
9
+ 'authv2_roles',
10
+ 'authv2_user_roles',
11
+ ]);
12
+ function normalizeNonEmpty(value) {
13
+ if (typeof value !== 'string')
14
+ return undefined;
15
+ const normalized = value.trim();
16
+ return normalized || undefined;
17
+ }
18
+ function normalizeValue(value) {
19
+ if (value instanceof Date)
20
+ return value.toISOString();
21
+ if (Array.isArray(value))
22
+ return value.map(normalizeValue);
23
+ if (value && typeof value === 'object') {
24
+ return Object.fromEntries(Object.entries(value)
25
+ .sort(([left], [right]) => left.localeCompare(right))
26
+ .map(([key, entry]) => [key, normalizeValue(entry)]));
27
+ }
28
+ return value;
29
+ }
30
+ function stableJson(value) {
31
+ return `${JSON.stringify(normalizeValue(value), null, 2)}\n`;
32
+ }
33
+ function resolveLegacyRepoUrl(options) {
34
+ const processUrl = normalizeNonEmpty(process.env.AOPS_PG_URL);
35
+ if (processUrl)
36
+ return processUrl;
37
+ const snapshot = readAopsServerEnvFileContent(process.env, options.envPath);
38
+ const assignments = parseDotEnvAssignments(snapshot.content);
39
+ const saved = normalizeNonEmpty(assignments.AOPS_PG_URL);
40
+ if (!saved) {
41
+ throw new Error('legacy_auth_export_pg_url_required:set_AOPS_PG_URL_or_select_the_private_server_env');
42
+ }
43
+ return saved;
44
+ }
45
+ function writeExclusivePrivateFile(target, content) {
46
+ if (!path.isAbsolute(target) || /[\0\r\n]/.test(target)) {
47
+ throw new Error('legacy_auth_export_output_path_invalid');
48
+ }
49
+ const parent = path.dirname(target);
50
+ if (!existsSync(parent))
51
+ throw new Error('legacy_auth_export_output_parent_missing');
52
+ let descriptor;
53
+ try {
54
+ descriptor = openSync(target, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
55
+ writeFileSync(descriptor, content, 'utf8');
56
+ fsyncSync(descriptor);
57
+ }
58
+ catch (error) {
59
+ if (error.code === 'EEXIST') {
60
+ throw new Error('legacy_auth_export_output_exists:choose_a_new_path');
61
+ }
62
+ throw error;
63
+ }
64
+ finally {
65
+ if (descriptor !== undefined)
66
+ closeSync(descriptor);
67
+ }
68
+ }
69
+ export async function buildLegacyAuthExport(repoUrl, dependencies = {}) {
70
+ const client = dependencies.clientFactory?.(repoUrl) ?? new Client({ connectionString: repoUrl });
71
+ let transactionOpen = false;
72
+ try {
73
+ await client.connect();
74
+ await client.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
75
+ transactionOpen = true;
76
+ const relationResult = await client.query(`SELECT c.relname AS "table"
77
+ FROM pg_catalog.pg_class c
78
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
79
+ WHERE n.nspname = 'public'
80
+ AND c.relkind IN ('r', 'p')
81
+ AND c.relname = ANY($1::text[])
82
+ ORDER BY c.relname`, [LEGACY_RELATIONS]);
83
+ const present = relationResult.rows.map((row) => String(row.table));
84
+ if (present.length === 0) {
85
+ await client.query('COMMIT');
86
+ transactionOpen = false;
87
+ const data = {
88
+ schemaVersion: 1,
89
+ source: 'aops-community-authv2-historical',
90
+ status: 'not-present',
91
+ generatedAt: (dependencies.now ?? (() => new Date()))().toISOString(),
92
+ tenants: [],
93
+ roles: [],
94
+ users: [],
95
+ roleAssignments: [],
96
+ };
97
+ return {
98
+ ...data,
99
+ sha256: createHash('sha256').update(stableJson(data)).digest('hex'),
100
+ };
101
+ }
102
+ if (present.length !== LEGACY_RELATIONS.length) {
103
+ throw new Error(`legacy_auth_export_schema_incomplete:${present.join(',')}`);
104
+ }
105
+ const users = await client.query(`SELECT id::text AS id, "tenantId", email, "fullName", "emailVerified",
106
+ COALESCE(roles, ARRAY[]::text[]) AS "legacyRoleKeys",
107
+ COALESCE(permissions, ARRAY[]::text[]) AS permissions,
108
+ "createdAt", "updatedAt"
109
+ FROM public.authv2_users
110
+ ORDER BY "tenantId", lower(email), id`);
111
+ const roles = await client.query(`SELECT id::text AS id, "tenantId", "roleKey", "displayName",
112
+ description, "isSystem", source, "createdAt", "updatedAt"
113
+ FROM public.authv2_roles
114
+ ORDER BY "tenantId", "roleKey", id`);
115
+ const assignments = await client.query(`SELECT links."tenantId", links."userId"::text AS "userId",
116
+ links."roleId"::text AS "roleId", roles."roleKey",
117
+ links."assignedAt", links."expiresAt"
118
+ FROM public.authv2_user_roles links
119
+ JOIN public.authv2_roles roles
120
+ ON roles.id = links."roleId" AND roles."tenantId" = links."tenantId"
121
+ ORDER BY links."tenantId", links."userId", roles."roleKey", links."roleId"`);
122
+ await client.query('COMMIT');
123
+ transactionOpen = false;
124
+ const tenantIds = [...new Set([
125
+ ...users.rows.map((row) => String(row.tenantId)),
126
+ ...roles.rows.map((row) => String(row.tenantId)),
127
+ ])].sort();
128
+ const data = {
129
+ schemaVersion: 1,
130
+ source: 'aops-community-authv2-historical',
131
+ status: 'historical-read-only',
132
+ generatedAt: (dependencies.now ?? (() => new Date()))().toISOString(),
133
+ tenants: tenantIds.map((tenantId) => ({ tenantId })),
134
+ roles: roles.rows,
135
+ users: users.rows,
136
+ roleAssignments: assignments.rows,
137
+ reauthentication: {
138
+ required: true,
139
+ credentialMaterialExported: false,
140
+ nextAction: 'create-a-new-remote-session-through-the-South-device-flow',
141
+ },
142
+ };
143
+ return {
144
+ ...data,
145
+ sha256: createHash('sha256').update(stableJson(data)).digest('hex'),
146
+ };
147
+ }
148
+ catch (error) {
149
+ if (transactionOpen)
150
+ await client.query('ROLLBACK').catch(() => undefined);
151
+ throw error;
152
+ }
153
+ finally {
154
+ await client.end().catch(() => undefined);
155
+ }
156
+ }
157
+ export async function runLegacyAuthExport(options = {}, dependencies = {}) {
158
+ if (options.out && (options.apply !== true || options.confirm !== true)) {
159
+ throw new Error('legacy_auth_export_write_requires_--apply_and_--confirm');
160
+ }
161
+ const exported = await buildLegacyAuthExport(resolveLegacyRepoUrl(options), dependencies);
162
+ const content = stableJson(exported);
163
+ if (options.out) {
164
+ const outputPath = path.resolve(options.out);
165
+ writeExclusivePrivateFile(outputPath, content);
166
+ process.stdout.write(`${JSON.stringify({
167
+ ok: true,
168
+ action: 'auth.legacy-export',
169
+ status: 'written',
170
+ outputPath,
171
+ sha256: exported.sha256,
172
+ credentialMaterialExported: false,
173
+ }, null, options.json ? 2 : 0)}\n`);
174
+ return;
175
+ }
176
+ process.stdout.write(content);
177
+ }
@@ -0,0 +1,426 @@
1
+ import { Command } from 'commander';
2
+ import { banner, logError, logInfo, logSuccess } from '@aopslabs/xf-cli-ui';
3
+ import { createAopsApiClient, xfMessage } from '@aopslabs/api-client';
4
+ import { clearApiTokensInConfig, getActiveApiTarget, getApiTarget, isRemoteSessionAuthProvider, setApiTokensInConfig, } from '../../utils/config.js';
5
+ import { createCliApiClientFromOptions, probeCliRuntimeMode, resolveCliApiBaseUrl, } from '../../utils/api.js';
6
+ import { openApprovedHttpsUrl } from '../../lib/external-url.js';
7
+ import { runRemoteSessionDeviceLogin } from './device-login.js';
8
+ function normalizeNonEmpty(value) {
9
+ if (typeof value !== 'string')
10
+ return undefined;
11
+ const trimmed = value.trim();
12
+ return trimmed.length > 0 ? trimmed : undefined;
13
+ }
14
+ function resultErrorCode(result, fallback) {
15
+ const direct = normalizeNonEmpty(result?.error);
16
+ return direct ?? xfMessage(result, fallback);
17
+ }
18
+ function safePrincipal(value) {
19
+ const userId = normalizeNonEmpty(value?.userId);
20
+ if (!userId)
21
+ return null;
22
+ const normalizedStrings = (items) => {
23
+ if (!Array.isArray(items))
24
+ return undefined;
25
+ const values = items.map(normalizeNonEmpty).filter((item) => Boolean(item));
26
+ return values.length > 0 ? values : [];
27
+ };
28
+ return {
29
+ userId,
30
+ ...(normalizeNonEmpty(value?.email) ? { email: normalizeNonEmpty(value?.email) } : {}),
31
+ ...(normalizeNonEmpty(value?.fullName) ? { fullName: normalizeNonEmpty(value?.fullName) } : {}),
32
+ ...(value?.roles !== undefined ? { roles: normalizedStrings(value.roles) ?? [] } : {}),
33
+ ...(value?.permissions !== undefined ? { permissions: normalizedStrings(value.permissions) ?? [] } : {}),
34
+ ...(value?.capabilities !== undefined ? { capabilities: normalizedStrings(value.capabilities) ?? [] } : {}),
35
+ };
36
+ }
37
+ export async function verifyAuthSessionWithTokenPair(input) {
38
+ const accessToken = normalizeNonEmpty(input.accessToken);
39
+ const refreshToken = normalizeNonEmpty(input.refreshToken);
40
+ if (!accessToken || !refreshToken)
41
+ throw new Error('auth_token_pair_missing');
42
+ const api = createAopsApiClient({
43
+ baseUrl: input.apiBaseUrl,
44
+ defaultTimeoutMs: input.timeoutMs,
45
+ getAccessToken: () => accessToken,
46
+ getRefreshToken: () => refreshToken,
47
+ });
48
+ const response = await api.requestXfJson('/api/auth/me', { method: 'GET' }, { retry401: false, timeoutMs: input.timeoutMs });
49
+ const principal = safePrincipal(response.result?.data?.principal);
50
+ const providerVerified = isRemoteSessionAuthProvider(response.result?.data?.authProvider);
51
+ const authRequiredVerified = response.result?.data?.authRequired === true;
52
+ if (response.status !== 200 ||
53
+ response.result?.ok !== true ||
54
+ !principal?.userId ||
55
+ !providerVerified ||
56
+ !authRequiredVerified) {
57
+ throw new Error(`auth_session_verification_failed:${response.status}:${resultErrorCode(response.result, 'auth_principal_missing')}`);
58
+ }
59
+ const expectedUserId = normalizeNonEmpty(input.expectedUserId);
60
+ if (expectedUserId && principal.userId !== expectedUserId) {
61
+ throw new Error('auth_session_principal_mismatch');
62
+ }
63
+ return {
64
+ userId: principal.userId,
65
+ principal,
66
+ authProvider: 'remote-session',
67
+ authRequired: response.result.data?.authRequired,
68
+ };
69
+ }
70
+ function printLoginJson(params) {
71
+ console.log(JSON.stringify({
72
+ schemaVersion: 1,
73
+ action: 'auth.device-login',
74
+ status: params.status,
75
+ ok: params.ok,
76
+ target: params.target ?? null,
77
+ userId: params.userId ?? null,
78
+ stored: params.stored,
79
+ browserOpened: params.browserOpened ?? false,
80
+ }, null, 2));
81
+ }
82
+ export async function runAuthLogin(options = {}, dependencies = {}) {
83
+ const interactive = !options.yes && !options.json;
84
+ const browserFlow = options.browser === true;
85
+ const deviceLogin = dependencies.deviceLogin ?? runRemoteSessionDeviceLogin;
86
+ const openApprovedUrl = dependencies.openApprovedUrl ?? openApprovedHttpsUrl;
87
+ const apiBaseUrl = resolveCliApiBaseUrl(options.apiBaseUrl, options.target);
88
+ const apiState = await createCliApiClientFromOptions({
89
+ apiBaseUrl,
90
+ targetName: options.target,
91
+ timeoutMs: options.timeoutMs,
92
+ });
93
+ const runtime = await probeCliRuntimeMode(apiState, { timeoutMs: options.timeoutMs });
94
+ if (runtime.authRequired === false) {
95
+ if (options.json) {
96
+ printLoginJson({
97
+ status: 200,
98
+ ok: true,
99
+ target: apiState.targetName,
100
+ userId: runtime.principalUserId,
101
+ stored: false,
102
+ });
103
+ return;
104
+ }
105
+ if (interactive) {
106
+ banner('AOPS CLI Auth Login');
107
+ logInfo(`API: ${apiBaseUrl}`);
108
+ }
109
+ logSuccess('Trusted-local auth is active. Login is disabled and the local principal is already available.');
110
+ return;
111
+ }
112
+ if (runtime.authProvider === 'remote-session') {
113
+ if (!interactive && !browserFlow) {
114
+ if (options.json) {
115
+ printLoginJson({
116
+ status: 400,
117
+ ok: false,
118
+ target: apiState.targetName,
119
+ stored: false,
120
+ });
121
+ }
122
+ else {
123
+ logError('Remote-session login requires interactive approval or the explicit --browser flow.');
124
+ }
125
+ process.exitCode = 1;
126
+ return;
127
+ }
128
+ if (interactive) {
129
+ banner('AOPS CLI Remote Session');
130
+ logInfo(`API: ${apiBaseUrl}`);
131
+ }
132
+ let browserOpened = false;
133
+ let tokens;
134
+ try {
135
+ tokens = await deviceLogin({
136
+ apiBaseUrl,
137
+ timeoutMs: options.timeoutMs,
138
+ onChallenge: async ({ verificationUriComplete, userCode, expiresIn }) => {
139
+ if (browserFlow) {
140
+ await openApprovedUrl(verificationUriComplete, new URL(apiBaseUrl).origin);
141
+ browserOpened = true;
142
+ }
143
+ if (interactive) {
144
+ logInfo(browserFlow
145
+ ? 'The same-origin approval page was opened in your browser.'
146
+ : `Open: ${verificationUriComplete}`);
147
+ logInfo(`Confirm code: ${userCode}`);
148
+ logInfo(`Waiting for browser approval (expires in ${expiresIn} seconds)…`);
149
+ }
150
+ },
151
+ });
152
+ }
153
+ catch (error) {
154
+ const message = error instanceof Error ? error.message : String(error);
155
+ if (options.json) {
156
+ printLoginJson({
157
+ status: 401,
158
+ ok: false,
159
+ target: apiState.targetName,
160
+ stored: false,
161
+ browserOpened,
162
+ });
163
+ }
164
+ else {
165
+ logError(`Remote-session login failed: ${message}`);
166
+ }
167
+ process.exitCode = 1;
168
+ return;
169
+ }
170
+ let verified;
171
+ try {
172
+ verified = await verifyAuthSessionWithTokenPair({
173
+ apiBaseUrl,
174
+ accessToken: tokens.accessToken,
175
+ refreshToken: tokens.refreshToken,
176
+ expectedUserId: tokens.userId,
177
+ timeoutMs: options.timeoutMs,
178
+ });
179
+ await setApiTokensInConfig({
180
+ accessToken: tokens.accessToken,
181
+ refreshToken: tokens.refreshToken,
182
+ userId: verified.userId,
183
+ apiServer: apiBaseUrl,
184
+ targetName: apiState.targetName,
185
+ verifiedAuthProvider: 'remote-session',
186
+ });
187
+ }
188
+ catch (error) {
189
+ const message = error instanceof Error ? error.message : String(error);
190
+ if (options.json) {
191
+ printLoginJson({
192
+ status: 401,
193
+ ok: false,
194
+ target: apiState.targetName,
195
+ stored: false,
196
+ browserOpened,
197
+ });
198
+ }
199
+ else {
200
+ logError(`Remote-session tokens were not stored because verification failed: ${message}`);
201
+ }
202
+ process.exitCode = 1;
203
+ return;
204
+ }
205
+ if (options.json) {
206
+ printLoginJson({
207
+ status: 200,
208
+ ok: true,
209
+ target: apiState.targetName,
210
+ userId: verified.userId,
211
+ stored: true,
212
+ browserOpened,
213
+ });
214
+ }
215
+ else {
216
+ logSuccess(`Remote session ready (userId: ${verified.userId}).`);
217
+ }
218
+ return;
219
+ }
220
+ logError('The target does not expose the supported remote-session contract. ' +
221
+ 'Local password sessions were retired; configure a South remote-session target and authenticate again.');
222
+ process.exitCode = 1;
223
+ }
224
+ function selectedTarget(apiState) {
225
+ return apiState.targetName ? getApiTarget(apiState.targetName) : getActiveApiTarget();
226
+ }
227
+ export async function inspectAuthStatus(options = {}) {
228
+ const apiState = await createCliApiClientFromOptions({
229
+ targetName: options.target,
230
+ timeoutMs: options.timeoutMs,
231
+ });
232
+ const target = selectedTarget(apiState);
233
+ try {
234
+ const response = await apiState.client.requestXfJson('/api/auth/me', { method: 'GET' }, { timeoutMs: options.timeoutMs });
235
+ const principal = safePrincipal(response.result?.data?.principal);
236
+ const observedAuthProvider = response.result?.data?.authProvider === 'trusted-local' ||
237
+ isRemoteSessionAuthProvider(response.result?.data?.authProvider)
238
+ ? 'remote-session'
239
+ : undefined;
240
+ const observedAuthRequired = typeof response.result?.data?.authRequired === 'boolean'
241
+ ? response.result.data.authRequired
242
+ : undefined;
243
+ const expectedAuthRequired = isRemoteSessionAuthProvider(target?.authProvider);
244
+ const providerKnown = observedAuthProvider !== undefined;
245
+ const authRequiredKnown = observedAuthRequired !== undefined;
246
+ const providerMatches = providerKnown && (!target || observedAuthProvider === target.authProvider);
247
+ const authRequiredMatches = authRequiredKnown && (!target || observedAuthRequired === expectedAuthRequired);
248
+ const contractKnown = providerKnown && authRequiredKnown;
249
+ const contractMatches = providerMatches && authRequiredMatches;
250
+ const authenticated = response.status === 200 &&
251
+ response.result?.ok === true &&
252
+ Boolean(principal) &&
253
+ contractMatches;
254
+ const status = !contractKnown
255
+ ? 'auth-contract-unknown'
256
+ : !contractMatches
257
+ ? 'provider-mismatch'
258
+ : authenticated
259
+ ? 'authenticated'
260
+ : 'unauthenticated';
261
+ const reason = !contractKnown
262
+ ? 'auth_contract_missing'
263
+ : !providerMatches
264
+ ? 'target_auth_provider_mismatch'
265
+ : !authRequiredMatches
266
+ ? 'target_auth_required_mismatch'
267
+ : authenticated
268
+ ? null
269
+ : resultErrorCode(response.result, 'auth_required');
270
+ return {
271
+ status,
272
+ ok: authenticated,
273
+ target: target ?? null,
274
+ endpointSource: apiState.endpointSource,
275
+ apiBaseUrl: apiState.baseUrl,
276
+ authProvider: observedAuthProvider ?? null,
277
+ authRequired: observedAuthRequired ?? null,
278
+ credentialsStored: target?.hasCredentials ?? false,
279
+ authenticated,
280
+ principal,
281
+ httpStatus: response.status,
282
+ contractKnown,
283
+ providerMatches,
284
+ authRequiredMatches,
285
+ reason,
286
+ };
287
+ }
288
+ catch (error) {
289
+ return {
290
+ status: 'unavailable',
291
+ ok: false,
292
+ target: target ?? null,
293
+ endpointSource: apiState.endpointSource,
294
+ apiBaseUrl: apiState.baseUrl,
295
+ authProvider: target?.authProvider ?? null,
296
+ authRequired: isRemoteSessionAuthProvider(target?.authProvider),
297
+ credentialsStored: target?.hasCredentials ?? false,
298
+ authenticated: null,
299
+ principal: null,
300
+ httpStatus: null,
301
+ reason: error instanceof Error ? error.message : String(error),
302
+ };
303
+ }
304
+ }
305
+ export async function runAuthStatus(options = {}) {
306
+ const result = await inspectAuthStatus(options);
307
+ if (options.json)
308
+ console.log(JSON.stringify(result, null, 2));
309
+ else if (result.ok) {
310
+ const target = result.target;
311
+ const principal = result.principal;
312
+ logSuccess(`Authenticated${target ? ` to ${target.name}` : ''} as ${principal?.email ?? principal?.userId ?? 'current principal'}.`);
313
+ logInfo(`API: ${String(result.apiBaseUrl)}`);
314
+ }
315
+ else {
316
+ logError(`Auth status: ${String(result.status)} (${String(result.reason ?? 'auth_required')})`);
317
+ }
318
+ if (!result.ok)
319
+ process.exitCode = 1;
320
+ }
321
+ export async function runAuthLogout(options = {}) {
322
+ const target = options.target ? getApiTarget(options.target) : getActiveApiTarget();
323
+ if (!target) {
324
+ logError('No selected target. Add and select a target before logging out.');
325
+ process.exitCode = 1;
326
+ return;
327
+ }
328
+ if (!target.hasCredentials) {
329
+ const result = { ok: true, target: target.name, remote: 'not-attempted', cleared: false };
330
+ if (options.json)
331
+ console.log(JSON.stringify(result, null, 2));
332
+ else
333
+ logInfo('No stored auth tokens were present for the selected target.');
334
+ return;
335
+ }
336
+ if (!options.localOnly && isRemoteSessionAuthProvider(target.authProvider)) {
337
+ let apiState;
338
+ try {
339
+ apiState = await createCliApiClientFromOptions({
340
+ targetName: target.name,
341
+ timeoutMs: options.timeoutMs,
342
+ });
343
+ }
344
+ catch (error) {
345
+ const reason = error instanceof Error ? error.message : String(error);
346
+ logError(`Remote logout could not load the stored session; local credentials were preserved. ${reason}`);
347
+ logInfo('Use `aops auth logout --local-only` only if you intentionally want offline local cleanup.');
348
+ process.exitCode = 1;
349
+ return;
350
+ }
351
+ let response;
352
+ try {
353
+ response = await apiState.client.requestXfJson('/api/auth/logout', { method: 'POST', body: {} }, { timeoutMs: options.timeoutMs });
354
+ }
355
+ catch (error) {
356
+ const reason = error instanceof Error ? error.message : String(error);
357
+ logError(`Remote logout failed; local credentials were preserved. ${reason}`);
358
+ logInfo('Use `aops auth logout --local-only` only if you intentionally want offline local cleanup.');
359
+ process.exitCode = 1;
360
+ return;
361
+ }
362
+ if (response.status !== 200 ||
363
+ response.result?.ok !== true ||
364
+ response.result.data?.loggedOut !== true) {
365
+ const reason = resultErrorCode(response.result, 'logout_failed');
366
+ logError(`Remote logout failed (${response.status}); local credentials were preserved. ${reason}`);
367
+ logInfo('Use `aops auth logout --local-only` only if you intentionally want offline local cleanup.');
368
+ process.exitCode = 1;
369
+ return;
370
+ }
371
+ }
372
+ const cleared = clearApiTokensInConfig(target.name);
373
+ if (options.json) {
374
+ console.log(JSON.stringify({
375
+ ok: true,
376
+ target: target.name,
377
+ remote: options.localOnly
378
+ ? 'skipped-local-only'
379
+ : isRemoteSessionAuthProvider(target.authProvider) ? 'revoked' : 'not-applicable',
380
+ cleared,
381
+ }, null, 2));
382
+ return;
383
+ }
384
+ if (cleared) {
385
+ logSuccess(options.localOnly
386
+ ? 'Local auth tokens cleared without contacting the server.'
387
+ : 'Remote session revoked and local auth tokens cleared.');
388
+ }
389
+ else
390
+ logInfo('No stored auth tokens were present for the selected target.');
391
+ }
392
+ export function makeAuthLoginCommand() {
393
+ const cmd = new Command('login').description('Login and store API tokens locally');
394
+ cmd
395
+ .option('--target <name>', 'Named target (must match --api-base-url when both are supplied)')
396
+ .option('--api-base-url <url>', 'API base URL (default: AOPS_API_BASE_URL or http://localhost:5900)')
397
+ .option('--email <email>', 'User email')
398
+ .option('--timeout-ms <ms>', 'Request timeout in milliseconds', (v) => Number.parseInt(String(v), 10))
399
+ .option('--yes', 'Non-interactive (fail if required args are missing)')
400
+ .option('--browser', 'Open the same-origin approval page and wait headlessly')
401
+ .option('--json', 'Output JSON only')
402
+ .action(async (options) => {
403
+ await runAuthLogin(options);
404
+ });
405
+ return cmd;
406
+ }
407
+ export function makeAuthLogoutCommand() {
408
+ const cmd = new Command('logout').description('Revoke the remote session, then clear stored API tokens');
409
+ cmd.option('--target <name>', 'Named target; default is the active target')
410
+ .option('--timeout-ms <ms>', 'Request timeout in milliseconds', (v) => Number.parseInt(String(v), 10))
411
+ .option('--local-only', 'Skip remote revocation and explicitly clear only local credentials')
412
+ .option('--json', 'Output JSON only').action(async (options) => {
413
+ await runAuthLogout(options);
414
+ });
415
+ return cmd;
416
+ }
417
+ export function makeAuthStatusCommand() {
418
+ const cmd = new Command('status').description('Verify the selected target and current authenticated principal');
419
+ cmd.option('--target <name>', 'Named target; default is the active target')
420
+ .option('--timeout-ms <ms>', 'Request timeout in milliseconds', (v) => Number.parseInt(String(v), 10))
421
+ .option('--json', 'Output JSON only')
422
+ .action(async (options) => {
423
+ await runAuthStatus(options);
424
+ });
425
+ return cmd;
426
+ }