@scribe-platform/cli 0.7.0 → 0.9.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 +28 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +15 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/proof.d.ts +5 -0
- package/dist/commands/proof.d.ts.map +1 -0
- package/dist/commands/proof.js +440 -0
- package/dist/commands/proof.js.map +1 -0
- package/dist/commands/prospect.d.ts +27 -0
- package/dist/commands/prospect.d.ts.map +1 -0
- package/dist/commands/prospect.js +222 -0
- package/dist/commands/prospect.js.map +1 -0
- package/dist/commands/request.d.ts +38 -0
- package/dist/commands/request.d.ts.map +1 -0
- package/dist/commands/request.js +214 -0
- package/dist/commands/request.js.map +1 -0
- package/dist/commands/site.d.ts +11 -0
- package/dist/commands/site.d.ts.map +1 -1
- package/dist/commands/site.js +41 -3
- package/dist/commands/site.js.map +1 -1
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +128 -31
- package/dist/commands/update.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/claude-hook.d.ts +74 -0
- package/dist/lib/claude-hook.d.ts.map +1 -0
- package/dist/lib/claude-hook.js +108 -0
- package/dist/lib/claude-hook.js.map +1 -0
- package/dist/lib/client.d.ts +351 -0
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/client.js +159 -0
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/proof-folder.d.ts +86 -0
- package/dist/lib/proof-folder.d.ts.map +1 -0
- package/dist/lib/proof-folder.js +226 -0
- package/dist/lib/proof-folder.js.map +1 -0
- package/dist/lib/proof-preview.d.ts +39 -0
- package/dist/lib/proof-preview.d.ts.map +1 -0
- package/dist/lib/proof-preview.js +318 -0
- package/dist/lib/proof-preview.js.map +1 -0
- package/dist/lib/proof-push.d.ts +42 -0
- package/dist/lib/proof-push.d.ts.map +1 -0
- package/dist/lib/proof-push.js +91 -0
- package/dist/lib/proof-push.js.map +1 -0
- package/dist/lib/skill.d.ts +13 -0
- package/dist/lib/skill.d.ts.map +1 -1
- package/dist/lib/skill.js +33 -2
- package/dist/lib/skill.js.map +1 -1
- package/dist/lib/update-check.d.ts +46 -0
- package/dist/lib/update-check.d.ts.map +1 -0
- package/dist/lib/update-check.js +103 -0
- package/dist/lib/update-check.js.map +1 -0
- package/package.json +5 -3
- package/runtime/proof.js +139 -0
- package/skills/scribe-site-builder/SKILL.md +175 -33
- package/skills/scribe-site-builder/recipes/README.md +11 -3
- package/skills/scribe-site-builder/recipes/domains.md +151 -0
- package/skills/scribe-site-builder/recipes/forms.md +5 -2
- package/skills/scribe-site-builder/recipes/proofs.md +311 -0
- package/skills/scribe-site-builder/recipes/prospects.md +192 -0
- package/skills/scribe-site-builder/recipes/requests.md +145 -0
- package/skills/scribe-site-builder/recipes/schema.md +24 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { CALL_OUTCOMES } from '../lib/client.js';
|
|
2
|
+
import { printResult, requireClient, runAction } from '../lib/cli-utils.js';
|
|
3
|
+
const STATUSES = ['prospect', 'active', 'churned', 'lost'];
|
|
4
|
+
function isValidStatus(status) {
|
|
5
|
+
return STATUSES.includes(status);
|
|
6
|
+
}
|
|
7
|
+
/** Mirrors the console's sort dropdown — see RFC 0027. */
|
|
8
|
+
const SORTS = ['due', 'neglected', 'warm', 'name'];
|
|
9
|
+
function isValidSort(sort) {
|
|
10
|
+
return SORTS.includes(sort);
|
|
11
|
+
}
|
|
12
|
+
function isValidOutcome(outcome) {
|
|
13
|
+
return CALL_OUTCOMES.includes(outcome);
|
|
14
|
+
}
|
|
15
|
+
/** `--next` and `--clear-next` are mutually exclusive — a reschedule is to a date or to nothing, never both. */
|
|
16
|
+
function resolveFollowUpUpdate(opts) {
|
|
17
|
+
if (opts.next && opts.clearNext) {
|
|
18
|
+
return { ok: false, error: '--next and --clear-next are mutually exclusive' };
|
|
19
|
+
}
|
|
20
|
+
if (opts.clearNext)
|
|
21
|
+
return { ok: true, value: null };
|
|
22
|
+
if (opts.next)
|
|
23
|
+
return { ok: true, value: opts.next };
|
|
24
|
+
return { ok: true };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* RFC 0027 — the nurture-loop primitive. `log` is one note plus, optionally,
|
|
28
|
+
* one reschedule of the next follow-up; `--next`/`--clear-next` are mutually
|
|
29
|
+
* exclusive because a reschedule is either to a date or to nothing, never
|
|
30
|
+
* both. No delete subcommand here or anywhere under `/api/v1/prospects` —
|
|
31
|
+
* deletion stays console-only.
|
|
32
|
+
*/
|
|
33
|
+
export function registerProspectCommands(program) {
|
|
34
|
+
const prospect = program.command('prospect').description('Manage prospects and clients in the calling pipeline');
|
|
35
|
+
prospect
|
|
36
|
+
.command('list')
|
|
37
|
+
.description('List prospects, optionally filtered by status or search term')
|
|
38
|
+
.option('--status <status>', 'prospect, active, churned, or lost')
|
|
39
|
+
.option('--search <text>', 'Filter by name')
|
|
40
|
+
.option('--due', 'Only prospects with a follow-up due now or overdue, soonest/most-overdue first')
|
|
41
|
+
.option('--sort <sort>', `one of: ${SORTS.join(', ')} (default: due)`)
|
|
42
|
+
.option('--json', 'Output as JSON')
|
|
43
|
+
.action(runAction(async (opts) => {
|
|
44
|
+
if (opts.sort && !isValidSort(opts.sort)) {
|
|
45
|
+
console.error(`--sort must be one of: ${SORTS.join(', ')}`);
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const items = await requireClient().listProspects(opts);
|
|
50
|
+
if (opts.json) {
|
|
51
|
+
printResult(items, true);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
printResult(items.map((p) => ({
|
|
55
|
+
id: p.id,
|
|
56
|
+
name: p.name,
|
|
57
|
+
status: p.status,
|
|
58
|
+
// The attempt count travels with the warmth because `cold` means
|
|
59
|
+
// something different after one try than after five.
|
|
60
|
+
warmth: p.warmth.warmth,
|
|
61
|
+
tries: p.warmth.attempts,
|
|
62
|
+
nextFollowUp: p.prospecting?.nextFollowUpAt ?? '',
|
|
63
|
+
lastContact: p.warmth.lastContactAt ?? '',
|
|
64
|
+
})), false);
|
|
65
|
+
// Below the table, not as a column: these are sentences, and a column
|
|
66
|
+
// wide enough for one would squeeze everything that tells Ashley who
|
|
67
|
+
// to call. RFC 0029 — context for the call, not a change to the list.
|
|
68
|
+
const withProofs = items.filter((p) => p.proofNote);
|
|
69
|
+
if (withProofs.length > 0) {
|
|
70
|
+
console.log('');
|
|
71
|
+
for (const p of withProofs)
|
|
72
|
+
console.log(`${p.name} — ${p.proofNote}`);
|
|
73
|
+
}
|
|
74
|
+
}));
|
|
75
|
+
prospect
|
|
76
|
+
.command('show')
|
|
77
|
+
.description('Show one prospect, including its full call log')
|
|
78
|
+
.argument('<id>')
|
|
79
|
+
.option('--json', 'Output as JSON')
|
|
80
|
+
.action(runAction(async (id, opts) => {
|
|
81
|
+
const p = await requireClient().getProspect(id);
|
|
82
|
+
if (opts.json) {
|
|
83
|
+
printResult(p, true);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
console.log(`${p.name} (${p.status})`);
|
|
87
|
+
if (p.primaryContact) {
|
|
88
|
+
const contact = [p.primaryContact.name, p.primaryContact.email, p.primaryContact.phone].filter(Boolean).join(' / ');
|
|
89
|
+
if (contact)
|
|
90
|
+
console.log(`Contact: ${contact}`);
|
|
91
|
+
}
|
|
92
|
+
console.log(`Warmth: ${p.warmth.warmth} (${p.warmth.attempts} ${p.warmth.attempts === 1 ? 'attempt' : 'attempts'})`);
|
|
93
|
+
console.log(`Next follow-up: ${p.prospecting?.nextFollowUpAt ?? 'Not scheduled'}`);
|
|
94
|
+
console.log('Notes:');
|
|
95
|
+
if (p.notes.length === 0)
|
|
96
|
+
console.log(' (none yet)');
|
|
97
|
+
for (const note of [...p.notes].reverse()) {
|
|
98
|
+
console.log(` [${note.createdAt}]${note.outcome ? ` (${note.outcome})` : ''} ${note.body}`);
|
|
99
|
+
}
|
|
100
|
+
}));
|
|
101
|
+
prospect
|
|
102
|
+
.command('pipeline')
|
|
103
|
+
.description('The funnel\'s shape — counts by warmth, and how many prospects are still workable')
|
|
104
|
+
.option('--json', 'Output as JSON')
|
|
105
|
+
.action(runAction(async (opts) => {
|
|
106
|
+
const summary = await requireClient().prospectPipeline();
|
|
107
|
+
if (opts.json) {
|
|
108
|
+
printResult(summary, true);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
console.log(`${summary.workable} workable of ${summary.total} prospects`);
|
|
112
|
+
for (const warmth of ['hot', 'warm', 'untouched', 'cold', 'stalled']) {
|
|
113
|
+
const count = summary.counts[warmth];
|
|
114
|
+
if (count > 0)
|
|
115
|
+
console.log(` ${warmth.padEnd(10)} ${count}`);
|
|
116
|
+
}
|
|
117
|
+
// The actionable read, rather than leaving it to be inferred from the
|
|
118
|
+
// numbers: stalled is the only bucket that is genuinely spent.
|
|
119
|
+
if (summary.workable === 0 && summary.total > 0) {
|
|
120
|
+
console.log('\nEvery prospect has stalled — time to find more leads.');
|
|
121
|
+
}
|
|
122
|
+
}));
|
|
123
|
+
prospect
|
|
124
|
+
.command('create')
|
|
125
|
+
.description('Create a new prospect')
|
|
126
|
+
.argument('<name>')
|
|
127
|
+
.option('--status <status>', 'prospect, active, churned, or lost', 'prospect')
|
|
128
|
+
.option('--contact-name <name>')
|
|
129
|
+
.option('--email <email>')
|
|
130
|
+
.option('--phone <phone>')
|
|
131
|
+
.option('--note <text>', 'A seed note — e.g. how/where this prospect was found')
|
|
132
|
+
.option('--json', 'Output as JSON')
|
|
133
|
+
.action(runAction(async (name, opts) => {
|
|
134
|
+
if (!isValidStatus(opts.status)) {
|
|
135
|
+
console.error(`--status must be one of: ${STATUSES.join(', ')}`);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const hasContact = opts.contactName || opts.email || opts.phone;
|
|
140
|
+
const result = await requireClient().createProspect({
|
|
141
|
+
name,
|
|
142
|
+
status: opts.status,
|
|
143
|
+
primaryContact: hasContact
|
|
144
|
+
? { name: opts.contactName ?? null, email: opts.email ?? null, phone: opts.phone ?? null }
|
|
145
|
+
: null,
|
|
146
|
+
note: opts.note,
|
|
147
|
+
});
|
|
148
|
+
printResult(result, opts.json);
|
|
149
|
+
}));
|
|
150
|
+
prospect
|
|
151
|
+
.command('update')
|
|
152
|
+
.description("Update a prospect's name or contact info")
|
|
153
|
+
.argument('<id>')
|
|
154
|
+
.option('--name <name>')
|
|
155
|
+
.option('--contact-name <name>')
|
|
156
|
+
.option('--email <email>')
|
|
157
|
+
.option('--phone <phone>')
|
|
158
|
+
.option('--json', 'Output as JSON')
|
|
159
|
+
.action(runAction(async (id, opts) => {
|
|
160
|
+
const hasContact = opts.contactName !== undefined || opts.email !== undefined || opts.phone !== undefined;
|
|
161
|
+
if (opts.name === undefined && !hasContact) {
|
|
162
|
+
console.error('Provide at least one field to update');
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const result = await requireClient().updateProspect(id, {
|
|
167
|
+
name: opts.name,
|
|
168
|
+
primaryContact: hasContact
|
|
169
|
+
? { name: opts.contactName ?? null, email: opts.email ?? null, phone: opts.phone ?? null }
|
|
170
|
+
: undefined,
|
|
171
|
+
});
|
|
172
|
+
printResult(result, opts.json);
|
|
173
|
+
}));
|
|
174
|
+
prospect
|
|
175
|
+
.command('log')
|
|
176
|
+
.description('Log a call — a note, and optionally a next follow-up date, together')
|
|
177
|
+
.argument('<id>')
|
|
178
|
+
.requiredOption('--note <text>', 'What happened')
|
|
179
|
+
.option('--outcome <outcome>', `how far it got — one of: ${CALL_OUTCOMES.join(', ')}`)
|
|
180
|
+
.option('--next <date>', 'ISO date (or anything Date can parse) for the next follow-up')
|
|
181
|
+
.option('--clear-next', 'Clear any scheduled follow-up')
|
|
182
|
+
.option('--json', 'Output as JSON')
|
|
183
|
+
.action(runAction(async (id, opts) => {
|
|
184
|
+
const followUp = resolveFollowUpUpdate(opts);
|
|
185
|
+
if (!followUp.ok) {
|
|
186
|
+
console.error(followUp.error);
|
|
187
|
+
process.exitCode = 1;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (opts.outcome !== undefined && !isValidOutcome(opts.outcome)) {
|
|
191
|
+
console.error(`--outcome must be one of: ${CALL_OUTCOMES.join(', ')}`);
|
|
192
|
+
process.exitCode = 1;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const result = await requireClient().logProspectCall(id, {
|
|
196
|
+
note: opts.note,
|
|
197
|
+
nextFollowUpAt: followUp.value,
|
|
198
|
+
// Omitted means "not a contact attempt", which is what keeps a
|
|
199
|
+
// research note out of the attempt tally that decides stalling.
|
|
200
|
+
outcome: opts.outcome ?? null,
|
|
201
|
+
});
|
|
202
|
+
printResult(result, opts.json);
|
|
203
|
+
}));
|
|
204
|
+
prospect
|
|
205
|
+
.command('status')
|
|
206
|
+
.description('Move a prospect through prospect → active/lost (or a client through active ↔ churned)')
|
|
207
|
+
.argument('<id>')
|
|
208
|
+
.argument('<status>', 'prospect, active, churned, or lost')
|
|
209
|
+
.option('--reason <reason>', 'Carried with a lost or churned move')
|
|
210
|
+
.option('--json', 'Output as JSON')
|
|
211
|
+
.action(runAction(async (id, status, opts) => {
|
|
212
|
+
if (!isValidStatus(status)) {
|
|
213
|
+
console.error(`status must be one of: ${STATUSES.join(', ')}`);
|
|
214
|
+
process.exitCode = 1;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const result = await requireClient().setProspectStatus(id, { status, reason: opts.reason });
|
|
218
|
+
printResult(result, opts.json);
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
export { isValidOutcome, isValidSort, isValidStatus, resolveFollowUpUpdate };
|
|
222
|
+
//# sourceMappingURL=prospect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prospect.js","sourceRoot":"","sources":["../../src/commands/prospect.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAwB,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAE5E,MAAM,QAAQ,GAAG,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;AAE3D,SAAS,aAAa,CAAC,MAAc;IACnC,OAAO,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC;AAED,0DAA0D;AAC1D,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAEnD,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,cAAc,CAAC,OAAe;IACrC,OAAQ,aAAmC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAChE,CAAC;AAID,gHAAgH;AAChH,SAAS,qBAAqB,CAAC,IAA4C;IACzE,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gDAAgD,EAAE,CAAC;IAChF,CAAC;IACD,IAAI,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACrD,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACrD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAgB;IACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,WAAW,CAAC,sDAAsD,CAAC,CAAC;IAEjH,QAAQ;SACL,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,8DAA8D,CAAC;SAC3E,MAAM,CAAC,mBAAmB,EAAE,oCAAoC,CAAC;SACjE,MAAM,CAAC,iBAAiB,EAAE,gBAAgB,CAAC;SAC3C,MAAM,CAAC,OAAO,EAAE,gFAAgF,CAAC;SACjG,MAAM,CAAC,eAAe,EAAE,WAAW,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC;SACrE,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CACL,SAAS,CAAC,KAAK,EAAE,IAAwF,EAAE,EAAE;QAC3G,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,KAAK,CAAC,0BAA0B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YACzB,OAAO;QACT,CAAC;QACD,WAAW,CACT,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAChB,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,iEAAiE;YACjE,qDAAqD;YACrD,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM;YACvB,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ;YACxB,YAAY,EAAE,CAAC,CAAC,WAAW,EAAE,cAAc,IAAI,EAAE;YACjD,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE;SAC1C,CAAC,CAAC,EACH,KAAK,CACN,CAAC;QAEF,sEAAsE;QACtE,qEAAqE;QACrE,sEAAsE;QACtE,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,KAAK,MAAM,CAAC,IAAI,UAAU;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEJ,QAAQ;SACL,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,gDAAgD,CAAC;SAC7D,QAAQ,CAAC,MAAM,CAAC;SAChB,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CACL,SAAS,CAAC,KAAK,EAAE,EAAU,EAAE,IAAwB,EAAE,EAAE;QACvD,MAAM,CAAC,GAAG,MAAM,aAAa,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAChD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;YACrB,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpH,IAAI,OAAO;gBAAE,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC;QACrH,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,WAAW,EAAE,cAAc,IAAI,eAAe,EAAE,CAAC,CAAC;QACnF,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtB,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACtD,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YAC1C,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/F,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEJ,QAAQ;SACL,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CAAC,mFAAmF,CAAC;SAChG,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CACL,SAAS,CAAC,KAAK,EAAE,IAAwB,EAAE,EAAE;QAC3C,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAC,gBAAgB,EAAE,CAAC;QACzD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC3B,OAAO;QACT,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,gBAAgB,OAAO,CAAC,KAAK,YAAY,CAAC,CAAC;QAC1E,KAAK,MAAM,MAAM,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,CAAU,EAAE,CAAC;YAC9E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACrC,IAAI,KAAK,GAAG,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,sEAAsE;QACtE,+DAA+D;QAC/D,IAAI,OAAO,CAAC,QAAQ,KAAK,CAAC,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;QACzE,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEJ,QAAQ;SACL,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,uBAAuB,CAAC;SACpC,QAAQ,CAAC,QAAQ,CAAC;SAClB,MAAM,CAAC,mBAAmB,EAAE,oCAAoC,EAAE,UAAU,CAAC;SAC7E,MAAM,CAAC,uBAAuB,CAAC;SAC/B,MAAM,CAAC,iBAAiB,CAAC;SACzB,MAAM,CAAC,iBAAiB,CAAC;SACzB,MAAM,CAAC,eAAe,EAAE,sDAAsD,CAAC;SAC/E,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CACL,SAAS,CACP,KAAK,EACH,IAAY,EACZ,IAA6G,EAC7G,EAAE;QACF,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,KAAK,CAAC,4BAA4B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC;QAChE,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAC,cAAc,CAAC;YAClD,IAAI;YACJ,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,cAAc,EAAE,UAAU;gBACxB,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI,EAAE;gBAC1F,CAAC,CAAC,IAAI;YACR,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAC,CAAC;QACH,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC,CACF,CACF,CAAC;IAEJ,QAAQ;SACL,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,0CAA0C,CAAC;SACvD,QAAQ,CAAC,MAAM,CAAC;SAChB,MAAM,CAAC,eAAe,CAAC;SACvB,MAAM,CAAC,uBAAuB,CAAC;SAC/B,MAAM,CAAC,iBAAiB,CAAC;SACzB,MAAM,CAAC,iBAAiB,CAAC;SACzB,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CACL,SAAS,CACP,KAAK,EAAE,EAAU,EAAE,IAA6F,EAAE,EAAE;QAClH,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC;QAC1G,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3C,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;YACtD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAC,cAAc,CAAC,EAAE,EAAE;YACtD,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,cAAc,EAAE,UAAU;gBACxB,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI,EAAE;gBAC1F,CAAC,CAAC,SAAS;SACd,CAAC,CAAC;QACH,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC,CACF,CACF,CAAC;IAEJ,QAAQ;SACL,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CAAC,qEAAqE,CAAC;SAClF,QAAQ,CAAC,MAAM,CAAC;SAChB,cAAc,CAAC,eAAe,EAAE,eAAe,CAAC;SAChD,MAAM,CAAC,qBAAqB,EAAE,4BAA4B,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;SACrF,MAAM,CAAC,eAAe,EAAE,8DAA8D,CAAC;SACvF,MAAM,CAAC,cAAc,EAAE,+BAA+B,CAAC;SACvD,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CACL,SAAS,CACP,KAAK,EAAE,EAAU,EAAE,IAA4F,EAAE,EAAE;QACjH,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC9B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAChE,OAAO,CAAC,KAAK,CAAC,6BAA6B,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE;YACvD,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,cAAc,EAAE,QAAQ,CAAC,KAAK;YAC9B,+DAA+D;YAC/D,gEAAgE;YAChE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;SAC9B,CAAC,CAAC;QACH,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC,CACF,CACF,CAAC;IAEJ,QAAQ;SACL,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,uFAAuF,CAAC;SACpG,QAAQ,CAAC,MAAM,CAAC;SAChB,QAAQ,CAAC,UAAU,EAAE,oCAAoC,CAAC;SAC1D,MAAM,CAAC,mBAAmB,EAAE,qCAAqC,CAAC;SAClE,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CACL,SAAS,CAAC,KAAK,EAAE,EAAU,EAAE,MAAc,EAAE,IAAyC,EAAE,EAAE;QACxF,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,0BAA0B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5F,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC,CAAC,CACH,CAAC;AACN,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,qBAAqB,EAAE,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
declare function isValidRequestStatus(status: string): boolean;
|
|
3
|
+
declare function isValidRequestType(type: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Every status name is phrased from the **Client's** side, because that is who
|
|
6
|
+
* reads it on their own dashboard — so the one status an agency user is most
|
|
7
|
+
* likely to misread is `waiting-on-you`, which means waiting on *them*, not on
|
|
8
|
+
* you. Printed alongside the raw name rather than instead of it: the raw name
|
|
9
|
+
* is what goes back into `request status`.
|
|
10
|
+
*/
|
|
11
|
+
declare const STAFF_LABELS: Record<string, string>;
|
|
12
|
+
type Assignment = {
|
|
13
|
+
ok: true;
|
|
14
|
+
value: 'any' | 'unassigned' | 'mine';
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
error: string;
|
|
18
|
+
};
|
|
19
|
+
/** `--mine` and `--unassigned` ask opposite questions, so passing both is a mistake rather than a narrowing. */
|
|
20
|
+
declare function resolveAssignment(opts: {
|
|
21
|
+
mine?: boolean;
|
|
22
|
+
unassigned?: boolean;
|
|
23
|
+
}): Assignment;
|
|
24
|
+
/**
|
|
25
|
+
* RFC 0005 — the Request queue, as the CLI sees it.
|
|
26
|
+
*
|
|
27
|
+
* A Request is a Client asking the Agency for work, so unlike every other
|
|
28
|
+
* command group here nothing about it lives in a Site's repo. The whole surface
|
|
29
|
+
* is staff-only for that reason: a thread carries internal notes, and claiming
|
|
30
|
+
* one only means something from inside the Agency.
|
|
31
|
+
*
|
|
32
|
+
* There is deliberately no `create` — a Request comes from the Client, through
|
|
33
|
+
* their own dashboard. Filing one on their behalf would put words in their mouth
|
|
34
|
+
* and make the queue's own numbers a measure of us rather than of them.
|
|
35
|
+
*/
|
|
36
|
+
export declare function registerRequestCommands(program: Command): void;
|
|
37
|
+
export { isValidRequestStatus, isValidRequestType, resolveAssignment, STAFF_LABELS };
|
|
38
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/commands/request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,iBAAS,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,iBAAS,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;;;GAMG;AACH,QAAA,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAMxC,CAAC;AAEF,KAAK,UAAU,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,KAAK,GAAG,YAAY,GAAG,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpG,gHAAgH;AAChH,iBAAS,iBAAiB,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,UAAU,CAOrF;AA+BD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwK9D;AAED,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { REQUEST_STATUSES, REQUEST_TYPES } from '../lib/client.js';
|
|
2
|
+
import { printResult, requireClient, runAction } from '../lib/cli-utils.js';
|
|
3
|
+
function isValidRequestStatus(status) {
|
|
4
|
+
return REQUEST_STATUSES.includes(status);
|
|
5
|
+
}
|
|
6
|
+
function isValidRequestType(type) {
|
|
7
|
+
return REQUEST_TYPES.includes(type);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Every status name is phrased from the **Client's** side, because that is who
|
|
11
|
+
* reads it on their own dashboard — so the one status an agency user is most
|
|
12
|
+
* likely to misread is `waiting-on-you`, which means waiting on *them*, not on
|
|
13
|
+
* you. Printed alongside the raw name rather than instead of it: the raw name
|
|
14
|
+
* is what goes back into `request status`.
|
|
15
|
+
*/
|
|
16
|
+
const STAFF_LABELS = {
|
|
17
|
+
open: 'Open — nobody has picked it up',
|
|
18
|
+
'in-progress': 'In progress',
|
|
19
|
+
'waiting-on-you': 'Waiting on client',
|
|
20
|
+
done: 'Done',
|
|
21
|
+
cancelled: 'Cancelled',
|
|
22
|
+
};
|
|
23
|
+
/** `--mine` and `--unassigned` ask opposite questions, so passing both is a mistake rather than a narrowing. */
|
|
24
|
+
function resolveAssignment(opts) {
|
|
25
|
+
if (opts.mine && opts.unassigned) {
|
|
26
|
+
return { ok: false, error: '--mine and --unassigned are mutually exclusive' };
|
|
27
|
+
}
|
|
28
|
+
if (opts.mine)
|
|
29
|
+
return { ok: true, value: 'mine' };
|
|
30
|
+
if (opts.unassigned)
|
|
31
|
+
return { ok: true, value: 'unassigned' };
|
|
32
|
+
return { ok: true, value: 'any' };
|
|
33
|
+
}
|
|
34
|
+
function authorOf(thread, message) {
|
|
35
|
+
if (!message.authorUserId)
|
|
36
|
+
return 'Scribe';
|
|
37
|
+
return thread.actors[message.authorUserId]?.name ?? 'Someone';
|
|
38
|
+
}
|
|
39
|
+
function printThread(thread) {
|
|
40
|
+
const { request } = thread;
|
|
41
|
+
console.log(`${request.subject}`);
|
|
42
|
+
console.log(`Site: ${thread.site ?? request.siteId} Type: ${request.type}`);
|
|
43
|
+
console.log(`Status: ${request.status} (${STAFF_LABELS[request.status] ?? request.status})`);
|
|
44
|
+
console.log(`Assigned: ${request.assignedToUserId ? (thread.actors[request.assignedToUserId]?.name ?? 'someone') : 'nobody'}`);
|
|
45
|
+
if (request.page)
|
|
46
|
+
console.log(`Page: ${request.page}`);
|
|
47
|
+
console.log(`Opened: ${request.createdAt} Last activity: ${request.lastActivityAt}`);
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log(request.body);
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log('Thread:');
|
|
52
|
+
if (thread.messages.length === 0) {
|
|
53
|
+
console.log(' (nothing yet — the request body above is the whole of it)');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
for (const message of thread.messages) {
|
|
57
|
+
// The internal marker is first, not a suffix: it decides whether the line
|
|
58
|
+
// can be quoted back to the Client at all.
|
|
59
|
+
const tags = [message.internal ? 'INTERNAL' : null, message.kind === 'system' ? 'system' : null].filter(Boolean).join(' ');
|
|
60
|
+
console.log(` [${message.createdAt}] ${tags ? `(${tags}) ` : ''}${authorOf(thread, message)}: ${message.body}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* RFC 0005 — the Request queue, as the CLI sees it.
|
|
65
|
+
*
|
|
66
|
+
* A Request is a Client asking the Agency for work, so unlike every other
|
|
67
|
+
* command group here nothing about it lives in a Site's repo. The whole surface
|
|
68
|
+
* is staff-only for that reason: a thread carries internal notes, and claiming
|
|
69
|
+
* one only means something from inside the Agency.
|
|
70
|
+
*
|
|
71
|
+
* There is deliberately no `create` — a Request comes from the Client, through
|
|
72
|
+
* their own dashboard. Filing one on their behalf would put words in their mouth
|
|
73
|
+
* and make the queue's own numbers a measure of us rather than of them.
|
|
74
|
+
*/
|
|
75
|
+
export function registerRequestCommands(program) {
|
|
76
|
+
const request = program.command('request').description('Work the queue of requests clients have sent in');
|
|
77
|
+
request
|
|
78
|
+
.command('list')
|
|
79
|
+
.description('List requests, oldest activity first — the queue, worked from the end that has waited longest')
|
|
80
|
+
.option('--site <slug>', 'Only this site\'s requests')
|
|
81
|
+
.option('--status <status>', `one of: ${REQUEST_STATUSES.join(', ')}`)
|
|
82
|
+
.option('--type <type>', `one of: ${REQUEST_TYPES.join(', ')}`)
|
|
83
|
+
.option('--mine', 'Only requests this key\'s owner has claimed')
|
|
84
|
+
.option('--unassigned', 'Only requests nobody has claimed yet')
|
|
85
|
+
.option('--all', 'Include finished requests (done, cancelled), which are hidden by default')
|
|
86
|
+
.option('--sort <sort>', 'oldest (default) or newest, by last activity')
|
|
87
|
+
.option('--page <n>', 'Page number, 1-based')
|
|
88
|
+
.option('--json', 'Output as JSON')
|
|
89
|
+
.action(runAction(async (opts) => {
|
|
90
|
+
if (opts.status && !isValidRequestStatus(opts.status)) {
|
|
91
|
+
console.error(`--status must be one of: ${REQUEST_STATUSES.join(', ')}`);
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (opts.type && !isValidRequestType(opts.type)) {
|
|
96
|
+
console.error(`--type must be one of: ${REQUEST_TYPES.join(', ')}`);
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (opts.sort && opts.sort !== 'newest' && opts.sort !== 'oldest') {
|
|
101
|
+
console.error('--sort must be newest or oldest');
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const assignment = resolveAssignment(opts);
|
|
106
|
+
if (!assignment.ok) {
|
|
107
|
+
console.error(assignment.error);
|
|
108
|
+
process.exitCode = 1;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const result = await requireClient().listRequests({
|
|
112
|
+
site: opts.site,
|
|
113
|
+
status: opts.status,
|
|
114
|
+
type: opts.type,
|
|
115
|
+
assignment: assignment.value,
|
|
116
|
+
includeClosed: opts.all,
|
|
117
|
+
sort: opts.sort,
|
|
118
|
+
page: opts.page ? Number(opts.page) : undefined,
|
|
119
|
+
});
|
|
120
|
+
if (opts.json) {
|
|
121
|
+
printResult(result, true);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// An empty queue is the most common answer there is, and
|
|
125
|
+
// `console.table([])` renders it as an empty frame that reads like a
|
|
126
|
+
// broken command rather than good news.
|
|
127
|
+
if (result.items.length === 0) {
|
|
128
|
+
console.log(opts.all || opts.status ? 'No requests match.' : 'Nothing in the queue.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
printResult(result.items.map((item) => ({
|
|
132
|
+
id: item.id,
|
|
133
|
+
site: item.site ?? '',
|
|
134
|
+
subject: item.subject,
|
|
135
|
+
type: item.type,
|
|
136
|
+
status: item.status,
|
|
137
|
+
claimed: item.assignedToUserId ? 'yes' : 'no',
|
|
138
|
+
lastActivity: item.lastActivityAt,
|
|
139
|
+
})), false);
|
|
140
|
+
// The total is the only way to know there is a page 2, and a queue
|
|
141
|
+
// that silently showed its first 25 of 60 is the way one stops being
|
|
142
|
+
// believed.
|
|
143
|
+
if (result.total > result.items.length) {
|
|
144
|
+
console.log(`\nShowing ${result.items.length} of ${result.total} — pass --page 2 for more.`);
|
|
145
|
+
}
|
|
146
|
+
}));
|
|
147
|
+
request
|
|
148
|
+
.command('show')
|
|
149
|
+
.description('Show one request: the ask, its status, and the whole thread including internal notes')
|
|
150
|
+
.argument('<id>')
|
|
151
|
+
.option('--json', 'Output as JSON')
|
|
152
|
+
.action(runAction(async (id, opts) => {
|
|
153
|
+
const thread = await requireClient().getRequest(id);
|
|
154
|
+
if (opts.json) {
|
|
155
|
+
printResult(thread, true);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
printThread(thread);
|
|
159
|
+
}));
|
|
160
|
+
request
|
|
161
|
+
.command('claim')
|
|
162
|
+
.description("Claim a request for this key's owner, or release it with --release")
|
|
163
|
+
.argument('<id>')
|
|
164
|
+
.option('--release', 'Hand it back to nobody instead of claiming it')
|
|
165
|
+
.option('--json', 'Output as JSON')
|
|
166
|
+
.action(runAction(async (id, opts) => {
|
|
167
|
+
const thread = await requireClient().updateRequest(id, { assignTo: opts.release ? null : 'me' });
|
|
168
|
+
if (opts.json) {
|
|
169
|
+
printResult(thread, true);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.log(opts.release ? `Released: ${thread.request.subject}` : `Claimed: ${thread.request.subject}`);
|
|
173
|
+
}));
|
|
174
|
+
request
|
|
175
|
+
.command('reply')
|
|
176
|
+
.description('Reply to the client on a request, or add an Agency-only note with --internal')
|
|
177
|
+
.argument('<id>')
|
|
178
|
+
.requiredOption('--message <text>', 'What to say')
|
|
179
|
+
.option('--internal', 'An internal note: never shown to the client, and emails nobody')
|
|
180
|
+
.option('--json', 'Output as JSON')
|
|
181
|
+
.action(runAction(async (id, opts) => {
|
|
182
|
+
const thread = await requireClient().replyToRequest(id, { body: opts.message, internal: opts.internal });
|
|
183
|
+
if (opts.json) {
|
|
184
|
+
printResult(thread, true);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
console.log(opts.internal ? 'Note added (internal — the client will not see it).' : 'Replied — the client has been emailed.');
|
|
188
|
+
// A visible reply on an `open` request also picks it up, which is a
|
|
189
|
+
// status change the caller didn't ask for and should hear about.
|
|
190
|
+
if (thread.movedTo)
|
|
191
|
+
console.log(`Status is now: ${thread.movedTo}`);
|
|
192
|
+
}));
|
|
193
|
+
request
|
|
194
|
+
.command('status')
|
|
195
|
+
.description('Move a request to another status')
|
|
196
|
+
.argument('<id>')
|
|
197
|
+
.argument('<status>', REQUEST_STATUSES.join(', '))
|
|
198
|
+
.option('--json', 'Output as JSON')
|
|
199
|
+
.action(runAction(async (id, status, opts) => {
|
|
200
|
+
if (!isValidRequestStatus(status)) {
|
|
201
|
+
console.error(`status must be one of: ${REQUEST_STATUSES.join(', ')}`);
|
|
202
|
+
process.exitCode = 1;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const thread = await requireClient().updateRequest(id, { status });
|
|
206
|
+
if (opts.json) {
|
|
207
|
+
printResult(thread, true);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
console.log(`${thread.request.subject} is now ${thread.request.status} (${STAFF_LABELS[thread.request.status] ?? ''}).`);
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
export { isValidRequestStatus, isValidRequestType, resolveAssignment, STAFF_LABELS };
|
|
214
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/commands/request.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/site.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* True when changing a Site's stored domain to `next` would be a **cutover**
|
|
4
|
+
* rather than a correction — i.e. the Site is still on its provisional
|
|
5
|
+
* `<owner>.github.io` Pages host and is being moved to a real hostname.
|
|
6
|
+
*
|
|
7
|
+
* `site update --domain` only rewrites the Mongo record and the Turnstile
|
|
8
|
+
* widget. It does not touch DNS, `public/CNAME`, or the repo's Pages settings,
|
|
9
|
+
* so using it for a cutover leaves the Site claiming a domain that serves
|
|
10
|
+
* nothing — the half-state `site promote` exists to prevent.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isDomainCutover(current: string, next: string, githubOwner: string): boolean;
|
|
2
13
|
export declare function registerSiteCommands(program: Command): void;
|
|
3
14
|
//# sourceMappingURL=site.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../../src/commands/site.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0CpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../../src/commands/site.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0CpC;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAG3F;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwX3D"}
|
package/dist/commands/site.js
CHANGED
|
@@ -35,6 +35,20 @@ function printDnsRecords(records, write = console.error) {
|
|
|
35
35
|
}
|
|
36
36
|
write('In Cloudflare the record must be DNS-only (grey cloud), not proxied.\n');
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* True when changing a Site's stored domain to `next` would be a **cutover**
|
|
40
|
+
* rather than a correction — i.e. the Site is still on its provisional
|
|
41
|
+
* `<owner>.github.io` Pages host and is being moved to a real hostname.
|
|
42
|
+
*
|
|
43
|
+
* `site update --domain` only rewrites the Mongo record and the Turnstile
|
|
44
|
+
* widget. It does not touch DNS, `public/CNAME`, or the repo's Pages settings,
|
|
45
|
+
* so using it for a cutover leaves the Site claiming a domain that serves
|
|
46
|
+
* nothing — the half-state `site promote` exists to prevent.
|
|
47
|
+
*/
|
|
48
|
+
export function isDomainCutover(current, next, githubOwner) {
|
|
49
|
+
const pagesHost = `${githubOwner}.github.io`.toLowerCase();
|
|
50
|
+
return current.toLowerCase() === pagesHost && next.toLowerCase() !== pagesHost;
|
|
51
|
+
}
|
|
38
52
|
export function registerSiteCommands(program) {
|
|
39
53
|
const site = program.command('site').description('Manage sites');
|
|
40
54
|
site
|
|
@@ -51,7 +65,7 @@ export function registerSiteCommands(program) {
|
|
|
51
65
|
.command('add')
|
|
52
66
|
.description('Provision a new site: create its GitHub repo, push a starter template, and register it')
|
|
53
67
|
.requiredOption('--slug <slug>', 'Site slug (also used as the repo name)')
|
|
54
|
-
.option('--domain <domain>', 'Site domain (defaults to the GitHub Pages host —
|
|
68
|
+
.option('--domain <domain>', 'Site domain (defaults to the GitHub Pages host — take the site live on the real one later with `scribe site promote`)')
|
|
55
69
|
.option('--turnstile-key <key>', 'Cloudflare Turnstile site key (defaults to the server-configured shared widget)')
|
|
56
70
|
.option('--name <name>', 'Display name (defaults to the slug)')
|
|
57
71
|
.option('--form-collection <name>', 'Name for the default form/inbox collection (defaults to "inquiries")')
|
|
@@ -71,10 +85,11 @@ export function registerSiteCommands(program) {
|
|
|
71
85
|
}));
|
|
72
86
|
site
|
|
73
87
|
.command('update')
|
|
74
|
-
.description("
|
|
88
|
+
.description("Correct a site's stored settings: its domain and/or Turnstile site key")
|
|
75
89
|
.argument('<slug>', 'Site slug')
|
|
76
90
|
.option('--turnstile-key <key>', 'Cloudflare Turnstile site key')
|
|
77
|
-
.option('--domain <domain>', "
|
|
91
|
+
.option('--domain <domain>', "Correct the site's stored hostname (record + Turnstile only). To take a site live on a new domain use `scribe site promote`")
|
|
92
|
+
.option('--record-only', 'Allow --domain to move a site off its github.io host without doing the cutover')
|
|
78
93
|
.action(runAction(async (slug, opts) => {
|
|
79
94
|
if (!opts.turnstileKey && !opts.domain) {
|
|
80
95
|
console.error('Nothing to update — pass --turnstile-key and/or --domain.');
|
|
@@ -82,6 +97,29 @@ export function registerSiteCommands(program) {
|
|
|
82
97
|
return;
|
|
83
98
|
}
|
|
84
99
|
const client = requireClient();
|
|
100
|
+
// A cutover reached through `update` is the classic half-move: the
|
|
101
|
+
// record names a domain that DNS, Pages and `public/CNAME` know
|
|
102
|
+
// nothing about, so the site 404s on the new host while still
|
|
103
|
+
// reporting healthy. Refuse rather than warn — a warning on stderr is
|
|
104
|
+
// exactly what gets scrolled past.
|
|
105
|
+
if (opts.domain && !opts.recordOnly) {
|
|
106
|
+
const existing = await client
|
|
107
|
+
.listSites()
|
|
108
|
+
.then((sites) => sites.find((s) => s.slug === slug))
|
|
109
|
+
// A lookup failure shouldn't block a legitimate correction.
|
|
110
|
+
.catch(() => undefined);
|
|
111
|
+
if (existing && isDomainCutover(existing.domain, opts.domain, existing.githubOwner)) {
|
|
112
|
+
console.error(`"${slug}" is still on ${existing.domain}, so this is a cutover, not a correction.`);
|
|
113
|
+
console.error('`site update --domain` changes only the stored record and the Turnstile widget —');
|
|
114
|
+
console.error('it does not touch DNS, public/CNAME, or the repo\'s GitHub Pages settings, so the');
|
|
115
|
+
console.error('site would 404 on the new domain. Use promote instead, which does all of it:\n');
|
|
116
|
+
console.error(` scribe site promote ${slug} --domain ${opts.domain} --dns-only # check DNS, change nothing`);
|
|
117
|
+
console.error(` scribe site promote ${slug} --domain ${opts.domain} # cut over\n`);
|
|
118
|
+
console.error('Pass --record-only if you really do just want to rewrite the stored value.');
|
|
119
|
+
process.exitCode = 1;
|
|
120
|
+
throw new CliExit();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
85
123
|
const result = await client.updateSite(slug, {
|
|
86
124
|
turnstileSiteKey: opts.turnstileKey,
|
|
87
125
|
domain: opts.domain,
|