primcli 1.2.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.
@@ -0,0 +1,71 @@
1
+ import { spawnSync } from "node:child_process";
2
+ //#region src/oclif/proxy-auto-detect.ts
3
+ const PROXY_ENV_VARS = [
4
+ "HTTP_PROXY",
5
+ "HTTPS_PROXY",
6
+ "http_proxy",
7
+ "https_proxy"
8
+ ];
9
+ let hintPrinted = false;
10
+ function _resetHintLatchForTest() {
11
+ hintPrinted = false;
12
+ }
13
+ function detectProxyVars(env) {
14
+ return PROXY_ENV_VARS.filter((name) => {
15
+ const value = env[name];
16
+ return typeof value === "string" && value.length > 0;
17
+ });
18
+ }
19
+ function restartWithProxyEnvIfNeeded(options = {}) {
20
+ const env = options.env ?? process.env;
21
+ const stderr = options.stderr ?? process.stderr;
22
+ const detectedVars = detectProxyVars(env);
23
+ if (detectedVars.length === 0) return {
24
+ applied: false,
25
+ detectedVars: [],
26
+ reason: "no_proxy_env"
27
+ };
28
+ if (Object.hasOwn(env, "NODE_USE_ENV_PROXY")) return {
29
+ applied: false,
30
+ detectedVars,
31
+ reason: "node_use_env_proxy_already_set"
32
+ };
33
+ const argv = options.argv ?? process.argv;
34
+ const entrypoint = argv[1];
35
+ if (!entrypoint) return {
36
+ applied: false,
37
+ detectedVars,
38
+ reason: "missing_entrypoint"
39
+ };
40
+ const execPath = options.execPath ?? process.execPath;
41
+ const execArgv = options.execArgv ?? process.execArgv;
42
+ const spawn = options.spawn ?? spawnSync;
43
+ const exit = options.exit ?? ((code) => {
44
+ process.exit(code);
45
+ throw new Error("process.exit returned unexpectedly");
46
+ });
47
+ if (!hintPrinted) {
48
+ hintPrinted = true;
49
+ const names = detectedVars.join("/");
50
+ stderr.write(`primitive: proxy detected via ${names}, restarting with NODE_USE_ENV_PROXY=1\n`);
51
+ }
52
+ const child = spawn(execPath, [
53
+ ...execArgv,
54
+ entrypoint,
55
+ ...argv.slice(2)
56
+ ], {
57
+ env: {
58
+ ...env,
59
+ NODE_USE_ENV_PROXY: "1"
60
+ },
61
+ stdio: "inherit"
62
+ });
63
+ if (child.error) throw child.error;
64
+ if (child.signal) {
65
+ (options.kill ?? process.kill)(options.pid ?? process.pid, child.signal);
66
+ return exit(1);
67
+ }
68
+ return exit(child.status ?? 1);
69
+ }
70
+ //#endregion
71
+ export { _resetHintLatchForTest, restartWithProxyEnvIfNeeded };
@@ -0,0 +1,136 @@
1
+ import { c as resolveConfigEnvironment, i as loadCliConfig, x as normalizeApiBaseUrl } from "../cli-config-B5hrwe8q.js";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ //#region src/oclif/root-signup-hint.ts
6
+ const CREDENTIALS_FILE = "credentials.json";
7
+ const ROOT_AUTH_TIMEOUT_MS = 1e3;
8
+ function activeConfigDir(env, home) {
9
+ if (env.PRIMITIVE_CONFIG_DIR) return env.PRIMITIVE_CONFIG_DIR;
10
+ return join(env.XDG_CONFIG_HOME || join(home, ".config"), "primitive");
11
+ }
12
+ function readRootCredentials(configDir) {
13
+ let raw;
14
+ try {
15
+ raw = readFileSync(join(configDir, CREDENTIALS_FILE), "utf8");
16
+ } catch {
17
+ return null;
18
+ }
19
+ try {
20
+ const parsed = JSON.parse(raw);
21
+ if (parsed.auth_method !== "oauth") return null;
22
+ if (typeof parsed.access_token !== "string" || !parsed.access_token.trim()) return null;
23
+ const apiBaseUrl = parsed.api_base_url ?? parsed.api_base_url_1;
24
+ if (typeof apiBaseUrl !== "string" || !apiBaseUrl.trim()) return null;
25
+ return {
26
+ accessToken: parsed.access_token,
27
+ apiBaseUrl: apiBaseUrl.replace(/\/+$/, "")
28
+ };
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+ function accountEndpoint(apiBaseUrl) {
34
+ return `${apiBaseUrl.replace(/\/+$/, "")}/account`;
35
+ }
36
+ function parseRootAccount(payload) {
37
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return null;
38
+ const data = payload.data;
39
+ if (data === null || typeof data !== "object" || Array.isArray(data)) return null;
40
+ const account = data;
41
+ if (typeof account.email !== "string" || !account.email.trim()) return null;
42
+ if (typeof account.id !== "string" || !account.id.trim()) return null;
43
+ return {
44
+ email: account.email,
45
+ id: account.id
46
+ };
47
+ }
48
+ function rootAuthLine(account) {
49
+ return `Signed in as ${account.email} (org ${account.id})\n\n`;
50
+ }
51
+ function rootRequestConfig(configDir, env) {
52
+ const currentEnvironment = resolveConfigEnvironment(loadCliConfig(configDir));
53
+ const configuredApiBaseUrl = currentEnvironment?.config.api_base_url;
54
+ const envApiBaseUrl = env.PRIMITIVE_API_BASE_URL?.trim();
55
+ if (currentEnvironment !== null && currentEnvironment.name !== "default" && !envApiBaseUrl && !configuredApiBaseUrl) return null;
56
+ return {
57
+ apiBaseUrl: normalizeApiBaseUrl(envApiBaseUrl || configuredApiBaseUrl),
58
+ headers: currentEnvironment?.config.headers
59
+ };
60
+ }
61
+ async function fetchRootAccount(params) {
62
+ const controller = new AbortController();
63
+ const timer = setTimeout(() => controller.abort(), params.timeoutMs);
64
+ try {
65
+ const response = await params.fetch(accountEndpoint(params.apiBaseUrl), {
66
+ headers: {
67
+ ...params.headers ?? {},
68
+ accept: "application/json",
69
+ authorization: `Bearer ${params.apiKey}`
70
+ },
71
+ signal: controller.signal
72
+ });
73
+ if (!response.ok) return null;
74
+ return parseRootAccount(await response.json().catch(() => null));
75
+ } catch {
76
+ return null;
77
+ } finally {
78
+ clearTimeout(timer);
79
+ }
80
+ }
81
+ async function rootSignedInSummary(options = {}) {
82
+ if ((options.argv ?? process.argv.slice(2)).length > 0) return null;
83
+ const env = options.env ?? process.env;
84
+ const configDir = activeConfigDir(env, options.home ?? homedir());
85
+ let requestConfig;
86
+ try {
87
+ requestConfig = rootRequestConfig(configDir, env);
88
+ } catch {
89
+ return null;
90
+ }
91
+ if (!requestConfig) return null;
92
+ const explicitApiKey = env.PRIMITIVE_API_KEY?.trim();
93
+ const stored = explicitApiKey ? null : readRootCredentials(configDir);
94
+ const apiKey = explicitApiKey || stored?.accessToken;
95
+ if (!apiKey) return null;
96
+ const account = await fetchRootAccount({
97
+ apiBaseUrl: stored?.apiBaseUrl ?? requestConfig.apiBaseUrl,
98
+ apiKey,
99
+ fetch: options.fetch ?? fetch,
100
+ headers: requestConfig.headers,
101
+ timeoutMs: options.timeoutMs ?? ROOT_AUTH_TIMEOUT_MS
102
+ });
103
+ return account ? rootAuthLine(account) : null;
104
+ }
105
+ function shouldShowLoggedOutSignupHint(options = {}) {
106
+ if ((options.argv ?? process.argv.slice(2)).length > 0) return false;
107
+ const env = options.env ?? process.env;
108
+ if (env.PRIMITIVE_HIDE_SIGNUP_HINT === "1") return false;
109
+ if (env.PRIMITIVE_API_KEY?.trim()) return false;
110
+ return !existsSync(join(activeConfigDir(env, options.home ?? homedir()), CREDENTIALS_FILE));
111
+ }
112
+ function loggedOutSignupHint() {
113
+ return [
114
+ "New to Primitive?",
115
+ " You or your user don't have an account yet?",
116
+ " Run `primitive signup <email> --accept-terms`",
117
+ " to create an account and get started.",
118
+ " Add `--signup-code <code>` if you have one.",
119
+ ""
120
+ ].join("\n");
121
+ }
122
+ function writeLoggedOutSignupHintIfNeeded(options = {}) {
123
+ if (!shouldShowLoggedOutSignupHint(options)) return;
124
+ (options.write ?? ((message) => process.stdout.write(message)))(loggedOutSignupHint());
125
+ }
126
+ async function writeRootAuthContextIfNeeded(options = {}) {
127
+ const write = options.write ?? ((message) => process.stdout.write(message));
128
+ const signedIn = await rootSignedInSummary(options);
129
+ if (signedIn) {
130
+ write(signedIn);
131
+ return;
132
+ }
133
+ writeLoggedOutSignupHintIfNeeded(options);
134
+ }
135
+ //#endregion
136
+ export { loggedOutSignupHint, rootSignedInSummary, shouldShowLoggedOutSignupHint, writeLoggedOutSignupHintIfNeeded, writeRootAuthContextIfNeeded };
@@ -0,0 +1,111 @@
1
+ .TH PRIMITIVE 1
2
+ .SH NAME
3
+ primitive \- command line interface for Primitive email and Functions
4
+ .SH SYNOPSIS
5
+ .B primitive
6
+ .I command
7
+ [\fIoptions\fR]
8
+ .br
9
+ .B primitive
10
+ .B --help
11
+ .br
12
+ .B primitive
13
+ .I command
14
+ .B --help
15
+ .SH DESCRIPTION
16
+ .B primitive
17
+ is the command line interface for Primitive. It sends mail, inspects inbound and outbound email, manages domains and webhook endpoints, deploys Primitive Functions, and exposes generated API operations for scripting.
18
+ .PP
19
+ Most commands print human-readable output by default. Commands that expose JSON usually provide a
20
+ .B --json
21
+ flag or use generated operation output that can be piped into tools such as
22
+ .BR jq (1).
23
+ .SH AUTHENTICATION
24
+ Commands use saved OAuth credentials from
25
+ .B primitive signin
26
+ or
27
+ .B primitive login
28
+ or an API key from
29
+ .B --api-key
30
+ or
31
+ .BR PRIMITIVE_API_KEY .
32
+ .PP
33
+ Run
34
+ .B primitive signin EMAIL --signup-code CODE --accept-terms
35
+ to start email-code sign-in, then run
36
+ .B primitive signin confirm EMAIL CODE
37
+ with the emailed verification code.
38
+ .B primitive login EMAIL
39
+ and
40
+ .B primitive otp EMAIL
41
+ support the same email-code flow. Run
42
+ .B primitive signin
43
+ or
44
+ .B primitive login
45
+ with no email to use browser approval.
46
+ .PP
47
+ Run
48
+ .B primitive logout --force
49
+ to remove local credentials, pending email-code auth state, and stale credential locks without contacting Primitive.
50
+ .PP
51
+ Run
52
+ .B primitive whoami
53
+ to verify which account the CLI is authenticated as.
54
+ .SH COMMON COMMANDS
55
+ .TP
56
+ .B primitive send --to ADDRESS --subject SUBJECT --body TEXT
57
+ Send an outbound email.
58
+ .TP
59
+ .B primitive send --to ADDRESS --body TEXT --attachment PATH
60
+ Send an outbound email with a file attachment.
61
+ .TP
62
+ .B primitive domains add DOMAIN
63
+ Start a custom-domain claim.
64
+ .TP
65
+ .B primitive domains verify --id DOMAIN_ID
66
+ Verify DNS records for a pending domain claim.
67
+ .TP
68
+ .B primitive domains zone-file --id DOMAIN_ID
69
+ Download DNS records as a BIND-format zone file.
70
+ .TP
71
+ .B primitive emails latest
72
+ Show recent inbound emails.
73
+ .TP
74
+ .B primitive functions init NAME
75
+ Scaffold a Primitive Function.
76
+ .TP
77
+ .B primitive functions deploy --name NAME --file PATH
78
+ Deploy a Primitive Function bundle.
79
+ .TP
80
+ .B primitive list-operations
81
+ Print the generated API operation manifest.
82
+ .TP
83
+ .B primitive describe COMMAND_OR_OPERATION
84
+ Describe a generated API operation, including request and response schemas.
85
+ .SH GLOBAL OPTIONS
86
+ .TP
87
+ .B --api-key KEY
88
+ Use an explicit Primitive API key for this command.
89
+ .TP
90
+ .B --time
91
+ Print command duration to standard error after completion.
92
+ .TP
93
+ .B --help
94
+ Show command help.
95
+ .SH ENVIRONMENT
96
+ .TP
97
+ .B PRIMITIVE_API_KEY
98
+ API key used when
99
+ .B --api-key
100
+ is not supplied and no saved OAuth credential should be used.
101
+ .TP
102
+ .B PRIMITIVE_API_BASE_URL
103
+ Override the API base URL. Intended for development and testing.
104
+ .SH FILES
105
+ .TP
106
+ .I ~/.config/primitive
107
+ Default CLI configuration directory used by oclif on most systems.
108
+ .SH SEE ALSO
109
+ .B primitive --help
110
+ .br
111
+ Primitive documentation: https://primitive.dev
package/package.json ADDED
@@ -0,0 +1,143 @@
1
+ {
2
+ "name": "primcli",
3
+ "version": "1.2.0",
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
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "bin",
9
+ "dist",
10
+ "man"
11
+ ],
12
+ "bin": {
13
+ "primitive": "./bin/run.js",
14
+ "prim": "./bin/run.js"
15
+ },
16
+ "man": [
17
+ "./man/primitive.1"
18
+ ],
19
+ "oclif": {
20
+ "bin": "primitive",
21
+ "commands": {
22
+ "strategy": "explicit",
23
+ "target": "./dist/oclif/index.js",
24
+ "identifier": "COMMANDS"
25
+ },
26
+ "dirname": "primitive",
27
+ "plugins": [
28
+ "@oclif/plugin-help",
29
+ "@oclif/plugin-autocomplete",
30
+ "@oclif/plugin-warn-if-update-available"
31
+ ],
32
+ "warn-if-update-available": {
33
+ "timeoutInDays": 1,
34
+ "frequency": 1,
35
+ "frequencyUnit": "days",
36
+ "message": "Primitive CLI update available from <%= chalk.greenBright(config.version) %> to <%= chalk.greenBright(latest) %>. Run `npm install -g @primitivedotdev/cli@latest` to update."
37
+ },
38
+ "topics": {
39
+ "chat": {
40
+ "description": "Chat with agents over email. Use `primitive chat <email> <message>` to start, `primitive chat reply <message>` to continue the active chat, or `primitive chat reply <id> <message>` to reply in a specific local chat."
41
+ },
42
+ "cli": {
43
+ "description": "CLI authentication"
44
+ },
45
+ "config": {
46
+ "description": "Manage local Primitive CLI request environments",
47
+ "hidden": true
48
+ },
49
+ "account": {
50
+ "description": "Manage your account settings, storage, and webhook secret"
51
+ },
52
+ "agent": {
53
+ "description": "Agent signup and authentication API operations"
54
+ },
55
+ "domains": {
56
+ "description": "Claim, verify, manage email domains, and download DNS zone files"
57
+ },
58
+ "inbox": {
59
+ "description": "Check inbound email setup and processing readiness. Prefer `primitive inbox setup` for a guided setup path and `primitive inbox status` for the readiness table."
60
+ },
61
+ "emails": {
62
+ "description": "List, inspect, and wait for received emails. Prefer task aliases like `primitive emails list`, `primitive emails get`, `primitive emails latest`, `primitive emails wait`, and `primitive emails watch`; generated API names remain available for compatibility."
63
+ },
64
+ "search": {
65
+ "description": "Cross-corpus semantic search. Prefer `primitive search <query>` for lexical inbound search and `primitive semantic-search <query>` for meaning-aware ranking across inbound and outbound.",
66
+ "hidden": true
67
+ },
68
+ "sending": {
69
+ "description": "Send outbound emails. Prefer `primitive send` for fresh sends and `primitive reply --id <inbound-id>` for replies. Use `primitive domains list` or `primitive inbox status` to find usable sender domains for --from; `primitive sending permissions` lists recipient-scope destinations you may send to."
70
+ },
71
+ "signin": {
72
+ "description": "Sign in to an existing Primitive account"
73
+ },
74
+ "login": {
75
+ "description": "Log in to an existing Primitive account"
76
+ },
77
+ "otp": {
78
+ "description": "Authenticate with an emailed one-time code"
79
+ },
80
+ "sent": {
81
+ "description": "Short aliases for outbound sent-email history: `primitive sent list` and `primitive sent get`."
82
+ },
83
+ "threads": {
84
+ "description": "Inspect conversation threads spanning received and sent emails. Prefer `primitive threads get --id <thread-id>`; generated API name `primitive threads get-thread --id <thread-id>` remains available."
85
+ },
86
+ "endpoints": {
87
+ "description": "Manage webhook endpoints that receive email events"
88
+ },
89
+ "filters": {
90
+ "description": "Manage whitelist and blocklist filter rules"
91
+ },
92
+ "webhook-deliveries": {
93
+ "description": "View and replay webhook delivery attempts. Prefer `primitive webhook-deliveries list` and `primitive webhook-deliveries replay`."
94
+ },
95
+ "deliveries": {
96
+ "description": "Short aliases for webhook delivery attempts: `primitive deliveries list` and `primitive deliveries replay`."
97
+ },
98
+ "functions": {
99
+ "description": "Deploy JavaScript handlers that run on inbound mail. Prefer `primitive functions templates`, `primitive functions init`, `primitive functions deploy`, `primitive functions redeploy`, `primitive functions list`, `primitive functions get`, `primitive functions logs`, and `primitive functions set-secret`; generated API names remain available for compatibility."
100
+ }
101
+ },
102
+ "topicSeparator": " "
103
+ },
104
+ "keywords": [
105
+ "primitive",
106
+ "cli",
107
+ "email",
108
+ "functions",
109
+ "webhook"
110
+ ],
111
+ "author": "Primitive <support@primitive.dev>",
112
+ "license": "MIT",
113
+ "repository": {
114
+ "type": "git",
115
+ "url": "git+https://github.com/primitivedotdev/sdks.git",
116
+ "directory": "cli-node"
117
+ },
118
+ "bugs": {
119
+ "url": "https://github.com/primitivedotdev/sdks/issues"
120
+ },
121
+ "homepage": "https://primitive.dev",
122
+ "engines": {
123
+ "node": ">=22"
124
+ },
125
+ "dependencies": {
126
+ "@oclif/core": "^4.10.5",
127
+ "@oclif/plugin-autocomplete": "^3.2.45",
128
+ "@oclif/plugin-help": "^6.2.44",
129
+ "@oclif/plugin-warn-if-update-available": "^3.1.65"
130
+ },
131
+ "devDependencies": {
132
+ "@biomejs/biome": "^2.4.10",
133
+ "@primitivedotdev/api-core": "workspace:*",
134
+ "@types/node": "^22.10.2",
135
+ "@vitest/coverage-v8": "^4.1.4",
136
+ "oclif": "^4.23.0",
137
+ "tsdown": "^0.21.10",
138
+ "tsx": "^4.21.0",
139
+ "typescript": "^5.7.2",
140
+ "vite": "^8.0.8",
141
+ "vitest": "^4.1.4"
142
+ }
143
+ }