citeclaw 2.0.8 → 2.0.10
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 +18 -12
- package/package.json +1 -1
- package/scripts/botcite.js +85 -36
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
|
|
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
package/scripts/botcite.js
CHANGED
|
@@ -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(
|
|
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(
|
|
176
|
+
console.error( ' - removes local credentials from .local/state/zotero-auth.json' );
|
|
195
177
|
return;
|
|
196
178
|
}
|
|
197
179
|
if ( action === 'whoami' ) {
|
|
@@ -361,6 +343,24 @@ function fileExists( filePath ) {
|
|
|
361
343
|
}
|
|
362
344
|
}
|
|
363
345
|
|
|
346
|
+
function normalizeCommandForSpawn( command, platform ) {
|
|
347
|
+
const activePlatform = platform || process.platform;
|
|
348
|
+
if ( activePlatform !== 'win32' ) {
|
|
349
|
+
return command;
|
|
350
|
+
}
|
|
351
|
+
if ( /[\\/]/.test( command ) || /\.[^\\/]+$/.test( command ) ) {
|
|
352
|
+
return command;
|
|
353
|
+
}
|
|
354
|
+
const windowsCommands = {
|
|
355
|
+
npm: 'npm.cmd',
|
|
356
|
+
npx: 'npx.cmd',
|
|
357
|
+
git: 'git.exe',
|
|
358
|
+
curl: 'curl.exe',
|
|
359
|
+
bun: 'bun.exe'
|
|
360
|
+
};
|
|
361
|
+
return windowsCommands[ command ] || command;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
364
|
function commandExists( command ) {
|
|
365
365
|
const args = process.platform === 'win32' ? [ '/c', 'where', command ] : [ '-lc', `command -v ${ command }` ];
|
|
366
366
|
const found = spawnSync( process.platform === 'win32' ? 'cmd' : 'bash', args, {
|
|
@@ -457,7 +457,7 @@ function jsonOut( value ) {
|
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
function runCommandText( command, args ) {
|
|
460
|
-
const result = spawnSync( command, args, {
|
|
460
|
+
const result = spawnSync( normalizeCommandForSpawn( command ), args, {
|
|
461
461
|
stdio: 'pipe',
|
|
462
462
|
encoding: 'utf8'
|
|
463
463
|
} );
|
|
@@ -472,7 +472,7 @@ function runCommandText( command, args ) {
|
|
|
472
472
|
}
|
|
473
473
|
|
|
474
474
|
function runCommandOrThrow( command, args, cwd ) {
|
|
475
|
-
const result = spawnSync( command, args, {
|
|
475
|
+
const result = spawnSync( normalizeCommandForSpawn( command ), args, {
|
|
476
476
|
cwd: cwd || rootDir,
|
|
477
477
|
stdio: 'pipe',
|
|
478
478
|
encoding: 'utf8'
|
|
@@ -487,6 +487,46 @@ function runCommandOrThrow( command, args, cwd ) {
|
|
|
487
487
|
return result;
|
|
488
488
|
}
|
|
489
489
|
|
|
490
|
+
function styleLocaleUrls( locale ) {
|
|
491
|
+
const fileName = `locales-${ locale }.xml`;
|
|
492
|
+
return [
|
|
493
|
+
`https://raw.githubusercontent.com/citation-style-language/locales/master/${ fileName }`,
|
|
494
|
+
`https://github.com/citation-style-language/locales/raw/master/${ fileName }`,
|
|
495
|
+
`https://cdn.jsdelivr.net/gh/citation-style-language/locales@master/${ fileName }`
|
|
496
|
+
];
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function downloadFileWithCurlFallbacks( urls, dest, label, runner ) {
|
|
500
|
+
const runCommand = runner || runCommandOrThrow;
|
|
501
|
+
const candidates = Array.isArray( urls ) ? urls : [ urls ];
|
|
502
|
+
const targetLabel = label || path.basename( dest );
|
|
503
|
+
const tempDest = `${ dest }.tmp`;
|
|
504
|
+
const failures = [];
|
|
505
|
+
fs.mkdirSync( path.dirname( dest ), { recursive: true } );
|
|
506
|
+
|
|
507
|
+
for ( const url of candidates ) {
|
|
508
|
+
try {
|
|
509
|
+
fs.rmSync( tempDest, { force: true } );
|
|
510
|
+
runCommand( 'curl', [
|
|
511
|
+
'-fsSL',
|
|
512
|
+
'--retry', '3',
|
|
513
|
+
'--retry-delay', '1',
|
|
514
|
+
'--retry-all-errors',
|
|
515
|
+
url,
|
|
516
|
+
'-o', tempDest
|
|
517
|
+
] );
|
|
518
|
+
fs.rmSync( dest, { force: true } );
|
|
519
|
+
fs.renameSync( tempDest, dest );
|
|
520
|
+
return;
|
|
521
|
+
} catch ( error ) {
|
|
522
|
+
failures.push( `${ url }: ${ error.message }` );
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
fs.rmSync( tempDest, { force: true } );
|
|
527
|
+
throw new Error( `failed to download ${ targetLabel }: ${ failures.join( ' | ' ) }` );
|
|
528
|
+
}
|
|
529
|
+
|
|
490
530
|
function resolveInstallCommand() {
|
|
491
531
|
if ( commandExists( 'npm' ) ) {
|
|
492
532
|
return { command: 'npm', args: [ 'install' ] };
|
|
@@ -657,7 +697,7 @@ function bootstrapLocalEnvironment() {
|
|
|
657
697
|
runCommandOrThrow( installer.command, installer.args, rootDir );
|
|
658
698
|
}
|
|
659
699
|
if ( !fileExists( path.join( zoteroDir, 'modules', 'translators' ) ) ) {
|
|
660
|
-
throw new Error(
|
|
700
|
+
throw new Error( 'missing vendored zotero contents under vendor/zotero/modules/translators' );
|
|
661
701
|
}
|
|
662
702
|
if ( !fileExists( path.join( zoteroDir, 'node_modules' ) ) ) {
|
|
663
703
|
runCommandOrThrow( installer.command, installer.args, zoteroDir );
|
|
@@ -1522,19 +1562,24 @@ async function runCitationFromPdf( pdfPath, options ) {
|
|
|
1522
1562
|
} );
|
|
1523
1563
|
}
|
|
1524
1564
|
|
|
1525
|
-
function syncStyles( options ) {
|
|
1565
|
+
function syncStyles( options, dependencies ) {
|
|
1566
|
+
const styleOptions = options || {};
|
|
1567
|
+
const hooks = dependencies || {};
|
|
1568
|
+
const downloadFile = hooks.downloadFile || downloadFileWithCurlFallbacks;
|
|
1569
|
+
const downloadUsesCurl = downloadFile === downloadFileWithCurlFallbacks;
|
|
1570
|
+
|
|
1526
1571
|
ensureDirs();
|
|
1527
1572
|
fs.mkdirSync( cslDir, { recursive: true } );
|
|
1528
1573
|
fs.mkdirSync( localeDir, { recursive: true } );
|
|
1529
|
-
const configuredSourceDirs =
|
|
1574
|
+
const configuredSourceDirs = styleOptions.repo ?
|
|
1530
1575
|
[
|
|
1531
|
-
path.isAbsolute(
|
|
1532
|
-
|
|
1533
|
-
path.join( rootDir,
|
|
1576
|
+
path.isAbsolute( styleOptions.repo || '' ) ?
|
|
1577
|
+
styleOptions.repo :
|
|
1578
|
+
path.join( rootDir, styleOptions.repo || 'vendor/styles' )
|
|
1534
1579
|
] :
|
|
1535
1580
|
ensureDefaultStyleSources();
|
|
1536
1581
|
const sourceDirs = configuredSourceDirs.filter( ( sourceDir ) => fileExists( sourceDir ) );
|
|
1537
|
-
if (
|
|
1582
|
+
if ( styleOptions.repo && !sourceDirs.length ) {
|
|
1538
1583
|
throw new Error( `styles source not found: ${ configuredSourceDirs.join( ', ' ) }` );
|
|
1539
1584
|
}
|
|
1540
1585
|
|
|
@@ -1550,13 +1595,12 @@ function syncStyles( options ) {
|
|
|
1550
1595
|
} );
|
|
1551
1596
|
|
|
1552
1597
|
const localeTargets = [ 'en-US', 'zh-CN' ];
|
|
1553
|
-
if ( !commandExists( 'curl' ) ) {
|
|
1598
|
+
if ( downloadUsesCurl && !commandExists( 'curl' ) ) {
|
|
1554
1599
|
throw new Error( 'curl is required for styles sync' );
|
|
1555
1600
|
}
|
|
1556
1601
|
for ( const locale of localeTargets ) {
|
|
1557
|
-
const localeUrl = `https://raw.githubusercontent.com/citation-style-language/locales/master/locales-${ locale }.xml`;
|
|
1558
1602
|
const dest = path.join( localeDir, `locales-${ locale }.xml` );
|
|
1559
|
-
|
|
1603
|
+
downloadFile( styleLocaleUrls( locale ), dest, `locale ${ locale }` );
|
|
1560
1604
|
}
|
|
1561
1605
|
|
|
1562
1606
|
process.stdout.write( `styles synced to ${ cslDir } from ${ sourceDirs.join( ', ' ) } (copied ${ copiedCount } files)\n` );
|
|
@@ -4997,9 +5041,11 @@ function main() {
|
|
|
4997
5041
|
usage();
|
|
4998
5042
|
process.exit( 1 );
|
|
4999
5043
|
}
|
|
5000
|
-
|
|
5044
|
+
try {
|
|
5045
|
+
syncStyles( parsed );
|
|
5046
|
+
} catch ( error ) {
|
|
5001
5047
|
handleCommandError( error, parsed, 'styles', 'sync' );
|
|
5002
|
-
}
|
|
5048
|
+
}
|
|
5003
5049
|
return;
|
|
5004
5050
|
}
|
|
5005
5051
|
|
|
@@ -5158,12 +5204,15 @@ function main() {
|
|
|
5158
5204
|
}
|
|
5159
5205
|
|
|
5160
5206
|
module.exports = {
|
|
5207
|
+
downloadFileWithCurlFallbacks,
|
|
5161
5208
|
detectPdfIdentifierCandidates,
|
|
5162
5209
|
extractBestDoiCandidate,
|
|
5163
5210
|
extractPdfCandidates,
|
|
5164
5211
|
main,
|
|
5212
|
+
normalizeCommandForSpawn,
|
|
5165
5213
|
normalizeArxivId,
|
|
5166
5214
|
normalizeDoi,
|
|
5215
|
+
syncStyles,
|
|
5167
5216
|
shouldAttemptPdfOcr
|
|
5168
5217
|
};
|
|
5169
5218
|
|