citeclaw 2.0.8 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,18 +37,7 @@ npx citeclaw citoid bibtex "https://aclanthology.org/2023.emnlp-main.398/"
37
37
  npx citeclaw cite mediawiki "https://arxiv.org/abs/1706.03762"
38
38
  ```
39
39
 
40
- Fresh npm installs can run normal citation commands directly. `CiteClaw` will bootstrap Zotero and build a local translator runtime automatically from Zotero's bundled translator set.
41
-
42
- Runtime state is persisted in a user-level cache directory by default:
43
-
44
- - Linux: `~/.cache/citeclaw`
45
- - macOS: `~/Library/Caches/citeclaw`
46
- - Windows: `%LOCALAPPDATA%\\citeclaw`
47
-
48
- Override it with `CITECLAW_HOME=/path/to/runtime`.
49
-
50
- By default, the Zotero translation server checkout is also stored under this runtime root, at `runtime/zotero`.
51
- Override it with `ZOTERO_DIR=/path/to/zotero`.
40
+ Fresh npm installs can run normal citation commands directly. `CiteClaw` will bootstrap Zotero and build a local translator runtime automatically from the bundled Zotero translator set.
52
41
 
53
42
  ## Local Service
54
43
 
@@ -176,6 +165,23 @@ Current package metadata and entrypoints live in:
176
165
  - [package.json](/mnt/e/botcite/package.json)
177
166
  - [scripts/citeclaw.js](/mnt/e/botcite/scripts/citeclaw.js)
178
167
 
168
+ ## npm Publishing
169
+
170
+ This repo includes a GitHub Actions workflow for npm publishing:
171
+
172
+ - workflow file: `.github/workflows/npm-publish.yml`
173
+ - trigger: push a tag like `v2.0.8` that matches `package.json`'s `version`
174
+ - required secret: add `NPM_TOKEN` in GitHub repository settings with publish permission for the `citeclaw` package
175
+
176
+ Typical release flow:
177
+
178
+ ```bash
179
+ npm version patch
180
+ git push origin master --follow-tags
181
+ ```
182
+
183
+ The workflow installs dependencies with `npm ci`, checks that the tag matches `package.json`, validates the tarball with `npm pack --dry-run`, and then runs `npm publish --access public --provenance`.
184
+
179
185
  ## Notes
180
186
 
181
187
  - The npm package name and primary CLI are `citeclaw`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "citeclaw",
3
- "version": "2.0.8",
3
+ "version": "2.0.9",
4
4
  "description": "Citation and bibliography toolkit for DOI, URL, arXiv, PDF, Zotero, and MCP workflows.",
5
5
  "homepage": "https://github.com/trotsky1997/citeclaw",
6
6
  "license": "Apache-2.0",
@@ -19,31 +19,13 @@ const yaml = require( 'js-yaml' );
19
19
  const citoidApp = require( '../app.js' );
20
20
 
21
21
  const rootDir = path.resolve( __dirname, '..' );
22
+ const zoteroDir = process.env.ZOTERO_DIR || path.join( rootDir, 'vendor', 'zotero' );
22
23
  const cnTranslatorsDir = process.env.CN_TRANSLATORS_DIR || path.join( rootDir, 'vendor', 'translators_CN' );
23
24
  const officialTranslatorsDir = process.env.OFFICIAL_TRANSLATORS_DIR ||
24
25
  path.join( rootDir, 'vendor', 'translators-official' );
25
26
  const vendoredStylesDir = path.join( rootDir, 'vendor', 'styles' );
26
27
  const vendoredOfficialStylesDir = path.join( rootDir, 'vendor', 'styles-official' );
27
-
28
- function defaultRuntimeRoot() {
29
- if ( process.env.CITECLAW_HOME ) {
30
- return path.resolve( process.env.CITECLAW_HOME );
31
- }
32
- if ( process.env.XDG_CACHE_HOME ) {
33
- return path.join( process.env.XDG_CACHE_HOME, 'citeclaw' );
34
- }
35
- if ( process.platform === 'win32' ) {
36
- const base = process.env.LOCALAPPDATA || process.env.APPDATA || path.join( os.homedir(), 'AppData', 'Local' );
37
- return path.join( base, 'citeclaw' );
38
- }
39
- if ( process.platform === 'darwin' ) {
40
- return path.join( os.homedir(), 'Library', 'Caches', 'citeclaw' );
41
- }
42
- return path.join( os.homedir(), '.cache', 'citeclaw' );
43
- }
44
-
45
- const localDir = defaultRuntimeRoot();
46
- const zoteroDir = process.env.ZOTERO_DIR || path.join( localDir, 'runtime', 'zotero' );
28
+ const localDir = path.join( rootDir, '.local' );
47
29
  const logDir = path.join( localDir, 'logs' );
48
30
  const stateDir = path.join( localDir, 'state' );
49
31
  const localTranslatorSourcesDir = path.join( localDir, 'translator-sources' );
@@ -183,7 +165,7 @@ function usageZotero( subAction = '' ) {
183
165
  console.error( ' citeclaw zotero login --library-type groups --library-id <group-id> --api-key <key>' );
184
166
  console.error( 'notes:' );
185
167
  console.error( ' - personal library defaults to library-type users' );
186
- console.error( ` - credentials are saved to ${ zoteroAuthPath }` );
168
+ console.error( ' - credentials are saved to .local/state/zotero-auth.json' );
187
169
  console.error( ' - API key can be created at https://www.zotero.org/settings/keys' );
188
170
  return;
189
171
  }
@@ -191,7 +173,7 @@ function usageZotero( subAction = '' ) {
191
173
  console.error( 'usage:' );
192
174
  console.error( ' citeclaw zotero logout' );
193
175
  console.error( 'notes:' );
194
- console.error( ` - removes local credentials from ${ zoteroAuthPath }` );
176
+ console.error( ' - removes local credentials from .local/state/zotero-auth.json' );
195
177
  return;
196
178
  }
197
179
  if ( action === 'whoami' ) {
@@ -487,6 +469,46 @@ function runCommandOrThrow( command, args, cwd ) {
487
469
  return result;
488
470
  }
489
471
 
472
+ function styleLocaleUrls( locale ) {
473
+ const fileName = `locales-${ locale }.xml`;
474
+ return [
475
+ `https://raw.githubusercontent.com/citation-style-language/locales/master/${ fileName }`,
476
+ `https://github.com/citation-style-language/locales/raw/master/${ fileName }`,
477
+ `https://cdn.jsdelivr.net/gh/citation-style-language/locales@master/${ fileName }`
478
+ ];
479
+ }
480
+
481
+ function downloadFileWithCurlFallbacks( urls, dest, label, runner ) {
482
+ const runCommand = runner || runCommandOrThrow;
483
+ const candidates = Array.isArray( urls ) ? urls : [ urls ];
484
+ const targetLabel = label || path.basename( dest );
485
+ const tempDest = `${ dest }.tmp`;
486
+ const failures = [];
487
+ fs.mkdirSync( path.dirname( dest ), { recursive: true } );
488
+
489
+ for ( const url of candidates ) {
490
+ try {
491
+ fs.rmSync( tempDest, { force: true } );
492
+ runCommand( 'curl', [
493
+ '-fsSL',
494
+ '--retry', '3',
495
+ '--retry-delay', '1',
496
+ '--retry-all-errors',
497
+ url,
498
+ '-o', tempDest
499
+ ] );
500
+ fs.rmSync( dest, { force: true } );
501
+ fs.renameSync( tempDest, dest );
502
+ return;
503
+ } catch ( error ) {
504
+ failures.push( `${ url }: ${ error.message }` );
505
+ }
506
+ }
507
+
508
+ fs.rmSync( tempDest, { force: true } );
509
+ throw new Error( `failed to download ${ targetLabel }: ${ failures.join( ' | ' ) }` );
510
+ }
511
+
490
512
  function resolveInstallCommand() {
491
513
  if ( commandExists( 'npm' ) ) {
492
514
  return { command: 'npm', args: [ 'install' ] };
@@ -657,7 +679,7 @@ function bootstrapLocalEnvironment() {
657
679
  runCommandOrThrow( installer.command, installer.args, rootDir );
658
680
  }
659
681
  if ( !fileExists( path.join( zoteroDir, 'modules', 'translators' ) ) ) {
660
- throw new Error( `missing zotero translator contents under ${ path.join( zoteroDir, 'modules', 'translators' ) }` );
682
+ throw new Error( 'missing vendored zotero contents under vendor/zotero/modules/translators' );
661
683
  }
662
684
  if ( !fileExists( path.join( zoteroDir, 'node_modules' ) ) ) {
663
685
  runCommandOrThrow( installer.command, installer.args, zoteroDir );
@@ -1522,19 +1544,24 @@ async function runCitationFromPdf( pdfPath, options ) {
1522
1544
  } );
1523
1545
  }
1524
1546
 
1525
- function syncStyles( options ) {
1547
+ function syncStyles( options, dependencies ) {
1548
+ const styleOptions = options || {};
1549
+ const hooks = dependencies || {};
1550
+ const downloadFile = hooks.downloadFile || downloadFileWithCurlFallbacks;
1551
+ const downloadUsesCurl = downloadFile === downloadFileWithCurlFallbacks;
1552
+
1526
1553
  ensureDirs();
1527
1554
  fs.mkdirSync( cslDir, { recursive: true } );
1528
1555
  fs.mkdirSync( localeDir, { recursive: true } );
1529
- const configuredSourceDirs = options.repo ?
1556
+ const configuredSourceDirs = styleOptions.repo ?
1530
1557
  [
1531
- path.isAbsolute( options.repo || '' ) ?
1532
- options.repo :
1533
- path.join( rootDir, options.repo || 'vendor/styles' )
1558
+ path.isAbsolute( styleOptions.repo || '' ) ?
1559
+ styleOptions.repo :
1560
+ path.join( rootDir, styleOptions.repo || 'vendor/styles' )
1534
1561
  ] :
1535
1562
  ensureDefaultStyleSources();
1536
1563
  const sourceDirs = configuredSourceDirs.filter( ( sourceDir ) => fileExists( sourceDir ) );
1537
- if ( options.repo && !sourceDirs.length ) {
1564
+ if ( styleOptions.repo && !sourceDirs.length ) {
1538
1565
  throw new Error( `styles source not found: ${ configuredSourceDirs.join( ', ' ) }` );
1539
1566
  }
1540
1567
 
@@ -1550,13 +1577,12 @@ function syncStyles( options ) {
1550
1577
  } );
1551
1578
 
1552
1579
  const localeTargets = [ 'en-US', 'zh-CN' ];
1553
- if ( !commandExists( 'curl' ) ) {
1580
+ if ( downloadUsesCurl && !commandExists( 'curl' ) ) {
1554
1581
  throw new Error( 'curl is required for styles sync' );
1555
1582
  }
1556
1583
  for ( const locale of localeTargets ) {
1557
- const localeUrl = `https://raw.githubusercontent.com/citation-style-language/locales/master/locales-${ locale }.xml`;
1558
1584
  const dest = path.join( localeDir, `locales-${ locale }.xml` );
1559
- runCommandOrThrow( 'curl', [ '-fsSL', localeUrl, '-o', dest ] );
1585
+ downloadFile( styleLocaleUrls( locale ), dest, `locale ${ locale }` );
1560
1586
  }
1561
1587
 
1562
1588
  process.stdout.write( `styles synced to ${ cslDir } from ${ sourceDirs.join( ', ' ) } (copied ${ copiedCount } files)\n` );
@@ -4997,9 +5023,11 @@ function main() {
4997
5023
  usage();
4998
5024
  process.exit( 1 );
4999
5025
  }
5000
- syncStyles( parsed ).catch( ( error ) => {
5026
+ try {
5027
+ syncStyles( parsed );
5028
+ } catch ( error ) {
5001
5029
  handleCommandError( error, parsed, 'styles', 'sync' );
5002
- } );
5030
+ }
5003
5031
  return;
5004
5032
  }
5005
5033
 
@@ -5158,12 +5186,14 @@ function main() {
5158
5186
  }
5159
5187
 
5160
5188
  module.exports = {
5189
+ downloadFileWithCurlFallbacks,
5161
5190
  detectPdfIdentifierCandidates,
5162
5191
  extractBestDoiCandidate,
5163
5192
  extractPdfCandidates,
5164
5193
  main,
5165
5194
  normalizeArxivId,
5166
5195
  normalizeDoi,
5196
+ syncStyles,
5167
5197
  shouldAttemptPdfOcr
5168
5198
  };
5169
5199