agmsg-cloud 0.1.0-rc.6 → 0.1.0-rc.7
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/dist/src/commands/approve.js +15 -1
- package/dist/src/commands/login.js +33 -13
- package/dist/src/commands/sync.js +27 -7
- package/dist/src/config.js +42 -1
- package/dist/src/preflight.js +89 -3
- package/package.json +3 -2
|
@@ -360,7 +360,21 @@ args, deps = {}) {
|
|
|
360
360
|
done();
|
|
361
361
|
// Closed, not refunded: §4.1 keeps a success counted in the window.
|
|
362
362
|
closeAttempt(scope, requestId, 'succeeded', undefined, env);
|
|
363
|
-
|
|
363
|
+
// The LABEL, not the id.
|
|
364
|
+
//
|
|
365
|
+
// `device_id` is a UUID the approver cannot use: no subcommand of this
|
|
366
|
+
// CLI accepts one (derived from index.ts — approve, connect, fetch,
|
|
367
|
+
// login, logout, pull, recovery, request, sync, watch, whoami, and none
|
|
368
|
+
// take a device). So it fails all three tests for reaching a person: it
|
|
369
|
+
// decides nothing for them, it cannot help them repair anything, and the
|
|
370
|
+
// label they just compared eight digits against is what actually names
|
|
371
|
+
// the machine they approved (#275 — the operator gets the minimum, the
|
|
372
|
+
// rest belongs in a log).
|
|
373
|
+
//
|
|
374
|
+
// Not routed to a log instead, because this CLI has no log to route it
|
|
375
|
+
// to. Building one for a single value would be the wrong size; when a
|
|
376
|
+
// second diagnostic needs a home, that is the moment to give it one.
|
|
377
|
+
out(`approved; "${start.label}" can now read this team\n`);
|
|
364
378
|
}
|
|
365
379
|
finally {
|
|
366
380
|
rmSync(scratch, { recursive: true, force: true });
|
|
@@ -63,6 +63,7 @@ export async function cmdLogin(opts) {
|
|
|
63
63
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
64
64
|
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
65
65
|
const now = opts.now ?? (() => Date.now());
|
|
66
|
+
const out = opts.out ?? ((text) => void process.stdout.write(text));
|
|
66
67
|
const endpoint = (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, '');
|
|
67
68
|
// The flag, validated, before anything is asked. `settleMachineName` returns
|
|
68
69
|
// it verbatim when it is given, so nothing below needs the prompt to know
|
|
@@ -73,7 +74,7 @@ export async function cmdLogin(opts) {
|
|
|
73
74
|
// the thing it takes away is the moment the operator typed the destination —
|
|
74
75
|
// so the destination is printed instead. Nobody should have to guess which
|
|
75
76
|
// server their machine is about to be registered with.
|
|
76
|
-
|
|
77
|
+
out(`Connecting to ${new URL(endpoint).host}\n`);
|
|
77
78
|
const post = async (path, body, bearer) => {
|
|
78
79
|
try {
|
|
79
80
|
return await fetchImpl(`${endpoint}${path}`, {
|
|
@@ -115,7 +116,7 @@ export async function cmdLogin(opts) {
|
|
|
115
116
|
if (stored && (asked === undefined || stored.machineName === asked)) {
|
|
116
117
|
const res = await post('/v1/device/activate', {}, stored.secret);
|
|
117
118
|
if (res.ok) {
|
|
118
|
-
|
|
119
|
+
out(`Already signed in as machine "${stored.machineName}".\n`);
|
|
119
120
|
return;
|
|
120
121
|
}
|
|
121
122
|
const code = await errorCode(res);
|
|
@@ -148,16 +149,32 @@ export async function cmdLogin(opts) {
|
|
|
148
149
|
throw new Error(`could not start login: ${codeRes.status} ${code}`);
|
|
149
150
|
}
|
|
150
151
|
const grant = (await codeRes.json());
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
out(`\nOpen this page and check the code matches:\n\n`);
|
|
153
|
+
out(` ${grant.verification_uri_complete}\n\n`);
|
|
154
|
+
out(` code: ${grant.user_code}\n`);
|
|
155
|
+
out(` machine: ${machineName}\n\n`);
|
|
155
156
|
// Armed, never awaited: an approval done from a phone, or from a URL typed by
|
|
156
157
|
// hand, must still be noticed — so the poll below runs whether or not any key
|
|
157
158
|
// is ever pressed. `disarm` is bound to every exit path, because a live stdin
|
|
158
159
|
// listener would hold the process open after login has already finished.
|
|
159
160
|
const opener = (opts.armOpener ?? armEnterToOpen)(grant.verification_uri_complete, endpoint);
|
|
160
|
-
|
|
161
|
+
// SAYS THAT THE BLOCKING IS THE DESIGN, and that the two lines above are
|
|
162
|
+
// already usable.
|
|
163
|
+
//
|
|
164
|
+
// An agent held the URL and the code for 42 seconds waiting for this command
|
|
165
|
+
// to return before relaying them — and it cannot return until someone
|
|
166
|
+
// approves, which nobody can do without seeing what it has already printed.
|
|
167
|
+
// The one step that needs a person was the step the person was locked out of
|
|
168
|
+
// (issue 279).
|
|
169
|
+
//
|
|
170
|
+
// The prompt handed to an agent carries four sentences about this. They are a
|
|
171
|
+
// description of behaviour this output can state itself, and prose in a
|
|
172
|
+
// prompt does not reach anyone who did not read that prompt — someone running
|
|
173
|
+
// the command by hand gets nothing.
|
|
174
|
+
out(`Waiting for approval — nothing is granted until you approve it.\n`);
|
|
175
|
+
out(`This command will not return until then. That is expected, not a failure:\n` +
|
|
176
|
+
`the page and the code above are ready to use now — open them, do not wait\n` +
|
|
177
|
+
`for this to finish.\n`);
|
|
161
178
|
try {
|
|
162
179
|
return await pollUntilDecided(grant, {
|
|
163
180
|
post,
|
|
@@ -165,6 +182,7 @@ export async function cmdLogin(opts) {
|
|
|
165
182
|
now,
|
|
166
183
|
endpoint,
|
|
167
184
|
armOpener: opts.armOpener ?? armEnterToOpen,
|
|
185
|
+
out,
|
|
168
186
|
});
|
|
169
187
|
}
|
|
170
188
|
finally {
|
|
@@ -172,7 +190,7 @@ export async function cmdLogin(opts) {
|
|
|
172
190
|
}
|
|
173
191
|
}
|
|
174
192
|
async function pollUntilDecided(grant, ctx) {
|
|
175
|
-
const { post, sleep, now, endpoint, armOpener } = ctx;
|
|
193
|
+
const { post, sleep, now, endpoint, armOpener, out } = ctx;
|
|
176
194
|
// The poll is single-flight by construction: one loop, one request in flight.
|
|
177
195
|
// A concurrent second poll on the same grant would revoke the credential the
|
|
178
196
|
// first poll received.
|
|
@@ -198,7 +216,7 @@ async function pollUntilDecided(grant, ctx) {
|
|
|
198
216
|
if (err instanceof Unreachable) {
|
|
199
217
|
// Said out loud rather than swallowed: an operator watching a long wait
|
|
200
218
|
// should see that contact was lost and regained, not silence.
|
|
201
|
-
|
|
219
|
+
out(` (lost contact with the server, still waiting)\n`);
|
|
202
220
|
continue;
|
|
203
221
|
}
|
|
204
222
|
throw err;
|
|
@@ -227,7 +245,7 @@ async function pollUntilDecided(grant, ctx) {
|
|
|
227
245
|
if (!isOrgAddress(issued.org)) {
|
|
228
246
|
throw new Error('the server answered with an org address this build does not recognise — nothing was stored');
|
|
229
247
|
}
|
|
230
|
-
return finish(issued, endpoint, post);
|
|
248
|
+
return finish(issued, endpoint, post, out);
|
|
231
249
|
}
|
|
232
250
|
const body = await errorBody(res);
|
|
233
251
|
const code = body.code;
|
|
@@ -248,7 +266,9 @@ async function pollUntilDecided(grant, ctx) {
|
|
|
248
266
|
throw new Error(explained ? `login stopped: ${explained}` : `login failed: ${res.status} ${code}`);
|
|
249
267
|
}
|
|
250
268
|
}
|
|
251
|
-
async function finish(issued, endpoint, post
|
|
269
|
+
async function finish(issued, endpoint, post,
|
|
270
|
+
/** The caller's sink, so every line this command prints goes one place. */
|
|
271
|
+
out) {
|
|
252
272
|
const secret = secretFromCapabilityUrl(issued.capability_url);
|
|
253
273
|
// Durable write FIRST. If the process dies between here and activate, the
|
|
254
274
|
// credential is on disk and the machine can be activated by running login
|
|
@@ -270,8 +290,8 @@ async function finish(issued, endpoint, post) {
|
|
|
270
290
|
const code = await errorCode(res);
|
|
271
291
|
throw new Error(`the credential was saved but could not be activated (${res.status} ${code}) — run login again`);
|
|
272
292
|
}
|
|
273
|
-
|
|
274
|
-
|
|
293
|
+
out(`\nSigned in as machine "${issued.machine_name}".\n`);
|
|
294
|
+
out(`Its sync address is saved on this machine; no token to copy.\n`);
|
|
275
295
|
}
|
|
276
296
|
async function errorCode(res) {
|
|
277
297
|
return (await errorBody(res)).code;
|
|
@@ -87,13 +87,33 @@ export async function cmdSync(config, opts) {
|
|
|
87
87
|
// this machine may join, and a scary line about an unrelated call would
|
|
88
88
|
// compete with the instructions below.
|
|
89
89
|
}
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
// PROSE, NOT A BLOCK TO COPY — and what changes is what this command claims
|
|
91
|
+
// at this moment, not what it knows (#307).
|
|
92
|
+
//
|
|
93
|
+
// Nothing here has established that the team exists, and nothing can. The
|
|
94
|
+
// local store is what `connect` and `fetch` fail closed on, and a second
|
|
95
|
+
// machine does not have one — that absence is the premise of `sync`. The name
|
|
96
|
+
// does not decide it either: `agmsg_validate_team_name` bars `.` `..`, `/`,
|
|
97
|
+
// `\`, a leading `-` and control characters, so `<team>` is a VALID team name.
|
|
98
|
+
// The ceremony below is scoped to the org rather than the team (#148). The one
|
|
99
|
+
// remaining source, asking the service by name, answers nothing for every team
|
|
100
|
+
// today (#250).
|
|
101
|
+
//
|
|
102
|
+
// So this is not a fix, and the issue stays open. What it removes is a
|
|
103
|
+
// misreading: an indented, copyable command reads as "this is the step", and
|
|
104
|
+
// when the person had not filled the placeholder it put a literal `'<team>'`
|
|
105
|
+
// on the other machine, quoted and ready to paste. As a sentence it reads as
|
|
106
|
+
// what it is — something to ask for, on a machine that has the team, which is
|
|
107
|
+
// a condition the reader can check and this process cannot.
|
|
108
|
+
//
|
|
109
|
+
// The command still goes through `shellArg`: the name reaching it is arbitrary
|
|
110
|
+
// text either way.
|
|
111
|
+
out(` Ask a machine that already has "${opts.team}" to run ` +
|
|
112
|
+
`\`agmsg-cloud approve ${shellArg(opts.team)}\`, and compare the eight digits on ` +
|
|
113
|
+
'both screens before answering there.\n\n');
|
|
114
|
+
out(` This machine has not confirmed that "${opts.team}" is on the service — it has no\n` +
|
|
115
|
+
' way to, before the steps below. If that machine has no such team, stop here and\n' +
|
|
116
|
+
' connect it there first.\n\n');
|
|
97
117
|
// Everything the ceremony guarantees happens inside here: the commitment is
|
|
98
118
|
// pinned before anything opens, nothing is sealed or uploaded until the
|
|
99
119
|
// digits match, and this side refuses a transcript it cannot verify.
|
package/dist/src/config.js
CHANGED
|
@@ -23,7 +23,48 @@ function fromEnv(env) {
|
|
|
23
23
|
// `connect --preflight` is a dry run whose whole purpose is to be usable before
|
|
24
24
|
// anything is set up, so it must not be refused for not being signed in.
|
|
25
25
|
export function resolveScriptsDir(env = process.env) {
|
|
26
|
-
return env
|
|
26
|
+
return scriptsDirChoice(env).dir;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The resolution, with its PROVENANCE — because the path alone does not answer
|
|
30
|
+
* the question an operator is actually asking (issue 282).
|
|
31
|
+
*
|
|
32
|
+
* An empty value is treated as unset. `??` passes the empty string through, so
|
|
33
|
+
* `export AGMSG_SCRIPTS_DIR=` resolved to `""` and every script path became a
|
|
34
|
+
* bare filename — measured, not supposed:
|
|
35
|
+
*
|
|
36
|
+
* resolveScriptsDir({ AGMSG_SCRIPTS_DIR: '' }) -> ""
|
|
37
|
+
*
|
|
38
|
+
* That is the shape someone reaches for when they are trying to undo the
|
|
39
|
+
* override, and it left them further from the default rather than back at it.
|
|
40
|
+
*/
|
|
41
|
+
export function scriptsDirChoice(env = process.env) {
|
|
42
|
+
const given = env.AGMSG_SCRIPTS_DIR;
|
|
43
|
+
if (given !== undefined && given !== '')
|
|
44
|
+
return { dir: given, from: 'env' };
|
|
45
|
+
return { dir: defaultScriptsDir(), from: 'default' };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The install used when `AGMSG_SCRIPTS_DIR` says nothing.
|
|
49
|
+
*
|
|
50
|
+
* Split out because it is also the answer to "what is the OTHER install"
|
|
51
|
+
* (issue 279), and a second copy of the path would be free to drift from the
|
|
52
|
+
* one the resolver uses — which is exactly the disagreement being reported.
|
|
53
|
+
*
|
|
54
|
+
* `home` is a PARAMETER, not `env.HOME`. The first version read
|
|
55
|
+
* `env.HOME ?? homedir()` and called it injectable-but-equivalent, on the
|
|
56
|
+
* grounds that `homedir()` reads `$HOME` on POSIX. That does not close: this
|
|
57
|
+
* CLI targets Windows, where a POSIX-compatible shell sets `HOME` to something
|
|
58
|
+
* like `/c/Users/x` while `homedir()` returns the native path — so the
|
|
59
|
+
* "equivalent" version would have moved the default install location for every
|
|
60
|
+
* Git Bash user (raised in review).
|
|
61
|
+
*
|
|
62
|
+
* Production never passes it. A seam a caller must opt into cannot change what
|
|
63
|
+
* anyone runs; an environment variable that production already has is not a
|
|
64
|
+
* seam at all.
|
|
65
|
+
*/
|
|
66
|
+
export function defaultScriptsDir(home = homedir()) {
|
|
67
|
+
return join(home, '.agents', 'skills', 'agmsg', 'scripts');
|
|
27
68
|
}
|
|
28
69
|
/**
|
|
29
70
|
* THE credential decision. Every caller that needs to know whether this machine
|
package/dist/src/preflight.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
-
import { hasCredential } from './config.js';
|
|
3
|
+
import { defaultScriptsDir, hasCredential, scriptsDirChoice } from './config.js';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { platform } from 'node:process';
|
|
6
6
|
// What `connect` needs before it starts, checked all at once.
|
|
@@ -151,7 +151,17 @@ function toolsFor(scripts) {
|
|
|
151
151
|
python3: scripts.includes('remote.sh'),
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
|
-
export function preflight(scriptsDir, needs, env = process.env
|
|
154
|
+
export function preflight(scriptsDir, needs, env = process.env,
|
|
155
|
+
/**
|
|
156
|
+
* The home directory the default install would be under.
|
|
157
|
+
*
|
|
158
|
+
* Only ever passed by a check that needs to put a second install somewhere it
|
|
159
|
+
* owns. It is a parameter rather than `env.HOME` because this CLI runs on
|
|
160
|
+
* Windows too, where a POSIX-compatible shell sets `HOME` to a path
|
|
161
|
+
* `homedir()` does not return — reading it would have moved the default
|
|
162
|
+
* install for every Git Bash user in exchange for a testable line.
|
|
163
|
+
*/
|
|
164
|
+
home) {
|
|
155
165
|
const { command, scripts } = needs;
|
|
156
166
|
const requireConnect = needs.requireConnect ?? false;
|
|
157
167
|
const requirements = [];
|
|
@@ -229,8 +239,43 @@ export function preflight(scriptsDir, needs, env = process.env) {
|
|
|
229
239
|
command,
|
|
230
240
|
scriptsDir,
|
|
231
241
|
agmsgVersion: canConnect ? installedVersion(scriptsDir) : null,
|
|
242
|
+
scriptsDirFrom: provenance(env, home),
|
|
243
|
+
otherInstalls: otherInstalls(scriptsDir, env, home),
|
|
232
244
|
};
|
|
233
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* The install that exists at the location this command is NOT using.
|
|
248
|
+
*
|
|
249
|
+
* Only ever one, and only when `AGMSG_SCRIPTS_DIR` points somewhere other than
|
|
250
|
+
* the default: those are the two places `resolveScriptsDir` can name, so they
|
|
251
|
+
* are the two places worth reporting. `version.sh` is the marker rather than
|
|
252
|
+
* the directory itself — an empty `~/.agents/skills/agmsg/scripts` is not a
|
|
253
|
+
* second install, and calling it one would send someone to look at nothing.
|
|
254
|
+
*/
|
|
255
|
+
/**
|
|
256
|
+
* Which of the three states the resolution is in.
|
|
257
|
+
*
|
|
258
|
+
* Three, not two: the variable can be set AND point at the default, which is
|
|
259
|
+
* what someone has on the way to undoing an override. Collapsing it into "env"
|
|
260
|
+
* makes the output say "not the default location" about the default location.
|
|
261
|
+
*/
|
|
262
|
+
function provenance(env, home) {
|
|
263
|
+
const choice = scriptsDirChoice(env);
|
|
264
|
+
if (choice.from === 'default')
|
|
265
|
+
return 'default';
|
|
266
|
+
return choice.dir === defaultScriptsDir(home) ? 'env-at-default' : 'env';
|
|
267
|
+
}
|
|
268
|
+
function otherInstalls(scriptsDir, env, home) {
|
|
269
|
+
// Through the resolver's own accessor, so the two can never disagree about
|
|
270
|
+
// where "the default" is — a disagreement here would report the wrong
|
|
271
|
+
// directory as the one not in use.
|
|
272
|
+
const fallback = defaultScriptsDir(home);
|
|
273
|
+
if (scriptsDir === fallback)
|
|
274
|
+
return [];
|
|
275
|
+
if (!env.AGMSG_SCRIPTS_DIR)
|
|
276
|
+
return [];
|
|
277
|
+
return existsSync(join(fallback, 'version.sh')) ? [fallback] : [];
|
|
278
|
+
}
|
|
234
279
|
// The refusal in front of every command that shells out: one place decides how
|
|
235
280
|
// a missing prerequisite reads, so it reads the same whichever command found
|
|
236
281
|
// it.
|
|
@@ -262,7 +307,39 @@ export function formatPreflight(result) {
|
|
|
262
307
|
// decoration on a label; it is the answer to "which one did you look at".
|
|
263
308
|
lines.push('');
|
|
264
309
|
lines.push(` scripts directory: ${result.scriptsDir}`);
|
|
265
|
-
|
|
310
|
+
// WHAT PUT IT THERE, when something did.
|
|
311
|
+
//
|
|
312
|
+
// The hint below is advice to redirect the install. Printed while
|
|
313
|
+
// AGMSG_SCRIPTS_DIR is already set, it invites the reader to do the thing
|
|
314
|
+
// that is currently confusing them — "you could redirect this" when the truth
|
|
315
|
+
// is "this is redirected". So the two are alternatives, not a pair.
|
|
316
|
+
//
|
|
317
|
+
// Nothing extra when the default is in force: "this is the default" on every
|
|
318
|
+
// ordinary run is noise, and the sentence is only missing when something
|
|
319
|
+
// non-default is deciding.
|
|
320
|
+
if (result.scriptsDirFrom === 'env') {
|
|
321
|
+
lines.push(` chosen by AGMSG_SCRIPTS_DIR, not the default location.`);
|
|
322
|
+
}
|
|
323
|
+
else if (result.scriptsDirFrom === 'env-at-default') {
|
|
324
|
+
// The variable is set AND points at the default. Saying "not the default
|
|
325
|
+
// location" here would be false, and it is the state someone reaches while
|
|
326
|
+
// trying to undo an override — the one moment the sentence has to be exact.
|
|
327
|
+
lines.push(` chosen by AGMSG_SCRIPTS_DIR, which points at the default location.`);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
lines.push(` set AGMSG_SCRIPTS_DIR to check a different install.`);
|
|
331
|
+
}
|
|
332
|
+
// NAMES THE OTHER ONE, and says which of the two is in force.
|
|
333
|
+
//
|
|
334
|
+
// Without this the reader has to decide, and an agent that finds two installs
|
|
335
|
+
// stops and asks — the incident four lines of the handed-over prompt exist to
|
|
336
|
+
// prevent (issue 279). The answer is not a preference to be settled; it is
|
|
337
|
+
// already settled by the resolver, and this is it being said out loud.
|
|
338
|
+
for (const other of result.otherInstalls) {
|
|
339
|
+
lines.push('');
|
|
340
|
+
lines.push(` another install is here: ${other}`);
|
|
341
|
+
lines.push(` it is NOT the one in use — AGMSG_SCRIPTS_DIR decides, and it points above.`);
|
|
342
|
+
}
|
|
266
343
|
if (result.agmsgVersion !== null) {
|
|
267
344
|
// Named as what it is. Calling it "agmsg 1.1.11" would invite the reader to
|
|
268
345
|
// compare it with a release number, which is the thing it cannot be
|
|
@@ -274,6 +351,15 @@ export function formatPreflight(result) {
|
|
|
274
351
|
if (missing.length === 0) {
|
|
275
352
|
lines.push('');
|
|
276
353
|
lines.push(`Everything ${result.command} needs is here.`);
|
|
354
|
+
// SAYS THAT THIS IS THE CHECK.
|
|
355
|
+
//
|
|
356
|
+
// An agent told "make sure the prerequisites are met" checks them again
|
|
357
|
+
// unless something says not to — it has no way to know this output was the
|
|
358
|
+
// check rather than a summary of one. The prompt carries a sentence for
|
|
359
|
+
// exactly that ("do not check for tools yourself"), which is a property
|
|
360
|
+
// this output should have rather than a warning the reader must be given
|
|
361
|
+
// separately (issue 279).
|
|
362
|
+
lines.push(`This was the check. Nothing else needs testing before you run it.`);
|
|
277
363
|
return `${lines.join('\n')}\n`;
|
|
278
364
|
}
|
|
279
365
|
for (const r of missing) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agmsg-cloud",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.7",
|
|
4
4
|
"description": "Companion CLI for the agmsg cloud service: connect a team, join from another machine, and back up its keys.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"pretypecheck": "pnpm run build:deps",
|
|
34
34
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
35
35
|
"lint": "tsx scripts/check-printed-commands.ts && tsx scripts/check-dist-is-current.ts && tsx scripts/check-readme-commands.ts && tsx scripts/check-handles.ts",
|
|
36
|
-
"prepack": "pnpm run build && tsx scripts/check-dist-is-current.ts && tsx scripts/check-readme-commands.ts && tsx scripts/check-handles.ts"
|
|
36
|
+
"prepack": "pnpm run build && tsx scripts/check-dist-is-current.ts && tsx scripts/check-readme-commands.ts && tsx scripts/check-handles.ts",
|
|
37
|
+
"verify:published": "tsx scripts/verify-published.ts"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
40
|
"@agmsg-cloud/sas-core": "workspace:*",
|