@primitivedotdev/cli 0.37.0 → 1.0.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.
|
@@ -949,6 +949,22 @@ function deleteCliCredentials(configDir) {
|
|
|
949
949
|
rmSync(credentialsPath(configDir), { force: true });
|
|
950
950
|
deleteChatState(configDir);
|
|
951
951
|
}
|
|
952
|
+
function saveSignupCredentials(params) {
|
|
953
|
+
deleteChatState(params.configDir);
|
|
954
|
+
saveCliCredentials(params.configDir, {
|
|
955
|
+
access_token: params.signup.access_token,
|
|
956
|
+
api_base_url: params.apiBaseUrl,
|
|
957
|
+
auth_method: "oauth",
|
|
958
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
959
|
+
expires_at: cliAccessTokenExpiresAt(params.signup.expires_in),
|
|
960
|
+
oauth_client_id: params.signup.oauth_client_id,
|
|
961
|
+
oauth_grant_id: params.signup.oauth_grant_id,
|
|
962
|
+
org_id: params.signup.org_id,
|
|
963
|
+
org_name: params.signup.org_name,
|
|
964
|
+
refresh_token: params.signup.refresh_token,
|
|
965
|
+
token_type: params.signup.token_type
|
|
966
|
+
});
|
|
967
|
+
}
|
|
952
968
|
function deleteCliCredentialsLock(configDir) {
|
|
953
969
|
rmSync(credentialsLockPath(configDir), {
|
|
954
970
|
force: true,
|
|
@@ -1311,4 +1327,4 @@ function redactCliEnvironment(environment) {
|
|
|
1311
1327
|
};
|
|
1312
1328
|
}
|
|
1313
1329
|
//#endregion
|
|
1314
|
-
export {
|
|
1330
|
+
export { PrimitiveApiClient as A, saveCliCredentials as C, loadActiveChatState as D, deleteChatState as E, createConfig as M, loadChatConversationByLocalId as O, resolveCliAuth as S, chatStatePath as T, deleteCliCredentials as _, normalizeCliEnvironmentName as a, loadCliCredentials as b, resolveConfigEnvironment as c, validateCliHeaderName as d, validateCliHeaderValue as f, credentialsPath as g, credentialsLockPath as h, loadCliConfig as i, createClient as j, saveActiveChatState as k, saveCliConfig as l, cliAccessTokenExpiresAt as m, deleteCliConfig as n, redactCliEnvironment as o, acquireCliCredentialsLock as p, emptyCliConfig as r, removeCliEnvironment as s, DEFAULT_ENVIRONMENT as t, upsertCliEnvironment as u, deleteCliCredentialsLock as v, saveSignupCredentials as w, normalizeApiBaseUrl as x, detectPrimitiveKeyEnvMisname as y };
|
package/dist/oclif/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as PrimitiveApiClient, C as saveCliCredentials, D as loadActiveChatState, E as deleteChatState, M as createConfig, O as loadChatConversationByLocalId, S as resolveCliAuth, T as chatStatePath, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as loadCliCredentials, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as createClient, k as saveActiveChatState, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveSignupCredentials, x as normalizeApiBaseUrl, y as detectPrimitiveKeyEnvMisname } from "../cli-config-B5hrwe8q.js";
|
|
2
2
|
import { Args, Command, Errors, Flags, ux } from "@oclif/core";
|
|
3
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { hostname } from "node:os";
|
|
@@ -14570,6 +14570,16 @@ const OPERATION_HINTS = {
|
|
|
14570
14570
|
verifyAgentSignup: "Tip: pass --verification-code <code> (or --code; both work). The response carries OAuth tokens but not your assigned inbox domain; run `primitive domains list` (or `primitive whoami`) after success to see the managed *.primitive.email address that routes to this account."
|
|
14571
14571
|
};
|
|
14572
14572
|
const OPERATION_FLAG_ALIASES = { verifyAgentSignup: { verification_code: ["code"] } };
|
|
14573
|
+
const OPERATION_SUCCESS_HOOKS = { verifyAgentSignup: ({ envelope, configDir, apiBaseUrl, writeStderr }) => {
|
|
14574
|
+
const data = envelope?.data;
|
|
14575
|
+
if (!data?.access_token || !data?.refresh_token) return;
|
|
14576
|
+
saveSignupCredentials({
|
|
14577
|
+
apiBaseUrl,
|
|
14578
|
+
configDir,
|
|
14579
|
+
signup: data
|
|
14580
|
+
});
|
|
14581
|
+
writeStderr("Credentials saved to the CLI config; `primitive whoami` will work on the next call.\n");
|
|
14582
|
+
} };
|
|
14573
14583
|
function createOperationCommand(operation) {
|
|
14574
14584
|
const { flags, bodyFieldFlagToProperty } = buildFlags(operation);
|
|
14575
14585
|
const baseDescription = operation.description !== null && operation.description !== void 0 ? canonicalizeCliReferences(operation.description) : `${operation.method} ${operation.path}`;
|
|
@@ -14663,6 +14673,20 @@ function createOperationCommand(operation) {
|
|
|
14663
14673
|
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
14664
14674
|
process.stderr.write(chunk);
|
|
14665
14675
|
} });
|
|
14676
|
+
const successHook = OPERATION_SUCCESS_HOOKS[operation.sdkName];
|
|
14677
|
+
if (successHook) try {
|
|
14678
|
+
successHook({
|
|
14679
|
+
envelope,
|
|
14680
|
+
configDir: this.config.configDir,
|
|
14681
|
+
apiBaseUrl: auth.apiBaseUrl,
|
|
14682
|
+
writeStderr: (chunk) => {
|
|
14683
|
+
process.stderr.write(chunk);
|
|
14684
|
+
}
|
|
14685
|
+
});
|
|
14686
|
+
} catch (hookError) {
|
|
14687
|
+
const detail = hookError instanceof Error ? hookError.message : String(hookError);
|
|
14688
|
+
process.stderr.write(`Warning: ${operation.sdkName} succeeded but its post-success hook threw (${detail}). The response below is still valid; act on it manually.\n`);
|
|
14689
|
+
}
|
|
14666
14690
|
this.log(JSON.stringify(operationOutputPayload(envelope, parsedFlags.envelope === true), null, 2));
|
|
14667
14691
|
if (isIncompleteDomainVerification(operation, envelope)) {
|
|
14668
14692
|
writeIncompleteDomainVerificationHint();
|
|
@@ -14718,6 +14742,173 @@ function readAttachmentFiles(paths, readFile = readFileSync) {
|
|
|
14718
14742
|
});
|
|
14719
14743
|
}
|
|
14720
14744
|
//#endregion
|
|
14745
|
+
//#region src/oclif/chat-lock.ts
|
|
14746
|
+
/**
|
|
14747
|
+
* Filesystem mutex around the chat-state read → POST → save sequence.
|
|
14748
|
+
*
|
|
14749
|
+
* Why this exists. The chat reply flow is read-modify-write across an
|
|
14750
|
+
* RTT to the API: `loadActiveChatState` → `replyToEmail` → poll → save.
|
|
14751
|
+
* Two concurrent invocations (e.g. the user re-runs `primitive chat
|
|
14752
|
+
* reply` before the first cycle finishes its 5–12 s poll) read the
|
|
14753
|
+
* same stale `last_reply_email_id` and POST to it, producing a
|
|
14754
|
+
* duplicate /v1/emails/{id}/reply that the server deduplicates by
|
|
14755
|
+
* content_hash. The second invocation then polls forever for a reply
|
|
14756
|
+
* that arrived in response to the *first* send and has already been
|
|
14757
|
+
* surfaced by the *first* invocation.
|
|
14758
|
+
*
|
|
14759
|
+
* The lock is per-process-config-dir, not per-conversation: holding it
|
|
14760
|
+
* for a few seconds while one chat reply completes is a reasonable UX
|
|
14761
|
+
* constraint and clearly explained on contention. The lock is
|
|
14762
|
+
* re-entrant within a single Node process (ChatReplyCommand wraps
|
|
14763
|
+
* ChatCommand internally), but rejects cross-process contention.
|
|
14764
|
+
*
|
|
14765
|
+
* Liveness. The lock file stores the holder's PID. On EEXIST we probe
|
|
14766
|
+
* the holder with `process.kill(pid, 0)`; if the holder is gone (e.g.
|
|
14767
|
+
* a previous chat invocation crashed without releasing), we steal the
|
|
14768
|
+
* lock. This avoids needing a heartbeat or mtime-based stale check,
|
|
14769
|
+
* either of which has its own race surface.
|
|
14770
|
+
*
|
|
14771
|
+
* Releases. The returned function is idempotent. Callers must call it
|
|
14772
|
+
* in a finally block. We also register process-exit / signal handlers
|
|
14773
|
+
* so a Ctrl-C during the poll loop still cleans up.
|
|
14774
|
+
*/
|
|
14775
|
+
const LOCK_FILENAME = "chat-state.lock";
|
|
14776
|
+
let processHolder = null;
|
|
14777
|
+
/**
|
|
14778
|
+
* Whether we've installed our exit / signal listeners on the
|
|
14779
|
+
* `process` object. Done lazily on first acquire and never undone:
|
|
14780
|
+
* adding handlers per-acquire would let them accumulate (each
|
|
14781
|
+
* acquire registers four listeners, and `process.once` can't be
|
|
14782
|
+
* un-once'd), which is harmless in production but produces
|
|
14783
|
+
* `MaxListenersExceededWarning` + cascading no-op fires in tests
|
|
14784
|
+
* that acquire/release across many `it()` blocks. Greptile P2.
|
|
14785
|
+
*
|
|
14786
|
+
* The handlers consult the current `processHolder` and act only if
|
|
14787
|
+
* a lock is actually held, so leaving them installed across
|
|
14788
|
+
* release boundaries is safe: a released or never-acquired lock
|
|
14789
|
+
* makes them no-ops.
|
|
14790
|
+
*/
|
|
14791
|
+
let exitListenersInstalled = false;
|
|
14792
|
+
function installExitListenersOnce() {
|
|
14793
|
+
if (exitListenersInstalled) return;
|
|
14794
|
+
exitListenersInstalled = true;
|
|
14795
|
+
const cleanup = () => {
|
|
14796
|
+
if (processHolder === null) return;
|
|
14797
|
+
try {
|
|
14798
|
+
unlinkSync(lockPath(processHolder.configDir));
|
|
14799
|
+
} catch {}
|
|
14800
|
+
processHolder = null;
|
|
14801
|
+
};
|
|
14802
|
+
process.on("exit", cleanup);
|
|
14803
|
+
for (const signal of [
|
|
14804
|
+
"SIGINT",
|
|
14805
|
+
"SIGTERM",
|
|
14806
|
+
"SIGHUP"
|
|
14807
|
+
]) {
|
|
14808
|
+
const handler = () => {
|
|
14809
|
+
cleanup();
|
|
14810
|
+
process.removeListener(signal, handler);
|
|
14811
|
+
process.kill(process.pid, signal);
|
|
14812
|
+
};
|
|
14813
|
+
process.on(signal, handler);
|
|
14814
|
+
}
|
|
14815
|
+
}
|
|
14816
|
+
function lockPath(configDir) {
|
|
14817
|
+
return join(configDir, LOCK_FILENAME);
|
|
14818
|
+
}
|
|
14819
|
+
/**
|
|
14820
|
+
* `process.kill(pid, 0)` returns true if the process exists and we
|
|
14821
|
+
* have permission to signal it. Throws ESRCH when the pid is gone,
|
|
14822
|
+
* EPERM if it exists but isn't ours. Both "exists" cases mean we
|
|
14823
|
+
* should NOT steal the lock; only ESRCH proves the holder is dead.
|
|
14824
|
+
*/
|
|
14825
|
+
function pidIsAlive(pid) {
|
|
14826
|
+
try {
|
|
14827
|
+
process.kill(pid, 0);
|
|
14828
|
+
return true;
|
|
14829
|
+
} catch (err) {
|
|
14830
|
+
if (err.code === "ESRCH") return false;
|
|
14831
|
+
return true;
|
|
14832
|
+
}
|
|
14833
|
+
}
|
|
14834
|
+
function readHolderPid(configDir) {
|
|
14835
|
+
try {
|
|
14836
|
+
const raw = readFileSync(lockPath(configDir), "utf8").trim();
|
|
14837
|
+
const pid = Number.parseInt(raw, 10);
|
|
14838
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
14839
|
+
} catch {
|
|
14840
|
+
return null;
|
|
14841
|
+
}
|
|
14842
|
+
}
|
|
14843
|
+
var ChatLockContentionError = class extends Error {
|
|
14844
|
+
constructor(holderPid) {
|
|
14845
|
+
super(`Another \`primitive chat\` invocation (pid ${holderPid}) is in progress. Wait for it to finish, or kill it before retrying.`);
|
|
14846
|
+
this.holderPid = holderPid;
|
|
14847
|
+
this.name = "ChatLockContentionError";
|
|
14848
|
+
}
|
|
14849
|
+
};
|
|
14850
|
+
/**
|
|
14851
|
+
* Acquire the chat-state mutex for this configDir. Returns a release
|
|
14852
|
+
* function that is safe to call any number of times. Throws
|
|
14853
|
+
* `ChatLockContentionError` if another live process holds the lock.
|
|
14854
|
+
*/
|
|
14855
|
+
function acquireChatLock(configDir) {
|
|
14856
|
+
if (processHolder?.configDir === configDir) {
|
|
14857
|
+
processHolder.depth += 1;
|
|
14858
|
+
let released = false;
|
|
14859
|
+
return () => {
|
|
14860
|
+
if (released) return;
|
|
14861
|
+
released = true;
|
|
14862
|
+
if (processHolder !== null) processHolder.depth -= 1;
|
|
14863
|
+
};
|
|
14864
|
+
}
|
|
14865
|
+
mkdirSync(configDir, {
|
|
14866
|
+
mode: 448,
|
|
14867
|
+
recursive: true
|
|
14868
|
+
});
|
|
14869
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
14870
|
+
let fd;
|
|
14871
|
+
try {
|
|
14872
|
+
fd = openSync(lockPath(configDir), "wx", 384);
|
|
14873
|
+
} catch (err) {
|
|
14874
|
+
if (err.code !== "EEXIST") throw err;
|
|
14875
|
+
const holder = readHolderPid(configDir);
|
|
14876
|
+
if (holder === null || !pidIsAlive(holder)) {
|
|
14877
|
+
try {
|
|
14878
|
+
unlinkSync(lockPath(configDir));
|
|
14879
|
+
} catch (unlinkErr) {
|
|
14880
|
+
if (unlinkErr.code !== "ENOENT") throw unlinkErr;
|
|
14881
|
+
}
|
|
14882
|
+
continue;
|
|
14883
|
+
}
|
|
14884
|
+
throw new ChatLockContentionError(holder);
|
|
14885
|
+
}
|
|
14886
|
+
writeSync(fd, `${process.pid}\n`);
|
|
14887
|
+
closeSync(fd);
|
|
14888
|
+
processHolder = {
|
|
14889
|
+
configDir,
|
|
14890
|
+
depth: 1
|
|
14891
|
+
};
|
|
14892
|
+
installExitListenersOnce();
|
|
14893
|
+
let released = false;
|
|
14894
|
+
return () => {
|
|
14895
|
+
if (released) return;
|
|
14896
|
+
released = true;
|
|
14897
|
+
if (processHolder?.configDir === configDir) {
|
|
14898
|
+
processHolder.depth -= 1;
|
|
14899
|
+
if (processHolder.depth <= 0) {
|
|
14900
|
+
try {
|
|
14901
|
+
unlinkSync(lockPath(configDir));
|
|
14902
|
+
} catch {}
|
|
14903
|
+
processHolder = null;
|
|
14904
|
+
}
|
|
14905
|
+
}
|
|
14906
|
+
};
|
|
14907
|
+
}
|
|
14908
|
+
/* v8 ignore next 4 -- the for-loop returns or throws on every iteration; the unreachable trailer keeps TS happy. */
|
|
14909
|
+
throw new Error("acquireChatLock: exhausted retries (this is a bug — should not be reachable)");
|
|
14910
|
+
}
|
|
14911
|
+
//#endregion
|
|
14721
14912
|
//#region src/oclif/outbound-defaults.ts
|
|
14722
14913
|
const SUBJECT_MAX_LENGTH = 200;
|
|
14723
14914
|
function deriveSubject(body) {
|
|
@@ -15031,7 +15222,7 @@ function matchDescription(strategy) {
|
|
|
15031
15222
|
return strategy === "strict" ? "strict, matched by reply_to_sent_email_id" : "fallback, matched by sender/time window";
|
|
15032
15223
|
}
|
|
15033
15224
|
function normalizeEmailAddress(value) {
|
|
15034
|
-
return value.trim().toLowerCase();
|
|
15225
|
+
return (value.match(/<([^>]+)>/)?.[1] ?? value).trim().toLowerCase();
|
|
15035
15226
|
}
|
|
15036
15227
|
function derivedReplySubject(parent) {
|
|
15037
15228
|
const subject = parent.subject?.trim();
|
|
@@ -15209,6 +15400,28 @@ function persistActiveChat(params) {
|
|
|
15209
15400
|
return null;
|
|
15210
15401
|
}
|
|
15211
15402
|
}
|
|
15403
|
+
/**
|
|
15404
|
+
* Pick the email id to surface from the one-shot strict search we run
|
|
15405
|
+
* when the server returned `idempotent_replay: true`. The search has
|
|
15406
|
+
* no `since` filter (the existing reply, if any, predates this
|
|
15407
|
+
* attempt's `sentAtIso`), so we may receive multiple historical
|
|
15408
|
+
* inbounds matching `reply_to_sent_email_id = sent.id` if the user
|
|
15409
|
+
* has been hammering the same content. Prefer the most recent
|
|
15410
|
+
* accepted/completed row; reject pending/processing rows the way the
|
|
15411
|
+
* normal poll does.
|
|
15412
|
+
*/
|
|
15413
|
+
async function resolveIdempotentReplayReply(page) {
|
|
15414
|
+
if (!page.ok || page.rows.length === 0) return null;
|
|
15415
|
+
let latest = null;
|
|
15416
|
+
for (const row of page.rows) {
|
|
15417
|
+
if (row.status !== "accepted" && row.status !== "completed") continue;
|
|
15418
|
+
if (latest === null || row.received_at.localeCompare(latest.receivedAt) > 0) latest = {
|
|
15419
|
+
id: row.id,
|
|
15420
|
+
receivedAt: row.received_at
|
|
15421
|
+
};
|
|
15422
|
+
}
|
|
15423
|
+
return latest?.id ?? null;
|
|
15424
|
+
}
|
|
15212
15425
|
function formatChatResponse(context) {
|
|
15213
15426
|
const accepted = context.sent.accepted.join(", ") || context.recipient;
|
|
15214
15427
|
const responseBody = resolveChatResponseBody(context.reply);
|
|
@@ -15423,181 +15636,243 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
15423
15636
|
const message = flags.reply !== void 0 ? flags.reply : args.message !== void 0 && args.message !== "" ? args.message : await readStdinToString();
|
|
15424
15637
|
if (!message.trim()) throw cliError$6(replyMode ? "Reply body is empty." : "Message body is empty.");
|
|
15425
15638
|
await runWithTiming(flags.time, async () => {
|
|
15426
|
-
|
|
15427
|
-
|
|
15428
|
-
|
|
15429
|
-
|
|
15430
|
-
|
|
15431
|
-
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15639
|
+
let releaseLock;
|
|
15640
|
+
try {
|
|
15641
|
+
releaseLock = acquireChatLock(this.config.configDir);
|
|
15642
|
+
} catch (err) {
|
|
15643
|
+
if (err instanceof ChatLockContentionError) throw cliError$6(err.message);
|
|
15644
|
+
throw err;
|
|
15645
|
+
}
|
|
15646
|
+
try {
|
|
15647
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
15648
|
+
apiKey: flags["api-key"],
|
|
15649
|
+
apiBaseUrl: flags["api-base-url"],
|
|
15650
|
+
configDir: this.config.configDir
|
|
15651
|
+
});
|
|
15652
|
+
const authFailureContext = {
|
|
15653
|
+
auth,
|
|
15654
|
+
baseUrlOverridden,
|
|
15655
|
+
configDir: this.config.configDir
|
|
15656
|
+
};
|
|
15657
|
+
const progress = flags.quiet ? null : new ChatProgressIndicator(process.stderr);
|
|
15658
|
+
const attachments = readAttachmentFiles(flags.attachment);
|
|
15659
|
+
let from;
|
|
15660
|
+
let parentReply;
|
|
15661
|
+
let subject;
|
|
15662
|
+
if (replyMode) {
|
|
15663
|
+
const replyContext = await (async () => {
|
|
15664
|
+
let replyContextFailureMessage = "Could not load reply context.";
|
|
15665
|
+
try {
|
|
15666
|
+
if (flags["reply-to-email-id"] !== void 0) {
|
|
15667
|
+
progress?.start(`Loading reply context for ${flags["reply-to-email-id"]}`);
|
|
15668
|
+
const exactParentReply = await loadInboundEmailDetail({
|
|
15669
|
+
apiClient,
|
|
15670
|
+
authFailureContext,
|
|
15671
|
+
id: flags["reply-to-email-id"]
|
|
15672
|
+
});
|
|
15673
|
+
replyContextFailureMessage = `Inbound email ${flags["reply-to-email-id"]} does not match recipient ${args.recipient}.`;
|
|
15674
|
+
assertParentMatchesRecipient(exactParentReply, args.recipient);
|
|
15675
|
+
return {
|
|
15676
|
+
from: flags.from ?? exactParentReply.to_email,
|
|
15677
|
+
parentReply: exactParentReply
|
|
15678
|
+
};
|
|
15679
|
+
}
|
|
15680
|
+
const replyFrom = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
15681
|
+
progress?.start(`Finding latest inbound email from ${args.recipient}`);
|
|
15682
|
+
const latestParentReply = await findLatestInboundFromRecipient({
|
|
15448
15683
|
apiClient,
|
|
15449
15684
|
authFailureContext,
|
|
15450
|
-
|
|
15685
|
+
from: replyFrom,
|
|
15686
|
+
pageSize: flags["page-size"],
|
|
15687
|
+
recipient: args.recipient
|
|
15451
15688
|
});
|
|
15452
|
-
|
|
15453
|
-
|
|
15689
|
+
if (!latestParentReply) {
|
|
15690
|
+
replyContextFailureMessage = "No prior inbound email found.";
|
|
15691
|
+
throw cliError$6(`No prior inbound email from ${args.recipient} to ${replyFrom}. Start a new chat with \`primitive chat ${args.recipient} <message>\`, pass --from, or pass --reply-to-email-id <inbound-email-id>.`);
|
|
15692
|
+
}
|
|
15693
|
+
replyContextFailureMessage = `Inbound email ${latestParentReply.id} does not match recipient ${args.recipient}.`;
|
|
15694
|
+
assertParentMatchesRecipient(latestParentReply, args.recipient);
|
|
15454
15695
|
return {
|
|
15455
|
-
from:
|
|
15456
|
-
parentReply:
|
|
15696
|
+
from: replyFrom,
|
|
15697
|
+
parentReply: latestParentReply
|
|
15457
15698
|
};
|
|
15699
|
+
} catch (error) {
|
|
15700
|
+
progress?.fail(replyContextFailureMessage);
|
|
15701
|
+
throw error;
|
|
15458
15702
|
}
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
15472
|
-
|
|
15473
|
-
|
|
15474
|
-
|
|
15475
|
-
|
|
15476
|
-
|
|
15477
|
-
|
|
15478
|
-
|
|
15479
|
-
|
|
15480
|
-
|
|
15481
|
-
|
|
15482
|
-
|
|
15483
|
-
|
|
15484
|
-
|
|
15485
|
-
|
|
15486
|
-
|
|
15487
|
-
|
|
15488
|
-
|
|
15489
|
-
|
|
15490
|
-
const sentAtIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
15491
|
-
if (replyMode) progress?.update(`Sending reply to ${args.recipient}`);
|
|
15492
|
-
else progress?.start(`Sending message to ${args.recipient}`);
|
|
15493
|
-
const sendResult = parentReply !== void 0 ? await replyToEmail({
|
|
15494
|
-
body: {
|
|
15495
|
-
body_text: message,
|
|
15496
|
-
from,
|
|
15497
|
-
...attachments !== void 0 ? { attachments } : {}
|
|
15498
|
-
},
|
|
15499
|
-
client: apiClient.client,
|
|
15500
|
-
path: { id: parentReply.id },
|
|
15501
|
-
responseStyle: "fields"
|
|
15502
|
-
}) : await sendEmail({
|
|
15503
|
-
body: {
|
|
15504
|
-
from,
|
|
15505
|
-
to: args.recipient,
|
|
15506
|
-
subject,
|
|
15507
|
-
body_text: message,
|
|
15508
|
-
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
|
|
15509
|
-
...attachments !== void 0 ? { attachments } : {}
|
|
15510
|
-
},
|
|
15511
|
-
client: apiClient.client,
|
|
15512
|
-
responseStyle: "fields"
|
|
15513
|
-
});
|
|
15514
|
-
if (sendResult.error) {
|
|
15515
|
-
progress?.fail(replyMode ? "Reply send failed." : "Message send failed.");
|
|
15516
|
-
const errorPayload = extractErrorPayload(sendResult.error);
|
|
15517
|
-
writeErrorWithHints(errorPayload);
|
|
15518
|
-
surfaceUnauthorizedHint({
|
|
15519
|
-
...authFailureContext,
|
|
15520
|
-
payload: errorPayload
|
|
15703
|
+
})();
|
|
15704
|
+
from = replyContext.from;
|
|
15705
|
+
parentReply = replyContext.parentReply;
|
|
15706
|
+
subject = derivedReplySubject(replyContext.parentReply);
|
|
15707
|
+
} else {
|
|
15708
|
+
from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
15709
|
+
subject = flags.subject ?? deriveSubject(message);
|
|
15710
|
+
}
|
|
15711
|
+
const sentAtIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
15712
|
+
if (replyMode) progress?.update(`Sending reply to ${args.recipient}`);
|
|
15713
|
+
else progress?.start(`Sending message to ${args.recipient}`);
|
|
15714
|
+
const sendResult = parentReply !== void 0 ? await replyToEmail({
|
|
15715
|
+
body: {
|
|
15716
|
+
body_text: message,
|
|
15717
|
+
from,
|
|
15718
|
+
...attachments !== void 0 ? { attachments } : {}
|
|
15719
|
+
},
|
|
15720
|
+
client: apiClient.client,
|
|
15721
|
+
path: { id: parentReply.id },
|
|
15722
|
+
responseStyle: "fields"
|
|
15723
|
+
}) : await sendEmail({
|
|
15724
|
+
body: {
|
|
15725
|
+
from,
|
|
15726
|
+
to: args.recipient,
|
|
15727
|
+
subject,
|
|
15728
|
+
body_text: message,
|
|
15729
|
+
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
|
|
15730
|
+
...attachments !== void 0 ? { attachments } : {}
|
|
15731
|
+
},
|
|
15732
|
+
client: apiClient.client,
|
|
15733
|
+
responseStyle: "fields"
|
|
15521
15734
|
});
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
|
|
15525
|
-
|
|
15526
|
-
|
|
15527
|
-
|
|
15528
|
-
|
|
15529
|
-
|
|
15530
|
-
|
|
15531
|
-
|
|
15532
|
-
|
|
15533
|
-
|
|
15534
|
-
|
|
15535
|
-
|
|
15536
|
-
|
|
15537
|
-
|
|
15538
|
-
|
|
15539
|
-
|
|
15540
|
-
recipient: args.recipient,
|
|
15541
|
-
sent,
|
|
15542
|
-
sentAtIso,
|
|
15543
|
-
strictOnly: flags["strict-only"],
|
|
15544
|
-
strictPhaseSeconds: flags["strict-phase-seconds"],
|
|
15545
|
-
subject,
|
|
15546
|
-
timeoutSeconds: flags.timeout
|
|
15547
|
-
};
|
|
15548
|
-
let replyResult;
|
|
15549
|
-
try {
|
|
15550
|
-
replyResult = await waitForReply({
|
|
15551
|
-
apiClient,
|
|
15552
|
-
authFailureContext,
|
|
15735
|
+
if (sendResult.error) {
|
|
15736
|
+
progress?.fail(replyMode ? "Reply send failed." : "Message send failed.");
|
|
15737
|
+
const errorPayload = extractErrorPayload(sendResult.error);
|
|
15738
|
+
writeErrorWithHints(errorPayload);
|
|
15739
|
+
surfaceUnauthorizedHint({
|
|
15740
|
+
...authFailureContext,
|
|
15741
|
+
payload: errorPayload
|
|
15742
|
+
});
|
|
15743
|
+
process.exitCode = 1;
|
|
15744
|
+
return;
|
|
15745
|
+
}
|
|
15746
|
+
const sent = sendResult.data?.data;
|
|
15747
|
+
if (!sent) {
|
|
15748
|
+
progress?.fail("Send succeeded but the API returned no data.");
|
|
15749
|
+
throw cliError$6("Send succeeded but the API returned no data.");
|
|
15750
|
+
}
|
|
15751
|
+
const replyAddress = sent.from || from;
|
|
15752
|
+
const baseContext = {
|
|
15553
15753
|
from: replyAddress,
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
progress.notice(message);
|
|
15558
|
-
return;
|
|
15559
|
-
}
|
|
15560
|
-
process.stderr.write(`${message}\n`);
|
|
15561
|
-
},
|
|
15562
|
-
pageSize: flags["page-size"],
|
|
15754
|
+
json: flags.json,
|
|
15755
|
+
parentReply,
|
|
15756
|
+
quiet: flags.quiet,
|
|
15563
15757
|
recipient: args.recipient,
|
|
15758
|
+
sent,
|
|
15564
15759
|
sentAtIso,
|
|
15565
|
-
sentId: sent.id,
|
|
15566
15760
|
strictOnly: flags["strict-only"],
|
|
15567
15761
|
strictPhaseSeconds: flags["strict-phase-seconds"],
|
|
15762
|
+
subject,
|
|
15763
|
+
timeoutSeconds: flags.timeout
|
|
15764
|
+
};
|
|
15765
|
+
if (sent.idempotent_replay) {
|
|
15766
|
+
progress?.update("Server returned idempotent_replay: looking up the existing reply");
|
|
15767
|
+
const replyId = await resolveIdempotentReplayReply(await fetchEmailSearchPage({
|
|
15768
|
+
apiClient,
|
|
15769
|
+
cursor: null,
|
|
15770
|
+
filters: { replyToSentEmailId: sent.id },
|
|
15771
|
+
pageSize: flags["page-size"]
|
|
15772
|
+
}));
|
|
15773
|
+
if (replyId) {
|
|
15774
|
+
const full = await getEmail({
|
|
15775
|
+
client: apiClient.client,
|
|
15776
|
+
path: { id: replyId },
|
|
15777
|
+
responseStyle: "fields"
|
|
15778
|
+
});
|
|
15779
|
+
if (full.error) {
|
|
15780
|
+
const payload = extractErrorPayload(full.error);
|
|
15781
|
+
writeErrorWithHints(payload);
|
|
15782
|
+
surfaceUnauthorizedHint({
|
|
15783
|
+
...authFailureContext,
|
|
15784
|
+
payload
|
|
15785
|
+
});
|
|
15786
|
+
throw cliError$6(`Idempotent replay: existing reply found but fetching it failed (id=${replyId}).`);
|
|
15787
|
+
}
|
|
15788
|
+
const envelope = full.data;
|
|
15789
|
+
const detail = envelope?.data ?? envelope ?? null;
|
|
15790
|
+
if (!detail) throw cliError$6(`Idempotent replay: existing reply body could not be loaded (id=${replyId}).`);
|
|
15791
|
+
progress?.succeed(`Idempotent replay; surfacing existing reply from ${detail.from_email ?? args.recipient}`);
|
|
15792
|
+
let outputContext = {
|
|
15793
|
+
...baseContext,
|
|
15794
|
+
matchStrategy: "strict",
|
|
15795
|
+
reply: detail
|
|
15796
|
+
};
|
|
15797
|
+
const localChatId = persistActiveChat({
|
|
15798
|
+
configDir: this.config.configDir,
|
|
15799
|
+
context: outputContext,
|
|
15800
|
+
preferredLocalId: flags["chat-local-id"],
|
|
15801
|
+
writeWarning: (message) => process.stderr.write(message)
|
|
15802
|
+
});
|
|
15803
|
+
if (localChatId !== null) outputContext = {
|
|
15804
|
+
...outputContext,
|
|
15805
|
+
localChatId
|
|
15806
|
+
};
|
|
15807
|
+
if (flags.json) this.log(JSON.stringify(buildChatJsonEnvelope(outputContext), null, 2));
|
|
15808
|
+
else this.log(formatChatResponse(outputContext));
|
|
15809
|
+
return;
|
|
15810
|
+
}
|
|
15811
|
+
progress?.fail("Server deduplicated this send (idempotent_replay: true).");
|
|
15812
|
+
process.stderr.write(`${chatNoticeText(`The server detected this exact content was sent earlier and did not put a new message on the wire. The original send (sent.id=${sent.id}) has not received a reply yet. Vary the body — or change the parent (reply to a different inbound) — to retry with a fresh send.`)}\n`);
|
|
15813
|
+
process.exitCode = 1;
|
|
15814
|
+
return;
|
|
15815
|
+
}
|
|
15816
|
+
progress?.update(`${replyMode ? "Reply" : "Message"} sent; waiting for reply from ${args.recipient}`, {
|
|
15817
|
+
heartbeatMs: 15e3,
|
|
15568
15818
|
timeoutSeconds: flags.timeout
|
|
15569
15819
|
});
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15820
|
+
let replyResult;
|
|
15821
|
+
try {
|
|
15822
|
+
replyResult = await waitForReply({
|
|
15823
|
+
apiClient,
|
|
15824
|
+
authFailureContext,
|
|
15825
|
+
from: replyAddress,
|
|
15826
|
+
interval: flags.interval,
|
|
15827
|
+
notice: (message) => {
|
|
15828
|
+
if (progress) {
|
|
15829
|
+
progress.notice(message);
|
|
15830
|
+
return;
|
|
15831
|
+
}
|
|
15832
|
+
process.stderr.write(`${message}\n`);
|
|
15833
|
+
},
|
|
15834
|
+
pageSize: flags["page-size"],
|
|
15835
|
+
recipient: args.recipient,
|
|
15836
|
+
sentAtIso,
|
|
15837
|
+
sentId: sent.id,
|
|
15838
|
+
strictOnly: flags["strict-only"],
|
|
15839
|
+
strictPhaseSeconds: flags["strict-phase-seconds"],
|
|
15840
|
+
timeoutSeconds: flags.timeout
|
|
15841
|
+
});
|
|
15842
|
+
} catch (error) {
|
|
15843
|
+
progress?.fail("Reply polling failed.");
|
|
15844
|
+
process.stderr.write(`${formatChatRecoveryContext(baseContext)}\n`);
|
|
15845
|
+
throw error;
|
|
15846
|
+
}
|
|
15847
|
+
if (replyResult === null) {
|
|
15848
|
+
const timeoutMessage = `Timed out after ${flags.timeout}s waiting for a reply from ${args.recipient}.`;
|
|
15849
|
+
progress?.fail(timeoutMessage);
|
|
15850
|
+
if (progress === null) process.stderr.write(`${timeoutMessage}\n`);
|
|
15851
|
+
process.stderr.write(`${formatChatRecoveryContext(baseContext)}\n`);
|
|
15852
|
+
process.exitCode = 1;
|
|
15853
|
+
return;
|
|
15854
|
+
}
|
|
15855
|
+
progress?.succeed(`Reply received from ${replyResult.reply.from_email}`);
|
|
15856
|
+
let outputContext = {
|
|
15857
|
+
...baseContext,
|
|
15858
|
+
matchStrategy: replyResult.matchStrategy,
|
|
15859
|
+
reply: replyResult.reply
|
|
15860
|
+
};
|
|
15861
|
+
const localChatId = persistActiveChat({
|
|
15862
|
+
configDir: this.config.configDir,
|
|
15863
|
+
context: outputContext,
|
|
15864
|
+
preferredLocalId: flags["chat-local-id"],
|
|
15865
|
+
writeWarning: (message) => process.stderr.write(message)
|
|
15866
|
+
});
|
|
15867
|
+
if (localChatId !== null) outputContext = {
|
|
15868
|
+
...outputContext,
|
|
15869
|
+
localChatId
|
|
15870
|
+
};
|
|
15871
|
+
if (flags.json) this.log(JSON.stringify(buildChatJsonEnvelope(outputContext), null, 2));
|
|
15872
|
+
else this.log(formatChatResponse(outputContext));
|
|
15873
|
+
} finally {
|
|
15874
|
+
releaseLock();
|
|
15582
15875
|
}
|
|
15583
|
-
progress?.succeed(`Reply received from ${replyResult.reply.from_email}`);
|
|
15584
|
-
let outputContext = {
|
|
15585
|
-
...baseContext,
|
|
15586
|
-
matchStrategy: replyResult.matchStrategy,
|
|
15587
|
-
reply: replyResult.reply
|
|
15588
|
-
};
|
|
15589
|
-
const localChatId = persistActiveChat({
|
|
15590
|
-
configDir: this.config.configDir,
|
|
15591
|
-
context: outputContext,
|
|
15592
|
-
preferredLocalId: flags["chat-local-id"],
|
|
15593
|
-
writeWarning: (message) => process.stderr.write(message)
|
|
15594
|
-
});
|
|
15595
|
-
if (localChatId !== null) outputContext = {
|
|
15596
|
-
...outputContext,
|
|
15597
|
-
localChatId
|
|
15598
|
-
};
|
|
15599
|
-
if (flags.json) this.log(JSON.stringify(buildChatJsonEnvelope(outputContext), null, 2));
|
|
15600
|
-
else this.log(formatChatResponse(outputContext));
|
|
15601
15876
|
});
|
|
15602
15877
|
}
|
|
15603
15878
|
};
|
|
@@ -15675,38 +15950,49 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
|
|
|
15675
15950
|
const positionalLocalId = flags.id === void 0 && args.message !== void 0 ? parseLocalChatIdArg(args.idOrMessage) : void 0;
|
|
15676
15951
|
if (flags.id === void 0 && args.message !== void 0 && positionalLocalId === null) throw cliError$6("When passing two positional arguments to `primitive chat reply`, the first must be a local chat id. Use `primitive chat reply '<message>'` for the active chat or `primitive chat reply --id <id> '<message>'` for a specific chat.");
|
|
15677
15952
|
if (flags.id !== void 0 && args.message !== void 0) throw cliError$6("With --id, pass the reply body as a single positional argument or pipe it via stdin.");
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
state.
|
|
15689
|
-
"
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
15707
|
-
|
|
15708
|
-
|
|
15709
|
-
|
|
15953
|
+
let release;
|
|
15954
|
+
try {
|
|
15955
|
+
release = acquireChatLock(this.config.configDir);
|
|
15956
|
+
} catch (err) {
|
|
15957
|
+
if (err instanceof ChatLockContentionError) throw cliError$6(err.message);
|
|
15958
|
+
throw err;
|
|
15959
|
+
}
|
|
15960
|
+
try {
|
|
15961
|
+
const localId = flags.id ?? (typeof positionalLocalId === "number" ? positionalLocalId : void 0);
|
|
15962
|
+
const state = localId === void 0 ? loadActiveChatState(this.config.configDir) : loadChatConversationByLocalId(this.config.configDir, localId);
|
|
15963
|
+
if (!state) throw cliError$6(localId === void 0 ? "No open chat. Start one with `primitive chat <email> '<message>'`." : `No local chat ${localId}. Start one with \`primitive chat <email> '<message>'\` or omit --id to use the active chat.`);
|
|
15964
|
+
const message = args.message !== void 0 ? args.message : args.idOrMessage !== void 0 && args.idOrMessage !== "" ? args.idOrMessage : await readStdinToString("No reply body provided. Pass the reply body as a positional argument or pipe it via stdin.");
|
|
15965
|
+
if (!message.trim()) throw cliError$6("Reply body is empty.");
|
|
15966
|
+
const argv = [
|
|
15967
|
+
state.recipient,
|
|
15968
|
+
"--reply",
|
|
15969
|
+
message,
|
|
15970
|
+
"--from",
|
|
15971
|
+
state.from,
|
|
15972
|
+
"--reply-to-email-id",
|
|
15973
|
+
state.last_reply_email_id,
|
|
15974
|
+
"--timeout",
|
|
15975
|
+
String(flags.timeout ?? state.timeout_seconds),
|
|
15976
|
+
"--strict-phase-seconds",
|
|
15977
|
+
String(flags["strict-phase-seconds"] ?? state.strict_phase_seconds),
|
|
15978
|
+
"--interval",
|
|
15979
|
+
String(flags.interval ?? 2),
|
|
15980
|
+
"--page-size",
|
|
15981
|
+
String(flags["page-size"] ?? 50),
|
|
15982
|
+
"--chat-local-id",
|
|
15983
|
+
String(state.local_id)
|
|
15984
|
+
];
|
|
15985
|
+
if (flags["api-key"] !== void 0) argv.push("--api-key", flags["api-key"]);
|
|
15986
|
+
if (flags["api-base-url"] !== void 0) argv.push("--api-base-url", flags["api-base-url"]);
|
|
15987
|
+
if (flags.json) argv.push("--json");
|
|
15988
|
+
if (flags.quiet) argv.push("--quiet");
|
|
15989
|
+
for (const attachment of flags.attachment ?? []) argv.push("--attachment", attachment);
|
|
15990
|
+
if (state.strict_only || flags["strict-only"]) argv.push("--strict-only");
|
|
15991
|
+
if (flags.time) argv.push("--time");
|
|
15992
|
+
await ChatCommand.run(argv, { root: this.config.root });
|
|
15993
|
+
} finally {
|
|
15994
|
+
release();
|
|
15995
|
+
}
|
|
15710
15996
|
}
|
|
15711
15997
|
};
|
|
15712
15998
|
async function waitForReply(params) {
|
|
@@ -17684,8 +17970,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
17684
17970
|
name: "Primitive Team",
|
|
17685
17971
|
url: "https://primitive.dev"
|
|
17686
17972
|
};
|
|
17687
|
-
const SDK_VERSION_RANGE = "^0.
|
|
17688
|
-
const CLI_VERSION_RANGE = "^0.
|
|
17973
|
+
const SDK_VERSION_RANGE = "^1.0.0";
|
|
17974
|
+
const CLI_VERSION_RANGE = "^1.0.0";
|
|
17689
17975
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
17690
17976
|
function renderHandler() {
|
|
17691
17977
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -20189,22 +20475,6 @@ async function checkExistingCredentials(params) {
|
|
|
20189
20475
|
}
|
|
20190
20476
|
throw cliError$2(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before ${copy.actionGerund}.`);
|
|
20191
20477
|
}
|
|
20192
|
-
function saveSignupCredentials(params) {
|
|
20193
|
-
deleteChatState(params.configDir);
|
|
20194
|
-
saveCliCredentials(params.configDir, {
|
|
20195
|
-
access_token: params.signup.access_token,
|
|
20196
|
-
api_base_url: params.apiBaseUrl,
|
|
20197
|
-
auth_method: "oauth",
|
|
20198
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20199
|
-
expires_at: cliAccessTokenExpiresAt(params.signup.expires_in),
|
|
20200
|
-
oauth_client_id: params.signup.oauth_client_id,
|
|
20201
|
-
oauth_grant_id: params.signup.oauth_grant_id,
|
|
20202
|
-
org_id: params.signup.org_id,
|
|
20203
|
-
org_name: params.signup.org_name,
|
|
20204
|
-
refresh_token: params.signup.refresh_token,
|
|
20205
|
-
token_type: params.signup.token_type
|
|
20206
|
-
});
|
|
20207
|
-
}
|
|
20208
20478
|
function writeStartInstructions(start, copy = DEFAULT_SIGNUP_COMMAND_COPY) {
|
|
20209
20479
|
process$1.stderr.write(`Sent a ${start.verification_code_length}-digit verification code to ${start.email}.\n`);
|
|
20210
20480
|
process$1.stderr.write(`The code expires in ${formatSignupSeconds(start.expires_in)}.\n`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as resolveConfigEnvironment, i as loadCliConfig, x as normalizeApiBaseUrl } from "../cli-config-
|
|
1
|
+
import { c as resolveConfigEnvironment, i as loadCliConfig, x as normalizeApiBaseUrl } from "../cli-config-B5hrwe8q.js";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitivedotdev/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|