@hireobi/cli 0.1.8 → 0.1.10
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 +12 -10
- package/dist/obi.js +525 -89
- package/package.json +1 -1
- package/skills/obi/SKILL.md +5 -1
- package/skills/obi/references/fit-and-scope.md +32 -0
- package/skills/obi/references/purchase.md +33 -2
- package/skills/obi/references/questions.md +1 -1
package/dist/obi.js
CHANGED
|
@@ -14618,6 +14618,7 @@ var optionDefinitions = {
|
|
|
14618
14618
|
"field-path": { type: "string" },
|
|
14619
14619
|
"segment-name": { type: "string" },
|
|
14620
14620
|
text: { type: "string" },
|
|
14621
|
+
tag: { type: "string" },
|
|
14621
14622
|
company: { type: "string" },
|
|
14622
14623
|
"company-unknown": { type: "boolean" },
|
|
14623
14624
|
product: { type: "string" },
|
|
@@ -14632,7 +14633,7 @@ var optionDefinitions = {
|
|
|
14632
14633
|
help: { type: "boolean" },
|
|
14633
14634
|
version: { type: "boolean" }
|
|
14634
14635
|
};
|
|
14635
|
-
var namedCommands = ["business", "capabilities", "credentials", "doctor", "eval", "harness", "login", "logout", "memory", "questions", "readiness", "research", "skill", "status", "strategies"];
|
|
14636
|
+
var namedCommands = ["business", "capabilities", "credentials", "doctor", "eval", "harness", "login", "logout", "memory", "questions", "readiness", "research", "skill", "status", "strategies", "support"];
|
|
14636
14637
|
var memoryKinds = ["preference", "constraint", "decision"];
|
|
14637
14638
|
var credentialsActions = ["list", "revoke"];
|
|
14638
14639
|
var emailSchema = external_exports.email().max(254);
|
|
@@ -14651,6 +14652,8 @@ var CliError = class extends Error {
|
|
|
14651
14652
|
command;
|
|
14652
14653
|
authorization;
|
|
14653
14654
|
account;
|
|
14655
|
+
/** The message `obi support` could not send, with the block to paste. */
|
|
14656
|
+
support;
|
|
14654
14657
|
constructor(options) {
|
|
14655
14658
|
super(options.message);
|
|
14656
14659
|
this.name = "CliError";
|
|
@@ -14662,6 +14665,7 @@ var CliError = class extends Error {
|
|
|
14662
14665
|
this.command = options.command;
|
|
14663
14666
|
this.authorization = options.authorization;
|
|
14664
14667
|
this.account = options.account;
|
|
14668
|
+
this.support = options.support;
|
|
14665
14669
|
}
|
|
14666
14670
|
};
|
|
14667
14671
|
function usageError(message = "Invalid command usage.", options = {}) {
|
|
@@ -15174,6 +15178,52 @@ function parseMemoryArguments(positionals, values, context, common2) {
|
|
|
15174
15178
|
throw invalid3();
|
|
15175
15179
|
}
|
|
15176
15180
|
|
|
15181
|
+
// src/support-contract.ts
|
|
15182
|
+
var SUPPORT_EMAIL = "support@hireobi.com";
|
|
15183
|
+
var SUPPORT_TAGS = ["market-request", "scope-request"];
|
|
15184
|
+
var lastErrorSchema = external_exports.strictObject({
|
|
15185
|
+
code: external_exports.string().regex(/^[A-Z][A-Z0-9_]{0,79}$/),
|
|
15186
|
+
command: external_exports.string().regex(/^[a-z][a-z-]{0,39}$/),
|
|
15187
|
+
at: external_exports.iso.datetime({ offset: true })
|
|
15188
|
+
});
|
|
15189
|
+
var SupportMessageSchema = external_exports.strictObject({
|
|
15190
|
+
requestId: external_exports.string().regex(/^[0-9a-f]{32}$/),
|
|
15191
|
+
message: external_exports.string().trim().min(1).max(2e3),
|
|
15192
|
+
tag: external_exports.enum(SUPPORT_TAGS).nullable(),
|
|
15193
|
+
diagnostic: external_exports.strictObject({
|
|
15194
|
+
cliVersion: external_exports.string().min(1).max(40),
|
|
15195
|
+
node: external_exports.string().min(1).max(40),
|
|
15196
|
+
os: external_exports.string().min(1).max(120),
|
|
15197
|
+
lastError: lastErrorSchema.nullable()
|
|
15198
|
+
})
|
|
15199
|
+
});
|
|
15200
|
+
var SupportMessageResponseSchema = external_exports.object({
|
|
15201
|
+
schemaVersion: external_exports.literal(1),
|
|
15202
|
+
data: external_exports.object({ received: external_exports.literal(true) })
|
|
15203
|
+
});
|
|
15204
|
+
var supportDataSchema = external_exports.object({
|
|
15205
|
+
delivery: external_exports.enum(["sent", "not_sent"]),
|
|
15206
|
+
/** Why nothing was sent: the error code met, null once sent. */
|
|
15207
|
+
reason: external_exports.string().nullable(),
|
|
15208
|
+
supportEmail: external_exports.literal(SUPPORT_EMAIL),
|
|
15209
|
+
mailto: external_exports.string(),
|
|
15210
|
+
tag: external_exports.enum(SUPPORT_TAGS).nullable(),
|
|
15211
|
+
account: external_exports.string().nullable(),
|
|
15212
|
+
origin: external_exports.string().min(1),
|
|
15213
|
+
diagnosticText: external_exports.string()
|
|
15214
|
+
});
|
|
15215
|
+
|
|
15216
|
+
// src/support-arguments.ts
|
|
15217
|
+
function parseSupportArguments(positionals, values, context, common2) {
|
|
15218
|
+
const allowed = /* @__PURE__ */ new Set(["json", "timeout-ms", "tag"]);
|
|
15219
|
+
const message = positionals[1]?.trim() ?? "";
|
|
15220
|
+
const tag = values.tag;
|
|
15221
|
+
if (Object.entries(values).some(([key, value]) => value !== void 0 && value !== false && !allowed.has(key)) || positionals.length !== 2 || message.length === 0 || message.length > 2e3 || tag !== void 0 && !SUPPORT_TAGS.includes(tag)) {
|
|
15222
|
+
throw usageError(`Use support "<message of at most 2000 characters>" [--tag ${SUPPORT_TAGS.join("|")}].`, context);
|
|
15223
|
+
}
|
|
15224
|
+
return { command: "support", supportMessage: message, ...tag === void 0 ? {} : { supportTag: tag }, ...common2 };
|
|
15225
|
+
}
|
|
15226
|
+
|
|
15177
15227
|
// src/research-arguments.ts
|
|
15178
15228
|
function parseResearchScope(values, context) {
|
|
15179
15229
|
const locale = values.locale;
|
|
@@ -15576,7 +15626,7 @@ function commandMode(command, testServer) {
|
|
|
15576
15626
|
if (testServer) {
|
|
15577
15627
|
return "test";
|
|
15578
15628
|
}
|
|
15579
|
-
return command === "login" || command === "logout" || command === "status" || command === "credentials" || command === "eval" || command === "harness" || command === "business" || command === "questions" || command === "strategies" || command === "readiness" || command === "memory" ? "product" : "local";
|
|
15629
|
+
return command === "login" || command === "logout" || command === "status" || command === "credentials" || command === "eval" || command === "harness" || command === "business" || command === "questions" || command === "strategies" || command === "readiness" || command === "memory" || command === "support" ? "product" : "local";
|
|
15580
15630
|
}
|
|
15581
15631
|
function errorContext(argv) {
|
|
15582
15632
|
const usesTestServer = argv.some(
|
|
@@ -15762,6 +15812,8 @@ function parseCommandArguments(argv) {
|
|
|
15762
15812
|
throw usageError("Use research crawl <domain> --checkpoint <file> --output <file> [--batch-size <1-100>] [--locale <tag>] [--pages priority|all] [--max-pages <n|none>] [--retry-failed|--refresh], research capture --input <selected-page-file> --checkpoint <file> --output <corpus-file>, research readiness --input <file> --output <file>, or research video <url> --output <directory> [--frames <0-30>].", context);
|
|
15763
15813
|
}
|
|
15764
15814
|
if (positionals[0] === "memory") return parseMemoryArguments(positionals, values, context, { evaluationCaseId, json: json2, timeoutMs });
|
|
15815
|
+
if (positionals[0] === "support") return parseSupportArguments(positionals, values, context, { json: json2, timeoutMs });
|
|
15816
|
+
if (values.tag !== void 0) throw usageError("--tag is only valid with obi support.", context);
|
|
15765
15817
|
if (stage !== void 0 || input !== void 0 || output !== void 0 || checkpoint !== void 0 || batchSize !== void 0 || retryFailed || refresh || scopeFlagsUsed || values.frames !== void 0 || free || evidenceOptionsUsed) {
|
|
15766
15818
|
throw usageError("--stage and --input are only valid with obi harness.", context);
|
|
15767
15819
|
}
|
|
@@ -15856,9 +15908,6 @@ function parseCommandArguments(argv) {
|
|
|
15856
15908
|
}
|
|
15857
15909
|
if (command === "doctor") {
|
|
15858
15910
|
if (evaluationCaseId !== void 0) throw usageError("Doctor cannot use --eval-case.", context);
|
|
15859
|
-
if (timeoutValue !== void 0 && testServer === void 0) {
|
|
15860
|
-
throw usageError("A timeout is only valid with a test server.", context);
|
|
15861
|
-
}
|
|
15862
15911
|
return { command, json: json2, testServer, timeoutMs };
|
|
15863
15912
|
}
|
|
15864
15913
|
if (command === "status") {
|
|
@@ -16587,14 +16636,78 @@ var BusinessItemReviewRequestSchema = external_exports.discriminatedUnion("decis
|
|
|
16587
16636
|
})
|
|
16588
16637
|
]);
|
|
16589
16638
|
|
|
16639
|
+
// ../shared/lib/geo/markets.ts
|
|
16640
|
+
var SUPPORTED_MARKETS = [
|
|
16641
|
+
{ iso: "US", name: "United States" },
|
|
16642
|
+
{ iso: "CA", name: "Canada" },
|
|
16643
|
+
{ iso: "GB", name: "United Kingdom" },
|
|
16644
|
+
{ iso: "FR", name: "France" },
|
|
16645
|
+
{ iso: "DE", name: "Germany" },
|
|
16646
|
+
{ iso: "ES", name: "Spain" },
|
|
16647
|
+
{ iso: "IT", name: "Italy" },
|
|
16648
|
+
{ iso: "NL", name: "Netherlands" },
|
|
16649
|
+
{ iso: "BE", name: "Belgium" },
|
|
16650
|
+
{ iso: "CH", name: "Switzerland" },
|
|
16651
|
+
{ iso: "AU", name: "Australia" },
|
|
16652
|
+
{ iso: "SA", name: "Saudi Arabia" },
|
|
16653
|
+
{ iso: "AE", name: "UAE" },
|
|
16654
|
+
{ iso: "QA", name: "Qatar" },
|
|
16655
|
+
{ iso: "KW", name: "Kuwait" }
|
|
16656
|
+
];
|
|
16657
|
+
var BY_KEY = new Map(
|
|
16658
|
+
SUPPORTED_MARKETS.flatMap((market) => [
|
|
16659
|
+
[market.iso.toLowerCase(), market],
|
|
16660
|
+
[market.name.toLowerCase(), market]
|
|
16661
|
+
])
|
|
16662
|
+
);
|
|
16663
|
+
var SUPPORTED_MARKET_NAMES = SUPPORTED_MARKETS.map(
|
|
16664
|
+
(market) => market.name
|
|
16665
|
+
);
|
|
16666
|
+
|
|
16667
|
+
// ../shared/lib/cli-version.ts
|
|
16668
|
+
var CLI_VERSION_HEADER = "x-obi-cli-version";
|
|
16669
|
+
var CLI_UPDATE_COMMAND = "npm i -g @hireobi/cli";
|
|
16670
|
+
var release = /^(0|[1-9][0-9]{0,8})\.(0|[1-9][0-9]{0,8})\.(0|[1-9][0-9]{0,8})$/;
|
|
16671
|
+
function parseRelease(version3) {
|
|
16672
|
+
const match = release.exec(version3);
|
|
16673
|
+
return match === null ? null : [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
16674
|
+
}
|
|
16675
|
+
function isOlderRelease(version3, other) {
|
|
16676
|
+
const left = parseRelease(version3);
|
|
16677
|
+
const right = parseRelease(other);
|
|
16678
|
+
if (left === null || right === null) return false;
|
|
16679
|
+
for (let index = 0; index < 3; index += 1) {
|
|
16680
|
+
if (left[index] !== right[index]) return left[index] < right[index];
|
|
16681
|
+
}
|
|
16682
|
+
return false;
|
|
16683
|
+
}
|
|
16684
|
+
|
|
16685
|
+
// src/version.ts
|
|
16686
|
+
var cliVersion = "0.1.10";
|
|
16687
|
+
|
|
16590
16688
|
// src/product-api-failures.ts
|
|
16689
|
+
var versionHeaders = { [CLI_VERSION_HEADER]: cliVersion };
|
|
16690
|
+
function updateRequiredError() {
|
|
16691
|
+
return new CliError({
|
|
16692
|
+
code: "CLI_UPDATE_REQUIRED",
|
|
16693
|
+
message: `This Obi CLI (${cliVersion}) is too old for the Obi service.`,
|
|
16694
|
+
exitCode: 7,
|
|
16695
|
+
retryable: false,
|
|
16696
|
+
nextAction: `Run ${CLI_UPDATE_COMMAND}, then run obi once to refresh the Skill, and try again.`,
|
|
16697
|
+
mode: "product"
|
|
16698
|
+
});
|
|
16699
|
+
}
|
|
16700
|
+
function apiFailure(code, httpStatus, retryAfterSeconds) {
|
|
16701
|
+
if (code === "CLI_UPDATE_REQUIRED") throw updateRequiredError();
|
|
16702
|
+
return { ok: false, failure: { code, httpStatus, retryAfterSeconds } };
|
|
16703
|
+
}
|
|
16591
16704
|
function transportError(code, message, exitCode, retryable) {
|
|
16592
16705
|
return new CliError({
|
|
16593
16706
|
code,
|
|
16594
16707
|
message,
|
|
16595
16708
|
exitCode,
|
|
16596
16709
|
retryable,
|
|
16597
|
-
nextAction: exitCode === 6 ?
|
|
16710
|
+
nextAction: exitCode === 6 ? `Check the network connection and try again. If it keeps failing, run obi support "<what happened>" or write to ${SUPPORT_EMAIL} with the command you ran and the error code ${code}.` : "Update the Obi CLI, then try again.",
|
|
16598
16711
|
mode: "product"
|
|
16599
16712
|
});
|
|
16600
16713
|
}
|
|
@@ -16624,6 +16737,14 @@ function failureError(failure2, evaluationCaseId) {
|
|
|
16624
16737
|
mode: "product",
|
|
16625
16738
|
nextAction: `Run obi business show${scope} --json and read the current work before preparing a new result.`
|
|
16626
16739
|
});
|
|
16740
|
+
if (failure2.code === "MARKET_UNSUPPORTED") return new CliError({
|
|
16741
|
+
code: failure2.code,
|
|
16742
|
+
message: "Obi does not measure this market yet.",
|
|
16743
|
+
exitCode: 5,
|
|
16744
|
+
retryable: false,
|
|
16745
|
+
mode: "product",
|
|
16746
|
+
nextAction: `Obi measures in: ${SUPPORTED_MARKET_NAMES.join(", ")}. Choose one of them with the customer, or record the request with obi support "<market and domain>" --tag market-request.`
|
|
16747
|
+
});
|
|
16627
16748
|
if (failure2.code === "RATE_LIMITED") {
|
|
16628
16749
|
const wait = failure2.retryAfterSeconds === null ? "a moment" : `${failure2.retryAfterSeconds} seconds`;
|
|
16629
16750
|
return new CliError({
|
|
@@ -17209,11 +17330,13 @@ var pendingAuthorizationViewSchema = external_exports.object({
|
|
|
17209
17330
|
var originMarker = {
|
|
17210
17331
|
origin: external_exports.string().min(1).max(200).optional(),
|
|
17211
17332
|
development: external_exports.literal(true).optional(),
|
|
17212
|
-
evaluationCaseId: external_exports.uuid().optional()
|
|
17333
|
+
evaluationCaseId: external_exports.uuid().optional(),
|
|
17334
|
+
// Set when npm holds a newer CLI than this one (cli/src/update-check.ts).
|
|
17335
|
+
update: external_exports.object({ installed: external_exports.string().max(32), latest: external_exports.string().max(32), command: external_exports.string().max(80) }).optional()
|
|
17213
17336
|
};
|
|
17214
17337
|
var helpDataSchema = external_exports.object({
|
|
17215
17338
|
usage: external_exports.string(),
|
|
17216
|
-
commands: external_exports.array(external_exports.enum(["business", "capabilities", "credentials", "doctor", "eval", "harness", "login", "logout", "memory", "questions", "readiness", "research", "skill", "status", "strategies"]))
|
|
17339
|
+
commands: external_exports.array(external_exports.enum(["business", "capabilities", "credentials", "doctor", "eval", "harness", "login", "logout", "memory", "questions", "readiness", "research", "skill", "status", "strategies", "support"]))
|
|
17217
17340
|
});
|
|
17218
17341
|
var versionDataSchema = external_exports.object({ version: external_exports.string() });
|
|
17219
17342
|
var skillDataSchema = external_exports.discriminatedUnion("action", [
|
|
@@ -17223,6 +17346,13 @@ var skillDataSchema = external_exports.discriminatedUnion("action", [
|
|
|
17223
17346
|
var capabilitiesDataSchema = external_exports.object({
|
|
17224
17347
|
expertise: external_exports.object({ manifestVersion: external_exports.string(), competencies: external_exports.array(external_exports.object({ id: external_exports.string(), reference: external_exports.string(), sha256: external_exports.string(), reviewedOn: external_exports.string(), sources: external_exports.array(external_exports.string()) })) }).optional(),
|
|
17225
17348
|
version: external_exports.string(),
|
|
17349
|
+
measurementMarkets: external_exports.array(external_exports.object({ iso: external_exports.string().length(2), name: external_exports.string().min(1) })),
|
|
17350
|
+
catalog: external_exports.object({ version: external_exports.string(), currency: external_exports.literal("usd"), monthlyCents: external_exports.object({
|
|
17351
|
+
corePack: external_exports.number().int().positive(),
|
|
17352
|
+
claudePerPack: external_exports.number().int().positive(),
|
|
17353
|
+
perplexityPerPack: external_exports.number().int().positive()
|
|
17354
|
+
}) }),
|
|
17355
|
+
support: external_exports.object({ email: external_exports.literal(SUPPORT_EMAIL) }),
|
|
17226
17356
|
realLogin: external_exports.literal("available"),
|
|
17227
17357
|
paidExecution: external_exports.literal("server_authorized"),
|
|
17228
17358
|
commands: external_exports.object({
|
|
@@ -17240,15 +17370,21 @@ var capabilitiesDataSchema = external_exports.object({
|
|
|
17240
17370
|
memory: external_exports.object({ actions: external_exports.array(external_exports.string()), options: external_exports.array(external_exports.string()) }).optional(),
|
|
17241
17371
|
status: external_exports.object({ options: external_exports.array(external_exports.string()) }),
|
|
17242
17372
|
readiness: external_exports.object({ options: external_exports.array(external_exports.string()) }),
|
|
17243
|
-
strategies: external_exports.object({ options: external_exports.array(external_exports.string()) })
|
|
17373
|
+
strategies: external_exports.object({ options: external_exports.array(external_exports.string()) }),
|
|
17374
|
+
support: external_exports.object({ tags: external_exports.array(external_exports.enum(SUPPORT_TAGS)), options: external_exports.array(external_exports.string()) })
|
|
17244
17375
|
})
|
|
17245
17376
|
});
|
|
17246
17377
|
var localDoctorDataSchema = external_exports.object({
|
|
17247
17378
|
node: external_exports.string(),
|
|
17248
17379
|
cliVersion: external_exports.string(),
|
|
17380
|
+
latestVersion: external_exports.string().nullable(),
|
|
17381
|
+
// Set only when npm could not be reached: the last version it named.
|
|
17382
|
+
lastKnownVersion: external_exports.string().nullable(),
|
|
17383
|
+
updateCheck: external_exports.enum(["on", "off"]),
|
|
17249
17384
|
origin: external_exports.string(),
|
|
17250
17385
|
credentialStorage: external_exports.union([backendKindSchema, external_exports.literal("none")]),
|
|
17251
17386
|
videoTools: external_exports.object({ ytDlp: external_exports.boolean(), ffmpeg: external_exports.boolean() }),
|
|
17387
|
+
server: external_exports.enum(["reachable", "degraded", "unreachable"]),
|
|
17252
17388
|
diagnostic: external_exports.literal("completed")
|
|
17253
17389
|
});
|
|
17254
17390
|
var loginDataSchema = external_exports.object({
|
|
@@ -17545,6 +17681,7 @@ var successfulResultSchema = external_exports.union([
|
|
|
17545
17681
|
external_exports.object({ ...envelope2, command: external_exports.literal("harness"), mode: external_exports.literal("product"), data: harnessCoverageDataSchema }),
|
|
17546
17682
|
...harnessEvidenceDataSchema.map((schema) => external_exports.object({ ...envelope2, command: external_exports.literal("harness"), mode: external_exports.literal("product"), data: schema })),
|
|
17547
17683
|
external_exports.object({ ...envelope2, command: external_exports.literal("harness"), mode: external_exports.literal("product"), data: harnessEvidenceExportDataSchema }),
|
|
17684
|
+
external_exports.object({ ...envelope2, command: external_exports.literal("support"), mode: external_exports.literal("product"), data: supportDataSchema }),
|
|
17548
17685
|
external_exports.object({ ...envelope2, command: external_exports.literal("memory"), mode: external_exports.literal("product"), data: memoryListDataSchema }),
|
|
17549
17686
|
external_exports.object({ ...envelope2, command: external_exports.literal("memory"), mode: external_exports.literal("product"), data: memoryChangeDataSchema }),
|
|
17550
17687
|
external_exports.object({
|
|
@@ -17595,7 +17732,8 @@ var failedResultSchema = external_exports.object({
|
|
|
17595
17732
|
...originMarker,
|
|
17596
17733
|
error: errorSchema,
|
|
17597
17734
|
authorization: pendingAuthorizationViewSchema.optional(),
|
|
17598
|
-
account: confirmedAccountSchema.optional()
|
|
17735
|
+
account: confirmedAccountSchema.optional(),
|
|
17736
|
+
support: supportDataSchema.optional()
|
|
17599
17737
|
});
|
|
17600
17738
|
var cliResultSchema = external_exports.union([successfulResultSchema, failedResultSchema]);
|
|
17601
17739
|
|
|
@@ -17684,7 +17822,7 @@ function retryAfter(response) {
|
|
|
17684
17822
|
return typeof value === "string" && /^[0-9]{1,5}$/.test(value) ? Number(value) : null;
|
|
17685
17823
|
}
|
|
17686
17824
|
async function send(context, request) {
|
|
17687
|
-
const headers2 = { accept: "application/json" };
|
|
17825
|
+
const headers2 = { accept: "application/json", ...versionHeaders };
|
|
17688
17826
|
if (request.body !== void 0) {
|
|
17689
17827
|
headers2["content-type"] = "application/json";
|
|
17690
17828
|
}
|
|
@@ -17894,12 +18032,7 @@ function interpret(response, schema) {
|
|
|
17894
18032
|
return { ok: true, data: parsed.data.data };
|
|
17895
18033
|
}
|
|
17896
18034
|
const failure2 = ProductApiErrorResponseSchema.safeParse(body);
|
|
17897
|
-
if (failure2.success)
|
|
17898
|
-
return {
|
|
17899
|
-
ok: false,
|
|
17900
|
-
failure: { code: failure2.data.error.code, httpStatus: response.status, retryAfterSeconds: retryAfter(response) }
|
|
17901
|
-
};
|
|
17902
|
-
}
|
|
18035
|
+
if (failure2.success) return apiFailure(failure2.data.error.code, response.status, retryAfter(response));
|
|
17903
18036
|
if (response.status >= 500) {
|
|
17904
18037
|
throw transportError("SERVICE_UNAVAILABLE", "The Obi API is unavailable.", 6, true);
|
|
17905
18038
|
}
|
|
@@ -18060,6 +18193,9 @@ async function readReadiness(context, accessToken) {
|
|
|
18060
18193
|
responseLimitBytes: BUSINESS_REVIEW_RESPONSE_LIMIT_BYTES
|
|
18061
18194
|
}), ProductReadinessResponseSchema);
|
|
18062
18195
|
}
|
|
18196
|
+
async function sendSupportMessage(context, accessToken, message) {
|
|
18197
|
+
return interpretReview(await send(context, { method: "POST", path: "/api/v1/support", bearer: accessToken, body: SupportMessageSchema.parse(message) }), SupportMessageResponseSchema);
|
|
18198
|
+
}
|
|
18063
18199
|
|
|
18064
18200
|
// src/refresh.ts
|
|
18065
18201
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
@@ -19210,7 +19346,7 @@ async function submitHarnessObservation(context, path) {
|
|
|
19210
19346
|
|
|
19211
19347
|
// src/commands.ts
|
|
19212
19348
|
import { access } from "node:fs/promises";
|
|
19213
|
-
import { fileURLToPath } from "node:url";
|
|
19349
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19214
19350
|
|
|
19215
19351
|
// src/onboarding-guide.ts
|
|
19216
19352
|
var READING_POLL_MS = 6e4;
|
|
@@ -20275,6 +20411,108 @@ function memoryText(data) {
|
|
|
20275
20411
|
`;
|
|
20276
20412
|
}
|
|
20277
20413
|
|
|
20414
|
+
// src/support-commands.ts
|
|
20415
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
20416
|
+
import { release as release2 } from "node:os";
|
|
20417
|
+
|
|
20418
|
+
// src/last-error.ts
|
|
20419
|
+
import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
|
|
20420
|
+
import { join as join4 } from "node:path";
|
|
20421
|
+
var fileIn = (stateDirectory) => join4(stateDirectory, "last-error.json");
|
|
20422
|
+
async function recordLastError(stateDirectory, error51) {
|
|
20423
|
+
try {
|
|
20424
|
+
const parsed = lastErrorSchema.safeParse({ ...error51, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
20425
|
+
if (!parsed.success) return;
|
|
20426
|
+
await ensureStateDirectory(stateDirectory);
|
|
20427
|
+
await writeFile3(fileIn(stateDirectory), JSON.stringify(parsed.data), { mode: 384 });
|
|
20428
|
+
} catch {
|
|
20429
|
+
}
|
|
20430
|
+
}
|
|
20431
|
+
async function readLastError(stateDirectory) {
|
|
20432
|
+
try {
|
|
20433
|
+
const parsed = lastErrorSchema.safeParse(JSON.parse(await readFile6(fileIn(stateDirectory), "utf8")));
|
|
20434
|
+
return parsed.success ? parsed.data : null;
|
|
20435
|
+
} catch {
|
|
20436
|
+
return null;
|
|
20437
|
+
}
|
|
20438
|
+
}
|
|
20439
|
+
|
|
20440
|
+
// src/support-commands.ts
|
|
20441
|
+
async function probeServer(origin, timeoutMs) {
|
|
20442
|
+
try {
|
|
20443
|
+
const response = await exchange({
|
|
20444
|
+
url: new URL("/api/health", origin.origin),
|
|
20445
|
+
method: "GET",
|
|
20446
|
+
headers: { accept: "application/json" },
|
|
20447
|
+
timeoutMs
|
|
20448
|
+
});
|
|
20449
|
+
return response.status === 200 ? "reachable" : "degraded";
|
|
20450
|
+
} catch {
|
|
20451
|
+
return "unreachable";
|
|
20452
|
+
}
|
|
20453
|
+
}
|
|
20454
|
+
async function sendSupport(context, input) {
|
|
20455
|
+
const lastError = await readLastError(context.stateDirectory);
|
|
20456
|
+
const diagnostic = { cliVersion, node: process.version, os: `${process.platform} ${release2()} ${process.arch}`, lastError };
|
|
20457
|
+
let account2 = null;
|
|
20458
|
+
let reason = null;
|
|
20459
|
+
try {
|
|
20460
|
+
const located = await locateCredential(context);
|
|
20461
|
+
if (located === null) {
|
|
20462
|
+
reason = "AUTH_REQUIRED";
|
|
20463
|
+
} else {
|
|
20464
|
+
account2 = located.credential.account.email;
|
|
20465
|
+
const requestId = createHash2("sha256").update(JSON.stringify([
|
|
20466
|
+
context.origin.origin,
|
|
20467
|
+
account2,
|
|
20468
|
+
input.message,
|
|
20469
|
+
input.tag,
|
|
20470
|
+
(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
20471
|
+
])).digest("hex").slice(0, 32);
|
|
20472
|
+
const { result } = await useCredential(
|
|
20473
|
+
context,
|
|
20474
|
+
located,
|
|
20475
|
+
(token) => sendSupportMessage(context, token, { requestId, ...input, diagnostic })
|
|
20476
|
+
);
|
|
20477
|
+
if (!result.ok) reason = result.failure.httpStatus >= 500 ? "SERVICE_UNAVAILABLE" : result.failure.code;
|
|
20478
|
+
}
|
|
20479
|
+
} catch (error51) {
|
|
20480
|
+
reason = error51 instanceof CliError ? error51.code : "INTERNAL_ERROR";
|
|
20481
|
+
}
|
|
20482
|
+
const diagnosticText = [
|
|
20483
|
+
`Message: ${input.message}`,
|
|
20484
|
+
`Tag: ${input.tag ?? "general"}`,
|
|
20485
|
+
`Obi CLI ${diagnostic.cliVersion} \xB7 Node ${diagnostic.node} \xB7 ${diagnostic.os}`,
|
|
20486
|
+
`Last error: ${lastError === null ? "none" : `${lastError.code} (${lastError.command}) at ${lastError.at}`}`,
|
|
20487
|
+
`Account: ${account2 ?? "not signed in"}`
|
|
20488
|
+
].join("\n");
|
|
20489
|
+
const data = {
|
|
20490
|
+
delivery: reason === null ? "sent" : "not_sent",
|
|
20491
|
+
reason,
|
|
20492
|
+
supportEmail: SUPPORT_EMAIL,
|
|
20493
|
+
mailto: `mailto:${SUPPORT_EMAIL}`,
|
|
20494
|
+
tag: input.tag,
|
|
20495
|
+
account: account2,
|
|
20496
|
+
origin: context.origin.origin,
|
|
20497
|
+
diagnosticText
|
|
20498
|
+
};
|
|
20499
|
+
if (reason === null) return data;
|
|
20500
|
+
throw new CliError({
|
|
20501
|
+
code: "SUPPORT_NOT_SENT",
|
|
20502
|
+
message: `The message was not sent to Obi support (${reason}).`,
|
|
20503
|
+
exitCode: reason === "AUTH_REQUIRED" ? 3 : 6,
|
|
20504
|
+
retryable: reason !== "AUTH_REQUIRED",
|
|
20505
|
+
mode: "product",
|
|
20506
|
+
command: "support",
|
|
20507
|
+
nextAction: `Write to ${SUPPORT_EMAIL} and paste the block below (support.diagnosticText with --json). Never add a token or a credential file.`,
|
|
20508
|
+
support: data
|
|
20509
|
+
});
|
|
20510
|
+
}
|
|
20511
|
+
function supportText(data) {
|
|
20512
|
+
return `Sent to Obi support (${data.supportEmail}). The reply goes to ${sanitizeHumanText(data.account ?? "the account email")}.
|
|
20513
|
+
`;
|
|
20514
|
+
}
|
|
20515
|
+
|
|
20278
20516
|
// src/scan-output.ts
|
|
20279
20517
|
function heading(data) {
|
|
20280
20518
|
const scored = `${data.scoredCells}/${data.expectedCells} answers scored from question version ${data.candidateVersion ?? "none"}.`;
|
|
@@ -20399,6 +20637,7 @@ function failureText(result) {
|
|
|
20399
20637
|
lines.push(`Approving account: ${sanitizeHumanText(result.account.email)} (${result.account.role}).`);
|
|
20400
20638
|
}
|
|
20401
20639
|
lines.push(sanitizeHumanText(result.error.nextAction));
|
|
20640
|
+
if (result.support !== void 0) lines.push("", ...result.support.diagnosticText.split("\n").map(sanitizeHumanText));
|
|
20402
20641
|
return `${lines.join("\n")}
|
|
20403
20642
|
`;
|
|
20404
20643
|
}
|
|
@@ -20441,11 +20680,12 @@ function toHumanOutput(result) {
|
|
|
20441
20680
|
return `Obi \xB7 AI Search Employee
|
|
20442
20681
|
|
|
20443
20682
|
Get started
|
|
20444
|
-
1.
|
|
20445
|
-
2. In a new Claude Code or Cursor chat, type /obi.
|
|
20683
|
+
1. In Claude Code or Cursor, type /obi.
|
|
20446
20684
|
In Codex CLI, type $obi or choose Obi from /skills.
|
|
20447
20685
|
In the desktop app, select Obi from the skills picker.
|
|
20448
|
-
|
|
20686
|
+
In OpenCode, type @obi.
|
|
20687
|
+
2. Give Obi a website or ask to continue saved work.
|
|
20688
|
+
Every obi command registers the Skill; obi skill install repairs it.
|
|
20449
20689
|
|
|
20450
20690
|
Commands
|
|
20451
20691
|
${usage2}
|
|
@@ -20457,11 +20697,11 @@ ${usage2}
|
|
|
20457
20697
|
}
|
|
20458
20698
|
if (result.command === "skill") {
|
|
20459
20699
|
return result.data.action === "path" ? `${sanitizeHumanText(result.data.path)}
|
|
20460
|
-
` : `Obi Skill registered locally. In a new Claude Code or Cursor chat, type /obi. In Codex CLI, type $obi or choose Obi from /skills. In the desktop app, select Obi from the skills picker.
|
|
20700
|
+
` : `Obi Skill registered locally. In a new Claude Code or Cursor chat, type /obi. In Codex CLI, type $obi or choose Obi from /skills. In the desktop app, select Obi from the skills picker. In OpenCode, type @obi.
|
|
20461
20701
|
${result.data.removed.length > 0 ? "Earlier Skill links of this package under another name were removed; use /obi.\n" : ""}`;
|
|
20462
20702
|
}
|
|
20463
20703
|
if (result.command === "capabilities") {
|
|
20464
|
-
return "Available: business, capabilities, credentials, doctor, eval, harness, login, logout, memory, questions, readiness, research, skill, status, strategies. Scan execution requires server authorization and a separate customer decision.\n";
|
|
20704
|
+
return "Available: business, capabilities, credentials, doctor, eval, harness, login, logout, memory, questions, readiness, research, skill, status, strategies, support. Scan execution requires server authorization and a separate customer decision.\n";
|
|
20465
20705
|
}
|
|
20466
20706
|
if (result.command === "eval") {
|
|
20467
20707
|
if ("evaluation" in result.data) {
|
|
@@ -20476,6 +20716,7 @@ Next: ${sanitizeHumanText(result.data.nextStep)}
|
|
|
20476
20716
|
return result.data.action === "list" ? connectionListText(result.data) : revocationText(result.data);
|
|
20477
20717
|
}
|
|
20478
20718
|
if (result.command === "memory") return memoryText(result.data);
|
|
20719
|
+
if (result.command === "support") return supportText(result.data);
|
|
20479
20720
|
if (result.command === "doctor") {
|
|
20480
20721
|
if (result.mode === "test") {
|
|
20481
20722
|
return `Synthetic test protocol reachable. State: ${String(result.data.state)}.
|
|
@@ -20483,7 +20724,14 @@ Next: ${sanitizeHumanText(result.data.nextStep)}
|
|
|
20483
20724
|
}
|
|
20484
20725
|
const storage = result.data.credentialStorage === "none" ? "no stored connection" : `connection stored in ${storageLabels[result.data.credentialStorage]}`;
|
|
20485
20726
|
const video = result.data.videoTools.ytDlp ? `video reading available${result.data.videoTools.ffmpeg ? " with frames" : " (captions only; frames need ffmpeg)"}` : "video reading needs yt-dlp";
|
|
20486
|
-
|
|
20727
|
+
const server = {
|
|
20728
|
+
reachable: "Obi server reachable.",
|
|
20729
|
+
degraded: `Obi server degraded: try again later, and write to ${SUPPORT_EMAIL} if it lasts.`,
|
|
20730
|
+
unreachable: `Obi server unreachable: check the network connection, and write to ${SUPPORT_EMAIL} if it lasts.`
|
|
20731
|
+
}[result.data.server];
|
|
20732
|
+
const { cliVersion: installed, latestVersion: latest } = result.data;
|
|
20733
|
+
const version3 = result.data.updateCheck === "off" ? `CLI ${sanitizeHumanText(installed)} (update check off)` : latest === null ? `CLI ${sanitizeHumanText(installed)} (latest version unknown: npm could not be reached${result.data.lastKnownVersion === null ? "" : `, last known ${sanitizeHumanText(result.data.lastKnownVersion)}`})` : isOlderRelease(installed, latest) ? `CLI ${sanitizeHumanText(installed)}, latest ${sanitizeHumanText(latest)}: update with ${CLI_UPDATE_COMMAND}` : `CLI ${sanitizeHumanText(installed)}, the latest`;
|
|
20734
|
+
return `Local diagnostic completed for ${sanitizeHumanText(result.data.origin)}: ${version3}; ${storage}; ${video}. ${server}
|
|
20487
20735
|
`;
|
|
20488
20736
|
}
|
|
20489
20737
|
if (result.command === "login") {
|
|
@@ -20819,9 +21067,6 @@ async function confirmAccount(account2) {
|
|
|
20819
21067
|
}
|
|
20820
21068
|
}
|
|
20821
21069
|
|
|
20822
|
-
// src/version.ts
|
|
20823
|
-
var cliVersion = "0.1.8";
|
|
20824
|
-
|
|
20825
21070
|
// src/login.ts
|
|
20826
21071
|
var loginRequestTimeoutMs = 1e4;
|
|
20827
21072
|
var maxLinkLength = 200;
|
|
@@ -21031,7 +21276,7 @@ function accountMismatch(account2, revoked, expected) {
|
|
|
21031
21276
|
}
|
|
21032
21277
|
async function confirmAndStore(context, store, pair, receivedAt, expectedEmail) {
|
|
21033
21278
|
const api = context;
|
|
21034
|
-
const
|
|
21279
|
+
const release4 = guardNewFamily(api, pair);
|
|
21035
21280
|
let stored = false;
|
|
21036
21281
|
try {
|
|
21037
21282
|
const status = await readAccountStatus(api, pair.accessToken);
|
|
@@ -21062,7 +21307,7 @@ async function confirmAndStore(context, store, pair, receivedAt, expectedEmail)
|
|
|
21062
21307
|
}
|
|
21063
21308
|
await store.writeCredential(credentialFromPair(context.origin.origin, pair, confirmed, receivedAt));
|
|
21064
21309
|
stored = true;
|
|
21065
|
-
|
|
21310
|
+
release4();
|
|
21066
21311
|
});
|
|
21067
21312
|
try {
|
|
21068
21313
|
await store.removePending();
|
|
@@ -21082,7 +21327,7 @@ async function confirmAndStore(context, store, pair, receivedAt, expectedEmail)
|
|
|
21082
21327
|
}
|
|
21083
21328
|
throw error51;
|
|
21084
21329
|
} finally {
|
|
21085
|
-
|
|
21330
|
+
release4();
|
|
21086
21331
|
}
|
|
21087
21332
|
}
|
|
21088
21333
|
async function login(session, options) {
|
|
@@ -21221,12 +21466,12 @@ async function proceedToFirstScan(context, candidateVersion) {
|
|
|
21221
21466
|
}
|
|
21222
21467
|
|
|
21223
21468
|
// src/research-commands.ts
|
|
21224
|
-
import { readFile as
|
|
21469
|
+
import { readFile as readFile7, stat as stat6 } from "node:fs/promises";
|
|
21225
21470
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
21226
21471
|
import { resolve as resolve2 } from "node:path";
|
|
21227
21472
|
|
|
21228
21473
|
// ../geo-engine/src/geo/host-acquisition.ts
|
|
21229
|
-
import { createHash as
|
|
21474
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
21230
21475
|
|
|
21231
21476
|
// ../geo-engine/src/url-safety.ts
|
|
21232
21477
|
var PRIVATE_IP_RE = /^(127\.|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2\d|3[01])\.|0\.)/;
|
|
@@ -21397,7 +21642,7 @@ async function safeFetch(url3, opts = {}) {
|
|
|
21397
21642
|
if (!REDIRECT_STATUSES.has(res.status)) return res;
|
|
21398
21643
|
const location = res.headers.get("location");
|
|
21399
21644
|
if (!location) return res;
|
|
21400
|
-
await
|
|
21645
|
+
await release3(res);
|
|
21401
21646
|
let next;
|
|
21402
21647
|
try {
|
|
21403
21648
|
next = new URL(location, current).href;
|
|
@@ -21408,7 +21653,7 @@ async function safeFetch(url3, opts = {}) {
|
|
|
21408
21653
|
}
|
|
21409
21654
|
throw new TooManyRedirectsError(url3, maxRedirects);
|
|
21410
21655
|
}
|
|
21411
|
-
async function
|
|
21656
|
+
async function release3(res) {
|
|
21412
21657
|
await res.body?.cancel().catch((err) => {
|
|
21413
21658
|
console.warn("[safe-fetch] cancelling an unread body failed:", err);
|
|
21414
21659
|
});
|
|
@@ -21416,7 +21661,7 @@ async function release(res) {
|
|
|
21416
21661
|
async function readCapped(res, maxBytes = MAX_RESPONSE_BYTES) {
|
|
21417
21662
|
const declared = Number(res.headers.get("content-length"));
|
|
21418
21663
|
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
21419
|
-
await
|
|
21664
|
+
await release3(res);
|
|
21420
21665
|
return "";
|
|
21421
21666
|
}
|
|
21422
21667
|
if (!res.body) return (await res.text()).slice(0, maxBytes);
|
|
@@ -21582,7 +21827,7 @@ function createVettedTransport(options = {}) {
|
|
|
21582
21827
|
}
|
|
21583
21828
|
|
|
21584
21829
|
// ../geo-engine/src/geo/page-evidence.ts
|
|
21585
|
-
import { createHash as
|
|
21830
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
21586
21831
|
|
|
21587
21832
|
// ../node_modules/parse5/dist/common/unicode.js
|
|
21588
21833
|
var UNDEFINED_CODE_POINTS = /* @__PURE__ */ new Set([
|
|
@@ -29502,7 +29747,7 @@ function* descendants(node) {
|
|
|
29502
29747
|
}
|
|
29503
29748
|
}
|
|
29504
29749
|
function sha256(s) {
|
|
29505
|
-
return
|
|
29750
|
+
return createHash3("sha256").update(s).digest("hex");
|
|
29506
29751
|
}
|
|
29507
29752
|
var SKIPPED = /* @__PURE__ */ new Set(["script", "style", "noscript", "template", "svg", "iframe", "object", "canvas", "select", "head"]);
|
|
29508
29753
|
var HEADING = /^h([1-6])$/;
|
|
@@ -29793,7 +30038,7 @@ function headingTexts(html, level) {
|
|
|
29793
30038
|
}
|
|
29794
30039
|
|
|
29795
30040
|
// ../geo-engine/src/geo/selected-page-evidence.ts
|
|
29796
|
-
import { createHash as
|
|
30041
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
29797
30042
|
|
|
29798
30043
|
// ../geo-engine/src/geo/material-fact-contract.ts
|
|
29799
30044
|
var text = external_exports.string().trim().min(1);
|
|
@@ -29899,10 +30144,10 @@ var SelectedPageEvidenceSchema = external_exports.strictObject({
|
|
|
29899
30144
|
function selectedPageId(pageUrl) {
|
|
29900
30145
|
const selected = new URL(pageUrl);
|
|
29901
30146
|
selected.hash = "";
|
|
29902
|
-
return `page:${
|
|
30147
|
+
return `page:${createHash4("sha256").update(selected.href).digest("hex")}`;
|
|
29903
30148
|
}
|
|
29904
30149
|
function observationBodyHash(body) {
|
|
29905
|
-
return
|
|
30150
|
+
return createHash4("sha256").update(body).digest("hex");
|
|
29906
30151
|
}
|
|
29907
30152
|
function sameSelectedHost(selectedUrl, finalUrl) {
|
|
29908
30153
|
const host = (value) => new URL(value).hostname.toLowerCase().replace(/^www\./, "");
|
|
@@ -30354,14 +30599,14 @@ function evaluateAccess(parsed, token, url3, fallbackTokens = []) {
|
|
|
30354
30599
|
}
|
|
30355
30600
|
|
|
30356
30601
|
// ../geo-engine/src/geo/readiness/findings.ts
|
|
30357
|
-
import { createHash as
|
|
30602
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
30358
30603
|
function issueId(origin, checkId, scopeKey) {
|
|
30359
|
-
return
|
|
30604
|
+
return createHash5("sha256").update(`${origin.toLowerCase()}
|
|
30360
30605
|
${checkId}
|
|
30361
30606
|
${scopeKey}`).digest("hex").slice(0, 16);
|
|
30362
30607
|
}
|
|
30363
30608
|
function fingerprint(body) {
|
|
30364
|
-
return body === null ? null :
|
|
30609
|
+
return body === null ? null : createHash5("sha256").update(body).digest("hex");
|
|
30365
30610
|
}
|
|
30366
30611
|
function makeFinding({ origin, scopeKey, policyChoice = false, materiality = "site", ...rest }) {
|
|
30367
30612
|
return { id: issueId(origin, rest.checkId, scopeKey), policyChoice, materiality, ...rest };
|
|
@@ -30864,7 +31109,7 @@ function validateState(raw) {
|
|
|
30864
31109
|
return state;
|
|
30865
31110
|
}
|
|
30866
31111
|
function matchesFingerprint(observation) {
|
|
30867
|
-
return observation.body === null ? observation.bodyHash === null : observation.bodyHash ===
|
|
31112
|
+
return observation.body === null ? observation.bodyHash === null : observation.bodyHash === createHash6("sha256").update(observation.body).digest("hex");
|
|
30868
31113
|
}
|
|
30869
31114
|
function pendingProbeRequests(state) {
|
|
30870
31115
|
const home = state.resources.find((resource) => resource.kind === "page" && resource.url === `${state.origin}/`);
|
|
@@ -32635,7 +32880,7 @@ async function readJson(path, maxBytes = maxCorpusBytes) {
|
|
|
32635
32880
|
try {
|
|
32636
32881
|
const info = await stat6(path);
|
|
32637
32882
|
if (!info.isFile() || info.size > maxBytes) throw fileError(`The research file is not a regular JSON file within the ${maxBytes / 1024 / 1024} MiB local limit.`);
|
|
32638
|
-
const content = await
|
|
32883
|
+
const content = await readFile7(path);
|
|
32639
32884
|
if (content.byteLength > maxBytes) throw fileError(`The research file exceeds the ${maxBytes / 1024 / 1024} MiB local limit.`);
|
|
32640
32885
|
return JSON.parse(content.toString("utf8"));
|
|
32641
32886
|
} catch (error51) {
|
|
@@ -32823,8 +33068,8 @@ async function captureResearch(input, checkpoint, output) {
|
|
|
32823
33068
|
|
|
32824
33069
|
// src/research-video.ts
|
|
32825
33070
|
import { spawn as spawn2 } from "node:child_process";
|
|
32826
|
-
import { mkdir as mkdir2, readdir as readdir3, readFile as
|
|
32827
|
-
import { join as
|
|
33071
|
+
import { mkdir as mkdir2, readdir as readdir3, readFile as readFile8, rm as rm2 } from "node:fs/promises";
|
|
33072
|
+
import { join as join5, resolve as resolve3 } from "node:path";
|
|
32828
33073
|
var maxToolOutputBytes = 256 * 1024;
|
|
32829
33074
|
var runTool = (executable, args, timeoutMs) => new Promise((done, fail) => {
|
|
32830
33075
|
const child = spawn2(executable, [...args], { stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
@@ -32923,12 +33168,12 @@ async function readVideo(rawUrl, outputDir, frames, runner = runTool) {
|
|
|
32923
33168
|
const directory = resolve3(outputDir);
|
|
32924
33169
|
await mkdir2(directory, { recursive: true });
|
|
32925
33170
|
const base = ["--ignore-config", "--no-playlist", "--quiet", "--no-warnings"];
|
|
32926
|
-
const target = ["-o",
|
|
33171
|
+
const target = ["-o", join5(templateSafe(directory), "video.%(ext)s")];
|
|
32927
33172
|
const failed = (message, nextAction) => videoError("RESEARCH_VIDEO_FAILED", message, nextAction);
|
|
32928
33173
|
const metadata = await runner("yt-dlp", [...base, "--skip-download", "--write-info-json", ...target, url3.href], 18e4).catch((error51) => {
|
|
32929
33174
|
throw error51.code === "ENOENT" ? missingTool(frames) : failed("yt-dlp did not finish reading this video page.", "Retry once; a slow or blocked network leaves the video unread.");
|
|
32930
33175
|
});
|
|
32931
|
-
const info = metadata.exitCode === 0 ? JSON.parse(await
|
|
33176
|
+
const info = metadata.exitCode === 0 ? JSON.parse(await readFile8(join5(directory, "video.info.json"), "utf8")) : null;
|
|
32932
33177
|
if (!info) {
|
|
32933
33178
|
throw failed("yt-dlp could not read this video page.", "Check the address in a browser; a private or region-locked video stays unread.");
|
|
32934
33179
|
}
|
|
@@ -32950,11 +33195,11 @@ async function readVideo(rawUrl, outputDir, frames, runner = runTool) {
|
|
|
32950
33195
|
if (subtitles?.exitCode !== 0 || !file2) {
|
|
32951
33196
|
throw failed("The video has captions but their download failed.", "Retry in a minute; the platform may be rate-limiting.");
|
|
32952
33197
|
}
|
|
32953
|
-
segments = parseVtt(await
|
|
33198
|
+
segments = parseVtt(await readFile8(join5(directory, file2), "utf8"));
|
|
32954
33199
|
}
|
|
32955
33200
|
const duration3 = typeof info.duration === "number" ? info.duration : null;
|
|
32956
33201
|
const frameFiles = [];
|
|
32957
|
-
const output =
|
|
33202
|
+
const output = join5(directory, "transcript.json");
|
|
32958
33203
|
const save = () => writeJsonAtomic(
|
|
32959
33204
|
output,
|
|
32960
33205
|
{
|
|
@@ -32974,7 +33219,7 @@ async function readVideo(rawUrl, outputDir, frames, runner = runTool) {
|
|
|
32974
33219
|
throw failed("The video's length is unknown, so frames cannot be spaced.", "Retry with --frames 0 to keep the captions only.");
|
|
32975
33220
|
}
|
|
32976
33221
|
if (frames > 0 && duration3) {
|
|
32977
|
-
const removeMedia = async () => Promise.all((await readdir3(directory)).filter((file2) => file2.startsWith("media.")).map((file2) => rm2(
|
|
33222
|
+
const removeMedia = async () => Promise.all((await readdir3(directory)).filter((file2) => file2.startsWith("media.")).map((file2) => rm2(join5(directory, file2), { force: true })));
|
|
32978
33223
|
await removeMedia();
|
|
32979
33224
|
try {
|
|
32980
33225
|
const download = await runner("yt-dlp", [
|
|
@@ -32982,7 +33227,7 @@ async function readVideo(rawUrl, outputDir, frames, runner = runTool) {
|
|
|
32982
33227
|
"-f",
|
|
32983
33228
|
"b[height<=480]/bv*[height<=480]/w",
|
|
32984
33229
|
"-o",
|
|
32985
|
-
|
|
33230
|
+
join5(templateSafe(directory), "media.%(ext)s"),
|
|
32986
33231
|
url3.href
|
|
32987
33232
|
], 6e5).catch(() => {
|
|
32988
33233
|
throw failed("The video download did not finish.", "Retry with --frames 0 to keep the captions only.");
|
|
@@ -33002,14 +33247,14 @@ async function readVideo(rawUrl, outputDir, frames, runner = runTool) {
|
|
|
33002
33247
|
"-ss",
|
|
33003
33248
|
String(atSeconds),
|
|
33004
33249
|
"-i",
|
|
33005
|
-
|
|
33250
|
+
join5(directory, media),
|
|
33006
33251
|
"-frames:v",
|
|
33007
33252
|
"1",
|
|
33008
33253
|
"-vf",
|
|
33009
33254
|
"scale=1024:-2",
|
|
33010
|
-
|
|
33255
|
+
join5(directory, file2)
|
|
33011
33256
|
], 6e4).catch(() => null);
|
|
33012
|
-
if (extracted?.exitCode === 0) frameFiles.push({ file:
|
|
33257
|
+
if (extracted?.exitCode === 0) frameFiles.push({ file: join5(directory, file2), atSeconds });
|
|
33013
33258
|
}
|
|
33014
33259
|
} finally {
|
|
33015
33260
|
await removeMedia();
|
|
@@ -33128,13 +33373,15 @@ async function showAlphaStrategies(context) {
|
|
|
33128
33373
|
// src/skill-install.ts
|
|
33129
33374
|
import { lstat as lstat3, mkdir as mkdir3, readlink, realpath, symlink, unlink as unlink5 } from "node:fs/promises";
|
|
33130
33375
|
import { homedir as homedir2 } from "node:os";
|
|
33131
|
-
import { join as
|
|
33376
|
+
import { join as join6, sep } from "node:path";
|
|
33377
|
+
import { fileURLToPath } from "node:url";
|
|
33132
33378
|
var skillDirectories = [".agents/skills", ".claude/skills"];
|
|
33133
33379
|
var alphaSkillName = "heysurface";
|
|
33380
|
+
var messageLimit = 300;
|
|
33134
33381
|
function installError(message) {
|
|
33135
33382
|
return new CliError({
|
|
33136
33383
|
code: "SKILL_INSTALL_FAILED",
|
|
33137
|
-
message,
|
|
33384
|
+
message: message.length > messageLimit ? `${message.slice(0, messageLimit - 1)}\u2026` : message,
|
|
33138
33385
|
exitCode: 4,
|
|
33139
33386
|
retryable: false,
|
|
33140
33387
|
nextAction: "Check the personal Skill directories and run obi skill install again. Existing files are never replaced.",
|
|
@@ -33164,7 +33411,7 @@ async function linksIntoPackage(path, packageRoot) {
|
|
|
33164
33411
|
return target === packageRoot || target.startsWith(`${packageRoot}${sep}`);
|
|
33165
33412
|
} catch {
|
|
33166
33413
|
try {
|
|
33167
|
-
return await readlink(path) ===
|
|
33414
|
+
return await readlink(path) === join6(packageRoot, "skills", alphaSkillName);
|
|
33168
33415
|
} catch {
|
|
33169
33416
|
return false;
|
|
33170
33417
|
}
|
|
@@ -33173,37 +33420,70 @@ async function linksIntoPackage(path, packageRoot) {
|
|
|
33173
33420
|
async function installSkill(sourceDirectory) {
|
|
33174
33421
|
const source = await realpath(sourceDirectory);
|
|
33175
33422
|
const home = homedir2();
|
|
33176
|
-
const destinations = skillDirectories.map((directory) =>
|
|
33423
|
+
const destinations = skillDirectories.map((directory) => join6(home, directory, "obi"));
|
|
33424
|
+
const registered = async (destination) => {
|
|
33425
|
+
try {
|
|
33426
|
+
return await realpath(destination) === source;
|
|
33427
|
+
} catch {
|
|
33428
|
+
return false;
|
|
33429
|
+
}
|
|
33430
|
+
};
|
|
33177
33431
|
for (const destination of destinations) {
|
|
33178
|
-
if (!await existingPath(destination)) continue;
|
|
33432
|
+
if (!await existingPath(destination) || await registered(destination)) continue;
|
|
33433
|
+
let target = null;
|
|
33179
33434
|
try {
|
|
33180
|
-
if (await
|
|
33435
|
+
if ((await lstat3(destination)).isSymbolicLink()) target = await readlink(destination);
|
|
33181
33436
|
} catch {
|
|
33437
|
+
continue;
|
|
33182
33438
|
}
|
|
33183
|
-
throw installError(`A Skill entry already exists at ${destination}
|
|
33439
|
+
throw installError(target === null ? `A Skill entry already exists: a file or folder this package did not create. Move it, then run obi skill install. It is at ${destination}` : `A Skill entry already exists: a link outside this installation. To use this installation, remove that link, then run obi skill install. The link: ${destination} -> ${target}`);
|
|
33184
33440
|
}
|
|
33185
33441
|
for (const destination of destinations) {
|
|
33186
|
-
if (await
|
|
33442
|
+
if (await registered(destination)) continue;
|
|
33187
33443
|
try {
|
|
33188
|
-
await mkdir3(
|
|
33444
|
+
await mkdir3(join6(destination, ".."), { recursive: true });
|
|
33189
33445
|
await symlink(source, destination, process.platform === "win32" ? "junction" : "dir");
|
|
33190
33446
|
} catch {
|
|
33191
|
-
|
|
33447
|
+
if (await registered(destination)) continue;
|
|
33448
|
+
throw installError(`Could not register the Obi Skill. Check that its folder can be written, then run obi skill install. The link to make: ${destination}`);
|
|
33192
33449
|
}
|
|
33193
33450
|
}
|
|
33194
33451
|
const removed = [];
|
|
33195
|
-
const packageRoot = await realpath(
|
|
33196
|
-
for (const alpha of skillDirectories.map((directory) =>
|
|
33452
|
+
const packageRoot = await realpath(join6(source, "..", ".."));
|
|
33453
|
+
for (const alpha of skillDirectories.map((directory) => join6(home, directory, alphaSkillName))) {
|
|
33197
33454
|
if (!await linksIntoPackage(alpha, packageRoot)) continue;
|
|
33198
33455
|
try {
|
|
33199
33456
|
await unlink5(alpha);
|
|
33200
|
-
} catch {
|
|
33457
|
+
} catch (error51) {
|
|
33458
|
+
if (error51.code === "ENOENT") continue;
|
|
33201
33459
|
throw installError(`Could not retire the earlier Skill link at ${alpha}.`);
|
|
33202
33460
|
}
|
|
33203
33461
|
removed.push(alpha);
|
|
33204
33462
|
}
|
|
33205
33463
|
return { paths: destinations, removed };
|
|
33206
33464
|
}
|
|
33465
|
+
function packagedSkillDirectory() {
|
|
33466
|
+
return fileURLToPath(new URL("../skills/obi", import.meta.url));
|
|
33467
|
+
}
|
|
33468
|
+
async function registerSkillLinks(sourceDirectory, env) {
|
|
33469
|
+
if (env.OBI_SKILL_LINKS === "off") return { state: "off" };
|
|
33470
|
+
try {
|
|
33471
|
+
const source = await realpath(sourceDirectory);
|
|
33472
|
+
let present3 = true;
|
|
33473
|
+
for (const directory of skillDirectories) {
|
|
33474
|
+
try {
|
|
33475
|
+
if (await realpath(join6(homedir2(), directory, "obi")) !== source) present3 = false;
|
|
33476
|
+
} catch {
|
|
33477
|
+
present3 = false;
|
|
33478
|
+
}
|
|
33479
|
+
}
|
|
33480
|
+
if (present3) return { state: "present" };
|
|
33481
|
+
await installSkill(sourceDirectory);
|
|
33482
|
+
return { state: "created" };
|
|
33483
|
+
} catch (error51) {
|
|
33484
|
+
return { state: "failed", reason: error51 instanceof CliError ? error51.message : "The Obi Skill could not be registered." };
|
|
33485
|
+
}
|
|
33486
|
+
}
|
|
33207
33487
|
|
|
33208
33488
|
// src/transport.ts
|
|
33209
33489
|
var loopbackOrigin = /^http:\/\/(127\.0\.0\.1|\[::1\]):([1-9][0-9]{0,4})$/;
|
|
@@ -33328,7 +33608,7 @@ async function fetchTestStatus(origin, timeoutMs) {
|
|
|
33328
33608
|
}
|
|
33329
33609
|
|
|
33330
33610
|
// src/commands.ts
|
|
33331
|
-
var usage =
|
|
33611
|
+
var usage = 'obi <business|capabilities|credentials|doctor|eval|harness|login|logout|memory|questions|readiness|research|skill|status|strategies|support> [--json]\n obi support "<message>" [--tag market-request|scope-request] [--json]\n obi eval <start <domain>|list> [--json]\n Evaluation commands: add --eval-case <id> to status, business, harness, readiness, questions and strategies\n obi harness coverage [--json]\n obi harness activate --free [--json]\n obi harness show --stage <business|questions|strategies> [--packs <1-40> for questions review] [--json]\n obi harness submit --stage <business|questions|strategies> --input <file> [--json]\n obi harness outcomes --task-id <UUID> [--cursor <event-id>] [--limit <1-50>] [--json]\n obi harness observe --input <file> [--json]\n obi harness purchase <quote|prepare|status|resume|change> [--input <quote-and-basket.json>; resume: quote ID only] [--confirm-checkout for prepare] [--confirm-change for change] [--json]\n obi harness artifact <save|show|history|export> --work-id <UUID> [--input <JSON file>] [--revision <n>] [--output <new file>] [--cursor <opaque>] [--limit <1-100>] [--json]\n obi harness evidence --work-id <UUID> [--kind claims|sections|measurements] [--cursor <opaque>] [--query <text>] [--url <exact URL>] [--all --output <file>] [--json]\n obi research crawl <domain> --checkpoint <file> --output <file> [--batch-size <1-100>] [--locale <tag>] [--pages priority|all] [--max-pages <n|none>] [--retry-failed|--refresh] [--json]\n obi research capture --input <selected-page-file> --checkpoint <file> --output <corpus-file> [--json]\n obi research readiness --input <corpus-file> --output <report-file> [--json]\n obi research video <url> --output <directory> [--frames <0-30>] [--json]\n obi memory <list|add --kind <preference|constraint|decision> --text <words>|remove <id>> [--eval-case <id>] [--json]\n obi questions show [--json]\n obi questions prepare --confirm-preparation [--json]\n obi questions edit <id> --candidate-version <n> --text <wording> [--json]\n obi questions proceed --candidate-version <n> --confirm-scan [--json]\n obi questions results [--run latest|first|<run ID>] [--from <question position>] [--json]\n obi readiness [--json]\n obi strategies [--json]\n obi skill <path|install> [--json]\n obi credentials <list|revoke <credentialId>> [--force]\n obi business start --confirm-domain <domain>\n obi business show\n obi business discover --snapshot <token> --confirm-discovery\n obi business clarify-identity --snapshot <token> --company <name>\n obi business clarify-fact <itemId> --snapshot <token> --field-path <path> --text <answer> --company <current name> --company-wide [--segment-name <name> for segments.append]\n obi business challenge <itemId> --snapshot <token> --reason <reason>\n obi business correct <itemId> --snapshot <token> --claim-id <id> --field-path <path> --text <affirmative correction> --company <current name> --company-wide\n obi business remove <competitorItemId> --snapshot <token> [--reason <reason>]\n obi business <add-competitor|remove-competitor|readd-competitor> <domain> --snapshot <token> [--reason <reason>]\n obi business confirm --snapshot <token> --confirm-review\n Global options: --json, --timeout-ms <100-30000>; login: --expect-email <email> [--storage file]; status/doctor test: --test-server <origin>';
|
|
33332
33612
|
function success2(command, mode, data) {
|
|
33333
33613
|
return cliResultSchema.parse({ schemaVersion: 1, command, mode, ok: true, data });
|
|
33334
33614
|
}
|
|
@@ -33355,14 +33635,19 @@ function statusResult(status) {
|
|
|
33355
33635
|
}
|
|
33356
33636
|
return success2("status", "test", status);
|
|
33357
33637
|
}
|
|
33358
|
-
async function localDoctor(runtime) {
|
|
33638
|
+
async function localDoctor(runtime, timeoutMs) {
|
|
33359
33639
|
const located = await locateCredential(runtime);
|
|
33640
|
+
const updates = await runtime.updates;
|
|
33360
33641
|
return success2("doctor", "local", {
|
|
33361
33642
|
node: process.version,
|
|
33362
33643
|
cliVersion,
|
|
33644
|
+
latestVersion: updates?.state === "on" && updates.reached ? updates.latest : null,
|
|
33645
|
+
lastKnownVersion: updates?.state === "on" && !updates.reached ? updates.latest : null,
|
|
33646
|
+
updateCheck: updates?.state ?? "off",
|
|
33363
33647
|
origin: runtime.origin.origin,
|
|
33364
33648
|
credentialStorage: located === null ? "none" : located.store.backend.kind,
|
|
33365
33649
|
videoTools: await videoTools(),
|
|
33650
|
+
server: await probeServer(runtime.origin, timeoutMs),
|
|
33366
33651
|
diagnostic: "completed"
|
|
33367
33652
|
});
|
|
33368
33653
|
}
|
|
@@ -33375,7 +33660,7 @@ async function executeCommand(args, runtime) {
|
|
|
33375
33660
|
if (args.command === "help") {
|
|
33376
33661
|
return success2("help", "local", {
|
|
33377
33662
|
usage,
|
|
33378
|
-
commands: ["business", "capabilities", "credentials", "doctor", "eval", "harness", "login", "logout", "memory", "questions", "readiness", "research", "skill", "status", "strategies"]
|
|
33663
|
+
commands: ["business", "capabilities", "credentials", "doctor", "eval", "harness", "login", "logout", "memory", "questions", "readiness", "research", "skill", "status", "strategies", "support"]
|
|
33379
33664
|
});
|
|
33380
33665
|
}
|
|
33381
33666
|
if (args.command === "version") {
|
|
@@ -33384,6 +33669,13 @@ async function executeCommand(args, runtime) {
|
|
|
33384
33669
|
if (args.command === "capabilities") {
|
|
33385
33670
|
return success2("capabilities", "local", {
|
|
33386
33671
|
version: cliVersion,
|
|
33672
|
+
measurementMarkets: SUPPORTED_MARKETS,
|
|
33673
|
+
catalog: { version: CLI_DOLLAR_CATALOG_VERSION, currency: "usd", monthlyCents: {
|
|
33674
|
+
corePack: CLI_DOLLAR_PRICES.core,
|
|
33675
|
+
claudePerPack: CLI_DOLLAR_PRICES.claude,
|
|
33676
|
+
perplexityPerPack: CLI_DOLLAR_PRICES.perplexity
|
|
33677
|
+
} },
|
|
33678
|
+
support: { email: SUPPORT_EMAIL },
|
|
33387
33679
|
realLogin: "available",
|
|
33388
33680
|
paidExecution: "server_authorized",
|
|
33389
33681
|
expertise: { manifestVersion: AI_SEARCH_COMPETENCY_VERSION, competencies: AI_SEARCH_COMPETENCIES },
|
|
@@ -33402,13 +33694,14 @@ async function executeCommand(args, runtime) {
|
|
|
33402
33694
|
skill: { actions: ["path", "install"], options: ["--json"] },
|
|
33403
33695
|
status: { options: ["--json", "--eval-case", "--test-server", "--timeout-ms"] },
|
|
33404
33696
|
readiness: { options: ["--json", "--eval-case", "--timeout-ms"] },
|
|
33405
|
-
strategies: { options: ["--json", "--eval-case", "--timeout-ms"] }
|
|
33697
|
+
strategies: { options: ["--json", "--eval-case", "--timeout-ms"] },
|
|
33698
|
+
support: { tags: [...SUPPORT_TAGS], options: ["--json", "--tag", "--timeout-ms"] }
|
|
33406
33699
|
}
|
|
33407
33700
|
});
|
|
33408
33701
|
}
|
|
33409
33702
|
if (args.command === "doctor") {
|
|
33410
33703
|
if (args.testServer === void 0) {
|
|
33411
|
-
return localDoctor(runtime);
|
|
33704
|
+
return localDoctor(runtime, args.timeoutMs);
|
|
33412
33705
|
}
|
|
33413
33706
|
const status = await fetchTestStatus(args.testServer, args.timeoutMs);
|
|
33414
33707
|
return success2("doctor", "test", {
|
|
@@ -33418,7 +33711,7 @@ async function executeCommand(args, runtime) {
|
|
|
33418
33711
|
});
|
|
33419
33712
|
}
|
|
33420
33713
|
if (args.command === "skill") {
|
|
33421
|
-
const path =
|
|
33714
|
+
const path = fileURLToPath2(new URL("../skills/obi/SKILL.md", import.meta.url));
|
|
33422
33715
|
try {
|
|
33423
33716
|
await access(path);
|
|
33424
33717
|
} catch {
|
|
@@ -33431,7 +33724,7 @@ async function executeCommand(args, runtime) {
|
|
|
33431
33724
|
mode: "local"
|
|
33432
33725
|
});
|
|
33433
33726
|
}
|
|
33434
|
-
return success2("skill", "local", args.skillAction === "install" ? { action: "install", ...await installSkill(
|
|
33727
|
+
return success2("skill", "local", args.skillAction === "install" ? { action: "install", ...await installSkill(fileURLToPath2(new URL("../skills/obi", import.meta.url))) } : { action: "path", path });
|
|
33435
33728
|
}
|
|
33436
33729
|
if (args.command === "login") {
|
|
33437
33730
|
return success2("login", "product", await login(session, {
|
|
@@ -33488,6 +33781,9 @@ async function executeCommand(args, runtime) {
|
|
|
33488
33781
|
}
|
|
33489
33782
|
) : args.researchAction === "video" ? await readVideo(args.researchVideoUrl, args.researchOutput, args.researchFrames ?? 0) : args.researchAction === "capture" ? await captureResearch(args.researchInput, args.researchCheckpoint, args.researchOutput) : await assessResearch(args.researchInput, args.researchOutput));
|
|
33490
33783
|
}
|
|
33784
|
+
if (args.command === "support") {
|
|
33785
|
+
return success2("support", "product", await sendSupport(session, { message: args.supportMessage, tag: args.supportTag ?? null }));
|
|
33786
|
+
}
|
|
33491
33787
|
if (args.command === "memory") {
|
|
33492
33788
|
if (args.memoryAction === "list") return success2("memory", "product", await listMemory(session));
|
|
33493
33789
|
return success2("memory", "product", await changeMemory(session, args.memoryAction === "add" ? { op: "add", kind: args.memoryKind, text: args.memoryText } : { op: "remove", id: args.memoryEntryId }));
|
|
@@ -33540,23 +33836,126 @@ async function executeCommand(args, runtime) {
|
|
|
33540
33836
|
return success2("status", "product", await readStatus(session));
|
|
33541
33837
|
}
|
|
33542
33838
|
|
|
33839
|
+
// src/start.ts
|
|
33840
|
+
import { join as join7 } from "node:path";
|
|
33841
|
+
var linkStates = {
|
|
33842
|
+
created: "created",
|
|
33843
|
+
present: "present",
|
|
33844
|
+
off: "off (OBI_SKILL_LINKS), run obi skill install"
|
|
33845
|
+
};
|
|
33846
|
+
function renderStart(input) {
|
|
33847
|
+
const links = input.registration.state === "failed" ? `failed. ${sanitizeHumanText(input.registration.reason)}` : `${linkStates[input.registration.state]}.`;
|
|
33848
|
+
const account2 = input.accountEmail === null ? [
|
|
33849
|
+
"Account: not connected. Connect: obi login --expect-email <the user's email> (prints an address and a code for the user).",
|
|
33850
|
+
"Then: obi status --json"
|
|
33851
|
+
] : [`Account: connected as ${sanitizeHumanText(input.accountEmail)}. Its state: obi status --json`];
|
|
33852
|
+
return [
|
|
33853
|
+
`Obi CLI ${cliVersion}, npm package @hireobi/cli. Skill links: ${links}`,
|
|
33854
|
+
`Agent guide, a local file in this package: ${sanitizeHumanText(join7(input.skillDirectory, "SKILL.md"))}`,
|
|
33855
|
+
...account2,
|
|
33856
|
+
input.registration.state === "created" || input.registration.state === "present" ? "In the agent: /obi (Claude Code, Cursor), $obi (Codex) or @obi (OpenCode); /reload-skills in Claude Code if /obi is missing." : "In the agent: /obi, $obi and @obi exist once the Skill links do; until then, read the guide above.",
|
|
33857
|
+
'The obi command comes only from npm i -g @hireobi/cli; the npm package named "obi" is unrelated.'
|
|
33858
|
+
].join("\n") + "\n";
|
|
33859
|
+
}
|
|
33860
|
+
|
|
33861
|
+
// src/update-check.ts
|
|
33862
|
+
import { readFile as readFile9, writeFile as writeFile4 } from "node:fs/promises";
|
|
33863
|
+
import { join as join8 } from "node:path";
|
|
33864
|
+
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
33865
|
+
var UPDATE_CHECK_TIMEOUT_MS = 1500;
|
|
33866
|
+
var releaseSchema = external_exports.string().max(32).refine((value) => parseRelease(value) !== null);
|
|
33867
|
+
var cacheSchema = external_exports.object({
|
|
33868
|
+
checkedAt: external_exports.iso.datetime(),
|
|
33869
|
+
latest: releaseSchema.nullable()
|
|
33870
|
+
});
|
|
33871
|
+
var cacheFile = (stateDirectory) => join8(stateDirectory, "update-check.json");
|
|
33872
|
+
var npmRegistry = "https://registry.npmjs.org";
|
|
33873
|
+
var loopbackRegistry = /^http:\/\/(127\.0\.0\.1|\[::1\]):[1-9][0-9]{0,4}$/;
|
|
33874
|
+
function updateCheckDisabled(env) {
|
|
33875
|
+
const on = (value) => value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
|
|
33876
|
+
return on(env.OBI_NO_UPDATE_CHECK) || on(env.CI);
|
|
33877
|
+
}
|
|
33878
|
+
async function fetchLatestVersion(env, timeoutMs) {
|
|
33879
|
+
const override = env.OBI_UPDATE_REGISTRY;
|
|
33880
|
+
const registry2 = override !== void 0 && loopbackRegistry.test(override) ? override : npmRegistry;
|
|
33881
|
+
try {
|
|
33882
|
+
const response = await exchange({
|
|
33883
|
+
url: new URL("/-/package/@hireobi%2fcli/dist-tags", registry2),
|
|
33884
|
+
method: "GET",
|
|
33885
|
+
headers: { accept: "application/json" },
|
|
33886
|
+
timeoutMs,
|
|
33887
|
+
responseLimitBytes: 16 * 1024
|
|
33888
|
+
});
|
|
33889
|
+
if (response.status !== 200) return null;
|
|
33890
|
+
const parsed = external_exports.object({ latest: releaseSchema }).safeParse(JSON.parse(response.body));
|
|
33891
|
+
return parsed.success ? parsed.data.latest : null;
|
|
33892
|
+
} catch {
|
|
33893
|
+
return null;
|
|
33894
|
+
}
|
|
33895
|
+
}
|
|
33896
|
+
async function readCache(stateDirectory) {
|
|
33897
|
+
try {
|
|
33898
|
+
const parsed = cacheSchema.safeParse(JSON.parse(await readFile9(cacheFile(stateDirectory), "utf8")));
|
|
33899
|
+
return parsed.success ? parsed.data : null;
|
|
33900
|
+
} catch {
|
|
33901
|
+
return null;
|
|
33902
|
+
}
|
|
33903
|
+
}
|
|
33904
|
+
async function writeCache(stateDirectory, cache) {
|
|
33905
|
+
try {
|
|
33906
|
+
await ensureStateDirectory(stateDirectory);
|
|
33907
|
+
await writeFile4(cacheFile(stateDirectory), JSON.stringify(cache), { mode: 384 });
|
|
33908
|
+
} catch {
|
|
33909
|
+
}
|
|
33910
|
+
}
|
|
33911
|
+
async function checkForUpdate(input) {
|
|
33912
|
+
if (updateCheckDisabled(input.env) || parseRelease(input.installed) === null) return { state: "off" };
|
|
33913
|
+
try {
|
|
33914
|
+
const now = (input.now ?? Date.now)();
|
|
33915
|
+
const cache = await readCache(input.stateDirectory);
|
|
33916
|
+
const age = cache === null ? Infinity : now - Date.parse(cache.checkedAt);
|
|
33917
|
+
let latest = cache?.latest ?? null;
|
|
33918
|
+
let reached = true;
|
|
33919
|
+
if (input.fresh === true || !(age >= 0 && age < UPDATE_CHECK_INTERVAL_MS)) {
|
|
33920
|
+
const fetched = await (input.fetchLatest ?? ((timeoutMs) => fetchLatestVersion(input.env, timeoutMs)))(UPDATE_CHECK_TIMEOUT_MS);
|
|
33921
|
+
reached = fetched !== null;
|
|
33922
|
+
latest = fetched ?? latest;
|
|
33923
|
+
await writeCache(input.stateDirectory, { checkedAt: new Date(now).toISOString(), latest });
|
|
33924
|
+
}
|
|
33925
|
+
const notice = latest !== null && isOlderRelease(input.installed, latest) ? { installed: input.installed, latest, command: CLI_UPDATE_COMMAND } : null;
|
|
33926
|
+
return { state: "on", latest, reached, notice };
|
|
33927
|
+
} catch {
|
|
33928
|
+
return { state: "on", latest: null, reached: false, notice: null };
|
|
33929
|
+
}
|
|
33930
|
+
}
|
|
33931
|
+
function updateNoticeLine(notice) {
|
|
33932
|
+
return `Obi CLI ${notice.latest} is available (you have ${notice.installed}). Update with: ${notice.command}
|
|
33933
|
+
`;
|
|
33934
|
+
}
|
|
33935
|
+
|
|
33543
33936
|
// src/welcome.ts
|
|
33544
33937
|
var copy = {
|
|
33545
33938
|
en: {
|
|
33546
33939
|
tagline: "Your AI Search employee.",
|
|
33547
33940
|
greeting: "Hi, I'm Obi. Let's get you discovered.",
|
|
33548
|
-
|
|
33941
|
+
ready: "I'm in your assistants: Claude Code, Codex, Cursor and OpenCode.",
|
|
33942
|
+
off: "Get started: obi skill install",
|
|
33943
|
+
failed: "I could not join your assistants.",
|
|
33549
33944
|
claude: "Claude Code / Cursor: /obi",
|
|
33550
33945
|
codex: "Codex: $obi or the skills picker",
|
|
33946
|
+
opencode: "OpenCode: @obi",
|
|
33551
33947
|
next: "Then give me your website in your assistant's chat.",
|
|
33552
33948
|
help: "All commands: obi --help"
|
|
33553
33949
|
},
|
|
33554
33950
|
fr: {
|
|
33555
33951
|
tagline: "Ton agent AI Search.",
|
|
33556
33952
|
greeting: "Salut, moi c'est Obi. Faisons conna\xEEtre ton entreprise.",
|
|
33557
|
-
|
|
33953
|
+
ready: "Je suis dans tes assistants : Claude Code, Codex, Cursor et OpenCode.",
|
|
33954
|
+
off: "Pour d\xE9marrer : obi skill install",
|
|
33955
|
+
failed: "Je n'ai pas pu rejoindre tes assistants.",
|
|
33558
33956
|
claude: "Claude Code / Cursor : /obi",
|
|
33559
33957
|
codex: "Codex : $obi ou le s\xE9lecteur de skills",
|
|
33958
|
+
opencode: "OpenCode : @obi",
|
|
33560
33959
|
next: "Donne-moi ensuite ton site dans ton assistant.",
|
|
33561
33960
|
help: "Toutes les commandes : obi --help"
|
|
33562
33961
|
}
|
|
@@ -33610,7 +34009,7 @@ function mascotLine(row, depth) {
|
|
|
33610
34009
|
return `${color(top, false, depth)}${color(bottom, true, depth)}\u2580`;
|
|
33611
34010
|
}).join("") + reset;
|
|
33612
34011
|
}
|
|
33613
|
-
function renderWelcome(env, columns, colorDepth) {
|
|
34012
|
+
function renderWelcome(env, columns, colorDepth, registration) {
|
|
33614
34013
|
const locale = [env.OBI_LANGUAGE, env.LC_ALL, env.LC_MESSAGES, env.LANG].find((value) => value?.trim())?.trim().toLowerCase() ?? "en";
|
|
33615
34014
|
const text2 = copy[/^fr(?:[-_.@]|$)/.test(locale) ? "fr" : "en"];
|
|
33616
34015
|
const colorful = columns >= 64 && colorDepth >= 8 && env.TERM !== "dumb" && !env.NO_COLOR;
|
|
@@ -33619,14 +34018,16 @@ function renderWelcome(env, columns, colorDepth) {
|
|
|
33619
34018
|
return ` ${mascotLine(row, colorDepth)} ${aside}`;
|
|
33620
34019
|
}).join("\n") : `Obi \xB7 ${cliVersion}
|
|
33621
34020
|
${text2.tagline}`;
|
|
34021
|
+
const skill = registration.state === "failed" ? `${text2.failed} ${sanitizeHumanText(registration.reason)}` : registration.state === "off" ? text2.off : text2.ready;
|
|
33622
34022
|
return `
|
|
33623
34023
|
${title}
|
|
33624
34024
|
|
|
33625
34025
|
${text2.greeting}
|
|
33626
34026
|
|
|
33627
|
-
${
|
|
34027
|
+
${skill}
|
|
33628
34028
|
${text2.claude}
|
|
33629
34029
|
${text2.codex}
|
|
34030
|
+
${text2.opencode}
|
|
33630
34031
|
|
|
33631
34032
|
${text2.next}
|
|
33632
34033
|
${text2.help}
|
|
@@ -33648,22 +34049,37 @@ function failureResult(error51, command, mode) {
|
|
|
33648
34049
|
nextAction: error51.nextAction
|
|
33649
34050
|
},
|
|
33650
34051
|
authorization: error51.authorization,
|
|
33651
|
-
account: error51.account
|
|
34052
|
+
account: error51.account,
|
|
34053
|
+
support: error51.support
|
|
33652
34054
|
});
|
|
33653
34055
|
}
|
|
33654
34056
|
function markOrigin(result, origin) {
|
|
33655
34057
|
return origin?.development === true ? cliResultSchema.parse({ ...result, origin: origin.origin, development: true }) : result;
|
|
33656
34058
|
}
|
|
34059
|
+
async function updateNotice(updates) {
|
|
34060
|
+
const check3 = await updates;
|
|
34061
|
+
return check3?.state === "on" ? check3.notice : null;
|
|
34062
|
+
}
|
|
34063
|
+
function withUpdate(result, notice) {
|
|
34064
|
+
return notice === null ? result : cliResultSchema.parse({ ...result, update: notice });
|
|
34065
|
+
}
|
|
34066
|
+
async function announceUpdate(updates) {
|
|
34067
|
+
const notice = await updateNotice(updates);
|
|
34068
|
+
if (notice !== null) process.stderr.write(updateNoticeLine(notice));
|
|
34069
|
+
}
|
|
33657
34070
|
async function runCli() {
|
|
33658
34071
|
const argv = process.argv.slice(2);
|
|
33659
34072
|
const json2 = wantsJson(argv);
|
|
33660
34073
|
const welcome = argv.length === 0 && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
34074
|
+
const start = argv.length === 0 && !welcome;
|
|
34075
|
+
const registration = await registerSkillLinks(packagedSkillDirectory(), process.env);
|
|
33661
34076
|
let command = "unknown";
|
|
33662
34077
|
let mode = commandMode(
|
|
33663
34078
|
void 0,
|
|
33664
34079
|
argv.some((value) => value === "--test-server" || value.startsWith("--test-server="))
|
|
33665
34080
|
);
|
|
33666
34081
|
let origin;
|
|
34082
|
+
let updates;
|
|
33667
34083
|
let warned = false;
|
|
33668
34084
|
const activeOrigin = () => {
|
|
33669
34085
|
const resolved = resolveOrigin(process.env);
|
|
@@ -33675,14 +34091,24 @@ async function runCli() {
|
|
|
33675
34091
|
return resolved;
|
|
33676
34092
|
};
|
|
33677
34093
|
try {
|
|
33678
|
-
const args = parseCommandArguments(welcome ? ["--help"] : argv);
|
|
34094
|
+
const args = parseCommandArguments(welcome || start ? ["--help"] : argv);
|
|
33679
34095
|
command = args.command;
|
|
33680
34096
|
mode = commandMode(args.command, args.testServer !== void 0);
|
|
33681
34097
|
origin = activeOrigin();
|
|
33682
|
-
const
|
|
33683
|
-
|
|
33684
|
-
|
|
33685
|
-
|
|
34098
|
+
const stateDirectory = await prepareStateDirectory(process.env);
|
|
34099
|
+
updates = checkForUpdate({ env: process.env, stateDirectory, installed: cliVersion, fresh: command === "doctor" });
|
|
34100
|
+
const runtime = { origin, stateDirectory, updates };
|
|
34101
|
+
if (start) {
|
|
34102
|
+
const located = await locateCredential(runtime, "skip");
|
|
34103
|
+
process.stdout.write(renderStart({
|
|
34104
|
+
registration,
|
|
34105
|
+
skillDirectory: packagedSkillDirectory(),
|
|
34106
|
+
accountEmail: located === null ? null : located.credential.account.email
|
|
34107
|
+
}));
|
|
34108
|
+
await announceUpdate(updates);
|
|
34109
|
+
return;
|
|
34110
|
+
}
|
|
34111
|
+
const result = await executeCommand(args, runtime);
|
|
33686
34112
|
const evaluationCaseId = args.evaluationCaseId;
|
|
33687
34113
|
const scopeNext = (data) => ({
|
|
33688
34114
|
...data,
|
|
@@ -33694,9 +34120,11 @@ async function runCli() {
|
|
|
33694
34120
|
...result.ok && result.command === "status" && result.mode === "product" ? { data: scopeNext(result.data) } : result.ok && result.command === "business" && result.data.action === "start" ? { data: scopeNext(result.data) } : {}
|
|
33695
34121
|
}) : result;
|
|
33696
34122
|
if (scoped.ok && welcome) {
|
|
33697
|
-
process.stdout.write(renderWelcome(process.env, process.stdout.columns, process.stdout.getColorDepth()));
|
|
34123
|
+
process.stdout.write(renderWelcome(process.env, process.stdout.columns, process.stdout.getColorDepth(), registration));
|
|
34124
|
+
await announceUpdate(updates);
|
|
33698
34125
|
} else {
|
|
33699
|
-
writeResult(markOrigin(scoped, origin), args.json);
|
|
34126
|
+
writeResult(withUpdate(markOrigin(scoped, origin), args.json ? await updateNotice(updates) : null), args.json);
|
|
34127
|
+
if (!args.json) await announceUpdate(updates);
|
|
33700
34128
|
}
|
|
33701
34129
|
} catch (error51) {
|
|
33702
34130
|
if (origin === void 0) {
|
|
@@ -33712,7 +34140,15 @@ async function runCli() {
|
|
|
33712
34140
|
retryable: false,
|
|
33713
34141
|
nextAction: "Run the command again or inspect the local CLI installation."
|
|
33714
34142
|
});
|
|
33715
|
-
|
|
34143
|
+
const failure2 = failureResult(knownError, knownError.command ?? command, mode);
|
|
34144
|
+
if (failure2.mode !== "test" && failure2.command !== "support" && knownError.code !== "AUTHORIZATION_PENDING") {
|
|
34145
|
+
await prepareStateDirectory(process.env).then((directory) => recordLastError(
|
|
34146
|
+
directory,
|
|
34147
|
+
{ code: knownError.code, command: failure2.command }
|
|
34148
|
+
), () => void 0);
|
|
34149
|
+
}
|
|
34150
|
+
writeResult(withUpdate(markOrigin(failure2, origin), json2 ? await updateNotice(updates) : null), json2);
|
|
34151
|
+
if (!json2) await announceUpdate(updates);
|
|
33716
34152
|
process.exitCode = knownError.exitCode;
|
|
33717
34153
|
} finally {
|
|
33718
34154
|
closeIdleConnections();
|