@primitivedotdev/cli 1.1.0 → 1.2.1
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 +2 -0
- package/dist/oclif/index.js +339 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ primitive whoami
|
|
|
16
16
|
prim whoami
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
The same CLI is also published unscoped as [`primcli`](https://www.npmjs.com/package/primcli) — `npm install -g primcli` installs an identical build with the same `primitive`/`prim` commands. Use whichever name you prefer; they track the same version.
|
|
20
|
+
|
|
19
21
|
Or with no install:
|
|
20
22
|
|
|
21
23
|
```bash
|
package/dist/oclif/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { A as PrimitiveApiClient, C as saveCliCredentials, D as loadActiveChatSt
|
|
|
2
2
|
import { Args, Command, Errors, Flags, ux } from "@oclif/core";
|
|
3
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
|
-
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import path, { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { hostname } from "node:os";
|
|
7
7
|
import process$1 from "node:process";
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
@@ -17171,6 +17171,108 @@ async function runSourceDeploy(api, params) {
|
|
|
17171
17171
|
result: data
|
|
17172
17172
|
};
|
|
17173
17173
|
}
|
|
17174
|
+
async function runSourceDeployWithSecrets(api, params) {
|
|
17175
|
+
const listed = await api.listFunctions();
|
|
17176
|
+
if (listed.error) return {
|
|
17177
|
+
kind: "error",
|
|
17178
|
+
payload: extractErrorPayload(listed.error),
|
|
17179
|
+
stage: "lookup"
|
|
17180
|
+
};
|
|
17181
|
+
const foundId = (listed.data?.data ?? []).find((f) => f.name === params.name)?.id ?? null;
|
|
17182
|
+
let functionId;
|
|
17183
|
+
let createPayload;
|
|
17184
|
+
if (foundId === null) {
|
|
17185
|
+
const created = await api.createFunction({
|
|
17186
|
+
files: params.files,
|
|
17187
|
+
name: params.name
|
|
17188
|
+
});
|
|
17189
|
+
if (created.error) return {
|
|
17190
|
+
kind: "error",
|
|
17191
|
+
payload: extractErrorPayload(created.error),
|
|
17192
|
+
stage: "create"
|
|
17193
|
+
};
|
|
17194
|
+
const data = created.data?.data;
|
|
17195
|
+
if (!data) return {
|
|
17196
|
+
kind: "error",
|
|
17197
|
+
payload: {
|
|
17198
|
+
code: "client_error",
|
|
17199
|
+
message: "Create returned no data"
|
|
17200
|
+
},
|
|
17201
|
+
stage: "create"
|
|
17202
|
+
};
|
|
17203
|
+
functionId = data.id;
|
|
17204
|
+
createPayload = data;
|
|
17205
|
+
} else functionId = foundId;
|
|
17206
|
+
const writtenSecrets = [];
|
|
17207
|
+
const succeededKeys = [];
|
|
17208
|
+
for (let i = 0; i < params.secrets.length; i++) {
|
|
17209
|
+
const pair = params.secrets[i];
|
|
17210
|
+
const pendingKeys = params.secrets.slice(i + 1).map((p) => p.key);
|
|
17211
|
+
const setResult = await api.setSecret({
|
|
17212
|
+
id: functionId,
|
|
17213
|
+
key: pair.key,
|
|
17214
|
+
value: pair.value
|
|
17215
|
+
});
|
|
17216
|
+
if (setResult.error) return {
|
|
17217
|
+
...createPayload ? { created: createPayload } : {},
|
|
17218
|
+
failedKey: pair.key,
|
|
17219
|
+
functionId,
|
|
17220
|
+
kind: "error",
|
|
17221
|
+
payload: extractErrorPayload(setResult.error),
|
|
17222
|
+
pendingKeys,
|
|
17223
|
+
stage: "set-secret",
|
|
17224
|
+
succeededKeys
|
|
17225
|
+
};
|
|
17226
|
+
const secret = setResult.data?.data;
|
|
17227
|
+
if (!secret) return {
|
|
17228
|
+
...createPayload ? { created: createPayload } : {},
|
|
17229
|
+
failedKey: pair.key,
|
|
17230
|
+
functionId,
|
|
17231
|
+
kind: "error",
|
|
17232
|
+
payload: {
|
|
17233
|
+
code: "client_error",
|
|
17234
|
+
message: "Secret write returned no data"
|
|
17235
|
+
},
|
|
17236
|
+
pendingKeys,
|
|
17237
|
+
stage: "set-secret",
|
|
17238
|
+
succeededKeys
|
|
17239
|
+
};
|
|
17240
|
+
writtenSecrets.push(secret);
|
|
17241
|
+
succeededKeys.push(pair.key);
|
|
17242
|
+
}
|
|
17243
|
+
const updated = await api.updateFunction({
|
|
17244
|
+
files: params.files,
|
|
17245
|
+
id: functionId
|
|
17246
|
+
});
|
|
17247
|
+
if (updated.error) return {
|
|
17248
|
+
...createPayload ? { created: createPayload } : {},
|
|
17249
|
+
functionId,
|
|
17250
|
+
kind: "error",
|
|
17251
|
+
payload: extractErrorPayload(updated.error),
|
|
17252
|
+
stage: "secret-redeploy",
|
|
17253
|
+
succeededKeys
|
|
17254
|
+
};
|
|
17255
|
+
const redeployed = updated.data?.data;
|
|
17256
|
+
if (!redeployed) return {
|
|
17257
|
+
...createPayload ? { created: createPayload } : {},
|
|
17258
|
+
functionId,
|
|
17259
|
+
kind: "error",
|
|
17260
|
+
payload: {
|
|
17261
|
+
code: "client_error",
|
|
17262
|
+
message: "Redeploy returned no data"
|
|
17263
|
+
},
|
|
17264
|
+
stage: "secret-redeploy",
|
|
17265
|
+
succeededKeys
|
|
17266
|
+
};
|
|
17267
|
+
return {
|
|
17268
|
+
kind: "ok",
|
|
17269
|
+
result: {
|
|
17270
|
+
action: foundId === null ? "created" : "redeployed",
|
|
17271
|
+
redeploy: redeployed,
|
|
17272
|
+
secrets: writtenSecrets
|
|
17273
|
+
}
|
|
17274
|
+
};
|
|
17275
|
+
}
|
|
17174
17276
|
function renderBuildFailure(payload, write) {
|
|
17175
17277
|
if (typeof payload !== "object" || payload === null) return false;
|
|
17176
17278
|
const error = payload.error ?? payload;
|
|
@@ -17657,14 +17759,23 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
17657
17759
|
|
|
17658
17760
|
Pass secret source flags to seed bindings in the same command. Keys
|
|
17659
17761
|
must match \`^[A-Z_][A-Z0-9_]*$\` (uppercase letters, digits,
|
|
17660
|
-
underscores; first character is a letter or underscore).
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
|
|
17667
|
-
|
|
17762
|
+
underscores; first character is a letter or underscore).
|
|
17763
|
+
|
|
17764
|
+
With one or more secrets the deploy fans out to multiple API calls.
|
|
17765
|
+
For --file (and for --source when no function with the given name
|
|
17766
|
+
exists yet): create-function, set-secret per pair, then a final
|
|
17767
|
+
update-function so the running handler picks up the bindings. For
|
|
17768
|
+
--source against an existing function name: the create-function step
|
|
17769
|
+
is replaced by an id lookup, then set-secret per pair, then a single
|
|
17770
|
+
update-function that binds the new code and the new secret env in
|
|
17771
|
+
one step (avoiding an intermediate redeploy that would briefly run
|
|
17772
|
+
the new code with the previous secret bindings).
|
|
17773
|
+
|
|
17774
|
+
If a secret write fails before the final redeploy, the function row
|
|
17775
|
+
carries whatever bindings landed but the running handler has NOT yet
|
|
17776
|
+
picked them up. Re-run \`primitive functions set-secret\` for the
|
|
17777
|
+
missing keys, then re-run \`primitive functions deploy\` (or
|
|
17778
|
+
\`functions redeploy\`) to push them live. ${SECRET_SOURCE_FLAGS_DESCRIPTION}`;
|
|
17668
17779
|
static summary = "Deploy a new function from a bundled handler file";
|
|
17669
17780
|
static examples = [
|
|
17670
17781
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js",
|
|
@@ -17674,6 +17785,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
17674
17785
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
17675
17786
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
|
|
17676
17787
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret-from-env OPENAI_KEY --secret-from-env-file .env.local:OWNER_EMAIL",
|
|
17788
|
+
"<%= config.bin %> functions deploy --name triage --source . --secret-from-env ANTHROPIC_API_KEY",
|
|
17677
17789
|
"printf '%s' \"$OPENAI_KEY\" | <%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret-from-stdin OPENAI_KEY"
|
|
17678
17790
|
];
|
|
17679
17791
|
static flags = {
|
|
@@ -17863,8 +17975,15 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
17863
17975
|
});
|
|
17864
17976
|
}
|
|
17865
17977
|
async runSourceMode(flags, sourceDir) {
|
|
17866
|
-
|
|
17867
|
-
|
|
17978
|
+
const parsedSecrets = resolveSecretFlags({
|
|
17979
|
+
fromEnv: flags["secret-from-env"] ?? [],
|
|
17980
|
+
fromEnvFile: flags["secret-from-env-file"] ?? [],
|
|
17981
|
+
fromFile: flags["secret-from-file"] ?? [],
|
|
17982
|
+
fromStdin: flags["secret-from-stdin"],
|
|
17983
|
+
inline: flags.secret ?? []
|
|
17984
|
+
});
|
|
17985
|
+
if (parsedSecrets.kind === "error") {
|
|
17986
|
+
process.stderr.write(`${parsedSecrets.message}\n`);
|
|
17868
17987
|
process.exitCode = 1;
|
|
17869
17988
|
return;
|
|
17870
17989
|
}
|
|
@@ -17884,7 +18003,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
17884
18003
|
baseUrlOverridden,
|
|
17885
18004
|
configDir: this.config.configDir
|
|
17886
18005
|
};
|
|
17887
|
-
const
|
|
18006
|
+
const apiSurface = {
|
|
17888
18007
|
createFunction: (p) => createFunction({
|
|
17889
18008
|
body: {
|
|
17890
18009
|
files: p.files,
|
|
@@ -17897,27 +18016,80 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
17897
18016
|
client: apiClient.client,
|
|
17898
18017
|
responseStyle: "fields"
|
|
17899
18018
|
}),
|
|
18019
|
+
setSecret: (p) => setFunctionSecret({
|
|
18020
|
+
body: { value: p.value },
|
|
18021
|
+
client: apiClient.client,
|
|
18022
|
+
path: {
|
|
18023
|
+
id: p.id,
|
|
18024
|
+
key: p.key
|
|
18025
|
+
},
|
|
18026
|
+
responseStyle: "fields"
|
|
18027
|
+
}),
|
|
17900
18028
|
updateFunction: (p) => updateFunction({
|
|
17901
18029
|
body: { files: p.files },
|
|
17902
18030
|
client: apiClient.client,
|
|
17903
18031
|
path: { id: p.id },
|
|
17904
18032
|
responseStyle: "fields"
|
|
17905
18033
|
})
|
|
17906
|
-
}
|
|
18034
|
+
};
|
|
18035
|
+
if (parsedSecrets.secrets.length === 0) {
|
|
18036
|
+
const outcome = await runSourceDeploy(apiSurface, {
|
|
18037
|
+
files: collected.files,
|
|
18038
|
+
name: flags.name
|
|
18039
|
+
});
|
|
18040
|
+
if (outcome.kind === "error") {
|
|
18041
|
+
renderBuildFailure(outcome.payload, (chunk) => process.stderr.write(chunk));
|
|
18042
|
+
writeErrorWithHints(outcome.payload);
|
|
18043
|
+
surfaceUnauthorizedHint({
|
|
18044
|
+
...authFailureContext,
|
|
18045
|
+
payload: outcome.payload
|
|
18046
|
+
});
|
|
18047
|
+
process.exitCode = 1;
|
|
18048
|
+
return;
|
|
18049
|
+
}
|
|
18050
|
+
await this.finishSourceDeploy({
|
|
18051
|
+
apiClient,
|
|
18052
|
+
authFailureContext,
|
|
18053
|
+
flags,
|
|
18054
|
+
payload: outcome.result
|
|
18055
|
+
});
|
|
18056
|
+
return;
|
|
18057
|
+
}
|
|
18058
|
+
const secretsOutcome = await runSourceDeployWithSecrets(apiSurface, {
|
|
17907
18059
|
files: collected.files,
|
|
17908
|
-
name: flags.name
|
|
18060
|
+
name: flags.name,
|
|
18061
|
+
secrets: parsedSecrets.secrets
|
|
17909
18062
|
});
|
|
17910
|
-
if (
|
|
17911
|
-
|
|
17912
|
-
|
|
18063
|
+
if (secretsOutcome.kind === "error") {
|
|
18064
|
+
if (secretsOutcome.stage === "set-secret") {
|
|
18065
|
+
const succeeded = secretsOutcome.succeededKeys.length > 0 ? secretsOutcome.succeededKeys.join(", ") : "(none)";
|
|
18066
|
+
const pending = secretsOutcome.pendingKeys.length > 0 ? secretsOutcome.pendingKeys.join(", ") : "(none)";
|
|
18067
|
+
const allMissing = [secretsOutcome.failedKey, ...secretsOutcome.pendingKeys].join(", ");
|
|
18068
|
+
const createdClause = secretsOutcome.created ? `Function ${secretsOutcome.created.name} (${secretsOutcome.functionId}) was created` : `Function ${flags.name} (${secretsOutcome.functionId}) already existed`;
|
|
18069
|
+
const stagingWarning = secretsOutcome.succeededKeys.length > 0 ? ` Note: [${succeeded}] are now staged on the function row and will bind on the next deploy of this function (including one that does not pass --secret).` : "";
|
|
18070
|
+
process.stderr.write(`${createdClause}, but writing secret ${secretsOutcome.failedKey} failed; succeeded keys so far: ${succeeded}; keys not yet attempted: ${pending}. The redeploy is NOT yet live. Re-run \`primitive functions set-secret\` for each of [${allMissing}], then \`primitive functions deploy --source ${sourceDir} --name ${flags.name}\` to push them live.${stagingWarning}\n`);
|
|
18071
|
+
} else if (secretsOutcome.stage === "secret-redeploy") {
|
|
18072
|
+
const succeeded = secretsOutcome.succeededKeys.length > 0 ? secretsOutcome.succeededKeys.join(", ") : "(none)";
|
|
18073
|
+
const createdClause = secretsOutcome.created ? `Function ${secretsOutcome.created.name} (${secretsOutcome.functionId}) was created and` : `Function ${flags.name} (${secretsOutcome.functionId}) already existed and`;
|
|
18074
|
+
process.stderr.write(`${createdClause} secrets [${succeeded}] were written, but the final redeploy failed; the new bindings are NOT yet live. Re-run \`primitive functions deploy --source ${sourceDir} --name ${flags.name}\` once the cause is fixed.\n`);
|
|
18075
|
+
} else renderBuildFailure(secretsOutcome.payload, (chunk) => process.stderr.write(chunk));
|
|
18076
|
+
writeErrorWithHints(secretsOutcome.payload);
|
|
17913
18077
|
surfaceUnauthorizedHint({
|
|
17914
18078
|
...authFailureContext,
|
|
17915
|
-
payload:
|
|
18079
|
+
payload: secretsOutcome.payload
|
|
17916
18080
|
});
|
|
17917
18081
|
process.exitCode = 1;
|
|
17918
18082
|
return;
|
|
17919
18083
|
}
|
|
17920
|
-
|
|
18084
|
+
await this.finishSourceDeploy({
|
|
18085
|
+
apiClient,
|
|
18086
|
+
authFailureContext,
|
|
18087
|
+
flags,
|
|
18088
|
+
payload: secretsOutcome.result.redeploy
|
|
18089
|
+
});
|
|
18090
|
+
}
|
|
18091
|
+
async finishSourceDeploy(args) {
|
|
18092
|
+
const { apiClient, authFailureContext, flags, payload } = args;
|
|
17921
18093
|
if (flags.wait) {
|
|
17922
18094
|
const waitResult = await waitForFunctionDeploy({
|
|
17923
18095
|
getFunction: (p) => getFunction({
|
|
@@ -17966,8 +18138,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
17966
18138
|
name: "Primitive Team",
|
|
17967
18139
|
url: "https://primitive.dev"
|
|
17968
18140
|
};
|
|
17969
|
-
const SDK_VERSION_RANGE = "^1.
|
|
17970
|
-
const CLI_VERSION_RANGE = "^1.
|
|
18141
|
+
const SDK_VERSION_RANGE = "^1.2.0";
|
|
18142
|
+
const CLI_VERSION_RANGE = "^1.2.0";
|
|
17971
18143
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
17972
18144
|
function renderHandler() {
|
|
17973
18145
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -20770,6 +20942,74 @@ var SignupCommand = class SignupCommand extends Command {
|
|
|
20770
20942
|
}
|
|
20771
20943
|
}
|
|
20772
20944
|
};
|
|
20945
|
+
function resolveVerificationCode(input) {
|
|
20946
|
+
const sources = [
|
|
20947
|
+
input.positional !== void 0 ? "positional" : null,
|
|
20948
|
+
input.fromStdin === true ? "--code-from-stdin" : null,
|
|
20949
|
+
input.fromFile !== void 0 ? "--code-from-file" : null,
|
|
20950
|
+
input.fromEnv !== void 0 ? "--code-from-env" : null
|
|
20951
|
+
].filter((v) => v !== null);
|
|
20952
|
+
if (sources.length === 0) return {
|
|
20953
|
+
kind: "error",
|
|
20954
|
+
message: "Pass the verification code as a positional argument or via one of --code-from-stdin, --code-from-file, or --code-from-env."
|
|
20955
|
+
};
|
|
20956
|
+
if (sources.length > 1) return {
|
|
20957
|
+
kind: "error",
|
|
20958
|
+
message: `Pass exactly one source for the verification code; got ${sources.join(", ")}.`
|
|
20959
|
+
};
|
|
20960
|
+
if (input.positional !== void 0) return {
|
|
20961
|
+
kind: "ok",
|
|
20962
|
+
code: input.positional
|
|
20963
|
+
};
|
|
20964
|
+
if (input.fromEnv !== void 0) {
|
|
20965
|
+
const value = (input.env ?? process$1.env)[input.fromEnv];
|
|
20966
|
+
if (value === void 0) return {
|
|
20967
|
+
kind: "error",
|
|
20968
|
+
message: `--code-from-env ${input.fromEnv}: environment variable is not set.`
|
|
20969
|
+
};
|
|
20970
|
+
return {
|
|
20971
|
+
kind: "ok",
|
|
20972
|
+
code: stripTrailingNewline(value)
|
|
20973
|
+
};
|
|
20974
|
+
}
|
|
20975
|
+
if (input.fromFile !== void 0) {
|
|
20976
|
+
const readFile = input.readFile ?? defaultReadCodeFile;
|
|
20977
|
+
try {
|
|
20978
|
+
return {
|
|
20979
|
+
kind: "ok",
|
|
20980
|
+
code: stripTrailingNewline(readFile(input.fromFile))
|
|
20981
|
+
};
|
|
20982
|
+
} catch (error) {
|
|
20983
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
20984
|
+
return {
|
|
20985
|
+
kind: "error",
|
|
20986
|
+
message: `--code-from-file ${input.fromFile}: could not read file: ${detail}`
|
|
20987
|
+
};
|
|
20988
|
+
}
|
|
20989
|
+
}
|
|
20990
|
+
const readStdin = input.readStdin ?? defaultReadCodeStdin;
|
|
20991
|
+
try {
|
|
20992
|
+
return {
|
|
20993
|
+
kind: "ok",
|
|
20994
|
+
code: stripTrailingNewline(readStdin())
|
|
20995
|
+
};
|
|
20996
|
+
} catch (error) {
|
|
20997
|
+
return {
|
|
20998
|
+
kind: "error",
|
|
20999
|
+
message: `--code-from-stdin: ${error instanceof Error ? error.message : String(error)}`
|
|
21000
|
+
};
|
|
21001
|
+
}
|
|
21002
|
+
}
|
|
21003
|
+
function stripTrailingNewline(value) {
|
|
21004
|
+
return value.replace(/\r?\n$/, "");
|
|
21005
|
+
}
|
|
21006
|
+
function defaultReadCodeFile(path) {
|
|
21007
|
+
return readFileSync(path, "utf8");
|
|
21008
|
+
}
|
|
21009
|
+
function defaultReadCodeStdin() {
|
|
21010
|
+
if (process$1.stdin.isTTY) throw new Error("stdin is a TTY; pipe the code into this command or use --code-from-file / --code-from-env instead.");
|
|
21011
|
+
return readFileSync(0, "utf8");
|
|
21012
|
+
}
|
|
20773
21013
|
var SignupConfirmCommand = class SignupConfirmCommand extends Command {
|
|
20774
21014
|
static args = {
|
|
20775
21015
|
email: Args.string({
|
|
@@ -20777,19 +21017,28 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
|
|
|
20777
21017
|
required: true
|
|
20778
21018
|
}),
|
|
20779
21019
|
code: Args.string({
|
|
20780
|
-
description: "Verification code from the signup email",
|
|
20781
|
-
required:
|
|
21020
|
+
description: "Verification code from the signup email. Optional when one of --code-from-stdin / --code-from-file / --code-from-env is passed; exactly one source must be set.",
|
|
21021
|
+
required: false
|
|
20782
21022
|
})
|
|
20783
21023
|
};
|
|
20784
21024
|
static description = "Confirm a pending Primitive signup, create an OAuth session, and save CLI credentials locally.";
|
|
20785
21025
|
static summary = "Confirm account signup";
|
|
20786
|
-
static examples = [
|
|
21026
|
+
static examples = [
|
|
21027
|
+
"<%= config.bin %> signup confirm user@example.com 123456",
|
|
21028
|
+
"<%= config.bin %> signup confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000",
|
|
21029
|
+
"read -rs CODE && CODE=\"$CODE\" <%= config.bin %> signup confirm user@example.com --code-from-env CODE && unset CODE",
|
|
21030
|
+
"read -rs CODE && printf '%s' \"$CODE\" | <%= config.bin %> signup confirm user@example.com --code-from-stdin && unset CODE",
|
|
21031
|
+
"<%= config.bin %> signup confirm user@example.com --code-from-file /run/user/$(id -u)/verification-code"
|
|
21032
|
+
];
|
|
20787
21033
|
static flags = {
|
|
20788
21034
|
"api-base-url": Flags.string({
|
|
20789
21035
|
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
20790
21036
|
env: "PRIMITIVE_API_BASE_URL",
|
|
20791
21037
|
hidden: true
|
|
20792
21038
|
}),
|
|
21039
|
+
"code-from-stdin": Flags.boolean({ description: "Read the verification code from stdin instead of the positional argument. Use when an agent is constructing the command for the user to run, so the code never enters the agent's prompt context." }),
|
|
21040
|
+
"code-from-file": Flags.string({ description: "Read the verification code from a UTF-8 file at this path. Trailing newlines are stripped." }),
|
|
21041
|
+
"code-from-env": Flags.string({ description: "Read the verification code from this environment variable. Pair with `read -rs CODE && CODE=\"$CODE\" primitive signup confirm <email> --code-from-env CODE && unset CODE` so the value never appears on the command line or in shell history. Plain `read` creates a shell-local variable that child processes cannot see; the inline `CODE=\"$CODE\"` exports it for just the one command." }),
|
|
20793
21042
|
force: Flags.boolean({
|
|
20794
21043
|
char: "f",
|
|
20795
21044
|
description: "Replace saved credentials after verification"
|
|
@@ -20798,6 +21047,13 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
|
|
|
20798
21047
|
};
|
|
20799
21048
|
async run() {
|
|
20800
21049
|
const { args, flags } = await this.parse(SignupConfirmCommand);
|
|
21050
|
+
const resolvedCode = resolveVerificationCode({
|
|
21051
|
+
positional: args.code,
|
|
21052
|
+
fromStdin: flags["code-from-stdin"] === true,
|
|
21053
|
+
fromFile: flags["code-from-file"],
|
|
21054
|
+
fromEnv: flags["code-from-env"]
|
|
21055
|
+
});
|
|
21056
|
+
if (resolvedCode.kind === "error") throw cliError$2(resolvedCode.message);
|
|
20801
21057
|
let releaseCredentialsLock;
|
|
20802
21058
|
try {
|
|
20803
21059
|
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
@@ -20806,7 +21062,7 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
|
|
|
20806
21062
|
}
|
|
20807
21063
|
try {
|
|
20808
21064
|
await runSignupConfirmWithCredentialLock({
|
|
20809
|
-
code:
|
|
21065
|
+
code: resolvedCode.code,
|
|
20810
21066
|
configDir: this.config.configDir,
|
|
20811
21067
|
email: args.email,
|
|
20812
21068
|
flags
|
|
@@ -22211,6 +22467,43 @@ function renderFishCompletion(binName) {
|
|
|
22211
22467
|
return `${lines.join("\n")}\n`;
|
|
22212
22468
|
}
|
|
22213
22469
|
//#endregion
|
|
22470
|
+
//#region src/oclif/shell-completion-script.ts
|
|
22471
|
+
/**
|
|
22472
|
+
* Path to the sourceable completion *function* file that
|
|
22473
|
+
* `@oclif/plugin-autocomplete` writes under the CLI cache dir when its cache is
|
|
22474
|
+
* built. This is the artifact a shell is meant to source -- a package manager
|
|
22475
|
+
* dropping a file into `bash_completion.d/` or zsh's `site-functions/` wants
|
|
22476
|
+
* this, NOT the human-readable setup instructions printed by
|
|
22477
|
+
* `<bin> autocomplete <shell>`. The layout mirrors the plugin's own
|
|
22478
|
+
* `Create.bashCompletionFunctionPath` / `zshCompletionFunctionPath` getters:
|
|
22479
|
+
* <cacheDir>/autocomplete/functions/bash/<bin>.bash
|
|
22480
|
+
* <cacheDir>/autocomplete/functions/zsh/_<bin>
|
|
22481
|
+
*
|
|
22482
|
+
* This couples to a private path layout in `@oclif/plugin-autocomplete`
|
|
22483
|
+
* (pinned `^3.2.45` in package.json). If a major bump reorganises that cache
|
|
22484
|
+
* dir, `readCompletionFunction` will fail even after a successful
|
|
22485
|
+
* `--refresh-cache`; re-verify this layout when bumping the plugin.
|
|
22486
|
+
*/
|
|
22487
|
+
function completionFunctionPath(cacheDir, bin, shell) {
|
|
22488
|
+
const functionsDir = path.join(cacheDir, "autocomplete", "functions", shell);
|
|
22489
|
+
const fileName = shell === "bash" ? `${bin}.bash` : `_${bin}`;
|
|
22490
|
+
return path.join(functionsDir, fileName);
|
|
22491
|
+
}
|
|
22492
|
+
/**
|
|
22493
|
+
* Read the generated completion function script, trimmed of trailing
|
|
22494
|
+
* whitespace so the caller can re-add a single newline. Throws an actionable
|
|
22495
|
+
* error (rather than a bare `ENOENT`) if the cached script is missing -- e.g.
|
|
22496
|
+
* the cache build failed, or the plugin changed its path layout.
|
|
22497
|
+
*/
|
|
22498
|
+
function readCompletionFunction(cacheDir, bin, shell) {
|
|
22499
|
+
const filePath = completionFunctionPath(cacheDir, bin, shell);
|
|
22500
|
+
try {
|
|
22501
|
+
return readFileSync(filePath, "utf8").trimEnd();
|
|
22502
|
+
} catch (cause) {
|
|
22503
|
+
throw new Error(`Could not read the generated ${shell} completion script at ${filePath}. Run \`${bin} autocomplete ${shell} --refresh-cache\` and try again.`, { cause });
|
|
22504
|
+
}
|
|
22505
|
+
}
|
|
22506
|
+
//#endregion
|
|
22214
22507
|
//#region src/oclif/index.ts
|
|
22215
22508
|
var ListOperationsCommand = class extends Command {
|
|
22216
22509
|
static description = "List all generated API operations as JSON. Useful for piping to `jq` to discover available commands, their request/response schemas, and per-field descriptions. For inspecting a single operation in detail, prefer `primitive describe <command-or-operation-name>`.";
|
|
@@ -22331,14 +22624,32 @@ var CompletionCommand = class CompletionCommand extends Command {
|
|
|
22331
22624
|
],
|
|
22332
22625
|
required: true
|
|
22333
22626
|
}) };
|
|
22334
|
-
static description =
|
|
22335
|
-
|
|
22627
|
+
static description = `Output a sourceable shell completion script, or print setup instructions.
|
|
22628
|
+
|
|
22629
|
+
For fish, and for bash/zsh when the output is piped or redirected (e.g. into a
|
|
22630
|
+
completion file under bash_completion.d or zsh's site-functions), this emits
|
|
22631
|
+
the raw completion script. For bash/zsh in an interactive terminal it prints
|
|
22632
|
+
the human-readable setup instructions instead. This keeps a redirected
|
|
22633
|
+
\`<%= config.bin %> completion bash > <file>\` safe to source -- the file holds an
|
|
22634
|
+
actual completion function, never instructional prose a shell would choke on.`;
|
|
22635
|
+
static summary = "Output a shell completion script or print setup instructions";
|
|
22636
|
+
static examples = [
|
|
22637
|
+
"<%= config.bin %> completion bash >> /etc/bash_completion.d/primitive",
|
|
22638
|
+
"<%= config.bin %> completion zsh > /usr/local/share/zsh/site-functions/_primitive",
|
|
22639
|
+
"<%= config.bin %> completion fish > ~/.config/fish/completions/primitive.fish"
|
|
22640
|
+
];
|
|
22336
22641
|
async run() {
|
|
22337
22642
|
const { args } = await this.parse(CompletionCommand);
|
|
22338
|
-
|
|
22643
|
+
const shell = args.shell;
|
|
22644
|
+
if (shell === "fish") {
|
|
22339
22645
|
this.log(renderFishCompletion(this.config.bin));
|
|
22340
22646
|
return;
|
|
22341
22647
|
}
|
|
22648
|
+
if ((shell === "bash" || shell === "zsh") && !process.stdout.isTTY) {
|
|
22649
|
+
await this.config.runCommand("autocomplete", [shell, "--refresh-cache"]);
|
|
22650
|
+
this.log(readCompletionFunction(this.config.cacheDir, this.config.bin, shell));
|
|
22651
|
+
return;
|
|
22652
|
+
}
|
|
22342
22653
|
await this.config.runCommand("autocomplete", [args.shell]);
|
|
22343
22654
|
}
|
|
22344
22655
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitivedotdev/cli",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
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,
|