@goliapkg/sentori-cli 0.5.2 → 0.6.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.
- package/README.md +51 -0
- package/lib/index.js +314 -3
- package/lib/index.js.map +1 -1
- package/lib/mcp.d.ts +14 -0
- package/lib/mcp.d.ts.map +1 -0
- package/lib/mcp.js +371 -0
- package/lib/mcp.js.map +1 -0
- package/lib/push.d.ts +42 -0
- package/lib/push.d.ts.map +1 -0
- package/lib/push.js +92 -0
- package/lib/push.js.map +1 -0
- package/lib/source-bundle.d.ts +28 -0
- package/lib/source-bundle.d.ts.map +1 -0
- package/lib/source-bundle.js +193 -0
- package/lib/source-bundle.js.map +1 -0
- package/lib/upload.d.ts +1 -1
- package/lib/upload.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/__tests__/mcp.test.ts +124 -0
- package/src/__tests__/source-bundle-from-dir.test.ts +85 -0
- package/src/__tests__/source-bundle.test.ts +105 -0
- package/src/index.ts +306 -3
- package/src/mcp.ts +399 -0
- package/src/push.ts +174 -0
- package/src/source-bundle.ts +234 -0
- package/src/upload.ts +1 -1
package/src/index.ts
CHANGED
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
import { parseArgs } from 'node:util'
|
|
3
3
|
|
|
4
4
|
import { formatIssueLine, issueList, issuePatch } from './issue.js'
|
|
5
|
+
import { runMcpServer } from './mcp.js'
|
|
5
6
|
import { uploadDsym, uploadMapping } from './native-artifacts.js'
|
|
7
|
+
import {
|
|
8
|
+
parseJsonArg,
|
|
9
|
+
pushCredsDelete,
|
|
10
|
+
pushCredsList,
|
|
11
|
+
pushCredsSet,
|
|
12
|
+
pushReceipt,
|
|
13
|
+
pushSend,
|
|
14
|
+
} from './push.js'
|
|
6
15
|
import { reactNativeUpload } from './react-native.js'
|
|
16
|
+
import { uploadSourceBundle } from './source-bundle.js'
|
|
7
17
|
import { uploadSourcemaps } from './upload.js'
|
|
8
18
|
|
|
9
19
|
const HELP = `sentori-cli — Sentori command-line interface
|
|
@@ -43,16 +53,33 @@ Native artifacts (project-scoped, need --project + admin token):
|
|
|
43
53
|
with a "# pg_map_id:" line the server sniffs the debug-id from
|
|
44
54
|
it; otherwise you can pass it explicitly.
|
|
45
55
|
|
|
56
|
+
sentori-cli upload source-bundle --project <uuid> --release <r> --platform ios|android [--module <label>] <archive.tar.gz>
|
|
57
|
+
Upload a pre-built tar.gz of your project's source so the
|
|
58
|
+
dashboard can render inline source for native (Swift / Kotlin /
|
|
59
|
+
Objective-C) frames the way it already does for JS via source
|
|
60
|
+
maps. Build the archive yourself:
|
|
61
|
+
tar -czf ios-source.tar.gz Sources/
|
|
62
|
+
Pass --module to upload multiple bundles per (release, platform)
|
|
63
|
+
— e.g. \`--module main\`, \`--module watch-ext\`. Omitting --module
|
|
64
|
+
reuses the v1.3 single-bundle slot (re-uploading replaces it).
|
|
65
|
+
|
|
46
66
|
CI triage:
|
|
47
67
|
sentori-cli issue list --project <uuid> [--status active|silenced|resolved|closed] [--limit N] [--error-type <t>]
|
|
48
68
|
sentori-cli issue resolve <issue-uuid> --project <uuid> [--in-release <r>]
|
|
49
69
|
sentori-cli issue silence <issue-uuid> --project <uuid>
|
|
50
70
|
|
|
71
|
+
LLM agents (MCP):
|
|
72
|
+
sentori-cli mcp serve --project <uuid> [--token <t>] [--api-url <url>]
|
|
73
|
+
Run a stdio MCP server. Connect from Claude Code / any MCP
|
|
74
|
+
client by pointing at \`sentori-cli mcp serve …\` as the command.
|
|
75
|
+
Exposes sentori_issue_list / _get / _comment / _transition /
|
|
76
|
+
_assign / _set_priority / _set_labels / _watch tools.
|
|
77
|
+
|
|
51
78
|
Options (upload commands):
|
|
52
79
|
--release <r> release identifier — MUST equal the value the SDK
|
|
53
80
|
reports via init({ release }). Required.
|
|
54
81
|
--token <t> Sentori token (or set $SENTORI_TOKEN).
|
|
55
|
-
--api-url <url> Sentori API base (default https://
|
|
82
|
+
--api-url <url> Sentori API base (default https://sentori.golia.jp,
|
|
56
83
|
or $SENTORI_API_URL). For a self-hosted instance, your
|
|
57
84
|
host. (Accepts --ingest-url as an alias.)
|
|
58
85
|
--dry-run describe what would be uploaded; don't upload.
|
|
@@ -110,7 +137,7 @@ function parseCommon(values: Record<string, unknown>): Common | null {
|
|
|
110
137
|
(typeof values['api-url'] === 'string' ? values['api-url'] : undefined) ??
|
|
111
138
|
(typeof values['ingest-url'] === 'string' ? values['ingest-url'] : undefined) ??
|
|
112
139
|
process.env.SENTORI_API_URL ??
|
|
113
|
-
'https://
|
|
140
|
+
'https://sentori.golia.jp'
|
|
114
141
|
return { apiUrl, dryRun, release, token: token ?? '' }
|
|
115
142
|
}
|
|
116
143
|
|
|
@@ -254,7 +281,7 @@ function parseAdminCfg(values: Record<string, unknown>): AdminCfg | null {
|
|
|
254
281
|
(typeof values['api-url'] === 'string' ? values['api-url'] : undefined) ??
|
|
255
282
|
(typeof values['ingest-url'] === 'string' ? values['ingest-url'] : undefined) ??
|
|
256
283
|
process.env.SENTORI_API_URL ??
|
|
257
|
-
'https://
|
|
284
|
+
'https://sentori.golia.jp'
|
|
258
285
|
return { apiUrl, projectId, token }
|
|
259
286
|
}
|
|
260
287
|
|
|
@@ -468,6 +495,101 @@ async function cmdUploadMapping(argv: string[]): Promise<number> {
|
|
|
468
495
|
}
|
|
469
496
|
}
|
|
470
497
|
|
|
498
|
+
async function cmdUploadSourceBundle(argv: string[]): Promise<number> {
|
|
499
|
+
let parsed
|
|
500
|
+
try {
|
|
501
|
+
parsed = parseArgs({
|
|
502
|
+
allowPositionals: true,
|
|
503
|
+
args: argv,
|
|
504
|
+
options: {
|
|
505
|
+
'api-url': { type: 'string' },
|
|
506
|
+
help: { short: 'h', type: 'boolean' },
|
|
507
|
+
'ingest-url': { type: 'string' },
|
|
508
|
+
// v1.4 W26 — optional module label so polyrepo apps can
|
|
509
|
+
// upload multiple bundles per (release, platform) without
|
|
510
|
+
// clobbering each other (main vs watch-ext vs share-ext…).
|
|
511
|
+
module: { type: 'string' },
|
|
512
|
+
platform: { type: 'string' },
|
|
513
|
+
project: { type: 'string' },
|
|
514
|
+
release: { type: 'string' },
|
|
515
|
+
token: { type: 'string' },
|
|
516
|
+
},
|
|
517
|
+
})
|
|
518
|
+
} catch (e) {
|
|
519
|
+
console.error(`error: ${(e as Error).message}\n${HELP}`)
|
|
520
|
+
return 2
|
|
521
|
+
}
|
|
522
|
+
if (parsed.values.help) {
|
|
523
|
+
console.log(HELP)
|
|
524
|
+
return 0
|
|
525
|
+
}
|
|
526
|
+
const cfg = parseAdminCfg(parsed.values)
|
|
527
|
+
if (!cfg) return 2
|
|
528
|
+
const path = parsed.positionals[0]
|
|
529
|
+
if (!path) {
|
|
530
|
+
console.error('error: a path to a tar.gz archive is required')
|
|
531
|
+
return 2
|
|
532
|
+
}
|
|
533
|
+
const platform = parsed.values.platform
|
|
534
|
+
if (platform !== 'ios' && platform !== 'android') {
|
|
535
|
+
console.error('error: --platform must be ios or android')
|
|
536
|
+
return 2
|
|
537
|
+
}
|
|
538
|
+
const release = typeof parsed.values.release === 'string' ? parsed.values.release : undefined
|
|
539
|
+
if (!release) {
|
|
540
|
+
console.error('error: --release is required for source-bundle uploads')
|
|
541
|
+
return 2
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
const r = await uploadSourceBundle({
|
|
545
|
+
apiUrl: cfg.apiUrl,
|
|
546
|
+
module: typeof parsed.values.module === 'string' ? parsed.values.module : undefined,
|
|
547
|
+
path,
|
|
548
|
+
platform,
|
|
549
|
+
projectId: cfg.projectId,
|
|
550
|
+
release,
|
|
551
|
+
token: cfg.token,
|
|
552
|
+
})
|
|
553
|
+
console.log(`uploaded ${r.kind} (${r.sizeBytes} bytes, sha256:${r.contentHash.slice(0, 12)}…)`)
|
|
554
|
+
return 0
|
|
555
|
+
} catch (e) {
|
|
556
|
+
console.error(`source-bundle upload failed: ${(e as Error).message}`)
|
|
557
|
+
return 1
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async function cmdMcpServe(argv: string[]): Promise<number> {
|
|
562
|
+
let parsed
|
|
563
|
+
try {
|
|
564
|
+
parsed = parseArgs({
|
|
565
|
+
args: argv,
|
|
566
|
+
options: {
|
|
567
|
+
'api-url': { type: 'string' },
|
|
568
|
+
help: { short: 'h', type: 'boolean' },
|
|
569
|
+
'ingest-url': { type: 'string' },
|
|
570
|
+
project: { type: 'string' },
|
|
571
|
+
token: { type: 'string' },
|
|
572
|
+
},
|
|
573
|
+
})
|
|
574
|
+
} catch (e) {
|
|
575
|
+
console.error(`error: ${(e as Error).message}\n${HELP}`)
|
|
576
|
+
return 2
|
|
577
|
+
}
|
|
578
|
+
if (parsed.values.help) {
|
|
579
|
+
console.log(HELP)
|
|
580
|
+
return 0
|
|
581
|
+
}
|
|
582
|
+
const cfg = parseAdminCfg(parsed.values)
|
|
583
|
+
if (!cfg) return 2
|
|
584
|
+
try {
|
|
585
|
+
await runMcpServer({ apiUrl: cfg.apiUrl, projectId: cfg.projectId, token: cfg.token })
|
|
586
|
+
return 0
|
|
587
|
+
} catch (e) {
|
|
588
|
+
console.error(`mcp serve failed: ${(e as Error).message}`)
|
|
589
|
+
return 1
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
471
593
|
async function main(argv: string[]): Promise<number> {
|
|
472
594
|
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
473
595
|
console.log(HELP)
|
|
@@ -477,16 +599,197 @@ async function main(argv: string[]): Promise<number> {
|
|
|
477
599
|
if (a === 'upload' && b === 'sourcemap') return cmdUploadSourcemap(rest)
|
|
478
600
|
if (a === 'upload' && b === 'dsym') return cmdUploadDsym(rest)
|
|
479
601
|
if (a === 'upload' && b === 'mapping') return cmdUploadMapping(rest)
|
|
602
|
+
if (a === 'upload' && b === 'source-bundle') return cmdUploadSourceBundle(rest)
|
|
603
|
+
if (a === 'mcp' && b === 'serve') return cmdMcpServe(rest)
|
|
480
604
|
if (a === 'react-native' && b === 'upload') return cmdReactNativeUpload(rest)
|
|
481
605
|
if (a === 'issue' && b === 'list') return cmdIssueList(rest)
|
|
482
606
|
if (a === 'issue' && b === 'resolve') return cmdIssuePatch(rest, { status: 'resolved' }, 'resolved')
|
|
483
607
|
if (a === 'issue' && b === 'silence') return cmdIssuePatch(rest, { status: 'silenced' }, 'silenced')
|
|
484
608
|
if (a === 'issue' && b === 'close') return cmdIssuePatch(rest, { status: 'closed' }, 'closed')
|
|
609
|
+
if (a === 'push' && b === 'send') return cmdPushSend(rest)
|
|
610
|
+
if (a === 'push' && b === 'receipt') return cmdPushReceipt(rest)
|
|
611
|
+
if (a === 'push' && b === 'creds') {
|
|
612
|
+
const [c, ...rest2] = rest
|
|
613
|
+
if (c === 'list') return cmdPushCredsList(rest2)
|
|
614
|
+
if (c === 'set') return cmdPushCredsSet(rest2)
|
|
615
|
+
if (c === 'delete') return cmdPushCredsDelete(rest2)
|
|
616
|
+
}
|
|
485
617
|
console.error(`unknown command: ${[a, b].filter(Boolean).join(' ') || '(none)'}\n`)
|
|
486
618
|
console.error(HELP)
|
|
487
619
|
return 2
|
|
488
620
|
}
|
|
489
621
|
|
|
622
|
+
// ── push commands (v2.12) ─────────────────────────────────────────
|
|
623
|
+
|
|
624
|
+
async function cmdPushSend(argv: string[]): Promise<number> {
|
|
625
|
+
const parsed = parseArgs({
|
|
626
|
+
args: argv,
|
|
627
|
+
options: {
|
|
628
|
+
'api-url': { type: 'string' },
|
|
629
|
+
body: { type: 'string' },
|
|
630
|
+
data: { type: 'string' },
|
|
631
|
+
'idempotency-key': { type: 'string' },
|
|
632
|
+
'ingest-url': { type: 'string' },
|
|
633
|
+
priority: { type: 'string' },
|
|
634
|
+
project: { type: 'string' },
|
|
635
|
+
title: { type: 'string' },
|
|
636
|
+
to: { type: 'string' },
|
|
637
|
+
token: { type: 'string' },
|
|
638
|
+
ttl: { type: 'string' },
|
|
639
|
+
},
|
|
640
|
+
strict: true,
|
|
641
|
+
})
|
|
642
|
+
const cfg = parseAdminCfg(parsed.values)
|
|
643
|
+
if (!cfg) return 2
|
|
644
|
+
const to = parsed.values.to as string | undefined
|
|
645
|
+
if (!to) {
|
|
646
|
+
console.error('error: --to <ipt_handle> is required')
|
|
647
|
+
return 2
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
const data = parsed.values.data ? (parseJsonArg(parsed.values.data as string, '--data') as Record<string, unknown>) : undefined
|
|
651
|
+
const priority = parsed.values.priority as 'high' | 'normal' | undefined
|
|
652
|
+
const ticket = await pushSend(cfg, {
|
|
653
|
+
to,
|
|
654
|
+
title: parsed.values.title as string | undefined,
|
|
655
|
+
body: parsed.values.body as string | undefined,
|
|
656
|
+
data,
|
|
657
|
+
priority,
|
|
658
|
+
ttl: parsed.values.ttl ? Number(parsed.values.ttl) : undefined,
|
|
659
|
+
idempotencyKey: parsed.values['idempotency-key'] as string | undefined,
|
|
660
|
+
})
|
|
661
|
+
console.log(`${ticket.id} ${ticket.status}`)
|
|
662
|
+
return 0
|
|
663
|
+
} catch (e) {
|
|
664
|
+
console.error(`push send failed: ${(e as Error).message}`)
|
|
665
|
+
return 1
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async function cmdPushReceipt(argv: string[]): Promise<number> {
|
|
670
|
+
const parsed = parseArgs({
|
|
671
|
+
args: argv,
|
|
672
|
+
options: {
|
|
673
|
+
'api-url': { type: 'string' },
|
|
674
|
+
'ingest-url': { type: 'string' },
|
|
675
|
+
project: { type: 'string' },
|
|
676
|
+
token: { type: 'string' },
|
|
677
|
+
},
|
|
678
|
+
allowPositionals: true,
|
|
679
|
+
strict: true,
|
|
680
|
+
})
|
|
681
|
+
const sendId = parsed.positionals[0]
|
|
682
|
+
if (!sendId) {
|
|
683
|
+
console.error('error: <send-id> positional is required')
|
|
684
|
+
return 2
|
|
685
|
+
}
|
|
686
|
+
const cfg = parseAdminCfg(parsed.values)
|
|
687
|
+
if (!cfg) return 2
|
|
688
|
+
try {
|
|
689
|
+
const r = await pushReceipt(cfg, sendId)
|
|
690
|
+
console.log(`${r.ticket.id} ${r.ticket.status}${r.ticket.providerOutcome ? ` (${r.ticket.providerOutcome})` : ''}${r.ticket.error ? ` — ${r.ticket.error}` : ''}`)
|
|
691
|
+
return 0
|
|
692
|
+
} catch (e) {
|
|
693
|
+
console.error(`push receipt failed: ${(e as Error).message}`)
|
|
694
|
+
return 1
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async function cmdPushCredsList(argv: string[]): Promise<number> {
|
|
699
|
+
const parsed = parseArgs({
|
|
700
|
+
args: argv,
|
|
701
|
+
options: {
|
|
702
|
+
'api-url': { type: 'string' },
|
|
703
|
+
'ingest-url': { type: 'string' },
|
|
704
|
+
project: { type: 'string' },
|
|
705
|
+
token: { type: 'string' },
|
|
706
|
+
},
|
|
707
|
+
strict: true,
|
|
708
|
+
})
|
|
709
|
+
const cfg = parseAdminCfg(parsed.values)
|
|
710
|
+
if (!cfg) return 2
|
|
711
|
+
try {
|
|
712
|
+
const rows = await pushCredsList(cfg)
|
|
713
|
+
if (rows.length === 0) {
|
|
714
|
+
console.log('(no providers configured)')
|
|
715
|
+
return 0
|
|
716
|
+
}
|
|
717
|
+
for (const r of rows) {
|
|
718
|
+
console.log(`${r.provider}\t${r.updatedAt}\t${JSON.stringify(r.config)}`)
|
|
719
|
+
}
|
|
720
|
+
return 0
|
|
721
|
+
} catch (e) {
|
|
722
|
+
console.error(`push creds list failed: ${(e as Error).message}`)
|
|
723
|
+
return 1
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function cmdPushCredsSet(argv: string[]): Promise<number> {
|
|
728
|
+
const parsed = parseArgs({
|
|
729
|
+
args: argv,
|
|
730
|
+
options: {
|
|
731
|
+
'api-url': { type: 'string' },
|
|
732
|
+
config: { type: 'string' },
|
|
733
|
+
'ingest-url': { type: 'string' },
|
|
734
|
+
project: { type: 'string' },
|
|
735
|
+
secret: { type: 'string' },
|
|
736
|
+
token: { type: 'string' },
|
|
737
|
+
},
|
|
738
|
+
allowPositionals: true,
|
|
739
|
+
strict: true,
|
|
740
|
+
})
|
|
741
|
+
const provider = parsed.positionals[0]
|
|
742
|
+
if (!provider) {
|
|
743
|
+
console.error('error: <provider> positional (apns/fcm/webpush/hcm/mipush) is required')
|
|
744
|
+
return 2
|
|
745
|
+
}
|
|
746
|
+
const cfg = parseAdminCfg(parsed.values)
|
|
747
|
+
if (!cfg) return 2
|
|
748
|
+
if (!parsed.values.config || !parsed.values.secret) {
|
|
749
|
+
console.error('error: --config @file.json and --secret @file.json are both required')
|
|
750
|
+
return 2
|
|
751
|
+
}
|
|
752
|
+
try {
|
|
753
|
+
const config = parseJsonArg(parsed.values.config as string, '--config')
|
|
754
|
+
const secret = parseJsonArg(parsed.values.secret as string, '--secret')
|
|
755
|
+
await pushCredsSet(cfg, provider, config, secret)
|
|
756
|
+
console.log(`${provider} ✓ saved`)
|
|
757
|
+
return 0
|
|
758
|
+
} catch (e) {
|
|
759
|
+
console.error(`push creds set failed: ${(e as Error).message}`)
|
|
760
|
+
return 1
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
async function cmdPushCredsDelete(argv: string[]): Promise<number> {
|
|
765
|
+
const parsed = parseArgs({
|
|
766
|
+
args: argv,
|
|
767
|
+
options: {
|
|
768
|
+
'api-url': { type: 'string' },
|
|
769
|
+
'ingest-url': { type: 'string' },
|
|
770
|
+
project: { type: 'string' },
|
|
771
|
+
token: { type: 'string' },
|
|
772
|
+
},
|
|
773
|
+
allowPositionals: true,
|
|
774
|
+
strict: true,
|
|
775
|
+
})
|
|
776
|
+
const provider = parsed.positionals[0]
|
|
777
|
+
if (!provider) {
|
|
778
|
+
console.error('error: <provider> positional is required')
|
|
779
|
+
return 2
|
|
780
|
+
}
|
|
781
|
+
const cfg = parseAdminCfg(parsed.values)
|
|
782
|
+
if (!cfg) return 2
|
|
783
|
+
try {
|
|
784
|
+
await pushCredsDelete(cfg, provider)
|
|
785
|
+
console.log(`${provider} ✓ deleted`)
|
|
786
|
+
return 0
|
|
787
|
+
} catch (e) {
|
|
788
|
+
console.error(`push creds delete failed: ${(e as Error).message}`)
|
|
789
|
+
return 1
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
490
793
|
main(process.argv.slice(2)).then(
|
|
491
794
|
(code) => process.exit(code),
|
|
492
795
|
(e: unknown) => {
|