@modelprofile.com/authswitch 8.1.0 → 9.0.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 (73) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/authority-contract.d.ts +73 -0
  3. package/dist_ts/authority-contract.js +55 -2
  4. package/dist_ts/authority-import-contract.d.ts +8 -4
  5. package/dist_ts/authority-import-contract.js +12 -13
  6. package/dist_ts/authority-paths.d.ts +37 -0
  7. package/dist_ts/authority-paths.js +46 -0
  8. package/dist_ts/authority-runtime-contract.d.ts +11 -1
  9. package/dist_ts/classes.authoritybroker.d.ts +9 -0
  10. package/dist_ts/classes.authoritybroker.js +72 -11
  11. package/dist_ts/classes.authorityclient.d.ts +17 -11
  12. package/dist_ts/classes.authorityclient.js +47 -22
  13. package/dist_ts/classes.authoritydaemon.d.ts +22 -3
  14. package/dist_ts/classes.authoritydaemon.js +106 -59
  15. package/dist_ts/classes.authoritydatabase.d.ts +9 -1
  16. package/dist_ts/classes.authoritydatabase.js +39 -13
  17. package/dist_ts/classes.authorityimport.d.ts +33 -6
  18. package/dist_ts/classes.authorityimport.js +102 -25
  19. package/dist_ts/classes.authoritymodels.d.ts +4 -1
  20. package/dist_ts/classes.authoritymodels.js +15 -3
  21. package/dist_ts/classes.authoritypreuse.js +8 -3
  22. package/dist_ts/classes.authorityservice.d.ts +20 -11
  23. package/dist_ts/classes.authorityservice.js +37 -23
  24. package/dist_ts/classes.claudeauthority.js +19 -11
  25. package/dist_ts/classes.claudenative.d.ts +63 -3
  26. package/dist_ts/classes.claudenative.js +68 -8
  27. package/dist_ts/classes.cli.d.ts +10 -2
  28. package/dist_ts/classes.cli.js +12 -4
  29. package/dist_ts/classes.codexmanaged.d.ts +8 -0
  30. package/dist_ts/classes.codexmanaged.js +19 -12
  31. package/dist_ts/classes.legacyfence.d.ts +53 -0
  32. package/dist_ts/classes.legacyfence.js +189 -0
  33. package/dist_ts/classes.operations.d.ts +15 -3
  34. package/dist_ts/classes.operations.js +22 -4
  35. package/dist_ts/classes.service.d.ts +21 -2
  36. package/dist_ts/classes.service.js +35 -8
  37. package/dist_ts/classes.tui.d.ts +2 -1
  38. package/dist_ts/classes.tui.js +3 -2
  39. package/dist_ts/ts_migration/0003_claude_handoff_proof.d.ts +13 -0
  40. package/dist_ts/ts_migration/0003_claude_handoff_proof.js +20 -0
  41. package/dist_ts/ts_migration/index.js +3 -1
  42. package/dist_ts/ts_migration/legacysources/authswitchstores.js +5 -2
  43. package/dist_ts/ts_migration/legacysources/nativestores.js +15 -8
  44. package/dist_ts/ts_migration/legacysources/shared.d.ts +3 -2
  45. package/dist_ts/ts_migration/legacysources/shared.js +3 -5
  46. package/package.json +11 -7
  47. package/readme.md +188 -13
  48. package/ts/00_commitinfo_data.ts +1 -1
  49. package/ts/authority-contract.ts +125 -0
  50. package/ts/authority-import-contract.ts +12 -11
  51. package/ts/authority-paths.ts +69 -0
  52. package/ts/authority-runtime-contract.ts +11 -1
  53. package/ts/classes.authoritybroker.ts +70 -11
  54. package/ts/classes.authorityclient.ts +59 -21
  55. package/ts/classes.authoritydaemon.ts +116 -60
  56. package/ts/classes.authoritydatabase.ts +39 -12
  57. package/ts/classes.authorityimport.ts +131 -26
  58. package/ts/classes.authoritymodels.ts +11 -2
  59. package/ts/classes.authoritypreuse.ts +7 -1
  60. package/ts/classes.authorityservice.ts +42 -30
  61. package/ts/classes.claudeauthority.ts +22 -10
  62. package/ts/classes.claudenative.ts +104 -10
  63. package/ts/classes.cli.ts +14 -3
  64. package/ts/classes.codexmanaged.ts +18 -7
  65. package/ts/classes.legacyfence.ts +219 -0
  66. package/ts/classes.operations.ts +22 -3
  67. package/ts/classes.service.ts +45 -8
  68. package/ts/classes.tui.ts +3 -1
  69. package/ts/ts_migration/0003_claude_handoff_proof.ts +19 -0
  70. package/ts/ts_migration/index.ts +2 -0
  71. package/ts/ts_migration/legacysources/authswitchstores.ts +4 -1
  72. package/ts/ts_migration/legacysources/nativestores.ts +15 -7
  73. package/ts/ts_migration/legacysources/shared.ts +4 -6
@@ -0,0 +1,19 @@
1
+ import * as plugins from '../plugins.js';
2
+
3
+ /**
4
+ * Gives every Claude native handoff journalled before 8.2.0 its `proofFailure` field.
5
+ *
6
+ * The handoff record is exact-persistence: a document with a key the shape does not declare, or without one
7
+ * it does, is refused rather than read loosely. A published release therefore cannot gain a field by
8
+ * declaring it -- the rows that already exist have to carry it too, and this is where that happens.
9
+ *
10
+ * Only the absence is filled, with `null`: no release before this one could say which condition of the
11
+ * native-login proof failed, so there is nothing to infer and nothing to overwrite. A row that already has
12
+ * the field is left exactly as it is, which is what makes an interrupted run safe to repeat.
13
+ */
14
+ export const addClaudeHandoffProofFailure = async (db: plugins.nosqldb.SmartdataDb): Promise<void> => {
15
+ const handoffs = db.mongoDb.collection('authswitch_authority_claude_handoffs');
16
+ const missing = { proofFailure: { $exists: false } };
17
+ if (!await handoffs.findOne(missing)) return;
18
+ await handoffs.updateMany(missing, { $set: { proofFailure: null } });
19
+ };
@@ -1,6 +1,7 @@
1
1
  import * as plugins from '../plugins.js';
2
2
  import { migrateAuthorityMeta } from './0001_authority_meta.js';
3
3
  import { removeAuthorityBackupRecords } from './0002_remove_backup_records.js';
4
+ import { addClaudeHandoffProofFailure } from './0003_claude_handoff_proof.js';
4
5
 
5
6
  /**
6
7
  * Runs on every start of an existing store, in this fixed order. Each module recognises the published shapes it
@@ -9,4 +10,5 @@ import { removeAuthorityBackupRecords } from './0002_remove_backup_records.js';
9
10
  export const runAuthorityMigrations = async (db: plugins.nosqldb.SmartdataDb): Promise<void> => {
10
11
  await migrateAuthorityMeta(db);
11
12
  await removeAuthorityBackupRecords(db);
13
+ await addClaudeHandoffProofFailure(db);
12
14
  };
@@ -175,7 +175,10 @@ export const readAuthSwitchStores = (locations: ILegacySourceLocations): ILegacy
175
175
  try { entries = plugins.fs.readdirSync(locations.authSwitchHome, { withFileTypes: true }); }
176
176
  catch (error) {
177
177
  if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { sources, problems };
178
- return { sources, problems: [`${locations.authSwitchHome} could not be listed`] };
178
+ // The store is named by what it is, never by where it is: this report goes to a CLI and to a backend,
179
+ // and the location of a credential store is not something either of them may be told. Everything
180
+ // below names an entry inside the store instead, which is what the owner needs to find it.
181
+ return { sources, problems: ['the authswitch store could not be listed'] };
179
182
  }
180
183
  for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
181
184
  const path = plugins.path.join(locations.authSwitchHome, entry.name);
@@ -1,5 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { CodexHome } from '../../classes.codexhome.js';
3
+ import { claudeNativeHomeId } from '../../classes.claudenative.js';
3
4
  import { accessProofAvailable, accessTokenExpiry, claudeSourceIdentity, EXPIRED_ACCESS_PROBLEM, legacyHash,
4
5
  legacySourceDigest, legacySourceId, openAiSourceIdentity, openCodeOpenAiIdentity, parseLegacyJson,
5
6
  readLegacyFile, type ILegacySourceFile, type ILegacySourceLocations, type ILegacySourceRecord,
@@ -48,9 +49,9 @@ const countCodexEnrollments = (codexHome: CodexHome): { count: number | null; pr
48
49
  } finally { database?.close(); }
49
50
  };
50
51
 
51
- const nativeRecord = (sourceKind: TAuthSwitchImportSourceKind, path: string,
52
+ /** `sourcePathHash` is passed in, because a Claude home's id belongs to the module that owns that home. */
53
+ const nativeRecord = (sourceKind: TAuthSwitchImportSourceKind, path: string, sourcePathHash: string,
52
54
  files: readonly ILegacySourceFile[], problems: string[]): ILegacySourceRecord => {
53
- const sourcePathHash = legacyHash(path);
54
55
  return {
55
56
  id: legacySourceId(sourceKind, sourcePathHash),
56
57
  sourceKind,
@@ -86,7 +87,7 @@ const readCodexNative = (locations: ILegacySourceLocations, now: number): ILegac
86
87
  if (read.problem === null && read.file === null) return null;
87
88
  const files = read.file ? [read.file] : [];
88
89
  const problems = read.problem ? [read.problem] : [];
89
- const record = nativeRecord('codex_native', path, files, problems);
90
+ const record = nativeRecord('codex_native', path, legacyHash(path), files, problems);
90
91
  const enrollments = countCodexEnrollments(new CodexHome(locations.codexHome));
91
92
  record.enrollmentCount = enrollments.count;
92
93
  if (enrollments.problem) problems.push(enrollments.problem);
@@ -114,12 +115,19 @@ const readCodexNative = (locations: ILegacySourceLocations, now: number): ILegac
114
115
  };
115
116
 
116
117
  const readClaudeNative = (locations: ILegacySourceLocations, now: number): ILegacySourceRecord | null => {
117
- const credentials = readLegacyFile(locations.claudeCredentialFile, '.credentials.json');
118
- const config = readLegacyFile(locations.claudeConfigFile, '.claude.json');
118
+ if (locations.claudeHome.kind === 'unusable') {
119
+ // Reported, never skipped and never read from a guessed location: the owner sees the defect and fixes it.
120
+ const { declaredCredentialFile, problem } = locations.claudeHome.defect;
121
+ return nativeRecord('claude_native', declaredCredentialFile,
122
+ claudeNativeHomeId(declaredCredentialFile), [], [problem]);
123
+ }
124
+ const { credentialFile, configFile, homeId } = locations.claudeHome.home;
125
+ const credentials = readLegacyFile(credentialFile, '.credentials.json');
126
+ const config = readLegacyFile(configFile, '.claude.json');
119
127
  if (credentials.problem === null && credentials.file === null) return null;
120
128
  const files = [credentials.file, config.file].filter((file): file is ILegacySourceFile => file !== null);
121
129
  const problems = [credentials.problem, config.problem].filter((problem): problem is string => problem !== null);
122
- const record = nativeRecord('claude_native', locations.claudeCredentialFile, files, problems);
130
+ const record = nativeRecord('claude_native', credentialFile, homeId, files, problems);
123
131
  if (!credentials.file) return record;
124
132
  const credentialData = parseLegacyJson(credentials.file.raw);
125
133
  const configData = config.file === null ? null : parseLegacyJson(config.file.raw);
@@ -157,7 +165,7 @@ const readOpenCodeNative = (locations: ILegacySourceLocations, now: number): ILe
157
165
  if (!value || typeof value !== 'object' || Array.isArray(value)) continue;
158
166
  const entry = value as Record<string, unknown>;
159
167
  const path = `${locations.openCodeAuthFile}#${provider}`;
160
- const record = nativeRecord('opencode_native', path,
168
+ const record = nativeRecord('opencode_native', path, legacyHash(path),
161
169
  [{ name: provider, raw: JSON.stringify(entry) }], []);
162
170
  record.label = provider;
163
171
  if (entry.type !== 'oauth') {
@@ -1,5 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { readCredentialRaw } from '../../classes.credentialstore.js';
3
+ import { resolveClaudeNativeHome, type TClaudeNativeHomeResolution } from '../../classes.claudenative.js';
3
4
  import type { TAuthSwitchImportProof, TAuthSwitchImportSourceKind,
4
5
  TAuthSwitchImportTreatment } from '../../authority-import-contract.js';
5
6
 
@@ -50,21 +51,18 @@ export interface ILegacySourceReport {
50
51
  export interface ILegacySourceLocations {
51
52
  authSwitchHome: string;
52
53
  codexHome: string;
53
- claudeCredentialFile: string;
54
- claudeConfigFile: string;
54
+ /** Claude's own paths come from the module that owns them, so the daemon and this reader cannot drift. */
55
+ claudeHome: TClaudeNativeHomeResolution;
55
56
  openCodeAuthFile: string;
56
57
  }
57
58
 
58
59
  export const resolveLegacySourceLocations = (env: NodeJS.ProcessEnv,
59
60
  homeDirectory: string): ILegacySourceLocations => {
60
- const claudeConfigDirectory = env.CLAUDE_CONFIG_DIR || plugins.path.join(homeDirectory, '.claude');
61
61
  const dataHome = env.XDG_DATA_HOME || plugins.path.join(homeDirectory, '.local', 'share');
62
62
  return {
63
63
  authSwitchHome: env.AUTHSWITCH_HOME || plugins.path.join(homeDirectory, '.authswitch'),
64
64
  codexHome: env.CODEX_HOME || plugins.path.join(homeDirectory, '.codex'),
65
- claudeCredentialFile: plugins.path.join(claudeConfigDirectory, '.credentials.json'),
66
- // Claude Code keeps its account metadata beside the home directory, not inside a custom config directory.
67
- claudeConfigFile: plugins.path.join(env.CLAUDE_CONFIG_DIR || homeDirectory, '.claude.json'),
65
+ claudeHome: resolveClaudeNativeHome(env, homeDirectory),
68
66
  openCodeAuthFile: plugins.path.join(dataHome, 'opencode', 'auth.json'),
69
67
  };
70
68
  };